GISTools

用于 Swift 的 GIS 工具,包括 GeoJSON 实现以及许多从 https://turfjs.org 移植的算法。

功能特性

注意

此软件包对“相等”做出了一些假设,即 1e-10 度以内的坐标被认为是相等的(这是微米级的精度,可能有点过 overkill)。请参阅 GISTool.equalityDelta

要求

此软件包需要 Swift 5.10 或更高版本(至少 Xcode 14),并可在 iOS (>= iOS 13)、macOS (>= macOS 10.15)、tvOS (>= tvOS 13)、watchOS (>= watchOS 6) 以及 Linux 上编译。

使用 Swift Package Manager 安装

dependencies: [
    .package(url: "https://github.com/Outdooractive/gis-tools", from: "1.8.2"),
],
targets: [
    .target(name: "MyTarget", dependencies: [
        .product(name: "GISTools", package: "gis-tools"),
    ]),
]

用法

另请参阅 API 文档 (通过 Swift Package Index)。

import GISTools

var feature = Feature(Point(Coordinate3D(latitude: 3.870163, longitude: 11.518585)))
feature.properties = [
    "test": 1,
    "test2": 5.567,
    "test3": [1, 2, 3],
    "test4": [
        "sub1": 1,
        "sub2": 2
    ]
]

// To and from String:
let jsonString = feature.asJsonString(prettyPrinted: true)
let feature = Feature(jsonString: jsonString)

// To and from Data:
let jsonData = feature.asJsonData(prettyPrinted: true)
let feature = Feature(jsonData: jsonData)

// Using Codable:
let jsonData = try JSONEncoder().encode(feature)
let feature = try JSONDecoder().decode(Feature.self, from: jsonData)

// Generic:
let someGeoJson = GeoJsonReader.geoJsonFrom(json: [
    "type": "Point",
    "coordinates": [100.0, 0.0],
])
let someGeoJson = GeoJsonReader.geoJsonFrom(contentsOf: URL(...))
let someGeoJson = GeoJsonReader.geoJsonFrom(jsonData: Data(...))
let someGeoJson = GeoJsonReader.geoJsonFrom(jsonString: "{\"type\":\"Point\",\"coordinates\":[100.0,0.0]}")

switch someGeoJson {
case let point as Point: ...
}
// or
switch someGeoJson.type {
case .point: ...
}

// Wraps *any* GeoJSON into a FeatureCollection
let featureCollection = FeatureCollection(jsonData: someData)
let featureCollection = try JSONDecoder().decode(FeatureCollection.self, from: someData)

...

请参阅 测试以获取更多示例 以及 API 文档

GeoJSON

引用自 RFC 7946

GeoJSON 是一种基于 JavaScript 对象表示法 (JSON) 的地理空间数据交换格式。
它定义了几种 JSON 对象类型以及它们组合起来表示关于地理要素、其属性和空间范围的数据的方式。
GeoJSON 使用地理坐标参考系统,世界大地测量系统 1984,单位为十进制度。

请先阅读 RFC 以大致了解 GeoJSON 是什么以及不是什么(如果您还不知道所有这些...🙂,这种情况不太可能发生)。

GeoJson 协议

实现

每个 GeoJSON 对象的基础

/// All permitted GeoJSON types.
public enum GeoJsonType: String {
    case point              = "Point"
    case multiPoint         = "MultiPoint"
    case lineString         = "LineString"
    case multiLineString    = "MultiLineString"
    case polygon            = "Polygon"
    case multiPolygon       = "MultiPolygon"
    case geometryCollection = "GeometryCollection"
    case feature            = "Feature"
    case featureCollection  = "FeatureCollection"
}

/// GeoJSON object type.
var type: GeoJsonType { get }

/// The GeoJSON's projection, which should typically be EPSG:4326.
var projection: Projection { get }

/// All of the receiver's coordinates.
var allCoordinates: [Coordinate3D] { get }

/// Any foreign members, i.e. keys in the JSON that are
/// not part of the GeoJSON standard.
var foreignMembers: [String: Any] { get set }

/// Try to initialize a GeoJSON object from any JSON and calculate a bounding box if necessary.
init?(json: Any?, calculateBoundingBox: Bool)

