Bugs with 180 degrees longitude

2252
4
04-18-2016 01:02 PM
carloscasanova
New Contributor III

There are "bugs" in multiple APIs handling of 180.

1. GeometryEngine.Project when converting to Web Mercator returns NaN for +/-180. It should return +/-pi*EarthWGS84Radius

2. Polyline graphics that span across the 180 line are connected the "long" way instead of the "short" way: a short line becomes one that spans the globe.

For problem 2, I understand there is a theoretical ambiguity as to which way to connect the lines. In most cases, picking the shortest path is likely to be the correct answer.

0 Kudos
4 Replies
carloscasanova
New Contributor III

I've worked through many of the issues by just avoiding the GeometryEngine and using my own implementations instead. To make lines connect correctly, I test if a simple planar connect would be too long and if so, I adjust the "left" point to be the right by adding the width of the WebMercator projection before supplying the points to the runtime.

0 Kudos
AnttiKajanus1
Regular Contributor II

Hi,

Could you provide simple reproducers for all the issues that you find with the geometry engine and attach them to this chain?

cheers,

Antti

0 Kudos
carloscasanova
New Contributor III

Hi Antti, I'll provide some examples below. There's more. I've written more than a thousand lines of workarounds last week and it is hard to keep track of it all! Another strange behavior I uncovered was the map view extent going null when the map center is set to NaN. Granted, NaN is nonesense, but SetViewAsync should complain/throw right away instead. The NaN made it to the map view because of innocent looking code like this:

static async void TestExtentAndSetView(MapView map, MapPoint point)

{

  var p = Project(NormalizeCentralMeridian(point), map.SpatialReference);

  var extent = NormalizeCentralMeridian(map.Extent);

  if (!Within(p, extent))

  {

    try { await map.SetViewAsync(point, TimeSpan.FromSeconds(1.5)); }

    catch (Exception ex) { /*log the error*/ }

  }

}

I have since switched over to my own "envelope" and "projection" code that is easier to use and get right.

GeometryEngine.Project

static MapPoint EsriProject(double lat, double lon)

{

  return (MapPoint)GeometryEngine.Project(new MapPoint(lon, lat, SpatialReferences.Wgs84), SpatialReferences.WebMercator);

}

static MapPoint EsriUnProject(double x, double y)

{

  return (MapPoint)GeometryEngine.Project(new MapPoint(x, y, SpatialReferences.WebMercator), SpatialReferences.Wgs84);

}

public void Test()

{

  Action<double> xNaN = lon => Assert.IsTrue(double.IsNaN(EsriProject(0, lon).X));

  xNaN(-180);

  xNaN(180);

  xNaN(-179.99999);

  xNaN(179.99999);

}

const double Wgs84EarthSemiMajorRadiusMeters = 6378137;

const double webMercatorMaxX = Wgs84EarthSemiMajorRadiusMeters * System.Math.PI;

var p1 = EsriUnProject(webMercatorMaxX, 0);

var p2 = EsriUnProject(-webMercatorMaxX, 0);

System.Console.WriteLine(p1); // prints MapPoint[X=NaN, Y=NaN, Wkid=4326]

System.Console.WriteLine(p2); // prints MapPoint[X=NaN, Y=NaN, Wkid=4326]

Envelopes, Polyline, etc

Envelope unions do simple min/max and do not handle the wrap around. That means that the a union operation may connect the long way across the world when the intent is the short way. I've written my own "GeoRect" code that has a well defined left and right and can handle the wraparound. In other places, I do something like this:

static void FixXsForEsri(ref double x1, ref double x2, double y1, double y2, double expectedLength)

{

  var distSqd = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);

  bool simpleConnectWouldBeTheLongWayAcrossTheWorld = distSqd > expectedLength * expectedLength * 2.0;

  if (simpleConnectWouldBeTheLongWayAcrossTheWorld)

  {

  // Map X on negative side of the projection origin to the right.

  if (x2 < 0.0)

   x2 += GeoRectMaxBounds.WebMercator.Width;

  else if (x1 < 0.0)

   x1 += GeoRectMaxBounds.WebMercator.Width;

  }

}

I can see how the way Envelope behaves could be more renderer friendly. Simple min/max-ing is less "branchy" than the wrap test code. From a map API user's view, it is quite unfriendly.

Simple Repro:

var p1 = new MapPoint(179, 0, SpatialReferences.Wgs84);

var p2 = GeometryEngine.GeodesicMove(p1, 1e3, LinearUnits.Kilometers, 90);

var e1 = new Envelope(p1, p1);

var e2 = new Envelope(p2, p2);

System.Console.WriteLine(e1.Union(e2).Width); // prints 351.016847158805 degrees !!

0 Kudos
MichaelBranscomb
Esri Frequent Contributor

Hi,

I'm investigating your questions with our geometry team.

Cheers

Mike

0 Kudos