Simplify this foreach loop (to find min/max in a nested array) in swift - swift

I would love to get rid of the foreach loop. Currently I am doing a foreach loop to populate a temp variable to separate my array to get the min/max for each Lat/Lon.
eg: slopeLatLonArray = [ [111,111],[111.1,111.2] ]
func drawFullRouteOverlay() {
/// Reset Array to Nil
vLocations = []
/// populate vLocations as a CLLocation2D
for index in slopeLatLonArray.indices {
vLocations.append(CLLocationCoordinate2D(latitude: Double(slopeLatLonArray[index][0]), longitude: Double(slopeLatLonArray[index][1])))
}
/// Draw the resulting polyline
let polyline = MKPolyline(coordinates: vLocations, count: vLocations.count)
vcTrainMapView.addOverlay(polyline)
/// Bunch of stuffs to do to get the Max/Min of Lat/Lon
var tempLat: [Double] = []
var tempLon: [Double] = []
slopeLatLonArray.forEach {
tempLat.append($0[0])
tempLon.append($0[1])
}
/// Zoom to the entire route polyline
let center = CLLocationCoordinate2D(latitude : (tempLat.min()! + tempLat.max()!) / 2,
longitude: (tempLon.min()! + tempLon.max()!) / 2)
let span = MKCoordinateSpan(latitudeDelta : (tempLat.max()! - tempLat.min()!) * 1.3,
longitudeDelta: (tempLon.max()! - tempLon.min()!) * 1.3)
let region = MKCoordinateRegion(center: center, span: span)
vcTrainMapView.setRegion(region, animated: true)
}

You are unnecessarily iterating all your locations multiple times. First when populating vLocations. Second when populating slopeLatLonArray. Third, fourth, fifth and sixth when getting tempLat and tempLon minimum and maximum values. And another 4 times when getting them again for the span (this might be optimized by the compiler but I am not sure).
What I suggest is to get all those values during the first iteration when populating vLocations. This way you will iterate all locations only once:
func drawFullRouteOverlay() {
guard let first = slopeLatLonArray.first, first.count == 2 else { return }
var minLatitude = first[0]
var maxLatitude = first[0]
var minLongitude = first[1]
var maxLongitude = first[1]
vLocations = slopeLatLonArray.map {
let latitude = $0[0]
let longitude = $0[1]
minLatitude = min(minLatitude, latitude)
maxLatitude = max(maxLatitude, latitude)
minLongitude = min(minLongitude, longitude)
maxLongitude = max(maxLongitude, longitude)
return .init(latitude: latitude, longitude: longitude)
}
/// Draw the resulting polyline
let polyline = MKPolyline(coordinates: vLocations, count: vLocations.count)
vcTrainMapView.addOverlay(polyline)
/// Zoom to the entire route polyline
let center = CLLocationCoordinate2D(latitude: (minLatitude + maxLatitude) / 2, longitude: (minLongitude + maxLongitude) / 2)
let span = MKCoordinateSpan(latitudeDelta: (maxLatitude - minLatitude) * 1.3, longitudeDelta: (maxLongitude - minLongitude) * 1.3)
let region = MKCoordinateRegion(center: center, span: span)
vcTrainMapView.setRegion(region, animated: true)
}

How about either .map...
var tempLat = slopeLatLonArray.map { $0[0] }
var tempLon = slopeLatLonArray.map { $0[1] }
// Could also zip to vLocations for a 1 liner
var vLocations = zip(tempLat, tempLon).map(CLLocationCoordinate2D.init)
or setting in the initial for loop...
var tempLat: [Double] = []
var tempLon: [Double] = []
for index in slopeLatLonArray.indices {
tempLat[index] = Double(slopeLatLonArray[index][0])
tempLon[index] = Double(slopeLatLonArray[index][1])
vLocations.append(CLLocationCoordinate2D(latitude: tempLat[index], longitude: tempLon[index]))
}

Related

can I calculate accurate distance between two latitude and longitude inside a house within 5 meters? [duplicate]

