I have a KML file which defines a region (or polygon). I would like to make a function that checks if a given coordinate is inside or outside that polygon.
This is the KML if you want to take a look: http://pastebin.com/LGfn3L8H
I don't want to show any map, I just want to return a boolean.
Point-in-polygon (PiP) is a very well-studied computational geometry problem, so there are lots of algorithms and implementations out there that you can use. Searching SO will probably find several you can copy-paste, even.
There's a catch, though—you're dealing with polygons on the surface of the Earth... which is a sphere, not the infinite Euclidean plane that most PiP algorithms expect to work with. (You can, for example, have triangles whose internal angles add up to greater than π radians.) So naively deploying a PiP algorithm will give you incorrect answers for edge cases.
It's probably easiest to use a library that can account for differences between Euclidean and spherical (or, more precisely, Earth-shaped) geometry—that is, a mapping library like MapKit. There are tricks like the one in this SO answer that let you convert a MKPolygon to a CGPath through map projection, after which you can use the CGPathContainsPoint function to test against the flat 2D polygon corresponding to your Earth-surface polygon.
Of course, to do that you'll also need to get your KML file imported to MapKit. Apple has a sample code project illustrating how to do this.
This can be done with GMSGeometryContainsLocation.
I wrote a method that makes use of the GoogleMapsUtils library's GMUKMLParser.
func findPolygonName(_ location: CLLocationCoordinate2D) {
var name: String?
outerLoop: for placemark in kmlParser.placemarks {
if let polygon = (placemark as? GMUPlacemark)?.geometry as? GMUPolygon {
for path in polygon.paths {
if GMSGeometryContainsLocation(location, path, true) {
name = (placemark as? GMUPlacemark)?.title
break outerLoop
}
}
}
}
if let n = name, !n.isEmpty {
locationLabel.text = n
} else {
locationLabel.text = "We do not deliver here"
}
}
This function iterates over the polygon and its path to determine whether the given coordinates are within the path.
Related
I have an app with a barcode scanner, and I would like to draw a box around the barcode when the app scans it. I think I can do this with .boundingBox but I am not really sure how to go about this.
Here is my setup for the barcode handler right now:
func barcodeRequestHandler(request: VNRequest, error: Error?) {
guard let results = request.results as? [VNBarcodeObservation],
let payloadStringValue = results.last?.payloadStringValue,
let box = results.last?.boundingBox
else {
return
}
// I think I can do something with 'box' here
}
This is used to create a VNDetectBarcodesRequest, which is passed to a VNImageRequestHandler.perform() function.
I have tried to find a way to draw the box with a CAShapeLayer, but haven't yet found a way to make that work. Is there something built into the barcode scanning system that would let me draw a box around the barcode? Or a different way I can use the bounding box to draw it?
Check if this link is useful. It is not related to border but you will get an idea about boundingbox.
I have a Leaflet based mapping solution that uses ArcGIS map configuration supplied by a user (I have no idea what it will be, they will customize it with their own ArcGIS services). The issue is that the projection can be pretty much anything, and I will need to use Proj4Leaflet to configure the CRS of the map accordingly. The problem I'm running into is I'm not sure how to calculate the scale/resolution array. The user is inputting these values: projection key, Proj4 string, origin, bounds, zoom levels.
So, for example (yes I know EPSG:3857 is standard and I could just use L.CRS.EPSG3857 but it serves as a good example of how to set the same thing up using Proj4Leaflet):
Projection key = EPSG:3857
Proj4 string = +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=#null +wktext +no_defs
Origin = [0,0]
Bounds = [[-20026376.39, -20048966.10], [20026376.39, 20048966.10]]
Zoom levels = 18
With that I think I have enough to set up a L.Proj.CRS for it:
var crs = new L.Proj.CRS("EPSG:3857", "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=#null +wktext +no_defs",
{
resolutions : [?????],
origin : [0,0],
bounds : [[-20026376.39, -20048966.10], [20026376.39, 20048966.10]]
});
I have everything I need apart from the resolutions array, I am not sure exactly how to go about setting that up based on the data given and having a hard time finding answers to get me pointed in the right direction.
So bottom line, the only way I found to calculate resolutions is if it is a mercator projection and we know the longitude extents of it and the tile size. Otherwise the resolutions will need to be looked up at the ArcGIS Server tile server REST endpoint. Thus for my project I will need the user to supply the array themselves and cannot calculate it for them.
In the case of the mercator projection, I came up with this function that does the trick:
function parseResolutionsFromZoomLevels(zoomLevels, tileSize, mapWGS84Extent)
{
var metersPerExtent = 40075008/360;
var mapWGS84Meters = mapWGS84Extent*metersPerExtent;
var resolutionArray = [];
for (var i=0; i<zoomLevels; i++)
{
var tilesAtZoom = Math.pow(2,i);
var pixelsAtZoom = tilesAtZoom*tileSize;
resolutionArray.push(mapWGS84Meters/pixelsAtZoom);
}
return resolutionArray;
}
Hope this helps anyone else that happens to encounter this same situation.
I am trying to determine the marker/s currently shown in a mapview. I have researched the following the method:
self.mapView.bounds.contains(markers[0].position)
But the contains command accepts CGPoint or CGRect. In other platforms except Swift, contains can accept the marker's position.
How do I convert the marker's position to be accepted by contains?
Use the mapview's current projection. Use the projection's method called containsCoordinate to check if you marker's position is inside the projection, i.e. currently visible.
So something like:
let coord = marker.position
let isVisible = self.mapview.projection.containsCoordinate(coord)
https://developers.google.com/maps/documentation/ios-sdk/reference/interface_g_m_s_projection.html#aa6ad29e6831e40f00435c3aaeda8e08a
In MapKit:
I assume with Marker you mean an MKAnnotation. Instead of using the bounds of the mapView you should use the visibleMapRect and see if it contains the coordinates of the Marker in MKMapPoints. This is the code I used:
let markerPoint = MKMapPointForCoordinate(markers[0].coordinate)
if MKMapRectContainsPoint(mapView.visibleMapRect, markerPoint) {
print("Found")
} else {
print("Not found")
}
Only when the coordinates of the markers are visible (in other words, the marker is being displayed), this will print "Found". If off-screen, it will print "Not found".
In my game, I'm trying to determine what points to dole out depending on where an arrow hits a target. I've got the physics and collisions worked out and I've decided to draw several nested circular SKShapeNodes to represent the different rings of the target.
I'm just having issues working out the logic involved in checking if the contact point coordinates are in one of the circle nodes...
Is it even possible?
The easiest solution specific to Sprite Kit is to use the SKPhysicsWorld method bodyAtPoint:, assuming all of the SKShapeNode also have an appropriate SKPhysicsBody.
For example:
SKPhysicsBody* body = [self.scene.physicsWorld bodyAtPoint:CGPointMake(100, 200)];
if (body != nil)
{
// your cat content here ...
}
If there could be overlapping bodies at the same point you can enumerate them with enumerateBodiesAtPoint:usingBlock:
You can also compare the SKShapeNode's path with your CGPoint.
SKShapeNode node; // let there be your node
CGPoint point; // let there be your point
if (CGPathContainsPoint(node.path, NULL, point, NO)) {
// yepp, that point is inside of that shape
}
I've been playing with the google earth API. I thought it would be neat to draw some lines between places from a relative 3D viewpoint. I've searched through the GE documentation and searched on google for answers but didn't find anything that led me down the correct path, so I thought I'd post some code and perhaps get some insight.
The following code plots two places and then draws a line between those places. Unfortunately the line that gets drawn splices the earth. Is there a method to make it wrap to the contour of the earth when drawn in 3D like this? I've attempted to vary the line height placement with a varying degree of success, but at the cost of accuracy and overall visual appeal when the line doesn't appear to connect the places.
function init() {
google.earth.createInstance('map3d', initCB, failureCB);
}
function initCB(instance) {
ge = instance;
ge.getWindow().setVisibility(true);
//---------------------------------PLACES
// Create the placemark.
var placemark = ge.createPlacemark('');
placemark.setName("Location 1");
// Set the placemark's location.
var point = ge.createPoint('');
point.setLatitude(39.96028);
point.setLongitude(-82.979736);
placemark.setGeometry(point);
// Add the placemark to Earth.
ge.getFeatures().appendChild(placemark);
// Create the placemark.
var placemark2 = ge.createPlacemark('');
placemark2.setName("Hop #2");
// Set the placemark's location.
var point2 = ge.createPoint('');
point2.setLatitude(25.7615);
point2.setLongitude(-80.2939);
placemark2.setGeometry(point2);
// Add the placemark to Earth.
ge.getFeatures().appendChild(placemark2);
//---------------------------------FOCUS
var lookAt = ge.createLookAt('');
lookAt.setLatitude(39.96028);
lookAt.setLongitude(-82.979736);
lookAt.setRange(1000000.0);
lookAt.setAltitude(0);
lookAt.setTilt(45);
ge.getView().setAbstractView(lookAt);
//---------------------------------LINES
// Create the placemark
var lineStringPlacemark = ge.createPlacemark('');
// Create the LineString
var lineString = ge.createLineString('');
lineStringPlacemark.setGeometry(lineString);
// Add LineString points
lineString.getCoordinates().pushLatLngAlt(39.96028, -82.979736, 0);
lineString.getCoordinates().pushLatLngAlt(25.7615, -80.2939, 0);
//lineString.setAltitudeMode(ge.ALTITUDE_CLAMP_TO_GROUND);
//lineString.setAltitudeMode(ge.ALTITUDE_RELATIVE_TO_GROUND);
lineString.setAltitudeMode(ge.absolute);
// Create a style and set width and color of line
lineStringPlacemark.setStyleSelector(ge.createStyle(''));
var lineStyle = lineStringPlacemark.getStyleSelector().getLineStyle();
lineStyle.setWidth(2);
lineStyle.getColor().set('9900ffff'); // aabbggrr format
// Add the feature to Earth
ge.getFeatures().appendChild(lineStringPlacemark);
}
function failureCB(errorCode) {
}
google.setOnLoadCallback(init);
You will want to set tesselation, and optionally extrude, on your linestring to true.
See https://developers.google.com/kml/documentation/kmlreference#tessellate and https://developers.google.com/kml/documentation/kmlreference#extrude for details
For the API, your syntax would be something like
lineStringPlacemark.setTessellate(true);
lineStringPlacemark.setExtrude(true);
There's some additional API examples on this at https://developers.google.com/earth/documentation/geometries