Search code examples
javascripthighmaps

Manage projections in custom Highmaps


I'm generating html files from python (mainly using geopandas module) containing highmaps objects.

(The code is too long to fit here in stackoverflow as geojson are included in html files : please don't check the included code but refer to the fiddles instead).

The map datas are :

  • one geojson (directly generated from geopandas, using the .to_json() method), loaded in Departements serie as map serie. It is directly stored in the html file, using mapData option. Both crs and hc-transform properties are set manually using python.

  • one array of points (fields beeing lon, lat and name), manually generated using python ; this is loaded in cities serie as mappoint serie.

Both series are prealably converted in the same geodesic system using geopandas (which I think uses pyproj4 dll somewhere).

I got geodesics systems as proj4 strings from spatialreference, that is:

  • EPSG 2154 (official french system) : "+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs"

  • WGS84 (= epsg 4326) : "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs"

When I convert all datas in WGS 84, I get correct results (though the map looks distorded for French users) : see this fiddle.

When I convert all datas in 2154, I get serious errors (as you can see, cities are displayed erraticly, which seems to include some latitude inversion as well) ; see this fiddle.

When I keep all datas in WGS 84 and just set the hc-transform to epsg2154, I still have strange results, though I think this is the correct way referenced in the doc (I'm not 100% certain of that, as those are maps generated using distant geojson datas and I'm not used to program in javascript)...

I also tried using the x/y properties for the mappoint serie (instead of lon/lat) but this does not improve the result (though I checked datas in QGis and these coordinates are 100% right).

How should I do this ?

Code sample (please refer to fiddles for working example with geojson) :

<html>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.3.6/proj4.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://code.highcharts.com/maps/highmaps.js"></script>
<script src="https://code.highcharts.com/maps/modules/data.js"></script>
<script src="https://code.highcharts.com/maps/modules/exporting.js"></script>
<script src="https://code.highcharts.com/maps/modules/offline-exporting.js"></script>
<div id="container"></div>
<script type="text/javascript">
    Highcharts.mapChart("container", {
        title: {
            text: 'Testmap Highmaps Hauts-de-France'
        },                   

        mapNavigation: {
            enabled: true,
            buttonOptions: {
                verticalAlign: 'bottom'
            }
        },

        series: [
            {
                name: 'areas',
                type: 'map',
                mapData: {'type': 'FeatureCollection', 'features': [...], 'hc-transform': {'default': {'crs': '+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'}}, 'crs': '+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'},
            },
            {
                name: 'cities',
                type: 'mappoint',
                data: [{'lon': 727759.0000000142, 'lat': 6884382.999997055, 'name': 'Chateau-Thierry'}, ...],
                color: 'black',
                marker: {
                    radius: 2
                },
                dataLabels: {
                    align: 'left',
                    verticalAlign: 'middle'
                },
                animation: false,
                tooltip: {
                    pointFormat: '{point.name}'
                }
            },
        ]
    });
</script>
</body>
</html>

Solution

  • Ok, so this is an akward solution : I still don't understand how this could solve the problem, but it works... [EDIT : this is indeed the correct solution for the time beeing, see EDIT at the end of the post]

    What I did was :

    • use the proj4 string for ESPG2154 as previously said : "+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs"

    • set the mapData with xy coordinates in EPSG2154's system : [761574.9000000217, 6918670.299997976], [761648.2000000217, 6918469.799997974], ...

    • set the hc-transform/crs on the mapData as previously said : 'hc-transform': {'default': {'crs': '+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'}}, 'crs': '+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'

    • set the mappoint datas using x/y instead of lat/lon, without any 'hc-transform' or crs, but with y coordinates inverted : data: [{'x': 727759.0000000142, 'y': -6884382.999997055, 'name': 'Chateau-Thierry'}, ...]

    Note that :

    • I inverted the y coordinates, for the mappoint datas, and that these are not the correct coordinates in any sense ;
    • I also set yAxis to reversed: true, which should not be the default value as stated in the API's doc. But in fact it doesn't change anything if you remove this line or not : somehow, using multiple series including one mapData seems to be changing the default property (at least when using proj4 in this configuration). In fact, if you put it to reversed:false, you won't have any correct results, even if you don't invert the y coordinate as stated before.

    I think this is some kind of undocumented bug (I might be wrong still), I will try to refer if to highcharts'staff...

    See complete functional exemple in this fiddle.

    <html>
    <body>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.3.6/proj4.js"></script>
    <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    <script src="https://code.highcharts.com/maps/highmaps.js"></script>
    <script src="https://code.highcharts.com/maps/modules/data.js"></script>
    <script src="https://code.highcharts.com/maps/modules/exporting.js"></script>
    <script src="https://code.highcharts.com/maps/modules/offline-exporting.js"></script>
    <div id="container"></div>
    <script type="text/javascript">
        Highcharts.mapChart("container", {
            title: {
                text: 'Testmap Highmaps Hauts-de-France'
            },                   
    
            mapNavigation: {
                enabled: true,
                buttonOptions: {
                    verticalAlign: 'bottom'
                }
            },
    
            yAxis: {
                reversed: true
            },
    
            series: [
                {
                    name: 'areas',
                    type: 'map',
                    mapData: {'type': 'FeatureCollection', 'features': [{'id': '1', 'type': 'Feature', 'properties': {'DEP': '02'}, 'geometry': {'type': 'Polygon', 'coordinates': [[[761574.9000000217, 6918670.299997976], [761648.2000000217, 6918469.799997974], ..., [699287.6999999998, 6901218.199997955]]]}}], 'hc-transform': {'default': {'crs': '+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'}}, 'crs': '+proj=lcc +lat_1=49 +lat_2=44 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'},
                },
                {
                    name: 'cities',
                    type: 'mappoint',
                    data: [{'x': 727759.0000000142, 'y': -6884382.999997055, 'name': 'Chateau-Thierry'}, ...],
                    color: 'black',
                    marker: {
                        radius: 2
                    },
                    dataLabels: {
                        align: 'left',
                        verticalAlign: 'middle'
                    },
                    animation: false,
                    tooltip: {
                        pointFormat: '{point.name}'
                    }
                },
            ]
        });
    </script>
    </body>
    </html>
    

    EDIT

    So this is indeed a tricky problem... I have been in touch with highsoft and there seem to be multiple facts to take in consideration : - first, the yAxis.reverse=true IS the default behaviour, regardless to what is currently stated in the doc ; - secondly, some inner algorithm about mapData corrects this behaviour, because this type of layer is assumed to be GeoJSON ; - 3rdly, this is not the case for mappoint aw well as mapbubble layers.

    Notice that if you set yAxis.reverse = false, you are up for some bad time. The mappoint layers may seem to be correctly superposed with your mapData (if I understood this correctly, this have something to do with the mappoint layer's y range to be more or less alike to those of the map area.

    Highsoft has opened an issue on this subject.

    Morality : my solution was in fact the good one (at least until they decide what to do with this "issue"). As for this version of Highmaps, always work with yAxis.reverse = true, and be aware that mapDatas passed as GeoJSON are not affected by this command.