This question already exists:
I want to build something like that if a user enters in a room he recieve a notification in swift ios is it possible?
Closed 3 months ago.
I want to calculate distance between two coordinates within 5 meters or even within one meters is it possible
I have tried haversine formula but not getting the desired result
func calculateDistanceWithHaversin(crrLat: Double, crrLong: Double, desLat: Double = 23.1780068, desLong: Double = 75.7865060, radius: Double = 6367444.7) -> Double {
print("CrrLat \(crrLat) = CrrLong = \(crrLong)")
let haversin = { (angle: Double) -> Double in
return (1 - cos(angle))/2
}
let ahaversin = { (angle: Double) -> Double in
return 2 * asin(sqrt(angle))
}
// degree to radian
let dToR = { (angle: Double) -> Double in
return (angle / 360) * 2 * .pi
}
let lat1 = dToR(crrLat)
let lon1 = dToR(crrLong)
let lat2 = dToR(desLat)
let lon2 = dToR(desLong)
return radius * ahaversin(haversin(lat2 - lat1) + cos(lat1) * cos(lat2) * haversin(lon2 - lon1))
}
i have tried this also
func calculateDistance(crrLat: Double, crrLong: Double) {
let destinationLocation = CLLocation(latitude: 23.1780068, longitude: 75.7865060)
let currentLocation = CLLocation(latitude: crrLat, longitude: crrLong)
distance = currentLocation.distance(from: destinationLocation)
print(String(format: "The distance to my buddy is %.02f m", distance))
}
You can calculate distance with this Builtin Function provided by CoreLocation . The provided distance will be in meters
import CoreLocation
let locationOne = CLLocation(latitude: 37.899, longitude: 74.8989)
let locationTwo = CLLocation(latitude: 38.0900, longitude: 78.98898)
let distance = locationOne.distance(from: locationTwo)

How do I calculate the altitude needed to display the full MKCoordinateRegion in a MKMapView?

So I want to set a specific position with a specific region shown at a specific rotation.
I know there is a setRegion that calculates the altitude automatically, but it doesn't let you set a rotation. To set a rotation I need to use setRotation that takes a center, altitude and heading.
I have no idea how to calculate the altitude needed to file the full region on the screen, regardless of screen side.
I'm trying:
func updateUIView(_ view: ZooMap, context: Context) {
let coordinate = CLLocationCoordinate2D(
latitude: 21.270381425220606,
longitude: -157.81908260613335
)
let span = MKCoordinateSpan(latitudeDelta: 0.007075784913642025, longitudeDelta: 0.010196763428524491)
let region = MKCoordinateRegion(center: coordinate, span: span)
let camera = MKMapCamera()
camera.heading = 71
camera.centerCoordinate = coordinate;
camera.altitude = determineAltitudeForRegion(region: region, heading: camera.heading, viewport: UIScreen.main.bounds.size)
view.setCamera(camera, animated: false)
}
func determineAltitudeForRegion(region: MKCoordinateRegion, heading: Double, viewport: CGSize) -> Double {
// Calculate a new bounding rectangle that is corrected for the aspect ratio
// of the viewport/camera -- this will be needed to ensure the resulting
// altitude actually fits the polygon in view for the observer.
let upperLeftCoord = CLLocationCoordinate2DMake(region.center.latitude + region.span.latitudeDelta / 2, region.center.longitude - region.span.longitudeDelta / 2)
let upperRightCoord = CLLocationCoordinate2DMake(region.center.latitude + region.span.latitudeDelta / 2, region.center.longitude + region.span.longitudeDelta / 2)
let lowerLeftCoord = CLLocationCoordinate2DMake(region.center.latitude - region.span.latitudeDelta / 2, region.center.longitude - region.span.longitudeDelta / 2)
let hDist = MKMapPoint(upperLeftCoord).distance(to: MKMapPoint(upperRightCoord));
let vDist = MKMapPoint(upperLeftCoord).distance(to: MKMapPoint(lowerLeftCoord));
var adjacent: Double = 0;
var newHDist: Double = 0;
var newVDist: Double = 0;
let rect = RectForRegion(region: region)
if (rect.size.height > rect.size.width) {
newVDist = (Double(viewport.height) / Double(viewport.width)) * hDist;
adjacent = newVDist;
} else {
newHDist = (Double(viewport.width) / Double(viewport.height)) * vDist;
adjacent = newHDist;
}
return adjacent / tan(heading.degreesToRadians) / 2
}
func RectForRegion(region: MKCoordinateRegion) -> MKMapRect {
let a: MKMapPoint = MKMapPoint(CLLocationCoordinate2DMake(region.center.latitude + region.span.latitudeDelta / 2, region.center.longitude - region.span.longitudeDelta / 2))
let b: MKMapPoint = MKMapPoint(CLLocationCoordinate2DMake(region.center.latitude - region.span.latitudeDelta / 2, region.center.longitude + region.span.longitudeDelta / 2))
return MKMapRect(x: min(a.x, b.x), y: min(a.y, b.y), width: abs(a.x - b.x), height: abs(a.y - b.y))
}
but its not working properly. How do I do this??