/// Type erased equality check.
func isEqualTo(_ other: GeoJson) -> Bool

BoundingBoxRepresentable 协议

实现

所有 GeoJSON 对象都可以有一个边界框。如果您想使用 R 树空间索引(见下文),则这是必需的。

/// The GeoJSON's projection.
var projection: Projection { get }

/// The receiver's bounding box.
var boundingBox: BoundingBox? { get set }

/// Calculates and returns the receiver's bounding box.
func calculateBoundingBox() -> BoundingBox?

/// Calculates the receiver's bounding box and updates the `boundingBox` property.
///
/// - parameter ifNecessary: Only update the bounding box if the receiver doesn't already have one.
@discardableResult
mutating func updateBoundingBox(onlyIfNecessary ifNecessary: Bool) -> BoundingBox?

/// Check if the receiver is inside or crosses  the other bounding box.
///
/// - parameter otherBoundingBox: The bounding box to check.
func intersects(_ otherBoundingBox: BoundingBox) -> Bool

GeoJsonConvertible 协议 / GeoJsonCodable

实现

GeoJSON 对象可以从多种来源初始化

/// Try to initialize a GeoJSON object from any JSON.
init?(json: Any?)

/// Try to initialize a GeoJSON object from a file.
init?(contentsOf url: URL)

/// Try to initialize a GeoJSON object from a data object.
init?(jsonData: Data)

/// Try to initialize a GeoJSON object from a string.
init?(jsonString: String)

/// Try to initialize a GeoJSON object from a Decoder.
init(from decoder: Decoder) throws

它们也可以通过多种方式导出

/// Return the GeoJson object as Key/Value pairs.
var asJson: [String: Any] { get }

/// Dump the object as JSON data.
func asJsonData(prettyPrinted: Bool = false) -> Data?

/// Dump the object as a JSON string.
func asJsonString(prettyPrinted: Bool = false) -> String?

/// Write the object in it's JSON represenation to a file.
func write(to url: URL, prettyPrinted: Bool = false) throws

/// Write the GeoJSON object to an Encoder.
func encode(to encoder: Encoder) throws

示例

let point = Point(jsonString: "{\"type\":\"Point\",\"coordinates\":[100.0,0.0]}")!
print(point.allCoordinates)
print(point.asJsonString(prettyPrinted: true)!)

let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
let data = try encoder.encode(point)

// This works because `FeatureCollection` will wrap any valid GeoJSON object.
// This is a good way to enforce a common structure for all loaded objects.
let featureCollection = FeatureCollection(jsonData: data)!

重要提示:导入和导出将始终以 EPSG:4326 进行,但有一个例外:没有 SRID 的 GeoJSON 对象将按原样导出。

GeoJsonReader

实现

这是一种从任何看起来像 GeoJSON 的东西创建 GeoJSON 对象的通用方法

/// Try to initialize a GeoJSON object from any JSON.
static func geoJsonFrom(json: Any?) -> GeoJson?

/// Try to initialize a GeoJSON object from a file.
static func geoJsonFrom(contentsOf url: URL) -> GeoJson?

/// Try to initialize a GeoJSON object from a data object.
static func geoJsonFrom(jsonData: Data) -> GeoJson?

/// Try to initialize a GeoJSON object from a string.
static func geoJsonFrom(jsonString: String) -> GeoJson?

示例

let json: [String: Any] = [
    "type": "Point",
    "coordinates": [100.0, 0.0],
    "other": "something",
]
let geoJson = GeoJsonReader.geoJsonFrom(json: json)!
print("Type is \(geoJson.type.rawValue)")
print("Foreign members: \(geoJson.foreignMembers)")

switch geoJson {
case let point as Point:
    print("It's a Point!")
case let multiPoint as MultiPoint:
    print("It's a MultiPoint!")
case let lineString as LineString:
    print("It's a LineString!")
case let multiLineString as MultiLineString:
    print("It's a MultiLineString!")
case let polygon as Polygon:
    print("It's a Polygon!")
case let multiPolygon as MultiPolygon:
    print("It's a MultiPolygon!")
case let geometryCollection as GeometryCollection:
    print("It's a GeometryCollection!")
case let feature as Feature:
    print("It's a Feature!")
case let featureCollection as FeatureCollection:
    print("It's a FeatureCollection!")
default: 
    assertionFailure("Missed an object type?")
}

