11

I am trying to build an API that would allow understanding whether or not a CLLocation represents land or not. I need this to work offline as I expect most of my users not to have connectivity. I'm using MapBox as a tile server but this is still a MapKit question because I'm not using the MapBox SDK.

I've tried several approaches to figuring out if a given coordinate represents a land or ocean location:

  • Offline database of coordinates that roughly make up the world's coastline. Still a problem to figure out whether or not a given point is inside or outside the contour.
  • Color analysis of a png tile resource (there MUST be a better way! Also requires a lot of offline data to be available in order to be an effective approach)

Also (after the above is dealt with) is there an effective way to decide given a tile coordinate (x,y,z) whether or not it's a land/sea/coast tile?

If anyone has ever struggled with this issue, I'd appreciate some advise here.

Umur Kontacı
  • 35,403
  • 8
  • 73
  • 96
Stavash
  • 14,244
  • 5
  • 52
  • 80
  • Why is it a problem to determine if a point is within or outside of a closed curve? The standard approach is, I believe, to draw a half line from the point to infinity, and count how often is crosses the curve. If this number is odd, the point is within. – Reinhard Männer Dec 28 '14 at 15:42
  • Thanks @ReinhardMänner, I'm currently struggling with creating the polygon given a set of singular points. Also, do you have a working example that demonstrates what you suggest? – Stavash Dec 28 '14 at 15:59
  • I don't have working code, but maybe you can find more info at – Reinhard Männer Dec 28 '14 at 20:10
  • try to find the `altitude` .. if it’s 0… then in 99% cases there is no land – TonyMkenu Dec 29 '14 at 22:06
  • @TonyMkenu is the altitude available offline? – Stavash Dec 30 '14 at 08:03
  • To find the border of a point set is more complex than it seems. It is very nicely described in the link below, unfortunately without an algorithm. Maybe this helps a little. http://www.google.de/url?sa=t&rct=j&q=&esrc=s&source=web&cd=2&ved=0CEIQFjAB&url=http%3A%2F%2Fwww.geosensor.net%2Fpapers%2Fgalton06.GISCIENCE.ppt&ei=s4xdUf_BMqT-4QSmrICADw&usg=AFQjCNHKBdBDxQt9G29_l2QW2yd-AxRXXQ&bvm=bv.44770516,d.bGE – Reinhard Männer Jan 03 '15 at 14:00
  • Thanks @ReinhardMänner the presentation is very insightful. Also it demonstrates how far I am from a concrete solution :) – Stavash Jan 03 '15 at 18:41
  • 2
    I've provided an answer I hope is helpful, although I don't have source to hand, sorry. I'd like to ask though – don't you think people needing an App to tell them whether they are at sea or not, are likely to have more serious issues than an App is likely to solve? :-) – Benjohn Jan 04 '15 at 23:42
  • upvote for a neat question – Paul Cezanne Jan 04 '15 at 23:45
  • Why try a complex approach when Apple has already given us tools? (check my answer) – Jerome Diaz Jan 09 '15 at 09:45

4 Answers4

13

Don't worry, seems like Apple has already thought about the question!

If you take a look at the CLGeocoder class (CoreLocation), there is a reverseGeocodeLocation:completionHandler: method.

In the completion handler you can retrieve an array of CLPlacemark objects

CLPlacemark has two interesting properties:

[placemark inlandWater]

For coordinates that lie over an inland body of water, this property contains the name of that water body—the name of a lake, stream, river, or other waterway.

and [placemark ocean]

coordinates that lie over an ocean, this property contains the name of the ocean.

So you only have to reverse-geocode your location and check if ocean property is set on resulting CLPlacemark objects.

Benjohn
  • 13,228
  • 9
  • 65
  • 127