How to display large amount of GMSPolylines without maxing out CPU usage?

I'm making an app that displays bus routes using the NextBus API and Google Maps. However, I'm having an issue with CPU usage that I think is being caused by the amount of GMSPolylines on the map. The route is displayed by an array of polylines made up of the points given by NextBus for a given route. When the polylines are added to the map and the GMSCamera is overviewing the entire route, the CPU on the simulator (iPhone X) maxes out at 100%. When zoomed in on a particular section of the route, however, the CPU usage goes down to ~2%.
Map Screenshot: https://i.imgur.com/jLmN26e.png
Performance: https://i.imgur.com/nUbIv5w.png
The NextBus API returns route information including the route of a specific bus path. Here's an small example of the data that I'm working with:
Route: {
"path": [Path]
}
Path: {
"points:" [Coordinate]
}
Coordinate: {
"lat": Float,
"lon": Float
}
And here's my method that creates the polylines from the data. All in all there are on average ~700 coordinates spread across ~28 polylines (each path object) for a route. Keep in mind I'm not displaying multiple routes on one page, I'm only displaying one at a time.
func buildRoute(routePath: [Path?]) -> [GMSPolyline] {
var polylines: [GMSPolyline] = []
for path in routePath {
let path = GMSMutablePath()
guard let coords = path?.points else {continue}
for coordinate in coords {
// Safely unwrap latitude strings and convert them to doubles.
guard let latStr = coordinate?.lat,
let lonStr = coordinate?.lon else {
continue
}
guard let latOne = Double(latStr),
let lonOne = Double(lonStr) else {
continue
}
// Create location coordinates.
let pointCoordinatie = CLLocationCoordinate2D(latitude: latOne, longitude: lonOne)
path.add(pointCoordinatie)
}
let line = GMSPolyline(path: path)
line.strokeWidth = 6
line.strokeColor = UIColor(red: 0/255, green: 104/255, blue: 139/255, alpha: 1.0)
polylines.append(line)
}
return polylines
}
Finally here is my method that adds the polylines to the map:
fileprivate func buildRoute(routeConfig: RouteConfig?) {
if let points = routeConfig?.route?.path {
let polylines = RouteBuiler.shared.buildRoute(routePath: points)
DispatchQueue.main.async {
// Remove polylines from map if there are any.
for line in self.currentRoute {
line.map = nil
}
// Set new current route and add it to the map.
self.currentRoute = polylines
for line in self.currentRoute {
line.map = self.mapView
}
}
}
}
Is there a problem with how I'm constructing the polylines? Or are there simply too many coordinates?
I ran into this exact problem. It is quite an odd bug -- when you go over a certain threshold of polylines, the CPU suddenly pegs to 100%.
I discovered that GMSPolygon does not have this problem. So I switched over all of GMSPolyline to GMSPolygon.
To get the correct stroke width, I am using the following code to create a polygon that traces the outline of a polyline at a given stroke width. My calculation requires the LASwift linear algebra library.
https://github.com/AlexanderTar/LASwift
import CoreLocation
import LASwift
import GoogleMaps
struct Segment {
let from: CLLocationCoordinate2D
let to: CLLocationCoordinate2D
}
enum RightLeft {
case right, left
}
// Offset the given path to the left or right by the given distance
func offsetPath(rightLeft: RightLeft, path: [CLLocationCoordinate2D], offset: Double) -> [CLLocationCoordinate2D] {
var offsetPoints = [CLLocationCoordinate2D]()
var prevSegment: Segment!
for i in 0..<path.count {
// Test if this is the last point
if i == path.count-1 {
if let to = prevSegment?.to {
offsetPoints.append(to)
}
continue
}
let from = path[i]
let to = path[i+1]
// Skip duplicate points
if from.latitude == to.latitude && from.longitude == to.longitude {
continue
}
// Calculate the miter corner for the offset point
let segmentAngle = -atan2(to.latitude - from.latitude, to.longitude - from.longitude)
let sinA = sin(segmentAngle)
let cosA = cos(segmentAngle)
let rotate =
Matrix([[cosA, -sinA, 0.0],
[sinA, cosA, 0.0],
[0.0, 0.0, 1.0]])
let translate =
Matrix([[1.0, 0.0, 0.0 ],
[0.0, 1.0, rightLeft == .left ? offset : -offset ],
[0.0, 0.0, 1.0]])
let mat = inv(rotate) * translate * rotate
let fromOff = mat * Matrix([[from.x], [from.y], [1.0]])
let toOff = mat * Matrix([[to.x], [to.y], [1.0]])
let offsetSegment = Segment(
from: CLLocationCoordinate2D(latitude: fromOff[1,0], longitude: fromOff[0,0]),
to: CLLocationCoordinate2D(latitude: toOff[1,0], longitude: toOff[0,0]))
if prevSegment == nil {
prevSegment = offsetSegment
offsetPoints.append(offsetSegment.from)
continue
}
// Calculate line intersection
guard let intersection = getLineIntersection(line0: prevSegment, line1: offsetSegment, segment: false) else {
prevSegment = offsetSegment
continue
}
prevSegment = offsetSegment
offsetPoints.append(intersection)
}
return offsetPoints
}
// Returns the intersection point if the line segments intersect, otherwise nil
func getLineIntersection(line0: Segment, line1: Segment, segment: Bool) -> CLLocationCoordinate2D? {
return getLineIntersection(p0: line0.from, p1: line0.to, p2: line1.from, p3: line1.to, segment: segment)
}
// https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
// Returns the intersection point if the line segments intersect, otherwise nil
func getLineIntersection(p0: CLLocationCoordinate2D, p1: CLLocationCoordinate2D, p2: CLLocationCoordinate2D, p3: CLLocationCoordinate2D, segment: Bool) -> CLLocationCoordinate2D? {
let s1x = p1.longitude - p0.longitude
let s1y = p1.latitude - p0.latitude
let s2x = p3.longitude - p2.longitude
let s2y = p3.latitude - p2.latitude
let numerator = (s2x * (p0.latitude - p2.latitude) - s2y * (p0.longitude - p2.longitude))
let denominator = (s1x * s2y - s2x * s1y)
if denominator == 0.0 {
return nil
}
let t = numerator / denominator
if segment {
let s = (s1y * (p0.longitude - p2.longitude) + s1x * (p0.latitude - p2.latitude)) / (s1x * s2y - s2x * s1y)
guard (s >= 0 && s <= 1 && t >= 0 && t <= 1) else {
return nil
}
}
return CLLocationCoordinate2D(latitude: p0.latitude + (t * s1y), longitude: p0.longitude + (t * s1x))
}
// The path from NextBus
let path: CLLocationCoordinate2D = pathFromNextBus()
// The desired width of the polyline
let strokeWidth: Double = desiredPolylineWidth()
let polygon: GMSPolygon
do {
let polygonPath = GMSMutablePath()
let w = strokeWidth / 2.0
for point in offsetPath(rightLeft: .left, path: route.offsetPath, offset: w) {
polygonPath.add(CLLocationCoordinate2D(latitude: point.latitude, longitude: point.longitude))
}
for point in offsetPath(rightLeft: .right, path: route.offsetPath, offset: w).reversed() {
polygonPath.add(CLLocationCoordinate2D(latitude: point.latitude, longitude: point.longitude))
}
polygon = GMSPolygon(path: polygonPath)
polygon.strokeWidth = 0.0
}