重要提示:导入将始终以 EPSG:4326 进行。

Coordinate3D

实现 / 坐标测试用例

坐标是此软件包中最基本的构建块。每个对象和算法都建立在它们之上

/// The coordinates projection, either EPSG:4326 or EPSG:3857.
let projection: Projection

/// The coordinate's `latitude`.
var latitude: CLLocationDegrees
/// The coordinate's `longitude`.
var longitude: CLLocationDegrees
/// The coordinate's `altitude`.
var altitude: CLLocationDistance?

/// Linear referencing, timestamp or whatever you want it to use for.
///
/// The GeoJSON specification doesn't specifiy the meaning of this value,
/// and it doesn't guarantee that parsers won't ignore or discard it. See
/// https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.1.
/// - Important: The JSON for a coordinate will contain a `null` altitude value
///              if `altitude` is `nil` so that `m` won't get lost (since it is
///              the 4th value).
///              This might lead to compatibilty issues with other GeoJSON readers.
var m: Double?

/// Alias for longitude
var x: Double { longitude }

/// Alias for latitude
var y: Double { latitude }

/// Create a coordinate with `latitude`, `longitude`, `altitude` and `m`.
/// Projection will be EPSG:4326.
init(latitude: CLLocationDegrees,
     longitude: CLLocationDegrees,
     altitude: CLLocationDistance? = nil,
     m: Double? = nil)

/// Create a coordinate with ``x``, ``y``, ``z`` and ``m``.
/// Default projection will we EPSG:3857 but can be overridden.
init(
    x: Double,
    y: Double,
    z: Double? = nil,
    m: Double? = nil,
    projection: Projection = .epsg3857)

/// Reproject this coordinate.
func projected(to newProjection: Projection) -> Coordinate3D

示例

let coordinate = Coordinate3D(latitude: 0.0, longitude: 0.0)
print(coordinate.isZero)

BoundingBox

实现 / BoundingBox 测试用例