Jerome Diaz
  • 1,746
  • 8
  • 15
  • That's great, and a tidy solution. Much better than my algorithms you could write! It's a shame that the `CLPlacemark` doesn't include the local tine zone offset – that'd be sweet. – Benjohn Jan 10 '15 at 17:40
  • Thanks Jerome, is there any way I can achieve this while offline? – Stavash Jan 11 '15 at 13:42
  • The `CLGeocoder` API mentioned in this answer is online only. – Benjohn Jan 11 '15 at 20:29
  • For offline mode, you would have to create your own database using the GLGeocoder method, let's start with lat x = -90, try for long y = -180 to long y = +180 incrementing long by, let's say 0.5 each time. Then increment lat and restart. This will gives you a huge database, but don't worry. For each tried latitude we will try to define "ranges" of "above ocean" longitudes. Example : for latitude -40, you may have 3 ranges [-180; -100] [-60; +40] [+100;+180]. – Jerome Diaz Jan 12 '15 at 08:55
  • It's those ranges you should keep on a database. In your app, when testing a coordinates, rounds the latitude to have one value present in the database (round to 0.5 with this example), and test whether or not your longitude is inside one of the ranges for this latitude – Jerome Diaz Jan 12 '15 at 08:56
  • Interesting approach, I'll see if I have the resources to try it out. – Stavash Jan 12 '15 at 13:28
  • I _think_ the geocoding APIs throttle look ups to 10s a minute, so database creation may take a while. – Benjohn Jan 13 '15 at 14:59
6

I spent a while looking for a robust algorithm to do this on the sphere for time zone look up and didn't even find good pseudocode, let alone c / c++ I was entirely happy with. I'm going to run through what I found. I'll finish with resources that could be used to put this together fairly easily.

It's Easy …On the Plane

The problem is known as "Point in Polygon".

A frequently used and simple POP algorithm is "Ray Casting". POP on the 2D plane relies on a point at infinity. On the plane this is very easy. There are an infinite number of points at infinity. Pick any one! But there is no such point on the sphere.

You can get away with this if you have a known point either inside or outside of any given query polygon. Given your use case, this is not an onerous requirement: you can easily pick any single point in the sea, and this will be outside of all of the land polygons.

The "Winding Number" POP algorithm also fails (as far as I can see) because on the sphere you can approach any edge in either of two directions.

An Algorithm

I wanted an approach that would work without an auxiliary point and without heuristics (used to generate an auxiliary point from the edge data). If I'm honest, I wanted this because I was convinced it should be possible, no because I genuinely needed it.

For your use case, you can get away with the usual ray casting algorithm and a single point known to be in the ocean, so you don't need to rely on heuristics, although they probably work fairly well anyway.

The approach that I came up with works like this…

  • You'll need to loop through your polygons yourself. For each polygon…
  • Find a great circle through your query point and through at least one edge of your polygon (a mid point between two corners will do).
  • Intersect the great circle with your polygon's edges.
  • The inside of the polygon is to your right as you walk its edges in sequence (or to the left, if you prefer). This gives each intersection point sufficient information to know on which side is the inside or outside.
  • From the nearest intersection point, you can determine if your query point is inside or outside.

Implementation Tips

If you plan on implementing this (or any of the other POP algorithms), don't try to work in sine or cosine.

Represent your points (polygon corners & query point) as unit vectors. Represent your great circles (polygon edges and the circle the query point is on) as unit vectors normal to the plane that the great is on. Use the dot product and cross product. Don't think in angles. Think in vectors.

It shouldn't be too hard – I didn't need it quite enough to implement it. Get in contact if you'd like it written freelance!

Links From Which You Could Build a Solution

The c++ boost library has a POP implementation that I also didn't like but this is largely because I'm a perfectionist – I imagine it will serve the purpose in nearly all cases.

The tz_world database contains polygons for land masses, and there is a GeoJSON variant of it. You could parse this nicely with the built in NSJSONSerialization class.

Here are some algorithms by NASA for points and spheres (I didn't like their POP though).

Community
  • 1
  • 1
Benjohn
  • 13,228
  • 9
  • 65
  • 127
0

You might look into Mapbox's UTFGrid interactivity, which can work offline (either by caching grid tiles, which are essentially text, or pre-bundling them into an MBTiles file).

Check out the Mapbox iOS Example's third tab, pictured in the README, which essentially encodes & retrieves information about which country is tapped. This is done at a pixel resolution level, pre-rasterized basically, so that you don't need massive amounts of data — it doesn't store at higher resolution because you can't touch at higher than a per-pixel resolution.

incanus
  • 5,100
  • 1
  • 13
  • 20
0

Use:

mapView.visibleFeatures(at: CGPoint, styleLayerIdentifiers: Set<String>)

See this question

François Legras
  • 308
  • 2
  • 12