Skip to content

Commit

Permalink
✨ Add bounding box memoization
Browse files Browse the repository at this point in the history
  • Loading branch information
RemiBardon committed Feb 10, 2022
1 parent 0aa9cda commit da78ab2
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 44 deletions.
9 changes: 8 additions & 1 deletion Sources/GeoJSON/BoundingBox.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import GeoModels

/// A [GeoJSON Bounding Box](https://datatracker.ietf.org/doc/html/rfc7946#section-5).
public protocol BoundingBox: Hashable, Codable {
public protocol BoundingBox: GeoModels.BoundingBox, Codable {

/// This bonding box, but type-erased.
var asAny: AnyBoundingBox { get }
Expand Down Expand Up @@ -37,6 +37,13 @@ extension BoundingBox3D: BoundingBox {
/// A type-erased ``BoundingBox``.
public enum AnyBoundingBox: BoundingBox, Hashable, Codable {

public static var zero: AnyBoundingBox = .twoDimensions(.zero)

public func union(_ other: AnyBoundingBox) -> AnyBoundingBox {
#warning("Implement `AnyBoundingBox.union`")
fatalError("Not implemented yet")
}

case twoDimensions(BoundingBox2D)
case threeDimensions(BoundingBox3D)

Expand Down
2 changes: 0 additions & 2 deletions Sources/GeoJSON/GeoJSON+Codable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,6 @@ extension Feature {
try container.encodeIfPresent(self.geometry, forKey: .geometry)
try container.encode(self.properties, forKey: .properties)
// TODO: Create GeoJSONEncoder that allows setting "export bboxes" to a boolean value
// TODO: Memoize bboxes not to recompute them all the time (bboxes tree)
try container.encodeIfPresent(self.bbox, forKey: .bbox)
}

Expand Down Expand Up @@ -445,7 +444,6 @@ extension FeatureCollection {
try container.encode(Self.geoJSONType, forKey: .geoJSONType)
try container.encodeIfPresent(self.features, forKey: .features)
// TODO: Create GeoJSONEncoder that allows setting "export bboxes" to a boolean value
// TODO: Memoize bboxes not to recompute them all the time (bboxes tree)
try container.encodeIfPresent(self.bbox, forKey: .bbox)
}

Expand Down
2 changes: 1 addition & 1 deletion Sources/GeoJSON/Geometries/GeometryCollection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public struct GeometryCollection: CodableGeometry {

public static var geometryType: GeoJSON.`Type`.Geometry { .geometryCollection }

public var bbox: AnyBoundingBox?
public var _bbox: AnyBoundingBox { asAnyGeometry.bbox }

public var asAnyGeometry: AnyGeometry { .geometryCollection(self) }

Expand Down
2 changes: 1 addition & 1 deletion Sources/GeoJSON/Geometries/Polygon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public struct LinearRingCoordinates<Point: GeoJSON.Point>: Boundable, Hashable,

public var rawValue: RawValue

public var bbox: RawValue.BoundingBox { rawValue.bbox }
public var _bbox: RawValue.BoundingBox { rawValue.bbox }

public init(rawValue: RawValue) throws {
guard rawValue.first == rawValue.last else {
Expand Down
4 changes: 2 additions & 2 deletions Sources/GeoJSON/Helpers/GeoJSON+Boundable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
import NonEmpty
import Turf

extension NonEmpty: Boundable where Element: Boundable {
extension NonEmpty: Boundable where Collection: Hashable, Element: Boundable {

public var bbox: Element.BoundingBox {
public var _bbox: Element.BoundingBox {
self.reduce(.zero, { $0.union($1.bbox) })
}

Expand Down
36 changes: 18 additions & 18 deletions Sources/GeoJSON/Objects/Geometry.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// File.swift
// Geometry.swift
// GeoSwift
//
// Created by Rémi Bardon on 04/02/2022.
Expand All @@ -9,9 +9,9 @@
import Turf

/// A [GeoJSON Geometry](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1).
public protocol Geometry: GeoJSON.Object, Hashable {
var bbox: BoundingBox? { get }
public protocol Geometry: GeoJSON.Object, Boundable, Hashable
where BoundingBox: GeoJSON.BoundingBox
{

/// This geometry, but type-erased.
var asAnyGeometry: AnyGeometry { get }
Expand Down Expand Up @@ -46,7 +46,7 @@ public protocol SingleGeometry: CodableGeometry {

extension SingleGeometry {

public var bbox: Coordinates.BoundingBox? { coordinates.bbox }
public var _bbox: Coordinates.BoundingBox { coordinates.bbox }

}

Expand Down Expand Up @@ -89,23 +89,23 @@ public enum AnyGeometry: Geometry, Hashable, Codable {
// }
// }

public var bbox: AnyBoundingBox? {
public var _bbox: AnyBoundingBox {
switch self {
case .geometryCollection(let geo): return geo.bbox

case .point2D(let geo): return geo.bbox?.asAny
case .multiPoint2D(let geo): return geo.bbox?.asAny
case .lineString2D(let geo): return geo.bbox?.asAny
case .multiLineString2D(let geo): return geo.bbox?.asAny
case .polygon2D(let geo): return geo.bbox?.asAny
case .multiPolygon2D(let geo): return geo.bbox?.asAny
case .point2D(let geo): return geo.bbox.asAny
case .multiPoint2D(let geo): return geo.bbox.asAny
case .lineString2D(let geo): return geo.bbox.asAny
case .multiLineString2D(let geo): return geo.bbox.asAny
case .polygon2D(let geo): return geo.bbox.asAny
case .multiPolygon2D(let geo): return geo.bbox.asAny

case .point3D(let geo): return geo.bbox?.asAny
case .multiPoint3D(let geo): return geo.bbox?.asAny
case .lineString3D(let geo): return geo.bbox?.asAny
case .multiLineString3D(let geo): return geo.bbox?.asAny
case .polygon3D(let geo): return geo.bbox?.asAny
case .multiPolygon3D(let geo): return geo.bbox?.asAny
case .point3D(let geo): return geo.bbox.asAny
case .multiPoint3D(let geo): return geo.bbox.asAny
case .lineString3D(let geo): return geo.bbox.asAny
case .multiLineString3D(let geo): return geo.bbox.asAny
case .polygon3D(let geo): return geo.bbox.asAny
case .multiPolygon3D(let geo): return geo.bbox.asAny
}
}

Expand Down
8 changes: 1 addition & 7 deletions Sources/GeoJSON/Objects/Object.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,7 @@
//

/// A [GeoJSON Object](https://datatracker.ietf.org/doc/html/rfc7946#section-3).
public protocol Object {

associatedtype BoundingBox: GeoJSON.BoundingBox

var bbox: BoundingBox? { get }

}
public protocol Object {}

public protocol CodableObject: GeoJSON.Object, Codable {

Expand Down
2 changes: 1 addition & 1 deletion Sources/GeoJSON/Position.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import GeoModels
import Turf

/// A [GeoJSON Position](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.1).
public protocol Position: Hashable, Boundable {}
public protocol Position: Boundable {}

/// A ``Position`` with two elements (longitude and latitude).
public typealias Position2D = Coordinate2D
Expand Down
34 changes: 23 additions & 11 deletions Sources/Turf/Boundable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,74 +12,86 @@ public protocol Boundable {

associatedtype BoundingBox: GeoModels.BoundingBox

var bbox: BoundingBox { get }
var _bbox: BoundingBox { get }

}

extension Coordinate2D: Boundable {
extension Boundable {

public var bbox: BoundingBox2D {
public var bbox: BoundingBox { _bbox }

}

extension Boundable where Self: Hashable {

public var bbox: BoundingBox { BoundingBoxCache.shared.bbox(for: self) }

}

extension Coordinate2D {

public var _bbox: BoundingBox2D {
BoundingBox2D(southWest: self, width: .zero, height: .zero)
}

}

extension Coordinate3D: Boundable {

public var bbox: BoundingBox3D {
public var _bbox: BoundingBox3D {
BoundingBox3D(southWestLow: self, width: .zero, height: .zero, zHeight: .zero)
}

}

extension Line2D: Boundable {

public var bbox: BoundingBox2D {
public var _bbox: BoundingBox2D {
Turf.bbox(for: [start, end])!
}

}

extension Line3D: Boundable {

public var bbox: BoundingBox3D {
public var _bbox: BoundingBox3D {
Turf.bbox(for: [start, end])!
}

}

extension BoundingBox2D: Boundable {

public var bbox: BoundingBox2D { self }
public var _bbox: BoundingBox2D { self }

}

extension BoundingBox3D: Boundable {

public var bbox: BoundingBox3D { self }
public var _bbox: BoundingBox3D { self }

}

// Extension of protocol 'Collection' cannot have an inheritance clause
//extension Collection: Boundable where Element: Boundable {
//
// public var bbox: Element.BoundingBox {
// public var _bbox: Element.BoundingBox {
// self.reduce(.zero, { $0.union($1.bbox) })
// }
//
//}

extension Array: Boundable where Element: Boundable {

public var bbox: Element.BoundingBox {
public var _bbox: Element.BoundingBox {
self.reduce(.zero, { $0.union($1.bbox) })
}

}

extension Set: Boundable where Element: Boundable {

public var bbox: Element.BoundingBox {
public var _bbox: Element.BoundingBox {
self.reduce(.zero, { $0.union($1.bbox) })
}

Expand Down
41 changes: 41 additions & 0 deletions Sources/Turf/BoundingBoxCache.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//
// BoundingBoxCache.swift
// SwiftGeo
//
// Created by Rémi Bardon on 10/02/2022.
// Copyright © 2022 Rémi Bardon. All rights reserved.
//

import GeoModels

public class BoundingBoxCache {

public static let shared = BoundingBoxCache()

private var values = [AnyHashable: Any]()

private init() {}

internal func store<B: BoundingBox, K: Hashable>(_ value: B, forKey key: K) {
values[AnyHashable(key)] = value
}

internal func get<B: BoundingBox, K: Hashable>(_ type: B.Type, forKey key: K) -> B? {
values[AnyHashable(key)] as? B
}

public func bbox<B: Boundable & Hashable>(for boundable: B) -> B.BoundingBox {
if let cachedValue = self.get(B.BoundingBox.self, forKey: boundable) {
return cachedValue
} else {
let bbox = boundable._bbox
self.store(bbox, forKey: boundable)
return bbox
}
}

public func removeCache<K: Hashable>(for key: K) {
values.removeValue(forKey: AnyHashable(key))
}

}

0 comments on commit da78ab2

Please sign in to comment.