├── .gitattributes ├── .gitignore ├── GeoJSON.Tests ├── 3dlinestringbbox.json ├── GeoJSON.Tests.csproj ├── GeoJsonUnitTests.cs ├── WkbUnitTests.cs ├── feature.json ├── feature_id_num_as_string.json ├── feature_id_number.json ├── feature_id_string.json ├── feature_null_geometry.json ├── feature_out_of_range.json ├── featurebbox.json ├── featurecollection.json ├── featurecollectionbbox.json ├── geometrycollection.json ├── linestring.json ├── multilinestring.json ├── multipoint.json ├── multipolygon.json ├── point.json ├── polygon.json ├── polygonwithhole.json └── position.json ├── GeoJSON.sln ├── GeoJSON ├── Feature.cs ├── FeatureCollection.cs ├── FeatureId.cs ├── GeoJSON.csproj ├── GeoJSON.snk ├── GeoJson.cs ├── GeoJsonConfig.cs ├── GeoJsonType.cs ├── Geometry.cs ├── GeometryCollection.cs ├── GeometryType.cs ├── Hashing.cs ├── LineString.cs ├── LinearRing.cs ├── MultiLineString.cs ├── MultiPoint.cs ├── MultiPolygon.cs ├── Point.cs ├── Polygon.cs ├── Position.cs ├── Serde │ ├── FeatureIdConverter.cs │ ├── GeoJsonConverter.cs │ ├── GeometryConverter.cs │ ├── InheritanceBlockerConverter.cs │ ├── MultiLineStringConverter.cs │ ├── MultiPolygonConverter.cs │ ├── PolygonConverter.cs │ └── PositionConverter.cs └── Wkb │ ├── EndianAwareBinaryReader.cs │ ├── EndianAwareBinaryWriter.cs │ ├── Endianness.cs │ ├── WkbConverter.cs │ └── WkbType.cs ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /GeoJSON.Tests/3dlinestringbbox.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "LineString", 3 | "bbox": [ 100.0, 0.0, 0, 105.0, 1.0, 10.0 ], 4 | "coordinates": [ 5 | [ 102.0, 0.0, 1.0 ], 6 | [ 103.0, 1.0, 2.0 ], 7 | [ 104.0, 0.0, 3.0 ], 8 | [ 105.0, 1.0, 4.0 ] 9 | ] 10 | } -------------------------------------------------------------------------------- /GeoJSON.Tests/GeoJSON.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | true 6 | 7 | 8 | 9 | DEBUG;TRACE 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | PreserveNewest 26 | 27 | 28 | PreserveNewest 29 | 30 | 31 | PreserveNewest 32 | 33 | 34 | PreserveNewest 35 | 36 | 37 | PreserveNewest 38 | 39 | 40 | PreserveNewest 41 | 42 | 43 | PreserveNewest 44 | 45 | 46 | PreserveNewest 47 | 48 | 49 | PreserveNewest 50 | 51 | 52 | PreserveNewest 53 | 54 | 55 | PreserveNewest 56 | 57 | 58 | PreserveNewest 59 | 60 | 61 | PreserveNewest 62 | 63 | 64 | PreserveNewest 65 | 66 | 67 | PreserveNewest 68 | 69 | 70 | PreserveNewest 71 | 72 | 73 | PreserveNewest 74 | 75 | 76 | PreserveNewest 77 | 78 | 79 | PreserveNewest 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /GeoJSON.Tests/GeoJsonUnitTests.cs: -------------------------------------------------------------------------------- 1 | using BAMCIS.GeoJSON; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.IO; 5 | using Xunit; 6 | 7 | namespace GeoJSON.Tests 8 | { 9 | public class GeoJsonUnitTests 10 | { 11 | [Fact] 12 | public void MultiLineStringTest() 13 | { 14 | // ARRANGE 15 | string content = File.ReadAllText("multilinestring.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 16 | 17 | // ACT 18 | MultiLineString geo = MultiLineString.FromJson(content); 19 | 20 | string content2 = geo.ToJson(); 21 | 22 | MultiLineString geo2 = MultiLineString.FromJson(content2); 23 | 24 | // ASSERT 25 | Assert.True(geo.Equals(geo2)); 26 | } 27 | 28 | [Fact] 29 | public void LineStringTest() 30 | { 31 | // ARRANGE 32 | string content = File.ReadAllText("linestring.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 33 | 34 | // ACT 35 | LineString geo = JsonConvert.DeserializeObject(content); 36 | string content2 = JsonConvert.SerializeObject(geo); 37 | LineString geo2 = JsonConvert.DeserializeObject(content2); 38 | 39 | // ASSERT 40 | Assert.True(geo.Equals(geo2)); 41 | } 42 | 43 | [Fact] 44 | public void MultiPolygonTest() 45 | { 46 | // ARRANGE 47 | string content = File.ReadAllText("multipolygon.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 48 | 49 | // ACT 50 | MultiPolygon geo = JsonConvert.DeserializeObject(content); 51 | string content2 = JsonConvert.SerializeObject(geo); 52 | 53 | // ASSERT 54 | Assert.Equal(content, content2, true, true, true); 55 | } 56 | 57 | [Fact] 58 | public void FeatureTest() 59 | { 60 | // ARRANGE 61 | string content = File.ReadAllText("feature.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 62 | 63 | // ACT 64 | Feature geo = JsonConvert.DeserializeObject(content); 65 | string content2 = JsonConvert.SerializeObject(geo); 66 | Feature geo2 = JsonConvert.DeserializeObject(content2); 67 | 68 | // ASSERT 69 | Assert.True(geo.Equals(geo2)); 70 | } 71 | 72 | [Fact] 73 | public void FeatureIdNotEqualTest() 74 | { 75 | // ARRANGE 76 | string content1 = File.ReadAllText("feature_id_number.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 77 | string content2 = File.ReadAllText("feature_id_num_as_string.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 78 | 79 | // ACT 80 | Feature geoWithNumId = JsonConvert.DeserializeObject(content1); 81 | Feature geoWithStringId = JsonConvert.DeserializeObject(content2); 82 | 83 | // ASSERT 84 | Assert.Equal(geoWithNumId.Id.Value, geoWithStringId.Id.Value); 85 | Assert.NotEqual(geoWithNumId.Id, geoWithStringId.Id); 86 | } 87 | 88 | [Fact] 89 | public void FeatureIdEqualTest() 90 | { 91 | // ARRANGE 92 | string content = File.ReadAllText("feature_id_number.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 93 | 94 | // ACT 95 | Feature geo1 = JsonConvert.DeserializeObject(content); 96 | Feature geo2 = JsonConvert.DeserializeObject(content); 97 | 98 | // ASSERT 99 | Assert.Equal(geo1.Id.Value, geo2.Id.Value); 100 | Assert.Equal(geo1.Id, geo2.Id); 101 | } 102 | 103 | [Fact] 104 | public void FeatureTestStringId() 105 | { 106 | // ARRANGE 107 | string content = File.ReadAllText("feature_id_string.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 108 | 109 | // ACT 110 | Feature geo = JsonConvert.DeserializeObject(content); 111 | string content2 = JsonConvert.SerializeObject(geo); 112 | Feature geo2 = JsonConvert.DeserializeObject(content2); 113 | 114 | // ASSERT 115 | Assert.True(geo.Equals(geo2)); 116 | } 117 | 118 | [Fact] 119 | public void FeatureTestNumberId() 120 | { 121 | // ARRANGE 122 | string content = File.ReadAllText("feature_id_number.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 123 | 124 | // ACT 125 | Feature geo = JsonConvert.DeserializeObject(content); 126 | string content2 = JsonConvert.SerializeObject(geo); 127 | Feature geo2 = JsonConvert.DeserializeObject(content2); 128 | 129 | // ASSERT 130 | Assert.True(geo.Equals(geo2)); 131 | } 132 | 133 | [Fact] 134 | public void FeatureOutOfRangeTest() 135 | { 136 | // ARRANGE 137 | GeoJsonConfig.EnforcePositionValidation(); 138 | string content = File.ReadAllText("feature_out_of_range.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 139 | 140 | // ACT & ASSERT 141 | Assert.Throws(() => JsonConvert.DeserializeObject(content)); 142 | } 143 | 144 | [Fact] 145 | public void FeatureOutOfRangeTestIgnoreValidation() 146 | { 147 | // ARRANGE 148 | GeoJsonConfig.IgnorePositionValidation(); 149 | string content = File.ReadAllText("feature_out_of_range.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 150 | 151 | // ACT 152 | Feature geo = JsonConvert.DeserializeObject(content); 153 | string content2 = JsonConvert.SerializeObject(geo); 154 | Feature geo2 = JsonConvert.DeserializeObject(content2); 155 | 156 | // ASSERT 157 | Assert.True(geo.Equals(geo2)); 158 | } 159 | 160 | [Fact] 161 | public void FeatureTestNullGeometry() 162 | { 163 | // ARRANGE 164 | string content = File.ReadAllText("feature_null_geometry.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 165 | 166 | // ACT 167 | Feature geo = JsonConvert.DeserializeObject(content); 168 | string content2 = JsonConvert.SerializeObject(geo); 169 | Feature geo2 = JsonConvert.DeserializeObject(content2); 170 | 171 | // ASSERT 172 | Assert.True(geo.Equals(geo2)); 173 | } 174 | 175 | [Fact] 176 | public void FeatureCollectionTest() 177 | { 178 | // ARRANGE 179 | string content = File.ReadAllText("featurecollection.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 180 | 181 | // ACT 182 | FeatureCollection geo = JsonConvert.DeserializeObject(content); 183 | string content2 = JsonConvert.SerializeObject(geo); 184 | FeatureCollection geo2 = JsonConvert.DeserializeObject(content2); 185 | 186 | // ASSERT 187 | Assert.True(geo.Equals(geo2)); 188 | } 189 | 190 | [Fact] 191 | public void PolygonTest() 192 | { 193 | // ARRANGE 194 | string content = File.ReadAllText("polygon.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 195 | 196 | // ACT 197 | Polygon geo = JsonConvert.DeserializeObject(content); 198 | string content2 = JsonConvert.SerializeObject(geo); 199 | Polygon geo2 = JsonConvert.DeserializeObject(content2); 200 | 201 | // ASSERT 202 | Assert.True(geo.Equals(geo2)); 203 | } 204 | 205 | [Fact] 206 | public void PolygonWithHoleTest() 207 | { 208 | // ARRANGE 209 | string content = File.ReadAllText("polygonwithhole.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 210 | 211 | // ACT 212 | Polygon geo = JsonConvert.DeserializeObject(content); 213 | string content2 = JsonConvert.SerializeObject(geo); 214 | Polygon geo2 = JsonConvert.DeserializeObject(content2); 215 | 216 | // ASSERT 217 | Assert.True(geo.Equals(geo2)); 218 | } 219 | 220 | [Fact] 221 | public void PolygonRemoveInnerRingsTestWithHole() 222 | { 223 | // ARRANGE 224 | string content = File.ReadAllText("polygonwithhole.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 225 | 226 | // ACT 227 | Polygon geo = JsonConvert.DeserializeObject(content); 228 | bool result = geo.RemoveInteriorRings(); 229 | 230 | 231 | // ASSERT 232 | Assert.True(result); 233 | Assert.Single(geo.Coordinates); 234 | } 235 | 236 | [Fact] 237 | public void PolygonRemoveInnerRingsTestWithoutHole() 238 | { 239 | // ARRANGE 240 | string content = File.ReadAllText("polygon.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 241 | 242 | // ACT 243 | Polygon geo = JsonConvert.DeserializeObject(content); 244 | bool result = geo.RemoveInteriorRings(); 245 | 246 | 247 | // ASSERT 248 | Assert.False(result); 249 | Assert.Single(geo.Coordinates); 250 | } 251 | 252 | [Fact] 253 | public void PointTest() 254 | { 255 | // ARRANGE 256 | string content = File.ReadAllText("point.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 257 | 258 | // ACT 259 | Point geo = JsonConvert.DeserializeObject(content); 260 | string content2 = JsonConvert.SerializeObject(geo); 261 | Point geo2 = JsonConvert.DeserializeObject(content2); 262 | 263 | // ASSERT 264 | Assert.True(geo.Equals(geo2)); 265 | } 266 | 267 | [Fact] 268 | public void MultiPointTest() 269 | { 270 | // ARRANGE 271 | string content = File.ReadAllText("multipoint.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 272 | 273 | // ACT 274 | MultiPoint geo = JsonConvert.DeserializeObject(content); 275 | string content2 = JsonConvert.SerializeObject(geo); 276 | MultiPoint geo2 = JsonConvert.DeserializeObject(content2); 277 | 278 | // ASSERT 279 | Assert.True(geo.Equals(geo2)); 280 | } 281 | 282 | [Fact] 283 | public void GeometryCollectionTest() 284 | { 285 | // ARRANGE 286 | string content = File.ReadAllText("geometrycollection.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 287 | 288 | // ACT 289 | GeometryCollection geo = JsonConvert.DeserializeObject(content); 290 | string content2 = JsonConvert.SerializeObject(geo); 291 | GeometryCollection geo2 = JsonConvert.DeserializeObject(content2); 292 | 293 | // ASSERT 294 | Assert.True(geo.Equals(geo2)); 295 | } 296 | 297 | [Fact] 298 | public void PositionTest() 299 | { 300 | // ARRANGE 301 | string content = File.ReadAllText("position.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 302 | 303 | // ACT 304 | Position geo = JsonConvert.DeserializeObject(content); 305 | string content2 = JsonConvert.SerializeObject(geo); 306 | 307 | // ASSERT 308 | Assert.Equal(content, content2, true, true, true); 309 | } 310 | 311 | [Fact] 312 | public void GeoJsonFeatureTest() 313 | { 314 | // ARRANGE 315 | string content = File.ReadAllText("feature.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 316 | 317 | // ACT 318 | GeoJson geo = GeoJson.FromJson(content); 319 | string content2 = geo.ToJson(); 320 | GeoJson geo2 = GeoJson.FromJson(content2); 321 | 322 | // ASSERT 323 | Assert.True(geo.Equals(geo2)); 324 | } 325 | 326 | [Fact] 327 | public void GeoJsonFeatureTestWithBbox() 328 | { 329 | // ARRANGE 330 | string content = File.ReadAllText("featurebbox.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 331 | 332 | // ACT 333 | GeoJson geo = GeoJson.FromJson(content); 334 | string content2 = geo.ToJson(); 335 | GeoJson geo2 = GeoJson.FromJson(content2); 336 | 337 | // ASSERT 338 | Assert.True(geo.Equals(geo2)); 339 | } 340 | 341 | [Fact] 342 | public void GeoJson3DLineStringTestWithBbox() 343 | { 344 | // ARRANGE 345 | string content = File.ReadAllText("3dlinestringbbox.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 346 | 347 | // ACT 348 | GeoJson geo = GeoJson.FromJson(content); 349 | string content2 = geo.ToJson(); 350 | GeoJson geo2 = GeoJson.FromJson(content2); 351 | 352 | // ASSERT 353 | Assert.True(geo.Equals(geo2)); 354 | } 355 | 356 | [Fact] 357 | public void GeoJsonFeatureCollectionTestWithBbox() 358 | { 359 | // ARRANGE 360 | string content = File.ReadAllText("featurecollectionbbox.json").Replace("\r", "").Replace("\n", "").Replace("\t", "").Replace(" ", ""); 361 | 362 | // ACT 363 | GeoJson geo = GeoJson.FromJson(content); 364 | string content2 = geo.ToJson(); 365 | GeoJson geo2 = GeoJson.FromJson(content2); 366 | 367 | // ASSERT 368 | Assert.True(geo.Equals(geo2)); 369 | } 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /GeoJSON.Tests/WkbUnitTests.cs: -------------------------------------------------------------------------------- 1 | using BAMCIS.GeoJSON; 2 | using BAMCIS.GeoJSON.Wkb; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Globalization; 6 | using System.Linq; 7 | using System.Text; 8 | using Xunit; 9 | 10 | namespace GeoJSON.Tests 11 | { 12 | public class WkbUnitTests 13 | { 14 | #region WkbConverter Tests 15 | 16 | [Fact] 17 | public void WkbConverterTest_ToBinary_BigEndian() 18 | { 19 | // ARRANGE 20 | byte[] expectedBytes = HexStringToByteArray("000000000140000000000000004010000000000000"); 21 | Point point = new Point(new Position(2.0, 4.0)); 22 | 23 | // ACT 24 | byte[] bytes = WkbConverter.ToBinary(point, Endianness.BIG); 25 | 26 | // ASSERT 27 | Assert.Equal(expectedBytes, bytes); 28 | } 29 | 30 | [Fact] 31 | public void WkbConverterTest_FromBinary_BigEndian() 32 | { 33 | // ARRANGE 34 | byte[] bytes = HexStringToByteArray("000000000140000000000000004010000000000000"); 35 | 36 | // ACT 37 | Point point = WkbConverter.FromBinary(bytes); 38 | 39 | // ASSERT 40 | Assert.Equal(2.0, point.GetLongitude()); 41 | Assert.Equal(4.0, point.GetLatitude()); 42 | } 43 | 44 | #endregion 45 | 46 | #region Point Tests 47 | 48 | [Fact] 49 | public void PointTest_Conversion() 50 | { 51 | // ARRANGE 52 | Point point = new Point(new Position(10.0, 10.0)); 53 | 54 | // ACT 55 | byte[] bytes = point.ToWkb(); 56 | Geometry geo = Point.FromWkb(bytes); 57 | 58 | // ASSERT 59 | point = Assert.IsType(geo); 60 | } 61 | 62 | [Fact] 63 | public void PointTest_Conversion2() 64 | { 65 | // ARRANGE 66 | Point point = new Point(new Position(10.0, 10.0)); 67 | 68 | // ACT 69 | byte[] bytes = point.ToWkb(); 70 | point = Geometry.FromWkb(bytes); 71 | 72 | // ASSERT 73 | point = Assert.IsType(point); 74 | Assert.Equal(10.0, point.GetLongitude()); 75 | Assert.Equal(10.0, point.GetLatitude()); 76 | } 77 | 78 | [Fact] 79 | public void PointTest_FromBinary_BigEndian() 80 | { 81 | // ARRANGE 82 | 83 | // POINT(2.0 4.0) BIG ENDIAN 84 | byte[] bytes = HexStringToByteArray("000000000140000000000000004010000000000000"); 85 | 86 | // ACT 87 | Geometry geo = WkbConverter.FromBinary(bytes); 88 | 89 | // ASSERT 90 | Point point = Assert.IsType(geo); 91 | Assert.Equal(2.0, point.Coordinates.Longitude); 92 | Assert.Equal(4.0, point.Coordinates.Latitude); 93 | } 94 | 95 | [Fact] 96 | public void PointTest_ToBinary_BigEndian() 97 | { 98 | // ARRANGE 99 | 100 | // POINT(2.0 4.0) BIG ENDIAN 101 | byte[] bytes = HexStringToByteArray("000000000140000000000000004010000000000000"); 102 | 103 | // ACT 104 | Geometry geo = WkbConverter.FromBinary(bytes); 105 | byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); 106 | 107 | // ASSERT 108 | Assert.Equal(bytes, newBytes); 109 | } 110 | 111 | [Fact] 112 | public void PointTest_FromBinary_LittleEndian() 113 | { 114 | // ARRANGE 115 | 116 | // POINT(1.2345 2.3456) LITTLE ENDIAN 117 | byte[] bytes = HexStringToByteArray("01010000008D976E1283C0F33F16FBCBEEC9C30240"); 118 | 119 | // ACT 120 | Geometry geo = WkbConverter.FromBinary(bytes); 121 | 122 | // ASSERT 123 | Point point = Assert.IsType(geo); 124 | Assert.Equal(1.2345, point.Coordinates.Longitude); 125 | Assert.Equal(2.3456, point.Coordinates.Latitude); 126 | } 127 | 128 | [Fact] 129 | public void PointTest_ToBinary_LittleEndian() 130 | { 131 | // ARRANGE 132 | 133 | // POINT(1.2345 2.3456) LITTLE ENDIAN 134 | byte[] bytes = HexStringToByteArray("01010000008D976E1283C0F33F16FBCBEEC9C30240"); 135 | 136 | // ACT 137 | Geometry geo = WkbConverter.FromBinary(bytes); 138 | byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.LITTLE); 139 | 140 | // ASSERT 141 | Assert.Equal(bytes, newBytes); 142 | } 143 | 144 | #endregion 145 | 146 | #region LineString Tests 147 | 148 | [Fact] 149 | public void LineStringTest_FromBinary_BigEndian() 150 | { 151 | // ARRANGE 152 | 153 | // LINESTRING(30 10, 10 30, 40 40) 154 | byte[] bytes = HexStringToByteArray("000000000200000003403E00000000000040240000000000004024000000000000403E00000000000040440000000000004044000000000000"); 155 | 156 | // ACT 157 | Geometry geo = WkbConverter.FromBinary(bytes); 158 | 159 | // ASSERT 160 | LineString lineString = Assert.IsType(geo); 161 | Assert.Equal(3, lineString.Coordinates.Count()); 162 | Assert.Equal(new Position(30, 10), lineString.Coordinates.ElementAt(0)); 163 | Assert.Equal(new Position(10, 30), lineString.Coordinates.ElementAt(1)); 164 | Assert.Equal(new Position(40, 40), lineString.Coordinates.ElementAt(2)); 165 | } 166 | 167 | [Fact] 168 | public void LineStringTest_FromBinary_LittleEndian() 169 | { 170 | // ARRANGE 171 | 172 | // LINESTRING(30 10, 10 30, 40 40) 173 | byte[] bytes = HexStringToByteArray("0102000000030000000000000000003e40000000000000244000000000000024400000000000003e4000000000000044400000000000004440"); 174 | 175 | // ACT 176 | Geometry geo = WkbConverter.FromBinary(bytes); 177 | 178 | // ASSERT 179 | LineString lineString = Assert.IsType(geo); 180 | Assert.Equal(3, lineString.Coordinates.Count()); 181 | Assert.Equal(new Position(30, 10), lineString.Coordinates.ElementAt(0)); 182 | Assert.Equal(new Position(10, 30), lineString.Coordinates.ElementAt(1)); 183 | Assert.Equal(new Position(40, 40), lineString.Coordinates.ElementAt(2)); 184 | } 185 | 186 | [Fact] 187 | public void LineStringTestWithDoubles_FromBinary_BigEndian() 188 | { 189 | // ARRANGE 190 | 191 | // LINESTRING(30.1234 10.6, 10.77 30.85, 40.1 40.2, 21 07, 19 77) 192 | byte[] bytes = HexStringToByteArray("000000000200000005403E1F972474538F402533333333333340258A3D70A3D70A403ED9999999999A40440CCCCCCCCCCD404419999999999A4035000000000000401C00000000000040330000000000004053400000000000"); 193 | 194 | // ACT 195 | Geometry geo = WkbConverter.FromBinary(bytes); 196 | 197 | // ASSERT 198 | LineString lineString = Assert.IsType(geo); 199 | Assert.Equal(5, lineString.Coordinates.Count()); 200 | Assert.Equal(new Position(30.1234, 10.6), lineString.Coordinates.ElementAt(0)); 201 | Assert.Equal(new Position(10.77, 30.85), lineString.Coordinates.ElementAt(1)); 202 | Assert.Equal(new Position(19, 77), lineString.Coordinates.ElementAt(4)); 203 | } 204 | 205 | [Fact] 206 | public void LineStringTest_ToBinary_BigEndian() 207 | { 208 | // ARRANGE 209 | 210 | // LINESTRING(30 10, 10 30, 40 40) 211 | byte[] bytes = HexStringToByteArray("000000000200000003403E00000000000040240000000000004024000000000000403E00000000000040440000000000004044000000000000"); 212 | 213 | // ACT 214 | Geometry geo = WkbConverter.FromBinary(bytes); 215 | byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); 216 | 217 | // ASSERT 218 | Assert.Equal(bytes, newBytes); 219 | } 220 | 221 | [Fact] 222 | public void LineStringTest_ToBinary_LittleEndian() 223 | { 224 | // ARRANGE 225 | 226 | // LINESTRING(30 10, 10 30, 40 40) 227 | byte[] bytes = HexStringToByteArray("0102000000030000000000000000003e40000000000000244000000000000024400000000000003e4000000000000044400000000000004440"); 228 | 229 | // ACT 230 | Geometry geo = WkbConverter.FromBinary(bytes); 231 | byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.LITTLE); 232 | 233 | // ASSERT 234 | Assert.Equal(bytes, newBytes); 235 | } 236 | 237 | [Fact] 238 | public void LineStringTestWithDoubles_ToBinary_BigEndian() 239 | { 240 | // ARRANGE 241 | 242 | // LINESTRING(30.1234 10.6, 10.77 30.85, 40.1 40.2, 21 07, 19 77) 243 | byte[] bytes = HexStringToByteArray("000000000200000005403E1F972474538F402533333333333340258A3D70A3D70A403ED9999999999A40440CCCCCCCCCCD404419999999999A4035000000000000401C00000000000040330000000000004053400000000000"); 244 | 245 | // ACT 246 | Geometry geo = WkbConverter.FromBinary(bytes); 247 | byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); 248 | 249 | // ASSERT 250 | Assert.Equal(bytes, newBytes); 251 | } 252 | 253 | #endregion 254 | 255 | #region MultiLineString Tests 256 | 257 | [Fact] 258 | public void MultiLineStringTest_FromBinary_BigEndian() 259 | { 260 | // ARRANGE 261 | 262 | // MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10)) 263 | byte[] bytes = HexStringToByteArray("00000000050000000200000000020000000340240000000000004024000000000000403400000000000040340000000000004024000000000000404400000000000000000000020000000440440000000000004044000000000000403E000000000000403E00000000000040440000000000004034000000000000403E0000000000004024000000000000"); 264 | 265 | // ACT 266 | Geometry geo = WkbConverter.FromBinary(bytes); 267 | 268 | // ASSERT 269 | MultiLineString lineString = Assert.IsType(geo); 270 | Assert.Equal(2, lineString.Coordinates.Count()); 271 | LineString ls1 = Assert.IsType(lineString.Coordinates.ElementAt(1)); 272 | Assert.Equal(40, ls1.Coordinates.ElementAt(2).Longitude); 273 | Assert.Equal(20, ls1.Coordinates.ElementAt(2).Latitude); 274 | } 275 | 276 | [Fact] 277 | public void MultiLineStringTest_FromBinary_LittleEndian() 278 | { 279 | // ARRANGE 280 | 281 | // MULTILINESTRING((10 10,20 20,10 40),(40 40,30 30,40 20,30 10)) 282 | byte[] bytes = HexStringToByteArray("010500000002000000010200000003000000000000000000244000000000000024400000000000003440000000000000344000000000000024400000000000004440010200000004000000000000000000444000000000000044400000000000003e400000000000003e40000000000000444000000000000034400000000000003e400000000000002440"); 283 | 284 | // ACT 285 | Geometry geo = WkbConverter.FromBinary(bytes); 286 | 287 | // ASSERT 288 | MultiLineString lineString = Assert.IsType(geo); 289 | Assert.Equal(2, lineString.Coordinates.Count()); 290 | LineString ls1 = Assert.IsType(lineString.Coordinates.ElementAt(1)); 291 | Assert.Equal(40, ls1.Coordinates.ElementAt(2).Longitude); 292 | Assert.Equal(20, ls1.Coordinates.ElementAt(2).Latitude); 293 | } 294 | 295 | [Fact] 296 | public void MultiLineStringTest_ToBinary_BigEndian() 297 | { 298 | // ARRANGE 299 | 300 | // MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10)) 301 | byte[] bytes = HexStringToByteArray("00000000050000000200000000020000000340240000000000004024000000000000403400000000000040340000000000004024000000000000404400000000000000000000020000000440440000000000004044000000000000403E000000000000403E00000000000040440000000000004034000000000000403E0000000000004024000000000000"); 302 | 303 | // ACT 304 | Geometry geo = WkbConverter.FromBinary(bytes); 305 | byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); 306 | 307 | // ASSERT 308 | Assert.Equal(bytes, newBytes); 309 | } 310 | 311 | [Fact] 312 | public void MultiLineStringTest_ToBinary_LittleEndian() 313 | { 314 | // ARRANGE 315 | 316 | // MULTILINESTRING((10 10,20 20,10 40),(40 40,30 30,40 20,30 10)) 317 | byte[] bytes = HexStringToByteArray("010500000002000000010200000003000000000000000000244000000000000024400000000000003440000000000000344000000000000024400000000000004440010200000004000000000000000000444000000000000044400000000000003e400000000000003e40000000000000444000000000000034400000000000003e400000000000002440"); 318 | 319 | // ACT 320 | Geometry geo = WkbConverter.FromBinary(bytes); 321 | byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.LITTLE); 322 | 323 | // ASSERT 324 | Assert.Equal(bytes, newBytes); 325 | } 326 | 327 | #endregion 328 | 329 | #region MultiPoint Tests 330 | 331 | [Fact] 332 | public void MultiPointTest_FromBinary_LittleEndian() 333 | { 334 | // ARRANGE 335 | 336 | // MULTIPOINT(10 40,40 30,20 20,30 10) 337 | byte[] bytes = HexStringToByteArray("010400000004000000010100000000000000000024400000000000004440010100000000000000000044400000000000003e4001010000000000000000003440000000000000344001010000000000000000003e400000000000002440"); 338 | 339 | // ACT 340 | Geometry geo = WkbConverter.FromBinary(bytes); 341 | 342 | // ASSERT 343 | MultiPoint mp = Assert.IsType(geo); 344 | } 345 | 346 | [Fact] 347 | public void MultiPointTest_FromBinary_BigEndian() 348 | { 349 | // ARRANGE 350 | 351 | // MULTIPOINT ((21.06 19.77), (03.02 19.54), (40 20), (30 10)) 352 | byte[] bytes = HexStringToByteArray("000000000400000004000000000140350F5C28F5C28F4033C51EB851EB850000000001400828F5C28F5C2940338A3D70A3D70A0000000001404400000000000040340000000000000000000001403E0000000000004024000000000000"); 353 | 354 | 355 | // ACT 356 | Geometry geo = WkbConverter.FromBinary(bytes); 357 | 358 | // ASSERT 359 | MultiPoint mp = Assert.IsType(geo); 360 | Assert.Equal(21.06, mp.Coordinates.ElementAt(0).Longitude); 361 | Assert.Equal(19.77, mp.Coordinates.ElementAt(0).Latitude); 362 | } 363 | 364 | [Fact] 365 | public void MultiPointTest_ToBinary_BigEndian() 366 | { 367 | // ARRANGE 368 | 369 | // MULTIPOINT ((21.06 19.77), (03.02 19.54), (40 20), (30 10)) 370 | byte[] bytes = HexStringToByteArray("000000000400000004000000000140350F5C28F5C28F4033C51EB851EB850000000001400828F5C28F5C2940338A3D70A3D70A0000000001404400000000000040340000000000000000000001403E0000000000004024000000000000"); 371 | 372 | // ACT 373 | Geometry geo = WkbConverter.FromBinary(bytes); 374 | byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); 375 | 376 | // ASSERT 377 | Assert.Equal(bytes, newBytes); 378 | } 379 | 380 | [Fact] 381 | public void MultiPointTest_ToBinary_LittleEndian() 382 | { 383 | // ARRANGE 384 | 385 | // MULTIPOINT(10 40,40 30,20 20,30 10) 386 | byte[] bytes = HexStringToByteArray("010400000004000000010100000000000000000024400000000000004440010100000000000000000044400000000000003e4001010000000000000000003440000000000000344001010000000000000000003e400000000000002440"); 387 | 388 | // ACT 389 | Geometry geo = WkbConverter.FromBinary(bytes); 390 | byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.LITTLE); 391 | 392 | // ASSERT 393 | Assert.Equal(bytes, newBytes); 394 | } 395 | 396 | #endregion 397 | 398 | #region MultiPolygon Tests 399 | 400 | [Fact] 401 | public void MultiPolygonTest_FromBinary_BigEndian() 402 | { 403 | // ARRANGE 404 | 405 | // MULTIPOLYGON(((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5))) 406 | byte[] bytes = HexStringToByteArray("00000000060000000200000000030000000100000004403E00000000000040340000000000004046800000000000404400000000000040240000000000004044000000000000403E000000000000403400000000000000000000030000000100000005402E0000000000004014000000000000404400000000000040240000000000004024000000000000403400000000000040140000000000004024000000000000402E0000000000004014000000000000"); 407 | 408 | // ACT 409 | Geometry geo = WkbConverter.FromBinary(bytes); 410 | 411 | // ASSERT 412 | MultiPolygon mp = Assert.IsType(geo); 413 | } 414 | 415 | [Fact] 416 | public void MultiPolygonTest_FromBinary_LittleEndian() 417 | { 418 | // ARRANGE 419 | 420 | // MULTIPOLYGON(((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5))) 421 | byte[] bytes = HexStringToByteArray("010600000002000000010300000001000000040000000000000000003e40000000000000344000000000008046400000000000004440000000000000244000000000000044400000000000003e400000000000003440010300000001000000050000000000000000002e4000000000000014400000000000004440000000000000244000000000000024400000000000003440000000000000144000000000000024400000000000002e400000000000001440"); 422 | 423 | // ACT 424 | Geometry geo = WkbConverter.FromBinary(bytes); 425 | 426 | // ASSERT 427 | MultiPolygon mp = Assert.IsType(geo); 428 | } 429 | 430 | [Fact] 431 | public void MultiPolygonTest_ToBinary_LittleEndian() 432 | { 433 | // ARRANGE 434 | 435 | // MULTIPOLYGON(((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5))) 436 | byte[] bytes = HexStringToByteArray("010600000002000000010300000001000000040000000000000000003e40000000000000344000000000008046400000000000004440000000000000244000000000000044400000000000003e400000000000003440010300000001000000050000000000000000002e4000000000000014400000000000004440000000000000244000000000000024400000000000003440000000000000144000000000000024400000000000002e400000000000001440"); 437 | 438 | // ACT 439 | Geometry geo = WkbConverter.FromBinary(bytes); 440 | byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.LITTLE); 441 | 442 | // ASSERT 443 | Assert.Equal(bytes, newBytes); 444 | } 445 | 446 | [Fact] 447 | public void MultiPolygonTest_ToBinary_BigEndian() 448 | { 449 | // ARRANGE 450 | 451 | // MULTIPOLYGON(((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5))) 452 | byte[] bytes = HexStringToByteArray("00000000060000000200000000030000000100000004403E00000000000040340000000000004046800000000000404400000000000040240000000000004044000000000000403E000000000000403400000000000000000000030000000100000005402E0000000000004014000000000000404400000000000040240000000000004024000000000000403400000000000040140000000000004024000000000000402E0000000000004014000000000000"); 453 | 454 | // ACT 455 | Geometry geo = WkbConverter.FromBinary(bytes); 456 | byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); 457 | 458 | // ASSERT 459 | Assert.Equal(bytes, newBytes); 460 | } 461 | 462 | #endregion 463 | 464 | #region Polygon Tests 465 | 466 | [Fact] 467 | public void PolygonTest_FromBinary_BigEndian() 468 | { 469 | // ARRANGE 470 | 471 | // POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10)) 472 | byte[] bytes = HexStringToByteArray("00000000030000000100000005403E0000000000004024000000000000404400000000000040440000000000004034000000000000404400000000000040240000000000004034000000000000403E0000000000004024000000000000"); 473 | 474 | // ACT 475 | Geometry geo = WkbConverter.FromBinary(bytes); 476 | 477 | // ASSERT 478 | Polygon polygon = Assert.IsType(geo); 479 | } 480 | 481 | [Fact] 482 | public void PolygonTest_FromBinary_LittleEndian() 483 | { 484 | // ARRANGE 485 | 486 | // POLYGON((30 10,40 40,20 40,10 20,30 10)) 487 | byte[] bytes = HexStringToByteArray("010300000001000000050000000000000000003e4000000000000024400000000000004440000000000000444000000000000034400000000000004440000000000000244000000000000034400000000000003e400000000000002440"); 488 | 489 | // ACT 490 | Geometry geo = WkbConverter.FromBinary(bytes); 491 | 492 | // ASSERT 493 | Polygon polygon = Assert.IsType(geo); 494 | } 495 | 496 | [Fact] 497 | public void PolygonTest_ToBinary_LittleEndian() 498 | { 499 | // ARRANGE 500 | 501 | // POLYGON((30 10,40 40,20 40,10 20,30 10)) 502 | byte[] bytes = HexStringToByteArray("010300000001000000050000000000000000003e4000000000000024400000000000004440000000000000444000000000000034400000000000004440000000000000244000000000000034400000000000003e400000000000002440"); 503 | 504 | // ACT 505 | Geometry geo = WkbConverter.FromBinary(bytes); 506 | byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.LITTLE); 507 | 508 | // ASSERT 509 | Assert.Equal(bytes, newBytes); 510 | } 511 | 512 | [Fact] 513 | public void PolygonTest_ToBinary_BigEndian() 514 | { 515 | // ARRANGE 516 | 517 | // POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10)) 518 | byte[] bytes = HexStringToByteArray("00000000030000000100000005403E0000000000004024000000000000404400000000000040440000000000004034000000000000404400000000000040240000000000004034000000000000403E0000000000004024000000000000"); 519 | 520 | // ACT 521 | Geometry geo = WkbConverter.FromBinary(bytes); 522 | byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); 523 | 524 | // ASSERT 525 | Assert.Equal(bytes, newBytes); 526 | } 527 | 528 | #endregion 529 | 530 | #region GeometryCollection Tests 531 | 532 | [Fact] 533 | public void GeometryCollectionTest_FromBinary_BigEndian() 534 | { 535 | // ARRANGE 536 | 537 | // GEOMETRYCOLLECTION(POINT (40 10),LINESTRING(10 10, 20 20, 10 40),POLYGON((40 40, 20 45, 45 30, 40 40))) 538 | byte[] bytes = HexStringToByteArray("0000000007000000030000000001404400000000000040240000000000000000000002000000034024000000000000402400000000000040340000000000004034000000000000402400000000000040440000000000000000000003000000010000000440440000000000004044000000000000403400000000000040468000000000004046800000000000403E00000000000040440000000000004044000000000000"); 539 | 540 | // ACT 541 | Geometry geo = WkbConverter.FromBinary(bytes); 542 | 543 | // ASSERT 544 | GeometryCollection geoCollection = Assert.IsType(geo); 545 | Point point = Assert.IsType(geoCollection.Geometries.ElementAt(0)); 546 | LineString lineString = Assert.IsType(geoCollection.Geometries.ElementAt(1)); 547 | Polygon polygon = Assert.IsType(geoCollection.Geometries.ElementAt(2)); 548 | Assert.Equal(40, point.Coordinates.Longitude); 549 | Assert.Equal(10, point.Coordinates.Latitude); 550 | } 551 | 552 | [Fact] 553 | public void GeometryCollectionTest_FromBinary_LittleEndian() 554 | { 555 | // ARRANGE 556 | 557 | // GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10)) 558 | byte[] bytes = HexStringToByteArray("010700000002000000010100000000000000000010400000000000001840010200000002000000000000000000104000000000000018400000000000001c400000000000002440"); 559 | 560 | // ACT 561 | Geometry geo = WkbConverter.FromBinary(bytes); 562 | 563 | // ASSERT 564 | GeometryCollection geoCollection = Assert.IsType(geo); 565 | Point point = Assert.IsType(geoCollection.Geometries.ElementAt(0)); 566 | LineString lineString = Assert.IsType(geoCollection.Geometries.ElementAt(1)); 567 | Assert.Equal(4, point.Coordinates.Longitude); 568 | Assert.Equal(6, point.Coordinates.Latitude); 569 | } 570 | 571 | [Fact] 572 | public void GeometryCollectionTest_ToBinary_LittleEndian() 573 | { 574 | // ARRANGE 575 | 576 | // GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10)) 577 | byte[] bytes = HexStringToByteArray("010700000002000000010100000000000000000010400000000000001840010200000002000000000000000000104000000000000018400000000000001c400000000000002440"); 578 | 579 | // ACT 580 | Geometry geo = WkbConverter.FromBinary(bytes); 581 | byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.LITTLE); 582 | 583 | // ASSERT 584 | Assert.Equal(bytes, newBytes); 585 | } 586 | 587 | [Fact] 588 | public void GeometryCollectionTest_ToBinary_BigEndian() 589 | { 590 | // ARRANGE 591 | 592 | // GEOMETRYCOLLECTION(POINT (40 10),LINESTRING(10 10, 20 20, 10 40),POLYGON((40 40, 20 45, 45 30, 40 40))) 593 | byte[] bytes = HexStringToByteArray("0000000007000000030000000001404400000000000040240000000000000000000002000000034024000000000000402400000000000040340000000000004034000000000000402400000000000040440000000000000000000003000000010000000440440000000000004044000000000000403400000000000040468000000000004046800000000000403E00000000000040440000000000004044000000000000"); 594 | 595 | // ACT 596 | Geometry geo = WkbConverter.FromBinary(bytes); 597 | byte[] newBytes = WkbConverter.ToBinary(geo, Endianness.BIG); 598 | 599 | // ASSERT 600 | Assert.Equal(bytes, newBytes); 601 | } 602 | 603 | #endregion 604 | 605 | #region Private Methods 606 | 607 | private static byte[] HexStringToByteArray(string hexString) 608 | { 609 | if (hexString.Length % 2 != 0) 610 | { 611 | throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "The binary key cannot have an odd number of digits: {0}", hexString)); 612 | } 613 | 614 | byte[] data = new byte[hexString.Length / 2]; 615 | 616 | for (int index = 0; index < data.Length; index++) 617 | { 618 | string byteValue = hexString.Substring(index * 2, 2); 619 | data[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture); 620 | } 621 | 622 | return data; 623 | } 624 | 625 | #endregion 626 | } 627 | } 628 | -------------------------------------------------------------------------------- /GeoJSON.Tests/feature.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Feature", 3 | "geometry": { 4 | "type": "Point", 5 | "coordinates": [ 102.0, 0.5 ] 6 | }, 7 | "properties": { 8 | "prop0": "value0" 9 | } 10 | } -------------------------------------------------------------------------------- /GeoJSON.Tests/feature_id_num_as_string.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Feature", 3 | "id": "123", 4 | "geometry": { 5 | "type": "Point", 6 | "coordinates": [ 102.0, 0.5 ] 7 | }, 8 | "properties": { 9 | "prop0": "value0" 10 | } 11 | } -------------------------------------------------------------------------------- /GeoJSON.Tests/feature_id_number.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Feature", 3 | "id": 123, 4 | "geometry": { 5 | "type": "Point", 6 | "coordinates": [ 102.0, 0.5 ] 7 | }, 8 | "properties": { 9 | "prop0": "value0" 10 | } 11 | } -------------------------------------------------------------------------------- /GeoJSON.Tests/feature_id_string.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Feature", 3 | "id": "my_id", 4 | "geometry": { 5 | "type": "Point", 6 | "coordinates": [ 102.0, 0.5 ] 7 | }, 8 | "properties": { 9 | "prop0": "value0" 10 | } 11 | } -------------------------------------------------------------------------------- /GeoJSON.Tests/feature_null_geometry.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Feature", 3 | "geometry": null, 4 | "properties": { 5 | "prop0": "value0" 6 | } 7 | } -------------------------------------------------------------------------------- /GeoJSON.Tests/feature_out_of_range.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Feature", 3 | "geometry": { 4 | "type": "Point", 5 | "coordinates": [ 200.0, 0.5 ] 6 | }, 7 | "properties": { 8 | "prop0": "value0" 9 | } 10 | } -------------------------------------------------------------------------------- /GeoJSON.Tests/featurebbox.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Feature", 3 | "bbox": [ -10.0, -10.0, 10.0, 10.0 ], 4 | "geometry": { 5 | "type": "Point", 6 | "coordinates": [ 102.0, 0.5 ] 7 | }, 8 | "properties": { 9 | "prop0": "value0" 10 | } 11 | } -------------------------------------------------------------------------------- /GeoJSON.Tests/featurecollection.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "Point", 8 | "coordinates": [ 102.0, 0.5 ] 9 | }, 10 | "properties": { 11 | "prop0": "value0" 12 | } 13 | }, 14 | { 15 | "type": "Feature", 16 | "geometry": { 17 | "type": "LineString", 18 | "coordinates": [ 19 | [ 102.0, 0.0 ], 20 | [ 103.0, 1.0 ], 21 | [ 104.0, 0.0 ], 22 | [ 105.0, 1.0 ] 23 | ] 24 | }, 25 | "properties": { 26 | "prop0": "value0", 27 | "prop1": 0.0 28 | } 29 | }, 30 | { 31 | "type": "Feature", 32 | "geometry": { 33 | "type": "Polygon", 34 | "coordinates": [ 35 | [ 36 | [ 100.0, 0.0 ], 37 | [ 101.0, 0.0 ], 38 | [ 101.0, 1.0 ], 39 | [ 100.0, 1.0 ], 40 | [ 100.0, 0.0 ] 41 | ] 42 | ] 43 | }, 44 | "properties": { 45 | "prop0": "value0", 46 | "prop1": { 47 | "this": "that" 48 | } 49 | } 50 | } 51 | ] 52 | } -------------------------------------------------------------------------------- /GeoJSON.Tests/featurecollectionbbox.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "bbox": [ 100.0, 0.0, 105.0, 1.0 ], 4 | "features": [ 5 | { 6 | "type": "Feature", 7 | "geometry": { 8 | "type": "Point", 9 | "coordinates": [ 102.0, 0.5 ] 10 | }, 11 | "properties": { 12 | "prop0": "value0" 13 | } 14 | }, 15 | { 16 | "type": "Feature", 17 | "geometry": { 18 | "type": "LineString", 19 | "coordinates": [ 20 | [ 102.0, 0.0 ], 21 | [ 103.0, 1.0 ], 22 | [ 104.0, 0.0 ], 23 | [ 105.0, 1.0 ] 24 | ] 25 | }, 26 | "properties": { 27 | "prop0": "value0", 28 | "prop1": 0.0 29 | } 30 | }, 31 | { 32 | "type": "Feature", 33 | "geometry": { 34 | "type": "Polygon", 35 | "coordinates": [ 36 | [ 37 | [ 100.0, 0.0 ], 38 | [ 101.0, 0.0 ], 39 | [ 101.0, 1.0 ], 40 | [ 100.0, 1.0 ], 41 | [ 100.0, 0.0 ] 42 | ] 43 | ] 44 | }, 45 | "properties": { 46 | "prop0": "value0", 47 | "prop1": { 48 | "this": "that" 49 | } 50 | } 51 | } 52 | ] 53 | } -------------------------------------------------------------------------------- /GeoJSON.Tests/geometrycollection.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "GeometryCollection", 3 | "geometries": [ 4 | { 5 | "type": "LineString", 6 | "coordinates": [ 7 | [ 102.0, 0.0 ], 8 | [ 103.0, 1.0 ], 9 | [ 104.0, 0.0 ], 10 | [ 105.0, 1.0 ] 11 | ] 12 | }, 13 | { 14 | "type": "Polygon", 15 | "coordinates": [ 16 | [ 17 | [ -10.0, -10.0 ], 18 | [ 10.0, -10.0 ], 19 | [ 10.0, 10.0 ], 20 | [ -10.0, -10.0 ] 21 | ] 22 | ] 23 | }, 24 | { 25 | "type": "MultiPolygon", 26 | "coordinates": [ 27 | [ 28 | [ 29 | [ 180.0, 40.0 ], 30 | [ 180.0, 50.0 ], 31 | [ 170.0, 50.0 ], 32 | [ 170.0, 40.0 ], 33 | [ 180.0, 40.0 ] 34 | ] 35 | ], 36 | [ 37 | [ 38 | [ -170.0, 40.0 ], 39 | [ -170.0, 50.0 ], 40 | [ -180.0, 50.0 ], 41 | [ -180.0, 40.0 ], 42 | [ -170.0, 40.0 ] 43 | ] 44 | ] 45 | ] 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /GeoJSON.Tests/linestring.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "LineString", 3 | "coordinates": [ 4 | [ 102.0, 0.0 ], 5 | [ 103.0, 1.0 ], 6 | [ 104.0, 0.0 ], 7 | [ 105.0, 1.0 ] 8 | ] 9 | } -------------------------------------------------------------------------------- /GeoJSON.Tests/multilinestring.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "MultiLineString", 3 | "coordinates": [ 4 | [ 5 | [ 170.0, 45.0 ], 6 | [ 180.0, 45.0 ] 7 | ], 8 | [ 9 | [ -180.0, 45.0 ], 10 | [ -170.0, 45.0 ] 11 | ] 12 | ] 13 | } -------------------------------------------------------------------------------- /GeoJSON.Tests/multipoint.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "MultiPoint", 3 | "coordinates": [ 4 | [ 102.0, 1.5 ], 5 | [ 103.0, 2.5 ], 6 | [ 101.0, 1.0 ], 7 | [ 100.0, 2.5 ], 8 | [ 101.0, 1.5 ] 9 | ] 10 | } -------------------------------------------------------------------------------- /GeoJSON.Tests/multipolygon.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "MultiPolygon", 3 | "coordinates": [ 4 | [ 5 | [ 6 | [ 180.0, 40.0 ], 7 | [ 180.0, 50.0 ], 8 | [ 170.0, 50.0 ], 9 | [ 170.0, 40.0 ], 10 | [ 180.0, 40.0 ] 11 | ] 12 | ], 13 | [ 14 | [ 15 | [ -170.0, 40.0 ], 16 | [ -170.0, 50.0 ], 17 | [ -180.0, 50.0 ], 18 | [ -180.0, 40.0 ], 19 | [ -170.0, 40.0 ] 20 | ] 21 | ] 22 | ] 23 | } -------------------------------------------------------------------------------- /GeoJSON.Tests/point.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Point", 3 | "coordinates": [ 102.0, 0.5 ] 4 | } -------------------------------------------------------------------------------- /GeoJSON.Tests/polygon.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Polygon", 3 | "coordinates": [ 4 | [ 5 | [ -10.0, -10.0 ], 6 | [ 10.0, -10.0 ], 7 | [ 10.0, 10.0 ], 8 | [ -10.0, -10.0 ] 9 | ] 10 | ] 11 | } -------------------------------------------------------------------------------- /GeoJSON.Tests/polygonwithhole.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Polygon", 3 | "coordinates": [ 4 | [ 5 | [ 100.0, 0.0 ], 6 | [ 101.0, 0.0 ], 7 | [ 101.0, 1.0 ], 8 | [ 100.0, 1.0 ], 9 | [ 100.0, 0.0 ] 10 | ], 11 | [ 12 | [ 100.8, 0.8 ], 13 | [ 100.8, 0.2 ], 14 | [ 100.2, 0.2 ], 15 | [ 100.2, 0.8 ], 16 | [ 100.8, 0.8 ] 17 | ] 18 | ] 19 | } -------------------------------------------------------------------------------- /GeoJSON.Tests/position.json: -------------------------------------------------------------------------------- 1 | [ 2 | 100.0, 3 | 1.5, 4 | 1245.23 5 | ] -------------------------------------------------------------------------------- /GeoJSON.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30011.22 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GeoJSON", "GeoJSON\GeoJSON.csproj", "{13B19F69-F3B6-497E-A9E0-D6DFCC533EC6}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GeoJSON.Tests", "GeoJSON.Tests\GeoJSON.Tests.csproj", "{457C0EFB-99D9-46DB-8949-B5B90A855847}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D5ED7550-3703-4250-B3F5-B3DFB135A6A5}" 11 | ProjectSection(SolutionItems) = preProject 12 | README.md = README.md 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {13B19F69-F3B6-497E-A9E0-D6DFCC533EC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {13B19F69-F3B6-497E-A9E0-D6DFCC533EC6}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {13B19F69-F3B6-497E-A9E0-D6DFCC533EC6}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {13B19F69-F3B6-497E-A9E0-D6DFCC533EC6}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {457C0EFB-99D9-46DB-8949-B5B90A855847}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {457C0EFB-99D9-46DB-8949-B5B90A855847}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {457C0EFB-99D9-46DB-8949-B5B90A855847}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {457C0EFB-99D9-46DB-8949-B5B90A855847}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {45DBB9DB-72BC-4535-8A10-23DBFA396159} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /GeoJSON/Feature.cs: -------------------------------------------------------------------------------- 1 | using BAMCIS.GeoJSON.Serde; 2 | using Newtonsoft.Json; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace BAMCIS.GeoJSON 7 | { 8 | /// 9 | /// Feature objects in GeoJSON contain a Geometry object and additional members. 10 | /// 11 | [JsonConverter(typeof(InheritanceBlockerConverter))] 12 | public class Feature : GeoJson 13 | { 14 | #region Public Properties 15 | 16 | /// 17 | /// The geometry of this feature 18 | /// 19 | [JsonProperty(PropertyName = "geometry")] 20 | public Geometry Geometry { get; } 21 | 22 | /// 23 | /// Additional properties for the feature, the value 24 | /// is dynamic as it could be a string, bool, null, number, 25 | /// array, or object 26 | /// 27 | [JsonProperty(PropertyName = "properties")] 28 | public IDictionary Properties { get; } 29 | 30 | /// 31 | /// As a property, this is represented as a string 32 | /// 33 | [JsonProperty(PropertyName = "id", NullValueHandling = NullValueHandling.Ignore)] 34 | public FeatureId Id { get; } 35 | 36 | #endregion 37 | 38 | #region Constructors 39 | 40 | /// 41 | /// Builds a new Feature with the given geometry and optional properties 42 | /// 43 | /// The geometry to create the feature from 44 | /// The feature properties 45 | [JsonConstructor] 46 | public Feature(Geometry geometry, IDictionary properties = null, IEnumerable boundingBox = null, FeatureId id = null) : base(GeoJsonType.Feature, geometry == null ? false : geometry.IsThreeDimensional(), boundingBox) 47 | { 48 | this.Geometry = geometry; // Geometry can be null 49 | this.Properties = properties ?? new Dictionary(); 50 | this.Id = id; // id can be null 51 | } 52 | 53 | #endregion 54 | 55 | #region Public Methods 56 | 57 | /// 58 | /// Creates a Feature from json 59 | /// 60 | /// 61 | /// 62 | public static new Feature FromJson(string json) 63 | { 64 | return JsonConvert.DeserializeObject(json); 65 | } 66 | 67 | /// 68 | /// Compares equality of this Feature to another. It is important to note 69 | /// that only the Keys at the top level of the properties dictionary are 70 | /// checked to determine equality of that property. 71 | /// 72 | /// 73 | /// 74 | public override bool Equals(object obj) 75 | { 76 | if (ReferenceEquals(this, obj)) 77 | { 78 | return true; 79 | } 80 | 81 | if (obj == null || this.GetType() != obj.GetType()) 82 | { 83 | return false; 84 | } 85 | 86 | Feature other = (Feature)obj; 87 | 88 | bool bBoxEqual = true; 89 | 90 | if (this.BoundingBox != null && other.BoundingBox != null) 91 | { 92 | bBoxEqual = this.BoundingBox.SequenceEqual(other.BoundingBox); 93 | } 94 | else 95 | { 96 | bBoxEqual = (this.BoundingBox == null && other.BoundingBox == null); 97 | } 98 | 99 | bool propertiesEqual = true; 100 | 101 | if (this.Properties != null && other.Properties != null) 102 | { 103 | propertiesEqual = this.Properties.Keys.SequenceEqual(other.Properties.Keys); 104 | } 105 | else 106 | { 107 | propertiesEqual = (this.Properties == null && other.Properties == null); 108 | } 109 | 110 | 111 | return this.Type == other.Type && 112 | this.Geometry == other.Geometry && 113 | bBoxEqual && 114 | propertiesEqual; 115 | } 116 | 117 | public override int GetHashCode() 118 | { 119 | return Hashing.Hash(this.Type, this.Geometry, this.BoundingBox, this.Properties); 120 | } 121 | 122 | public static bool operator ==(Feature left, Feature right) 123 | { 124 | if (ReferenceEquals(left, right)) 125 | { 126 | return true; 127 | } 128 | 129 | if (right is null || left is null) 130 | { 131 | return false; 132 | } 133 | 134 | return left.Equals(right); 135 | } 136 | 137 | public static bool operator !=(Feature left, Feature right) 138 | { 139 | return !(left == right); 140 | } 141 | 142 | #endregion 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /GeoJSON/FeatureCollection.cs: -------------------------------------------------------------------------------- 1 | using BAMCIS.GeoJSON.Serde; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace BAMCIS.GeoJSON 8 | { 9 | /// 10 | /// Represents a collection of feature objects 11 | /// 12 | [JsonConverter(typeof(InheritanceBlockerConverter))] 13 | public class FeatureCollection : GeoJson 14 | { 15 | #region Public Properties 16 | 17 | /// 18 | /// The collection of features 19 | /// 20 | [JsonProperty(PropertyName = "features")] 21 | public IEnumerable Features { get; } 22 | 23 | #endregion 24 | 25 | #region Constructors 26 | 27 | /// 28 | /// The default constructor building the feature collection 29 | /// 30 | /// The features that are part of the feature collection 31 | [JsonConstructor] 32 | public FeatureCollection(IEnumerable features, IEnumerable boundingBox = null) : base(GeoJsonType.FeatureCollection, features.Any(x => x.IsThreeDimensional()), boundingBox) 33 | { 34 | this.Features = features ?? throw new ArgumentNullException("features"); 35 | } 36 | 37 | #endregion 38 | 39 | #region Public Methods 40 | 41 | public new static FeatureCollection FromJson(string json) 42 | { 43 | return JsonConvert.DeserializeObject(json); 44 | } 45 | 46 | public override bool Equals(object obj) 47 | { 48 | if (ReferenceEquals(this, obj)) 49 | { 50 | return true; 51 | } 52 | 53 | if (obj == null || this.GetType() != obj.GetType()) 54 | { 55 | return false; 56 | } 57 | 58 | FeatureCollection other = (FeatureCollection)obj; 59 | 60 | bool bBoxEqual = true; 61 | 62 | if (this.BoundingBox != null && other.BoundingBox != null) 63 | { 64 | bBoxEqual = this.BoundingBox.SequenceEqual(other.BoundingBox); 65 | } 66 | else 67 | { 68 | bBoxEqual = (this.BoundingBox == null && other.BoundingBox == null); 69 | } 70 | 71 | bool featuresEqual = true; 72 | 73 | if (this.Features != null && other.Features != null) 74 | { 75 | featuresEqual = this.Features.SequenceEqual(other.Features); 76 | } 77 | else 78 | { 79 | featuresEqual = (this.Features == null && other.Features == null); 80 | } 81 | 82 | return this.Type == other.Type && 83 | featuresEqual && 84 | bBoxEqual; 85 | } 86 | 87 | public override int GetHashCode() 88 | { 89 | return Hashing.Hash(this.Type, this.Features, this.BoundingBox); 90 | } 91 | 92 | public static bool operator ==(FeatureCollection left, FeatureCollection right) 93 | { 94 | if (ReferenceEquals(left, right)) 95 | { 96 | return true; 97 | } 98 | 99 | if (right is null || left is null) 100 | { 101 | return false; 102 | } 103 | 104 | return left.Equals(right); 105 | } 106 | 107 | public static bool operator !=(FeatureCollection left, FeatureCollection right) 108 | { 109 | return !(left == right); 110 | } 111 | 112 | #endregion 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /GeoJSON/FeatureId.cs: -------------------------------------------------------------------------------- 1 | using BAMCIS.GeoJSON.Serde; 2 | using Newtonsoft.Json; 3 | using System; 4 | 5 | namespace BAMCIS.GeoJSON 6 | { 7 | /// 8 | /// Represents a dynamic Feature Id that can be provided as a string or 9 | /// integer value 10 | /// 11 | [JsonConverter(typeof(FeatureIdConverter))] 12 | public class FeatureId 13 | { 14 | #region Private Properties 15 | 16 | private string stringValue = null; 17 | private Int64? intValue = null; 18 | private Type originalType = null; 19 | 20 | #endregion 21 | 22 | #region Public Properties 23 | 24 | /// 25 | /// The string representation of the value 26 | /// 27 | public string Value { get; } 28 | 29 | #endregion 30 | 31 | #region Constructors 32 | 33 | /// 34 | /// Create the id with a string value 35 | /// 36 | /// 37 | public FeatureId(string id) 38 | { 39 | this.stringValue = id; 40 | this.originalType = typeof(string); 41 | this.Value = id; 42 | } 43 | 44 | /// 45 | /// Create the id with an integer value 46 | /// 47 | /// 48 | public FeatureId(Int64 id) 49 | { 50 | this.intValue = id; 51 | this.originalType = typeof(int); 52 | this.Value = id.ToString(); 53 | } 54 | 55 | #endregion 56 | 57 | #region Public Methods 58 | 59 | /// 60 | /// Provides the original type of the id, either string or integer 61 | /// 62 | /// 63 | public Type GetOriginalType() 64 | { 65 | return this.originalType; 66 | } 67 | 68 | public override bool Equals(object obj) 69 | { 70 | if (ReferenceEquals(this, obj)) 71 | { 72 | return true; 73 | } 74 | 75 | if (obj == null || this.GetType() != obj.GetType()) 76 | { 77 | return false; 78 | } 79 | 80 | FeatureId other = (FeatureId)obj; 81 | 82 | if (other.originalType != this.originalType) 83 | { 84 | return false; 85 | } 86 | 87 | if (other.originalType == typeof(string)) 88 | { 89 | return other.stringValue == this.stringValue; 90 | } 91 | else 92 | { 93 | return other.intValue == this.intValue; 94 | } 95 | } 96 | 97 | public override int GetHashCode() 98 | { 99 | if (this.originalType == typeof(string)) 100 | { 101 | return Hashing.Hash(this.stringValue); 102 | } 103 | else 104 | { 105 | return Hashing.Hash(this.intValue); 106 | } 107 | } 108 | 109 | public static bool operator ==(FeatureId left, FeatureId right) 110 | { 111 | if (ReferenceEquals(left, right)) 112 | { 113 | return true; 114 | } 115 | 116 | if (right is null || left is null) 117 | { 118 | return false; 119 | } 120 | 121 | return left.Equals(right); 122 | } 123 | 124 | public static bool operator !=(FeatureId left, FeatureId right) 125 | { 126 | return !(left == right); 127 | } 128 | 129 | #endregion 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /GeoJSON/GeoJSON.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard1.6;netstandard2.0;net45 5 | 1.6 6 | BAMCIS.GeoJSON 7 | 2.3.1 8 | Michael Haken 9 | bamcis.io 10 | A .NET Core library for serializing and deserializing GeoJSON formatted JSON data. Complies with RFC 7946. 11 | bamcis.io 12 | true 13 | true 14 | 15 | https://github.com/bamcis-io/GeoJSON 16 | https://github.com/bamcis-io/GeoJSON 17 | Git 18 | GeoJSON RFC7946 19 | Fixed a bug in the Polygon serde. 20 | true 21 | GeoJSON.snk 22 | false 23 | 24 | MIT 25 | 26 | true 27 | true 28 | snupkg 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /GeoJSON/GeoJSON.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bamcis-io/GeoJSON/a9f7e293b06fe73a815498264f7e9fbd6ea3ae9c/GeoJSON/GeoJSON.snk -------------------------------------------------------------------------------- /GeoJSON/GeoJson.cs: -------------------------------------------------------------------------------- 1 | using BAMCIS.GeoJSON.Serde; 2 | using BAMCIS.GeoJSON.Wkb; 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace BAMCIS.GeoJSON 9 | { 10 | /// 11 | /// A base abstract class for the implementation of GeoJson 12 | /// 13 | [JsonConverter(typeof(GeoJsonConverter))] 14 | public abstract class GeoJson 15 | { 16 | #region Private Fields 17 | 18 | private static readonly Dictionary typeToDerivedType; 19 | private static readonly Dictionary derivedTypeToType; 20 | private bool is3D; 21 | 22 | #endregion 23 | 24 | #region Public Properties 25 | 26 | /// 27 | /// The type of the geojson object 28 | /// 29 | [JsonProperty(PropertyName = "type")] 30 | public GeoJsonType Type { get; } 31 | 32 | /// 33 | /// A GeoJSON object MAY have a member named "bbox" to include 34 | /// information on the coordinate range for its Geometries, Features, or 35 | /// FeatureCollections.The value of the bbox member MUST be an array of 36 | /// length 2*n where n is the number of dimensions represented in the 37 | /// contained geometries, with all axes of the most southwesterly point 38 | /// followed by all axes of the more northeasterly point. The axes order 39 | /// of a bbox follows the axes order of geometries. 40 | /// 41 | /// Takes the form [west, south, east, north] for 2D or of the form [west, south, min-altitude, east, north, max-altitude] for 3D 42 | /// 43 | [JsonProperty(PropertyName = "bbox", NullValueHandling = NullValueHandling.Ignore)] 44 | public IEnumerable BoundingBox { get; } 45 | 46 | #endregion 47 | 48 | #region Constructors 49 | 50 | /// 51 | /// Builds the dictionary that maintains the type mapping 52 | /// 53 | static GeoJson() 54 | { 55 | typeToDerivedType = new Dictionary() 56 | { 57 | { typeof(LineString), GeoJsonType.LineString }, 58 | { typeof(MultiLineString), GeoJsonType.MultiLineString }, 59 | { typeof(MultiPoint), GeoJsonType.MultiPoint }, 60 | { typeof(MultiPolygon), GeoJsonType.MultiPolygon }, 61 | { typeof(Point), GeoJsonType.Point }, 62 | { typeof(Polygon), GeoJsonType.Polygon }, 63 | { typeof(GeometryCollection), GeoJsonType.GeometryCollection }, 64 | { typeof(Feature), GeoJsonType.Feature }, 65 | { typeof(FeatureCollection), GeoJsonType.FeatureCollection } 66 | }; 67 | 68 | derivedTypeToType = typeToDerivedType.ToDictionary(pair => pair.Value, pair => pair.Key); 69 | } 70 | 71 | /// 72 | /// Base constructor that all derived classes must implement 73 | /// 74 | /// The type of the GeoJson object 75 | protected GeoJson(GeoJsonType type, bool is3D, IEnumerable boundingBox = null) 76 | { 77 | this.Type = type; 78 | this.BoundingBox = boundingBox; 79 | this.is3D = is3D; 80 | 81 | if (this.BoundingBox != null) 82 | { 83 | int length = boundingBox.Count(); 84 | 85 | if (this.is3D && length != 6) 86 | { 87 | throw new ArgumentOutOfRangeException("boundingBox", "The bounding box must contain 6 elements for a 3D GeoJSON object."); 88 | } 89 | else if (!this.is3D && length != 4) 90 | { 91 | throw new ArgumentOutOfRangeException("boundingBox", "The bounding box must contain 4 elements for a 2D GeoJSON object."); 92 | } 93 | } 94 | } 95 | 96 | #endregion 97 | 98 | #region Public Methods 99 | 100 | /// 101 | /// Indicates that at least one of the coordinates in the GeoJson has an elevation 102 | /// 103 | /// 104 | public bool IsThreeDimensional() 105 | { 106 | return this.is3D; 107 | } 108 | 109 | /// 110 | /// Gets the appropriate class type corresponding to the enum 111 | /// representing the type 112 | /// 113 | /// The GeoJson type of the 114 | /// The .NET type that corresponds to the GeoJson type 115 | public static Type GetType(GeoJsonType type) 116 | { 117 | return derivedTypeToType[type]; 118 | } 119 | 120 | public abstract override bool Equals(object obj); 121 | 122 | public abstract override int GetHashCode(); 123 | 124 | public virtual string ToJson() 125 | { 126 | return this.ToJson(Formatting.None); 127 | } 128 | 129 | public virtual string ToJson(Formatting formatting) 130 | { 131 | return JsonConvert.SerializeObject(this, formatting); 132 | } 133 | 134 | /// 135 | /// Deserializes the json to a GeoJson object 136 | /// 137 | /// 138 | /// 139 | public static GeoJson FromJson(string json) 140 | { 141 | return JsonConvert.DeserializeObject(json); 142 | } 143 | 144 | #endregion 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /GeoJSON/GeoJsonConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace BAMCIS.GeoJSON 6 | { 7 | /// 8 | /// Provides global configuration options for GeoJson serialization and 9 | /// deserialization 10 | /// 11 | public static class GeoJsonConfig 12 | { 13 | #region Private Fields 14 | 15 | private static volatile object sync = new object(); 16 | 17 | private static bool _ignoreLongitudeValidation; 18 | private static bool _ignoreLatitudeValidation; 19 | 20 | #endregion 21 | 22 | /// 23 | /// Set to true to ignore the validation applied to longitude 24 | /// coordinates in Position objects 25 | /// 26 | public static bool IgnoreLongitudeValidation { 27 | get 28 | { 29 | return _ignoreLongitudeValidation; 30 | } 31 | set 32 | { 33 | lock (sync) 34 | { 35 | _ignoreLongitudeValidation = value; 36 | } 37 | } 38 | } 39 | 40 | /// 41 | /// Set to true to ignore the validation applied to latitude 42 | /// coordinates in Position objects 43 | /// 44 | public static bool IgnoreLatitudeValidation { 45 | get 46 | { 47 | return _ignoreLatitudeValidation; 48 | } 49 | set 50 | { 51 | lock (sync) 52 | { 53 | _ignoreLatitudeValidation = value; 54 | } 55 | } 56 | } 57 | 58 | static GeoJsonConfig() 59 | { 60 | IgnoreLongitudeValidation = false; 61 | IgnoreLatitudeValidation = false; 62 | } 63 | 64 | /// 65 | /// Convenience method for ignoring both latitude and longitude 66 | /// validation in Position objects, this operation is thread safe. 67 | /// 68 | public static void IgnorePositionValidation() 69 | { 70 | lock (sync) 71 | { 72 | IgnoreLatitudeValidation = true; 73 | IgnoreLongitudeValidation = true; 74 | } 75 | } 76 | 77 | /// 78 | /// Convenience method for enforcing both latitude and longitude 79 | /// validation in Position objects, this operation is thread safe. 80 | /// 81 | public static void EnforcePositionValidation() 82 | { 83 | lock (sync) 84 | { 85 | IgnoreLatitudeValidation = false; 86 | IgnoreLongitudeValidation = false; 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /GeoJSON/GeoJsonType.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Converters; 3 | 4 | namespace BAMCIS.GeoJSON 5 | { 6 | [JsonConverter(typeof(StringEnumConverter))] 7 | public enum GeoJsonType 8 | { 9 | Point, 10 | 11 | MultiPoint, 12 | 13 | LineString, 14 | 15 | MultiLineString, 16 | 17 | Polygon, 18 | 19 | MultiPolygon, 20 | 21 | GeometryCollection, 22 | 23 | /// 24 | /// Feature objects in GeoJSON contain a Geometry object with one of the 25 | /// above geometry types and additional members. 26 | /// 27 | Feature, 28 | 29 | /// 30 | /// A FeatureCollection object contains an array of Feature objects. 31 | /// 32 | FeatureCollection 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /GeoJSON/Geometry.cs: -------------------------------------------------------------------------------- 1 | using BAMCIS.GeoJSON.Serde; 2 | using BAMCIS.GeoJSON.Wkb; 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | 8 | namespace BAMCIS.GeoJSON 9 | { 10 | /// 11 | /// A base abstract class for geometry types 12 | /// 13 | [JsonConverter(typeof(GeometryConverter))] 14 | public abstract class Geometry : GeoJson 15 | { 16 | #region Private Fields 17 | 18 | private static readonly Dictionary typeToDerivedType; 19 | private static readonly Dictionary derivedTypeToType; 20 | 21 | #endregion 22 | 23 | #region Constructors 24 | 25 | /// 26 | /// Builds the dictionary that maintains the type mapping 27 | /// 28 | static Geometry() 29 | { 30 | typeToDerivedType = new Dictionary() 31 | { 32 | { typeof(LineString), GeoJsonType.LineString }, 33 | { typeof(MultiLineString), GeoJsonType.MultiLineString }, 34 | { typeof(MultiPoint), GeoJsonType.MultiPoint }, 35 | { typeof(MultiPolygon), GeoJsonType.MultiPolygon }, 36 | { typeof(Point), GeoJsonType.Point }, 37 | { typeof(Polygon), GeoJsonType.Polygon }, 38 | { typeof(GeometryCollection), GeoJsonType.GeometryCollection }, 39 | }; 40 | 41 | derivedTypeToType = typeToDerivedType.ToDictionary(pair => pair.Value, pair => pair.Key); 42 | } 43 | 44 | /// 45 | /// Each inherited class must implement this constructor 46 | /// 47 | /// The GeoJson type 48 | [JsonConstructor] 49 | protected Geometry(GeoJsonType type, bool is3D, IEnumerable boundingBox = null) : base(type, is3D, boundingBox) 50 | { 51 | if (!derivedTypeToType.ContainsKey(type)) 52 | { 53 | throw new ArgumentException($"The type {type} is not a valid geometry type."); 54 | } 55 | } 56 | 57 | #endregion 58 | 59 | #region Public Methods 60 | 61 | public static new Geometry FromJson(string json) 62 | { 63 | return JsonConvert.DeserializeObject(json); 64 | } 65 | 66 | /// 67 | /// Deserialize WKB byte array to Geometry. 68 | /// 69 | /// 70 | /// 71 | public static Geometry FromWkb(byte[] bytes) 72 | { 73 | return WkbConverter.FromBinary(bytes); 74 | } 75 | 76 | /// 77 | /// Deserialize WKB byte array to Geometry. 78 | /// 79 | /// 80 | /// 81 | public static T FromWkb(byte[] bytes) where T : Geometry 82 | { 83 | return WkbConverter.FromBinary(bytes); 84 | } 85 | 86 | /// 87 | /// Serialize this geometry object to WKB 88 | /// 89 | /// The default is LITTLE 90 | /// 91 | public byte[] ToWkb(Endianness endianness = Endianness.LITTLE) 92 | { 93 | return WkbConverter.ToBinary(this, endianness); 94 | } 95 | 96 | /// 97 | /// Gets the appropriate class type corresponding to the enum 98 | /// representing the type 99 | /// 100 | /// The GeoJson type 101 | /// The .NET type that corresponds to the GeoJson type 102 | public new static Type GetType(GeoJsonType type) 103 | { 104 | if (derivedTypeToType.ContainsKey(type)) 105 | { 106 | return derivedTypeToType[type]; 107 | } 108 | else 109 | { 110 | throw new ArgumentException($"The type {type} is not a valid geometry type."); 111 | } 112 | } 113 | 114 | public static bool operator ==(Geometry left, Geometry right) 115 | { 116 | if (ReferenceEquals(left, right)) 117 | { 118 | return true; 119 | } 120 | 121 | if (right is null || left is null) 122 | { 123 | return false; 124 | } 125 | 126 | return left.Equals(right); 127 | } 128 | 129 | public static bool operator !=(Geometry left, Geometry right) 130 | { 131 | return !(left == right); 132 | } 133 | 134 | public abstract override bool Equals(object obj); 135 | 136 | public abstract override int GetHashCode(); 137 | 138 | #endregion 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /GeoJSON/GeometryCollection.cs: -------------------------------------------------------------------------------- 1 | using BAMCIS.GeoJSON.Serde; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace BAMCIS.GeoJSON 8 | { 9 | /// 10 | /// A GeoJSON object with type "GeometryCollection" is a Geometry object. 11 | /// A GeometryCollection has a member with the name "geometries". The 12 | /// value of "geometries" is an array. 13 | /// 14 | [JsonConverter(typeof(InheritanceBlockerConverter))] 15 | public class GeometryCollection : Geometry 16 | { 17 | #region Public Properties 18 | 19 | /// 20 | /// The value of "geometries" is an array.Each element of this array is a 21 | /// GeoJSON Geometry object. It is possible for this array to be empty. 22 | /// 23 | [JsonProperty(PropertyName = "geometries")] 24 | public IEnumerable Geometries { get; } 25 | 26 | #endregion 27 | 28 | #region Constructors 29 | 30 | /// 31 | /// Creates a new GeometryCollection 32 | /// 33 | /// The geometries that are part of the collection 34 | [JsonConstructor] 35 | public GeometryCollection(IEnumerable geometries, IEnumerable boundingBox = null) : base(GeoJsonType.GeometryCollection, geometries.Any(x => x.IsThreeDimensional()), boundingBox) 36 | { 37 | this.Geometries = geometries ?? throw new ArgumentNullException("geometries"); 38 | } 39 | 40 | #endregion 41 | 42 | #region Public Methods 43 | 44 | public new static GeometryCollection FromJson(string json) 45 | { 46 | return JsonConvert.DeserializeObject(json); 47 | } 48 | 49 | public override bool Equals(object obj) 50 | { 51 | if (ReferenceEquals(this, obj)) 52 | { 53 | return true; 54 | } 55 | 56 | if (obj == null || this.GetType() != obj.GetType()) 57 | { 58 | return false; 59 | } 60 | 61 | GeometryCollection other = (GeometryCollection)obj; 62 | 63 | bool bBoxEqual = true; 64 | 65 | if (this.BoundingBox != null && other.BoundingBox != null) 66 | { 67 | bBoxEqual = this.BoundingBox.SequenceEqual(other.BoundingBox); 68 | } 69 | else 70 | { 71 | bBoxEqual = (this.BoundingBox == null && other.BoundingBox == null); 72 | } 73 | 74 | bool geometriesEqual = true; 75 | 76 | if (this.Geometries != null && other.Geometries != null) 77 | { 78 | geometriesEqual = this.Geometries.SequenceEqual(other.Geometries); 79 | } 80 | else 81 | { 82 | geometriesEqual = (this.Geometries == null && other.Geometries == null); 83 | } 84 | 85 | return this.Type == other.Type && 86 | geometriesEqual && 87 | bBoxEqual; 88 | } 89 | 90 | public override int GetHashCode() 91 | { 92 | return Hashing.Hash(this.Type, this.Geometries, this.BoundingBox); 93 | } 94 | 95 | public static bool operator ==(GeometryCollection left, GeometryCollection right) 96 | { 97 | if (ReferenceEquals(left, right)) 98 | { 99 | return true; 100 | } 101 | 102 | if (right is null || left is null) 103 | { 104 | return false; 105 | } 106 | 107 | return left.Equals(right); 108 | } 109 | 110 | public static bool operator !=(GeometryCollection left, GeometryCollection right) 111 | { 112 | return !(left == right); 113 | } 114 | 115 | #endregion 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /GeoJSON/GeometryType.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Converters; 3 | 4 | namespace BAMCIS.GeoJSON 5 | { 6 | /// 7 | /// The seven case-sensitive geometry types defined in RFC7946 8 | /// 9 | [JsonConverter(typeof(StringEnumConverter))] 10 | public enum GeometryType 11 | { 12 | Point, 13 | 14 | MultiPoint, 15 | 16 | LineString, 17 | 18 | MultiLineString, 19 | 20 | Polygon, 21 | 22 | MultiPolygon, 23 | 24 | GeometryCollection 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /GeoJSON/Hashing.cs: -------------------------------------------------------------------------------- 1 | namespace BAMCIS.GeoJSON 2 | { 3 | /// 4 | /// Provides a method for implementing custom GetHashCode() overrides. 5 | /// 6 | internal class Hashing 7 | { 8 | /// 9 | /// Computes a hash for a set of objects 10 | /// 11 | /// The arguments to hash 12 | /// The hash code of the objects 13 | internal static int Hash(params object[] args) 14 | { 15 | unchecked // Overflow is fine, just wrap 16 | { 17 | int hash = 17; 18 | 19 | foreach (object item in args) 20 | { 21 | if (item != null) 22 | { 23 | hash = (hash * 23) + item.GetHashCode(); 24 | } 25 | } 26 | 27 | return hash; 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /GeoJSON/LineString.cs: -------------------------------------------------------------------------------- 1 | using BAMCIS.GeoJSON.Serde; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace BAMCIS.GeoJSON 8 | { 9 | /// 10 | /// For type "LineString", the "coordinates" member is an array of two or 11 | /// more positions. 12 | /// 13 | [JsonConverter(typeof(InheritanceBlockerConverter))] 14 | public class LineString : Geometry 15 | { 16 | #region Public Properties 17 | 18 | /// 19 | /// The coordinates of a linestring are an array of positions 20 | /// 21 | [JsonProperty(PropertyName = "coordinates")] 22 | public IEnumerable Coordinates { get; } 23 | 24 | #endregion 25 | 26 | #region Constructors 27 | 28 | /// 29 | /// Creates a new LineString 30 | /// 31 | /// The coordinates in the line string 32 | public LineString(IEnumerable coordinates, IEnumerable boundingBox = null) : base(GeoJsonType.LineString, coordinates.Any(x => x.HasElevation()), boundingBox) 33 | { 34 | this.Coordinates = coordinates ?? throw new ArgumentNullException("coordinates"); 35 | 36 | if (this.Coordinates.Count() < 2) 37 | { 38 | throw new ArgumentOutOfRangeException("coordinates", "A LineString must have at least two positions."); 39 | } 40 | } 41 | 42 | #endregion 43 | 44 | #region Public Methods 45 | 46 | public static new LineString FromJson(string json) 47 | { 48 | return JsonConvert.DeserializeObject(json); 49 | } 50 | 51 | public override bool Equals(object obj) 52 | { 53 | if (ReferenceEquals(this, obj)) 54 | { 55 | return true; 56 | } 57 | 58 | if (obj == null || this.GetType() != obj.GetType()) 59 | { 60 | return false; 61 | } 62 | 63 | LineString other = (LineString)obj; 64 | 65 | bool bBoxEqual = true; 66 | 67 | if (this.BoundingBox != null && other.BoundingBox != null) 68 | { 69 | bBoxEqual = this.BoundingBox.SequenceEqual(other.BoundingBox); 70 | } 71 | else 72 | { 73 | bBoxEqual = (this.BoundingBox == null && other.BoundingBox == null); 74 | } 75 | 76 | bool coordinatesEqual = true; 77 | 78 | if (this.Coordinates != null && other.Coordinates != null) 79 | { 80 | coordinatesEqual = this.Coordinates.SequenceEqual(other.Coordinates); 81 | } 82 | else 83 | { 84 | coordinatesEqual = (this.Coordinates == null && other.Coordinates == null); 85 | } 86 | 87 | return this.Type == other.Type && 88 | coordinatesEqual && 89 | bBoxEqual; 90 | } 91 | 92 | public override int GetHashCode() 93 | { 94 | return Hashing.Hash(this.Type, this.Coordinates, this.BoundingBox); 95 | } 96 | 97 | public static bool operator ==(LineString left, LineString right) 98 | { 99 | if (ReferenceEquals(left, right)) 100 | { 101 | return true; 102 | } 103 | 104 | if (right is null || left is null) 105 | { 106 | return false; 107 | } 108 | 109 | return left.Equals(right); 110 | } 111 | 112 | public static bool operator !=(LineString left, LineString right) 113 | { 114 | return !(left == right); 115 | } 116 | 117 | #endregion 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /GeoJSON/LinearRing.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace BAMCIS.GeoJSON 7 | { 8 | /// 9 | /// A linear ring is a closed LineString with four or more positions. 10 | /// 11 | /// A linear ring is the boundary of a surface or the boundary of a 12 | /// hole in a surface. 13 | /// 14 | /// A linear ring MUST follow the right-hand rule with respect to the 15 | /// area it bounds, i.e., exterior rings are counterclockwise, and 16 | /// holes are clockwise. 17 | /// 18 | public class LinearRing : LineString 19 | { 20 | #region Constructors 21 | 22 | /// 23 | /// Creates a new LinearRing 24 | /// 25 | /// The coordinates that make up the linear ring 26 | [JsonConstructor] 27 | public LinearRing(IEnumerable coordinates, IEnumerable boundingBox = null) : base(coordinates, boundingBox) 28 | { 29 | Position[] coords = this.Coordinates.ToArray(); 30 | 31 | if (coords.Length < 4) 32 | { 33 | throw new ArgumentOutOfRangeException("A linear ring requires 4 or more positions."); 34 | } 35 | 36 | if (!coords.First().Equals(coords.Last())) 37 | { 38 | throw new ArgumentException("The first and last value must be equivalent.", "coordinates"); 39 | } 40 | } 41 | 42 | #endregion 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /GeoJSON/MultiLineString.cs: -------------------------------------------------------------------------------- 1 | using BAMCIS.GeoJSON.Serde; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace BAMCIS.GeoJSON 8 | { 9 | /// 10 | /// For type "MultiLineString", the "coordinates" member is an array of 11 | /// LineString coordinate arrays. 12 | /// 13 | [JsonConverter(typeof(MultiLineStringConverter))] 14 | public class MultiLineString : Geometry 15 | { 16 | #region Public Properties 17 | 18 | /// 19 | /// For type "MultiLineString", the "coordinates" member is an array of 20 | /// LineString coordinate arrays. 21 | /// 22 | [JsonProperty(PropertyName = "coordinates")] 23 | public IEnumerable Coordinates { get; } 24 | 25 | #endregion 26 | 27 | #region Constructors 28 | 29 | /// 30 | /// Creates a new MultiLineString 31 | /// 32 | /// The line strings that make up the object 33 | [JsonConstructor] 34 | public MultiLineString(IEnumerable coordinates, IEnumerable boundingBox = null) : base(GeoJsonType.MultiLineString, coordinates.Any(x => x.IsThreeDimensional()), boundingBox) 35 | { 36 | this.Coordinates = coordinates ?? throw new ArgumentNullException("coordinates"); 37 | } 38 | 39 | #endregion 40 | 41 | #region Public Methods 42 | 43 | public static new MultiLineString FromJson(string json) 44 | { 45 | return JsonConvert.DeserializeObject(json); 46 | } 47 | 48 | public override bool Equals(object obj) 49 | { 50 | if (ReferenceEquals(this, obj)) 51 | { 52 | return true; 53 | } 54 | 55 | if (obj == null || this.GetType() != obj.GetType()) 56 | { 57 | return false; 58 | } 59 | 60 | MultiLineString other = (MultiLineString)obj; 61 | 62 | bool bBoxEqual = true; 63 | 64 | if (this.BoundingBox != null && other.BoundingBox != null) 65 | { 66 | bBoxEqual = this.BoundingBox.SequenceEqual(other.BoundingBox); 67 | } 68 | else 69 | { 70 | bBoxEqual = (this.BoundingBox == null && other.BoundingBox == null); 71 | } 72 | 73 | bool coordinatesEqual = true; 74 | 75 | if (this.Coordinates != null && other.Coordinates != null) 76 | { 77 | coordinatesEqual = this.Coordinates.SequenceEqual(other.Coordinates); 78 | } 79 | else 80 | { 81 | coordinatesEqual = (this.Coordinates == null && other.Coordinates == null); 82 | } 83 | 84 | return this.Type == other.Type && 85 | coordinatesEqual && 86 | bBoxEqual; 87 | } 88 | 89 | public override int GetHashCode() 90 | { 91 | return Hashing.Hash(this.Type, this.Coordinates, this.BoundingBox); 92 | } 93 | 94 | public static bool operator ==(MultiLineString left, MultiLineString right) 95 | { 96 | if (ReferenceEquals(left, right)) 97 | { 98 | return true; 99 | } 100 | 101 | if (right is null || left is null) 102 | { 103 | return false; 104 | } 105 | 106 | return left.Equals(right); 107 | } 108 | 109 | public static bool operator !=(MultiLineString left, MultiLineString right) 110 | { 111 | return !(left == right); 112 | } 113 | 114 | #endregion 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /GeoJSON/MultiPoint.cs: -------------------------------------------------------------------------------- 1 | using BAMCIS.GeoJSON.Serde; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace BAMCIS.GeoJSON 8 | { 9 | /// 10 | /// For type "MultiPoint", the "coordinates" member is an array of positions. 11 | /// 12 | [JsonConverter(typeof(InheritanceBlockerConverter))] 13 | public class MultiPoint : Geometry 14 | { 15 | #region Public Properties 16 | 17 | /// 18 | /// The coordinates of a multipoint are an array of positions 19 | /// 20 | [JsonProperty(PropertyName = "coordinates")] 21 | public IEnumerable Coordinates { get; } 22 | 23 | #endregion 24 | 25 | #region Constructors 26 | 27 | /// 28 | /// Creates a new multipoint object 29 | /// 30 | /// 31 | public MultiPoint(IEnumerable coordinates, IEnumerable boundingBox = null) : base(GeoJsonType.MultiPoint, coordinates.Any(x => x.HasElevation()), boundingBox) 32 | { 33 | this.Coordinates = coordinates ?? throw new ArgumentNullException("coordinates"); 34 | } 35 | 36 | #endregion 37 | 38 | #region Public Methods 39 | 40 | public static new MultiPoint FromJson(string json) 41 | { 42 | return JsonConvert.DeserializeObject(json); 43 | } 44 | 45 | public override bool Equals(object obj) 46 | { 47 | if (ReferenceEquals(this, obj)) 48 | { 49 | return true; 50 | } 51 | 52 | if (obj == null || this.GetType() != obj.GetType()) 53 | { 54 | return false; 55 | } 56 | 57 | MultiPoint other = (MultiPoint)obj; 58 | 59 | bool bBoxEqual = true; 60 | 61 | if (this.BoundingBox != null && other.BoundingBox != null) 62 | { 63 | bBoxEqual = this.BoundingBox.SequenceEqual(other.BoundingBox); 64 | } 65 | else 66 | { 67 | bBoxEqual = (this.BoundingBox == null && other.BoundingBox == null); 68 | } 69 | 70 | bool coordinatesEqual = true; 71 | 72 | if (this.Coordinates != null && other.Coordinates != null) 73 | { 74 | coordinatesEqual = this.Coordinates.SequenceEqual(other.Coordinates); 75 | } 76 | else 77 | { 78 | coordinatesEqual = (this.Coordinates == null && other.Coordinates == null); 79 | } 80 | 81 | return this.Type == other.Type && 82 | coordinatesEqual && 83 | bBoxEqual; 84 | } 85 | 86 | public override int GetHashCode() 87 | { 88 | return Hashing.Hash(this.Type, this.Coordinates, this.BoundingBox); 89 | } 90 | 91 | public static bool operator ==(MultiPoint left, MultiPoint right) 92 | { 93 | if (ReferenceEquals(left, right)) 94 | { 95 | return true; 96 | } 97 | 98 | if (right is null || left is null) 99 | { 100 | return false; 101 | } 102 | 103 | return left.Equals(right); 104 | } 105 | 106 | public static bool operator !=(MultiPoint left, MultiPoint right) 107 | { 108 | return !(left == right); 109 | } 110 | 111 | #endregion 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /GeoJSON/MultiPolygon.cs: -------------------------------------------------------------------------------- 1 | using BAMCIS.GeoJSON.Serde; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace BAMCIS.GeoJSON 8 | { 9 | /// 10 | /// For type "MultiPolygon", the "coordinates" member is an array of 11 | /// Polygon coordinate arrays. 12 | /// 13 | [JsonConverter(typeof(MultiPolygonConverter))] 14 | public class MultiPolygon : Geometry 15 | { 16 | #region Public Properties 17 | 18 | /// 19 | /// The coordinates are an array of polygons. 20 | /// 21 | [JsonProperty(PropertyName = "coordinates")] 22 | public IEnumerable Coordinates { get; } 23 | 24 | #endregion 25 | 26 | #region Constructors 27 | 28 | /// 29 | /// Creates a new MultiPolygon 30 | /// 31 | /// The coordinates that make up the multi polygon 32 | [JsonConstructor] 33 | public MultiPolygon(IEnumerable coordinates, IEnumerable boundingBox = null) : base(GeoJsonType.MultiPolygon, coordinates.Any(x => x.IsThreeDimensional()), boundingBox) 34 | { 35 | this.Coordinates = coordinates ?? throw new ArgumentNullException("coordinates"); 36 | 37 | if (!this.Coordinates.Any()) 38 | { 39 | throw new ArgumentOutOfRangeException("coordinates", "A MultiPolygon must have at least 1 polygon."); 40 | } 41 | } 42 | 43 | #endregion 44 | 45 | #region Public Methods 46 | public static new MultiPolygon FromJson(string json) 47 | { 48 | return JsonConvert.DeserializeObject(json); 49 | } 50 | 51 | 52 | public override bool Equals(object obj) 53 | { 54 | if (ReferenceEquals(this, obj)) 55 | { 56 | return true; 57 | } 58 | 59 | if (obj == null || this.GetType() != obj.GetType()) 60 | { 61 | return false; 62 | } 63 | 64 | MultiPolygon other = (MultiPolygon)obj; 65 | 66 | bool bBoxEqual = true; 67 | 68 | if (this.BoundingBox != null && other.BoundingBox != null) 69 | { 70 | bBoxEqual = this.BoundingBox.SequenceEqual(other.BoundingBox); 71 | } 72 | else 73 | { 74 | bBoxEqual = (this.BoundingBox == null && other.BoundingBox == null); 75 | } 76 | 77 | bool coordinatesEqual = true; 78 | 79 | if (this.Coordinates != null && other.Coordinates != null) 80 | { 81 | coordinatesEqual = this.Coordinates.SequenceEqual(other.Coordinates); 82 | } 83 | else 84 | { 85 | coordinatesEqual = (this.Coordinates == null && other.Coordinates == null); 86 | } 87 | 88 | return this.Type == other.Type && 89 | coordinatesEqual && 90 | bBoxEqual; 91 | } 92 | 93 | public override int GetHashCode() 94 | { 95 | return Hashing.Hash(this.Type, this.Coordinates, this.BoundingBox); 96 | } 97 | 98 | public static bool operator ==(MultiPolygon left, MultiPolygon right) 99 | { 100 | if (ReferenceEquals(left, right)) 101 | { 102 | return true; 103 | } 104 | 105 | if (right is null || left is null) 106 | { 107 | return false; 108 | } 109 | 110 | return left.Equals(right); 111 | } 112 | 113 | public static bool operator !=(MultiPolygon left, MultiPolygon right) 114 | { 115 | return !(left == right); 116 | } 117 | 118 | #endregion 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /GeoJSON/Point.cs: -------------------------------------------------------------------------------- 1 | using BAMCIS.GeoJSON.Serde; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace BAMCIS.GeoJSON 8 | { 9 | /// 10 | /// For type "Point", the "coordinates" member is a single position. 11 | /// 12 | [JsonConverter(typeof(InheritanceBlockerConverter))] 13 | public class Point : Geometry 14 | { 15 | #region Public Properties 16 | 17 | /// 18 | /// For type "Point", the "coordinates" member is a single position. 19 | /// 20 | [JsonProperty(PropertyName = "coordinates")] 21 | public Position Coordinates { get; } 22 | 23 | #endregion 24 | 25 | #region Constructors 26 | 27 | /// 28 | /// Creates a new point with the provided coordinates 29 | /// 30 | /// The position of this point 31 | [JsonConstructor] 32 | public Point(Position coordinates, IEnumerable boundingBox = null) : base(GeoJsonType.Point, coordinates.HasElevation(), boundingBox) 33 | { 34 | this.Coordinates = coordinates ?? throw new ArgumentNullException("coordinates"); 35 | } 36 | 37 | #endregion 38 | 39 | #region Public Methods 40 | 41 | /// 42 | /// Deserializes the json into a Point 43 | /// 44 | /// The json to deserialize 45 | /// A Point object 46 | public static new Point FromJson(string json) 47 | { 48 | return JsonConvert.DeserializeObject(json); 49 | } 50 | 51 | /// 52 | /// Gets the longitude or easting of the point 53 | /// 54 | /// The longitude 55 | public double GetLongitude() 56 | { 57 | return this.Coordinates.Longitude; 58 | } 59 | 60 | /// 61 | /// Gets the latitude or northing of the point 62 | /// 63 | /// The latitude 64 | public double GetLatitude() 65 | { 66 | return this.Coordinates.Latitude; 67 | } 68 | 69 | /// 70 | /// Gets the elevation of the point if it exists 71 | /// in the coordinates. 72 | /// 73 | /// The elevation or if it wasn't set, the returns NaN 74 | public bool TryGetElevation(out double elevation) 75 | { 76 | if (this.Coordinates.HasElevation()) 77 | { 78 | elevation = this.Coordinates.Elevation; 79 | return true; 80 | } 81 | 82 | elevation = double.NaN; 83 | return false; 84 | } 85 | 86 | public override bool Equals(object obj) 87 | { 88 | if (ReferenceEquals(this, obj)) 89 | { 90 | return true; 91 | } 92 | 93 | if (obj == null || this.GetType() != obj.GetType()) 94 | { 95 | return false; 96 | } 97 | 98 | Point other = (Point)obj; 99 | 100 | bool bBoxEqual = true; 101 | 102 | if (this.BoundingBox != null && other.BoundingBox != null) 103 | { 104 | bBoxEqual = this.BoundingBox.SequenceEqual(other.BoundingBox); 105 | } 106 | else 107 | { 108 | bBoxEqual = (this.BoundingBox == null && other.BoundingBox == null); 109 | } 110 | 111 | return this.Type == other.Type && 112 | this.Coordinates == other.Coordinates && 113 | bBoxEqual; 114 | } 115 | 116 | public override int GetHashCode() 117 | { 118 | return Hashing.Hash(this.Type, this.Coordinates, this.BoundingBox); 119 | } 120 | 121 | public static bool operator ==(Point left, Point right) 122 | { 123 | if (ReferenceEquals(left, right)) 124 | { 125 | return true; 126 | } 127 | 128 | if (right is null || left is null) 129 | { 130 | return false; 131 | } 132 | 133 | return left.Equals(right); 134 | } 135 | 136 | public static bool operator !=(Point left, Point right) 137 | { 138 | return !(left == right); 139 | } 140 | 141 | #endregion 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /GeoJSON/Polygon.cs: -------------------------------------------------------------------------------- 1 | using BAMCIS.GeoJSON.Serde; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace BAMCIS.GeoJSON 8 | { 9 | /// 10 | /// A polygon is formed with 1 or more linear rings, which are an enclosed LineString 11 | /// 12 | [JsonConverter(typeof(PolygonConverter))] 13 | public class Polygon : Geometry 14 | { 15 | #region Private Fields 16 | 17 | private IEnumerable _coordinates; 18 | 19 | #endregion 20 | 21 | #region Public Properties 22 | 23 | /// 24 | /// The coordinates are an array of linear ring coordinate arrays. 25 | /// For Polygons with more than one of these rings, the first MUST be 26 | /// the exterior ring, and any others MUST be interior rings.The 27 | /// exterior ring bounds the surface, and the interior rings(if 28 | /// present) bound holes within the surface. 29 | /// 30 | [JsonProperty(PropertyName = "coordinates")] 31 | public IEnumerable Coordinates { 32 | get 33 | { 34 | return this._coordinates; 35 | } 36 | } 37 | 38 | #endregion 39 | 40 | #region Constructors 41 | 42 | /// 43 | /// Creates a new Polygon 44 | /// 45 | /// The linear rings that make up the polygon 46 | [JsonConstructor] 47 | public Polygon(IEnumerable coordinates, IEnumerable boundingBox = null) : base(GeoJsonType.Polygon, coordinates.Any(x => x.IsThreeDimensional()), boundingBox) 48 | { 49 | this._coordinates = coordinates ?? throw new ArgumentNullException("coordinates"); 50 | 51 | if (!this.Coordinates.Any()) 52 | { 53 | throw new ArgumentOutOfRangeException("coordinates", "A polygon must have at least 1 linear ring."); 54 | } 55 | } 56 | 57 | #endregion 58 | 59 | #region Public Methods 60 | 61 | /// 62 | /// Removes the interior linear rings that bound holes within the surface from the polygon's coordinates 63 | /// leaving just 1 linear ring in the coordinates. 64 | /// 65 | /// Returns true if the polygon had more than linear ring and false if there were no linear rings to remove 66 | public bool RemoveInteriorRings() 67 | { 68 | // If there is more than element 69 | if (this._coordinates != null && this._coordinates.Skip(1).Any()) 70 | { 71 | this._coordinates = this._coordinates.Take(1); 72 | return true; 73 | } 74 | else 75 | { 76 | return false; 77 | } 78 | } 79 | 80 | /// 81 | /// Deserializes the json into a Polygon 82 | /// 83 | /// The json to deserialize 84 | /// A Polygon object 85 | public static new Polygon FromJson(string json) 86 | { 87 | return JsonConvert.DeserializeObject(json); 88 | } 89 | 90 | public override bool Equals(object obj) 91 | { 92 | if (ReferenceEquals(this, obj)) 93 | { 94 | return true; 95 | } 96 | 97 | if (obj == null || this.GetType() != obj.GetType()) 98 | { 99 | return false; 100 | } 101 | 102 | Polygon other = (Polygon)obj; 103 | 104 | bool bBoxEqual = true; 105 | 106 | if (this.BoundingBox != null && other.BoundingBox != null) 107 | { 108 | bBoxEqual = this.BoundingBox.SequenceEqual(other.BoundingBox); 109 | } 110 | else 111 | { 112 | bBoxEqual = (this.BoundingBox == null && other.BoundingBox == null); 113 | } 114 | 115 | bool coordinatesEqual = true; 116 | 117 | if (this.Coordinates != null && other.Coordinates != null) 118 | { 119 | coordinatesEqual = this.Coordinates.SequenceEqual(other.Coordinates); 120 | } 121 | else 122 | { 123 | coordinatesEqual = (this.Coordinates == null && other.Coordinates == null); 124 | } 125 | 126 | return this.Type == other.Type && 127 | coordinatesEqual && 128 | bBoxEqual; 129 | } 130 | 131 | public override int GetHashCode() 132 | { 133 | return Hashing.Hash(this.Type, this.Coordinates, this.BoundingBox); 134 | } 135 | 136 | public static bool operator ==(Polygon left, Polygon right) 137 | { 138 | if (ReferenceEquals(left, right)) 139 | { 140 | return true; 141 | } 142 | 143 | if (right is null || left is null) 144 | { 145 | return false; 146 | } 147 | 148 | return left.Equals(right); 149 | } 150 | 151 | public static bool operator !=(Polygon left, Polygon right) 152 | { 153 | return !(left == right); 154 | } 155 | 156 | #endregion 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /GeoJSON/Position.cs: -------------------------------------------------------------------------------- 1 | using BAMCIS.GeoJSON.Serde; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace BAMCIS.GeoJSON 7 | { 8 | /// 9 | /// A GeoJSON position consisting of a longitude, latitude, and optional elevation 10 | /// 11 | [JsonConverter(typeof(PositionConverter))] 12 | public class Position : IEquatable, IEqualityComparer 13 | { 14 | #region Public Properties 15 | 16 | /// 17 | /// The position's longitude or easting, valid values of 18 | /// -180 to 180. Set IgnorePositionValidation in the GeoJsonConfig 19 | /// class to ignore 20 | /// 21 | [JsonProperty(PropertyName = "longitude")] 22 | public double Longitude { get; } 23 | 24 | /// 25 | /// The position's latitude or northing 26 | /// 27 | [JsonProperty(PropertyName = "latitude")] 28 | public double Latitude { get; } 29 | 30 | /// 31 | /// The positions elevation. This will be NaN 32 | /// if an elevation is not provided for the position 33 | /// 34 | [JsonProperty(PropertyName = "elevation")] 35 | public double Elevation { get; } 36 | 37 | #endregion 38 | 39 | #region Constructors 40 | 41 | /// 42 | /// Creates a position with a longitude and latitude 43 | /// 44 | /// The position's longitude 45 | /// The position's latitude 46 | public Position(double longitude, double latitude) : this(longitude, latitude, double.NaN) 47 | { 48 | } 49 | 50 | /// 51 | /// Creates a position with a longitude, latitude, and elevation 52 | /// 53 | /// The position's longitude 54 | /// The position's latitude 55 | /// The position's elevation 56 | [JsonConstructor] 57 | public Position(double longitude, double latitude, double elevation) 58 | { 59 | if (double.IsInfinity(latitude) || double.IsNaN(latitude)) 60 | { 61 | throw new ArgumentOutOfRangeException("latitude", "The latitude cannot be NaN or infinity."); 62 | } 63 | 64 | if (double.IsInfinity(longitude) || double.IsNaN(longitude)) 65 | { 66 | throw new ArgumentOutOfRangeException("longitude", "The longitude cannot be NaN or infinity."); 67 | } 68 | 69 | if (!GeoJsonConfig.IgnoreLongitudeValidation) 70 | { 71 | if (longitude < -180 || longitude > 180) 72 | { 73 | throw new ArgumentOutOfRangeException("longitude", "Longitude must be between -180 and 180 degrees, inclusive."); 74 | } 75 | } 76 | 77 | if (!GeoJsonConfig.IgnoreLatitudeValidation) 78 | { 79 | if (latitude < -90 || latitude > 90) 80 | { 81 | throw new ArgumentOutOfRangeException("latitude", "Latitude must be between -90 and 90 degrees, inclusive."); 82 | } 83 | } 84 | 85 | if (double.IsInfinity(elevation)) 86 | { 87 | throw new ArgumentOutOfRangeException("elevation", "The elevation cannot be infinity."); 88 | } 89 | 90 | this.Latitude = latitude; 91 | this.Longitude = longitude; 92 | this.Elevation = elevation; 93 | } 94 | 95 | #endregion 96 | 97 | #region Public Methods 98 | 99 | /// 100 | /// Deserializes the provided json into a Position object 101 | /// 102 | /// The json to deserialize 103 | /// A Position object 104 | public static Position FromJson(string json) 105 | { 106 | return JsonConvert.DeserializeObject(json); 107 | } 108 | 109 | /// 110 | /// Determines if an elevation has been provided for a position. 111 | /// 112 | /// Whether the position has a valid elevation value 113 | public bool HasElevation() 114 | { 115 | return !double.IsNaN(this.Elevation); 116 | } 117 | 118 | public override bool Equals(object obj) 119 | { 120 | if (ReferenceEquals(this, obj)) 121 | { 122 | return true; 123 | } 124 | 125 | if (obj == null || this.GetType() != obj.GetType()) 126 | { 127 | return false; 128 | } 129 | 130 | Position other = (Position)obj; 131 | 132 | bool temp = this.Latitude == other.Latitude && 133 | this.Longitude == other.Longitude; 134 | 135 | if (!double.IsNaN(this.Elevation) || !double.IsNaN(other.Elevation)) 136 | { 137 | temp = temp && (this.Elevation == other.Elevation); 138 | } 139 | 140 | return temp; 141 | } 142 | 143 | public bool Equals(Position other) 144 | { 145 | if (ReferenceEquals(this, other)) 146 | { 147 | return true; 148 | } 149 | else 150 | { 151 | bool temp = this.Latitude == other.Latitude && 152 | this.Longitude == other.Longitude; 153 | 154 | if (!double.IsNaN(this.Elevation) || !double.IsNaN(other.Elevation)) 155 | { 156 | temp = temp && (this.Elevation == other.Elevation); 157 | } 158 | 159 | return temp; 160 | } 161 | } 162 | 163 | public bool Equals(Position left, Position right) 164 | { 165 | return left == right; 166 | } 167 | 168 | public override int GetHashCode() 169 | { 170 | return Hashing.Hash(this.Longitude, this.Latitude, this.Elevation); 171 | } 172 | 173 | public override string ToString() 174 | { 175 | return $"[{this.Longitude},{this.Latitude}{(!double.IsNaN(this.Elevation) ? $",{this.Elevation}" : "")}]"; 176 | } 177 | 178 | public static bool operator ==(Position left, Position right) 179 | { 180 | if (ReferenceEquals(left, right)) 181 | { 182 | return true; 183 | } 184 | 185 | if (right is null || left is null) 186 | { 187 | return false; 188 | } 189 | 190 | return left.Equals(right); 191 | } 192 | 193 | public static bool operator !=(Position left, Position right) 194 | { 195 | return !(left == right); 196 | } 197 | 198 | public int GetHashCode(Position other) 199 | { 200 | return other.GetHashCode(); 201 | } 202 | 203 | #endregion 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /GeoJSON/Serde/FeatureIdConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace BAMCIS.GeoJSON.Serde 5 | { 6 | /// 7 | /// Provides conversion of a FeatureId object to either a string or integer as the value 8 | /// for a feature's "id" property 9 | /// 10 | public class FeatureIdConverter : JsonConverter 11 | { 12 | #region Public Properties 13 | 14 | public override bool CanRead => true; 15 | 16 | public override bool CanWrite => true; 17 | 18 | #endregion 19 | 20 | #region Public Methods 21 | 22 | public override bool CanConvert(Type objectType) 23 | { 24 | return objectType == typeof(Position); 25 | } 26 | 27 | /// 28 | /// Reads an array of doubles and creates a Position object 29 | /// 30 | /// 31 | /// 32 | /// 33 | /// 34 | /// 35 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 36 | { 37 | switch (reader.TokenType) 38 | { 39 | case JsonToken.String: 40 | { 41 | return new FeatureId(reader.Value as string); 42 | } 43 | case JsonToken.Integer: 44 | { 45 | return new FeatureId((Int64)reader.Value); 46 | } 47 | case JsonToken.Null: 48 | { 49 | return null; 50 | } 51 | default: 52 | { 53 | throw new FormatException("The feature id was provided in an unexpected format."); 54 | } 55 | } 56 | } 57 | 58 | /// 59 | /// Takes the position object and converts it to an array of doubles 60 | /// 61 | /// 62 | /// 63 | /// 64 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 65 | { 66 | FeatureId id = (FeatureId)value; 67 | 68 | if (id.GetOriginalType() == typeof(string)) 69 | { 70 | writer.WriteValue(id.Value); 71 | } 72 | else 73 | { 74 | writer.WriteValue(Int64.Parse(id.Value)); 75 | } 76 | } 77 | 78 | #endregion 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /GeoJSON/Serde/GeoJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | 5 | namespace BAMCIS.GeoJSON.Serde 6 | { 7 | /// 8 | /// Used to serialize and deserialize GeoJSON objects 9 | /// 10 | public class GeoJsonConverter : JsonConverter 11 | { 12 | #region Public Properties 13 | 14 | /// 15 | /// Make sure this converter is used to deserialize 16 | /// 17 | public override bool CanRead => true; 18 | 19 | /// 20 | /// Use the default serializer 21 | /// 22 | public override bool CanWrite => false; 23 | 24 | #endregion 25 | 26 | /// 27 | /// Object of GeoJson type can be converted with this converter 28 | /// 29 | /// The object type 30 | /// Whether the object can be converted to GeoJson 31 | public override bool CanConvert(Type objectType) 32 | { 33 | return objectType == typeof(GeoJson); 34 | } 35 | 36 | /// 37 | /// Reads the json and converts to appropriate GeoJson class using the 'type' field as an indicator 38 | /// to which object it should be deserialized back to 39 | /// 40 | /// 41 | /// 42 | /// 43 | /// 44 | /// 45 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 46 | { 47 | // Allow an abstract GeoJson to be null in deserialization 48 | if (reader.TokenType == JsonToken.Null) 49 | { 50 | return null; 51 | } 52 | 53 | JObject token = JObject.Load(reader); 54 | 55 | if (!token.TryGetValue("type", StringComparison.OrdinalIgnoreCase, out JToken TypeToken)) 56 | { 57 | throw new JsonReaderException("Invalid geojson object, does not have 'type' field."); 58 | } 59 | 60 | Type actualType = GeoJson.GetType(TypeToken.ToObject(serializer)); 61 | 62 | if (existingValue == null || existingValue.GetType() != actualType) 63 | { 64 | return (GeoJson)token.ToObject(actualType, serializer); 65 | } 66 | else 67 | { 68 | using (JsonReader derivedTypeReader = token.CreateReader()) 69 | { 70 | serializer.Populate(derivedTypeReader, existingValue); 71 | } 72 | 73 | return existingValue; 74 | } 75 | } 76 | 77 | /// 78 | /// Use the default serializer 79 | /// 80 | /// 81 | /// 82 | /// 83 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 84 | { 85 | throw new NotImplementedException(); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /GeoJSON/Serde/GeometryConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | 5 | namespace BAMCIS.GeoJSON.Serde 6 | { 7 | /// 8 | /// Converts Geometry objects 9 | /// 10 | public class GeometryConverter : JsonConverter 11 | { 12 | 13 | #region Public Properties 14 | 15 | /// 16 | /// Make sure this converter is used to deserialize 17 | /// 18 | public override bool CanRead => true; 19 | 20 | /// 21 | /// Use the default serializer 22 | /// 23 | public override bool CanWrite => false; 24 | 25 | #endregion 26 | 27 | /// 28 | /// Object of GeoJson type can be converted with this converter 29 | /// 30 | /// 31 | /// 32 | public override bool CanConvert(Type objectType) 33 | { 34 | return objectType == typeof(Geometry); 35 | } 36 | 37 | /// 38 | /// Reads the json and converts to appropriate Geometry class using the 'type' field as an indicator 39 | /// to which object it should be deserialized back to 40 | /// 41 | /// 42 | /// 43 | /// 44 | /// 45 | /// 46 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 47 | { 48 | // Geometry can be null in a feature 49 | if (reader.TokenType == JsonToken.Null) 50 | { 51 | return null; 52 | } 53 | 54 | JObject token = JObject.Load(reader); 55 | 56 | if (!token.TryGetValue("type", StringComparison.OrdinalIgnoreCase, out JToken TypeToken)) 57 | { 58 | throw new JsonReaderException("Invalid geojson geometry object, does not have 'type' field."); 59 | } 60 | 61 | Type actualType = Geometry.GetType(TypeToken.ToObject(serializer)); 62 | 63 | if (existingValue == null || existingValue.GetType() != actualType) 64 | { 65 | return token.ToObject(actualType, serializer); 66 | } 67 | else 68 | { 69 | using (JsonReader DerivedTypeReader = token.CreateReader()) 70 | { 71 | serializer.Populate(DerivedTypeReader, existingValue); 72 | } 73 | 74 | return existingValue; 75 | } 76 | } 77 | 78 | /// 79 | /// Use the default serializer 80 | /// 81 | /// 82 | /// 83 | /// 84 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 85 | { 86 | throw new NotImplementedException(); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /GeoJSON/Serde/InheritanceBlockerConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | 4 | namespace BAMCIS.GeoJSON.Serde 5 | { 6 | /// 7 | /// This converter is just used to block the base Geometry converter 8 | /// so that any inherited classes use the default serde when the 9 | /// GeometryConverter calls ToObject() on them 10 | /// 11 | public class InheritanceBlockerConverter : JsonConverter 12 | { 13 | #region Public Properties 14 | 15 | public override bool CanRead => false; 16 | 17 | public override bool CanWrite => false; 18 | 19 | #endregion 20 | 21 | #region Public Methods 22 | 23 | public override bool CanConvert(Type objectType) 24 | { 25 | return true; 26 | } 27 | 28 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 29 | { 30 | throw new NotImplementedException(); 31 | } 32 | 33 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 34 | { 35 | throw new NotImplementedException(); 36 | } 37 | 38 | #endregion 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /GeoJSON/Serde/MultiLineStringConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace BAMCIS.GeoJSON.Serde 8 | { 9 | /// 10 | /// Converts a MultiLineString GeoJSON object to and from JSON 11 | /// 12 | public class MultiLineStringConverter : JsonConverter 13 | { 14 | #region Public Properties 15 | 16 | public override bool CanRead => true; 17 | 18 | public override bool CanWrite => true; 19 | 20 | #endregion 21 | 22 | #region Public Methods 23 | 24 | public override bool CanConvert(Type objectType) 25 | { 26 | return objectType == typeof(MultiLineString); 27 | } 28 | 29 | /// 30 | /// This takes the array of arrays and recasts them back to line string objects 31 | /// 32 | /// 33 | /// 34 | /// 35 | /// 36 | /// 37 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 38 | { 39 | JObject token = JObject.Load(reader); 40 | 41 | IEnumerable> coordinates = token.GetValue("coordinates", StringComparison.OrdinalIgnoreCase).ToObject>>(serializer); 42 | 43 | return new MultiLineString(coordinates.Select(x => new LineString(x))); 44 | } 45 | 46 | /// 47 | /// This flattens the coordinates property into an array of arrays 48 | /// 49 | /// 50 | /// 51 | /// 52 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 53 | { 54 | MultiLineString mls = (MultiLineString)value; 55 | 56 | JToken.FromObject(new 57 | { 58 | type = mls.Type, 59 | coordinates = mls.Coordinates.Select(x => x.Coordinates) 60 | }).WriteTo(writer); 61 | } 62 | 63 | #endregion 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /GeoJSON/Serde/MultiPolygonConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace BAMCIS.GeoJSON.Serde 8 | { 9 | /// 10 | /// Converts a MultiLineString GeoJSON object to and from JSON 11 | /// 12 | public class MultiPolygonConverter : JsonConverter 13 | { 14 | #region Public Properties 15 | 16 | public override bool CanRead => true; 17 | 18 | public override bool CanWrite => true; 19 | 20 | #endregion 21 | 22 | #region Public Methods 23 | 24 | public override bool CanConvert(Type objectType) 25 | { 26 | return objectType == typeof(MultiPolygon); 27 | } 28 | 29 | /// 30 | /// This takes the array of arrays and recasts them back to line string objects 31 | /// 32 | /// 33 | /// 34 | /// 35 | /// 36 | /// 37 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 38 | { 39 | JObject token = JObject.Load(reader); 40 | 41 | IEnumerable>> coordinates = token.GetValue("coordinates", StringComparison.OrdinalIgnoreCase).ToObject>>>(serializer); 42 | 43 | // Take this array of arrays of arrays and create linear rings 44 | // and use those to create create polygons 45 | return new MultiPolygon( 46 | coordinates 47 | .Select(x => new Polygon( 48 | x.Select(y => new LinearRing(y)) 49 | ) 50 | ) 51 | ); 52 | } 53 | 54 | /// 55 | /// This flattens the coordinates property into an array of arrays of arrays 56 | /// 57 | /// 58 | /// 59 | /// 60 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 61 | { 62 | MultiPolygon mp = (MultiPolygon)value; 63 | 64 | JToken.FromObject(new 65 | { 66 | type = mp.Type, 67 | coordinates = mp.Coordinates.Select(x => x.Coordinates.Select(y => y.Coordinates)) 68 | }).WriteTo(writer); 69 | } 70 | 71 | #endregion 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /GeoJSON/Serde/PolygonConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace BAMCIS.GeoJSON.Serde 8 | { 9 | /// 10 | /// Converts Polygon geometry objects 11 | /// 12 | public class PolygonConverter : JsonConverter 13 | { 14 | #region Public Properties 15 | 16 | public override bool CanRead => true; 17 | 18 | public override bool CanWrite => true; 19 | 20 | #endregion 21 | 22 | #region Public Methods 23 | 24 | public override bool CanConvert(Type objectType) 25 | { 26 | return objectType == typeof(Polygon); 27 | } 28 | 29 | /// 30 | /// This takes the array of arrays and recasts them back to linear ring objects 31 | /// 32 | /// 33 | /// 34 | /// 35 | /// 36 | /// 37 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 38 | { 39 | JObject token = JObject.Load(reader); 40 | 41 | IEnumerable> coordinates = token.GetValue("coordinates", StringComparison.OrdinalIgnoreCase).ToObject>>(serializer); 42 | 43 | // Take this array of arrays of arrays and create linear rings 44 | // and use those to create create polygons 45 | return new Polygon( 46 | coordinates.Select(x => new LinearRing(x)) 47 | ); 48 | } 49 | 50 | /// 51 | /// This flattens the coordinates property into an array of arrays 52 | /// 53 | /// 54 | /// 55 | /// 56 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 57 | { 58 | Polygon poly = (Polygon)value; 59 | 60 | JToken.FromObject(new 61 | { 62 | type = poly.Type, 63 | coordinates = poly.Coordinates.Select(x => x.Coordinates) 64 | }).WriteTo(writer); 65 | } 66 | 67 | #endregion 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /GeoJSON/Serde/PositionConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Linq; 5 | 6 | namespace BAMCIS.GeoJSON.Serde 7 | { 8 | /// 9 | /// Converts a position to an array of coordinates and back 10 | /// 11 | public class PositionConverter : JsonConverter 12 | { 13 | #region Public Properties 14 | 15 | public override bool CanRead => true; 16 | 17 | public override bool CanWrite => true; 18 | 19 | #endregion 20 | 21 | #region Public Methods 22 | 23 | public override bool CanConvert(Type objectType) 24 | { 25 | return objectType == typeof(Position); 26 | } 27 | 28 | /// 29 | /// Reads an array of doubles and creates a Position object 30 | /// 31 | /// 32 | /// 33 | /// 34 | /// 35 | /// 36 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 37 | { 38 | // This is an array of doubles 39 | JArray token = JArray.Load(reader); 40 | 41 | double longitude = token.ElementAt(0).ToObject(serializer); 42 | double latitude = token.ElementAt(1).ToObject(serializer); 43 | double elevation = double.NaN; 44 | 45 | if (token.Count == 3) 46 | { 47 | elevation = token.ElementAt(2).ToObject(serializer); 48 | } 49 | 50 | return new Position(longitude, latitude, elevation); 51 | } 52 | 53 | /// 54 | /// Takes the position object and converts it to an array of doubles 55 | /// 56 | /// 57 | /// 58 | /// 59 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 60 | { 61 | Position pos = (Position)value; 62 | 63 | if (pos.HasElevation()) 64 | { 65 | JToken.FromObject(new double[3] { pos.Longitude, pos.Latitude, pos.Elevation }).WriteTo(writer); 66 | } 67 | else 68 | { 69 | JToken.FromObject(new double[2] { pos.Longitude, pos.Latitude }).WriteTo(writer); 70 | } 71 | } 72 | 73 | #endregion 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /GeoJSON/Wkb/EndianAwareBinaryReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace BAMCIS.GeoJSON.Wkb 6 | { 7 | /// 8 | /// An extension to the binary reader class that allows you to 9 | /// specify the endianess of the binary data you are reading 10 | /// 11 | public class EndianAwareBinaryReader : BinaryReader 12 | { 13 | #region Private Fields 14 | 15 | private Endianness _endianness = Endianness.LITTLE; 16 | 17 | #endregion 18 | 19 | #region Constructors 20 | 21 | public EndianAwareBinaryReader(Stream input) : base(input) 22 | { 23 | } 24 | 25 | public EndianAwareBinaryReader(Stream input, Encoding encoding) : base(input, encoding) 26 | { 27 | } 28 | 29 | public EndianAwareBinaryReader(Stream input, Encoding encoding, bool leaveOpen) : base(input, encoding, leaveOpen) 30 | { 31 | } 32 | 33 | public EndianAwareBinaryReader(Stream input, Endianness endianness) : base(input) 34 | { 35 | _endianness = endianness; 36 | } 37 | 38 | public EndianAwareBinaryReader(Stream input, Encoding encoding, Endianness endianness) : base(input, encoding) 39 | { 40 | _endianness = endianness; 41 | } 42 | 43 | public EndianAwareBinaryReader(Stream input, Encoding encoding, bool leaveOpen, Endianness endianness) : base(input, encoding, leaveOpen) 44 | { 45 | _endianness = endianness; 46 | } 47 | 48 | #endregion 49 | 50 | #region Public Override Methods 51 | 52 | public override short ReadInt16() => ReadInt16(_endianness); 53 | 54 | public override int ReadInt32() => ReadInt32(_endianness); 55 | 56 | public override long ReadInt64() => ReadInt64(_endianness); 57 | 58 | public override ushort ReadUInt16() => ReadUInt16(_endianness); 59 | 60 | public override uint ReadUInt32() => ReadUInt32(_endianness); 61 | 62 | public override ulong ReadUInt64() => ReadUInt64(_endianness); 63 | 64 | public override double ReadDouble() => ReadDouble(_endianness); 65 | 66 | public override float ReadSingle() => ReadSingle(_endianness); 67 | 68 | public override bool ReadBoolean() => ReadBoolean(_endianness); 69 | 70 | public override char ReadChar() => ReadChar(_endianness); 71 | 72 | #endregion 73 | 74 | #region Public Methods 75 | 76 | public void SetEndianness(Endianness endianness) 77 | { 78 | this._endianness = endianness; 79 | } 80 | 81 | public short ReadInt16(Endianness endianness) => BitConverter.ToInt16(ReadForEndianness(sizeof(short), endianness), 0); 82 | 83 | public int ReadInt32(Endianness endianness) => BitConverter.ToInt32(ReadForEndianness(sizeof(int), endianness), 0); 84 | 85 | public long ReadInt64(Endianness endianness) => BitConverter.ToInt64(ReadForEndianness(sizeof(long), endianness), 0); 86 | 87 | public ushort ReadUInt16(Endianness endianness) => BitConverter.ToUInt16(ReadForEndianness(sizeof(ushort), endianness), 0); 88 | 89 | public uint ReadUInt32(Endianness endianness) => BitConverter.ToUInt32(ReadForEndianness(sizeof(uint), endianness), 0); 90 | 91 | public ulong ReadUInt64(Endianness endianness) => BitConverter.ToUInt64(ReadForEndianness(sizeof(ulong), endianness), 0); 92 | 93 | public float ReadSingle(Endianness endianness) => BitConverter.ToSingle(ReadForEndianness(sizeof(float), endianness), 0); 94 | 95 | public double ReadDouble(Endianness endianness) 96 | { 97 | byte[] temp = ReadForEndianness(sizeof(double), endianness); 98 | 99 | try 100 | { 101 | 102 | return BitConverter.ToDouble(temp, 0); 103 | } 104 | catch (Exception e) 105 | { 106 | return 0; 107 | } 108 | } 109 | 110 | public char ReadChar(Endianness endianness) => BitConverter.ToChar(ReadForEndianness(sizeof(char), endianness), 0); 111 | 112 | public bool ReadBoolean(Endianness endianness) => BitConverter.ToBoolean(ReadForEndianness(sizeof(bool), endianness), 0); 113 | 114 | #endregion 115 | 116 | #region Private Methods 117 | 118 | private byte[] ReadForEndianness(int bytesToRead, Endianness endianness) 119 | { 120 | try 121 | { 122 | byte[] bytesRead = this.ReadBytes(bytesToRead); 123 | 124 | if ((endianness == Endianness.LITTLE && !BitConverter.IsLittleEndian) 125 | || (endianness == Endianness.BIG && BitConverter.IsLittleEndian)) 126 | { 127 | Array.Reverse(bytesRead); 128 | } 129 | 130 | return bytesRead; 131 | } 132 | catch (Exception e) 133 | { 134 | return null; 135 | } 136 | } 137 | 138 | #endregion 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /GeoJSON/Wkb/EndianAwareBinaryWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace BAMCIS.GeoJSON.Wkb 8 | { 9 | public class EndianAwareBinaryWriter : BinaryWriter 10 | { 11 | #region Private Fields 12 | 13 | private readonly Endianness _endianness = Endianness.LITTLE; 14 | 15 | #endregion 16 | 17 | #region Constructors 18 | 19 | public EndianAwareBinaryWriter(Stream input) : base(input) 20 | { 21 | } 22 | 23 | public EndianAwareBinaryWriter(Stream input, Encoding encoding) : base(input, encoding) 24 | { 25 | } 26 | 27 | public EndianAwareBinaryWriter(Stream input, Encoding encoding, bool leaveOpen) : base(input, encoding, leaveOpen) 28 | { 29 | } 30 | 31 | public EndianAwareBinaryWriter(Stream input, Endianness endianness) : base(input) 32 | { 33 | _endianness = endianness; 34 | } 35 | 36 | public EndianAwareBinaryWriter(Stream input, Encoding encoding, Endianness endianness) : base(input, encoding) 37 | { 38 | _endianness = endianness; 39 | } 40 | 41 | public EndianAwareBinaryWriter(Stream input, Encoding encoding, bool leaveOpen, Endianness endianness) : base(input, encoding, leaveOpen) 42 | { 43 | _endianness = endianness; 44 | } 45 | 46 | #endregion 47 | 48 | #region Public Override Methods 49 | 50 | public override void Write(byte value) => Write(value, this._endianness); 51 | 52 | public override void Write(byte[] buffer) => Write(buffer, this._endianness); 53 | 54 | public override void Write(char ch) => Write(ch, this._endianness); 55 | 56 | public override void Write(double value) => Write(value, this._endianness); 57 | 58 | public override void Write(float value) => Write(value, this._endianness); 59 | 60 | public override void Write(int value) => Write(value, this._endianness); 61 | 62 | public override void Write(long value) => Write(value, this._endianness); 63 | 64 | public override void Write(short value) => Write(value, this._endianness); 65 | 66 | public override void Write(uint value) => Write(value, this._endianness); 67 | 68 | public override void Write(ushort value) => Write(value, this._endianness); 69 | 70 | public override void Write(ulong value) => Write(value, this._endianness); 71 | 72 | public override void Write(sbyte value) => Write(value, this._endianness); 73 | 74 | public override void Write(bool value) => Write(value, this._endianness); 75 | 76 | public override void Write(char[] chars) => Write(chars, this._endianness); 77 | #endregion 78 | 79 | #region Public Methods 80 | 81 | public void Write(byte value, Endianness endianness) => this.WriteForEndianness(new byte[1] { value }, endianness); 82 | 83 | public void Write(byte[] value, Endianness endianness) => this.WriteForEndianness(value, endianness); 84 | 85 | public void Write(short value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); 86 | 87 | public void Write(int value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); 88 | 89 | public void Write(long value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); 90 | 91 | public void Write(float value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); 92 | 93 | public void Write(double value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); 94 | 95 | public void Write(char value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); 96 | 97 | public void Write(char[] chars, Endianness endianness) => this.WriteForEndianness(chars.Select(x => (byte)x).ToArray(), endianness); 98 | 99 | public void Write(UInt16 value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); 100 | 101 | public void Write(UInt32 value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); 102 | 103 | public void Write(UInt64 value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); 104 | 105 | public void Write(bool value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); 106 | 107 | public void Write(sbyte value, Endianness endianness) => this.WriteForEndianness(BitConverter.GetBytes(value), endianness); 108 | 109 | public void WriteEndianness() => WriteEndianness(this._endianness); 110 | 111 | public void WriteEndianness(Endianness endianness) => this.Write((byte)endianness); 112 | 113 | #endregion 114 | 115 | #region Private Methods 116 | 117 | private EndianAwareBinaryWriter WriteForEndianness(byte[] bytesToWrite, Endianness endianness) 118 | { 119 | if ((endianness == Endianness.LITTLE && !BitConverter.IsLittleEndian) 120 | || (endianness == Endianness.BIG && BitConverter.IsLittleEndian)) 121 | { 122 | Array.Reverse(bytesToWrite); 123 | } 124 | 125 | this.BaseStream.Write(bytesToWrite, 0, bytesToWrite.Length); 126 | 127 | return this; 128 | } 129 | 130 | #endregion 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /GeoJSON/Wkb/Endianness.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace BAMCIS.GeoJSON.Wkb 6 | { 7 | public enum Endianness : byte 8 | { 9 | BIG = 0x00, 10 | LITTLE = 0x01 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /GeoJSON/Wkb/WkbConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace BAMCIS.GeoJSON.Wkb 8 | { 9 | public static class WkbConverter 10 | { 11 | #region Public Methods 12 | 13 | /// 14 | /// Returns the geometry of the specified type from WKB. 15 | /// 16 | /// The geometry type, i.e. Point, LineString, etc 17 | /// 18 | /// 19 | public static T FromBinary(byte[] bytes) where T : Geometry 20 | { 21 | return (T)FromBinary(bytes); 22 | } 23 | 24 | /// 25 | /// Returns the generic Geometry object from the WKB, but can be 26 | /// casted to the specific underlying geometry object like Point, LineString, 27 | /// Polygon, etc. 28 | /// 29 | /// 30 | /// 31 | public static Geometry FromBinary(byte[] bytes) 32 | { 33 | using (MemoryStream stream = new MemoryStream(bytes)) 34 | { 35 | using (EndianAwareBinaryReader reader = new EndianAwareBinaryReader(stream)) 36 | { 37 | return FromBinary(reader); 38 | } 39 | } 40 | } 41 | 42 | /// 43 | /// Converts a geometry object to WKB based on what it's real underlying 44 | /// type is, like Point or LineString. 45 | /// 46 | /// The geometry to serialize 47 | /// The endianness to use during serialization, defaults to LITTLE endian. 48 | /// 49 | public static byte[] ToBinary(Geometry geo, Endianness endianness = Endianness.LITTLE) 50 | { 51 | using (MemoryStream stream = new MemoryStream()) 52 | { 53 | using (EndianAwareBinaryWriter writer = new EndianAwareBinaryWriter(stream, endianness)) 54 | { 55 | ToBinary(writer, geo); 56 | 57 | return stream.ToArray(); 58 | } 59 | } 60 | } 61 | 62 | #endregion 63 | 64 | #region Private Methods 65 | 66 | #region From Binary Methods 67 | 68 | private static Geometry FromBinary(EndianAwareBinaryReader reader) 69 | { 70 | Endianness endianness = (Endianness)reader.ReadByte(); 71 | reader.SetEndianness(endianness); 72 | 73 | WkbType type = (WkbType)reader.ReadUInt32(); 74 | 75 | switch (type) 76 | { 77 | case WkbType.Geometry: 78 | { 79 | return FromBinary(reader); 80 | } 81 | case WkbType.Point: 82 | { 83 | return PointFrom(reader); 84 | } 85 | case WkbType.LineString: 86 | { 87 | return LineStringFrom(reader); 88 | } 89 | case WkbType.Polygon: 90 | { 91 | return PolygonFrom(reader); 92 | } 93 | case WkbType.MultiPoint: 94 | { 95 | return MultiPointFrom(reader); 96 | } 97 | case WkbType.MultiLineString: 98 | { 99 | return MultiLineStringFrom(reader); 100 | } 101 | case WkbType.MultiPolygon: 102 | { 103 | return MultiPolygonFrom(reader); 104 | } 105 | case WkbType.GeometryCollection: 106 | { 107 | return GeometryCollectionFrom(reader); 108 | } 109 | default: 110 | { 111 | throw new NotSupportedException($"Unsupported WKB type {type.ToString()}."); 112 | } 113 | } 114 | } 115 | 116 | private static Point PointFrom(EndianAwareBinaryReader reader) 117 | { 118 | return new Point(new Position(reader.ReadDouble(), reader.ReadDouble())); 119 | } 120 | 121 | private static LineString LineStringFrom(EndianAwareBinaryReader reader) 122 | { 123 | UInt32 amount = reader.ReadUInt32(); 124 | List coordinates = new List(); 125 | 126 | for (int i = 0; i < amount; i++) 127 | { 128 | coordinates.Add(new Position(reader.ReadDouble(), reader.ReadDouble())); 129 | } 130 | 131 | return new LineString(coordinates); 132 | } 133 | 134 | private static Polygon PolygonFrom(EndianAwareBinaryReader reader) 135 | { 136 | int ringQuantity = reader.ReadInt32(); 137 | List rings = new List(ringQuantity); 138 | 139 | for (int i = 0; i < ringQuantity; i++) 140 | { 141 | int numberOfPositions = reader.ReadInt32(); 142 | List coordinates = new List(numberOfPositions); 143 | for (int j = 0; j < numberOfPositions; j++) 144 | { 145 | coordinates.Add(new Position(reader.ReadDouble(), reader.ReadDouble())); 146 | } 147 | 148 | rings.Add(new LinearRing(coordinates)); 149 | } 150 | 151 | return new Polygon(rings); 152 | } 153 | 154 | private static MultiPoint MultiPointFrom(EndianAwareBinaryReader reader) 155 | { 156 | List coordinates = new List(); 157 | int numberOfGroups = reader.ReadInt32(); 158 | 159 | for (int i = 0; i < numberOfGroups; i++) 160 | { 161 | Endianness endianness = (Endianness)reader.ReadByte(); 162 | 163 | UInt32 numberOfPositions = reader.ReadUInt32((Endianness)endianness); 164 | 165 | for (int j = 0; j < numberOfPositions; j++) 166 | { 167 | coordinates.Add(new Position(reader.ReadDouble(), reader.ReadDouble())); 168 | } 169 | } 170 | 171 | return new MultiPoint(coordinates); 172 | } 173 | 174 | private static MultiLineString MultiLineStringFrom(EndianAwareBinaryReader reader) 175 | { 176 | List lineStrings = new List(); 177 | int quantityOfLineStrings = reader.ReadInt32(); 178 | 179 | for (int i = 0; i < quantityOfLineStrings; i++) 180 | { 181 | /* 182 | Endianness endianness = (Endianness)reader.ReadByte(); 183 | WkbType type = (WkbType)reader.ReadUInt32(endianness); 184 | lineStrings.Add(LineStringFrom(reader)); 185 | */ 186 | 187 | lineStrings.Add((LineString)FromBinary(reader)); 188 | } 189 | 190 | return new MultiLineString(lineStrings); 191 | } 192 | 193 | private static MultiPolygon MultiPolygonFrom(EndianAwareBinaryReader reader) 194 | { 195 | List polygons = new List(); 196 | int quantityOfPolygons = reader.ReadInt32(); 197 | 198 | for (int i = 0; i < quantityOfPolygons; i++) 199 | { 200 | /* 201 | Endianness endianness = (Endianness)reader.ReadByte(); 202 | WkbType type = (WkbType)reader.ReadUInt32(endianness); 203 | polygons.Add(PolygonFrom(reader)); 204 | */ 205 | 206 | polygons.Add((Polygon)FromBinary(reader)); 207 | } 208 | 209 | return new MultiPolygon(polygons); 210 | } 211 | 212 | private static GeometryCollection GeometryCollectionFrom(EndianAwareBinaryReader reader) 213 | { 214 | List geometries = new List(); 215 | int quantity = reader.ReadInt32(); 216 | 217 | for (int i = 0; i < quantity; i++) 218 | { 219 | geometries.Add(FromBinary(reader)); 220 | } 221 | 222 | return new GeometryCollection(geometries); 223 | } 224 | 225 | #endregion 226 | 227 | #region To Binary Methods 228 | 229 | private static void ToBinary(EndianAwareBinaryWriter writer, Point point) 230 | { 231 | writer.WriteEndianness(); 232 | writer.Write((UInt32)WkbType.Point); 233 | WritePosition(writer, point.Coordinates); 234 | } 235 | 236 | private static void ToBinary(EndianAwareBinaryWriter writer, LineString lineString) 237 | { 238 | writer.WriteEndianness(); 239 | writer.Write((UInt32)WkbType.LineString); 240 | writer.Write(lineString.Coordinates.Count()); 241 | 242 | foreach (Position pos in lineString.Coordinates) 243 | { 244 | WritePosition(writer, pos); 245 | } 246 | } 247 | 248 | private static void ToBinary(EndianAwareBinaryWriter writer, Polygon polygon) 249 | { 250 | writer.WriteEndianness(); 251 | writer.Write((UInt32)WkbType.Polygon); 252 | writer.Write((UInt32)polygon.Coordinates.Count()); 253 | 254 | foreach (LinearRing linearRing in polygon.Coordinates) 255 | { 256 | writer.Write((UInt32)linearRing.Coordinates.Count()); 257 | 258 | foreach (Position pos in linearRing.Coordinates) 259 | { 260 | WritePosition(writer, pos); 261 | } 262 | } 263 | } 264 | 265 | private static void ToBinary(EndianAwareBinaryWriter writer, MultiPoint multiPoint) 266 | { 267 | writer.WriteEndianness(); 268 | writer.Write((UInt32)WkbType.MultiPoint); 269 | writer.Write((UInt32)multiPoint.Coordinates.Count()); 270 | 271 | foreach (Position pos in multiPoint.Coordinates) 272 | { 273 | Point point = new Point(pos); 274 | ToBinary(writer, point); 275 | } 276 | } 277 | 278 | private static void ToBinary(EndianAwareBinaryWriter writer, MultiLineString multiLineString) 279 | { 280 | writer.WriteEndianness(); 281 | writer.Write((UInt32)WkbType.MultiLineString); 282 | writer.Write((UInt32)multiLineString.Coordinates.Count()); 283 | 284 | foreach (LineString lineString in multiLineString.Coordinates) 285 | { 286 | ToBinary(writer, lineString); 287 | } 288 | } 289 | 290 | private static void ToBinary(EndianAwareBinaryWriter writer, MultiPolygon multiPolygon) 291 | { 292 | writer.WriteEndianness(); 293 | writer.Write((UInt32)WkbType.MultiPolygon); 294 | writer.Write((UInt32)multiPolygon.Coordinates.Count()); 295 | 296 | foreach (Polygon polygon in multiPolygon.Coordinates) 297 | { 298 | ToBinary(writer, polygon); 299 | } 300 | } 301 | 302 | private static void ToBinary(EndianAwareBinaryWriter writer, GeometryCollection geometryCollection) 303 | { 304 | writer.WriteEndianness(); 305 | writer.Write((UInt32)WkbType.GeometryCollection); 306 | writer.Write((UInt32)geometryCollection.Geometries.Count()); 307 | 308 | foreach (Geometry geometry in geometryCollection.Geometries) 309 | { 310 | ToBinary(writer, geometry); 311 | } 312 | } 313 | 314 | private static void ToBinary(EndianAwareBinaryWriter writer, Geometry geometry) 315 | { 316 | switch (geometry.Type) 317 | { 318 | case GeoJsonType.Point: 319 | { 320 | ToBinary(writer, (Point)geometry); 321 | break; 322 | } 323 | case GeoJsonType.LineString: 324 | { 325 | ToBinary(writer, (LineString)geometry); 326 | break; 327 | } 328 | case GeoJsonType.Polygon: 329 | { 330 | ToBinary(writer, (Polygon)geometry); 331 | break; 332 | } 333 | case GeoJsonType.MultiPoint: 334 | { 335 | ToBinary(writer, (MultiPoint)geometry); 336 | break; 337 | } 338 | case GeoJsonType.MultiLineString: 339 | { 340 | ToBinary(writer, (MultiLineString)geometry); 341 | break; 342 | } 343 | case GeoJsonType.MultiPolygon: 344 | { 345 | ToBinary(writer, (MultiPolygon)geometry); 346 | break; 347 | } 348 | case GeoJsonType.GeometryCollection: 349 | { 350 | ToBinary(writer, (GeometryCollection)geometry); 351 | break; 352 | } 353 | default: 354 | { 355 | throw new NotSupportedException($"The GeoJson type {geometry.Type.ToString()} is not supported for conversion to WKB."); 356 | } 357 | } 358 | } 359 | 360 | private static EndianAwareBinaryWriter WritePosition(EndianAwareBinaryWriter writer, Position position) 361 | { 362 | writer.Write(position.Longitude); 363 | writer.Write(position.Latitude); 364 | 365 | return writer; 366 | } 367 | 368 | #endregion 369 | 370 | #endregion 371 | } 372 | } 373 | -------------------------------------------------------------------------------- /GeoJSON/Wkb/WkbType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BAMCIS.GeoJSON.Wkb 4 | { 5 | /// 6 | /// The different geometry types available in WKB 7 | /// 8 | public enum WkbType : UInt32 9 | { 10 | // 2D 11 | Geometry = 0000, 12 | Point = 0001, 13 | LineString = 0002, 14 | Polygon = 0003, 15 | MultiPoint = 0004, 16 | MultiLineString = 0005, 17 | MultiPolygon = 0006, 18 | GeometryCollection = 0007, 19 | CircularString = 0008, 20 | CompoundCurve = 0009, 21 | CurvePolygon = 0010, 22 | MultiCurve = 0011, 23 | MultiSurface = 0012, 24 | Curve = 0013, 25 | Surface = 0014, 26 | PolyhedralSurface = 0015, 27 | TIN = 0016, 28 | Triangle = 0017, 29 | Circle = 0018, 30 | GeodesicString = 0019, 31 | EllipticalCurve = 0020, 32 | NurbsCurve = 0021, 33 | Clothoid = 0022, 34 | SpiralCurve = 0023, 35 | CompoundSurface = 0024, 36 | BrepSolid = 0025, 37 | AffinePlacement = 0102, 38 | 39 | // Z 40 | Z_Geometry = 1000, 41 | Z_Point = 1001, 42 | Z_LineString = 1002, 43 | Z_Polygon = 1003, 44 | Z_MultiPoint = 1004, 45 | Z_MultiLineString = 1005, 46 | Z_MultiPolygon = 1006, 47 | Z_GeometryCollection = 1007, 48 | Z_CircularString = 1008, 49 | Z_CompoundCurve = 1009, 50 | Z_CurvePolygon = 1010, 51 | Z_MultiCurve = 1011, 52 | Z_MultiSurface = 1012, 53 | Z_Curve = 1013, 54 | Z_Surface = 1014, 55 | Z_PolyhedralSurface = 1015, 56 | Z_TIN = 1016, 57 | Z_Triangle = 1017, 58 | Z_Circle = 1018, 59 | Z_GeodesicString = 1019, 60 | Z_EllipticalCurve = 1020, 61 | Z_NurbsCurve = 1021, 62 | Z_Clothoid = 1022, 63 | Z_SpiralCurve = 1023, 64 | Z_CompoundSurface = 1024, 65 | Z_BrepSolid = 1025, 66 | Z_AffinePlacement = 1102, 67 | 68 | 69 | // M 70 | M_Geometry = 2000, 71 | M_Point = 2001, 72 | M_LineString = 2002, 73 | M_Polygon = 2003, 74 | M_MultiPoint = 2004, 75 | M_MultiLineString = 2005, 76 | M_MultiPolygon = 2006, 77 | M_GeometryCollection = 2007, 78 | M_CircularString = 2008, 79 | M_CompoundCurve = 2009, 80 | M_CurvePolygon = 2010, 81 | M_MultiCurve = 2011, 82 | M_MultiSurface = 2012, 83 | M_Curve = 2013, 84 | M_Surface = 2014, 85 | M_PolyhedralSurface = 2015, 86 | M_TIN = 2016, 87 | M_Triangle = 2017, 88 | M_Circle = 2018, 89 | M_GeodesicString = 2019, 90 | M_EllipticalCurve = 2020, 91 | M_NurbsCurve = 2021, 92 | M_Clothoid = 2022, 93 | M_SpiralCurve = 2023, 94 | M_CompoundSurface = 2024, 95 | M_BrepSolid = 2025, 96 | M_AffinePlacement = 2102, 97 | 98 | 99 | // ZM 100 | ZM_Geometry = 3000, 101 | ZM_Point = 3001, 102 | ZM_LineString = 3002, 103 | ZM_Polygon = 3003, 104 | ZM_MultiPoint = 3004, 105 | ZM_MultiLineString = 3005, 106 | ZM_MultiPolygon = 3006, 107 | ZM_GeometryCollection = 3007, 108 | ZM_CircularString = 3008, 109 | ZM_CompoundCurve = 3009, 110 | ZM_CurvePolygon = 3010, 111 | ZM_MultiCurve = 3011, 112 | ZM_MultiSurface = 3012, 113 | ZM_Curve = 3013, 114 | ZM_Surface = 3014, 115 | ZM_PolyhedralSurface = 3015, 116 | ZM_TIN = 3016, 117 | ZM_Triangle = 3017, 118 | ZM_Circle = 3018, 119 | ZM_GeodesicString = 3019, 120 | ZM_EllipticalCurve = 3020, 121 | ZM_NurbsCurve = 3021, 122 | ZM_Clothoid = 3022, 123 | ZM_SpiralCurve = 3023, 124 | ZM_CompoundSurface = 3024, 125 | ZM_BrepSolid = 3025, 126 | ZM_AffinePlacement = 3102 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 BAMCIS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BAMCIS GeoJSON 2 | An implementation of GeoJSON written in .NET Core 2.0. The library complies with the requirements of RFC 7946 (https://tools.ietf.org/html/rfc7946). Can be found on nuget https://www.nuget.org/packages/geojson. 3 | 4 | ## Table of Contents 5 | - [Limitations](#limitations) 6 | - [Usage](#usage) 7 | * [Example 1](#example-1) 8 | * [Example 2](#example-2) 9 | * [Example 3](#example-3) 10 | * [Example 4](#example-4) 11 | * [Usage Notes](#usage-notes) 12 | * [Global Configuration](#global-configuration) 13 | - [Revision History](#revision-history) 14 | 15 | 16 | ## Limitations 17 | 18 | The library does not support section 6 of RFC 7946, extending GeoJSON with Foreign Members. You would need to extend the existing Geometries, Feature, and FeatureCollection classes with the additional properties included in the serialized JSON. 19 | 20 | ## Usage 21 | 22 | ### Example 1 23 | 24 | Given some GeoJSON data, such as: 25 | 26 | ```json 27 | { 28 | "type": "Feature", 29 | "geometry": { 30 | "type": "Point", 31 | "coordinates": [ 102.0, 0.5 ] 32 | }, 33 | "properties": { 34 | "prop0": "value0" 35 | } 36 | } 37 | ``` 38 | 39 | You can receive the text and deserialize it to a GeoJSON object 40 | 41 | ```csharp 42 | GeoJson data = JsonConvert.DeserializeObject(jsonString); 43 | ``` 44 | 45 | Once the data is deserialized, you can cast it to its actual type 46 | 47 | ```csharp 48 | switch (data.Type) 49 | { 50 | case GeoJsonType.Feature: 51 | { 52 | Feature feature = (Feature)data; 53 | break; 54 | } 55 | ... 56 | } 57 | ``` 58 | 59 | ### Example 2 60 | If you know what kind of GeoJSON you're receiving, you can deserialize to it directly: 61 | 62 | ```csharp 63 | FeatureCollection col = FeatureCollection.FromJson(data); 64 | ``` 65 | 66 | And also go back to JSON data: 67 | 68 | ```csharp 69 | string json = col.ToJson(Formatting.Indented); 70 | ``` 71 | 72 | ### Example 3 73 | 74 | Or you can create a GeoJSON object and serialize it: 75 | ```csharp 76 | Position pos1 = new Position(100.0, 0.5); 77 | Position pos2 = new Position(101.0, 1.0); 78 | MultiPoint mp = new MultiPoint(new Position[] {pos1, pos2}); 79 | string json = JsonConvert.Serialize(mp); 80 | ``` 81 | 82 | ### Example 4 83 | The library also supports conversion of `Geometry` objects to and from Well-Known Binary (WKB). For example: 84 | 85 | ```json 86 | { 87 | "type": "Point", 88 | "coordinates": [ 2.0, 4.0 ] 89 | } 90 | ``` 91 | 92 | ```csharp 93 | Point point = new Point(102.0, 0.5); 94 | byte[] wkb = point.ToWkb(); 95 | point = Geometry.FromWkb(wkb); 96 | ``` 97 | 98 | The binary produced is `0x000000000140000000000000004010000000000000`. You can also convert this way. 99 | 100 | ```csharp 101 | Point point = new Point(102.0, 0.5); 102 | byte[] wkb = point.ToWkb(); 103 | Geometry geo = Point.FromWkb(wkb) 104 | point = (Point)geo; 105 | ``` 106 | 107 | You can also specify the endianness of the binary encoding (the default is LITTLE). 108 | 109 | ```csharp 110 | Point point = new Point(102.0, 0.5); 111 | byte[] wkb = point.ToWkb(Endianness.BIG); 112 | Geometry geo = Point.FromWkb(wkb) 113 | point = (Point)geo; 114 | ``` 115 | 116 | Finally, you can use the `WkbConverter` class directly. 117 | 118 | ```csharp 119 | Point point = new Point(new Position(2.0, 4.0)); 120 | byte[] bytes = WkbConverter.ToBinary(point, Endianness.BIG); 121 | ``` 122 | 123 | ```csharp 124 | byte[] bytes = HexStringToByteArray("000000000140000000000000004010000000000000"); 125 | Point point = WkbConverter.FromBinary(bytes); 126 | ``` 127 | 128 | ### Usage Notes 129 | 130 | Each of the 9 GeoJSON types: **Feature**, **FeatureCollection**, **GeometryCollection**, **LineString**, **MultiLineString**, **MultiPoint**, **MultiPolygon**, **Point**, and **Polygon** all have convenience methods ToJson() and FromJson() to make serialization and deserialization easy. 131 | 132 | There are two additional types that can be used. A **LinearRing** is a LineString that is connected as the start and end and forms the basis of a polygon. You can also use the abstract **Geometry** class that encompasses LineString, MultiLineString, MultiPoint, MultiPolygon, Point, and Polygon. 133 | 134 | The Feature **'Properties'** property implements an `IDictionary` in order to accommodate any type of property structure that may be sent. 135 | 136 | ### Global Configuration 137 | 138 | This library provides a global configuration class, `GeoJsonConfig`. Currently, the config offers a way to ignore the validation of latitude and longitude coordinates. For example, given this input: 139 | 140 | ```json 141 | { 142 | "type": "Feature", 143 | "geometry": { 144 | "type": "Point", 145 | "coordinates": [ 200.0, 65000.5 ] 146 | }, 147 | "properties": { 148 | "prop0": "value0" 149 | } 150 | } 151 | ``` 152 | 153 | We would expect this operation to throw an `ArgumentOutOfRangeException` due to the coordinate values. 154 | 155 | ```csharp 156 | Feature geo = JsonConvert.DeserializeObject(content); 157 | ``` 158 | 159 | To ignore the validation, do this: 160 | 161 | ```csharp 162 | GeoJsonConfig.IgnorePositionValidation(); 163 | Feature geo = JsonConvert.DeserializeObject(content); 164 | ``` 165 | 166 | ## Revision History 167 | 168 | ### 2.3.1 169 | Fixed the Polygon serde CanConvert method. 170 | 171 | ### 2.3.0 172 | Added Well-Known Binary serialization and deserialization support for `Geometry` objects. 173 | 174 | ### 2.2.0 175 | Added an Id property to `Feature`. Also added a global config object that can be used to ignore validation of coordinate values. 176 | 177 | ### 2.1.1 178 | Added validation for latitude and longitude values in `Position`. 179 | 180 | ### 2.1.0 181 | Added a method to remove interior linear rings from a `Polygon` object. 182 | 183 | ### 2.0.2 184 | Fixed bug for `MultiLineString` with less than 2 coordinates. 185 | 186 | ### 2.0.1 187 | Bug fix for NULL geometry JSON token in a Feature, which is allowed by the RFC. 188 | 189 | ### 2.0.0 190 | Changed JSON serialized property names to proper camel case by default to be RFC compliant. Added strong-named assembly signing. 191 | 192 | ### 1.2.1 193 | Fixed sequence comparison null value handling. 194 | 195 | ### 1.2.0 196 | Enabled the use of the bounding box property on all GeoJSON types. 197 | 198 | ### 1.1.2 199 | Actually fixed targeting `netstandard1.6` instead of v1.6.1. 200 | 201 | ### 1.1.1 202 | Fixed targeting of netstandard1.6, dropped JSON.NET to 9.0.1. Added `netstandard2.0` and `net45` as target frameworks. 203 | 204 | ### 1.1.0 205 | Retargeted library to netstandard1.6. 206 | 207 | ### 1.0.0 208 | Initial release of the library. 209 | --------------------------------------------------------------------------------