Monday, October 28, 2019

Android Studio: resolving duplicate AndroidX and support classes errors

While migrating an Android project from the support libraries to use the AndroidX libraries, I encountered the following errors regarding "duplicate class android.support.v4.app xxxx found in modules classes.jar (androidx.core:core:1.0.0) and classes.jar (com.android.support:support-compat:26.1.0)"; even though all the Java/Kotlin/XML source code files have been replaced with the AndroidX versions and the old support libraries have been removed from the app's build.gradle file.

The screenshot below illustrates the error.


The solution I found was to set project wide gradle properties.
  1. In Android Studio, open up the project's gradle.properties file.
  2. Insert the following two lines:

    android.enableJetifier=true
    android.useAndroidX=true


  3. Save the file, select Build | Clean Project. Then select recompile again.

    The duplicate classes error messages no longer appear.|

Monday, October 21, 2019

Setting and passing ROS double array parameters to a ROS C++ node

I tried to pass an array or list of double parameters to a ROS node program through the ROS rosrun program but my C++ ROS node program could not read the double array parameter. For example, the screenshot below shows the command to run a ROS node (hello_doubles) from the package beginner_tutorials and setting my_doubles_array parameter with the double list [1.1, 2.2, 3.3]:

$ rosrun beginner_tutorials hello_doubles _my_doubles_array:="[1.1,2.2,3.3]"

Note that after running the command, the ROS parameter server contains a parameter /hello_doubles/my_doubles_array with a string value of '[1.1, 2.2., 3.3]' and not the expected double array [1.1, 2.2, 3.3].

Eventually, I realized the rosrun program passes the list as a string instead of an array of doubles. In order to pass a double array parameter, the following methods could be used instead: (1) use the rosparam set command, or (2) use the rosparam load command.

Use the rosparam set command to pass a double array parameter
  1. In a Terminal, type in the following command:

    $ rosparam set /hello_doubles/my_double_array "[1.1, 2.2, 3.3]"

Use the rosparam load command to set a double array parameter
  1. Using a text editor, create a yaml file e.g. hello_doubles.yaml with the following lines:



    where my_doubles_array is the name of the double array parameter
  2. In a Terminal, type in the following command to load the yaml file:

    $ rosparam load /path/to/hello_doubles.yaml

Monday, October 14, 2019

Simple example of a ReactJS and OpenLayers map component

