For a project I’m working on I needed to know if a point (latitude,longitude) was in a continent, and if so, which one!
To do this, first I set up a Continent enum to work with, making it a String (to easily print) and CaseIterable (to iterate through the cases later):
enum Continent:String,CaseIterable { case australia case northAmerica case southAmerica case africa case europe case asia case antarctica }
I found this stackoverflow post, which though it wasn’t written in Swift, gave some very general polygons that vaguely encompassed the seven continents, exactly what I needed:
I first set up a utility method to convert a latitude and longitude array into a GMSMutablePath. (I’m going to solve this using the Google Maps SDK)
I then used the polygons from the StackOverflow post and converted the Latitude and Longitude arrays into paths in static variables in the Continent enum:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Mapper { | |
static func getPath(lat:[CLLocationDegrees],long:[CLLocationDegrees])->GMSMutablePath { | |
let path = GMSMutablePath() | |
for i in 0..<lat.count { | |
path.add(CLLocationCoordinate2D(latitude: lat[i], longitude: long[i])) | |
} | |
return path | |
} | |
} | |
enum Continent:String,CaseIterable { | |
case australia | |
case northAmerica | |
case southAmerica | |
case africa | |
case europe | |
case asia | |
case antarctica | |
static var australiaPath:GMSMutablePath = Mapper.getPath(lat: [-11.88, -10.27, -10, -30, -52.5, -31.88],long: [110, 140, 145, 161.25, 142.5, 110]) | |
static var africaPath:GMSMutablePath = Mapper.getPath(lat: [15, 28.25, 35.42, 38, 33, 31.74, 29.54, 27.78, 11.3, 12.5, -60, -60],long: [-30, -13, -10, 10, 27.5, 34.58, 34.92, 34.46, 44.3, 52, 75, -30]) | |
static var europePath:GMSMutablePath = Mapper.getPath(lat: [90, 90, 42.5, 42.5, 40.79, 41, 40.55, 40.40, 40.05, 39.17, 35.46, 33, 38, 35.42, 28.25, 15, 57.5, 78.13],long: [-10, 77.5, 48.8, 30, 28.81, 29, 27.31, 26.75, 26.36, 25.19, 27.91, 27.5, 10, -10, -13, -30, -37.5, -10]) | |
static var asiaPath:GMSMutablePath = Mapper.getPath(lat: [90, 42.5, 42.5, 40.79, 41, 40.55, 40.4, 40.05, 39.17, 35.46, 33, 31.74, 29.54, 27.78, 11.3, 12.5, -60, -60, -31.88, -11.88, -10.27, 33.13, 51, 60, 90],long: [77.5, 48.8, 30, 28.81, 29, 27.31, 26.75, 26.36, 25.19, 27.91, 27.5, 34.58, 34.92, 34.46, 44.3, 52, 75, 110, 110, 110, 140, 140, 166.6, 180, 180]) | |
static var asia2Path:GMSMutablePath = Mapper.getPath(lat: [90, 90, 60, 60],long: [-180, -168.75, -168.75, -180]) | |
static var northAmericaPath:GMSMutablePath = Mapper.getPath(lat: [90, 90, 78.13, 57.5, 15, 15, 1.25, 1.25, 51, 60, 60],long: [-168.75, -10, -10, -37.5, -30, -75, -82.5, -105, -180, -180, -168.75]) | |
static var northAmerica2Path:GMSMutablePath = Mapper.getPath(lat: [51, 51, 60],long: [166.6, 180, 180]) | |
static var southAmericaPath:GMSMutablePath = Mapper.getPath(lat: [1.25, 1.25, 15, 15, -60, -60],long: [-105, -82.5, -75, -30, -30, -105]) | |
static var antarcticaPath:GMSMutablePath = Mapper.getPath(lat: [-60, -60, -90, -90],long: [-180, 180, 180, -180]) | |
} |
Great! Now seeing if a point is inside a polygon is as simple as comparing a CLLocationCoordinate2D
property with a GMSMutablePath
property, and you can do that with the GMSGeometryContainsLocation
function.
I set up a contains
function in the enum that checks if a continent contains the point:
func contains(point:CLLocationCoordinate2D)->Bool { switch self { case .australia: return GMSGeometryContainsLocation(point, Continent.australiaPath, true) case .northAmerica: return GMSGeometryContainsLocation(point, Continent.northAmericaPath, true) || GMSGeometryContainsLocation(point, Continent.northAmerica2Path, true) case .southAmerica: return GMSGeometryContainsLocation(point, Continent.southAmericaPath, true) case .africa: return GMSGeometryContainsLocation(point, Continent.africaPath, true) case .europe: return GMSGeometryContainsLocation(point, Continent.europePath, true) case .asia: return GMSGeometryContainsLocation(point, Continent.asiaPath, true) || GMSGeometryContainsLocation(point, Continent.asia2Path, true) case .antarctica: return GMSGeometryContainsLocation(point, Continent.antarcticaPath, true) } }
North America and Asia both have two have polgyons that represent their areas.
So now, to determine which continent contains a point, all we have to do is iterate through the cases of the Continent
enum, calling the contains
method. I set up a getContinent
method to do this:
static func getContinent(at point:CLLocationCoordinate2D)->Continent? { for continent in Continent.allCases { if continent.contains(point: point) { return continent } } return nil }
To see how this could be used, imagine you’ve set up a map view in a view controller, let’s call it MapViewController
, and we’ve set the view controller as the map view’s delegate. You could set up the mapView:didTapAt
method to be called when the user taps on the map. We could use the coordinate
property passed into the method to determine which continent the user has tapped, simply by passing the coordinate property into the Continent.getContinent
method:
extension TestMapViewController:GMSMapViewDelegate { func mapView(_ mapView: GMSMapView, didTapAt coordinate: CLLocationCoordinate2D) { if let continent = Continent.getContinent(at: coordinate) { print(continent) } }
Leave a Reply