diff --git a/GeospatialSwift.xcodeproj/project.pbxproj b/GeospatialSwift.xcodeproj/project.pbxproj index 3506d1e..ab20eec 100644 --- a/GeospatialSwift.xcodeproj/project.pbxproj +++ b/GeospatialSwift.xcodeproj/project.pbxproj @@ -1,1359 +1,873 @@ // !$*UTF8*$! { - archiveVersion = "1"; - objectVersion = "46"; - objects = { - "GeospatialSwift::GeospatialSwift" = { - isa = "PBXNativeTarget"; - buildConfigurationList = "OBJ_96"; - buildPhases = ( - "OBJ_99", - "OBJ_135" - ); - dependencies = ( - ); - name = "GeospatialSwift"; - productName = "GeospatialSwift"; - productReference = "GeospatialSwift::GeospatialSwift::Product"; - productType = "com.apple.product-type.framework"; - }; - "GeospatialSwift::GeospatialSwift::Product" = { - isa = "PBXFileReference"; - path = "GeospatialSwift.framework"; - sourceTree = "BUILT_PRODUCTS_DIR"; - }; - "GeospatialSwift::GeospatialSwiftPackageTests::ProductTarget" = { - isa = "PBXAggregateTarget"; - buildConfigurationList = "OBJ_143"; - buildPhases = ( - ); - dependencies = ( - "OBJ_146" - ); - name = "GeospatialSwiftPackageTests"; - productName = "GeospatialSwiftPackageTests"; - }; - "GeospatialSwift::GeospatialSwiftTests" = { - isa = "PBXNativeTarget"; - buildConfigurationList = "OBJ_148"; - buildPhases = ( - "OBJ_151", - "OBJ_174" - ); - dependencies = ( - "OBJ_176" - ); - name = "GeospatialSwiftTests"; - productName = "GeospatialSwiftTests"; - productReference = "GeospatialSwift::GeospatialSwiftTests::Product"; - productType = "com.apple.product-type.bundle.unit-test"; - }; - "GeospatialSwift::GeospatialSwiftTests::Product" = { - isa = "PBXFileReference"; - path = "GeospatialSwiftTests.xctest"; - sourceTree = "BUILT_PRODUCTS_DIR"; - }; - "GeospatialSwift::SwiftPMPackageDescription" = { - isa = "PBXNativeTarget"; - buildConfigurationList = "OBJ_137"; - buildPhases = ( - "OBJ_140" - ); - dependencies = ( - ); - name = "GeospatialSwiftPackageDescription"; - productName = "GeospatialSwiftPackageDescription"; - productType = "com.apple.product-type.framework"; - }; - "OBJ_1" = { - isa = "PBXProject"; - attributes = { - LastSwiftMigration = "9999"; - LastUpgradeCheck = "9999"; - }; - buildConfigurationList = "OBJ_2"; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = "en"; - hasScannedForEncodings = "0"; - knownRegions = ( - "en" - ); - mainGroup = "OBJ_5"; - productRefGroup = "OBJ_86"; - projectDirPath = "."; - targets = ( - "GeospatialSwift::GeospatialSwift", - "GeospatialSwift::SwiftPMPackageDescription", - "GeospatialSwift::GeospatialSwiftPackageTests::ProductTarget", - "GeospatialSwift::GeospatialSwiftTests" - ); - }; - "OBJ_10" = { - isa = "PBXGroup"; - children = ( - "OBJ_11" - ); - name = "Calculator"; - path = "Calculator"; - sourceTree = ""; - }; - "OBJ_100" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_11"; - }; - "OBJ_101" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_13"; - }; - "OBJ_102" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_14"; - }; - "OBJ_103" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_15"; - }; - "OBJ_104" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_16"; - }; - "OBJ_105" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_17"; - }; - "OBJ_106" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_18"; - }; - "OBJ_107" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_19"; - }; - "OBJ_108" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_20"; - }; - "OBJ_109" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_22"; - }; - "OBJ_11" = { - isa = "PBXFileReference"; - path = "GeodesicCalculator.swift"; - sourceTree = ""; - }; - "OBJ_110" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_23"; - }; - "OBJ_111" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_24"; - }; - "OBJ_112" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_25"; - }; - "OBJ_113" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_26"; - }; - "OBJ_114" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_27"; - }; - "OBJ_115" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_28"; - }; - "OBJ_116" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_29"; - }; - "OBJ_117" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_30"; - }; - "OBJ_118" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_31"; - }; - "OBJ_119" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_33"; - }; - "OBJ_12" = { - isa = "PBXGroup"; - children = ( - "OBJ_13", - "OBJ_14", - "OBJ_15", - "OBJ_16", - "OBJ_17", - "OBJ_18", - "OBJ_19", - "OBJ_20", - "OBJ_21" - ); - name = "GeoJson"; - path = "GeoJson"; - sourceTree = ""; - }; - "OBJ_120" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_34"; - }; - "OBJ_121" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_35"; - }; - "OBJ_122" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_36"; - }; - "OBJ_123" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_37"; - }; - "OBJ_124" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_38"; - }; - "OBJ_125" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_40"; - }; - "OBJ_126" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_41"; - }; - "OBJ_127" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_42"; - }; - "OBJ_128" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_44"; - }; - "OBJ_129" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_45"; - }; - "OBJ_13" = { - isa = "PBXFileReference"; - path = "GeoJson.swift"; - sourceTree = ""; - }; - "OBJ_130" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_46"; - }; - "OBJ_131" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_47"; - }; - "OBJ_132" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_49"; - }; - "OBJ_133" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_50"; - }; - "OBJ_134" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_51"; - }; - "OBJ_135" = { - isa = "PBXFrameworksBuildPhase"; - files = ( - ); - }; - "OBJ_137" = { - isa = "XCConfigurationList"; - buildConfigurations = ( - "OBJ_138", - "OBJ_139" - ); - defaultConfigurationIsVisible = "0"; - defaultConfigurationName = "Release"; - }; - "OBJ_138" = { - isa = "XCBuildConfiguration"; - buildSettings = { - LD = "/usr/bin/true"; - OTHER_SWIFT_FLAGS = ( - "-swift-version", - "5", - "-I", - "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2", - "-target", - "x86_64-apple-macosx10.10", - "-sdk", - "/Applications/Xcode-11.3.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk", - "-package-description-version", - "5.1" - ); - SWIFT_VERSION = "5.0"; - }; - name = "Debug"; - }; - "OBJ_139" = { - isa = "XCBuildConfiguration"; - buildSettings = { - LD = "/usr/bin/true"; - OTHER_SWIFT_FLAGS = ( - "-swift-version", - "5", - "-I", - "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2", - "-target", - "x86_64-apple-macosx10.10", - "-sdk", - "/Applications/Xcode-11.3.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk", - "-package-description-version", - "5.1" - ); - SWIFT_VERSION = "5.0"; - }; - name = "Release"; - }; - "OBJ_14" = { - isa = "PBXFileReference"; - path = "GeoJsonDictionary.swift"; - sourceTree = ""; - }; - "OBJ_140" = { - isa = "PBXSourcesBuildPhase"; - files = ( - "OBJ_141" - ); - }; - "OBJ_141" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_6"; - }; - "OBJ_143" = { - isa = "XCConfigurationList"; - buildConfigurations = ( - "OBJ_144", - "OBJ_145" - ); - defaultConfigurationIsVisible = "0"; - defaultConfigurationName = "Release"; - }; - "OBJ_144" = { - isa = "XCBuildConfiguration"; - buildSettings = { - }; - name = "Debug"; - }; - "OBJ_145" = { - isa = "XCBuildConfiguration"; - buildSettings = { - }; - name = "Release"; - }; - "OBJ_146" = { - isa = "PBXTargetDependency"; - target = "GeospatialSwift::GeospatialSwiftTests"; - }; - "OBJ_148" = { - isa = "XCConfigurationList"; - buildConfigurations = ( - "OBJ_149", - "OBJ_150" - ); - defaultConfigurationIsVisible = "0"; - defaultConfigurationName = "Release"; - }; - "OBJ_149" = { - isa = "XCBuildConfiguration"; - buildSettings = { - CLANG_ENABLE_MODULES = "YES"; - EMBEDDED_CONTENT_CONTAINS_SWIFT = "YES"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PLATFORM_DIR)/Developer/Library/Frameworks" - ); - HEADER_SEARCH_PATHS = ( - "$(inherited)" - ); - INFOPLIST_FILE = "GeospatialSwift.xcodeproj/GeospatialSwiftTests_Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = "8.0"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@loader_path/../Frameworks", - "@loader_path/Frameworks" - ); - MACOSX_DEPLOYMENT_TARGET = "10.10"; - OTHER_CFLAGS = ( - "$(inherited)" - ); - OTHER_LDFLAGS = ( - "$(inherited)" - ); - OTHER_SWIFT_FLAGS = ( - "$(inherited)" - ); - SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( - "$(inherited)" - ); - SWIFT_VERSION = "5.0"; - TARGET_NAME = "GeospatialSwiftTests"; - TVOS_DEPLOYMENT_TARGET = "9.0"; - WATCHOS_DEPLOYMENT_TARGET = "2.0"; - }; - name = "Debug"; - }; - "OBJ_15" = { - isa = "PBXFileReference"; - path = "GeodesicBearing.swift"; - sourceTree = ""; - }; - "OBJ_150" = { - isa = "XCBuildConfiguration"; - buildSettings = { - CLANG_ENABLE_MODULES = "YES"; - EMBEDDED_CONTENT_CONTAINS_SWIFT = "YES"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PLATFORM_DIR)/Developer/Library/Frameworks" - ); - HEADER_SEARCH_PATHS = ( - "$(inherited)" - ); - INFOPLIST_FILE = "GeospatialSwift.xcodeproj/GeospatialSwiftTests_Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = "8.0"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@loader_path/../Frameworks", - "@loader_path/Frameworks" - ); - MACOSX_DEPLOYMENT_TARGET = "10.10"; - OTHER_CFLAGS = ( - "$(inherited)" - ); - OTHER_LDFLAGS = ( - "$(inherited)" - ); - OTHER_SWIFT_FLAGS = ( - "$(inherited)" - ); - SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( - "$(inherited)" - ); - SWIFT_VERSION = "5.0"; - TARGET_NAME = "GeospatialSwiftTests"; - TVOS_DEPLOYMENT_TARGET = "9.0"; - WATCHOS_DEPLOYMENT_TARGET = "2.0"; - }; - name = "Release"; - }; - "OBJ_151" = { - isa = "PBXSourcesBuildPhase"; - files = ( - "OBJ_152", - "OBJ_153", - "OBJ_154", - "OBJ_155", - "OBJ_156", - "OBJ_157", - "OBJ_158", - "OBJ_159", - "OBJ_160", - "OBJ_161", - "OBJ_162", - "OBJ_163", - "OBJ_164", - "OBJ_165", - "OBJ_166", - "OBJ_167", - "OBJ_168", - "OBJ_169", - "OBJ_170", - "OBJ_171", - "OBJ_172", - "OBJ_173" - ); - }; - "OBJ_152" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_55"; - }; - "OBJ_153" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_56"; - }; - "OBJ_154" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_57"; - }; - "OBJ_155" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_58"; - }; - "OBJ_156" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_60"; - }; - "OBJ_157" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_64"; - }; - "OBJ_158" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_66"; - }; - "OBJ_159" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_67"; - }; - "OBJ_16" = { - isa = "PBXFileReference"; - path = "GeodesicBoundingBox.swift"; - sourceTree = ""; - }; - "OBJ_160" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_69"; - }; - "OBJ_161" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_70"; - }; - "OBJ_162" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_71"; - }; - "OBJ_163" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_72"; - }; - "OBJ_164" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_73"; - }; - "OBJ_165" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_74"; - }; - "OBJ_166" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_75"; - }; - "OBJ_167" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_76"; - }; - "OBJ_168" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_77"; - }; - "OBJ_169" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_79"; - }; - "OBJ_17" = { - isa = "PBXFileReference"; - path = "GeodesicLine.swift"; - sourceTree = ""; - }; - "OBJ_170" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_81"; - }; - "OBJ_171" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_82"; - }; - "OBJ_172" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_84"; - }; - "OBJ_173" = { - isa = "PBXBuildFile"; - fileRef = "OBJ_85"; - }; - "OBJ_174" = { - isa = "PBXFrameworksBuildPhase"; - files = ( - "OBJ_175" - ); - }; - "OBJ_175" = { - isa = "PBXBuildFile"; - fileRef = "GeospatialSwift::GeospatialSwift::Product"; - }; - "OBJ_176" = { - isa = "PBXTargetDependency"; - target = "GeospatialSwift::GeospatialSwift"; - }; - "OBJ_18" = { - isa = "PBXFileReference"; - path = "GeodesicLineSegment.swift"; - sourceTree = ""; - }; - "OBJ_19" = { - isa = "PBXFileReference"; - path = "GeodesicPoint.swift"; - sourceTree = ""; - }; - "OBJ_2" = { - isa = "XCConfigurationList"; - buildConfigurations = ( - "OBJ_3", - "OBJ_4" - ); - defaultConfigurationIsVisible = "0"; - defaultConfigurationName = "Release"; - }; - "OBJ_20" = { - isa = "PBXFileReference"; - path = "GeodesicPolygon.swift"; - sourceTree = ""; - }; - "OBJ_21" = { - isa = "PBXGroup"; - children = ( - "OBJ_22", - "OBJ_23", - "OBJ_24", - "OBJ_25", - "OBJ_26", - "OBJ_27", - "OBJ_28", - "OBJ_29", - "OBJ_30", - "OBJ_31", - "OBJ_32" - ); - name = "Object"; - path = "Object"; - sourceTree = ""; - }; - "OBJ_22" = { - isa = "PBXFileReference"; - path = "Feature.swift"; - sourceTree = ""; - }; - "OBJ_23" = { - isa = "PBXFileReference"; - path = "FeatureCollection.swift"; - sourceTree = ""; - }; - "OBJ_24" = { - isa = "PBXFileReference"; - path = "GeometryCollection.swift"; - sourceTree = ""; - }; - "OBJ_25" = { - isa = "PBXFileReference"; - path = "LineString.swift"; - sourceTree = ""; - }; - "OBJ_26" = { - isa = "PBXFileReference"; - path = "LinearRing.swift"; - sourceTree = ""; - }; - "OBJ_27" = { - isa = "PBXFileReference"; - path = "MultiLineString.swift"; - sourceTree = ""; - }; - "OBJ_28" = { - isa = "PBXFileReference"; - path = "MultiPoint.swift"; - sourceTree = ""; - }; - "OBJ_29" = { - isa = "PBXFileReference"; - path = "MultiPolygon.swift"; - sourceTree = ""; - }; - "OBJ_3" = { - isa = "XCBuildConfiguration"; - buildSettings = { - CLANG_ENABLE_OBJC_ARC = "YES"; - COMBINE_HIDPI_IMAGES = "YES"; - COPY_PHASE_STRIP = "NO"; - DEBUG_INFORMATION_FORMAT = "dwarf"; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_NS_ASSERTIONS = "YES"; - GCC_OPTIMIZATION_LEVEL = "0"; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "SWIFT_PACKAGE=1", - "DEBUG=1" - ); - MACOSX_DEPLOYMENT_TARGET = "10.10"; - ONLY_ACTIVE_ARCH = "YES"; - OTHER_SWIFT_FLAGS = ( - "$(inherited)", - "-DXcode" - ); - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = "macosx"; - SUPPORTED_PLATFORMS = ( - "macosx", - "iphoneos", - "iphonesimulator", - "appletvos", - "appletvsimulator", - "watchos", - "watchsimulator" - ); - SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( - "$(inherited)", - "SWIFT_PACKAGE", - "DEBUG" - ); - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - USE_HEADERMAP = "NO"; - }; - name = "Debug"; - }; - "OBJ_30" = { - isa = "PBXFileReference"; - path = "Point.swift"; - sourceTree = ""; - }; - "OBJ_31" = { - isa = "PBXFileReference"; - path = "Polygon.swift"; - sourceTree = ""; - }; - "OBJ_32" = { - isa = "PBXGroup"; - children = ( - "OBJ_33", - "OBJ_34", - "OBJ_35", - "OBJ_36", - "OBJ_37", - "OBJ_38" - ); - name = "Protocol"; - path = "Protocol"; - sourceTree = ""; - }; - "OBJ_33" = { - isa = "PBXFileReference"; - path = "GeoJsonClosedGeometry.swift"; - sourceTree = ""; - }; - "OBJ_34" = { - isa = "PBXFileReference"; - path = "GeoJsonCoordinatesGeometry.swift"; - sourceTree = ""; - }; - "OBJ_35" = { - isa = "PBXFileReference"; - path = "GeoJsonGeometry.swift"; - sourceTree = ""; - }; - "OBJ_36" = { - isa = "PBXFileReference"; - path = "GeoJsonLinearGeometry.swift"; - sourceTree = ""; - }; - "OBJ_37" = { - isa = "PBXFileReference"; - path = "GeoJsonObject.swift"; - sourceTree = ""; - }; - "OBJ_38" = { - isa = "PBXFileReference"; - path = "GeoJsonObjectType.swift"; - sourceTree = ""; - }; - "OBJ_39" = { - isa = "PBXGroup"; - children = ( - "OBJ_40", - "OBJ_41" - ); - name = "Geohash"; - path = "Geohash"; - sourceTree = ""; - }; - "OBJ_4" = { - isa = "XCBuildConfiguration"; - buildSettings = { - CLANG_ENABLE_OBJC_ARC = "YES"; - COMBINE_HIDPI_IMAGES = "YES"; - COPY_PHASE_STRIP = "YES"; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_OPTIMIZATION_LEVEL = "s"; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "SWIFT_PACKAGE=1" - ); - MACOSX_DEPLOYMENT_TARGET = "10.10"; - OTHER_SWIFT_FLAGS = ( - "$(inherited)", - "-DXcode" - ); - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = "macosx"; - SUPPORTED_PLATFORMS = ( - "macosx", - "iphoneos", - "iphonesimulator", - "appletvos", - "appletvsimulator", - "watchos", - "watchsimulator" - ); - SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( - "$(inherited)", - "SWIFT_PACKAGE" - ); - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - USE_HEADERMAP = "NO"; - }; - name = "Release"; - }; - "OBJ_40" = { - isa = "PBXFileReference"; - path = "GeohashBox.swift"; - sourceTree = ""; - }; - "OBJ_41" = { - isa = "PBXFileReference"; - path = "GeohashCoder.swift"; - sourceTree = ""; - }; - "OBJ_42" = { - isa = "PBXFileReference"; - path = "Geospatial.swift"; - sourceTree = ""; - }; - "OBJ_43" = { - isa = "PBXGroup"; - children = ( - "OBJ_44", - "OBJ_45", - "OBJ_46", - "OBJ_47" - ); - name = "Extensions"; - path = "Extensions"; - sourceTree = ""; - }; - "OBJ_44" = { - isa = "PBXFileReference"; - path = "Array.swift"; - sourceTree = ""; - }; - "OBJ_45" = { - isa = "PBXFileReference"; - path = "Bundle.swift"; - sourceTree = ""; - }; - "OBJ_46" = { - isa = "PBXFileReference"; - path = "FloatingPoint.swift"; - sourceTree = ""; - }; - "OBJ_47" = { - isa = "PBXFileReference"; - path = "Result.swift"; - sourceTree = ""; - }; - "OBJ_48" = { - isa = "PBXGroup"; - children = ( - "OBJ_49", - "OBJ_50", - "OBJ_51" - ); - name = "Parser"; - path = "Parser"; - sourceTree = ""; - }; - "OBJ_49" = { - isa = "PBXFileReference"; - path = "GeoJsonParser.swift"; - sourceTree = ""; - }; - "OBJ_5" = { - isa = "PBXGroup"; - children = ( - "OBJ_6", - "OBJ_7", - "OBJ_52", - "OBJ_86", - "OBJ_89", - "OBJ_90", - "OBJ_91", - "OBJ_92", - "OBJ_93", - "OBJ_94" - ); - path = ""; - sourceTree = ""; - }; - "OBJ_50" = { - isa = "PBXFileReference"; - path = "InvalidGeoJson.swift"; - sourceTree = ""; - }; - "OBJ_51" = { - isa = "PBXFileReference"; - path = "WktParser.swift"; - sourceTree = ""; - }; - "OBJ_52" = { - isa = "PBXGroup"; - children = ( - "OBJ_53" - ); - name = "Tests"; - path = ""; - sourceTree = "SOURCE_ROOT"; - }; - "OBJ_53" = { - isa = "PBXGroup"; - children = ( - "OBJ_54", - "OBJ_59", - "OBJ_61" - ); - name = "GeospatialSwiftTests"; - path = "Tests/GeospatialSwiftTests"; - sourceTree = "SOURCE_ROOT"; - }; - "OBJ_54" = { - isa = "PBXGroup"; - children = ( - "OBJ_55", - "OBJ_56", - "OBJ_57", - "OBJ_58" - ); - name = "Data"; - path = "Data"; - sourceTree = ""; - }; - "OBJ_55" = { - isa = "PBXFileReference"; - path = "GeoJsonTestJson.swift"; - sourceTree = ""; - }; - "OBJ_56" = { - isa = "PBXFileReference"; - path = "GeoTestHelper.swift"; - sourceTree = ""; - }; - "OBJ_57" = { - isa = "PBXFileReference"; - path = "MockData.swift"; - sourceTree = ""; - }; - "OBJ_58" = { - isa = "PBXFileReference"; - path = "WktTestJson.swift"; - sourceTree = ""; - }; - "OBJ_59" = { - isa = "PBXGroup"; - children = ( - "OBJ_60" - ); - name = "Performance"; - path = "Performance"; - sourceTree = ""; - }; - "OBJ_6" = { - isa = "PBXFileReference"; - explicitFileType = "sourcecode.swift"; - path = "Package.swift"; - sourceTree = ""; - }; - "OBJ_60" = { - isa = "PBXFileReference"; - path = "GeoJsonParsingPerformanceTests.swift"; - sourceTree = ""; - }; - "OBJ_61" = { - isa = "PBXGroup"; - children = ( - "OBJ_62", - "OBJ_80", - "OBJ_83" - ); - name = "Test"; - path = "Test"; - sourceTree = ""; - }; - "OBJ_62" = { - isa = "PBXGroup"; - children = ( - "OBJ_63", - "OBJ_65", - "OBJ_78" - ); - name = "API"; - path = "API"; - sourceTree = ""; - }; - "OBJ_63" = { - isa = "PBXGroup"; - children = ( - "OBJ_64" - ); - name = "Calculator"; - path = "Calculator"; - sourceTree = ""; - }; - "OBJ_64" = { - isa = "PBXFileReference"; - path = "GeodesicCalculatorTests.swift"; - sourceTree = ""; - }; - "OBJ_65" = { - isa = "PBXGroup"; - children = ( - "OBJ_66", - "OBJ_67", - "OBJ_68" - ); - name = "GeoJson"; - path = "GeoJson"; - sourceTree = ""; - }; - "OBJ_66" = { - isa = "PBXFileReference"; - path = "BoundingBoxTests.swift"; - sourceTree = ""; - }; - "OBJ_67" = { - isa = "PBXFileReference"; - path = "GeodesicLineSegmentTests.swift"; - sourceTree = ""; - }; - "OBJ_68" = { - isa = "PBXGroup"; - children = ( - "OBJ_69", - "OBJ_70", - "OBJ_71", - "OBJ_72", - "OBJ_73", - "OBJ_74", - "OBJ_75", - "OBJ_76", - "OBJ_77" - ); - name = "Object"; - path = "Object"; - sourceTree = ""; - }; - "OBJ_69" = { - isa = "PBXFileReference"; - path = "FeatureCollectionTests.swift"; - sourceTree = ""; - }; - "OBJ_7" = { - isa = "PBXGroup"; - children = ( - "OBJ_8" - ); - name = "Sources"; - path = ""; - sourceTree = "SOURCE_ROOT"; - }; - "OBJ_70" = { - isa = "PBXFileReference"; - path = "FeatureTests.swift"; - sourceTree = ""; - }; - "OBJ_71" = { - isa = "PBXFileReference"; - path = "GeometryCollectionTests.swift"; - sourceTree = ""; - }; - "OBJ_72" = { - isa = "PBXFileReference"; - path = "LineStringTests.swift"; - sourceTree = ""; - }; - "OBJ_73" = { - isa = "PBXFileReference"; - path = "MultiLineStringTests.swift"; - sourceTree = ""; - }; - "OBJ_74" = { - isa = "PBXFileReference"; - path = "MultiPointTests.swift"; - sourceTree = ""; - }; - "OBJ_75" = { - isa = "PBXFileReference"; - path = "MultiPolygonTests.swift"; - sourceTree = ""; - }; - "OBJ_76" = { - isa = "PBXFileReference"; - path = "PointTests.swift"; - sourceTree = ""; - }; - "OBJ_77" = { - isa = "PBXFileReference"; - path = "PolygonTests.swift"; - sourceTree = ""; - }; - "OBJ_78" = { - isa = "PBXGroup"; - children = ( - "OBJ_79" - ); - name = "Geohash"; - path = "Geohash"; - sourceTree = ""; - }; - "OBJ_79" = { - isa = "PBXFileReference"; - path = "GeohashCoderTests.swift"; - sourceTree = ""; - }; - "OBJ_8" = { - isa = "PBXGroup"; - children = ( - "OBJ_9", - "OBJ_43", - "OBJ_48" - ); - name = "GeospatialSwift"; - path = "Sources/GeospatialSwift"; - sourceTree = "SOURCE_ROOT"; - }; - "OBJ_80" = { - isa = "PBXGroup"; - children = ( - "OBJ_81", - "OBJ_82" - ); - name = "Extensions"; - path = "Extensions"; - sourceTree = ""; - }; - "OBJ_81" = { - isa = "PBXFileReference"; - path = "Assertions.swift"; - sourceTree = ""; - }; - "OBJ_82" = { - isa = "PBXFileReference"; - path = "Equatable.swift"; - sourceTree = ""; - }; - "OBJ_83" = { - isa = "PBXGroup"; - children = ( - "OBJ_84", - "OBJ_85" - ); - name = "Parser"; - path = "Parser"; - sourceTree = ""; - }; - "OBJ_84" = { - isa = "PBXFileReference"; - path = "GeoJsonParserTests.swift"; - sourceTree = ""; - }; - "OBJ_85" = { - isa = "PBXFileReference"; - path = "WktParserTests.swift"; - sourceTree = ""; - }; - "OBJ_86" = { - isa = "PBXGroup"; - children = ( - "GeospatialSwift::GeospatialSwiftTests::Product", - "GeospatialSwift::GeospatialSwift::Product" - ); - name = "Products"; - path = ""; - sourceTree = "BUILT_PRODUCTS_DIR"; - }; - "OBJ_89" = { - isa = "PBXFileReference"; - path = "Tools"; - sourceTree = "SOURCE_ROOT"; - }; - "OBJ_9" = { - isa = "PBXGroup"; - children = ( - "OBJ_10", - "OBJ_12", - "OBJ_39", - "OBJ_42" - ); - name = "API"; - path = "API"; - sourceTree = ""; - }; - "OBJ_90" = { - isa = "PBXFileReference"; - path = "Scripts"; - sourceTree = "SOURCE_ROOT"; - }; - "OBJ_91" = { - isa = "PBXFileReference"; - path = "LICENSE"; - sourceTree = ""; - }; - "OBJ_92" = { - isa = "PBXFileReference"; - path = "MAINTAINERS"; - sourceTree = ""; - }; - "OBJ_93" = { - isa = "PBXFileReference"; - path = "README.md"; - sourceTree = ""; - }; - "OBJ_94" = { - isa = "PBXFileReference"; - path = "CONTRIBUTING.md"; - sourceTree = ""; - }; - "OBJ_96" = { - isa = "XCConfigurationList"; - buildConfigurations = ( - "OBJ_97", - "OBJ_98" - ); - defaultConfigurationIsVisible = "0"; - defaultConfigurationName = "Release"; - }; - "OBJ_97" = { - isa = "XCBuildConfiguration"; - buildSettings = { - ENABLE_TESTABILITY = "YES"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PLATFORM_DIR)/Developer/Library/Frameworks" - ); - HEADER_SEARCH_PATHS = ( - "$(inherited)" - ); - INFOPLIST_FILE = "GeospatialSwift.xcodeproj/GeospatialSwift_Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = "8.0"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx" - ); - MACOSX_DEPLOYMENT_TARGET = "10.10"; - OTHER_CFLAGS = ( - "$(inherited)" - ); - OTHER_LDFLAGS = ( - "$(inherited)" - ); - OTHER_SWIFT_FLAGS = ( - "$(inherited)" - ); - PRODUCT_BUNDLE_IDENTIFIER = "GeospatialSwift"; - PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = "YES"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( - "$(inherited)" - ); - SWIFT_VERSION = "5.0"; - TARGET_NAME = "GeospatialSwift"; - TVOS_DEPLOYMENT_TARGET = "9.0"; - WATCHOS_DEPLOYMENT_TARGET = "2.0"; - }; - name = "Debug"; - }; - "OBJ_98" = { - isa = "XCBuildConfiguration"; - buildSettings = { - ENABLE_TESTABILITY = "YES"; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PLATFORM_DIR)/Developer/Library/Frameworks" - ); - HEADER_SEARCH_PATHS = ( - "$(inherited)" - ); - INFOPLIST_FILE = "GeospatialSwift.xcodeproj/GeospatialSwift_Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = "8.0"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx" - ); - MACOSX_DEPLOYMENT_TARGET = "10.10"; - OTHER_CFLAGS = ( - "$(inherited)" - ); - OTHER_LDFLAGS = ( - "$(inherited)" - ); - OTHER_SWIFT_FLAGS = ( - "$(inherited)" - ); - PRODUCT_BUNDLE_IDENTIFIER = "GeospatialSwift"; - PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = "YES"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( - "$(inherited)" - ); - SWIFT_VERSION = "5.0"; - TARGET_NAME = "GeospatialSwift"; - TVOS_DEPLOYMENT_TARGET = "9.0"; - WATCHOS_DEPLOYMENT_TARGET = "2.0"; - }; - name = "Release"; - }; - "OBJ_99" = { - isa = "PBXSourcesBuildPhase"; - files = ( - "OBJ_100", - "OBJ_101", - "OBJ_102", - "OBJ_103", - "OBJ_104", - "OBJ_105", - "OBJ_106", - "OBJ_107", - "OBJ_108", - "OBJ_109", - "OBJ_110", - "OBJ_111", - "OBJ_112", - "OBJ_113", - "OBJ_114", - "OBJ_115", - "OBJ_116", - "OBJ_117", - "OBJ_118", - "OBJ_119", - "OBJ_120", - "OBJ_121", - "OBJ_122", - "OBJ_123", - "OBJ_124", - "OBJ_125", - "OBJ_126", - "OBJ_127", - "OBJ_128", - "OBJ_129", - "OBJ_130", - "OBJ_131", - "OBJ_132", - "OBJ_133", - "OBJ_134" - ); - }; - }; - rootObject = "OBJ_1"; + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXAggregateTarget section */ + "GeospatialSwift::GeospatialSwiftPackageTests::ProductTarget" /* GeospatialSwiftPackageTests */ = { + isa = PBXAggregateTarget; + buildConfigurationList = OBJ_143 /* Build configuration list for PBXAggregateTarget "GeospatialSwiftPackageTests" */; + buildPhases = ( + ); + dependencies = ( + OBJ_146 /* PBXTargetDependency */, + ); + name = GeospatialSwiftPackageTests; + productName = GeospatialSwiftPackageTests; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 60944696240FE3B70021D3A6 /* GeoJsonSimpleViolation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60944695240FE3B70021D3A6 /* GeoJsonSimpleViolation.swift */; }; + OBJ_100 /* GeodesicCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_11 /* GeodesicCalculator.swift */; }; + OBJ_101 /* GeoJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_13 /* GeoJson.swift */; }; + OBJ_102 /* GeoJsonDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_14 /* GeoJsonDictionary.swift */; }; + OBJ_103 /* GeodesicBearing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_15 /* GeodesicBearing.swift */; }; + OBJ_104 /* GeodesicBoundingBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_16 /* GeodesicBoundingBox.swift */; }; + OBJ_105 /* GeodesicLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_17 /* GeodesicLine.swift */; }; + OBJ_106 /* GeodesicLineSegment.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_18 /* GeodesicLineSegment.swift */; }; + OBJ_107 /* GeodesicPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_19 /* GeodesicPoint.swift */; }; + OBJ_108 /* GeodesicPolygon.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_20 /* GeodesicPolygon.swift */; }; + OBJ_109 /* Feature.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_22 /* Feature.swift */; }; + OBJ_110 /* FeatureCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_23 /* FeatureCollection.swift */; }; + OBJ_111 /* GeometryCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_24 /* GeometryCollection.swift */; }; + OBJ_112 /* LineString.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_25 /* LineString.swift */; }; + OBJ_113 /* LinearRing.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_26 /* LinearRing.swift */; }; + OBJ_114 /* MultiLineString.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_27 /* MultiLineString.swift */; }; + OBJ_115 /* MultiPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_28 /* MultiPoint.swift */; }; + OBJ_116 /* MultiPolygon.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_29 /* MultiPolygon.swift */; }; + OBJ_117 /* Point.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_30 /* Point.swift */; }; + OBJ_118 /* Polygon.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_31 /* Polygon.swift */; }; + OBJ_119 /* GeoJsonClosedGeometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_33 /* GeoJsonClosedGeometry.swift */; }; + OBJ_120 /* GeoJsonCoordinatesGeometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_34 /* GeoJsonCoordinatesGeometry.swift */; }; + OBJ_121 /* GeoJsonGeometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_35 /* GeoJsonGeometry.swift */; }; + OBJ_122 /* GeoJsonLinearGeometry.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_36 /* GeoJsonLinearGeometry.swift */; }; + OBJ_123 /* GeoJsonObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_37 /* GeoJsonObject.swift */; }; + OBJ_124 /* GeoJsonObjectType.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_38 /* GeoJsonObjectType.swift */; }; + OBJ_125 /* GeohashBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_40 /* GeohashBox.swift */; }; + OBJ_126 /* GeohashCoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_41 /* GeohashCoder.swift */; }; + OBJ_127 /* Geospatial.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_42 /* Geospatial.swift */; }; + OBJ_128 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_44 /* Array.swift */; }; + OBJ_129 /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_45 /* Bundle.swift */; }; + OBJ_130 /* FloatingPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_46 /* FloatingPoint.swift */; }; + OBJ_131 /* Result.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_47 /* Result.swift */; }; + OBJ_132 /* GeoJsonParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_49 /* GeoJsonParser.swift */; }; + OBJ_133 /* InvalidGeoJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_50 /* InvalidGeoJson.swift */; }; + OBJ_134 /* WktParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_51 /* WktParser.swift */; }; + OBJ_141 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; + OBJ_152 /* GeoJsonTestJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_55 /* GeoJsonTestJson.swift */; }; + OBJ_153 /* GeoTestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_56 /* GeoTestHelper.swift */; }; + OBJ_154 /* MockData.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_57 /* MockData.swift */; }; + OBJ_155 /* WktTestJson.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_58 /* WktTestJson.swift */; }; + OBJ_156 /* GeoJsonParsingPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_60 /* GeoJsonParsingPerformanceTests.swift */; }; + OBJ_157 /* GeodesicCalculatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_64 /* GeodesicCalculatorTests.swift */; }; + OBJ_158 /* BoundingBoxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_66 /* BoundingBoxTests.swift */; }; + OBJ_159 /* GeodesicLineSegmentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_67 /* GeodesicLineSegmentTests.swift */; }; + OBJ_160 /* FeatureCollectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_69 /* FeatureCollectionTests.swift */; }; + OBJ_161 /* FeatureTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_70 /* FeatureTests.swift */; }; + OBJ_162 /* GeometryCollectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_71 /* GeometryCollectionTests.swift */; }; + OBJ_163 /* LineStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_72 /* LineStringTests.swift */; }; + OBJ_164 /* MultiLineStringTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_73 /* MultiLineStringTests.swift */; }; + OBJ_165 /* MultiPointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_74 /* MultiPointTests.swift */; }; + OBJ_166 /* MultiPolygonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_75 /* MultiPolygonTests.swift */; }; + OBJ_167 /* PointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_76 /* PointTests.swift */; }; + OBJ_168 /* PolygonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_77 /* PolygonTests.swift */; }; + OBJ_169 /* GeohashCoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_79 /* GeohashCoderTests.swift */; }; + OBJ_170 /* Assertions.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_81 /* Assertions.swift */; }; + OBJ_171 /* Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_82 /* Equatable.swift */; }; + OBJ_172 /* GeoJsonParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_84 /* GeoJsonParserTests.swift */; }; + OBJ_173 /* WktParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_85 /* WktParserTests.swift */; }; + OBJ_175 /* GeospatialSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "GeospatialSwift::GeospatialSwift::Product" /* GeospatialSwift.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 60944684240EA1020021D3A6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = OBJ_1 /* Project object */; + proxyType = 1; + remoteGlobalIDString = "GeospatialSwift::GeospatialSwift"; + remoteInfo = GeospatialSwift; + }; + 60944685240EA1040021D3A6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = OBJ_1 /* Project object */; + proxyType = 1; + remoteGlobalIDString = "GeospatialSwift::GeospatialSwiftTests"; + remoteInfo = GeospatialSwiftTests; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 60944695240FE3B70021D3A6 /* GeoJsonSimpleViolation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoJsonSimpleViolation.swift; sourceTree = ""; }; + "GeospatialSwift::GeospatialSwift::Product" /* GeospatialSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = GeospatialSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + "GeospatialSwift::GeospatialSwiftTests::Product" /* GeospatialSwiftTests.xctest */ = {isa = PBXFileReference; lastKnownFileType = file; path = GeospatialSwiftTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + OBJ_11 /* GeodesicCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeodesicCalculator.swift; sourceTree = ""; }; + OBJ_13 /* GeoJson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoJson.swift; sourceTree = ""; }; + OBJ_14 /* GeoJsonDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoJsonDictionary.swift; sourceTree = ""; }; + OBJ_15 /* GeodesicBearing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeodesicBearing.swift; sourceTree = ""; }; + OBJ_16 /* GeodesicBoundingBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeodesicBoundingBox.swift; sourceTree = ""; }; + OBJ_17 /* GeodesicLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeodesicLine.swift; sourceTree = ""; }; + OBJ_18 /* GeodesicLineSegment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeodesicLineSegment.swift; sourceTree = ""; }; + OBJ_19 /* GeodesicPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeodesicPoint.swift; sourceTree = ""; }; + OBJ_20 /* GeodesicPolygon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeodesicPolygon.swift; sourceTree = ""; }; + OBJ_22 /* Feature.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Feature.swift; sourceTree = ""; }; + OBJ_23 /* FeatureCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureCollection.swift; sourceTree = ""; }; + OBJ_24 /* GeometryCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeometryCollection.swift; sourceTree = ""; }; + OBJ_25 /* LineString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineString.swift; sourceTree = ""; }; + OBJ_26 /* LinearRing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinearRing.swift; sourceTree = ""; }; + OBJ_27 /* MultiLineString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiLineString.swift; sourceTree = ""; }; + OBJ_28 /* MultiPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiPoint.swift; sourceTree = ""; }; + OBJ_29 /* MultiPolygon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiPolygon.swift; sourceTree = ""; }; + OBJ_30 /* Point.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Point.swift; sourceTree = ""; }; + OBJ_31 /* Polygon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Polygon.swift; sourceTree = ""; }; + OBJ_33 /* GeoJsonClosedGeometry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoJsonClosedGeometry.swift; sourceTree = ""; }; + OBJ_34 /* GeoJsonCoordinatesGeometry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoJsonCoordinatesGeometry.swift; sourceTree = ""; }; + OBJ_35 /* GeoJsonGeometry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoJsonGeometry.swift; sourceTree = ""; }; + OBJ_36 /* GeoJsonLinearGeometry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoJsonLinearGeometry.swift; sourceTree = ""; }; + OBJ_37 /* GeoJsonObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoJsonObject.swift; sourceTree = ""; }; + OBJ_38 /* GeoJsonObjectType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoJsonObjectType.swift; sourceTree = ""; }; + OBJ_40 /* GeohashBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeohashBox.swift; sourceTree = ""; }; + OBJ_41 /* GeohashCoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeohashCoder.swift; sourceTree = ""; }; + OBJ_42 /* Geospatial.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Geospatial.swift; sourceTree = ""; }; + OBJ_44 /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = ""; }; + OBJ_45 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = ""; }; + OBJ_46 /* FloatingPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingPoint.swift; sourceTree = ""; }; + OBJ_47 /* Result.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Result.swift; sourceTree = ""; }; + OBJ_49 /* GeoJsonParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoJsonParser.swift; sourceTree = ""; }; + OBJ_50 /* InvalidGeoJson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvalidGeoJson.swift; sourceTree = ""; }; + OBJ_51 /* WktParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WktParser.swift; sourceTree = ""; }; + OBJ_55 /* GeoJsonTestJson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoJsonTestJson.swift; sourceTree = ""; }; + OBJ_56 /* GeoTestHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoTestHelper.swift; sourceTree = ""; }; + OBJ_57 /* MockData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockData.swift; sourceTree = ""; }; + OBJ_58 /* WktTestJson.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WktTestJson.swift; sourceTree = ""; }; + OBJ_6 /* Package.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; + OBJ_60 /* GeoJsonParsingPerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoJsonParsingPerformanceTests.swift; sourceTree = ""; }; + OBJ_64 /* GeodesicCalculatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeodesicCalculatorTests.swift; sourceTree = ""; }; + OBJ_66 /* BoundingBoxTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoundingBoxTests.swift; sourceTree = ""; }; + OBJ_67 /* GeodesicLineSegmentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeodesicLineSegmentTests.swift; sourceTree = ""; }; + OBJ_69 /* FeatureCollectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureCollectionTests.swift; sourceTree = ""; }; + OBJ_70 /* FeatureTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureTests.swift; sourceTree = ""; }; + OBJ_71 /* GeometryCollectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeometryCollectionTests.swift; sourceTree = ""; }; + OBJ_72 /* LineStringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineStringTests.swift; sourceTree = ""; }; + OBJ_73 /* MultiLineStringTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiLineStringTests.swift; sourceTree = ""; }; + OBJ_74 /* MultiPointTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiPointTests.swift; sourceTree = ""; }; + OBJ_75 /* MultiPolygonTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiPolygonTests.swift; sourceTree = ""; }; + OBJ_76 /* PointTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointTests.swift; sourceTree = ""; }; + OBJ_77 /* PolygonTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PolygonTests.swift; sourceTree = ""; }; + OBJ_79 /* GeohashCoderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeohashCoderTests.swift; sourceTree = ""; }; + OBJ_81 /* Assertions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Assertions.swift; sourceTree = ""; }; + OBJ_82 /* Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Equatable.swift; sourceTree = ""; }; + OBJ_84 /* GeoJsonParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeoJsonParserTests.swift; sourceTree = ""; }; + OBJ_85 /* WktParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WktParserTests.swift; sourceTree = ""; }; + OBJ_89 /* Tools */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Tools; sourceTree = SOURCE_ROOT; }; + OBJ_90 /* Scripts */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Scripts; sourceTree = SOURCE_ROOT; }; + OBJ_91 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + OBJ_92 /* MAINTAINERS */ = {isa = PBXFileReference; lastKnownFileType = text; path = MAINTAINERS; sourceTree = ""; }; + OBJ_93 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + OBJ_94 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + OBJ_135 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + OBJ_174 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 0; + files = ( + OBJ_175 /* GeospatialSwift.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 60944697240FE4130021D3A6 /* Geodesic */ = { + isa = PBXGroup; + children = ( + OBJ_15 /* GeodesicBearing.swift */, + OBJ_16 /* GeodesicBoundingBox.swift */, + OBJ_17 /* GeodesicLine.swift */, + OBJ_18 /* GeodesicLineSegment.swift */, + OBJ_19 /* GeodesicPoint.swift */, + OBJ_20 /* GeodesicPolygon.swift */, + ); + path = Geodesic; + sourceTree = ""; + }; + OBJ_10 /* Calculator */ = { + isa = PBXGroup; + children = ( + OBJ_11 /* GeodesicCalculator.swift */, + ); + path = Calculator; + sourceTree = ""; + }; + OBJ_12 /* GeoJson */ = { + isa = PBXGroup; + children = ( + OBJ_13 /* GeoJson.swift */, + OBJ_14 /* GeoJsonDictionary.swift */, + 60944695240FE3B70021D3A6 /* GeoJsonSimpleViolation.swift */, + OBJ_21 /* Object */, + ); + path = GeoJson; + sourceTree = ""; + }; + OBJ_21 /* Object */ = { + isa = PBXGroup; + children = ( + OBJ_22 /* Feature.swift */, + OBJ_23 /* FeatureCollection.swift */, + OBJ_24 /* GeometryCollection.swift */, + OBJ_26 /* LinearRing.swift */, + OBJ_25 /* LineString.swift */, + OBJ_27 /* MultiLineString.swift */, + OBJ_28 /* MultiPoint.swift */, + OBJ_29 /* MultiPolygon.swift */, + OBJ_30 /* Point.swift */, + OBJ_31 /* Polygon.swift */, + OBJ_32 /* Protocol */, + ); + path = Object; + sourceTree = ""; + }; + OBJ_32 /* Protocol */ = { + isa = PBXGroup; + children = ( + OBJ_33 /* GeoJsonClosedGeometry.swift */, + OBJ_34 /* GeoJsonCoordinatesGeometry.swift */, + OBJ_35 /* GeoJsonGeometry.swift */, + OBJ_36 /* GeoJsonLinearGeometry.swift */, + OBJ_37 /* GeoJsonObject.swift */, + OBJ_38 /* GeoJsonObjectType.swift */, + ); + path = Protocol; + sourceTree = ""; + }; + OBJ_39 /* Geohash */ = { + isa = PBXGroup; + children = ( + OBJ_40 /* GeohashBox.swift */, + OBJ_41 /* GeohashCoder.swift */, + ); + path = Geohash; + sourceTree = ""; + }; + OBJ_43 /* Extensions */ = { + isa = PBXGroup; + children = ( + OBJ_44 /* Array.swift */, + OBJ_45 /* Bundle.swift */, + OBJ_46 /* FloatingPoint.swift */, + OBJ_47 /* Result.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + OBJ_48 /* Parser */ = { + isa = PBXGroup; + children = ( + OBJ_49 /* GeoJsonParser.swift */, + OBJ_50 /* InvalidGeoJson.swift */, + OBJ_51 /* WktParser.swift */, + ); + path = Parser; + sourceTree = ""; + }; + OBJ_5 = { + isa = PBXGroup; + children = ( + OBJ_6 /* Package.swift */, + OBJ_7 /* Sources */, + OBJ_52 /* Tests */, + OBJ_86 /* Products */, + OBJ_89 /* Tools */, + OBJ_90 /* Scripts */, + OBJ_91 /* LICENSE */, + OBJ_92 /* MAINTAINERS */, + OBJ_93 /* README.md */, + OBJ_94 /* CONTRIBUTING.md */, + ); + sourceTree = ""; + }; + OBJ_52 /* Tests */ = { + isa = PBXGroup; + children = ( + OBJ_53 /* GeospatialSwiftTests */, + ); + name = Tests; + sourceTree = SOURCE_ROOT; + }; + OBJ_53 /* GeospatialSwiftTests */ = { + isa = PBXGroup; + children = ( + OBJ_54 /* Data */, + OBJ_59 /* Performance */, + OBJ_61 /* Test */, + ); + name = GeospatialSwiftTests; + path = Tests/GeospatialSwiftTests; + sourceTree = SOURCE_ROOT; + }; + OBJ_54 /* Data */ = { + isa = PBXGroup; + children = ( + OBJ_55 /* GeoJsonTestJson.swift */, + OBJ_56 /* GeoTestHelper.swift */, + OBJ_57 /* MockData.swift */, + OBJ_58 /* WktTestJson.swift */, + ); + path = Data; + sourceTree = ""; + }; + OBJ_59 /* Performance */ = { + isa = PBXGroup; + children = ( + OBJ_60 /* GeoJsonParsingPerformanceTests.swift */, + ); + path = Performance; + sourceTree = ""; + }; + OBJ_61 /* Test */ = { + isa = PBXGroup; + children = ( + OBJ_62 /* API */, + OBJ_80 /* Extensions */, + OBJ_83 /* Parser */, + ); + path = Test; + sourceTree = ""; + }; + OBJ_62 /* API */ = { + isa = PBXGroup; + children = ( + OBJ_63 /* Calculator */, + OBJ_65 /* GeoJson */, + OBJ_78 /* Geohash */, + ); + path = API; + sourceTree = ""; + }; + OBJ_63 /* Calculator */ = { + isa = PBXGroup; + children = ( + OBJ_64 /* GeodesicCalculatorTests.swift */, + ); + path = Calculator; + sourceTree = ""; + }; + OBJ_65 /* GeoJson */ = { + isa = PBXGroup; + children = ( + OBJ_66 /* BoundingBoxTests.swift */, + OBJ_67 /* GeodesicLineSegmentTests.swift */, + OBJ_68 /* Object */, + ); + path = GeoJson; + sourceTree = ""; + }; + OBJ_68 /* Object */ = { + isa = PBXGroup; + children = ( + OBJ_69 /* FeatureCollectionTests.swift */, + OBJ_70 /* FeatureTests.swift */, + OBJ_71 /* GeometryCollectionTests.swift */, + OBJ_72 /* LineStringTests.swift */, + OBJ_73 /* MultiLineStringTests.swift */, + OBJ_74 /* MultiPointTests.swift */, + OBJ_75 /* MultiPolygonTests.swift */, + OBJ_76 /* PointTests.swift */, + OBJ_77 /* PolygonTests.swift */, + ); + path = Object; + sourceTree = ""; + }; + OBJ_7 /* Sources */ = { + isa = PBXGroup; + children = ( + OBJ_8 /* GeospatialSwift */, + ); + name = Sources; + sourceTree = SOURCE_ROOT; + }; + OBJ_78 /* Geohash */ = { + isa = PBXGroup; + children = ( + OBJ_79 /* GeohashCoderTests.swift */, + ); + path = Geohash; + sourceTree = ""; + }; + OBJ_8 /* GeospatialSwift */ = { + isa = PBXGroup; + children = ( + OBJ_9 /* API */, + OBJ_43 /* Extensions */, + OBJ_48 /* Parser */, + ); + name = GeospatialSwift; + path = Sources/GeospatialSwift; + sourceTree = SOURCE_ROOT; + }; + OBJ_80 /* Extensions */ = { + isa = PBXGroup; + children = ( + OBJ_81 /* Assertions.swift */, + OBJ_82 /* Equatable.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + OBJ_83 /* Parser */ = { + isa = PBXGroup; + children = ( + OBJ_84 /* GeoJsonParserTests.swift */, + OBJ_85 /* WktParserTests.swift */, + ); + path = Parser; + sourceTree = ""; + }; + OBJ_86 /* Products */ = { + isa = PBXGroup; + children = ( + "GeospatialSwift::GeospatialSwiftTests::Product" /* GeospatialSwiftTests.xctest */, + "GeospatialSwift::GeospatialSwift::Product" /* GeospatialSwift.framework */, + ); + name = Products; + sourceTree = BUILT_PRODUCTS_DIR; + }; + OBJ_9 /* API */ = { + isa = PBXGroup; + children = ( + OBJ_10 /* Calculator */, + 60944697240FE4130021D3A6 /* Geodesic */, + OBJ_39 /* Geohash */, + OBJ_12 /* GeoJson */, + OBJ_42 /* Geospatial.swift */, + ); + path = API; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + "GeospatialSwift::GeospatialSwift" /* GeospatialSwift */ = { + isa = PBXNativeTarget; + buildConfigurationList = OBJ_96 /* Build configuration list for PBXNativeTarget "GeospatialSwift" */; + buildPhases = ( + OBJ_99 /* Sources */, + OBJ_135 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GeospatialSwift; + productName = GeospatialSwift; + productReference = "GeospatialSwift::GeospatialSwift::Product" /* GeospatialSwift.framework */; + productType = "com.apple.product-type.framework"; + }; + "GeospatialSwift::GeospatialSwiftTests" /* GeospatialSwiftTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = OBJ_148 /* Build configuration list for PBXNativeTarget "GeospatialSwiftTests" */; + buildPhases = ( + OBJ_151 /* Sources */, + OBJ_174 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + OBJ_176 /* PBXTargetDependency */, + ); + name = GeospatialSwiftTests; + productName = GeospatialSwiftTests; + productReference = "GeospatialSwift::GeospatialSwiftTests::Product" /* GeospatialSwiftTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + "GeospatialSwift::SwiftPMPackageDescription" /* GeospatialSwiftPackageDescription */ = { + isa = PBXNativeTarget; + buildConfigurationList = OBJ_137 /* Build configuration list for PBXNativeTarget "GeospatialSwiftPackageDescription" */; + buildPhases = ( + OBJ_140 /* Sources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = GeospatialSwiftPackageDescription; + productName = GeospatialSwiftPackageDescription; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + OBJ_1 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftMigration = 9999; + LastUpgradeCheck = 9999; + }; + buildConfigurationList = OBJ_2 /* Build configuration list for PBXProject "GeospatialSwift" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = OBJ_5; + productRefGroup = OBJ_86 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + "GeospatialSwift::GeospatialSwift" /* GeospatialSwift */, + "GeospatialSwift::SwiftPMPackageDescription" /* GeospatialSwiftPackageDescription */, + "GeospatialSwift::GeospatialSwiftPackageTests::ProductTarget" /* GeospatialSwiftPackageTests */, + "GeospatialSwift::GeospatialSwiftTests" /* GeospatialSwiftTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + OBJ_140 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 0; + files = ( + OBJ_141 /* Package.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + OBJ_151 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 0; + files = ( + OBJ_152 /* GeoJsonTestJson.swift in Sources */, + OBJ_153 /* GeoTestHelper.swift in Sources */, + OBJ_154 /* MockData.swift in Sources */, + OBJ_155 /* WktTestJson.swift in Sources */, + OBJ_156 /* GeoJsonParsingPerformanceTests.swift in Sources */, + OBJ_157 /* GeodesicCalculatorTests.swift in Sources */, + OBJ_158 /* BoundingBoxTests.swift in Sources */, + OBJ_159 /* GeodesicLineSegmentTests.swift in Sources */, + OBJ_160 /* FeatureCollectionTests.swift in Sources */, + OBJ_161 /* FeatureTests.swift in Sources */, + OBJ_162 /* GeometryCollectionTests.swift in Sources */, + OBJ_163 /* LineStringTests.swift in Sources */, + OBJ_164 /* MultiLineStringTests.swift in Sources */, + OBJ_165 /* MultiPointTests.swift in Sources */, + OBJ_166 /* MultiPolygonTests.swift in Sources */, + OBJ_167 /* PointTests.swift in Sources */, + OBJ_168 /* PolygonTests.swift in Sources */, + OBJ_169 /* GeohashCoderTests.swift in Sources */, + OBJ_170 /* Assertions.swift in Sources */, + OBJ_171 /* Equatable.swift in Sources */, + OBJ_172 /* GeoJsonParserTests.swift in Sources */, + OBJ_173 /* WktParserTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + OBJ_99 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 0; + files = ( + OBJ_100 /* GeodesicCalculator.swift in Sources */, + OBJ_101 /* GeoJson.swift in Sources */, + OBJ_102 /* GeoJsonDictionary.swift in Sources */, + OBJ_103 /* GeodesicBearing.swift in Sources */, + OBJ_104 /* GeodesicBoundingBox.swift in Sources */, + OBJ_105 /* GeodesicLine.swift in Sources */, + OBJ_106 /* GeodesicLineSegment.swift in Sources */, + OBJ_107 /* GeodesicPoint.swift in Sources */, + OBJ_108 /* GeodesicPolygon.swift in Sources */, + OBJ_109 /* Feature.swift in Sources */, + OBJ_110 /* FeatureCollection.swift in Sources */, + 60944696240FE3B70021D3A6 /* GeoJsonSimpleViolation.swift in Sources */, + OBJ_111 /* GeometryCollection.swift in Sources */, + OBJ_112 /* LineString.swift in Sources */, + OBJ_113 /* LinearRing.swift in Sources */, + OBJ_114 /* MultiLineString.swift in Sources */, + OBJ_115 /* MultiPoint.swift in Sources */, + OBJ_116 /* MultiPolygon.swift in Sources */, + OBJ_117 /* Point.swift in Sources */, + OBJ_118 /* Polygon.swift in Sources */, + OBJ_119 /* GeoJsonClosedGeometry.swift in Sources */, + OBJ_120 /* GeoJsonCoordinatesGeometry.swift in Sources */, + OBJ_121 /* GeoJsonGeometry.swift in Sources */, + OBJ_122 /* GeoJsonLinearGeometry.swift in Sources */, + OBJ_123 /* GeoJsonObject.swift in Sources */, + OBJ_124 /* GeoJsonObjectType.swift in Sources */, + OBJ_125 /* GeohashBox.swift in Sources */, + OBJ_126 /* GeohashCoder.swift in Sources */, + OBJ_127 /* Geospatial.swift in Sources */, + OBJ_128 /* Array.swift in Sources */, + OBJ_129 /* Bundle.swift in Sources */, + OBJ_130 /* FloatingPoint.swift in Sources */, + OBJ_131 /* Result.swift in Sources */, + OBJ_132 /* GeoJsonParser.swift in Sources */, + OBJ_133 /* InvalidGeoJson.swift in Sources */, + OBJ_134 /* WktParser.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + OBJ_146 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = "GeospatialSwift::GeospatialSwiftTests" /* GeospatialSwiftTests */; + targetProxy = 60944685240EA1040021D3A6 /* PBXContainerItemProxy */; + }; + OBJ_176 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = "GeospatialSwift::GeospatialSwift" /* GeospatialSwift */; + targetProxy = 60944684240EA1020021D3A6 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + OBJ_138 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + LD = /usr/bin/true; + OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode-11.3.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -package-description-version 5.1"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + OBJ_139 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + LD = /usr/bin/true; + OTHER_SWIFT_FLAGS = "-swift-version 5 -I $(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2 -target x86_64-apple-macosx10.10 -sdk /Applications/Xcode-11.3.1.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk -package-description-version 5.1"; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + OBJ_144 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Debug; + }; + OBJ_145 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + }; + name = Release; + }; + OBJ_149 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + HEADER_SEARCH_PATHS = "$(inherited)"; + INFOPLIST_FILE = GeospatialSwift.xcodeproj/GeospatialSwiftTests_Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + OTHER_CFLAGS = "$(inherited)"; + OTHER_LDFLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; + SWIFT_VERSION = 5.0; + TARGET_NAME = GeospatialSwiftTests; + TVOS_DEPLOYMENT_TARGET = 9.0; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Debug; + }; + OBJ_150 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + HEADER_SEARCH_PATHS = "$(inherited)"; + INFOPLIST_FILE = GeospatialSwift.xcodeproj/GeospatialSwiftTests_Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @loader_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + OTHER_CFLAGS = "$(inherited)"; + OTHER_LDFLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; + SWIFT_VERSION = 5.0; + TARGET_NAME = GeospatialSwiftTests; + TVOS_DEPLOYMENT_TARGET = 9.0; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Release; + }; + OBJ_3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_ARC = YES; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "SWIFT_PACKAGE=1", + "DEBUG=1", + ); + MACOSX_DEPLOYMENT_TARGET = 10.10; + ONLY_ACTIVE_ARCH = YES; + OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE DEBUG"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + USE_HEADERMAP = NO; + }; + name = Debug; + }; + OBJ_4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_ARC = YES; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + GCC_OPTIMIZATION_LEVEL = s; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "SWIFT_PACKAGE=1", + ); + MACOSX_DEPLOYMENT_TARGET = 10.10; + OTHER_SWIFT_FLAGS = "$(inherited) -DXcode"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator watchos watchsimulator"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) SWIFT_PACKAGE"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + USE_HEADERMAP = NO; + }; + name = Release; + }; + OBJ_97 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ENABLE_TESTABILITY = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + HEADER_SEARCH_PATHS = "$(inherited)"; + INFOPLIST_FILE = GeospatialSwift.xcodeproj/GeospatialSwift_Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + OTHER_CFLAGS = "$(inherited)"; + OTHER_LDFLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited)"; + PRODUCT_BUNDLE_IDENTIFIER = GeospatialSwift; + PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; + SWIFT_VERSION = 5.0; + TARGET_NAME = GeospatialSwift; + TVOS_DEPLOYMENT_TARGET = 9.0; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Debug; + }; + OBJ_98 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ENABLE_TESTABILITY = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PLATFORM_DIR)/Developer/Library/Frameworks", + ); + HEADER_SEARCH_PATHS = "$(inherited)"; + INFOPLIST_FILE = GeospatialSwift.xcodeproj/GeospatialSwift_Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) $(TOOLCHAIN_DIR)/usr/lib/swift/macosx"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + OTHER_CFLAGS = "$(inherited)"; + OTHER_LDFLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited)"; + PRODUCT_BUNDLE_IDENTIFIER = GeospatialSwift; + PRODUCT_MODULE_NAME = "$(TARGET_NAME:c99extidentifier)"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited)"; + SWIFT_VERSION = 5.0; + TARGET_NAME = GeospatialSwift; + TVOS_DEPLOYMENT_TARGET = 9.0; + WATCHOS_DEPLOYMENT_TARGET = 2.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + OBJ_137 /* Build configuration list for PBXNativeTarget "GeospatialSwiftPackageDescription" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + OBJ_138 /* Debug */, + OBJ_139 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + OBJ_143 /* Build configuration list for PBXAggregateTarget "GeospatialSwiftPackageTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + OBJ_144 /* Debug */, + OBJ_145 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + OBJ_148 /* Build configuration list for PBXNativeTarget "GeospatialSwiftTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + OBJ_149 /* Debug */, + OBJ_150 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + OBJ_2 /* Build configuration list for PBXProject "GeospatialSwift" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + OBJ_3 /* Debug */, + OBJ_4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + OBJ_96 /* Build configuration list for PBXNativeTarget "GeospatialSwift" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + OBJ_97 /* Debug */, + OBJ_98 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = OBJ_1 /* Project object */; } diff --git a/MAINTAINERS b/MAINTAINERS index fd0882f..29de886 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1 +1,2 @@ (schlingding) Vernon Schierding +(mifanbing) Zeyi Wang \ No newline at end of file diff --git a/Sources/GeospatialSwift/API/Calculator/GeodesicCalculator.swift b/Sources/GeospatialSwift/API/Calculator/GeodesicCalculator.swift index 533124a..1d40572 100644 --- a/Sources/GeospatialSwift/API/Calculator/GeodesicCalculator.swift +++ b/Sources/GeospatialSwift/API/Calculator/GeodesicCalculator.swift @@ -1,15 +1,5 @@ import Foundation -// SOMEDAY: Overlaps / Contains(Fully)? (Line to Line, Line in Multi/Polygon, Polygon in Multi/Polygon) -// func contains(_ lineSegment: GeodesicLineSegment, in polygon: GeodesicPolygon, tolerance: Double) -> Bool -// SOMEDAY: Split Lines -// func canSplit(_ polygon: GeodesicPolygon, from point: GeodesicPoint, to otherPoint: GeodesicPoint) -> Bool -// func split(_ polygon: GeodesicPolygon, from point: GeodesicPoint, to otherPoint: GeodesicPoint) -> (GeodesicPolygon, GeodesicPolygon) - -// SOMEDAY: Altitude is not considered. Perhaps there should be 3D calculations as well. -// SOMEDAY: Break this up into pieces. -// swiftlint:disable file_length - internal let Calculator = GeodesicCalculator() /** @@ -69,7 +59,7 @@ public struct GeodesicCalculator { // SOMEDAY: Area calculations are not geodesic? extension GeodesicCalculator { - public func length(of line: GeodesicLine) -> Double { line.segments.reduce(0.0) { $0 + distance(from: $1.point, to: $1.otherPoint, tolerance: 0) } } + public func length(of line: GeodesicLine) -> Double { line.segments.reduce(0.0) { $0 + distance(from: $1.startPoint, to: $1.endPoint, tolerance: 0) } } public func area(of polygon: GeodesicPolygon) -> Double { let earthRadius = self.earthRadius(latitudeAverage: centroid(polygon: polygon).latitude) @@ -139,20 +129,20 @@ extension GeodesicCalculator { public func distance(from point: GeodesicPoint, to lineSegment: GeodesicLineSegment, tolerance: Double) -> Double { let distance1 = distancePartialResult(from: point, to: lineSegment) - let reverseSegment = GeodesicLineSegment(point: lineSegment.otherPoint, otherPoint: lineSegment.point) + let reverseSegment = GeodesicLineSegment(startPoint: lineSegment.endPoint, endPoint: lineSegment.startPoint) let distance2 = distancePartialResult(from: point, to: reverseSegment) return max(min(distance1, distance2) - tolerance, 0.0) } public func distance(from lineSegment: GeodesicLineSegment, to otherLineSegment: GeodesicLineSegment, tolerance: Double) -> Double { - guard intersection(of: lineSegment, with: otherLineSegment) == nil else { return 0 } + guard intersectionPoint(of: lineSegment, with: otherLineSegment) == nil else { return 0 } return min( - distance(from: lineSegment.point, to: otherLineSegment, tolerance: tolerance), - distance(from: lineSegment.otherPoint, to: otherLineSegment, tolerance: tolerance), - distance(from: otherLineSegment.point, to: lineSegment, tolerance: tolerance), - distance(from: otherLineSegment.otherPoint, to: lineSegment, tolerance: tolerance) + distance(from: lineSegment.startPoint, to: otherLineSegment, tolerance: tolerance), + distance(from: lineSegment.endPoint, to: otherLineSegment, tolerance: tolerance), + distance(from: otherLineSegment.startPoint, to: lineSegment, tolerance: tolerance), + distance(from: otherLineSegment.endPoint, to: lineSegment, tolerance: tolerance) ) } @@ -214,21 +204,21 @@ extension GeodesicCalculator { return angularDistance * earthRadius } - // SOMEDAY: It was suggested this could be almost twice as fast - Run tests! Protect asin from NaN -// func haversineOptimized(from point: GeodesicPoint, to otherPoint: GeodesicPoint) -> Double { -// let p = 0.017453292519943295 // Math.PI / 180 -// let a = 0.5 - (cos((otherPoint.latitude - point.latitude) * p) / 2) + (cos(point.latitude * p) * cos(otherPoint.latitude * p) * (1 - cos((otherPoint.longitude - point.longitude) * p)) / 2) -// -// return 12742 * asin(sqrt(a)) // 2 * R; R = 6371 km -// } + // It was suggested this could be almost twice as fast - Run tests! Protect asin from NaN + // func haversineOptimized(from point: GeodesicPoint, to otherPoint: GeodesicPoint) -> Double { + // let p = 0.017453292519943295 // Math.PI / 180 + // let a = 0.5 - (cos((otherPoint.latitude - point.latitude) * p) / 2) + (cos(point.latitude * p) * cos(otherPoint.latitude * p) * (1 - cos((otherPoint.longitude - point.longitude) * p)) / 2) + // + // return 12742 * asin(sqrt(a)) // 2 * R; R = 6371 km + // } // SOMEDAY: It would be nice to understand this better as there seems to be too much to calling this twice but twice produces the correct result. private func distancePartialResult(from point: GeodesicPoint, to lineSegment: GeodesicLineSegment) -> Double { - let earthRadius = self.earthRadius(latitudeAverage: midpoint(from: lineSegment.point, to: lineSegment.otherPoint).latitude) + let earthRadius = self.earthRadius(latitudeAverage: midpoint(from: lineSegment.startPoint, to: lineSegment.endPoint).latitude) - let θ12 = initialBearing(from: lineSegment.point, to: lineSegment.otherPoint).degreesToRadians - let θ13 = initialBearing(from: lineSegment.point, to: point).degreesToRadians - let δ13 = distance(from: lineSegment.point, to: point, tolerance: 0) + let θ12 = initialBearing(from: lineSegment.startPoint, to: lineSegment.endPoint).degreesToRadians + let θ13 = initialBearing(from: lineSegment.startPoint, to: point).degreesToRadians + let δ13 = distance(from: lineSegment.startPoint, to: point, tolerance: 0) guard abs(θ13 - θ12) <= .pi / 2.0 else { return δ13 } @@ -238,7 +228,7 @@ extension GeodesicCalculator { return partialCalculation > 1 || partialCalculation < -1 ? 0 : asin(partialCalculation) * earthRadius }() - let δ12 = distance(from: lineSegment.point, to: lineSegment.otherPoint, tolerance: 0) + let δ12 = distance(from: lineSegment.startPoint, to: lineSegment.endPoint, tolerance: 0) let δ14: Double = { let partialCalculation = cos(δ13 / earthRadius) / cos(δxt / earthRadius) @@ -248,7 +238,7 @@ extension GeodesicCalculator { guard δ14 > δ12 else { return abs(δxt) } - let δ23 = distance(from: lineSegment.otherPoint, to: point, tolerance: 0) + let δ23 = distance(from: lineSegment.endPoint, to: point, tolerance: 0) return δ23 } @@ -259,6 +249,8 @@ extension GeodesicCalculator { extension GeodesicCalculator { public func contains(_ point: GeodesicPoint, in otherPoint: GeodesicPoint, tolerance: Double) -> Bool { distance(from: point, to: otherPoint, tolerance: tolerance) == 0 } + public func contains(_ point: GeodesicPoint, in points: [GeodesicPoint], tolerance: Double) -> Bool { points.contains { contains(point, in: $0, tolerance: tolerance) } } + public func contains(_ point: GeodesicPoint, in lineSegment: GeodesicLineSegment, tolerance: Double) -> Bool { distance(from: point, to: lineSegment, tolerance: tolerance) == 0 } public func contains(_ point: GeodesicPoint, in line: GeodesicLine, tolerance: Double) -> Bool { distance(from: point, to: line, tolerance: tolerance) == 0 } @@ -284,18 +276,33 @@ extension GeodesicCalculator { } // SOMEDAY: Not geodesic. - private func contains(point: GeodesicPoint, vertices: [GeodesicPoint]) -> Bool { + public func contains(point: GeodesicPoint, vertices: [GeodesicPoint]) -> Bool { guard !vertices.isEmpty else { return false } var contains = false var previousVertex = vertices.last! - vertices.forEach { vertex in - let partial1 = (vertex.latitude > point.latitude) != (previousVertex.latitude > point.latitude) - let partial2 = (previousVertex.longitude - vertex.longitude) * (point.latitude - vertex.latitude) / (previousVertex.latitude - vertex.latitude) + vertex.longitude - let partial3 = point.longitude < partial2 + //shot a ray from point to right, parallel to latitude line + //After hitting a segment, the ray goes inside out or outside in + //If ray starts outside: out=>in=>...=>out + //If inside: in=>out=>in...=>out + //Thus, even number of hits: outside + //Odd number of hits: inside + for vertex in vertices { + let segment = GeodesicLineSegment(startPoint: previousVertex, endPoint: vertex) + guard !self.contains(point, in: segment, tolerance: 0) else { return true } + + //point.latitude is in [vertex.latitude, previousVertex.latitude], so ray will intersect segment + let latitudeIsInRange = (vertex.latitude > point.latitude) != (previousVertex.latitude > point.latitude) + //intersection with segment + //If segment is to the left of the point, the ray will go around the earth and hit the segment. Bad hit + //If segment is to the right, it will be a good hit + let intersectionOnSegment = (previousVertex.longitude - vertex.longitude) * (point.latitude - vertex.latitude) / (previousVertex.latitude - vertex.latitude) + vertex.longitude + //The intersection is to the right of point + let intersectionIsToTheRight = point.longitude < intersectionOnSegment - if partial1 && partial3 { contains = !contains } + //Hit once + if latitudeIsInRange && intersectionIsToTheRight { contains = !contains } previousVertex = vertex } @@ -325,7 +332,7 @@ extension GeodesicCalculator { $0.contains { hasIntersection($0, with: lineSegment, tolerance: tolerance) } } - return polygonIntersects || contains(lineSegment.point, in: polygon, tolerance: tolerance) + return polygonIntersects || contains(lineSegment.startPoint, in: polygon, tolerance: tolerance) } public func hasIntersection(_ line: GeodesicLine, tolerance: Double) -> Bool { @@ -334,7 +341,7 @@ extension GeodesicCalculator { guard currentLineIndex < nextLineIndex else { return false } // If next line continues from previous ensure no overlapping - if currentLineIndex == nextLineIndex - 1 && currentLineSegment.otherPoint == nextLineSegment.point { return contains(nextLineSegment.otherPoint, in: currentLineSegment, tolerance: tolerance) } + if currentLineIndex == nextLineIndex - 1 && currentLineSegment.endPoint == nextLineSegment.startPoint { return contains(nextLineSegment.endPoint, in: currentLineSegment, tolerance: tolerance) } return hasIntersection(currentLineSegment, with: nextLineSegment, tolerance: tolerance) } @@ -342,52 +349,370 @@ extension GeodesicCalculator { } public func hasIntersection(_ polygon: GeodesicPolygon, tolerance: Double) -> Bool { polygon.linearRings.contains { hasIntersection($0, tolerance: tolerance) } } +} + +// MARK: Violation Indicies + +extension GeodesicCalculator { + internal func simpleViolationDuplicateIndices(points: [GeodesicPoint], tolerance: Double) -> [[Int]] { + var uniquePoints = [GeodesicPoint]() + var duplicateIndices = [[Int]]() + + points.enumerated().forEach { pointIndex, point in + var isUnique = true + for index in (0.. 1 } + } - public func equalsIndices(_ points: [GeodesicPoint], tolerance: Double) -> [Int] { - return points.enumerated().filter { currentIndex, currentPoint in - points.enumerated().contains { nextIndex, nextPoint in - guard currentIndex < nextIndex else { return false } + internal func simpleViolationSelfIntersectionIndices(line: GeodesicLine, tolerance: Double) -> [Int: [Int]] { + func adjacentSegmentsOverlap(currentSegment: GeodesicLineSegment, nextSegment: GeodesicLineSegment) -> Bool { + return contains(currentSegment.startPoint, in: nextSegment, tolerance: tolerance) || contains(nextSegment.endPoint, in: currentSegment, tolerance: tolerance) + } + + var allIntersectionIndices = [Int: [Int]]() + let lastSegmentIndex = line.segments.count - 1 + //Compare current segment to each of the segments from next to last segment + line.segments.enumerated().forEach { currentIndex, currentSegment in + let nextSegmentIndex = currentIndex + 1 + guard let nextSegment = line.segments.at(nextSegmentIndex) else { return } + + //Comparison of current segment and next segment is a special case + //They always share a point, which cause intersection. As long as they don't overlap, they are good + if adjacentSegmentsOverlap(currentSegment: currentSegment, nextSegment: nextSegment) { + allIntersectionIndices[currentIndex] = [nextSegmentIndex] + } + + let remainingSegmentsEnumerated = Array(line.segments.enumerated().drop { $0.offset <= nextSegmentIndex }) + guard !remainingSegmentsEnumerated.isEmpty else { return } + + //Compare to the remaining segments + remainingSegmentsEnumerated.forEach { remainingIndex, remainingSegment in + if hasIntersection(currentSegment, with: remainingSegment, tolerance: tolerance) && + //An exception is that first segment is allowed to share a point with last segment + !(currentIndex == 0 && remainingIndex == lastSegmentIndex && currentSegment.startPoint == remainingSegment.endPoint && !adjacentSegmentsOverlap(currentSegment: remainingSegment, nextSegment: currentSegment)) { + allIntersectionIndices[currentIndex] = (allIntersectionIndices[currentIndex] ?? []) + [remainingIndex] + } + } + } + + return allIntersectionIndices + } + + internal func simpleViolationIntersectionIndices(lines: [GeodesicLine], tolerance: Double) -> LineSegmentIndiciesByLineSegmentIndex { + var allIntersectionIndices = LineSegmentIndiciesByLineSegmentIndex() + lines.enumerated().forEach { currentLineIndex, currentLine in + //Compare current LineString to each of the Linestring from next to last + let remainingLinesEnumerated = Array(lines.enumerated().drop { $0.offset <= currentLineIndex }) + + remainingLinesEnumerated.forEach { remainingLineIndex, remainingLine in - return distance(from: currentPoint, to: nextPoint, tolerance: tolerance) == 0 + currentLine.segments.enumerated().forEach { currentSegmentIndex, currentSegment in + let currentLineSegmentIndex = LineSegmentIndex(lineIndex: currentLineIndex, segmentIndex: currentSegmentIndex) + + remainingLine.segments.enumerated().forEach { remainingSegmentIndex, remainingSegment in + let remainingLineSegmentIndex = LineSegmentIndex(lineIndex: remainingLineIndex, segmentIndex: remainingSegmentIndex) + + if hasIntersection(currentSegment, with: remainingSegment, tolerance: tolerance) { + if overlapping(segment: currentSegment, segmentOther: remainingSegment, tolerance: tolerance) { + allIntersectionIndices[currentLineSegmentIndex] = allIntersectionIndices[currentLineSegmentIndex] ?? [] + [remainingLineSegmentIndex] + return + } + + //CurrentLine start and end is allowed to be shared with remainingLine start and end + let currentIsLineStartPoint = currentSegmentIndex == 0 + let remainingIsLineStartPoint = remainingSegmentIndex == 0 + let currentIsLineEndPoint = currentSegmentIndex == currentLine.segments.count - 1 + let remainingIsLineEndPoint = remainingSegmentIndex == remainingLine.segments.count - 1 + + if currentIsLineStartPoint, remainingIsLineStartPoint, currentSegment.startPoint == remainingSegment.startPoint { + return + } + + if currentIsLineStartPoint, remainingIsLineEndPoint, currentSegment.startPoint == remainingSegment.endPoint { + return + } + + if currentIsLineEndPoint, remainingIsLineStartPoint, currentSegment.endPoint == remainingSegment.startPoint { + return + } + + if currentIsLineEndPoint, remainingIsLineEndPoint, currentSegment.endPoint == remainingSegment.endPoint { + return + } + + allIntersectionIndices[currentLineSegmentIndex] = (allIntersectionIndices[currentLineSegmentIndex] ?? []) + [remainingLineSegmentIndex] + } + } + } } - }.map { $0.offset } + } + + return allIntersectionIndices } - // [Source Ring Index -> [Source Ring Segment Index -> [Compare Ring Intersecting Segement]]] - public func intersectionIndices(from line: GeodesicLine, tolerance: Double) -> [Int] { - return line.segments.enumerated().filter { currentLineIndex, currentLineSegment in - line.segments.enumerated().contains { nextLineIndex, nextLineSegment in - guard currentLineIndex < nextLineIndex else { return false } + internal func simpleViolationNegativeRingPointsOutsideMainRingIndices(from polygon: GeodesicPolygon, tolerance: Double) -> [LineSegmentPointIndex] { + let mainRing = polygon.mainRing + var outsideSegmentIndices = [LineSegmentPointIndex]() + polygon.negativeRings.enumerated().forEach { negativeRingIndex, negativeRing in + negativeRing.segments.enumerated().forEach { negativeSegmentIndex, negativeSegment in + let lineSegmentIndex = LineSegmentIndex(lineIndex: negativeRingIndex, segmentIndex: negativeSegmentIndex) + if !contains(point: negativeSegment.startPoint, vertices: mainRing.points) { + outsideSegmentIndices.append(LineSegmentPointIndex(lineSegmentIndex: lineSegmentIndex, pointIndex: .startPoint)) + } + if !contains(point: negativeSegment.endPoint, vertices: mainRing.points) { + outsideSegmentIndices.append(LineSegmentPointIndex(lineSegmentIndex: lineSegmentIndex, pointIndex: .endPoint)) + } + } + } + + return outsideSegmentIndices + } + + //Ring intersecting and crossing another Ring + internal func simpleViolationIntersectionIndices(from polygon: GeodesicPolygon, tolerance: Double) -> LineSegmentIndiciesByLineSegmentIndex { + var allIntersectionIndices = LineSegmentIndiciesByLineSegmentIndex() + polygon.linearRings.enumerated().forEach { currentRingIndex, currentRing in + //Compare current ring to each of the ring from next to last + let remainingRingsEnumerated = Array(polygon.linearRings.enumerated().drop { $0.offset <= currentRingIndex }) + + remainingRingsEnumerated.forEach { remainingRingIndex, remainingRing in - // If next line continues from previous ensure no overlapping - if currentLineIndex == nextLineIndex - 1 && currentLineSegment.otherPoint == nextLineSegment.point { return contains(nextLineSegment.otherPoint, in: currentLineSegment, tolerance: tolerance) } + currentRing.segments.enumerated().forEach { currentSegmentIndex, currentSegment in + let currentSegmentIndex = LineSegmentIndex(lineIndex: currentRingIndex, segmentIndex: currentSegmentIndex) + + remainingRing.segments.enumerated().forEach { remainingSegmentIndex, remainingSegment in + let remainingSegmentIndex = LineSegmentIndex(lineIndex: remainingRingIndex, segmentIndex: remainingSegmentIndex) + + if overlapping(segment: currentSegment, segmentOther: remainingSegment, tolerance: tolerance) { + allIntersectionIndices[currentSegmentIndex] = (allIntersectionIndices[currentSegmentIndex] ?? []) + [remainingSegmentIndex] + return + } + + if hasIntersection(currentSegment, with: remainingSegment, tolerance: tolerance) { + //Intersecting and crossing + if !contains(currentSegment.startPoint, in: remainingSegment, tolerance: tolerance) && + !contains(currentSegment.endPoint, in: remainingSegment, tolerance: tolerance) && + !contains(remainingSegment.startPoint, in: currentSegment, tolerance: tolerance) && + !contains(remainingSegment.endPoint, in: currentSegment, tolerance: tolerance) { + allIntersectionIndices[currentSegmentIndex] = (allIntersectionIndices[currentSegmentIndex] ?? []) + [remainingSegmentIndex] + } + } + } + } + } + } + + return allIntersectionIndices + } + + internal func simpleViolationMultipleVertexIntersectionIndices(from polygon: GeodesicPolygon, tolerance: Double) -> [LineSegmentIndex: [LineSegmentPointIndex]] { + var allIntersectionIndices = LineSegmentPointIndiciesByLineSegmentIndex() + polygon.linearRings.enumerated().forEach { currentRingIndex, currentRing in + //Compare current ring to each of the ring from next to last + let remainingRingsEnumerated = polygon.linearRings.enumerated().filter { $0.offset != currentRingIndex } + + remainingRingsEnumerated.forEach { remainingRingIndex, remainingRing in + var intersectionPoints = [GeodesicPoint]() + currentRing.segments.enumerated().forEach { currentSegmentIndex, currentSegment in + let currentSegmentIndex = LineSegmentIndex(lineIndex: currentRingIndex, segmentIndex: currentSegmentIndex) + + remainingRing.segments.enumerated().forEach { remainingSegmentIndex, remainingSegment in + //We have checked intersection and cross already, and will terminate if there is any + //so we can assume that any intersection here is vertex intersection. + if hasIntersection(currentSegment, with: remainingSegment, tolerance: tolerance) { + //Vertex Intersection + let lineSegmentIndex = LineSegmentIndex(lineIndex: remainingRingIndex, segmentIndex: remainingSegmentIndex) + if contains(remainingSegment.startPoint, in: currentSegment, tolerance: tolerance) { + let remainingPointIndex = LineSegmentPointIndex(lineSegmentIndex: lineSegmentIndex, pointIndex: .startPoint) + allIntersectionIndices[currentSegmentIndex] = (allIntersectionIndices[currentSegmentIndex] ?? []) + [remainingPointIndex] + if !contains(remainingSegment.startPoint, in: intersectionPoints, tolerance: tolerance) { + intersectionPoints.append(remainingSegment.startPoint) + } + } else { + let remainingPointIndex = LineSegmentPointIndex(lineSegmentIndex: lineSegmentIndex, pointIndex: .endPoint) + allIntersectionIndices[currentSegmentIndex] = (allIntersectionIndices[currentSegmentIndex] ?? []) + [remainingPointIndex] + if !contains(remainingSegment.endPoint, in: intersectionPoints, tolerance: tolerance) { + intersectionPoints.append(remainingSegment.endPoint) + } + } + } + } + } + + if intersectionPoints.count < 2 { + allIntersectionIndices.forEach { lineIndexBySegmentIndex, _ in + if lineIndexBySegmentIndex.lineIndex == currentRingIndex { + allIntersectionIndices[lineIndexBySegmentIndex] = nil + } + } + } - return hasIntersection(currentLineSegment, with: nextLineSegment, tolerance: tolerance) } - }.map { $0.offset } - } - - // SOMEDAY: Add this intersection rule - // Polygon where hole intersects with same point as exterior edge point - // [Source Ring Index -> [Source Ring Segment Index -> [Compare Ring Intersecting Segement]]] - public func intersectionIndices(from polygon: GeodesicPolygon, tolerance: Double) -> [[[Int]]] { - return polygon.linearRings.map { $0.segments }.map { ringSegments in - return ringSegments.map { lineSegment in - ringSegments.enumerated().filter { - intersection(of: $1, with: lineSegment) != nil - }.map { $0.offset } + } + + return allIntersectionIndices + } + + internal func simpleViolationNegativeRingInsideAnotherNegativeRingIndices(from polygon: GeodesicPolygon, tolerance: Double) -> [Int] { + let negativeRing = polygon.negativeRings + + var containedRingIndices = [Int]() + negativeRing.enumerated().forEach { ringIndex, ring in + let remainingNegativeRingsEnumerated = negativeRing.enumerated().filter { $0.offset != ringIndex } + remainingNegativeRingsEnumerated.forEach { remainingRingIndex, remainingRing in + //all point in remainingRing is contained in ring + let remainingRingPointsAreContained = remainingRing.points.map { contains(point: $0, vertices: ring.points) } + if !remainingRingPointsAreContained.contains(false) { + containedRingIndices.append(remainingRingIndex) + } } } + + return containedRingIndices } - // Does not return a point if overlaping the path - public func intersection(of lineSegment: GeodesicLineSegment, with otherLineSegment: GeodesicLineSegment) -> GeodesicPoint? { - let longitudeDeltaSegment2 = otherLineSegment.otherPoint.longitude - otherLineSegment.point.longitude - let latitudeDeltaSegment2 = otherLineSegment.otherPoint.latitude - otherLineSegment.point.latitude - let longitudeDeltaSegment1 = lineSegment.otherPoint.longitude - lineSegment.point.longitude - let latitudeDeltaSegment1 = lineSegment.otherPoint.latitude - lineSegment.point.latitude - let longitudeSegmentsOffset = lineSegment.point.longitude - otherLineSegment.point.longitude - let latitudeSegmentsOffset = lineSegment.point.latitude - otherLineSegment.point.latitude + internal func simpleViolationPolygonPointsContainedInAnotherPolygonIndices(from polygons: [GeodesicPolygon], tolerance: Double) -> [Int] { + func isOnEdge(point: GeodesicPoint, polygon: GeodesicPolygon, tolerance: Double) -> Bool { + let distanceToEdge = polygon.mainRing.segments.map { distance(from: point, to: $0, tolerance: tolerance) } + + return distanceToEdge.contains(0) + } + + var containedRingIndices = [Int]() + polygons.enumerated().forEach { currentPolygonIndex, currentPolygon in + let currentMainRing = currentPolygon.mainRing + + let remainingPolygonEnumerated = polygons.enumerated().filter { $0.offset != currentPolygonIndex } + remainingPolygonEnumerated.forEach { remainingPolygonIndex, remainingPolygon in + let remainingMainRing = remainingPolygon.mainRing + let remainingMainRingPointsAreContained = remainingMainRing.points.map { contains(point: $0, vertices: currentMainRing.points) && !isOnEdge(point: $0, polygon: currentPolygon, tolerance: tolerance) } + if remainingMainRingPointsAreContained.contains(true) { + containedRingIndices.append(remainingPolygonIndex) + } + } + } + + return containedRingIndices + } + + internal func simpleViolationIntersectionIndices(from polygons: [GeodesicPolygon], tolerance: Double) -> LineSegmentIndiciesByLineSegmentIndex { + var allIntersectionIndices = LineSegmentIndiciesByLineSegmentIndex() + polygons.enumerated().forEach { currentPolygonIndex, currentPolygon in + let currentMainRing = currentPolygon.mainRing + + let remainingPolygonEnumerated = Array(polygons.enumerated().drop(while: { $0.offset <= currentPolygonIndex })) + remainingPolygonEnumerated.forEach { remainingPolygonIndex, remainingPolygon in + let remainingMainRing = remainingPolygon.mainRing + + currentMainRing.segments.enumerated().forEach { currentSegmentIndex, currentSegment in + let currentSegmentIndex = LineSegmentIndex(lineIndex: currentPolygonIndex, segmentIndex: currentSegmentIndex) + + remainingMainRing.segments.enumerated().forEach { remainingSegmentIndex, remainingSegment in + let remainingSegmentIndex = LineSegmentIndex(lineIndex: remainingPolygonIndex, segmentIndex: remainingSegmentIndex) + //2 rings can intersect at a tangent point but never cross. + //A second intersection is not allowed. + if overlapping(segment: currentSegment, segmentOther: remainingSegment, tolerance: tolerance) { + allIntersectionIndices[currentSegmentIndex] = (allIntersectionIndices[currentSegmentIndex] ?? []) + [remainingSegmentIndex] + return + } + + if hasIntersection(currentSegment, with: remainingSegment, tolerance: tolerance) { + //If the segments are not crossing each other, there is no violation + if !(contains(currentSegment.startPoint, in: remainingSegment, tolerance: tolerance) || + contains(currentSegment.endPoint, in: remainingSegment, tolerance: tolerance) || + contains(remainingSegment.startPoint, in: currentSegment, tolerance: tolerance) || + contains(remainingSegment.endPoint, in: currentSegment, tolerance: tolerance)) { + allIntersectionIndices[currentSegmentIndex] = (allIntersectionIndices[currentSegmentIndex] ?? []) + [remainingSegmentIndex] + } + } + } + } + } + } + + return allIntersectionIndices + } + + // Overlapping returns true only if the lines are overlapping more than a single point. + private func overlapping(segment: GeodesicLineSegment, segmentOther: GeodesicLineSegment, tolerance: Double) -> Bool { + //segment inside segmentOther + if contains(segment.startPoint, in: segmentOther, tolerance: tolerance) + && contains(segment.endPoint, in: segmentOther, tolerance: tolerance) { + return true + } + + //segmentOther inside segment + if contains(segmentOther.startPoint, in: segment, tolerance: tolerance) + && contains(segmentOther.endPoint, in: segment, tolerance: tolerance) { + return true + } + + //sharing point + if contains(segment.startPoint, in: segmentOther.startPoint, tolerance: tolerance) + && !contains(segment.endPoint, in: segmentOther, tolerance: tolerance) + && !contains(segmentOther.endPoint, in: segment, tolerance: tolerance) { + return false + } + + if contains(segment.startPoint, in: segmentOther.endPoint, tolerance: tolerance) + && !contains(segment.endPoint, in: segmentOther, tolerance: tolerance) + && !contains(segmentOther.startPoint, in: segment, tolerance: tolerance) { + return false + } + + if contains(segment.endPoint, in: segmentOther.startPoint, tolerance: tolerance) + && !contains(segment.startPoint, in: segmentOther, tolerance: tolerance) + && !contains(segmentOther.endPoint, in: segment, tolerance: tolerance) { + return false + } + + if contains(segment.endPoint, in: segmentOther.endPoint, tolerance: tolerance) + && !contains(segment.startPoint, in: segmentOther, tolerance: tolerance) + && !contains(segmentOther.startPoint, in: segment, tolerance: tolerance) { + return false + } + + //part of segment is inside segmentOther, and part of segmentOther is inside segment + if contains(segment.startPoint, in: segmentOther, tolerance: tolerance) { + if contains(segmentOther.startPoint, in: segment, tolerance: tolerance) { + return true + } + if contains(segmentOther.endPoint, in: segment, tolerance: tolerance) { + return true + } + } + if contains(segment.endPoint, in: segmentOther, tolerance: tolerance) { + if contains(segmentOther.startPoint, in: segment, tolerance: tolerance) { + return true + } + if contains(segmentOther.endPoint, in: segment, tolerance: tolerance) { + return true + } + } + + return false + } + + // Does not return a point if overlaping the path OR if they simply only share a point + public func intersectionPoint(of lineSegment: GeodesicLineSegment, with otherLineSegment: GeodesicLineSegment) -> GeodesicPoint? { + let longitudeDeltaSegment2 = otherLineSegment.endPoint.longitude - otherLineSegment.startPoint.longitude + let latitudeDeltaSegment2 = otherLineSegment.endPoint.latitude - otherLineSegment.startPoint.latitude + let longitudeDeltaSegment1 = lineSegment.endPoint.longitude - lineSegment.startPoint.longitude + let latitudeDeltaSegment1 = lineSegment.endPoint.latitude - lineSegment.startPoint.latitude + let longitudeSegmentsOffset = lineSegment.startPoint.longitude - otherLineSegment.startPoint.longitude + let latitudeSegmentsOffset = lineSegment.startPoint.latitude - otherLineSegment.startPoint.latitude let denominator = (latitudeDeltaSegment2 * longitudeDeltaSegment1) - (longitudeDeltaSegment2 * latitudeDeltaSegment1) let numerator1 = (longitudeDeltaSegment2 * latitudeSegmentsOffset) - (latitudeDeltaSegment2 * longitudeSegmentsOffset) @@ -400,8 +725,8 @@ extension GeodesicCalculator { guard case 0...1 = result1, case 0...1 = result2 else { return nil } - let resultLongitude = lineSegment.point.longitude + (result1 * longitudeDeltaSegment1) - let resultLatitude = lineSegment.point.latitude + (result1 * latitudeDeltaSegment1) + let resultLongitude = lineSegment.startPoint.longitude + (result1 * longitudeDeltaSegment1) + let resultLatitude = lineSegment.startPoint.latitude + (result1 * latitudeDeltaSegment1) return SimplePoint(longitude: resultLongitude, latitude: resultLatitude) } @@ -462,7 +787,7 @@ extension GeodesicCalculator { let offsetLatitude = offset.latitude * (offset.latitude < 0 ? -1 : 1) let offsetSegments = linearRing.segments.map { lineSegment in - return (point: SimplePoint(longitude: lineSegment.point.longitude - offsetLongitude, latitude: lineSegment.point.latitude - offsetLatitude), otherPoint: SimplePoint(longitude: lineSegment.otherPoint.longitude - offsetLongitude, latitude: lineSegment.otherPoint.latitude - offsetLatitude)) + return (point: SimplePoint(longitude: lineSegment.startPoint.longitude - offsetLongitude, latitude: lineSegment.startPoint.latitude - offsetLatitude), otherPoint: SimplePoint(longitude: lineSegment.endPoint.longitude - offsetLongitude, latitude: lineSegment.endPoint.latitude - offsetLatitude)) } offsetSegments.forEach { point, otherPoint in @@ -477,3 +802,29 @@ extension GeodesicCalculator { return SimplePoint(longitude: sumX / 6 / area + offsetLongitude, latitude: sumY / 6 / area + offsetLatitude, altitude: offset.altitude) } } + +internal typealias LineSegmentIndiciesByLineSegmentIndex = [LineSegmentIndex: [LineSegmentIndex]] +internal typealias LineSegmentPointIndiciesByLineSegmentIndex = [LineSegmentIndex: [LineSegmentPointIndex]] + +internal struct LineSegmentIndex: Hashable { + let lineIndex: Int + let segmentIndex: Int + + public static func < (lhs: LineSegmentIndex, rhs: LineSegmentIndex) -> Bool { + if lhs.lineIndex != rhs.lineIndex { + return lhs.lineIndex < rhs.lineIndex + } else { + return lhs.segmentIndex < rhs.segmentIndex + } + } +} + +internal struct LineSegmentPointIndex: Hashable { + enum PointIndex { + case startPoint + case endPoint + } + + let lineSegmentIndex: LineSegmentIndex + let pointIndex: PointIndex +} diff --git a/Sources/GeospatialSwift/API/GeoJson/GeoJsonSimpleViolation.swift b/Sources/GeospatialSwift/API/GeoJson/GeoJsonSimpleViolation.swift new file mode 100644 index 0000000..8611ae3 --- /dev/null +++ b/Sources/GeospatialSwift/API/GeoJson/GeoJsonSimpleViolation.swift @@ -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 +} diff --git a/Sources/GeospatialSwift/API/GeoJson/Object/Feature.swift b/Sources/GeospatialSwift/API/GeoJson/Object/Feature.swift index 4b4ec9a..aac2a33 100644 --- a/Sources/GeospatialSwift/API/GeoJson/Object/Feature.swift +++ b/Sources/GeospatialSwift/API/GeoJson/Object/Feature.swift @@ -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 { diff --git a/Sources/GeospatialSwift/API/GeoJson/Object/FeatureCollection.swift b/Sources/GeospatialSwift/API/GeoJson/Object/FeatureCollection.swift index 38f6c0a..a85617d 100644 --- a/Sources/GeospatialSwift/API/GeoJson/Object/FeatureCollection.swift +++ b/Sources/GeospatialSwift/API/GeoJson/Object/FeatureCollection.swift @@ -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 { diff --git a/Sources/GeospatialSwift/API/GeoJson/Object/GeometryCollection.swift b/Sources/GeospatialSwift/API/GeoJson/Object/GeometryCollection.swift index 5cd1e8c..6359d35 100644 --- a/Sources/GeospatialSwift/API/GeoJson/Object/GeometryCollection.swift +++ b/Sources/GeospatialSwift/API/GeoJson/Object/GeometryCollection.swift @@ -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 { diff --git a/Sources/GeospatialSwift/API/GeoJson/Object/LineString.swift b/Sources/GeospatialSwift/API/GeoJson/Object/LineString.swift index cf39dd5..d64b927 100644 --- a/Sources/GeospatialSwift/API/GeoJson/Object/LineString.swift +++ b/Sources/GeospatialSwift/API/GeoJson/Object/LineString.swift @@ -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 @@ -20,7 +20,7 @@ extension GeoJson { geoJsonPoints = pointsJson.map { Point(coordinatesJson: $0) } } - fileprivate init(points: [Point]) { + internal init(points: [Point]) { geoJsonPoints = points } } @@ -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]) } } @@ -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 { diff --git a/Sources/GeospatialSwift/API/GeoJson/Object/LinearRing.swift b/Sources/GeospatialSwift/API/GeoJson/Object/LinearRing.swift index 7b652bb..7cb11c7 100644 --- a/Sources/GeospatialSwift/API/GeoJson/Object/LinearRing.swift +++ b/Sources/GeospatialSwift/API/GeoJson/Object/LinearRing.swift @@ -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 [] + } } } diff --git a/Sources/GeospatialSwift/API/GeoJson/Object/MultiLineString.swift b/Sources/GeospatialSwift/API/GeoJson/Object/MultiLineString.swift index 226b929..3f56eb6 100644 --- a/Sources/GeospatialSwift/API/GeoJson/Object/MultiLineString.swift +++ b/Sources/GeospatialSwift/API/GeoJson/Object/MultiLineString.swift @@ -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 { diff --git a/Sources/GeospatialSwift/API/GeoJson/Object/MultiPoint.swift b/Sources/GeospatialSwift/API/GeoJson/Object/MultiPoint.swift index c752ab2..8786f1f 100644 --- a/Sources/GeospatialSwift/API/GeoJson/Object/MultiPoint.swift +++ b/Sources/GeospatialSwift/API/GeoJson/Object/MultiPoint.swift @@ -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 { diff --git a/Sources/GeospatialSwift/API/GeoJson/Object/MultiPolygon.swift b/Sources/GeospatialSwift/API/GeoJson/Object/MultiPolygon.swift index 13f8441..4022fb9 100644 --- a/Sources/GeospatialSwift/API/GeoJson/Object/MultiPolygon.swift +++ b/Sources/GeospatialSwift/API/GeoJson/Object/MultiPolygon.swift @@ -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 { diff --git a/Sources/GeospatialSwift/API/GeoJson/Object/Point.swift b/Sources/GeospatialSwift/API/GeoJson/Object/Point.swift index aa8b912..b5cf2b0 100644 --- a/Sources/GeospatialSwift/API/GeoJson/Object/Point.swift +++ b/Sources/GeospatialSwift/API/GeoJson/Object/Point.swift @@ -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 { diff --git a/Sources/GeospatialSwift/API/GeoJson/Object/Polygon.swift b/Sources/GeospatialSwift/API/GeoJson/Object/Polygon.swift index 3d02936..9ed7a71 100644 --- a/Sources/GeospatialSwift/API/GeoJson/Object/Polygon.swift +++ b/Sources/GeospatialSwift/API/GeoJson/Object/Polygon.swift @@ -33,7 +33,7 @@ extension GeoJson.Polygon { public var mainRing: GeodesicLine { geoJsonMainRing } public var negativeRings: [GeodesicLine] { geoJsonNegativeRings } public var linearRings: [GeodesicLine] { geoJsonLinearRings } - + private var geoJsonLinearRings: [GeoJson.LineString] { [geoJsonMainRing] + geoJsonNegativeRings } public var geoJsonCoordinates: [Any] { geoJsonLinearRings.map { $0.geoJsonCoordinates } } @@ -66,11 +66,111 @@ extension GeoJson.Polygon { // Checking winding order is valid // Triangle that reprojection to tile coordinates will cause winding order reversed // Polygon that will be reprojected into tile coordinates as a line - // Polygon with "spike" - // Polygon with hole that has a "spike" // Polygon where area threshold removes geometry AFTER clipping // Polygon with reversed winding order // Polygon with hole where hole has invalid winding order + + public func simpleViolations(tolerance: Double) -> [GeoJsonSimpleViolation] { + //Ring self intersection + let ringSimpleViolations = geoJsonLinearRings.map { GeoJson.LinearRing.simpleViolations(linearRing: $0, tolerance: tolerance) }.filter { $0.count > 0 }.flatMap { $0 } + + guard ringSimpleViolations.isEmpty else { return ringSimpleViolations } + + //Any negative ring points are outside of the main ring + let outsideLineSegmentPointIndexs = Calculator.simpleViolationNegativeRingPointsOutsideMainRingIndices(from: self, tolerance: tolerance) + + guard outsideLineSegmentPointIndexs.isEmpty else { + return outsideLineSegmentPointIndexs.map { outsideLineSegmentPointIndex in + let lineSegmentIndex = outsideLineSegmentPointIndex.lineSegmentIndex + let segment = negativeRings[lineSegmentIndex.lineIndex].segments[lineSegmentIndex.segmentIndex] + + let point1: GeoJson.Point + if outsideLineSegmentPointIndex.pointIndex == .startPoint { + point1 = GeoJson.Point(longitude: segment.startPoint.longitude, latitude: segment.startPoint.latitude) + } else { + point1 = GeoJson.Point(longitude: segment.endPoint.longitude, latitude: segment.endPoint.latitude) + } + return GeoJsonSimpleViolation(problems: [point1], reason: .polygonHoleOutside) + } + } + + //Any negative ring points are inside another negative ring + let negativeRingsInsideIndices = Calculator.simpleViolationNegativeRingInsideAnotherNegativeRingIndices(from: self, tolerance: tolerance) + + guard negativeRingsInsideIndices.isEmpty else { + var violations = [GeoJsonSimpleViolation]() + negativeRingsInsideIndices.forEach { index in + var geometries = [GeoJsonCoordinatesGeometry]() + negativeRings[index].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: .polygonNegativeRingContained)] + } + return violations + } + + //Ring intersects another ring + let simpleViolationIntersectionIndices = Calculator.simpleViolationIntersectionIndices(from: self, tolerance: tolerance) + + guard simpleViolationIntersectionIndices.isEmpty else { + var violations = [GeoJsonSimpleViolation]() + simpleViolationIntersectionIndices.sorted(by: { $0.key < $1.key }).forEach { lineSegmentIndex1 in + let segment1 = linearRings[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 = linearRings[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: .polygonSelfIntersection)] + } + } + return violations + } + + //Ring has multiple vertex intersections with another ring + let simpleViolationMultipleVertexIntersectionIndices = Calculator.simpleViolationMultipleVertexIntersectionIndices(from: self, tolerance: tolerance) + + guard simpleViolationMultipleVertexIntersectionIndices.isEmpty else { + var violations = [GeoJsonSimpleViolation]() + simpleViolationMultipleVertexIntersectionIndices.sorted(by: { $0.key < $1.key }).forEach { lineSegmentPointIndiciesIndex in + let segment1 = linearRings[lineSegmentPointIndiciesIndex.key.lineIndex].segments[lineSegmentPointIndiciesIndex.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]) + + let lineSegmentIndex1 = lineSegmentPointIndiciesIndex.key + for lineSegmentPointIndex in lineSegmentPointIndiciesIndex.value { + //remove duplicacy + let lineSegmentIndex2 = lineSegmentPointIndex.lineSegmentIndex + let lineSegmentStartPointIndex = LineSegmentPointIndex(lineSegmentIndex: lineSegmentIndex1, pointIndex: .startPoint) + let lineSegmentEndPointIndex = LineSegmentPointIndex(lineSegmentIndex: lineSegmentIndex1, pointIndex: .endPoint) + if let lineSegmentPointIndices = simpleViolationMultipleVertexIntersectionIndices[lineSegmentIndex2], lineSegmentIndex2 < lineSegmentPointIndiciesIndex.key { + guard !lineSegmentPointIndices.contains(lineSegmentStartPointIndex) && !lineSegmentPointIndices.contains(lineSegmentEndPointIndex) else { continue } + } + + let segment2 = linearRings[lineSegmentIndex2.lineIndex].segments[lineSegmentIndex2.segmentIndex] + if lineSegmentPointIndex.pointIndex == .startPoint { + let point3 = GeoJson.Point(longitude: segment2.startPoint.longitude, latitude: segment2.startPoint.latitude) + violations += [GeoJsonSimpleViolation(problems: [point1, point2, line1, point3], reason: .polygonMultipleVertexIntersection)] + } else { + let point3 = GeoJson.Point(longitude: segment2.endPoint.longitude, latitude: segment2.endPoint.latitude) + violations += [GeoJsonSimpleViolation(problems: [point1, point2, line1, point3], reason: .polygonMultipleVertexIntersection)] + } + } + } + return violations + } + + return [] + } } extension GeoJson.Polygon { diff --git a/Sources/GeospatialSwift/API/GeoJson/Object/Protocol/GeoJsonObject.swift b/Sources/GeospatialSwift/API/GeoJson/Object/Protocol/GeoJsonObject.swift index b0e8dff..d753227 100644 --- a/Sources/GeospatialSwift/API/GeoJson/Object/Protocol/GeoJsonObject.swift +++ b/Sources/GeospatialSwift/API/GeoJson/Object/Protocol/GeoJsonObject.swift @@ -20,6 +20,8 @@ public protocol GeoJsonObject { func contains(_ point: GeodesicPoint, tolerance: Double) -> Bool + func simpleViolations(tolerance: Double) -> [GeoJsonSimpleViolation] + // SOMEDAY: More fun! //func overlaps(geoJsonObject: GeoJsonObject, tolerance: Double) -> Bool } diff --git a/Sources/GeospatialSwift/API/GeoJson/GeodesicBearing.swift b/Sources/GeospatialSwift/API/Geodesic/GeodesicBearing.swift similarity index 100% rename from Sources/GeospatialSwift/API/GeoJson/GeodesicBearing.swift rename to Sources/GeospatialSwift/API/Geodesic/GeodesicBearing.swift diff --git a/Sources/GeospatialSwift/API/GeoJson/GeodesicBoundingBox.swift b/Sources/GeospatialSwift/API/Geodesic/GeodesicBoundingBox.swift similarity index 95% rename from Sources/GeospatialSwift/API/GeoJson/GeodesicBoundingBox.swift rename to Sources/GeospatialSwift/API/Geodesic/GeodesicBoundingBox.swift index 79aad3f..9a15d24 100644 --- a/Sources/GeospatialSwift/API/GeoJson/GeodesicBoundingBox.swift +++ b/Sources/GeospatialSwift/API/Geodesic/GeodesicBoundingBox.swift @@ -16,7 +16,7 @@ public struct GeodesicBoundingBox { public var longitudeDelta: Double { maxLongitude - minLongitude } public var latitudeDelta: Double { maxLatitude - minLatitude } - public var segments: [GeodesicLineSegment] { [.init(point: points[0], otherPoint: points[1]), .init(point: points[1], otherPoint: points[2]), .init(point: points[2], otherPoint: points[3]), .init(point: points[3], otherPoint: points[0])] } + public var segments: [GeodesicLineSegment] { [.init(startPoint: points[0], endPoint: points[1]), .init(startPoint: points[1], endPoint: points[2]), .init(startPoint: points[2], endPoint: points[3]), .init(startPoint: points[3], endPoint: points[0])] } public var box: GeodesicPolygon { SimplePolygon(mainRing: SimpleLine(segments: segments)!)! } diff --git a/Sources/GeospatialSwift/API/GeoJson/GeodesicLine.swift b/Sources/GeospatialSwift/API/Geodesic/GeodesicLine.swift similarity index 82% rename from Sources/GeospatialSwift/API/GeoJson/GeodesicLine.swift rename to Sources/GeospatialSwift/API/Geodesic/GeodesicLine.swift index fea53c4..cda8f4b 100644 --- a/Sources/GeospatialSwift/API/GeoJson/GeodesicLine.swift +++ b/Sources/GeospatialSwift/API/Geodesic/GeodesicLine.swift @@ -12,7 +12,7 @@ public struct SimpleLine: GeodesicLine { 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]) } } @@ -28,10 +28,10 @@ public struct SimpleLine: GeodesicLine { guard segments.count >= 1 else { return nil } for (index, segment) in segments.enumerated() { - guard index == 0 || segment.point == segments[index - 1].otherPoint else { return nil } + guard index == 0 || segment.startPoint == segments[index - 1].endPoint else { return nil } } - self.points = segments.map { $0.point } + [segments.last!.otherPoint] + self.points = segments.map { $0.startPoint } + [segments.last!.endPoint] } } diff --git a/Sources/GeospatialSwift/API/GeoJson/GeodesicLineSegment.swift b/Sources/GeospatialSwift/API/Geodesic/GeodesicLineSegment.swift similarity index 65% rename from Sources/GeospatialSwift/API/GeoJson/GeodesicLineSegment.swift rename to Sources/GeospatialSwift/API/Geodesic/GeodesicLineSegment.swift index da5c699..adbad9f 100644 --- a/Sources/GeospatialSwift/API/GeoJson/GeodesicLineSegment.swift +++ b/Sources/GeospatialSwift/API/Geodesic/GeodesicLineSegment.swift @@ -1,30 +1,30 @@ public struct GeodesicLineSegment { - public let point: GeodesicPoint - public let otherPoint: GeodesicPoint + public let startPoint: GeodesicPoint + public let endPoint: GeodesicPoint - public var midpoint: GeodesicPoint { Calculator.midpoint(from: point, to: otherPoint) } + public var midpoint: GeodesicPoint { Calculator.midpoint(from: startPoint, to: endPoint) } public var initialBearing: GeodesicBearing { - let bearing = Calculator.initialBearing(from: point, to: otherPoint) + let bearing = Calculator.initialBearing(from: startPoint, to: endPoint) let back = bearing > 180 ? bearing - 180 : bearing + 180 return .init(bearing: bearing, back: back) } public var averageBearing: GeodesicBearing { - let bearing = Calculator.averageBearing(from: point, to: otherPoint) + let bearing = Calculator.averageBearing(from: startPoint, to: endPoint) let back = bearing > 180 ? bearing - 180 : bearing + 180 return .init(bearing: bearing, back: back) } public var finalBearing: GeodesicBearing { - let bearing = Calculator.finalBearing(from: point, to: otherPoint) + let bearing = Calculator.finalBearing(from: startPoint, to: endPoint) let back = bearing > 180 ? bearing - 180 : bearing + 180 return .init(bearing: bearing, back: back) } } public func == (lhs: GeodesicLineSegment, rhs: GeodesicLineSegment) -> Bool { - return lhs.point == rhs.point && lhs.otherPoint == rhs.otherPoint + return lhs.startPoint == rhs.startPoint && lhs.endPoint == rhs.endPoint } public func != (lhs: GeodesicLineSegment, rhs: GeodesicLineSegment) -> Bool { diff --git a/Sources/GeospatialSwift/API/GeoJson/GeodesicPoint.swift b/Sources/GeospatialSwift/API/Geodesic/GeodesicPoint.swift similarity index 100% rename from Sources/GeospatialSwift/API/GeoJson/GeodesicPoint.swift rename to Sources/GeospatialSwift/API/Geodesic/GeodesicPoint.swift diff --git a/Sources/GeospatialSwift/API/GeoJson/GeodesicPolygon.swift b/Sources/GeospatialSwift/API/Geodesic/GeodesicPolygon.swift similarity index 93% rename from Sources/GeospatialSwift/API/GeoJson/GeodesicPolygon.swift rename to Sources/GeospatialSwift/API/Geodesic/GeodesicPolygon.swift index 550adbe..9fab6ea 100644 --- a/Sources/GeospatialSwift/API/GeoJson/GeodesicPolygon.swift +++ b/Sources/GeospatialSwift/API/Geodesic/GeodesicPolygon.swift @@ -23,7 +23,7 @@ public struct SimplePolygon: GeodesicPolygon { for linearRingSegments in ([mainRing.segments] + negativeRings.map { $0.segments }) { guard linearRingSegments.count >= 3 else { return nil } - guard linearRingSegments.first!.point == linearRingSegments.last!.otherPoint else { return nil } + guard linearRingSegments.first!.startPoint == linearRingSegments.last!.endPoint else { return nil } } self.mainRing = mainRing diff --git a/Sources/GeospatialSwift/Extensions/Array.swift b/Sources/GeospatialSwift/Extensions/Array.swift index c3f28bc..4bd6f37 100644 --- a/Sources/GeospatialSwift/Extensions/Array.swift +++ b/Sources/GeospatialSwift/Extensions/Array.swift @@ -7,4 +7,13 @@ internal extension Array { append(newElement) return self } + + func at(_ index: Int) -> Element? { + return self.isAccessible(at: index) ? self[index] : nil + } + + func isAccessible(at index: Int) -> Bool { + return index >= 0 && index <= count - 1 + } + } diff --git a/Tests/GeospatialSwiftTests/Data/MockData.swift b/Tests/GeospatialSwiftTests/Data/MockData.swift index a9bca26..92905ba 100644 --- a/Tests/GeospatialSwiftTests/Data/MockData.swift +++ b/Tests/GeospatialSwiftTests/Data/MockData.swift @@ -18,8 +18,26 @@ final class MockData { static let point: GeoJson.Point = geoJson.point(longitude: 1, latitude: 2, altitude: 3) static let points: [GeoJson.Point] = linesPoints.first! static let lineStrings: [GeoJson.LineString] = linesPoints.map { geoJson.lineString(points: $0).success! } + static let selfIntersectingLineStrings: [GeoJson.LineString] = selfIntersectingLinesPoints.map { geoJson.lineString(points: $0).success! } + static let selfCrossingLineStrings: [GeoJson.LineString] = selfCrossingLinesPoints.map { geoJson.lineString(points: $0).success! } + static let sharingStartAndEndLineStrings: [GeoJson.LineString] = sharingStartAndEndLinesPoints.map { geoJson.lineString(points: $0).success! } + static let doubleNLineStrings: [GeoJson.LineString] = doubleNLinesPoints.map { geoJson.lineString(points: $0).success! } + static let linearRings: [GeoJson.LineString] = linearRingsList.first! static let polygons: [GeoJson.Polygon] = linearRingsList.map { geoJson.polygon(mainRing: $0.first!, negativeRings: Array($0.dropFirst())).success! } + static let sharingCornerLinearRings: [GeoJson.LineString] = sharingCornerRingsList.first! + static let sharingCornerAndOverlappingRings: [GeoJson.LineString] = sharingCornerAndOverlappingRingsList.first! + static let ringIntersectingLinearRings: [GeoJson.LineString] = ringIntersectingRingsList.first! + static let holeOutsideLinearRings: [GeoJson.LineString] = holeOutsideRingsList.first! + static let holeContainedLinearRings: [GeoJson.LineString] = holeContainedRingsList.first! + static let mShapeMainRingLinearRings: [GeoJson.LineString] = mShapeMainRingRingsList.first! + static let doubleMNegativeRingsLinearRings: [GeoJson.LineString] = doubleMNegativeRingsRingsList.first! + static let diamondNegativeRingLinearRings: [GeoJson.LineString] = diamondNegativeRingRingsList.first! + + static let touchingPolygons: [GeoJson.Polygon] = touchingLinearRingsList.map { geoJson.polygon(mainRing: $0.first!, negativeRings: Array($0.dropFirst())).success! } + static let sharingEdgePolygons: [GeoJson.Polygon] = sharingEdgeLinearRingsList.map { geoJson.polygon(mainRing: $0.first!, negativeRings: Array($0.dropFirst())).success! } + static let containingPolygons: [GeoJson.Polygon] = containingPolygonsLinearRingsList.map { geoJson.polygon(mainRing: $0.first!, negativeRings: Array($0.dropFirst())).success! } + static let geometries: [GeoJsonGeometry] = [ MockData.point, geoJson.multiPoint(points: MockData.points).success!, @@ -34,12 +52,12 @@ final class MockData { geoJson.feature(geometry: geoJson.polygon(mainRing: MockData.linearRings.first!, negativeRings: Array(MockData.linearRings.dropFirst())).success!, id: nil, properties: nil).success! ] - static let pointsCoordinatesJson = [[1.0, 2.0, 3.0], [2.0, 2.0, 4.0], [2.0, 3.0, 5.0]] - static let lineStringsCoordinatesJson = [[[1.0, 2.0, 3.0], [2.0, 2.0, 4.0], [2.0, 3.0, 5.0]], [[2.0, 3.0, 3.0], [3.0, 3.0, 4.0], [3.0, 4.0, 5.0], [4.0, 5.0, 6.0]]] - static let linearRingsCoordinatesJson = [[[1.0, 2.0, 3.0], [2.0, 2.0, 4.0], [2.0, 3.0, 5.0], [1.0, 3.0, 4.0], [1.0, 2.0, 3.0]], [[2.0, 3.0, 3.0], [3.0, 3.0, 4.0], [3.0, 4.0, 5.0], [2.0, 4.0, 4.0], [2.0, 3.0, 3.0]]] + static let pointsCoordinatesJson = [[1.0, 2.0, 3.0], [2.0, 2.0, 4.0], [2.0, 3.0, 3.0]] + static let lineStringsCoordinatesJson = [[[1.0, 2.0, 3.0], [2.0, 2.0, 4.0], [2.0, 3.0, 3.0]], [[2.0, 3.0, 3.0], [3.0, 3.0, 4.0], [3.0, 4.0, 5.0], [4.0, 5.0, 6.0]]] + static let linearRingsCoordinatesJson = [[[0.0, 0.0, 3.0], [3.0, 0.0, 4.0], [3.0, 3.0, 5.0], [0.0, 3.0, 4.0]], [[1.0, 1.0, 3.0], [1.0, 2.0, 4.0], [2.0, 2.0, 5.0], [2.0, 1.0, 4.0]]] - private static let partialPolygonsCoordinates1 = [[1.0, 2.0, 3.0], [2.0, 2.0, 4.0], [2.0, 3.0, 5.0], [1.0, 3.0, 4.0], [1.0, 2.0, 3.0]] - private static let partialPolygonsCoordinates2 = [[2.0, 3.0, 3.0], [3.0, 3.0, 4.0], [3.0, 4.0, 5.0], [2.0, 4.0, 4.0], [2.0, 3.0, 3.0]] + private static let partialPolygonsCoordinates1 = [[0.0, 0.0, 3.0], [3.0, 0.0, 4.0], [3.0, 3.0, 5.0], [0.0, 3.0, 4.0], [0.0, 0.0, 3.0]] + private static let partialPolygonsCoordinates2 = [[1.0, 1.0, 3.0], [1.0, 2.0, 4.0], [2.0, 2.0, 5.0], [2.0, 1.0, 4.0], [1.0, 1.0, 3.0]] private static let partialPolygonsCoordinates3 = [[5.0, 6.0, 13.0], [6.0, 6.0, 14.0], [6.0, 7.0, 15.0], [5.0, 7.0, 14.0], [5.0, 6.0, 13.0]] private static let partialPolygonsCoordinates4 = [[6.0, 7.0, 13.0], [7.0, 7.0, 14.0], [7.0, 8.0, 15.0], [6.0, 8.0, 14.0], [6.0, 7.0, 13.0]] static let polygonsCoordinatesJson = [[partialPolygonsCoordinates1, partialPolygonsCoordinates2], [partialPolygonsCoordinates3, partialPolygonsCoordinates4]] @@ -56,16 +74,40 @@ extension MockData { } private static let linesPoints: [[GeoJson.Point]] = [ - [GeoTestHelper.point(1, 2, 3), GeoTestHelper.point(2, 2, 4), GeoTestHelper.point(2, 3, 5)], + [GeoTestHelper.point(1, 2, 3), GeoTestHelper.point(2, 2, 4), GeoTestHelper.point(2, 3, 3)], [GeoTestHelper.point(2, 3, 3), GeoTestHelper.point(3, 3, 4), GeoTestHelper.point(3, 4, 5), GeoTestHelper.point(4, 5, 6)] ] + private static let selfIntersectingLinesPoints: [[GeoJson.Point]] = [ + [GeoTestHelper.point(21, 20, 3), GeoTestHelper.point(20, 21, 4), GeoTestHelper.point(20, 19, 5)], + [GeoTestHelper.point(19, 20, 3), GeoTestHelper.point(23, 20, 4)] + ] + + private static let selfCrossingLinesPoints: [[GeoJson.Point]] = [ + [GeoTestHelper.point(1, -1, 3), GeoTestHelper.point(0, 1, 4), GeoTestHelper.point(0, 0, 5)], + [GeoTestHelper.point(0, 0, 5), GeoTestHelper.point(3, 0, 4)] + ] + + private static let sharingStartAndEndLinesPoints: [[GeoJson.Point]] = [ + [GeoTestHelper.point(20, 20, 0), GeoTestHelper.point(20, 21, 0), GeoTestHelper.point(21, 21, 0)], + [GeoTestHelper.point(20, 20, 0), GeoTestHelper.point(19, 20, 0)], + [GeoTestHelper.point(21, 21, 0), GeoTestHelper.point(21, 22, 0)], + [GeoTestHelper.point(20, 20, 0), GeoTestHelper.point(21, 20, 0), GeoTestHelper.point(21, 21, 0)], + [GeoTestHelper.point(21, 21, 0), GeoTestHelper.point(22, 21, 0)] + ] + + private static let doubleNLinesPoints: [[GeoJson.Point]] = [ + [GeoTestHelper.point(0, 0, 0), GeoTestHelper.point(3, 0, 0), GeoTestHelper.point(0, 1, 0), GeoTestHelper.point(3, 1, 0)], + [GeoTestHelper.point(1, -1, 0), GeoTestHelper.point(1, 2, 0), GeoTestHelper.point(2, -1, 0), GeoTestHelper.point(2, 2, 0)] + ] + private static let polygonPoints: [[GeoJson.Point]] = polygonPointsList.first! private static let polygonPointsList: [[[GeoJson.Point]]] = [ [ - [GeoTestHelper.point(1, 2, 3), GeoTestHelper.point(2, 2, 4), GeoTestHelper.point(2, 3, 5), GeoTestHelper.point(1, 3, 4), GeoTestHelper.point(1, 2, 3)], - [GeoTestHelper.point(2, 3, 3), GeoTestHelper.point(3, 3, 4), GeoTestHelper.point(3, 4, 5), GeoTestHelper.point(2, 4, 4), GeoTestHelper.point(2, 3, 3)] + [GeoTestHelper.point(0, 0, 3), GeoTestHelper.point(3, 0, 4), GeoTestHelper.point(3, 3, 5), GeoTestHelper.point(0, 3, 4), GeoTestHelper.point(0, 0, 3)], + [GeoTestHelper.point(1, 1, 3), GeoTestHelper.point(1, 2, 4), GeoTestHelper.point(2, 2, 5), GeoTestHelper.point(2, 1, 4), GeoTestHelper.point(1, 1, 3)] + ], [ [GeoTestHelper.point(5, 6, 13), GeoTestHelper.point(6, 6, 14), GeoTestHelper.point(6, 7, 15), GeoTestHelper.point(5, 7, 14), GeoTestHelper.point(5, 6, 13)], @@ -74,4 +116,111 @@ extension MockData { ] private static let linearRingsList: [[GeoJson.LineString]] = polygonPointsList.map { $0.map { geoJson.lineString(points: $0).success! } } + + private static let sharingCornerPolygonPointsList: [[[GeoJson.Point]]] = [ + [ + [GeoTestHelper.point(20, 20), GeoTestHelper.point(23, 20), GeoTestHelper.point(23, 23), GeoTestHelper.point(20, 23), GeoTestHelper.point(20, 20)], + [GeoTestHelper.point(20, 20), GeoTestHelper.point(22, 21), GeoTestHelper.point(21, 22), GeoTestHelper.point(20, 20)] + ] + ] + + private static let sharingCornerRingsList: [[GeoJson.LineString]] = sharingCornerPolygonPointsList.map { $0.map { geoJson.lineString(points: $0).success! } } + + private static let sharingCornerAndOverlappingPolygonPointsList: [[[GeoJson.Point]]] = [ + [ + [GeoTestHelper.point(20, 0), GeoTestHelper.point(23, 0), GeoTestHelper.point(23, 3), GeoTestHelper.point(20, 3), GeoTestHelper.point(20, 0)], + [GeoTestHelper.point(20, 0), GeoTestHelper.point(21, 0), GeoTestHelper.point(21, 1), GeoTestHelper.point(20, 1), GeoTestHelper.point(20, 0)] + ] + ] + + private static let sharingCornerAndOverlappingRingsList: [[GeoJson.LineString]] = sharingCornerAndOverlappingPolygonPointsList.map { $0.map { geoJson.lineString(points: $0).success! } } + + private static let ringIntersectingPolygonPointsList: [[[GeoJson.Point]]] = [ + [ + [GeoTestHelper.point(20, 20), GeoTestHelper.point(23, 20), GeoTestHelper.point(23, 23), GeoTestHelper.point(20, 23), GeoTestHelper.point(20, 20)], + [GeoTestHelper.point(22, 21), GeoTestHelper.point(24, 21), GeoTestHelper.point(24, 22), GeoTestHelper.point(22, 22), GeoTestHelper.point(22, 21)] + ] + ] + + private static let ringIntersectingRingsList: [[GeoJson.LineString]] = ringIntersectingPolygonPointsList.map { $0.map { geoJson.lineString(points: $0).success! } } + + private static let holeOutsidePolygonPointsList: [[[GeoJson.Point]]] = [ + [ + [GeoTestHelper.point(20, 20), GeoTestHelper.point(23, 20), GeoTestHelper.point(23, 23), GeoTestHelper.point(20, 23), GeoTestHelper.point(20, 20)], + [GeoTestHelper.point(25, 25), GeoTestHelper.point(26, 25), GeoTestHelper.point(26, 26), GeoTestHelper.point(25, 26), GeoTestHelper.point(25, 25)] + ] + ] + + private static let holeOutsideRingsList: [[GeoJson.LineString]] = holeOutsidePolygonPointsList.map { $0.map { geoJson.lineString(points: $0).success! } } + + private static let holeContainedPolygonPointsList: [[[GeoJson.Point]]] = [ + [ + [GeoTestHelper.point(20, 20), GeoTestHelper.point(25, 20), GeoTestHelper.point(25, 25), GeoTestHelper.point(20, 25), GeoTestHelper.point(20, 20)], + [GeoTestHelper.point(21, 21), GeoTestHelper.point(24, 21), GeoTestHelper.point(24, 24), GeoTestHelper.point(21, 24), GeoTestHelper.point(21, 21)], + [GeoTestHelper.point(22, 22), GeoTestHelper.point(23, 22), GeoTestHelper.point(23, 23), GeoTestHelper.point(22, 23), GeoTestHelper.point(22, 22)] + ] + ] + + private static let holeContainedRingsList: [[GeoJson.LineString]] = holeContainedPolygonPointsList.map { $0.map { geoJson.lineString(points: $0).success! } } + + private static let mShapeMainRingPolygonPointsList: [[[GeoJson.Point]]] = [ + [ + [GeoTestHelper.point(20, 20), GeoTestHelper.point(24, 20), GeoTestHelper.point(24, 26), GeoTestHelper.point(22, 22), GeoTestHelper.point(20, 26), GeoTestHelper.point(20, 20)], + [GeoTestHelper.point(21, 21), GeoTestHelper.point(23, 21), GeoTestHelper.point(23, 23), GeoTestHelper.point(21, 23), GeoTestHelper.point(21, 21)] + ] + ] + + private static let mShapeMainRingRingsList: [[GeoJson.LineString]] = mShapeMainRingPolygonPointsList.map { $0.map { geoJson.lineString(points: $0).success! } } + + private static let doubleMNegativeRingsPolygonPointsList: [[[GeoJson.Point]]] = [ + [ + [GeoTestHelper.point(20, 20), GeoTestHelper.point(26, 20), GeoTestHelper.point(26, 26), GeoTestHelper.point(20, 26), GeoTestHelper.point(20, 20)], + [GeoTestHelper.point(21, 21), GeoTestHelper.point(23, 21), GeoTestHelper.point(22, 22), GeoTestHelper.point(23, 23), GeoTestHelper.point(21, 23), GeoTestHelper.point(21, 21)], + [GeoTestHelper.point(23, 21), GeoTestHelper.point(25, 21), GeoTestHelper.point(25, 23), GeoTestHelper.point(23, 23), GeoTestHelper.point(24, 22), GeoTestHelper.point(23, 21)] + ] + ] + + private static let doubleMNegativeRingsRingsList: [[GeoJson.LineString]] = doubleMNegativeRingsPolygonPointsList.map { $0.map { geoJson.lineString(points: $0).success! } } + + private static let diamondNegativeRingPolygonPointsList: [[[GeoJson.Point]]] = [ + [ + [GeoTestHelper.point(20, 20), GeoTestHelper.point(24, 20), GeoTestHelper.point(24, 24), GeoTestHelper.point(20, 24), GeoTestHelper.point(20, 20)], + [GeoTestHelper.point(20, 20), GeoTestHelper.point(23, 21), GeoTestHelper.point(24, 24), GeoTestHelper.point(21, 23), GeoTestHelper.point(20, 20)] + ] + ] + + private static let diamondNegativeRingRingsList: [[GeoJson.LineString]] = diamondNegativeRingPolygonPointsList.map { $0.map { geoJson.lineString(points: $0).success! } } + + private static let touchingPolygonPointsList: [[[GeoJson.Point]]] = [ + [ + [GeoTestHelper.point(20, 20), GeoTestHelper.point(20, 21), GeoTestHelper.point(21, 21), GeoTestHelper.point(21, 20), GeoTestHelper.point(20, 20)] + ], + [ + [GeoTestHelper.point(21, 21), GeoTestHelper.point(21, 22), GeoTestHelper.point(22, 22), GeoTestHelper.point(22, 21), GeoTestHelper.point(21, 21)] + ] + ] + + private static let touchingLinearRingsList: [[GeoJson.LineString]] = touchingPolygonPointsList.map { $0.map { geoJson.lineString(points: $0).success! } } + + private static let sharingEdgePolygonPointsList: [[[GeoJson.Point]]] = [ + [ + [GeoTestHelper.point(20, 20), GeoTestHelper.point(20, 21), GeoTestHelper.point(21, 21), GeoTestHelper.point(21, 20), GeoTestHelper.point(20, 20)] + ], + [ + [GeoTestHelper.point(21, 20), GeoTestHelper.point(22, 20), GeoTestHelper.point(22, 21), GeoTestHelper.point(21, 21), GeoTestHelper.point(21, 20)] + ] + ] + + private static let sharingEdgeLinearRingsList: [[GeoJson.LineString]] = sharingEdgePolygonPointsList.map { $0.map { geoJson.lineString(points: $0).success! } } + + private static let containingPolygonsPointsList: [[[GeoJson.Point]]] = [ + [ + [GeoTestHelper.point(20, 20), GeoTestHelper.point(20, 24), GeoTestHelper.point(24, 24), GeoTestHelper.point(24, 20), GeoTestHelper.point(20, 20)] + ], + [ + [GeoTestHelper.point(21, 21), GeoTestHelper.point(22, 21), GeoTestHelper.point(22, 22), GeoTestHelper.point(21, 22), GeoTestHelper.point(21, 21)] + ] + ] + + private static let containingPolygonsLinearRingsList: [[GeoJson.LineString]] = containingPolygonsPointsList.map { $0.map { geoJson.lineString(points: $0).success! } } } diff --git a/Tests/GeospatialSwiftTests/Test/API/Calculator/GeodesicCalculatorTests.swift b/Tests/GeospatialSwiftTests/Test/API/Calculator/GeodesicCalculatorTests.swift index c062a77..cdae083 100644 --- a/Tests/GeospatialSwiftTests/Test/API/Calculator/GeodesicCalculatorTests.swift +++ b/Tests/GeospatialSwiftTests/Test/API/Calculator/GeodesicCalculatorTests.swift @@ -8,9 +8,9 @@ class GeodesicCalculatorTests: XCTestCase { } func testHasIntersection_SameLine() { - let point = SimplePoint(longitude: 1, latitude: 2) - let otherPoint = SimplePoint(longitude: 1.5, latitude: 2.5) - let lineSegment = GeodesicLineSegment(point: point, otherPoint: otherPoint) + let startPoint = SimplePoint(longitude: 1, latitude: 2) + let endPoint = SimplePoint(longitude: 1.5, latitude: 2.5) + let lineSegment = GeodesicLineSegment(startPoint: startPoint, endPoint: endPoint) let intersects = Calculator.hasIntersection(lineSegment, with: lineSegment, tolerance: 0) @@ -18,13 +18,13 @@ class GeodesicCalculatorTests: XCTestCase { } func testHasIntersection_Overlaps_InsideLine_NotEnoughTolerence() { - var point = SimplePoint(longitude: 1, latitude: 2) - var otherPoint = SimplePoint(longitude: 1.5, latitude: 2.5) - let lineSegment1 = GeodesicLineSegment(point: point, otherPoint: otherPoint) + var startPoint = SimplePoint(longitude: 1, latitude: 2) + var endPoint = SimplePoint(longitude: 1.5, latitude: 2.5) + let lineSegment1 = GeodesicLineSegment(startPoint: startPoint, endPoint: endPoint) - point = SimplePoint(longitude: 1.2, latitude: 2.2) - otherPoint = SimplePoint(longitude: 1.7, latitude: 2.7) - let lineSegment2 = GeodesicLineSegment(point: point, otherPoint: otherPoint) + startPoint = SimplePoint(longitude: 1.2, latitude: 2.2) + endPoint = SimplePoint(longitude: 1.7, latitude: 2.7) + let lineSegment2 = GeodesicLineSegment(startPoint: startPoint, endPoint: endPoint) let intersects = Calculator.hasIntersection(lineSegment1, with: lineSegment2, tolerance: 4) @@ -32,13 +32,13 @@ class GeodesicCalculatorTests: XCTestCase { } func testHasIntersection_Overlaps_InsideLine_EnoughTolerence() { - var point = SimplePoint(longitude: 1, latitude: 2) - var otherPoint = SimplePoint(longitude: 1.5, latitude: 2.5) - let lineSegment1 = GeodesicLineSegment(point: point, otherPoint: otherPoint) + var startPoint = SimplePoint(longitude: 1, latitude: 2) + var endPoint = SimplePoint(longitude: 1.5, latitude: 2.5) + let lineSegment1 = GeodesicLineSegment(startPoint: startPoint, endPoint: endPoint) - point = SimplePoint(longitude: 1.2, latitude: 2.2) - otherPoint = SimplePoint(longitude: 1.7, latitude: 2.7) - let lineSegment2 = GeodesicLineSegment(point: point, otherPoint: otherPoint) + startPoint = SimplePoint(longitude: 1.2, latitude: 2.2) + endPoint = SimplePoint(longitude: 1.7, latitude: 2.7) + let lineSegment2 = GeodesicLineSegment(startPoint: startPoint, endPoint: endPoint) let intersects = Calculator.hasIntersection(lineSegment1, with: lineSegment2, tolerance: 5) @@ -46,13 +46,13 @@ class GeodesicCalculatorTests: XCTestCase { } func testHasIntersection_SameTrajectoryNoOverlap_Ahead() { - var point = SimplePoint(longitude: 1, latitude: 2) - var otherPoint = SimplePoint(longitude: 1.5, latitude: 2.5) - let lineSegment1 = GeodesicLineSegment(point: point, otherPoint: otherPoint) + var startPoint = SimplePoint(longitude: 1, latitude: 2) + var endPoint = SimplePoint(longitude: 1.5, latitude: 2.5) + let lineSegment1 = GeodesicLineSegment(startPoint: startPoint, endPoint: endPoint) - point = SimplePoint(longitude: 2, latitude: 3) - otherPoint = SimplePoint(longitude: 2.5, latitude: 3.5) - let lineSegment2 = GeodesicLineSegment(point: point, otherPoint: otherPoint) + startPoint = SimplePoint(longitude: 2, latitude: 3) + endPoint = SimplePoint(longitude: 2.5, latitude: 3.5) + let lineSegment2 = GeodesicLineSegment(startPoint: startPoint, endPoint: endPoint) let intersects = Calculator.hasIntersection(lineSegment1, with: lineSegment2, tolerance: 0) @@ -60,13 +60,13 @@ class GeodesicCalculatorTests: XCTestCase { } func testHasIntersection_SameTrajectoryNoOverlap_Behind() { - var point = SimplePoint(longitude: 1, latitude: 2) - var otherPoint = SimplePoint(longitude: 1.5, latitude: 2.5) - let lineSegment1 = GeodesicLineSegment(point: point, otherPoint: otherPoint) + var startPoint = SimplePoint(longitude: 1, latitude: 2) + var endPoint = SimplePoint(longitude: 1.5, latitude: 2.5) + let lineSegment1 = GeodesicLineSegment(startPoint: startPoint, endPoint: endPoint) - point = SimplePoint(longitude: 0, latitude: 1) - otherPoint = SimplePoint(longitude: 0.5, latitude: 1.5) - let lineSegment2 = GeodesicLineSegment(point: point, otherPoint: otherPoint) + startPoint = SimplePoint(longitude: 0, latitude: 1) + endPoint = SimplePoint(longitude: 0.5, latitude: 1.5) + let lineSegment2 = GeodesicLineSegment(startPoint: startPoint, endPoint: endPoint) let intersects = Calculator.hasIntersection(lineSegment1, with: lineSegment2, tolerance: 0) @@ -74,7 +74,7 @@ class GeodesicCalculatorTests: XCTestCase { } func testHasIntersection_LineIntersectingPolygon_Inside() { - let lineSegment = GeodesicLineSegment(point: SimplePoint(longitude: 0.2, latitude: 0.5), otherPoint: SimplePoint(longitude: 0.8, latitude: 0.5)) + let lineSegment = GeodesicLineSegment(startPoint: SimplePoint(longitude: 0.2, latitude: 0.5), endPoint: SimplePoint(longitude: 0.8, latitude: 0.5)) let polygon = MockData.box let intersects = Calculator.hasIntersection(lineSegment, with: polygon, tolerance: 0) @@ -83,7 +83,7 @@ class GeodesicCalculatorTests: XCTestCase { } func testHasIntersection_LineIntersectingPolygon_OnLine() { - let lineSegment = GeodesicLineSegment(point: SimplePoint(longitude: 1.5, latitude: 0.5), otherPoint: SimplePoint(longitude: 0.8, latitude: 0.5)) + let lineSegment = GeodesicLineSegment(startPoint: SimplePoint(longitude: 1.5, latitude: 0.5), endPoint: SimplePoint(longitude: 0.8, latitude: 0.5)) let polygon = MockData.box let intersects = Calculator.hasIntersection(lineSegment, with: polygon, tolerance: 0) @@ -92,7 +92,7 @@ class GeodesicCalculatorTests: XCTestCase { } func testHasIntersection_LineIntersectingPolygon_CrossesTwice() { - let lineSegment = GeodesicLineSegment(point: SimplePoint(longitude: 0.9, latitude: 0.5), otherPoint: SimplePoint(longitude: 2.0, latitude: 1.5)) + let lineSegment = GeodesicLineSegment(startPoint: SimplePoint(longitude: 0.9, latitude: 0.5), endPoint: SimplePoint(longitude: 2.0, latitude: 1.5)) let polygon = MockData.box let intersects = Calculator.hasIntersection(lineSegment, with: polygon, tolerance: 0) @@ -101,7 +101,7 @@ class GeodesicCalculatorTests: XCTestCase { } func testHasIntersection_LineIntersectingPolygon_LineCrossesThrough() { - let lineSegment = GeodesicLineSegment(point: SimplePoint(longitude: -1.0, latitude: 0.5), otherPoint: SimplePoint(longitude: 2.0, latitude: 0.5)) + let lineSegment = GeodesicLineSegment(startPoint: SimplePoint(longitude: -1.0, latitude: 0.5), endPoint: SimplePoint(longitude: 2.0, latitude: 0.5)) let polygon = MockData.box let intersects = Calculator.hasIntersection(lineSegment, with: polygon, tolerance: 0) @@ -110,7 +110,7 @@ class GeodesicCalculatorTests: XCTestCase { } func testHasIntersection_LineIntersectingPolygon_LineOutside() { - let lineSegment = GeodesicLineSegment(point: SimplePoint(longitude: 1.5, latitude: 0.5), otherPoint: SimplePoint(longitude: 2.0, latitude: 0.5)) + let lineSegment = GeodesicLineSegment(startPoint: SimplePoint(longitude: 1.5, latitude: 0.5), endPoint: SimplePoint(longitude: 2.0, latitude: 0.5)) let polygon = MockData.box let intersects = Calculator.hasIntersection(lineSegment, with: polygon, tolerance: 0) @@ -154,4 +154,136 @@ class GeodesicCalculatorTests: XCTestCase { XCTAssertEqual(intersects, false) } + + func testHasIntersection_LineTouchingPolygon_LineOutside() { + let lineSegment = GeodesicLineSegment(startPoint: SimplePoint(longitude: 0.5, latitude: 1.5), endPoint: SimplePoint(longitude: 2.0, latitude: 0)) + let polygon = MockData.box + + let intersects = Calculator.hasIntersection(lineSegment, with: polygon, tolerance: 0) + + XCTAssertEqual(intersects, true) + } + + func testHasIntersection_LineSegments_Closed() { + let line = SimpleLine(points: [ + SimplePoint(longitude: 0, latitude: 0), + SimplePoint(longitude: 0, latitude: 1), + SimplePoint(longitude: 1, latitude: 1), + SimplePoint(longitude: 0, latitude: 0) + ])! + + let intersects = Calculator.hasIntersection(line, tolerance: 0) + + XCTAssertEqual(intersects, true) + } + + // swiftlint:disable:next function_body_length + func testContainsInPolygon() { + let points = [ + SimplePoint(longitude: 0, latitude: 0), + SimplePoint(longitude: 0, latitude: 2), + SimplePoint(longitude: 1, latitude: 4), + SimplePoint(longitude: 2, latitude: 2), + SimplePoint(longitude: 3, latitude: 4), + SimplePoint(longitude: 4, latitude: 2), + SimplePoint(longitude: 4, latitude: 0), + SimplePoint(longitude: 0, latitude: 0) + ] + + let pointToTest1 = SimplePoint(longitude: -1, latitude: 5) + let pointToTest2 = SimplePoint(longitude: -1, latitude: 4) + let pointToTest3 = SimplePoint(longitude: -1, latitude: 3) + let pointToTest4 = SimplePoint(longitude: -1, latitude: 2) + let pointToTest5 = SimplePoint(longitude: -1, latitude: 1) + let pointToTest6 = SimplePoint(longitude: -1, latitude: 0) + let pointToTest7 = SimplePoint(longitude: -1, latitude: -1) + + XCTAssertEqual(Calculator.contains(point: pointToTest1, vertices: points), false) + XCTAssertEqual(Calculator.contains(point: pointToTest2, vertices: points), false) + XCTAssertEqual(Calculator.contains(point: pointToTest3, vertices: points), false) + XCTAssertEqual(Calculator.contains(point: pointToTest4, vertices: points), false) + XCTAssertEqual(Calculator.contains(point: pointToTest5, vertices: points), false) + XCTAssertEqual(Calculator.contains(point: pointToTest6, vertices: points), false) + XCTAssertEqual(Calculator.contains(point: pointToTest7, vertices: points), false) + + let pointToTest11 = SimplePoint(longitude: 0, latitude: 5) + let pointToTest12 = SimplePoint(longitude: 0, latitude: 4) + let pointToTest13 = SimplePoint(longitude: 0, latitude: 3) + let pointToTest14 = SimplePoint(longitude: 0, latitude: 2) + let pointToTest15 = SimplePoint(longitude: 0, latitude: 1) + let pointToTest16 = SimplePoint(longitude: 0, latitude: 0) + let pointToTest17 = SimplePoint(longitude: 0, latitude: -1) + + XCTAssertEqual(Calculator.contains(point: pointToTest11, vertices: points), false) + XCTAssertEqual(Calculator.contains(point: pointToTest12, vertices: points), false) + XCTAssertEqual(Calculator.contains(point: pointToTest13, vertices: points), false) + XCTAssertEqual(Calculator.contains(point: pointToTest14, vertices: points), true) + XCTAssertEqual(Calculator.contains(point: pointToTest15, vertices: points), true) + XCTAssertEqual(Calculator.contains(point: pointToTest16, vertices: points), true) + XCTAssertEqual(Calculator.contains(point: pointToTest17, vertices: points), false) + + let pointToTest21 = SimplePoint(longitude: 1, latitude: 5) + let pointToTest22 = SimplePoint(longitude: 1, latitude: 4) + let pointToTest23 = SimplePoint(longitude: 1, latitude: 3) + let pointToTest24 = SimplePoint(longitude: 1, latitude: 2) + let pointToTest25 = SimplePoint(longitude: 1, latitude: 1) + let pointToTest26 = SimplePoint(longitude: 1, latitude: 0) + let pointToTest27 = SimplePoint(longitude: 1, latitude: -1) + + XCTAssertEqual(Calculator.contains(point: pointToTest21, vertices: points), false) + XCTAssertEqual(Calculator.contains(point: pointToTest22, vertices: points), true) + XCTAssertEqual(Calculator.contains(point: pointToTest23, vertices: points), true) + XCTAssertEqual(Calculator.contains(point: pointToTest24, vertices: points), true) + XCTAssertEqual(Calculator.contains(point: pointToTest25, vertices: points), true) + XCTAssertEqual(Calculator.contains(point: pointToTest26, vertices: points), true) + XCTAssertEqual(Calculator.contains(point: pointToTest27, vertices: points), false) + + let pointToTest31 = SimplePoint(longitude: 2, latitude: 5) + let pointToTest32 = SimplePoint(longitude: 2, latitude: 4) + let pointToTest33 = SimplePoint(longitude: 2, latitude: 3) + let pointToTest34 = SimplePoint(longitude: 2, latitude: 2) + let pointToTest35 = SimplePoint(longitude: 2, latitude: 1) + let pointToTest36 = SimplePoint(longitude: 2, latitude: 0) + let pointToTest37 = SimplePoint(longitude: 2, latitude: -1) + + XCTAssertEqual(Calculator.contains(point: pointToTest31, vertices: points), false) + XCTAssertEqual(Calculator.contains(point: pointToTest32, vertices: points), false) + XCTAssertEqual(Calculator.contains(point: pointToTest33, vertices: points), false) + XCTAssertEqual(Calculator.contains(point: pointToTest34, vertices: points), true) + XCTAssertEqual(Calculator.contains(point: pointToTest35, vertices: points), true) + XCTAssertEqual(Calculator.contains(point: pointToTest36, vertices: points), true) + XCTAssertEqual(Calculator.contains(point: pointToTest37, vertices: points), false) + + let pointToTest41 = SimplePoint(longitude: 3, latitude: 5) + let pointToTest42 = SimplePoint(longitude: 3, latitude: 4) + let pointToTest43 = SimplePoint(longitude: 3, latitude: 3) + let pointToTest44 = SimplePoint(longitude: 3, latitude: 2) + let pointToTest45 = SimplePoint(longitude: 3, latitude: 1) + let pointToTest46 = SimplePoint(longitude: 3, latitude: 0) + let pointToTest47 = SimplePoint(longitude: 3, latitude: -1) + + XCTAssertEqual(Calculator.contains(point: pointToTest41, vertices: points), false) + XCTAssertEqual(Calculator.contains(point: pointToTest42, vertices: points), true) + XCTAssertEqual(Calculator.contains(point: pointToTest43, vertices: points), true) + XCTAssertEqual(Calculator.contains(point: pointToTest44, vertices: points), true) + XCTAssertEqual(Calculator.contains(point: pointToTest45, vertices: points), true) + XCTAssertEqual(Calculator.contains(point: pointToTest46, vertices: points), true) + XCTAssertEqual(Calculator.contains(point: pointToTest47, vertices: points), false) + + let pointToTest51 = SimplePoint(longitude: 4, latitude: 5) + let pointToTest52 = SimplePoint(longitude: 4, latitude: 4) + let pointToTest53 = SimplePoint(longitude: 4, latitude: 3) + let pointToTest54 = SimplePoint(longitude: 4, latitude: 2) + let pointToTest55 = SimplePoint(longitude: 4, latitude: 1) + let pointToTest56 = SimplePoint(longitude: 4, latitude: 0) + let pointToTest57 = SimplePoint(longitude: 4, latitude: -1) + + XCTAssertEqual(Calculator.contains(point: pointToTest51, vertices: points), false) + XCTAssertEqual(Calculator.contains(point: pointToTest52, vertices: points), false) + XCTAssertEqual(Calculator.contains(point: pointToTest53, vertices: points), false) + XCTAssertEqual(Calculator.contains(point: pointToTest54, vertices: points), true) + XCTAssertEqual(Calculator.contains(point: pointToTest55, vertices: points), true) + XCTAssertEqual(Calculator.contains(point: pointToTest56, vertices: points), true) + XCTAssertEqual(Calculator.contains(point: pointToTest57, vertices: points), false) + } } diff --git a/Tests/GeospatialSwiftTests/Test/API/GeoJson/GeodesicLineSegmentTests.swift b/Tests/GeospatialSwiftTests/Test/API/GeoJson/GeodesicLineSegmentTests.swift index 16a1114..97ad397 100644 --- a/Tests/GeospatialSwiftTests/Test/API/GeoJson/GeodesicLineSegmentTests.swift +++ b/Tests/GeospatialSwiftTests/Test/API/GeoJson/GeodesicLineSegmentTests.swift @@ -5,17 +5,17 @@ import XCTest class GeodesicLineSegmentTests: XCTestCase { private var lineSegment: GeodesicLineSegment! private var reversedLineSegment: GeodesicLineSegment! - private var point: GeodesicPoint! - private var otherPoint: GeodesicPoint! + private var startPoint: GeodesicPoint! + private var endPoint: GeodesicPoint! override func setUp() { super.setUp() - point = SimplePoint(longitude: 1, latitude: 2, altitude: 3) - otherPoint = SimplePoint(longitude: 10, latitude: 10, altitude: 10) + startPoint = SimplePoint(longitude: 1, latitude: 2, altitude: 3) + endPoint = SimplePoint(longitude: 10, latitude: 10, altitude: 10) - lineSegment = .init(point: point, otherPoint: otherPoint) - reversedLineSegment = .init(point: otherPoint, otherPoint: point) + lineSegment = .init(startPoint: startPoint, endPoint: endPoint) + reversedLineSegment = .init(startPoint: endPoint, endPoint: startPoint) } func testInitialBearing() { diff --git a/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/FeatureCollectionTests.swift b/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/FeatureCollectionTests.swift index 2ec3235..2b1c604 100644 --- a/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/FeatureCollectionTests.swift +++ b/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/FeatureCollectionTests.swift @@ -67,8 +67,16 @@ class FeatureCollectionTests: XCTestCase { XCTAssertEqual(features?[2]["type"] as? String, "Feature") XCTAssertEqual(features?[2]["properties"] as? NSNull, NSNull()) let feature3 = features?[2]["geometry"] as? [String: Any] + + let feature3Coordinates = feature3?["coordinates"] as? [[[Double]]] + var feature3CoordinatesTrimmed = [[[Double]]]() + + feature3Coordinates?.forEach { + feature3CoordinatesTrimmed.append($0.dropLast()) + } + XCTAssertEqual(feature3?["type"] as? String, "Polygon") - XCTAssertEqual(feature3?["coordinates"] as? [[[Double]]], MockData.linearRingsCoordinatesJson) + XCTAssertEqual(feature3CoordinatesTrimmed, MockData.linearRingsCoordinatesJson) } func testObjectDistance() { diff --git a/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/LineStringTests.swift b/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/LineStringTests.swift index cda989c..366d5fa 100644 --- a/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/LineStringTests.swift +++ b/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/LineStringTests.swift @@ -4,15 +4,26 @@ import XCTest class LineStringTests: XCTestCase { var points: [Point]! + var selfIntersectingPoints: [Point]! + var selfOverlappingPoints: [Point]! + var closedPoints: [Point]! var lineString: LineString! + var selfIntersectingLineString: LineString! + var selfOverlappingLineString: LineString! + var distancePoint: SimplePoint! override func setUp() { super.setUp() - points = [GeoTestHelper.point(1, 2, 3), GeoTestHelper.point(2, 2, 4), GeoTestHelper.point(2, 3, 5)] + points = [GeoTestHelper.point(1, 2, 3), GeoTestHelper.point(2, 2, 4), GeoTestHelper.point(2, 3, 3)] + selfIntersectingPoints = [GeoTestHelper.point(2, 0, 0), GeoTestHelper.point(0, 0, 0), GeoTestHelper.point(1, 3, 0), GeoTestHelper.point(1, -4, 0)] + selfOverlappingPoints = [GeoTestHelper.point(0, 0, 0), GeoTestHelper.point(3, 0, 0), GeoTestHelper.point(1, 0, 0), GeoTestHelper.point(2, 0, 0)] + closedPoints = [GeoTestHelper.point(1, 2, 3), GeoTestHelper.point(2, 2, 4), GeoTestHelper.point(2, 3, 3), GeoTestHelper.point(1, 2, 3)] lineString = GeoTestHelper.lineString(points) + selfIntersectingLineString = GeoTestHelper.lineString(selfIntersectingPoints) + selfOverlappingLineString = GeoTestHelper.lineString(selfOverlappingPoints) distancePoint = GeoTestHelper.simplePoint(10, 10, 10) } @@ -34,6 +45,32 @@ class LineStringTests: XCTestCase { XCTAssertEqual(lineString.closedGeometries.count, 0) } + func testLineString_IsValid() { + XCTAssertEqual(lineString.simpleViolations(tolerance: 0).count, 0) + } + + func testSelfInterSectingLineString_IsInvalid() { + let simpleViolations = selfIntersectingLineString.simpleViolations(tolerance: 0) + XCTAssertEqual(simpleViolations.count, 1) + let geometry = simpleViolations[0].problems + XCTAssertEqual(geometry[0].points[0].longitude, 2.0) + XCTAssertEqual(geometry[0].points[0].latitude, 0.0) + XCTAssertEqual(geometry[1].points[0].longitude, 0.0) + XCTAssertEqual(geometry[1].points[0].latitude, 0.0) + XCTAssertEqual(geometry[3].points[0].longitude, 1.0) + XCTAssertEqual(geometry[3].points[0].latitude, 3.0) + XCTAssertEqual(geometry[4].points[0].longitude, 1.0) + XCTAssertEqual(geometry[4].points[0].latitude, -4.0) + } + + func testSelfOverlappingLineString_IsInvalid() { + let simpleViolations = selfOverlappingLineString.simpleViolations(tolerance: 0) + XCTAssertEqual(simpleViolations.count, 1) + let geometry = simpleViolations[0].problems + XCTAssertEqual(geometry.count, 15) + } + + func testObjectBoundingBox() { XCTAssertEqual(lineString.objectBoundingBox, lineString.boundingBox) } diff --git a/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/MultiLineStringTests.swift b/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/MultiLineStringTests.swift index b47ddb3..dab331c 100644 --- a/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/MultiLineStringTests.swift +++ b/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/MultiLineStringTests.swift @@ -4,15 +4,33 @@ import XCTest class MultiLineStringTests: XCTestCase { var lineStrings: [LineString]! + var selfIntersectingLineStrings: [LineString]! + var selfCrossingLineStrings: [LineString]! + var sharingStartAndEndLineStrings: [LineString]! + var doubleNLineStrings: [LineString]! + var multiLineString: MultiLineString! + var selfIntersectingMultiLineString: MultiLineString! + var selfCrossingMultiLineString: MultiLineString! + var sharingStartAndEndMultiLineString: MultiLineString! + var doubleNMultiLineString: MultiLineString! + var distancePoint: SimplePoint! override func setUp() { super.setUp() lineStrings = MockData.lineStrings + selfIntersectingLineStrings = MockData.selfIntersectingLineStrings + selfCrossingLineStrings = MockData.selfCrossingLineStrings + sharingStartAndEndLineStrings = MockData.sharingStartAndEndLineStrings + doubleNLineStrings = MockData.doubleNLineStrings multiLineString = GeoTestHelper.multiLineString(lineStrings) + selfIntersectingMultiLineString = GeoTestHelper.multiLineString(selfIntersectingLineStrings) + selfCrossingMultiLineString = GeoTestHelper.multiLineString(selfCrossingLineStrings) + sharingStartAndEndMultiLineString = GeoTestHelper.multiLineString(sharingStartAndEndLineStrings) + doubleNMultiLineString = GeoTestHelper.multiLineString(doubleNLineStrings) distancePoint = GeoTestHelper.simplePoint(10, 10, 10) } @@ -34,6 +52,94 @@ class MultiLineStringTests: XCTestCase { XCTAssertEqual(multiLineString.closedGeometries.count, 0) } + func testMultiLineString_StartAndEndTouching_IsValid() { + XCTAssertEqual(multiLineString.simpleViolations(tolerance: 0).count, 0) + } + + func testMultiLineString_SharingStartAndEnd_IsValid() { + XCTAssertEqual(sharingStartAndEndMultiLineString.simpleViolations(tolerance: 0).count, 0) + } + + func testSelfInterSectingMultiLineString_IsInvalid() { + let simpleViolations = selfIntersectingMultiLineString.simpleViolations(tolerance: 0) + XCTAssertEqual(simpleViolations.count, 2) + XCTAssertEqual(simpleViolations[0].reason, GeoJsonSimpleViolationReason.multiLineIntersection) + + let problem0 = simpleViolations[0].problems + XCTAssertEqual(problem0.count, 6) + if let point0 = problem0[0] as? Point, let point1 = problem0[1] as? Point, let point2 = problem0[3] as? Point, let point3 = problem0[4] as? Point { + XCTAssertEqual(point0.longitude, 21.0) + XCTAssertEqual(point0.latitude, 20.0) + XCTAssertEqual(point1.longitude, 20.0) + XCTAssertEqual(point1.latitude, 21.0) + XCTAssertEqual(point2.longitude, 19.0) + XCTAssertEqual(point2.latitude, 20.0) + XCTAssertEqual(point3.longitude, 23.0) + XCTAssertEqual(point3.latitude, 20.0) + } else { + XCTFail("Geometry type is wrong") + } + + let problem1 = simpleViolations[1].problems + XCTAssertEqual(problem1.count, 6) + if let point0 = problem1[0] as? Point, let point1 = problem1[1] as? Point, let point2 = problem1[3] as? Point, let point3 = problem1[4] as? Point { + XCTAssertEqual(point0.longitude, 20.0) + XCTAssertEqual(point0.latitude, 21.0) + XCTAssertEqual(point1.longitude, 20.0) + XCTAssertEqual(point1.latitude, 19.0) + XCTAssertEqual(point2.longitude, 19.0) + XCTAssertEqual(point2.latitude, 20.0) + XCTAssertEqual(point3.longitude, 23.0) + XCTAssertEqual(point3.latitude, 20.0) + } else { + XCTFail("Geometry type is wrong") + } + + } + + func testSelfCrossingMultiLineString_IsInvalid() { + let simpleViolations = selfCrossingMultiLineString.simpleViolations(tolerance: 0) + XCTAssertEqual(simpleViolations.count, 1) + XCTAssertEqual(simpleViolations[0].reason, GeoJsonSimpleViolationReason.multiLineIntersection) + + let problem0 = simpleViolations[0].problems + XCTAssertEqual(problem0.count, 6) + if let point0 = problem0[0] as? Point, let point1 = problem0[1] as? Point, let point2 = problem0[3] as? Point, let point3 = problem0[4] as? Point { + XCTAssertEqual(point0.longitude, 1.0) + XCTAssertEqual(point0.latitude, -1.0) + XCTAssertEqual(point1.longitude, 0.0) + XCTAssertEqual(point1.latitude, 1.0) + XCTAssertEqual(point2.longitude, 0.0) + XCTAssertEqual(point2.latitude, 0.0) + XCTAssertEqual(point3.longitude, 3.0) + XCTAssertEqual(point3.latitude, 0.0) + } else { + XCTFail("Geometry type is wrong") + } + } + + func testDoubleNMultiLineString_IsInvalid() { + let simpleViolations = doubleNMultiLineString.simpleViolations(tolerance: 0) + XCTAssertEqual(simpleViolations.count, 9) + XCTAssertEqual(simpleViolations[0].reason, GeoJsonSimpleViolationReason.multiLineIntersection) + + let problem0 = simpleViolations[0].problems + XCTAssertEqual(problem0.count, 6) + if let point0 = problem0[0] as? Point, let point1 = problem0[1] as? Point, let point2 = problem0[3] as? Point, let point3 = problem0[4] as? Point { + XCTAssertEqual(point0.longitude, 0.0) + XCTAssertEqual(point0.latitude, 0.0) + XCTAssertEqual(point1.longitude, 3.0) + XCTAssertEqual(point1.latitude, 0.0) + XCTAssertEqual(point2.longitude, 1.0) + XCTAssertEqual(point2.latitude, -1.0) + XCTAssertEqual(point3.longitude, 1.0) + XCTAssertEqual(point3.latitude, 2.0) + } else { + XCTFail("Geometry type is wrong") + } + } + + func testObjectBoundingBox() { XCTAssertEqual(multiLineString.objectBoundingBox, multiLineString.boundingBox) } diff --git a/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/MultiPointTests.swift b/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/MultiPointTests.swift index 03f2bcf..e8fa0e2 100644 --- a/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/MultiPointTests.swift +++ b/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/MultiPointTests.swift @@ -5,14 +5,19 @@ import XCTest class MultiPointTests: XCTestCase { var points: [Point]! var multiPoint: MultiPoint! + var pointsWithDuplicate: [Point]! + var multiPointWithDuplicate: MultiPoint! + var distancePoint: SimplePoint! override func setUp() { super.setUp() - points = [GeoTestHelper.point(1, 2, 3), GeoTestHelper.point(2, 2, 4), GeoTestHelper.point(2, 3, 5)] + points = [GeoTestHelper.point(1, 2, 3), GeoTestHelper.point(2, 2, 4), GeoTestHelper.point(2, 3, 3)] + pointsWithDuplicate = [GeoTestHelper.point(1, 2, 3), GeoTestHelper.point(1, 3, 3), GeoTestHelper.point(1, 2, 3)] multiPoint = GeoTestHelper.multiPoint(points) + multiPointWithDuplicate = GeoTestHelper.multiPoint(pointsWithDuplicate) distancePoint = GeoTestHelper.simplePoint(10, 10, 10) } @@ -34,6 +39,16 @@ class MultiPointTests: XCTestCase { XCTAssertEqual(multiPoint.closedGeometries.count, 0) } + func testMultiPoints_AllUnique_IsValid() { + let simpleViolations = multiPoint.simpleViolations(tolerance: 0) + XCTAssertEqual(simpleViolations.count, 0) + } + + func testMultiPoints_WithDuplicate_IsInvalid() { + let simpleViolations = multiPointWithDuplicate.simpleViolations(tolerance: 0) + XCTAssertEqual(simpleViolations.count, 1) + } + func testObjectBoundingBox() { XCTAssertEqual(multiPoint.objectBoundingBox, multiPoint.boundingBox) } @@ -141,7 +156,7 @@ class MultiPointTests: XCTestCase { } func testDistance_ChooseCorrectPointForDistance() { -// points = [GeoTestHelper.point(1, 2, 3), GeoTestHelper.point(2, 2, 4), GeoTestHelper.point(2, 3, 5)] + // points = [GeoTestHelper.point(1, 2, 3), GeoTestHelper.point(2, 2, 4), GeoTestHelper.point(2, 3, 5)] let distance1 = multiPoint.distance(to: GeoTestHelper.simplePoint(1, 2.1, 0), tolerance: 11000) let distance2 = multiPoint.distance(to: GeoTestHelper.simplePoint(2, 3.1, 0), tolerance: 11000) diff --git a/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/MultiPolygonTests.swift b/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/MultiPolygonTests.swift index 076a198..ae65e34 100644 --- a/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/MultiPolygonTests.swift +++ b/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/MultiPolygonTests.swift @@ -5,6 +5,10 @@ import XCTest class MultiPolygonTests: XCTestCase { var polygons: [Polygon]! var multiPolygon: MultiPolygon! + var touchingMultiPolygon: MultiPolygon! + var sharingEdgeMultiPolygons: MultiPolygon! + var containingMultiPolygons: MultiPolygon! + var distancePoint: SimplePoint! var point: GeoJson.Point! @@ -19,6 +23,12 @@ class MultiPolygonTests: XCTestCase { distancePoint = GeoTestHelper.simplePoint(10, 10, 10) point = GeoTestHelper.point(0, 0, 0) + + touchingMultiPolygon = GeoTestHelper.multiPolygon(MockData.touchingPolygons) + + sharingEdgeMultiPolygons = GeoTestHelper.multiPolygon(MockData.sharingEdgePolygons) + + containingMultiPolygons = GeoTestHelper.multiPolygon(MockData.containingPolygons) } // GeoJsonObject Tests @@ -38,6 +48,48 @@ class MultiPolygonTests: XCTestCase { XCTAssertEqual(multiPolygon.closedGeometries.count, 1) } + func testTouchingMultiPolygonsIsValid() { + XCTAssertEqual(touchingMultiPolygon.simpleViolations(tolerance: 0).count, 0) + } + + func testSharingEdgeMultiPolygonsIsInvalid() { + let simpleViolations = sharingEdgeMultiPolygons.simpleViolations(tolerance: 0) + XCTAssertEqual(simpleViolations.count, 1) + XCTAssertEqual(simpleViolations[0].reason, GeoJsonSimpleViolationReason.multiPolygonIntersection) + + if let point1 = simpleViolations[0].problems[0] as? Point, let point2 = simpleViolations[0].problems[1] as? Point, let point3 = simpleViolations[0].problems[3] as? Point, let point4 = simpleViolations[0].problems[4] as? Point { + XCTAssertEqual(point1.longitude, 21.0) + XCTAssertEqual(point1.latitude, 21.0) + XCTAssertEqual(point2.longitude, 21.0) + XCTAssertEqual(point2.latitude, 20.0) + XCTAssertEqual(point3.longitude, 21.0) + XCTAssertEqual(point3.latitude, 21.0) + XCTAssertEqual(point4.longitude, 21.0) + XCTAssertEqual(point4.latitude, 20.0) + } else { + XCTFail("Geometry not valid") + } + } + + func testContainedMultiPolygonsIsInvalid() { + let simpleViolations = containingMultiPolygons.simpleViolations(tolerance: 0) + XCTAssertEqual(simpleViolations.count, 1) + XCTAssertEqual(simpleViolations[0].reason, GeoJsonSimpleViolationReason.multiPolygonContained) + + if let point1 = simpleViolations[0].problems[0] as? Point, let point2 = simpleViolations[0].problems[2] as? Point, let point3 = simpleViolations[0].problems[4] as? Point, let point4 = simpleViolations[0].problems[6] as? Point { + XCTAssertEqual(point1.longitude, 21.0) + XCTAssertEqual(point1.latitude, 21.0) + XCTAssertEqual(point2.longitude, 22.0) + XCTAssertEqual(point2.latitude, 21.0) + XCTAssertEqual(point3.longitude, 22.0) + XCTAssertEqual(point3.latitude, 22.0) + XCTAssertEqual(point4.longitude, 21.0) + XCTAssertEqual(point4.latitude, 22.0) + } else { + XCTFail("Geometry not valid") + } + } + func testObjectBoundingBox() { XCTAssertEqual(multiPolygon.objectBoundingBox, multiPolygon.boundingBox) } @@ -111,7 +163,7 @@ class MultiPolygonTests: XCTestCase { } func testArea() { - XCTAssertEqual(multiPolygon.area, 37490216.3337727, accuracy: 10) + XCTAssertEqual(multiPolygon.area, 98455858999.07483, accuracy: 10) } // SOMEDAY: Test Edge Distance diff --git a/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/PolygonTests.swift b/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/PolygonTests.swift index 441212a..e122cdb 100644 --- a/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/PolygonTests.swift +++ b/Tests/GeospatialSwiftTests/Test/API/GeoJson/Object/PolygonTests.swift @@ -9,6 +9,23 @@ class PolygonTests: XCTestCase { var linearRings: [LineString]! var polygon: Polygon! var polygonDistance: Polygon! + var sharingCornerLinearRings: [LineString]! + var sharingCornerPolygon: Polygon! + var sharingCornerAndOverlappingLinearRings: [LineString]! + var sharingCornerAndOverlappingPolygon: Polygon! + var ringIntersectingLinearRings: [LineString]! + var ringIntersectingPolygon: Polygon! + var holeOutsideLinearRings: [LineString]! + var holeOutsidePolygon: Polygon! + var holeContainedLinearRings: [LineString]! + var holeContainedPolygon: Polygon! + var mShapeMainRingLinearRings: [LineString]! + var mShapeMainRingPolygon: Polygon! + var doubleMNegativeRingsLinearRings: [LineString]! + var doubleMNegativeRingsPolygon: Polygon! + var diamondNegativeRingLinearRings: [LineString]! + var diamondNegativeRingPolygon: Polygon! + var distancePoint: SimplePoint! var point: GeoJson.Point! @@ -40,6 +57,30 @@ class PolygonTests: XCTestCase { polygon = GeoTestHelper.polygon(mainRing, negativeRings) + sharingCornerLinearRings = MockData.sharingCornerLinearRings + sharingCornerPolygon = GeoTestHelper.polygon(sharingCornerLinearRings.first!, Array(sharingCornerLinearRings.dropFirst())) + + sharingCornerAndOverlappingLinearRings = MockData.sharingCornerAndOverlappingRings + sharingCornerAndOverlappingPolygon = GeoTestHelper.polygon(sharingCornerAndOverlappingLinearRings.first!, Array(sharingCornerAndOverlappingLinearRings.dropFirst())) + + ringIntersectingLinearRings = MockData.ringIntersectingLinearRings + ringIntersectingPolygon = GeoTestHelper.polygon(ringIntersectingLinearRings.first!, Array(ringIntersectingLinearRings.dropFirst())) + + holeOutsideLinearRings = MockData.holeOutsideLinearRings + holeOutsidePolygon = GeoTestHelper.polygon(holeOutsideLinearRings.first!, Array(holeOutsideLinearRings.dropFirst())) + + holeContainedLinearRings = MockData.holeContainedLinearRings + holeContainedPolygon = GeoTestHelper.polygon(holeContainedLinearRings.first!, Array(holeContainedLinearRings.dropFirst())) + + mShapeMainRingLinearRings = MockData.mShapeMainRingLinearRings + mShapeMainRingPolygon = GeoTestHelper.polygon(mShapeMainRingLinearRings.first!, Array(mShapeMainRingLinearRings.dropFirst())) + + doubleMNegativeRingsLinearRings = MockData.doubleMNegativeRingsLinearRings + doubleMNegativeRingsPolygon = GeoTestHelper.polygon(doubleMNegativeRingsLinearRings.first!, Array(doubleMNegativeRingsLinearRings.dropFirst())) + + diamondNegativeRingLinearRings = MockData.diamondNegativeRingLinearRings + diamondNegativeRingPolygon = GeoTestHelper.polygon(diamondNegativeRingLinearRings.first!, Array(diamondNegativeRingLinearRings.dropFirst())) + distancePoint = GeoTestHelper.simplePoint(10, 10, 10) point = GeoTestHelper.point(0, 0, 0) @@ -68,13 +109,249 @@ class PolygonTests: XCTestCase { XCTAssertEqual(polygon.closedGeometries.count, 1) } + func testPolygonIsValid() { + XCTAssertEqual(polygon.simpleViolations(tolerance: 0).count, 0) + } + + func testPolygonSharingCorner_IsValid() { + XCTAssertEqual(sharingCornerPolygon.simpleViolations(tolerance: 0).count, 0) + } + + func testPolygonSharingCornerAndOverlappingEdge_IsInvalid() { + let simpleViolations = sharingCornerAndOverlappingPolygon.simpleViolations(tolerance: 0) + XCTAssertEqual(simpleViolations.count, 2) + + if let point1 = simpleViolations[0].problems[0] as? Point, let point2 = simpleViolations[0].problems[1] as? Point, let point3 = simpleViolations[0].problems[3] as? Point, let point4 = simpleViolations[0].problems[4] as? Point { + XCTAssertEqual(point1.longitude, 20.0) + XCTAssertEqual(point1.latitude, 0.0) + XCTAssertEqual(point2.longitude, 23.0) + XCTAssertEqual(point2.latitude, 0.0) + XCTAssertEqual(point3.longitude, 20.0) + XCTAssertEqual(point3.latitude, 0.0) + XCTAssertEqual(point4.longitude, 21.0) + XCTAssertEqual(point4.latitude, 0.0) + } else { + XCTFail("Geometry not valid") + } + + if let point1 = simpleViolations[1].problems[0] as? Point, let point2 = simpleViolations[1].problems[1] as? Point, let point3 = simpleViolations[1].problems[3] as? Point, let point4 = simpleViolations[1].problems[4] as? Point { + XCTAssertEqual(point1.longitude, 20.0) + XCTAssertEqual(point1.latitude, 3.0) + XCTAssertEqual(point2.longitude, 20.0) + XCTAssertEqual(point2.latitude, 0.0) + XCTAssertEqual(point3.longitude, 20.0) + XCTAssertEqual(point3.latitude, 1.0) + XCTAssertEqual(point4.longitude, 20.0) + XCTAssertEqual(point4.latitude, 0.0) + } else { + XCTFail("Geometry not valid") + } + } + + func testPolygonringIntersecting_IsInvalid() { + let simpleViolations = ringIntersectingPolygon.simpleViolations(tolerance: 0) + XCTAssertEqual(simpleViolations.count, 4) + + XCTAssertEqual(simpleViolations[0].reason, GeoJsonSimpleViolationReason.polygonHoleOutside) + if let point1 = simpleViolations[0].problems[0] as? Point { + XCTAssertEqual(point1.longitude, 24.0) + XCTAssertEqual(point1.latitude, 21.0) + } else { + XCTFail("Geometry not valid") + } + + XCTAssertEqual(simpleViolations[1].reason, GeoJsonSimpleViolationReason.polygonHoleOutside) + if let point1 = simpleViolations[1].problems[0] as? Point { + XCTAssertEqual(point1.longitude, 24.0) + XCTAssertEqual(point1.latitude, 21.0) + } else { + XCTFail("Geometry not valid") + } + + XCTAssertEqual(simpleViolations[2].reason, GeoJsonSimpleViolationReason.polygonHoleOutside) + if let point1 = simpleViolations[2].problems[0] as? Point { + XCTAssertEqual(point1.longitude, 24.0) + XCTAssertEqual(point1.latitude, 22.0) + + } else { + XCTFail("Geometry not valid") + } + + XCTAssertEqual(simpleViolations[3].reason, GeoJsonSimpleViolationReason.polygonHoleOutside) + if let point1 = simpleViolations[3].problems[0] as? Point { + XCTAssertEqual(point1.longitude, 24.0) + XCTAssertEqual(point1.latitude, 22.0) + + } else { + XCTFail("Geometry not valid") + } + } + + func testPolygonWithHoleOutside_IsInvalid() { + let simpleViolations = holeOutsidePolygon.simpleViolations(tolerance: 0) + XCTAssertEqual(simpleViolations.count, 8) + XCTAssertEqual(simpleViolations[0].reason, GeoJsonSimpleViolationReason.polygonHoleOutside) + XCTAssertEqual(simpleViolations[1].reason, GeoJsonSimpleViolationReason.polygonHoleOutside) + XCTAssertEqual(simpleViolations[2].reason, GeoJsonSimpleViolationReason.polygonHoleOutside) + XCTAssertEqual(simpleViolations[3].reason, GeoJsonSimpleViolationReason.polygonHoleOutside) + + if let point1 = simpleViolations[0].problems[0] as? Point { + XCTAssertEqual(point1.longitude, 25.0) + XCTAssertEqual(point1.latitude, 25.0) + } else { + XCTFail("Geometry not valid") + } + + if let point1 = simpleViolations[1].problems[0] as? Point { + XCTAssertEqual(point1.longitude, 26.0) + XCTAssertEqual(point1.latitude, 25.0) + } else { + XCTFail("Geometry not valid") + } + + if let point1 = simpleViolations[2].problems[0] as? Point { + XCTAssertEqual(point1.longitude, 26.0) + XCTAssertEqual(point1.latitude, 25.0) + } else { + XCTFail("Geometry not valid") + } + + if let point1 = simpleViolations[3].problems[0] as? Point { + XCTAssertEqual(point1.longitude, 26.0) + XCTAssertEqual(point1.latitude, 26.0) + } else { + XCTFail("Geometry not valid") + } + + if let point1 = simpleViolations[4].problems[0] as? Point { + XCTAssertEqual(point1.longitude, 26.0) + XCTAssertEqual(point1.latitude, 26.0) + } else { + XCTFail("Geometry not valid") + } + + if let point1 = simpleViolations[5].problems[0] as? Point { + XCTAssertEqual(point1.longitude, 25.0) + XCTAssertEqual(point1.latitude, 26.0) + } else { + XCTFail("Geometry not valid") + } + + if let point1 = simpleViolations[6].problems[0] as? Point { + XCTAssertEqual(point1.longitude, 25.0) + XCTAssertEqual(point1.latitude, 26.0) + } else { + XCTFail("Geometry not valid") + } + + if let point1 = simpleViolations[7].problems[0] as? Point { + XCTAssertEqual(point1.longitude, 25.0) + XCTAssertEqual(point1.latitude, 25.0) + } else { + XCTFail("Geometry not valid") + } + } + + func testPolygonWithHoleContainingHole_IsInvalid() { + let simpleViolations = holeContainedPolygon.simpleViolations(tolerance: 0) + XCTAssertEqual(simpleViolations.count, 1) + XCTAssertEqual(simpleViolations[0].reason, GeoJsonSimpleViolationReason.polygonNegativeRingContained) + + if let point1 = simpleViolations[0].problems[0] as? Point, let point2 = simpleViolations[0].problems[2] as? Point, let point3 = simpleViolations[0].problems[4] as? Point, let point4 = simpleViolations[0].problems[6] as? Point { + XCTAssertEqual(point1.longitude, 22.0) + XCTAssertEqual(point1.latitude, 22.0) + XCTAssertEqual(point2.longitude, 23.0) + XCTAssertEqual(point2.latitude, 22.0) + XCTAssertEqual(point3.longitude, 23.0) + XCTAssertEqual(point3.latitude, 23.0) + XCTAssertEqual(point4.longitude, 22.0) + XCTAssertEqual(point4.latitude, 23.0) + } else { + XCTFail("Geometry not valid") + } + } + + func testMShapePolygon_WithNegativeRingEdgeOutside_IsInvalid() { + let simpleViolations = mShapeMainRingPolygon.simpleViolations(tolerance: 0) + XCTAssertEqual(simpleViolations.count, 2) + + XCTAssertEqual(simpleViolations[0].reason, GeoJsonSimpleViolationReason.polygonSelfIntersection) + if let point1 = simpleViolations[0].problems[0] as? Point, let point2 = simpleViolations[0].problems[1] as? Point, let point3 = simpleViolations[0].problems[3] as? Point, let point4 = simpleViolations[0].problems[4] as? Point { + XCTAssertEqual(point1.longitude, 24.0) + XCTAssertEqual(point1.latitude, 26.0) + XCTAssertEqual(point2.longitude, 22.0) + XCTAssertEqual(point2.latitude, 22.0) + XCTAssertEqual(point3.longitude, 23.0) + XCTAssertEqual(point3.latitude, 23.0) + XCTAssertEqual(point4.longitude, 21.0) + XCTAssertEqual(point4.latitude, 23.0) + } else { + XCTFail("Geometry not valid") + } + + XCTAssertEqual(simpleViolations[1].reason, GeoJsonSimpleViolationReason.polygonSelfIntersection) + if let point1 = simpleViolations[1].problems[0] as? Point, let point2 = simpleViolations[1].problems[1] as? Point, let point3 = simpleViolations[1].problems[3] as? Point, let point4 = simpleViolations[1].problems[4] as? Point { + XCTAssertEqual(point1.longitude, 22.0) + XCTAssertEqual(point1.latitude, 22.0) + XCTAssertEqual(point2.longitude, 20.0) + XCTAssertEqual(point2.latitude, 26.0) + XCTAssertEqual(point3.longitude, 23.0) + XCTAssertEqual(point3.latitude, 23.0) + XCTAssertEqual(point4.longitude, 21.0) + XCTAssertEqual(point4.latitude, 23.0) + } else { + XCTFail("Geometry not valid") + } + } + + func testPolygon_WithDoubleMNegativeRing_IsInvalid() { + let simpleViolations = doubleMNegativeRingsPolygon.simpleViolations(tolerance: 0) + XCTAssertEqual(simpleViolations.count, 8) + XCTAssertEqual(simpleViolations[0].reason, GeoJsonSimpleViolationReason.polygonMultipleVertexIntersection) + + if let point = simpleViolations[0].problems[3] as? Point { + XCTAssertEqual(point.longitude, 23) + XCTAssertEqual(point.latitude, 21) + } else { + XCTFail("Geometry not valid") + } + + if let point = simpleViolations[4].problems[3] as? Point { + XCTAssertEqual(point.longitude, 23) + XCTAssertEqual(point.latitude, 23) + } else { + XCTFail("Geometry not valid") + } + } + + func testPolygon_WithDiamondNegativeRing_IsInvalid() { + let simpleViolations = diamondNegativeRingPolygon.simpleViolations(tolerance: 0) + XCTAssertEqual(simpleViolations.count, 8) + XCTAssertEqual(simpleViolations[0].reason, GeoJsonSimpleViolationReason.polygonMultipleVertexIntersection) + + if let point = simpleViolations[0].problems[3] as? Point { + XCTAssertEqual(point.longitude, 20) + XCTAssertEqual(point.latitude, 20) + } else { + XCTFail("Geometry not valid") + } + + if let point = simpleViolations[4].problems[3] as? Point { + XCTAssertEqual(point.longitude, 24) + XCTAssertEqual(point.latitude, 24) + } else { + XCTFail("Geometry not valid") + } + } + + func testObjectBoundingBox() { XCTAssertEqual(polygon.objectBoundingBox, polygon.boundingBox) } func testGeoJson() { XCTAssertEqual(polygon.geoJson["type"] as? String, "Polygon") - XCTAssertEqual(polygon.geoJson["coordinates"] as? [[[Double]]], MockData.linearRingsCoordinatesJson) + XCTAssertEqual(polygon.geoJson["coordinates"] as? [[[Double]]], MockData.polygonsCoordinatesJson[0]) } func testObjectDistance() { @@ -367,7 +644,7 @@ class PolygonTests: XCTestCase { // SOMEDAY: Verify func testArea() { - XCTAssertEqual(polygon.area, 11301732.6333942, accuracy: 10) + XCTAssertEqual(polygon.area, 98429670515.37445, accuracy: 10) } // Polygon Tests