每个 GeoJSON 对象都可以有一个矩形 BoundingBox(请参阅上面的 BoundingBoxRepresentable

/// The bounding box's `projection`.
let projection: Projection

/// The bounding boxes south-west (bottom-left) coordinate.
var southWest: Coordinate3D
/// The bounding boxes north-east (upper-right) coordinate.
var northEast: Coordinate3D

/// Create a bounding box with a `southWest` and `northEast` coordinate.
init(southWest: Coordinate3D, northEast: Coordinate3D)

/// Create a bounding box from `coordinates` and an optional padding in kilometers.
init?(coordinates: [Coordinate3D], paddingKilometers: Double = 0.0)

/// Create a bounding box from other bounding boxes.
init?(boundingBoxes: [BoundingBox])

/// Reproject this bounding box.
func projected(to newProjection: Projection) -> BoundingBox

示例

let point = Point(Coordinat3D(latitude: 47.56, longitude: 10.22), calculateBoundingBox: true)
print(point.boundingBox!)

Point

实现 / Point 测试用例

Point 是单个坐标的包装器

/// The receiver's coordinate.
let coordinate: Coordinate3D

/// Initialize a Point with a coordinate.
init(_ coordinate: Coordinate3D, calculateBoundingBox: Bool = false)

/// Reproject the Point.
func projected(to newProjection: Projection) -> Point

示例

let point = Point(Coordinate3D(latitude: 47.56, longitude: 10.22))

MultiPoint

实现 / MultiPoint 测试用例

MultiPoint 是坐标数组

/// The receiver's coordinates.
let coordinates: [Coordinate3D]

/// The receiver’s coordinates converted to Points.
var points: [Point]

/// Try to initialize a MultiPoint with some coordinates.
init?(_ coordinates: [Coordinate3D], calculateBoundingBox: Bool = false)

/// Try to initialize a MultiPoint with some Points.
init?(_ points: [Point], calculateBoundingBox: Bool = false)

/// Reproject the MultiPoint.
func projected(to newProjection: Projection) -> MultiPoint

示例

let multiPoint = MultiPoint([
    Coordinate3D(latitude: 0.0, longitude: 100.0),
    Coordinate3D(latitude: 1.0, longitude: 101.0)
])!

LineString

实现 / LineString 测试用例

LineString 是由两个或多个坐标组成的数组,形成一条线

/// The LineString's coordinates.
let coordinates: [Coordinate3D]

/// Try to initialize a LineString with some coordinates.
init?(_ coordinates: [Coordinate3D], calculateBoundingBox: Bool = false)

/// Initialize a LineString with a LineSegment.
init(_ lineSegment: LineSegment, calculateBoundingBox: Bool = false)

/// Try to initialize a LineString with some LineSegments.
init?(_ lineSegments: [LineSegment], calculateBoundingBox: Bool = false)

/// Reproject the LineString.
func projected(to newProjection: Projection) -> LineString

示例

let lineString = LineString([
    Coordinate3D(latitude: 0.0, longitude: 100.0),
    Coordinate3D(latitude: 1.0, longitude: 101.0)
])!

let segment = LineSegment(
    first: Coordinate3D(latitude: 0.0, longitude: 100.0),
    second: Coordinate3D(latitude: 1.0, longitude: 101.0))
let lineString = LineString(lineSegment)

MultiLineString

实现 / MultiLineString 测试用例

MultiLineStringLineString 的数组

/// The MultiLineString's coordinates.
let coordinates: [[Coordinate3D]]

/// The receiver’s coordinates converted to LineStrings.
var lineStrings: [LineString]

/// Try to initialize a MultiLineString with some coordinates.
init?(_ coordinates: [[Coordinate3D]], calculateBoundingBox: Bool = false)

/// Try to initialize a MultiLineString with some LineStrings.
init?(_ lineStrings: [LineString], calculateBoundingBox: Bool = false)

/// Try to initialize a MultiLineString with some LineSegments. Each LineSegment will result in one LineString.
init?(_ lineSegments: [LineSegment], calculateBoundingBox: Bool = false)

/// Reproject the MultiLineString.
func projected(to newProjection: Projection) -> MultiLineString

示例

let multiLineString = MultiLineString([
    [Coordinate3D(latitude: 0.0, longitude: 100.0), Coordinate3D(latitude: 1.0, longitude: 101.0)],
    [Coordinate3D(latitude: 2.0, longitude: 102.0), Coordinate3D(latitude: 3.0, longitude: 103.0)],
])!

Polygon

实现 / Polygon 测试用例

Polygon 是由一个或多个环组成的形状,其中第一个环是包围表面的外环,内环包围表面内的孔洞。有关更多信息,请参阅 RFC 中的 第 3.1.6 节

/// The receiver's coordinates.
let coordinates: [[Coordinate3D]]

/// The receiver's outer ring.
var outerRing: Ring?

/// All of the receiver's inner rings.
var innerRings: [Ring]?

/// All of the receiver's rings (outer + inner).
var rings: [Ring]

/// Try to initialize a Polygon with some coordinates.
init?(_ coordinates: [[Coordinate3D]], calculateBoundingBox: Bool = false)

/// Try to initialize a Polygon with some Rings.
init?(_ rings: [Ring], calculateBoundingBox: Bool = false)

/// Reproject the Polygon.
func projected(to newProjection: Projection) -> Polygon

示例

let polygonWithHole = Polygon([
    [
        Coordinate3D(latitude: 0.0, longitude: 100.0),
        Coordinate3D(latitude: 0.0, longitude: 101.0),
        Coordinate3D(latitude: 1.0, longitude: 101.0),
        Coordinate3D(latitude: 1.0, longitude: 100.0),
        Coordinate3D(latitude: 0.0, longitude: 100.0)
    ],
    [
        Coordinate3D(latitude: 1.0, longitude: 100.8),
        Coordinate3D(latitude: 0.0, longitude: 100.8),
        Coordinate3D(latitude: 0.0, longitude: 100.2),
        Coordinate3D(latitude: 1.0, longitude: 100.2),
        Coordinate3D(latitude: 1.0, longitude: 100.8)
    ],
])!
print(polygonWithHole.area)

MultiPolygon

实现 / MultiPolygon 测试用例

MultiPolygonPolygon 的数组

/// The receiver's coordinates.
let coordinates: [[[Coordinate3D]]]

/// The receiver’s coordinates converted to Polygons.
var polygons: [Polygon]

/// Try to initialize a MultiPolygon with some coordinates.
init?(_ coordinates: [[[Coordinate3D]]], calculateBoundingBox: Bool = false)

/// Try to initialize a MultiPolygon with some Polygons.
init?(_ polygons: [Polygon], calculateBoundingBox: Bool = false)

/// Reproject the MultiPolygon.
func projected(to newProjection: Projection) -> MultiPolygon

示例

let multiPolygon = MultiPolygon([
    [
        [
            Coordinate3D(latitude: 2.0, longitude: 102.0),
            Coordinate3D(latitude: 2.0, longitude: 103.0),
            Coordinate3D(latitude: 3.0, longitude: 103.0),
            Coordinate3D(latitude: 3.0, longitude: 102.0),
            Coordinate3D(latitude: 2.0, longitude: 102.0),
        ]
    ],
    [
        [
            Coordinate3D(latitude: 0.0, longitude: 100.0),
            Coordinate3D(latitude: 0.0, longitude: 101.0),
            Coordinate3D(latitude: 1.0, longitude: 101.0),
            Coordinate3D(latitude: 1.0, longitude: 100.0),
            Coordinate3D(latitude: 0.0, longitude: 100.0),
        ],
        [
            Coordinate3D(latitude: 0.0, longitude: 100.2),
            Coordinate3D(latitude: 1.0, longitude: 100.2),
            Coordinate3D(latitude: 1.0, longitude: 100.8),
            Coordinate3D(latitude: 0.0, longitude: 100.8),
            Coordinate3D(latitude: 0.0, longitude: 100.2),
        ]
    ]
])!

GeometryCollection

实现 / GeometryCollection 测试用例

GeometryCollection 是 GeoJSON 几何图形的数组,即 PointMultiPointLineStringMultiLineStringPolygonMultiPolygon 甚至 GeometryCollection,尽管不建议使用后者。有关更多信息,请参阅 RFC 中的 第 3.1.8 节

/// The GeometryCollection's geometry objects.
let geometries: [GeoJsonGeometry]

/// Initialize a GeometryCollection with a geometry object.
init(_ geometry: GeoJsonGeometry, calculateBoundingBox: Bool = false)

/// Initialize a GeometryCollection with some geometry objects.
init(_ geometries: [GeoJsonGeometry], calculateBoundingBox: Bool = false)

/// Reproject the GeometryCollection.
func projected(to newProjection: Projection) -> GeometryCollection

Feature

实现 / Feature 测试用例

Feature 是一种容器,恰好包含一个 GeoJSON 几何图形 (PointMultiPointLineStringMultiLineStringPolygonMultiPolygonGeometryCollection),以及一些 properties 和一个可选的 id

/// A GeoJSON identifier that can either be a string or number.
/// Any parsed integer value `Int64.min ⪬ i ⪬ Int64.max`  will be cast to `Int`
/// (or `Int64` on 32-bit platforms), values above `Int64.max` will be cast to `UInt`
/// (or `UInt64` on 32-bit platforms).
enum Identifier: Equatable, Hashable, CustomStringConvertible {
    case string(String)
    case int(Int)
    case uint(UInt)
    case double(Double)
}

/// An arbitrary identifier.
var id: Identifier?

/// The `Feature`s geometry object.
let geometry: GeoJsonGeometry

/// Only 'Feature' objects may have properties.
var properties: [String: Any]

/// Create a ``Feature`` from any ``GeoJsonGeometry`` object.
init(_ geometry: GeoJsonGeometry,
     id: Identifier? = nil,
     properties: [String: Any] = [:],
     calculateBoundingBox: Bool = false)

/// Reproject the Feature.
func projected(to newProjection: Projection) -> Feature

FeatureCollection

实现 / FeatureCollection 测试用例

FeatureCollectionFeature 对象的数组

/// The FeatureCollection's Feature objects.
private(set) var features: [Feature]

/// Initialize a FeatureCollection with one Feature.
init(_ feature: Feature, calculateBoundingBox: Bool = false)

/// Initialize a FeatureCollection with some geometry objects.
init(_ geometries: [GeoJsonGeometry], calculateBoundingBox: Bool = false)

/// Normalize any GeoJSON object into a FeatureCollection.
init?(_ geoJson: GeoJson?, calculateBoundingBox: Bool = false)

/// Reproject the FeatureCollection.
func projected(to newProjection: Projection) -> FeatureCollection

此类型有些特殊,因为它的初始化器将接受任何有效的 GeoJSON 对象,并返回一个 FeatureCollection,如果输入是几何图形,则将输入包装在 Feature 对象中,或者如果输入是 Feature,则收集输入。

SwiftData

您需要使用转换器才能将 GeoJson 与 SwiftData 一起使用(另请参阅 SwiftData 测试用例)。

首先,像这样注册转换器

GeoJsonTransformer.register()

然后像这样创建您的模型

@Attribute(.transformable(by: GeoJsonTransformer.name.rawValue)) var geoJson: GeoJson?
@Attribute(.transformable(by: GeoJsonTransformer.name.rawValue)) var point: Point?
...

这是必要的,因为 SwiftData 不能很好地与默认的 Codable 实现一起工作,所以您需要自己进行序列化...

WKB/WKT

支持以下几何类型:pointlinestringlinearringpolygonmultipointmultilinestringmultipolygongeometrycollectiontriangle。如果您需要更多类型,请打开一个 issue。

每个 GeoJSON 对象都有便捷的方法来将自身编码和解码为 WKB/WKT,并且 DataString 都有扩展,可以从 WKB 和 WKT 解码为 GeoJSON。最终,它们都转发到 WKBCoderWKTCoder,它们负责繁重的工作。

WKB

另请参阅 WKB 测试用例

解码

// SELECT 'POINT Z (1 2 3)'::geometry;
private let pointZData = Data(hex: "0101000080000000000000F03F00000000000000400000000000000840")!

// Generic
let point = try WKBCoder.decode(wkb: pointData, sourceProjection: .epsg4326) as! Point
let point = pointZData.asGeoJsonGeometry(sourceProjection: .epsg4326) as! Point

// Or create the geometry directly
let point = Point(wkb: pointZData, sourceProjection: .epsg4326)!

// Or create a Feature that contains the geometry
let feature = Feature(wkb: pointZData, sourceProjection: .epsg4326)
let feature = pointZData.asFeature(sourceProjection: .epsg4326)

// Or create a FeatureCollection that contains a feature with the geometry
let featureCollection = FeatureCollection(wkb: pointZData, sourceProjection: .epsg4326)
let featureCollection = pointZData.asFeatureCollection(sourceProjection: .epsg4326)

// Can also reproject on the fly
let point = try WKBCoder.decode(
    wkb: pointData,
    sourceProjection: .epsg4326,
    targetProjection: .epsg3857
) as! Point
print(point.projection)

编码

let point = Point(Coordinate3D(latitude: 0.0, longitude: 100.0))

// Generic
let encodedPoint = WKBCoder.encode(geometry: point, targetProjection: nil)

// Convenience
let encodedPoint = point.asWKB

WKT

这与 WKB 完全相同… 另请参阅测试以了解其工作原理:WKT 测试用例

解码

private let pointZString = "POINT Z (1 2 3)"

// Generic
let point = try WKTCoder.decode(wkt: pointZString, sourceProjection: .epsg4326) as! Point
let point = pointZString.asGeoJsonGeometry(sourceProjection: .epsg4326) as! Point

// Or create the geometry directly
let point = Point(wkt: pointZString, sourceProjection: .epsg4326)!

// Or create a Feature that contains the geometry
let feature = Feature(wkt: pointZString, sourceProjection: .epsg4326)
let feature = pointZString.asFeature(sourceProjection: .epsg4326)

// Or create a FeatureCollection that contains a feature with the geometry
let featureCollection = FeatureCollection(wkt: pointZString, sourceProjection: .epsg4326)
let featureCollection = pointZString.asFeatureCollection(sourceProjection: .epsg4326)

// Can also reproject on the fly
let point = try WKTCoder.decode(
    wkt: pointZString,
    sourceProjection: .epsg4326,
    targetProjection: .epsg3857
) as! Point
print(point.projection) // EPSG:3857

编码

let point = Point(Coordinate3D(latitude: 0.0, longitude: 100.0))

// Generic
let encodedPoint = WKTCoder.encode(geometry: point, targetProjection: nil)

// Convenience
let encodedPoint = point.asWKT

空间索引

此软件包包含一个简单的 R 树实现:RTree 测试用例

var nodes: [Point] = []
50.times {
    nodes.append(Point(Coordinate3D(
        latitude: Double.random(in: -10.0 ... 10.0),
        longitude: Double.random(in: -10.0 ... 10.0))))
    }

let rTree = RTree(nodes)
let objects = rTree.search(inBoundingBox: boundingBox)
let objectsAround = rTree.search(aroundCoordinate: center, maximumDistance: maximumDistance)

MapTile

这是一个用于处理 x/y/z 地图瓦片的助手。

let tile1 = MapTile(x: 138513, y: 91601, z: 18)
let center = tile1.centerCoordinate(projection: .epsg4326) // default
let boundingBox = tile1.boundingBox(projection: .epsg4326) // default

let tile2 = MapTile(coordinate: Coordinate3D(latitude: 47.56, longitude: 10.22), atZoom: 14)
let parent = tile2.parent
let firstChild = tile2.child
let allChildren = tile2.children

let quadkey = tile1.quadkey
let tile3 = MapTile(quadkey: "1202211303220032")

也与地图瓦片没有直接关系

let mpp = MapTile.metersPerPixel(at: 15.0, latitude: 45.0)

折线 (Polylines)

提供折线 (Polylines) 的编码器/解码器。

let polyline = [Coordinate3D(latitude: 47.56, longitude: 10.22)].encodePolyline()
let coordinates = polyline.decodePolyline()

算法

提示:大多数算法都针对 EPSG:4326 进行了优化。使用其他投影会由于添加投影而产生性能损失。

名称 示例 源文件/测试
along (沿线) let coordinate = lineString.coordinateAlong(distance: 100.0) 源文件 / 测试
area (面积) Polygon(…).area 源文件
bearing (方位角) Coordinate3D(…).bearing(to: Coordinate3D(…)) 源文件 / 测试
boolean-clockwise (布尔值-顺时针) Polygon(…).outerRing?.isClockwise 源文件 / 测试
boolean-crosses (布尔值-交叉) TODO 源文件
boolean-disjoint (布尔值-不相交) let result = polygon.isDisjoint(with: lineString) 源文件 / 测试
boolean-intersects (布尔值-相交) let result = polygon.intersects(with: lineString) 源文件
boolean-overlap (布尔值-重叠) lineString1.isOverlapping(with: lineString2) 源文件 / 测试
boolean-parallel (布尔值-平行) lineString1.isParallel(to: lineString2) 源文件 / 测试
boolean-point-in-polygon (布尔值-点在面内) polygon.contains(Coordinate3D(…)) 源文件
boolean-point-on-line (布尔值-点在线上) lineString.checkIsOnLine(Coordinate3D(…)) 源文件
boolean-valid (布尔值-有效) anyGeometry.isValid 源文件
bbox-clip (边界框裁剪) let clipped = lineString.clipped(to: boundingBox) 源文件 / 测试
buffer (缓冲区) TODO 源文件
center/centroid/center-mean (中心/质心/平均中心) let center = polygon.center 源文件
circle (圆) let circle = point.circle(radius: 5000.0) 源文件 / 测试
conversions/helpers (转换/助手) let distance = GISTool.convert(length: 1.0, from: .miles, to: .meters) 源文件
destination (目的地) let destination = coordinate.destination(distance: 1000.0, bearing: 173.0) 源文件 / 测试
distance (距离) let distance = coordinate1.distance(from: coordinate2) 源文件 / 测试
flatten (展平) let featureCollection = anyGeometry.flattened 源文件 / 测试
frechetDistance (弗雷歇距离) let distance = lineString.frechetDistance(from: other) 源文件 / 测试
length (长度) let length = lineString.length 源文件 / 测试
line-arc (线弧) let lineArc = point.lineArc(radius: 5000.0, bearing1: 20.0, bearing2: 60.0) 源文件 / 测试
line-chunk (线分块) let chunks = lineString.chunked(segmentLength: 1000.0).lineStrings let dividedLine = lineString.evenlyDivided(segmentLength: 1.0) 源文件 / 测试
line-intersect (线相交) let intersections = feature1.intersections(other: feature2) 源文件 / 测试
line-overlap (线重叠) let overlappingSegments = lineString1.overlappingSegments(with: lineString2) 源文件 / 测试
line-segments (线段) let segments = anyGeometry.lineSegments 源文件 / 测试
line-slice (线切片) let slice = lineString.slice(start: Coordinate3D(…), end: Coordinate3D(…)) 源文件 / 测试
line-slice-along (沿线切片) let sliced = lineString.sliceAlong(startDistance: 50.0, stopDistance: 2000.0) 源文件 / 测试
midpoint (中点) let middle = coordinate1.midpoint(to: coordinate2) 源文件 / 测试
nearest-point (最近点) let nearest = anyGeometry.nearestCoordinate(from: Coordinate3D(…)) 源文件
nearest-point-on-feature (要素上的最近点) let nearest = anyGeometry. nearestCoordinateOnFeature(from: Coordinate3D(…)) 源文件
nearest-point-on-line (线上的最近点) let nearest = lineString.nearestCoordinateOnLine(from: Coordinate3D(…))?.coordinate 源文件 / 测试
nearest-point-to-line (线外的最近点) let nearest = lineString. nearestCoordinate(outOf: coordinates) 源文件
point-on-feature (要素上的点) let coordinate = anyGeometry.coordinateOnFeature 源文件
points-within-polygon (面内的点) let within = polygon.coordinatesWithin(coordinates) 源文件
point-to-line-distance (点到线距离) let distance = lineString.distanceFrom(coordinate: Coordinate3D(…)) 源文件 / 测试
pole-of-inaccessibility (不可达极点) TODO 源文件
polygon-to-line (面转线) var lineStrings = polygon.lineStrings 源文件
reverse (反转) let lineStringReversed = lineString.reversed 源文件 / 测试
rhumb-bearing (恒向线方位角) let bearing = start.rhumbBearing(to: end) 源文件 / 测试
rhumb-destination (恒向线目的地) let destination = coordinate.rhumbDestination(distance: 1000.0, bearing: 0.0) 源文件 / 测试
rhumb-distance (恒向线距离) let distance = coordinate1.rhumbDistance(from: coordinate2) 源文件 / 测试
simplify (简化) let simplified = lineString. simplified(tolerance: 5.0, highQuality: false) 源文件 / 测试
tile-cover (瓦片覆盖率) let tileCover = anyGeometry.tileCover(atZoom: 14) 源文件 / 测试
transform-coordinates (坐标变换) let transformed = anyGeometry.transformCoordinates({ $0 }) 源文件 / 测试
transform-rotate (旋转变换) let transformed = anyGeometry. transformedRotate(angle: 25.0, pivot: Coordinate3D(…)) 源文件 / 测试
transform-scale (缩放变换) let transformed = anyGeometry. transformedScale(factor: 2.5, anchor: .center) 源文件 / 测试
transform-translate (平移变换) let transformed = anyGeometry. transformedTranslate(distance: 1000.0, direction: 25.0) 源文件 / 测试
truncate (截断) let truncated = lineString.truncated(precision: 2, removeAltitude: true) 源文件 / 测试
union (联合) TODO 源文件

相关软件包

目前只有两个

贡献

创建 issue打开 pull request 来修复或增强功能。

许可证

MIT

作者

Thomas Rasch, Outdooractive