Skip to content

Commit

Permalink
Simple Violations (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
schlingding authored Mar 4, 2020
1 parent 4403c6c commit 7088d46
Show file tree
Hide file tree
Showing 31 changed files with 2,433 additions and 1,503 deletions.
2,226 changes: 870 additions & 1,356 deletions GeospatialSwift.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions MAINTAINERS
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
(schlingding) Vernon Schierding
(mifanbing) Zeyi Wang
501 changes: 426 additions & 75 deletions Sources/GeospatialSwift/API/Calculator/GeodesicCalculator.swift

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions Sources/GeospatialSwift/API/GeoJson/GeoJsonSimpleViolation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
public struct GeoJsonSimpleViolation {
public let problems: [GeoJsonCoordinatesGeometry]
public let reason: GeoJsonSimpleViolationReason
}

public enum GeoJsonSimpleViolationReason {
case lineIntersection
case multiLineIntersection
case pointDuplication
case polygonHoleOutside
case polygonNegativeRingContained
case polygonSelfIntersection
case polygonMultipleVertexIntersection
case multiPolygonContained
case multiPolygonIntersection
}
2 changes: 2 additions & 0 deletions Sources/GeospatialSwift/API/GeoJson/Object/Feature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ extension GeoJson.Feature {
public func objectDistance(to point: GeodesicPoint, tolerance: Double) -> Double? { geometry?.objectDistance(to: point, tolerance: tolerance) }

public func contains(_ point: GeodesicPoint, tolerance: Double) -> Bool { geometry?.contains(point, tolerance: tolerance) ?? false }

public func simpleViolations(tolerance: Double) -> [GeoJsonSimpleViolation] { geometry.flatMap { $0.simpleViolations(tolerance: tolerance) } ?? [] }
}

extension GeoJson.Feature {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ extension GeoJson.FeatureCollection {
public func objectDistance(to point: GeodesicPoint, tolerance: Double) -> Double? { features.compactMap { $0.objectDistance(to: point, tolerance: tolerance) }.min() }

public func contains(_ point: GeodesicPoint, tolerance: Double) -> Bool { features.first { $0.contains(point, tolerance: tolerance) } != nil }

public func simpleViolations(tolerance: Double) -> [GeoJsonSimpleViolation] { features.flatMap { $0.simpleViolations(tolerance: tolerance) } }
}

extension GeoJson.FeatureCollection {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ extension GeoJson.GeometryCollection {
public func objectDistance(to point: GeodesicPoint, tolerance: Double) -> Double? { objectGeometries.compactMap { $0.objectDistance(to: point, tolerance: tolerance) }.min() }

public func contains(_ point: GeodesicPoint, tolerance: Double) -> Bool { objectGeometries.first { $0.contains(point, tolerance: tolerance) } != nil }

public func simpleViolations(tolerance: Double) -> [GeoJsonSimpleViolation] { objectGeometries.flatMap { $0.simpleViolations(tolerance: tolerance) } }
}

extension GeoJson.GeometryCollection {
Expand Down
37 changes: 34 additions & 3 deletions Sources/GeospatialSwift/API/GeoJson/Object/LineString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ extension GeoJson {
public struct LineString: GeoJsonLinearGeometry, GeodesicLine {
public let type: GeoJsonObjectType = .lineString

private let geoJsonPoints: [Point]
internal let geoJsonPoints: [Point]

internal init(coordinatesJson: [Any]) {
// swiftlint:disable:next force_cast
Expand All @@ -20,7 +20,7 @@ extension GeoJson {
geoJsonPoints = pointsJson.map { Point(coordinatesJson: $0) }
}

fileprivate init(points: [Point]) {
internal init(points: [Point]) {
geoJsonPoints = points
}
}
Expand All @@ -35,7 +35,7 @@ extension GeoJson.LineString {
points.enumerated().compactMap { (offset, point) in
if points.count == offset + 1 { return nil }

return .init(point: point, otherPoint: points[offset + 1])
return .init(startPoint: point, endPoint: points[offset + 1])
}
}

Expand All @@ -48,6 +48,37 @@ extension GeoJson.LineString {
public func distance(to point: GeodesicPoint, tolerance: Double) -> Double { Calculator.distance(from: point, to: self, tolerance: tolerance) }

public func contains(_ point: GeodesicPoint, tolerance: Double) -> Bool { Calculator.contains(point, in: self, tolerance: tolerance) }

public func simpleViolations(tolerance: Double) -> [GeoJsonSimpleViolation] {
let duplicatePoints = Calculator.simpleViolationDuplicateIndices(points: points, tolerance: tolerance).map { geoJsonPoints[$0[0]] }

guard duplicatePoints.isEmpty else { return [GeoJsonSimpleViolation(problems: duplicatePoints, reason: .pointDuplication)] }

let selfIntersectsIndices = Calculator.simpleViolationSelfIntersectionIndices(line: self, tolerance: tolerance)

guard selfIntersectsIndices.isEmpty else {
var simpleViolationGeometries = [GeoJsonCoordinatesGeometry]()
selfIntersectsIndices.forEach { firstIndex, secondIndices in
var point = GeoJson.Point(longitude: segments[firstIndex].startPoint.longitude, latitude: segments[firstIndex].startPoint.latitude)
var otherPoint = GeoJson.Point(longitude: segments[firstIndex].endPoint.longitude, latitude: segments[firstIndex].endPoint.latitude)
simpleViolationGeometries.append(point)
simpleViolationGeometries.append(otherPoint)
simpleViolationGeometries.append(GeoJson.LineString(points: [point, otherPoint]))

secondIndices.forEach {
point = GeoJson.Point(longitude: segments[$0].startPoint.longitude, latitude: segments[$0].startPoint.latitude)
otherPoint = GeoJson.Point(longitude: segments[$0].endPoint.longitude, latitude: segments[$0].endPoint.latitude)
simpleViolationGeometries.append(point)
simpleViolationGeometries.append(otherPoint)
simpleViolationGeometries.append(GeoJson.LineString(points: [point, otherPoint]))
}
}

return [GeoJsonSimpleViolation(problems: simpleViolationGeometries, reason: .lineIntersection)]
}

return []
}
}

extension GeoJson.LineString {
Expand Down
31 changes: 31 additions & 0 deletions Sources/GeospatialSwift/API/GeoJson/Object/LinearRing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,36 @@ extension GeoJson {

return nil
}

internal static func simpleViolations(linearRing: LineString, tolerance: Double) -> [GeoJsonSimpleViolation] {
let duplicatePoints = Calculator.simpleViolationDuplicateIndices(points: linearRing.points.dropLast(), tolerance: tolerance).map { linearRing.geoJsonPoints[$0[0]] }

guard duplicatePoints.isEmpty else { return [GeoJsonSimpleViolation(problems: duplicatePoints, reason: .pointDuplication)] }

let selfIntersectsIndices = Calculator.simpleViolationSelfIntersectionIndices(line: linearRing, tolerance: tolerance)

guard selfIntersectsIndices.isEmpty else {
var simpleViolationGeometries = [GeoJsonCoordinatesGeometry]()
selfIntersectsIndices.forEach { firstIndex, secondIndices in
var point = GeoJson.Point(longitude: linearRing.segments[firstIndex].startPoint.longitude, latitude: linearRing.segments[firstIndex].startPoint.latitude)
var otherPoint = GeoJson.Point(longitude: linearRing.segments[firstIndex].endPoint.longitude, latitude: linearRing.segments[firstIndex].endPoint.latitude)
simpleViolationGeometries.append(point)
simpleViolationGeometries.append(otherPoint)
simpleViolationGeometries.append(GeoJson.LineString(points: [point, otherPoint]))

secondIndices.forEach {
point = GeoJson.Point(longitude: linearRing.segments[$0].startPoint.longitude, latitude: linearRing.segments[$0].startPoint.latitude)
otherPoint = GeoJson.Point(longitude: linearRing.segments[$0].endPoint.longitude, latitude: linearRing.segments[$0].endPoint.latitude)
simpleViolationGeometries.append(point)
simpleViolationGeometries.append(otherPoint)
simpleViolationGeometries.append(GeoJson.LineString(points: [point, otherPoint]))
}
}

return [GeoJsonSimpleViolation(problems: simpleViolationGeometries, reason: .lineIntersection)]
}

return []
}
}
}
33 changes: 33 additions & 0 deletions Sources/GeospatialSwift/API/GeoJson/Object/MultiLineString.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,39 @@ extension GeoJson.MultiLineString {
public func distance(to point: GeodesicPoint, tolerance: Double) -> Double { geoJsonLineStrings.map { $0.distance(to: point, tolerance: tolerance) }.min()! }

public func contains(_ point: GeodesicPoint, tolerance: Double) -> Bool { geoJsonLineStrings.first { $0.contains(point, tolerance: tolerance) } != nil }

public func simpleViolations(tolerance: Double) -> [GeoJsonSimpleViolation] {
let lineSimpleViolations = geoJsonLineStrings.map { $0.simpleViolations(tolerance: tolerance) }.filter { $0.count>0 }.flatMap { $0 }

guard lineSimpleViolations.isEmpty else {
return lineSimpleViolations
}

let simpleViolationIntersectionIndices = Calculator.simpleViolationIntersectionIndices(lines: lines, tolerance: tolerance)

guard simpleViolationIntersectionIndices.isEmpty else {
var violations = [GeoJsonSimpleViolation]()
simpleViolationIntersectionIndices.sorted(by: { $0.key < $1.key }).forEach { lineSegmentIndex1 in
let segment1 = lines[lineSegmentIndex1.key.lineIndex].segments[lineSegmentIndex1.key.segmentIndex]
let point1 = GeoJson.Point(longitude: segment1.startPoint.longitude, latitude: segment1.startPoint.latitude)
let point2 = GeoJson.Point(longitude: segment1.endPoint.longitude, latitude: segment1.endPoint.latitude)
let line1 = GeoJson.LineString(points: [point1, point2])

lineSegmentIndex1.value.forEach { lineSegmentIndex2 in
let segment2 = lines[lineSegmentIndex2.lineIndex].segments[lineSegmentIndex2.segmentIndex]
let point3 = GeoJson.Point(longitude: segment2.startPoint.longitude, latitude: segment2.startPoint.latitude)
let point4 = GeoJson.Point(longitude: segment2.endPoint.longitude, latitude: segment2.endPoint.latitude)
let line2 = GeoJson.LineString(points: [point3, point4])

violations += [GeoJsonSimpleViolation(problems: [point1, point2, line1, point3, point4, line2], reason: .multiLineIntersection)]
}
}

return violations
}

return []
}
}

extension GeoJson.MultiLineString {
Expand Down
8 changes: 8 additions & 0 deletions Sources/GeospatialSwift/API/GeoJson/Object/MultiPoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ extension GeoJson.MultiPoint {
public func distance(to point: GeodesicPoint, tolerance: Double) -> Double { geoJsonPoints.map { $0.distance(to: point, tolerance: tolerance) }.min()! }

public func contains(_ point: GeodesicPoint, tolerance: Double) -> Bool { geoJsonPoints.first { $0.contains(point, tolerance: tolerance) } != nil }

public func simpleViolations(tolerance: Double) -> [GeoJsonSimpleViolation] {
let duplicatePoints = Calculator.simpleViolationDuplicateIndices(points: points, tolerance: tolerance).map { geoJsonPoints[$0[0]] }

guard duplicatePoints.isEmpty else { return [GeoJsonSimpleViolation(problems: duplicatePoints, reason: .pointDuplication)] }

return []
}
}

extension GeoJson.MultiPoint {
Expand Down
52 changes: 51 additions & 1 deletion Sources/GeospatialSwift/API/GeoJson/Object/MultiPolygon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,57 @@ extension GeoJson.MultiPolygon {

public func distance(to point: GeodesicPoint, tolerance: Double) -> Double { geoJsonPolygons.map { $0.distance(to: point, tolerance: tolerance) }.min()! }

public func contains(_ point: GeodesicPoint, tolerance: Double) -> Bool { geoJsonPolygons.first { $0.contains(point, tolerance: tolerance) } != nil }
public func contains(_ point: GeodesicPoint, tolerance: Double) -> Bool { geoJsonPolygons.contains { $0.contains(point, tolerance: tolerance) } }

public func simpleViolations(tolerance: Double) -> [GeoJsonSimpleViolation] {
let polygonSimpleViolation = geoJsonPolygons.map { $0.simpleViolations(tolerance: tolerance) }.filter { !$0.isEmpty }.flatMap { $0 }

guard polygonSimpleViolation.isEmpty else {
return polygonSimpleViolation
}

let polygonContainedIndices = Calculator.simpleViolationPolygonPointsContainedInAnotherPolygonIndices(from: polygons, tolerance: tolerance)

guard polygonContainedIndices.isEmpty else {
var violations = [GeoJsonSimpleViolation]()
polygonContainedIndices.forEach { index in
var geometries = [GeoJsonCoordinatesGeometry]()
polygons[index].mainRing.segments.forEach { segment in
let point1 = GeoJson.Point(longitude: segment.startPoint.longitude, latitude: segment.startPoint.latitude)
let point2 = GeoJson.Point(longitude: segment.endPoint.longitude, latitude: segment.endPoint.latitude)
geometries.append(point1)
geometries.append(GeoJson.LineString(points: [point1, point2]))
}
violations += [GeoJsonSimpleViolation(problems: geometries, reason: .multiPolygonContained)]
}
return violations
}

let polygonLineSegmentIndiciesByIndex = Calculator.simpleViolationIntersectionIndices(from: polygons, tolerance: tolerance)

guard polygonLineSegmentIndiciesByIndex.isEmpty else {
var violations = [GeoJsonSimpleViolation]()
polygonLineSegmentIndiciesByIndex.sorted(by: { $0.key < $1.key }).forEach { lineSegmentIndicies in
let segment1 = polygons[lineSegmentIndicies.key.lineIndex].mainRing.segments[lineSegmentIndicies.key.segmentIndex]
let point1 = GeoJson.Point(longitude: segment1.startPoint.longitude, latitude: segment1.startPoint.latitude)
let point2 = GeoJson.Point(longitude: segment1.endPoint.longitude, latitude: segment1.endPoint.latitude)
let line1 = GeoJson.LineString(points: [point1, point2])

lineSegmentIndicies.value.forEach { lineSegmentIndex in
let segment2 = polygons[lineSegmentIndex.lineIndex].mainRing.segments[lineSegmentIndex.segmentIndex]
let point3 = GeoJson.Point(longitude: segment2.startPoint.longitude, latitude: segment2.startPoint.latitude)
let point4 = GeoJson.Point(longitude: segment2.endPoint.longitude, latitude: segment2.endPoint.latitude)
let line2 = GeoJson.LineString(points: [point3, point4])

violations += [GeoJsonSimpleViolation(problems: [point1, point2, line1, point3, point4, line2], reason: .multiPolygonIntersection)]
}
}

return violations
}

return []
}
}

extension GeoJson.MultiPolygon {
Expand Down
2 changes: 2 additions & 0 deletions Sources/GeospatialSwift/API/GeoJson/Object/Point.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ extension GeoJson.Point {
public func distance(to point: GeodesicPoint, tolerance: Double) -> Double { Calculator.distance(from: self, to: point, tolerance: tolerance) }

public func contains(_ point: GeodesicPoint, tolerance: Double) -> Bool { Calculator.distance(from: self, to: point, tolerance: tolerance) == 0 }

public func simpleViolations(tolerance: Double) -> [GeoJsonSimpleViolation] { [] }
}

extension GeoJson.Point {
Expand Down
Loading

0 comments on commit 7088d46

Please sign in to comment.