Forum Discussion

welcomenxj's avatar
welcomenxj
Copper Contributor
Aug 26, 2020
Solved

Azure maps: Trying to draw direct line between two point at two corners of world

Am trying to plot direct line between "green arrow location" (right top corner) to "destination popup point". But it's moving all around world as shown in picture. Any suggestion? Please find image for reference.

Other lines whose points are not on world edges are working fine.

  • welcomenxj 

    Managed to reproduce the issue and after talking with one of our architects I was reminded that the GeoJSON specification does not allow shapes to cross the antemeridian. I'm not a fan of this, luckily there are a few ways around this:

    Options 1:

    • Similar to above, but break the line into segments to minimize complexity and shift longitude values outside of the -180/180 range as needed. Then create a MultiLineString from the segments.

    • You end up with a line with coordinates outside of the standard range but should render any line back and forth over the antemeridian as needed.

    Option 2:

    • Leverage a library to split the line segments on the antemeridian and create a MultiLineString.

    • This method is a lot more work and and might not be as accurate, but should also work with any line.

    Here is a code sample showing all two options:

     

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <title></title>
    
        <meta charset="utf-8" />
        <meta http-equiv="x-ua-compatible" content="IE=Edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
    
        <!-- Add references to the Azure Maps Map control JavaScript and CSS files. -->
        <link rel="stylesheet" href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.css" type="text/css" />
        <script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.js"></script>
    	
    	<!-- Load turf.js a spatial math library. https://turfjs.org/ -->
        <script src='https://npmcdn.com/@turf/turf/turf.min.js'></script>
    
        <script type='text/javascript'>
            var map, datasource;
    		
    		var data =  {
    					"type": "Feature",
    					"properties": {},
    					"geometry": {
    						"type": "LineString",
    						"coordinates": [
    							[
    								144.9586,
    								-37.848381
    							],
    							[
    								-118.1948,
    								33.767109
    							]
    						]
    					}
    				};
    
            function GetMap() {
                //Initialize a map instance.
                map = new atlas.Map('myMap', {
                    view: 'Auto',
    
                    //Add your Azure Maps subscription client ID to the map SDK. Get an Azure Maps client ID at https://azure.com/maps
                    authOptions: {
                        authType: 'subscriptionKey',
                        subscriptionKey: '<Your Azure Maps Key>'
                    }
                });
    
                //Wait until the map resources are ready.
                map.events.add('ready', function () {
                    //Create a data source to add your data to.
                    datasource = new atlas.source.DataSource();
                    map.sources.add(datasource);
    
                    //Add a layer for rendering data.
                    map.layers.add(new atlas.layer.LineLayer(datasource));
    				
    				map.setCamera({
    					bounds: atlas.data.BoundingBox.fromData(data)
    				});
    			});
            }
    				
    		function option1 () {
    			//Trick map to render one of the points on an adjacent map. Doing a deep copy of the coordinates to be safe since reusing in sample.
    			var c = data.geometry.coordinates.map(function(arr) {
    					return arr.slice();
    				});
    			
    			var lastCoord = c[0];
    			var newCoords = [];
    			var newCoord;
    						
    			for(var i = 1, len = c.length; i < len; i++){
    				let diff = lastCoord[0] - c[i][0];
    				
    				if(diff > 180){
    					//Line going left to right, but should go opposite direction.
    					newCoord = [c[i][0] + 360, c[i][1]];
    				} else if (diff < -180) {
    					//Line going right to left, but should go opposite direction.
    					newCoord = [c[i][0] - 360, c[i][1]];					
    				} else {
    					//Leave coord as is.
    					newCoord = c[i];					
    				}
    				
    				newCoords.push([lastCoord, newCoord]);
    				lastCoord = c[i];
    			}
    			
    			datasource.setShapes(new atlas.data.MultiLineString(newCoords));
    		}
    
    		function option2() {
    			//Split the line into segments and split each segment on the antimeridian, then create a MultuLineString from the segments.
    			var c = data.geometry.coordinates;
    			
    			var newCoords = [];
    			
    			for(var i = 1, len = c.length; i < len; i++){
    				newCoords = newCoords.concat(splitLineSegment(c[i - 1], c[i]));	
    			}
    			
    			datasource.setShapes(new atlas.data.MultiLineString(newCoords));
    		}
    		
    		function splitLineSegment(fromPos, toPos) {
    			var path;
    
    			//Check to see if path should cross anti-meridian.
    			if (Math.abs(fromPos[0] - toPos[0]) > 180 && Math.abs(fromPos[0]) > 90) {
    				var l1, l2;
    
    				var rightMostPos = toPos;
    				var leftMostPos = fromPos;
    
    				if (fromPos[0] > toPos[0]) {
    					rightMostPos = fromPos;
    					leftMostPos = toPos;
    				}
    
    				//Wrap the left most coordinate to a value between 180 and 540, then split on anti-meridian.
    				var fc = turf.lineSplit(
    					//Line to split.
    					turf.lineString([rightMostPos, [leftMostPos[0] + 360, leftMostPos[1]]]),
    
    					//Line to split with.
    					turf.lineString([[180, 90], [180, -90]]));
    
    				//Get the first line segment. 
    				var seg1 = fc.features[0].geometry.coordinates;								
    				shiftPositions(seg1[0], seg1[1]);
    				
    				//Its possible that there is a shared coordinate in the two line segments, do a deep copy of the second segment cooridnates to be safe.
    				var seg2 = fc.features[1].geometry.coordinates.map(function(arr) {
    					return arr.slice();
    				});
    				
    				shiftPositions(seg2[0], seg2[1]);
    			
    				return [
    					//Segment 1
    					seg1,
    					
    					//Segment 2
    					seg2
    				];			
    			} else {
    				//Only a single line segment.
    				return [[fromPos, toPos]];
    			}
    		}
    		
    		function shiftPositions(p1, p2) {
    			//If either position has a longitude value outside of the -180 to 180 range, shift the longitude value of both positions as needed.
    			
    			if(p1[0] > 180 || p2[0] > 180){
    				p1[0] -= 360;
    				p2[0] -= 360;
    			} else if(p1[0] < -180 || p2[0] < -180){
    				p1[0] += 360;
    				p2[0] += 360;
    			} 
    		}
        </script>
    </head>
    <body onload="GetMap()">
        <div id="myMap" style="position:relative;width:100%;height:600px;"></div>
    	
    	<input type="button" onclick="option1()" value="Option 1"/>
    	<input type="button" onclick="option2()" value="Option 2"/>
    </body>
    </html>
  • welcomenxj's avatar
    welcomenxj
    Copper Contributor

    I believe, i understand the issue now but still don't have solution for it. I have added 2 images 'third quadrant coordinates' and 'fourth quadrant coordinates' for your reference. See the 'position' coords in console and how they are varying when popup point is clicked, depending on its position in map.
    Why it is not returning the same co-ordinates irrespective of map position? I think answer to this will help to fix this issue

Resources