From da78ab2c7d19668bc4d1c978163a461c08391d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Bardon?= Date: Thu, 10 Feb 2022 21:37:14 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20bounding=20box=20memoization?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/GeoJSON/BoundingBox.swift | 9 +++- Sources/GeoJSON/GeoJSON+Codable.swift | 2 - .../Geometries/GeometryCollection.swift | 2 +- Sources/GeoJSON/Geometries/Polygon.swift | 2 +- .../GeoJSON/Helpers/GeoJSON+Boundable.swift | 4 +- Sources/GeoJSON/Objects/Geometry.swift | 36 ++++++++-------- Sources/GeoJSON/Objects/Object.swift | 8 +--- Sources/GeoJSON/Position.swift | 2 +- Sources/Turf/Boundable.swift | 34 ++++++++++----- Sources/Turf/BoundingBoxCache.swift | 41 +++++++++++++++++++ 10 files changed, 96 insertions(+), 44 deletions(-) create mode 100644 Sources/Turf/BoundingBoxCache.swift diff --git a/Sources/GeoJSON/BoundingBox.swift b/Sources/GeoJSON/BoundingBox.swift index 52f44fc..17bbbfb 100644 --- a/Sources/GeoJSON/BoundingBox.swift +++ b/Sources/GeoJSON/BoundingBox.swift @@ -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 } @@ -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) diff --git a/Sources/GeoJSON/GeoJSON+Codable.swift b/Sources/GeoJSON/GeoJSON+Codable.swift index 530f7c2..4a79b1d 100644 --- a/Sources/GeoJSON/GeoJSON+Codable.swift +++ b/Sources/GeoJSON/GeoJSON+Codable.swift @@ -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) } @@ -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) } diff --git a/Sources/GeoJSON/Geometries/GeometryCollection.swift b/Sources/GeoJSON/Geometries/GeometryCollection.swift index 5b3070a..0ffc767 100644 --- a/Sources/GeoJSON/Geometries/GeometryCollection.swift +++ b/Sources/GeoJSON/Geometries/GeometryCollection.swift @@ -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) } diff --git a/Sources/GeoJSON/Geometries/Polygon.swift b/Sources/GeoJSON/Geometries/Polygon.swift index d74dd81..c807cd8 100644 --- a/Sources/GeoJSON/Geometries/Polygon.swift +++ b/Sources/GeoJSON/Geometries/Polygon.swift @@ -29,7 +29,7 @@ public struct LinearRingCoordinates: 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 { diff --git a/Sources/GeoJSON/Helpers/GeoJSON+Boundable.swift b/Sources/GeoJSON/Helpers/GeoJSON+Boundable.swift index 0e7f8df..5f24725 100644 --- a/Sources/GeoJSON/Helpers/GeoJSON+Boundable.swift +++ b/Sources/GeoJSON/Helpers/GeoJSON+Boundable.swift @@ -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) }) } diff --git a/Sources/GeoJSON/Objects/Geometry.swift b/Sources/GeoJSON/Objects/Geometry.swift index 327109c..fbcfab2 100644 --- a/Sources/GeoJSON/Objects/Geometry.swift +++ b/Sources/GeoJSON/Objects/Geometry.swift @@ -1,5 +1,5 @@ // -// File.swift +// Geometry.swift // GeoSwift // // Created by Rémi Bardon on 04/02/2022. @@ -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 } @@ -46,7 +46,7 @@ public protocol SingleGeometry: CodableGeometry { extension SingleGeometry { - public var bbox: Coordinates.BoundingBox? { coordinates.bbox } + public var _bbox: Coordinates.BoundingBox { coordinates.bbox } } @@ -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 } } diff --git a/Sources/GeoJSON/Objects/Object.swift b/Sources/GeoJSON/Objects/Object.swift index f0eec22..f2afa28 100644 --- a/Sources/GeoJSON/Objects/Object.swift +++ b/Sources/GeoJSON/Objects/Object.swift @@ -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 { diff --git a/Sources/GeoJSON/Position.swift b/Sources/GeoJSON/Position.swift index b1401fb..979f442 100644 --- a/Sources/GeoJSON/Position.swift +++ b/Sources/GeoJSON/Position.swift @@ -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 diff --git a/Sources/Turf/Boundable.swift b/Sources/Turf/Boundable.swift index c9b4ff2..3bb48cc 100644 --- a/Sources/Turf/Boundable.swift +++ b/Sources/Turf/Boundable.swift @@ -12,13 +12,25 @@ 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) } @@ -26,7 +38,7 @@ extension Coordinate2D: Boundable { extension Coordinate3D: Boundable { - public var bbox: BoundingBox3D { + public var _bbox: BoundingBox3D { BoundingBox3D(southWestLow: self, width: .zero, height: .zero, zHeight: .zero) } @@ -34,7 +46,7 @@ extension Coordinate3D: Boundable { extension Line2D: Boundable { - public var bbox: BoundingBox2D { + public var _bbox: BoundingBox2D { Turf.bbox(for: [start, end])! } @@ -42,7 +54,7 @@ extension Line2D: Boundable { extension Line3D: Boundable { - public var bbox: BoundingBox3D { + public var _bbox: BoundingBox3D { Turf.bbox(for: [start, end])! } @@ -50,20 +62,20 @@ extension Line3D: Boundable { 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) }) // } // @@ -71,7 +83,7 @@ extension BoundingBox3D: Boundable { extension Array: Boundable where Element: Boundable { - public var bbox: Element.BoundingBox { + public var _bbox: Element.BoundingBox { self.reduce(.zero, { $0.union($1.bbox) }) } @@ -79,7 +91,7 @@ extension Array: Boundable where Element: Boundable { extension Set: Boundable where Element: Boundable { - public var bbox: Element.BoundingBox { + public var _bbox: Element.BoundingBox { self.reduce(.zero, { $0.union($1.bbox) }) } diff --git a/Sources/Turf/BoundingBoxCache.swift b/Sources/Turf/BoundingBoxCache.swift new file mode 100644 index 0000000..3a6e7cd --- /dev/null +++ b/Sources/Turf/BoundingBoxCache.swift @@ -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(_ value: B, forKey key: K) { + values[AnyHashable(key)] = value + } + + internal func get(_ type: B.Type, forKey key: K) -> B? { + values[AnyHashable(key)] as? B + } + + public func bbox(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(for key: K) { + values.removeValue(forKey: AnyHashable(key)) + } + +}