Identifying memory leak in Swift GCD.async call

Some background about my app: I am drawing a map. When the user moves the map I perform a database query. I first do an rTree query to find the features that would be draw in the current viewport. Once I have those IDs I perform a second database query to extract the features (geojson) from the database. I do a quick check to see if the item already has been drawn, if not I do a addChild to render the feature on the map. I want to do these database looks up in the background via GCD so the user can move the map smoothly. I've implemented this but the memory usage quickly grows to 1gb, whereas if I do all the work in the main thread it uses around 250mb (acceptable for me). I'm assuming something is not being cleaned up because of the closure use. Any insight into the cause of the memory leak is appreciated.
public func drawItemsInBox(boundingBox: [Double]) {
DispatchQueue.global(qos: .background).async { [weak self] in
guard let self = self else {
return
}
var drawItems: [Int64] = []
let table = Table("LNDARE_XS")
let tableRTree = Table("LNDARE_XS_virtual")
let coords = Expression<String?>("coords")
let foid = Expression<String>("foid")
let rTree = Expression<Int64>("rTree")
let minX = Expression<Double>("minX")
let maxX = Expression<Double>("maxX")
let minY = Expression<Double>("minY")
let maxY = Expression<Double>("maxY")
let id = Expression<Int64>("id")
// find all the features to draw via an rTree query
for row in try! self.db.prepare(tableRTree.filter(maxX >= boundingBox[0] && minX <= boundingBox[1] && maxY >= boundingBox[2] && minY <= boundingBox[3])) {
drawItems.append(row[id])
}
do {
// get all the features geojson data
let query = table.filter(drawItems.contains(rTree))
for row in try self.db.prepare(query) {
// skip drawing if the feature already exists on the map
if self.featureTracking["LNDARE_XS"]?[Int64(row[foid])!] == nil {
// convert the database string to an array of coords
var toBeRendered:[CGPoint] = []
let coordsArray = row[coords]!.components(separatedBy: ",")
for i in 0...(coordsArray.count / 2) - 1 {
toBeRendered.append(CGPoint(x: (Double(coordsArray[i*2])!), y: (Double(coordsArray[(i*2)+1])!)))
}
let linearShapeNode = SKShapeNode(points: &toBeRendered, count: toBeRendered.count)
linearShapeNode.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
linearShapeNode.lineWidth = 0
linearShapeNode.fillColor = NSColor.black
// append the featureId for tracking and call addChild to draw
self.scaleLayer.addChild(linearShapeNode)
self.featureTracking["LNDARE_XS"]?[Int64(row[foid])!] = linearShapeNode
}
}
} catch {
// catch
}
}
}
Maybe change toBeRendered can save some:
var toBeRendered:[CGPoint] = []
for row in try self.db.prepare(query) {
// skip drawing if the feature already exists on the map
if self.featureTracking["LNDARE_XS"]?[Int64(row[foid])!] == nil {
// convert the database string to an array of coords
toBeRendered.removeAll()
let coordsArray = row[coords]!.components(separatedBy: ",")
for i in 0...(coordsArray.count / 2) - 1 {
toBeRendered.append(CGPoint(x: (Double(coordsArray[i*2])!), y: (Double(coordsArray[(i*2)+1])!)))
}
Maybe try using an auto release pool since you are not on the main thread
public func drawItemsInBox(boundingBox: [Double]) {
DispatchQueue.global(qos: .background).async { [weak self] in
guard let self = self else {
return
}
var drawItems: [Int64] = []
let table = Table("LNDARE_XS")
let tableRTree = Table("LNDARE_XS_virtual")
let coords = Expression<String?>("coords")
let foid = Expression<String>("foid")
let rTree = Expression<Int64>("rTree")
let minX = Expression<Double>("minX")
let maxX = Expression<Double>("maxX")
let minY = Expression<Double>("minY")
let maxY = Expression<Double>("maxY")
let id = Expression<Int64>("id")
// find all the features to draw via an rTree query
for row in try! self.db.prepare(tableRTree.filter(maxX >= boundingBox[0] && minX <= boundingBox[1] && maxY >= boundingBox[2] && minY <= boundingBox[3])) {
drawItems.append(row[id])
}
do {
// get all the features geojson data
let query = table.filter(drawItems.contains(rTree))
for row in try self.db.prepare(query) {
autoreleasepool{
// skip drawing if the feature already exists on the map
if self.featureTracking["LNDARE_XS"]?[Int64(row[foid])!] == nil {
// convert the database string to an array of coords
var toBeRendered:[CGPoint] = []
let coordsArray = row[coords]!.components(separatedBy: ",")
for i in 0...(coordsArray.count / 2) - 1 {
toBeRendered.append(CGPoint(x: (Double(coordsArray[i*2])!), y: (Double(coordsArray[(i*2)+1])!)))
}
let linearShapeNode = SKShapeNode(points: &toBeRendered, count: toBeRendered.count)
linearShapeNode.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
linearShapeNode.lineWidth = 0
linearShapeNode.fillColor = NSColor.black
// append the featureId for tracking and call addChild to draw
self.scaleLayer.addChild(linearShapeNode)
self.featureTracking["LNDARE_XS"]?[Int64(row[foid])!] = linearShapeNode
}
}
}
} catch {
// catch
}
}
}

Getting An Arbitrary Type From Reduce

I'm doing a very simple operation. I'm sorting through a bunch of locations in a map to create an enclosing circle, like so:
var maxLong: Double = -180
var maxLat: Double = -180
var minLong: Double = 180
var minLat: Double = 180
for coord in inCoordinates {
maxLong = max(coord.longitude, maxLong)
maxLat = max(coord.latitude, maxLat)
minLong = min(coord.longitude, minLong)
minLat = min(coord.latitude, minLat)
}
let nw: CLLocation = CLLocation(latitude: maxLat, longitude: minLong)
let se: CLLocation = CLLocation(latitude: minLat, longitude: maxLong)
let center = CLLocationCoordinate2D(latitude: (maxLat + minLat) / 2.0, longitude: (maxLong + minLong) / 2.0)
let radiusInMeters = abs(nw.distance(from: se)) / 2.0
return MKCircle(center: center, radius: radiusInMeters)
Pretty straightforward (Yeah, I know about the IDL issue, but I want to keep this simple).
What I'd like to know, is if there were some way I could boil the loop into a variant of reduce, where you would end up with something like this:
let enclosingRect: MKMapRect = inCoordinates.magikalReduce {
// Magic Happens Here -Queue Doug Henning GIF
}
So the returned rect contains the distilled points.
Yeah, I know that I can simply extend Array (with maybe a type qualifier) to do this with a calculated property, but that sort of defeats the purpose of this. The above is fairly efficient, and I'd rather not add overhead, just to be fancy (Which means, even if I could do it, it might be too inefficient to use).
This is more of a curiosity exploration than a technical need. The above code does fine for me, and is relatively zippy.
Do you mean
// calculate the enclosing rect with `reduce` and `union`, you have to create an `MKMapRect` from each coordinate
let enclosingRect = inCoordinates.reduce(MKMapRect.null) { $0.union(MKMapRect(origin: MKMapPoint($1), size: MKMapSize())) }
You can create a struct for holding the min/max longitude and latitude values, then use reduce, where you use the initial values for these for creating an initial result, then creating an updated version of the struct with the necessary min/max calculations.
struct MinMaxCoordinates {
let maxLong:Double
let maxLat:Double
let minLong:Double
let minLat:Double
}
let minMaxCoordinates = inCoordinates.reduce(MinMaxCoordinates(maxLong: -180, maxLat: -180, minLong: 180, minLat: 180), {minMax, coord in
return MinMaxCoordinates(maxLong: max(minMax.maxLong, coord.longitude), maxLat: max(minMax.maxLat, coord.latitude), minLong: min(minMax.minLong, coord.longitude), minLat: max(minMax.minLat, coord.latitude))
})
let nw: CLLocation = CLLocation(latitude: minMaxCoordinates.maxLat, longitude: minMaxCoordinates.minLong)
let se: CLLocation = CLLocation(latitude: minMaxCoordinates.minLat, longitude: minMaxCoordinates.maxLong)
let center = CLLocationCoordinate2D(latitude: (minMaxCoordinates.maxLat + minMaxCoordinates.minLat) / 2.0, longitude: (minMaxCoordinates.maxLong + minMaxCoordinates.minLong) / 2.0)
let radiusInMeters = abs(nw.distance(from: se)) / 2.0
return MKCircle(center: center, radius: radiusInMeters)