OpenLayers (https://openlayers.org/) is a "high-performance, feature-packed (Javascript) library for all your mapping needs" for web sites. I spent some time figuring out how to use it with ReactJS. As most of the available examples in the official documentation are for plain vanilla HTML/Javascript, I stumbled a few times trying to get it to work. This post summarises the steps to get a minimal OpenLayers - React component to work. 


Installing OpenLayers for ReactJS
Assuming a ReactJS project has been created e.g. /path/to/React/project/, the first thing is to install the prerequisite software.
  1. Install OpenLayers package for node. Open a Terminal, change directory to the project root directory and type in the following command.

    $ npm install ol --save
  2. Optional. Install Proj4Js and Material-UI.

    $ npm install proj4 @material-ui/core --save
Create the React component for OpenLayers map
Under the ReactJS project e.g. /path/to/React/project/src/components/, create the OpenLayers React component e.g. OLMapFragment.js.

Code listing of OLMapFragment.js
import React from 'react'
import Grid from '@material-ui/core/Grid'

// Start Openlayers imports
import { 
    Map,
    View
 } from 'ol'
import {
    GeoJSON,
    XYZ
} from 'ol/format'
import {
    Tile as TileLayer,
    Vector as VectorLayer
} from 'ol/layer'
import {
    Vector as VectorSource,
    OSM as OSMSource,
    XYZ as XYZSource,
    TileWMS as TileWMSSource
} from 'ol/source'
import {
    Select as SelectInteraction,
    defaults as DefaultInteractions
} from 'ol/interaction'
import { 
    Attribution,
    ScaleLine,
    ZoomSlider,
    Zoom,
    Rotate,
    MousePosition,
    OverviewMap,
    defaults as DefaultControls
} from 'ol/control'
import {
    Style,
    Fill as FillStyle,
    RegularShape as RegularShapeStyle,
    Stroke as StrokeStyle
} from 'ol/style'

import { 
    Projection,
    get as getProjection
 } from 'ol/proj'

// End Openlayers imports

class OLMapFragment extends React.Component {
    constructor(props) {
        super(props)
        this.updateDimensions = this.updateDimensions.bind(this)
    }
    updateDimensions(){
        const h = window.innerWidth >= 992 ? window.innerHeight : 400
        this.setState({height: h})
    }
    componentWillMount(){
        window.addEventListener('resize', this.updateDimensions)
        this.updateDimensions()
    }
    componentDidMount(){

        // Create an Openlayer Map instance with two tile layers
        const map = new Map({
            //  Display the map in the div with the id of map
            target: 'map',
            layers: [
                new TileLayer({
                    source: new XYZSource({
                        url: 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png',
                        projection: 'EPSG:3857'
                    })
                }),
                new TileLayer({
                    source: new TileWMSSource({
                        url: 'https://ahocevar.com/geoserver/wms',
                        params: {
                            layers: 'topp:states',
                            'TILED': true,
                        },
                        projection: 'EPSG:4326'
                    }),
                    name: 'USA'
                }),
            ],
            // Add in the following map controls
            controls: DefaultControls().extend([
                new ZoomSlider(),
                new MousePosition(),
                new ScaleLine(),
                new OverviewMap()
            ]),
            // Render the tile layers in a map view with a Mercator projection
            view: new View({
                projection: 'EPSG:3857',
                center: [0, 0],
                zoom: 2
            })
        })
    }
    componentWillUnmount(){
        window.removeEventListener('resize', this.updateDimensions)
    }
    render(){
        const style = {
            width: '100%',
            height:this.state.height,
            backgroundColor: '#cccccc',
        }
        return (
            <Grid container>
                <Grid item xs={12}>
                    <div id='map' style={style} >
                    </div>
                </Grid>
            </Grid>
        )
    }
}
export default OLMapFragment

Use the React OpenLayers map component
Now in the ReactJS project's index.js file under /path/to/React/project/src/, import in the newly created OLMapFragment component and render it.

Listing of index.js
import React from 'react'
import { render} from 'react-dom'
import OLMapFragment from './js/components/OLMapFragment'

render(
    <OLMapFragment />
    ,
    document.getElementById('react-container')
)

Create the default index.html file
Finally, include links to OpenLayer's style sheets in the project's index.html file under /path/to/React/project/src/.
Listing of index.html file
<!DOCTYPE html>
<html class="no-js" lang="en">

<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content='ie=edge'>
    <title>Openlayers React</title>
    <meta name="description" content="Explore planet Mars">
    <meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no" />
    <!-- material-ui prerequisites -->
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
    <!-- end material-ui prerequisites -->

    <link rel="stylesheet" href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css"
        type="text/css" />

</head>

<body>
    <!--[if lte IE 8]
        <p class="chromeframe">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com">upgrade your browser</a></p>
        <![endif]-->
    <div id='react-container'></div>

</body>

</html>

Now, run the ReactJS project with a web server and open the web page with an Internet browser.

The web map tiles are rendered in the React OpenLayers component.

Monday, October 7, 2019

Add Mars Web Mapping Tile Server datasets from NASA to QGIS

NASA publishes Mars dataset such as the Viking Color Mosaic or the Mars Orbiter Laser Altimeter Color Hillshade  for public access at this web site https://api.nasa.gov/api.html. The dataset are published as OGC RESTful Web Mapping and Tile Services - hence they can be pulled down and displayed in QGIS.

For convenience, the full list of the dataset extracted from the service is shown below:
Layer Url
Viking Color Mosaic - Global Map https://api.nasa.gov/mars-wmts/catalog/Mars_Viking_MDIM21_ClrMosaic_global_232m/1.0.0//default/default028mm/{z}/{y}/{x}.jpg
CTX Mosaic - Curiosity Landing Site https://api.nasa.gov/mars-wmts/catalog/curiosity_ctx_mosaic/1.0.0//default/default028mm/{z}/{y}/{x}.png
HiRISE Mosaic - Curiosity Landing Site https://api.nasa.gov/mars-wmts/catalog/curiosity_hirise_mosaic/1.0.0//default/default028mm/{z}/{y}/{x}.png
HiRISE Mosaic - ESP_040776_2115 https://api.nasa.gov/mars-wmts/catalog/ESP_040776_2115_RED_A_01_ORTHO/1.0.0//default/default028mm/{z}/{y}/{x}.png
HiRISE Mosaic - ESP_042252_1930_RED_B_01_ORTHO https://api.nasa.gov/mars-wmts/catalog/ESP_042252_1930_RED_B_01_ORTHO/1.0.0//default/default028mm/{z}/{y}/{x}.png
HiRISE Mosaic - ESP_042647_1760_RED_B_01_ORTHO https://api.nasa.gov/mars-wmts/catalog/ESP_042647_1760_RED_B_01_ORTHO/1.0.0//default/default028mm/{z}/{y}/{x}.png
HRSC Mosaic - Martian East https://api.nasa.gov/mars-wmts/catalog/HRSC_Martian_east/1.0.0//default/default028mm/{z}/{y}/{x}.png
HRSC Color Mosaic - MC11 https://api.nasa.gov/mars-wmts/catalog/MC11E_HRMOSCO_COL/1.0.0//default/default028mm/{z}/{y}/{x}.png
HRSC Mosaic - MC11 https://api.nasa.gov/mars-wmts/catalog/MC11E_HRMOSND_ND5/1.0.0//default/default028mm/{z}/{y}/{x}.png
HiRISE Mosaic - Spirit Landing Site https://api.nasa.gov/mars-wmts/catalog/spirit_hirise_mosaic/1.0.0//default/default028mm/{z}/{y}/{x}.png
HiRISE Mosaic - Opportunity Landing Site https://api.nasa.gov/mars-wmts/catalog/opportunity_hirise_mosaic/1.0.0//default/default028mm/{z}/{y}/{x}.png
HiRISE Mosaic - Phoenix Landing Site https://api.nasa.gov/mars-wmts/catalog/phoenix_hirise_mosaic/1.0.0//default/default028mm/{z}/{y}/{x}.png
HiRISE Mosaic - Sojourner Landing Site https://api.nasa.gov/mars-wmts/catalog/sojourner_hirise_mosaic/1.0.0//default/default028mm/{z}/{y}/{x}.png
Albedo Mosaic - Thermal Emission Spectrometer https://api.nasa.gov/mars-wmts/catalog/Mars_MGS_TES_Albedo_mosaic_global_7410m/1.0.0//default/default028mm/{z}/{y}/{x}.png
DEM Grayscale - Mars Orbiter Laser Altimeter https://api.nasa.gov/mars-wmts/catalog/Mars_MGS_MOLA_DEM_mosaic_global_463m_8/1.0.0//default/default028mm/{z}/{y}/{x}.png
Color Hillshade - Mars Orbiter Laser Altimeter https://api.nasa.gov/mars-wmts/catalog/Mars_MGS_MOLA_ClrShade_merge_global_463m/1.0.0//default/default028mm/{z}/{y}/{x}.jpg
Experience Curiosity - Curiosity Landing Site https://api.nasa.gov/mars-wmts/catalog/mars_pahrump_patch_8k_256m/1.0.0//default/default028mm/{z}/{y}/{x}.png
Atlas Mosaic - Mars Orbiter Camera https://api.nasa.gov/mars-wmts/catalog/msss_atlas_simp_clon/1.0.0//default/default028mm/{z}/{y}/{x}.png
Infrared Night - Thermal Emission Imaging System https://api.nasa.gov/mars-wmts/catalog/Mars_MO_THEMIS-IR-Night_mosaic_60N60S_100m_v14_clon0_ly/1.0.0//default/default028mm/{z}/{y}/{x}.jpg
Infrared Day - Thermal Emission Imaging System https://api.nasa.gov/mars-wmts/catalog/Mars_MO_THEMIS-IR-Day_mosaic_global_100m_v12_clon0_ly/1.0.0//default/default028mm/{z}/{y}/{x}.jpg
HRSC Mosaic - Mawrth Vallis https://api.nasa.gov/mars-wmts/catalog/hrsc_mawrth_vallis/1.0.0//default/default028mm/{z}/{y}/{x}.png
HRSC Color Mosaic - Mawrth Vallis https://api.nasa.gov/mars-wmts/catalog/hrsc_mawrth_vallis_color/1.0.0//default/default028mm/{z}/{y}/{x}.png

To add a MARS dataset to QGIS, do the following:
  1. Start QGIS. In the Browser panel, mouse right click on the Tile Server (XYZ). Select New Connection.


     
  2.  In the New XYZ tile layer, type in the URL for the chosen dataset, e.g. https://api.nasa.gov/mars-wmts/catalog/Mars_MGS_MOLA_ClrShade_merge_global_463m/1.0.0//default/default028mm/{z}/{y}/{x}.jpg

  3. Click OK.
  4. Type in the name of the tile layer, e.g. Color Hillshade - Mars Orbiter Laser Altimeter.

  5. Click OK.

    The tile layer connection is added to the Tile Server (XYZ) node.
  6. Mouse right click on the newly added tile server node. Choose Add layer.

    The selected layer is displayed in QGIS.