├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── doc.go ├── ewkb ├── decoder.go ├── decoder_test.go ├── doc.go ├── encoder.go ├── encoder_test.go └── ewkb.go ├── geojson ├── encoder.go └── encoder_test.go ├── geom.go ├── types.go └── types_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright [2017] Alex Davies-Moore 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | 16 | Apache License 17 | Version 2.0, January 2004 18 | http://www.apache.org/licenses/ 19 | 20 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 21 | 22 | 1. Definitions. 23 | 24 | "License" shall mean the terms and conditions for use, reproduction, 25 | and distribution as defined by Sections 1 through 9 of this document. 26 | 27 | "Licensor" shall mean the copyright owner or entity authorized by 28 | the copyright owner that is granting the License. 29 | 30 | "Legal Entity" shall mean the union of the acting entity and all 31 | other entities that control, are controlled by, or are under common 32 | control with that entity. For the purposes of this definition, 33 | "control" means (i) the power, direct or indirect, to cause the 34 | direction or management of such entity, whether by contract or 35 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 36 | outstanding shares, or (iii) beneficial ownership of such entity. 37 | 38 | "You" (or "Your") shall mean an individual or Legal Entity 39 | exercising permissions granted by this License. 40 | 41 | "Source" form shall mean the preferred form for making modifications, 42 | including but not limited to software source code, documentation 43 | source, and configuration files. 44 | 45 | "Object" form shall mean any form resulting from mechanical 46 | transformation or translation of a Source form, including but 47 | not limited to compiled object code, generated documentation, 48 | and conversions to other media types. 49 | 50 | "Work" shall mean the work of authorship, whether in Source or 51 | Object form, made available under the License, as indicated by a 52 | copyright notice that is included in or attached to the work 53 | (an example is provided in the Appendix below). 54 | 55 | "Derivative Works" shall mean any work, whether in Source or Object 56 | form, that is based on (or derived from) the Work and for which the 57 | editorial revisions, annotations, elaborations, or other modifications 58 | represent, as a whole, an original work of authorship. For the purposes 59 | of this License, Derivative Works shall not include works that remain 60 | separable from, or merely link (or bind by name) to the interfaces of, 61 | the Work and Derivative Works thereof. 62 | 63 | "Contribution" shall mean any work of authorship, including 64 | the original version of the Work and any modifications or additions 65 | to that Work or Derivative Works thereof, that is intentionally 66 | submitted to Licensor for inclusion in the Work by the copyright owner 67 | or by an individual or Legal Entity authorized to submit on behalf of 68 | the copyright owner. For the purposes of this definition, "submitted" 69 | means any form of electronic, verbal, or written communication sent 70 | to the Licensor or its representatives, including but not limited to 71 | communication on electronic mailing lists, source code control systems, 72 | and issue tracking systems that are managed by, or on behalf of, the 73 | Licensor for the purpose of discussing and improving the Work, but 74 | excluding communication that is conspicuously marked or otherwise 75 | designated in writing by the copyright owner as "Not a Contribution." 76 | 77 | "Contributor" shall mean Licensor and any individual or Legal Entity 78 | on behalf of whom a Contribution has been received by Licensor and 79 | subsequently incorporated within the Work. 80 | 81 | 2. Grant of Copyright License. Subject to the terms and conditions of 82 | this License, each Contributor hereby grants to You a perpetual, 83 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 84 | copyright license to reproduce, prepare Derivative Works of, 85 | publicly display, publicly perform, sublicense, and distribute the 86 | Work and such Derivative Works in Source or Object form. 87 | 88 | 3. Grant of Patent License. Subject to the terms and conditions of 89 | this License, each Contributor hereby grants to You a perpetual, 90 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 91 | (except as stated in this section) patent license to make, have made, 92 | use, offer to sell, sell, import, and otherwise transfer the Work, 93 | where such license applies only to those patent claims licensable 94 | by such Contributor that are necessarily infringed by their 95 | Contribution(s) alone or by combination of their Contribution(s) 96 | with the Work to which such Contribution(s) was submitted. If You 97 | institute patent litigation against any entity (including a 98 | cross-claim or counterclaim in a lawsuit) alleging that the Work 99 | or a Contribution incorporated within the Work constitutes direct 100 | or contributory patent infringement, then any patent licenses 101 | granted to You under this License for that Work shall terminate 102 | as of the date such litigation is filed. 103 | 104 | 4. Redistribution. You may reproduce and distribute copies of the 105 | Work or Derivative Works thereof in any medium, with or without 106 | modifications, and in Source or Object form, provided that You 107 | meet the following conditions: 108 | 109 | (a) You must give any other recipients of the Work or 110 | Derivative Works a copy of this License; and 111 | 112 | (b) You must cause any modified files to carry prominent notices 113 | stating that You changed the files; and 114 | 115 | (c) You must retain, in the Source form of any Derivative Works 116 | that You distribute, all copyright, patent, trademark, and 117 | attribution notices from the Source form of the Work, 118 | excluding those notices that do not pertain to any part of 119 | the Derivative Works; and 120 | 121 | (d) If the Work includes a "NOTICE" text file as part of its 122 | distribution, then any Derivative Works that You distribute must 123 | include a readable copy of the attribution notices contained 124 | within such NOTICE file, excluding those notices that do not 125 | pertain to any part of the Derivative Works, in at least one 126 | of the following places: within a NOTICE text file distributed 127 | as part of the Derivative Works; within the Source form or 128 | documentation, if provided along with the Derivative Works; or, 129 | within a display generated by the Derivative Works, if and 130 | wherever such third-party notices normally appear. The contents 131 | of the NOTICE file are for informational purposes only and 132 | do not modify the License. You may add Your own attribution 133 | notices within Derivative Works that You distribute, alongside 134 | or as an addendum to the NOTICE text from the Work, provided 135 | that such additional attribution notices cannot be construed 136 | as modifying the License. 137 | 138 | You may add Your own copyright statement to Your modifications and 139 | may provide additional or different license terms and conditions 140 | for use, reproduction, or distribution of Your modifications, or 141 | for any such Derivative Works as a whole, provided Your use, 142 | reproduction, and distribution of the Work otherwise complies with 143 | the conditions stated in this License. 144 | 145 | 5. Submission of Contributions. Unless You explicitly state otherwise, 146 | any Contribution intentionally submitted for inclusion in the Work 147 | by You to the Licensor shall be under the terms and conditions of 148 | this License, without any additional terms or conditions. 149 | Notwithstanding the above, nothing herein shall supersede or modify 150 | the terms of any separate license agreement you may have executed 151 | with Licensor regarding such Contributions. 152 | 153 | 6. Trademarks. This License does not grant permission to use the trade 154 | names, trademarks, service marks, or product names of the Licensor, 155 | except as required for reasonable and customary use in describing the 156 | origin of the Work and reproducing the content of the NOTICE file. 157 | 158 | 7. Disclaimer of Warranty. Unless required by applicable law or 159 | agreed to in writing, Licensor provides the Work (and each 160 | Contributor provides its Contributions) on an "AS IS" BASIS, 161 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 162 | implied, including, without limitation, any warranties or conditions 163 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 164 | PARTICULAR PURPOSE. You are solely responsible for determining the 165 | appropriateness of using or redistributing the Work and assume any 166 | risks associated with Your exercise of permissions under this License. 167 | 168 | 8. Limitation of Liability. In no event and under no legal theory, 169 | whether in tort (including negligence), contract, or otherwise, 170 | unless required by applicable law (such as deliberate and grossly 171 | negligent acts) or agreed to in writing, shall any Contributor be 172 | liable to You for damages, including any direct, indirect, special, 173 | incidental, or consequential damages of any character arising as a 174 | result of this License or out of the use or inability to use the 175 | Work (including but not limited to damages for loss of goodwill, 176 | work stoppage, computer failure or malfunction, or any and all 177 | other commercial damages or losses), even if such Contributor 178 | has been advised of the possibility of such damages. 179 | 180 | 9. Accepting Warranty or Additional Liability. While redistributing 181 | the Work or Derivative Works thereof, You may choose to offer, 182 | and charge a fee for, acceptance of support, warranty, indemnity, 183 | or other liability obligations and/or rights consistent with this 184 | License. However, in accepting such obligations, You may act only 185 | on Your own behalf and on Your sole responsibility, not on behalf 186 | of any other Contributor, and only if You agree to indemnify, 187 | defend, and hold each Contributor harmless for any liability 188 | incurred by, or claims asserted against, such Contributor by reason 189 | of your accepting any such warranty or additional liability. 190 | 191 | END OF TERMS AND CONDITIONS 192 | 193 | APPENDIX: How to apply the Apache License to your work. 194 | 195 | To apply the Apache License to your work, attach the following 196 | boilerplate notice, with the fields enclosed by brackets "{}" 197 | replaced with your own identifying information. (Don't include 198 | the brackets!) The text should be enclosed in the appropriate 199 | comment syntax for the file format. We also recommend that a 200 | file or class name and description of purpose be included on the 201 | same "printed page" as the copyright notice for easier 202 | identification within third-party archives. 203 | 204 | Copyright {yyyy} {name of copyright owner} 205 | 206 | Licensed under the Apache License, Version 2.0 (the "License"); 207 | you may not use this file except in compliance with the License. 208 | You may obtain a copy of the License at 209 | 210 | http://www.apache.org/licenses/LICENSE-2.0 211 | 212 | Unless required by applicable law or agreed to in writing, software 213 | distributed under the License is distributed on an "AS IS" BASIS, 214 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 215 | See the License for the specific language governing permissions and 216 | limitations under the License. 217 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | clean: 2 | go clean 3 | 4 | deps: 5 | go get github.com/stretchr/testify/assert 6 | 7 | test: clean 8 | go test -v 9 | 10 | build: 11 | go clean 12 | go build 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ewkb 2 | Parser for the WKB and Postgis EWKB format 3 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2015] Alex Davies-Moore 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // geom package provides core geometry types and interfaces according to the geometry definitions in: 18 | // 19 | // OpenGIS® Implementation Specification for Geographic information - Simple feature access - Part 1: Common architecture 20 | package geom 21 | -------------------------------------------------------------------------------- /ewkb/decoder.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2015] Alex Davies-Moore 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ewkb 18 | 19 | import ( 20 | "encoding/binary" 21 | "io" 22 | 23 | "github.com/devork/geom" 24 | ) 25 | 26 | // wraps the byte order and reader into a single struct for easier mainpulation 27 | type decoder struct { 28 | order binary.ByteOrder 29 | reader io.Reader 30 | dim dimension 31 | gtype geomtype 32 | srid uint32 33 | } 34 | 35 | func (d *decoder) read(data interface{}) error { 36 | return binary.Read(d.reader, d.order, data) 37 | } 38 | 39 | func (d *decoder) u8() (uint8, error) { 40 | var value uint8 41 | 42 | err := d.read(&value) 43 | 44 | return value, err 45 | } 46 | 47 | func (d *decoder) u16() (uint16, error) { 48 | var value uint16 49 | 50 | err := d.read(&value) 51 | 52 | return value, err 53 | } 54 | 55 | func (d *decoder) u32() (uint32, error) { 56 | var value uint32 57 | 58 | err := d.read(&value) 59 | 60 | return value, err 61 | } 62 | 63 | func (d *decoder) hdr() *geom.Hdr { 64 | return &geom.Hdr{Dim: d.dim.dim(), Srid: d.srid} 65 | } 66 | 67 | func newDecoder(r io.Reader) (*decoder, error) { 68 | var otype byte 69 | err := binary.Read(r, binary.BigEndian, &otype) 70 | 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | if otype == bigEndian { 76 | return &decoder{order: binary.BigEndian, reader: r}, nil 77 | } 78 | return &decoder{order: binary.LittleEndian, reader: r}, nil 79 | 80 | } 81 | 82 | // Decode converts from the given reader to a geom type 83 | func Decode(r io.Reader) (geom.Geometry, error) { 84 | 85 | decoder, err := newDecoder(r) 86 | 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | err = unmarshalHdr(decoder) 92 | 93 | if err != nil { 94 | return nil, err 95 | } 96 | 97 | return unmarshal(decoder) 98 | } 99 | 100 | func unmarshalHdr(d *decoder) error { 101 | 102 | gtype, err := d.u32() 103 | 104 | if err != nil { 105 | return err 106 | } 107 | 108 | // fmt.Printf("gtype = %X\n", gtype) 109 | 110 | var geomv, dim uint16 111 | 112 | geomv = uint16(gtype & uint32(0xFFFF)) 113 | dim = uint16(gtype >> 16) 114 | d.dim = dimension(dim) 115 | 116 | // fmt.Printf("geomv = %X [%[1]d]\n", geomv) 117 | // fmt.Printf("dim = %X\n", dim) 118 | 119 | // switch on the mask first to check for EWKB 120 | switch d.dim { 121 | case xys, xyms, xyzs, xyzms: 122 | err = d.read(&d.srid) 123 | 124 | if err != nil { 125 | return err 126 | } 127 | } 128 | 129 | // fallback check for WKB & reset the WKB versions to regular geometry types 130 | switch { 131 | case geomv >= wkbzm: 132 | geomv = geomv - wkbzm 133 | d.dim = xyzm 134 | case geomv >= wkbm: 135 | geomv = geomv - wkbm 136 | d.dim = xym 137 | case geomv >= wkbz: 138 | geomv = geomv - wkbz 139 | d.dim = xyz 140 | } 141 | 142 | d.gtype = geomtype(geomv) 143 | 144 | return nil 145 | } 146 | 147 | func unmarshalPoint(d *decoder) (geom.Geometry, error) { 148 | coord, err := unmarshalCoord(d) 149 | if err != nil { 150 | return nil, err 151 | } 152 | 153 | return &geom.Point{*d.hdr(), *coord}, nil 154 | } 155 | 156 | func unmarshalMultiPoint(d *decoder) (geom.Geometry, error) { 157 | var idx uint32 158 | numPoints, err := d.u32() 159 | 160 | if err != nil { 161 | return nil, err 162 | } 163 | 164 | points := make([]geom.Point, numPoints, numPoints) 165 | var point geom.Geometry 166 | var hdr = d.hdr() 167 | for idx = 0; idx < numPoints; idx++ { 168 | d.u8() // byteorder 169 | err = unmarshalHdr(d) 170 | 171 | if err != nil { 172 | return nil, err 173 | } 174 | 175 | point, err = unmarshalPoint(d) 176 | 177 | if err != nil { 178 | return nil, err 179 | } 180 | 181 | points[idx] = *point.(*geom.Point) 182 | } 183 | 184 | return &geom.MultiPoint{*hdr, points}, nil 185 | } 186 | 187 | func unmarshalLineString(d *decoder) (geom.Geometry, error) { 188 | var idx uint32 189 | numPoints, err := d.u32() 190 | 191 | if err != nil { 192 | return nil, err 193 | } 194 | 195 | coords := make([]geom.Coordinate, numPoints, numPoints) 196 | var coord *geom.Coordinate 197 | for idx = 0; idx < numPoints; idx++ { 198 | coord, err = unmarshalCoord(d) 199 | 200 | if err != nil { 201 | return nil, err 202 | } 203 | 204 | coords[idx] = *coord 205 | } 206 | 207 | return &geom.LineString{*d.hdr(), coords}, nil 208 | } 209 | 210 | func unmarshalMultiLineString(d *decoder) (geom.Geometry, error) { 211 | var idx uint32 212 | numStrings, err := d.u32() 213 | 214 | if err != nil { 215 | return nil, err 216 | } 217 | 218 | lstrings := make([]geom.LineString, numStrings, numStrings) 219 | var lstring geom.Geometry 220 | var hdr = d.hdr() 221 | for idx = 0; idx < numStrings; idx++ { 222 | d.u8() // byteorder 223 | err = unmarshalHdr(d) 224 | 225 | if err != nil { 226 | return nil, err 227 | } 228 | 229 | lstring, err = unmarshalLineString(d) 230 | 231 | if err != nil { 232 | return nil, err 233 | } 234 | 235 | lstrings[idx] = *lstring.(*geom.LineString) 236 | } 237 | 238 | return &geom.MultiLineString{*hdr, lstrings}, nil 239 | } 240 | 241 | func unmarshalPolygon(d *decoder) (geom.Geometry, error) { 242 | var idx uint32 243 | numRings, err := d.u32() 244 | 245 | if err != nil { 246 | return nil, err 247 | } 248 | 249 | rings := make([]geom.LinearRing, numRings, numRings) 250 | var ring *geom.LinearRing 251 | for idx = 0; idx < numRings; idx++ { 252 | ring, err = unmarshalLinearRing(d) 253 | 254 | if err != nil { 255 | return nil, err 256 | } 257 | 258 | rings[idx] = *ring 259 | } 260 | 261 | return &geom.Polygon{*d.hdr(), rings}, nil 262 | } 263 | 264 | func unmarshalMultiPolygon(d *decoder) (geom.Geometry, error) { 265 | var idx uint32 266 | numPolys, err := d.u32() 267 | 268 | if err != nil { 269 | return nil, err 270 | } 271 | 272 | polys := make([]geom.Polygon, numPolys, numPolys) 273 | var poly geom.Geometry 274 | var hdr = d.hdr() 275 | for idx = 0; idx < numPolys; idx++ { 276 | d.u8() // byteorder 277 | err = unmarshalHdr(d) 278 | 279 | if err != nil { 280 | return nil, err 281 | } 282 | 283 | poly, err = unmarshalPolygon(d) 284 | 285 | if err != nil { 286 | return nil, err 287 | } 288 | 289 | polys[idx] = *poly.(*geom.Polygon) 290 | } 291 | 292 | return &geom.MultiPolygon{*hdr, polys}, nil 293 | } 294 | 295 | func unmarshalGeometryCollection(d *decoder) (geom.Geometry, error) { 296 | var idx uint32 297 | numGeoms, err := d.u32() 298 | 299 | if err != nil { 300 | return nil, err 301 | } 302 | 303 | geoms := make([]geom.Geometry, numGeoms, numGeoms) 304 | var g geom.Geometry 305 | var hdr = d.hdr() 306 | for idx = 0; idx < numGeoms; idx++ { 307 | d.u8() 308 | err := unmarshalHdr(d) 309 | 310 | if err != nil { 311 | return nil, err 312 | } 313 | 314 | g, err = unmarshal(d) 315 | 316 | if err != nil { 317 | return nil, err 318 | } 319 | geoms[idx] = g 320 | } 321 | 322 | return &geom.GeometryCollection{*hdr, geoms}, nil 323 | } 324 | 325 | func unmarshalLinearRing(d *decoder) (*geom.LinearRing, error) { 326 | var idx uint32 327 | numPoints, err := d.u32() 328 | 329 | if err != nil { 330 | return nil, err 331 | } 332 | 333 | coords := make([]geom.Coordinate, numPoints, numPoints) 334 | var coord *geom.Coordinate 335 | for idx = 0; idx < numPoints; idx++ { 336 | coord, err = unmarshalCoord(d) 337 | 338 | if err != nil { 339 | return nil, err 340 | } 341 | 342 | coords[idx] = *coord 343 | } 344 | 345 | return &geom.LinearRing{coords}, nil 346 | } 347 | 348 | func unmarshalCoord(d *decoder) (*geom.Coordinate, error) { 349 | var size int 350 | switch d.dim { 351 | case xyz, xyzs, xym, xyms: 352 | size = 3 353 | case xyzm, xyzms: 354 | size = 4 355 | default: 356 | size = 2 357 | } 358 | 359 | var coord geom.Coordinate = make([]float64, size, size) 360 | for idx := 0; idx < size; idx++ { 361 | err := d.read(&coord[idx]) 362 | 363 | if err != nil { 364 | return nil, err 365 | } 366 | } 367 | 368 | return &coord, nil 369 | } 370 | 371 | // Resolves a geometry type to its unmarshaller instance 372 | func unmarshal(d *decoder) (geom.Geometry, error) { 373 | switch d.gtype { 374 | case point: 375 | return unmarshalPoint(d) 376 | case linestring: 377 | return unmarshalLineString(d) 378 | case polygon: 379 | return unmarshalPolygon(d) 380 | case multipoint: 381 | return unmarshalMultiPoint(d) 382 | case multilinestring: 383 | return unmarshalMultiLineString(d) 384 | case multipolygon: 385 | return unmarshalMultiPolygon(d) 386 | case geometrycollection: 387 | return unmarshalGeometryCollection(d) 388 | default: 389 | return nil, geom.ErrUnsupportedGeom 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /ewkb/decoder_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2015] Alex Davies-Moore 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ewkb 18 | 19 | /* 20 | # Test Case Generation 21 | 22 | All test cases were generated from PostGIS: 23 | 24 | LINESTRING (30 10, 10 30, 40 40) 25 | 26 | with g as ( 27 | select st_geomfromewkt('SRID=27700;LINESTRING (30 10, 10 30, 40 40)') as geom 28 | ) 29 | select 30 | ST_AsEWKT(g.geom) as ewkt, 31 | st_asewkb(g.geom, 'XDR') as ewkb_xdr, 32 | st_asewkb(g.geom, 'HDR') as ewkb_hdr, 33 | st_asbinary(g.geom, 'XDR') as wkb_xdr, 34 | st_asbinary(g.geom, 'HDR') as wkb_hdr 35 | from 36 | g 37 | ; 38 | 39 | POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10)) 40 | 41 | with g as ( 42 | select st_geomfromewkt('SRID=27700;POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))') as geom 43 | ) 44 | select 45 | ST_AsEWKT(g.geom) as ewkt, 46 | st_asewkb(g.geom, 'XDR') as ewkb_xdr, 47 | st_asewkb(g.geom, 'HDR') as ewkb_hdr, 48 | st_asbinary(g.geom, 'XDR') as wkb_xdr, 49 | st_asbinary(g.geom, 'HDR') as wkb_hdr 50 | from 51 | g 52 | ; 53 | 54 | POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10),(20 30, 35 35, 30 20, 20 30)) 55 | 56 | with g as ( 57 | select st_geomfromewkt('SRID=27700;POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10),(20 30, 35 35, 30 20, 20 30))') as geom 58 | ) 59 | select 60 | ST_AsEWKT(g.geom) as ewkt, 61 | st_asewkb(g.geom, 'XDR') as ewkb_xdr, 62 | st_asewkb(g.geom, 'HDR') as ewkb_hdr, 63 | st_asbinary(g.geom, 'XDR') as wkb_xdr, 64 | st_asbinary(g.geom, 'HDR') as wkb_hdr 65 | from 66 | g 67 | ; 68 | 69 | MULTIPOINT ((10 40), (40 30), (20 20), (30 10)) 70 | 71 | with g as ( 72 | select st_geomfromewkt('SRID=27700;MULTIPOINT ((10 40), (40 30), (20 20), (30 10))') as geom 73 | ) 74 | select 75 | ST_AsEWKT(g.geom) as ewkt, 76 | st_asewkb(g.geom, 'XDR') as ewkb_xdr, 77 | st_asewkb(g.geom, 'HDR') as ewkb_hdr, 78 | st_asbinary(g.geom, 'XDR') as wkb_xdr, 79 | st_asbinary(g.geom, 'HDR') as wkb_hdr 80 | from 81 | g 82 | ; 83 | 84 | MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10)) 85 | 86 | with g as ( 87 | select st_geomfromewkt('SRID=27700;MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))') as geom 88 | ) 89 | select 90 | ST_AsEWKT(g.geom) as ewkt, 91 | st_asewkb(g.geom, 'XDR') as ewkb_xdr, 92 | st_asewkb(g.geom, 'HDR') as ewkb_hdr, 93 | st_asbinary(g.geom, 'XDR') as wkb_xdr, 94 | st_asbinary(g.geom, 'HDR') as wkb_hdr 95 | from 96 | g 97 | ; 98 | 99 | MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)),((20 35, 10 30, 10 10, 30 5, 45 20, 20 35),(30 20, 20 15, 20 25, 30 20))) 100 | 101 | with g as ( 102 | select st_geomfromewkt('SRID=27700;MULTIPOLYGON (((40 40, 20 45, 45 30, 40 40)),((20 35, 10 30, 10 10, 30 5, 45 20, 20 35),(30 20, 20 15, 20 25, 30 20)))') as geom 103 | ) 104 | select 105 | ST_AsEWKT(g.geom) as ewkt, 106 | st_asewkb(g.geom, 'XDR') as ewkb_xdr, 107 | st_asewkb(g.geom, 'HDR') as ewkb_hdr, 108 | st_asbinary(g.geom, 'XDR') as wkb_xdr, 109 | st_asbinary(g.geom, 'HDR') as wkb_hdr 110 | from 111 | g 112 | ; 113 | 114 | GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10)) 115 | 116 | with g as ( 117 | select st_geomfromewkt('SRID=27700;GEOMETRYCOLLECTION(POINT(4 6),LINESTRING(4 6,7 10))') as geom 118 | ) 119 | select 120 | ST_AsEWKT(g.geom) as ewkt, 121 | st_asewkb(g.geom, 'XDR') as ewkb_xdr, 122 | st_asewkb(g.geom, 'HDR') as ewkb_hdr, 123 | st_asbinary(g.geom, 'XDR') as wkb_xdr, 124 | st_asbinary(g.geom, 'HDR') as wkb_hdr 125 | from 126 | g 127 | ; 128 | 129 | */ 130 | 131 | import ( 132 | "bytes" 133 | "encoding/hex" 134 | "testing" 135 | 136 | "github.com/devork/geom" 137 | "github.com/stretchr/testify/assert" 138 | ) 139 | 140 | func TestGeometryCollection(t *testing.T) { 141 | datasets := []struct { 142 | data string 143 | srid uint32 144 | dim geom.Dimension 145 | }{ 146 | {"002000000700006c340000000200000000014010000000000000401800000000000000000000020000000240100000000000004018000000000000401c0000000000004024000000000000", 27700, geom.XY}, // ewkb_xdr 147 | {"0107000020346c000002000000010100000000000000000010400000000000001840010200000002000000000000000000104000000000000018400000000000001c400000000000002440", 27700, geom.XY}, // ewkb_hdr 148 | {"00000000070000000200000000014010000000000000401800000000000000000000020000000240100000000000004018000000000000401c0000000000004024000000000000", 0, geom.XY}, // wkb_hdr 149 | {"010700000002000000010100000000000000000010400000000000001840010200000002000000000000000000104000000000000018400000000000001c400000000000002440", 0, geom.XY}, // wkb_xdr 150 | } 151 | 152 | for _, dataset := range datasets { 153 | t.Log("Decoding HEX String: DIM = ", dataset.dim, ", Data = ", dataset.data) 154 | data, err := hex.DecodeString(dataset.data) 155 | 156 | if err != nil { 157 | t.Fatal("Failed to decode HEX string: err = ", err) 158 | } 159 | 160 | r := bytes.NewReader(data) 161 | 162 | g, err := Decode(r) 163 | 164 | if err != nil { 165 | t.Fatalf("Failed to convert geom: error = %s", err) 166 | } 167 | 168 | assert.Equal(t, "geometrycollection", g.Type()) 169 | 170 | gcol := g.(*geom.GeometryCollection) 171 | 172 | t.Log(gcol) 173 | 174 | assert.Equal(t, dataset.dim, gcol.Dimension(), "Expected dim %v, but got %v ", dataset.dim, g.Dimension) 175 | assert.Equal(t, dataset.srid, gcol.SRID(), "Expected srid %v, but got %v ", dataset.srid, g.SRID()) 176 | 177 | assert.Equal(t, 2, len(gcol.Geometries)) 178 | 179 | point := gcol.Geometries[0].(*geom.Point) 180 | 181 | assert.InDelta(t, 4, point.Coordinate[0], 1e-9) 182 | assert.InDelta(t, 6, point.Coordinate[1], 1e-9) 183 | 184 | lstring := gcol.Geometries[1].(*geom.LineString) 185 | 186 | assert.Equal(t, 2, len(lstring.Coordinates)) 187 | 188 | assert.InDelta(t, 4, lstring.Coordinates[0][0], 1e-9) 189 | assert.InDelta(t, 6, lstring.Coordinates[0][1], 1e-9) 190 | 191 | assert.InDelta(t, 7, lstring.Coordinates[1][0], 1e-9) 192 | assert.InDelta(t, 10, lstring.Coordinates[1][1], 1e-9) 193 | } 194 | } 195 | 196 | func TestMultiPolygon(t *testing.T) { 197 | datasets := []struct { 198 | data string 199 | srid uint32 200 | dim geom.Dimension 201 | }{ 202 | {"002000000600006c34000000020000000003000000010000000440440000000000004044000000000000403400000000000040468000000000004046800000000000403e0000000000004044000000000000404400000000000000000000030000000200000006403400000000000040418000000000004024000000000000403e00000000000040240000000000004024000000000000403e0000000000004014000000000000404680000000000040340000000000004034000000000000404180000000000000000004403e00000000000040340000000000004034000000000000402e00000000000040340000000000004039000000000000403e0000000000004034000000000000", 27700, geom.XY}, // ewkb_xdr 203 | {"0106000020346c00000200000001030000000100000004000000000000000000444000000000000044400000000000003440000000000080464000000000008046400000000000003e4000000000000044400000000000004440010300000002000000060000000000000000003440000000000080414000000000000024400000000000003e40000000000000244000000000000024400000000000003e4000000000000014400000000000804640000000000000344000000000000034400000000000804140040000000000000000003e40000000000000344000000000000034400000000000002e40000000000000344000000000000039400000000000003e400000000000003440", 27700, geom.XY}, // ewkb_hdr 204 | {"0000000006000000020000000003000000010000000440440000000000004044000000000000403400000000000040468000000000004046800000000000403e0000000000004044000000000000404400000000000000000000030000000200000006403400000000000040418000000000004024000000000000403e00000000000040240000000000004024000000000000403e0000000000004014000000000000404680000000000040340000000000004034000000000000404180000000000000000004403e00000000000040340000000000004034000000000000402e00000000000040340000000000004039000000000000403e0000000000004034000000000000", 0, geom.XY}, // wkb_hdr 205 | {"01060000000200000001030000000100000004000000000000000000444000000000000044400000000000003440000000000080464000000000008046400000000000003e4000000000000044400000000000004440010300000002000000060000000000000000003440000000000080414000000000000024400000000000003e40000000000000244000000000000024400000000000003e4000000000000014400000000000804640000000000000344000000000000034400000000000804140040000000000000000003e40000000000000344000000000000034400000000000002e40000000000000344000000000000039400000000000003e400000000000003440", 0, geom.XY}, // wkb_xdr 206 | } 207 | 208 | for _, dataset := range datasets { 209 | t.Log("Decoding HEX String: DIM = ", dataset.dim, ", Data = ", dataset.data) 210 | data, err := hex.DecodeString(dataset.data) 211 | 212 | if err != nil { 213 | t.Fatal("Failed to decode HEX string: err = ", err) 214 | } 215 | 216 | r := bytes.NewReader(data) 217 | 218 | g, err := Decode(r) 219 | 220 | if err != nil { 221 | t.Fatal("Failed to parse MultiPolygon: err = ", err) 222 | } 223 | 224 | assert.Equal(t, "multipolygon", g.Type()) 225 | 226 | mpolygon := g.(*geom.MultiPolygon) 227 | 228 | t.Log(mpolygon) 229 | 230 | assert.Equal(t, dataset.dim, mpolygon.Dimension(), "Expected dim %v, but got %v ", dataset.dim, g.Dimension()) 231 | assert.Equal(t, dataset.srid, mpolygon.SRID(), "Expected srid %v, but got %v ", dataset.srid, g.SRID()) 232 | 233 | assert.Equal(t, 2, len(mpolygon.Polygons)) 234 | 235 | polygon := mpolygon.Polygons[0] 236 | assert.Equal(t, 1, len(polygon.Rings)) 237 | 238 | lr := polygon.Rings[0] 239 | 240 | assert.Equal(t, 4, len(lr.Coordinates)) 241 | 242 | assert.InDelta(t, 40, lr.Coordinates[0][0], 1e-9) 243 | assert.InDelta(t, 40, lr.Coordinates[0][1], 1e-9) 244 | 245 | assert.InDelta(t, 20, lr.Coordinates[1][0], 1e-9) 246 | assert.InDelta(t, 45, lr.Coordinates[1][1], 1e-9) 247 | 248 | assert.InDelta(t, 45, lr.Coordinates[2][0], 1e-9) 249 | assert.InDelta(t, 30, lr.Coordinates[2][1], 1e-9) 250 | 251 | assert.InDelta(t, 40, lr.Coordinates[3][0], 1e-9) 252 | assert.InDelta(t, 40, lr.Coordinates[3][1], 1e-9) 253 | 254 | polygon = mpolygon.Polygons[1] 255 | assert.Equal(t, 2, len(polygon.Rings)) 256 | 257 | lr = polygon.Rings[0] 258 | 259 | assert.Equal(t, 6, len(lr.Coordinates)) 260 | 261 | assert.InDelta(t, 20, lr.Coordinates[0][0], 1e-9) 262 | assert.InDelta(t, 35, lr.Coordinates[0][1], 1e-9) 263 | 264 | assert.InDelta(t, 10, lr.Coordinates[1][0], 1e-9) 265 | assert.InDelta(t, 30, lr.Coordinates[1][1], 1e-9) 266 | 267 | assert.InDelta(t, 10, lr.Coordinates[2][0], 1e-9) 268 | assert.InDelta(t, 10, lr.Coordinates[2][1], 1e-9) 269 | 270 | assert.InDelta(t, 30, lr.Coordinates[3][0], 1e-9) 271 | assert.InDelta(t, 5, lr.Coordinates[3][1], 1e-9) 272 | 273 | assert.InDelta(t, 45, lr.Coordinates[4][0], 1e-9) 274 | assert.InDelta(t, 20, lr.Coordinates[4][1], 1e-9) 275 | 276 | assert.InDelta(t, 20, lr.Coordinates[5][0], 1e-9) 277 | assert.InDelta(t, 35, lr.Coordinates[5][1], 1e-9) 278 | 279 | lr = polygon.Rings[1] 280 | 281 | assert.Equal(t, 4, len(lr.Coordinates)) 282 | 283 | assert.InDelta(t, 30, lr.Coordinates[0][0], 1e-9) 284 | assert.InDelta(t, 20, lr.Coordinates[0][1], 1e-9) 285 | 286 | assert.InDelta(t, 20, lr.Coordinates[1][0], 1e-9) 287 | assert.InDelta(t, 15, lr.Coordinates[1][1], 1e-9) 288 | 289 | assert.InDelta(t, 20, lr.Coordinates[2][0], 1e-9) 290 | assert.InDelta(t, 25, lr.Coordinates[2][1], 1e-9) 291 | 292 | assert.InDelta(t, 30, lr.Coordinates[3][0], 1e-9) 293 | assert.InDelta(t, 20, lr.Coordinates[3][1], 1e-9) 294 | 295 | } 296 | } 297 | 298 | func TestMultiLineString(t *testing.T) { 299 | datasets := []struct { 300 | data string 301 | srid uint32 302 | dim geom.Dimension 303 | }{ 304 | {"002000000500006c340000000200000000020000000340240000000000004024000000000000403400000000000040340000000000004024000000000000404400000000000000000000020000000440440000000000004044000000000000403e000000000000403e00000000000040440000000000004034000000000000403e0000000000004024000000000000", 27700, geom.XY}, // ewkb_xdr 305 | {"0105000020346c000002000000010200000003000000000000000000244000000000000024400000000000003440000000000000344000000000000024400000000000004440010200000004000000000000000000444000000000000044400000000000003e400000000000003e40000000000000444000000000000034400000000000003e400000000000002440", 27700, geom.XY}, // ewkb_hdr 306 | {"00000000050000000200000000020000000340240000000000004024000000000000403400000000000040340000000000004024000000000000404400000000000000000000020000000440440000000000004044000000000000403e000000000000403e00000000000040440000000000004034000000000000403e0000000000004024000000000000", 0, geom.XY}, // wkb_hdr 307 | {"010500000002000000010200000003000000000000000000244000000000000024400000000000003440000000000000344000000000000024400000000000004440010200000004000000000000000000444000000000000044400000000000003e400000000000003e40000000000000444000000000000034400000000000003e400000000000002440", 0, geom.XY}, // wkb_xdr 308 | } 309 | 310 | for _, dataset := range datasets { 311 | t.Log("Decoding HEX String: DIM = ", dataset.dim, ", Data = ", dataset.data) 312 | data, err := hex.DecodeString(dataset.data) 313 | 314 | if err != nil { 315 | t.Fatal("Failed to decode HEX string: err = ", err) 316 | } 317 | 318 | r := bytes.NewReader(data) 319 | 320 | g, err := Decode(r) 321 | 322 | if err != nil { 323 | t.Fatal("Failed to parse MultiLineString: err = ", err) 324 | } 325 | 326 | assert.Equal(t, "multilinestring", g.Type()) 327 | 328 | mlstring := g.(*geom.MultiLineString) 329 | 330 | t.Log(mlstring) 331 | 332 | assert.Equal(t, dataset.dim, mlstring.Dimension(), "Expected dim %v, but got %v ", dataset.dim, g.Dimension()) 333 | assert.Equal(t, dataset.srid, mlstring.SRID(), "Expected srid %v, but got %v ", dataset.srid, g.SRID()) 334 | 335 | assert.Equal(t, 2, len(mlstring.LineStrings)) 336 | 337 | lstring := mlstring.LineStrings[0] 338 | 339 | assert.Equal(t, 3, len(lstring.Coordinates)) 340 | 341 | assert.InDelta(t, 10, lstring.Coordinates[0][0], 1e-9) 342 | assert.InDelta(t, 10, lstring.Coordinates[0][1], 1e-9) 343 | 344 | assert.InDelta(t, 20, lstring.Coordinates[1][0], 1e-9) 345 | assert.InDelta(t, 20, lstring.Coordinates[1][1], 1e-9) 346 | 347 | assert.InDelta(t, 10, lstring.Coordinates[2][0], 1e-9) 348 | assert.InDelta(t, 40, lstring.Coordinates[2][1], 1e-9) 349 | 350 | lstring = mlstring.LineStrings[1] 351 | 352 | assert.Equal(t, 4, len(lstring.Coordinates)) 353 | 354 | assert.InDelta(t, 40, lstring.Coordinates[0][0], 1e-9) 355 | assert.InDelta(t, 40, lstring.Coordinates[0][1], 1e-9) 356 | 357 | assert.InDelta(t, 30, lstring.Coordinates[1][0], 1e-9) 358 | assert.InDelta(t, 30, lstring.Coordinates[1][1], 1e-9) 359 | 360 | assert.InDelta(t, 40, lstring.Coordinates[2][0], 1e-9) 361 | assert.InDelta(t, 20, lstring.Coordinates[2][1], 1e-9) 362 | 363 | assert.InDelta(t, 30, lstring.Coordinates[3][0], 1e-9) 364 | assert.InDelta(t, 10, lstring.Coordinates[3][1], 1e-9) 365 | } 366 | } 367 | 368 | func TestMultiPoint(t *testing.T) { 369 | datasets := []struct { 370 | data string 371 | srid uint32 372 | dim geom.Dimension 373 | }{ 374 | {"002000000400006c340000000400000000014024000000000000404400000000000000000000014044000000000000403e0000000000000000000001403400000000000040340000000000000000000001403e0000000000004024000000000000", 27700, geom.XY}, // ewkb_xdr 375 | {"0104000020346c000004000000010100000000000000000024400000000000004440010100000000000000000044400000000000003e4001010000000000000000003440000000000000344001010000000000000000003e400000000000002440", 27700, geom.XY}, // ewkb_hdr 376 | {"00000000040000000400000000014024000000000000404400000000000000000000014044000000000000403e0000000000000000000001403400000000000040340000000000000000000001403e0000000000004024000000000000", 0, geom.XY}, // wkb_hdr 377 | {"010400000004000000010100000000000000000024400000000000004440010100000000000000000044400000000000003e4001010000000000000000003440000000000000344001010000000000000000003e400000000000002440", 0, geom.XY}, // wkb_xdr 378 | } 379 | 380 | for _, dataset := range datasets { 381 | t.Log("Decoding HEX String: DIM = ", dataset.dim, ", Data = ", dataset.data) 382 | data, err := hex.DecodeString(dataset.data) 383 | 384 | if err != nil { 385 | t.Fatal("Failed to decode HEX string: err = ", err) 386 | } 387 | 388 | r := bytes.NewReader(data) 389 | 390 | g, err := Decode(r) 391 | 392 | if err != nil { 393 | t.Fatal("Failed to parse MultiPoint: err = ", err) 394 | } 395 | 396 | assert.Equal(t, "multipoint", g.Type()) 397 | 398 | mpoint := g.(*geom.MultiPoint) 399 | 400 | t.Log(mpoint) 401 | 402 | assert.Equal(t, dataset.dim, mpoint.Dimension(), "Expected dim %v, but got %v ", dataset.dim, g.Dimension()) 403 | assert.Equal(t, dataset.srid, mpoint.SRID(), "Expected srid %v, but got %v ", dataset.srid, g.SRID()) 404 | 405 | assert.Equal(t, 4, len(mpoint.Points)) 406 | 407 | assert.InDelta(t, 10, mpoint.Points[0].Coordinate[0], 1e-9) 408 | assert.InDelta(t, 40, mpoint.Points[0].Coordinate[1], 1e-9) 409 | 410 | assert.InDelta(t, 40, mpoint.Points[1].Coordinate[0], 1e-9) 411 | assert.InDelta(t, 30, mpoint.Points[1].Coordinate[1], 1e-9) 412 | 413 | assert.InDelta(t, 20, mpoint.Points[2].Coordinate[0], 1e-9) 414 | assert.InDelta(t, 20, mpoint.Points[2].Coordinate[1], 1e-9) 415 | 416 | assert.InDelta(t, 30, mpoint.Points[3].Coordinate[0], 1e-9) 417 | assert.InDelta(t, 10, mpoint.Points[3].Coordinate[1], 1e-9) 418 | } 419 | } 420 | 421 | func TestHolePolygon(t *testing.T) { 422 | datasets := []struct { 423 | data string 424 | srid uint32 425 | dim geom.Dimension 426 | }{ 427 | {"002000000300006c3400000002000000054041800000000000402400000000000040468000000000004046800000000000402e00000000000040440000000000004024000000000000403400000000000040418000000000004024000000000000000000044034000000000000403e00000000000040418000000000004041800000000000403e00000000000040340000000000004034000000000000403e000000000000", 27700, geom.XY}, // ewkb_xdr 428 | {"0103000020346c0000020000000500000000000000008041400000000000002440000000000080464000000000008046400000000000002e40000000000000444000000000000024400000000000003440000000000080414000000000000024400400000000000000000034400000000000003e40000000000080414000000000008041400000000000003e40000000000000344000000000000034400000000000003e40", 27700, geom.XY}, // ewkb_hdr 429 | {"000000000300000002000000054041800000000000402400000000000040468000000000004046800000000000402e00000000000040440000000000004024000000000000403400000000000040418000000000004024000000000000000000044034000000000000403e00000000000040418000000000004041800000000000403e00000000000040340000000000004034000000000000403e000000000000", 0, geom.XY}, // wkb_hdr 430 | {"0103000000020000000500000000000000008041400000000000002440000000000080464000000000008046400000000000002e40000000000000444000000000000024400000000000003440000000000080414000000000000024400400000000000000000034400000000000003e40000000000080414000000000008041400000000000003e40000000000000344000000000000034400000000000003e40", 0, geom.XY}, // wkb_xdr 431 | } 432 | 433 | for _, dataset := range datasets { 434 | t.Log("Decoding HEX String: DIM = ", dataset.dim, ", Data = ", dataset.data) 435 | data, err := hex.DecodeString(dataset.data) 436 | 437 | if err != nil { 438 | t.Fatal("Failed to decode HEX string: err = ", err) 439 | } 440 | 441 | r := bytes.NewReader(data) 442 | 443 | g, err := Decode(r) 444 | 445 | if err != nil { 446 | t.Fatal("Failed to parse HolePolygon: err = ", err) 447 | } 448 | 449 | assert.Equal(t, "polygon", g.Type()) 450 | 451 | polygon := g.(*geom.Polygon) 452 | 453 | t.Log(polygon) 454 | 455 | assert.Equal(t, dataset.dim, polygon.Dimension(), "Expected dim %v, but got %v ", dataset.dim, g.Dimension()) 456 | assert.Equal(t, dataset.srid, polygon.SRID(), "Expected srid %v, but got %v ", dataset.srid, g.SRID()) 457 | 458 | assert.Equal(t, 2, len(polygon.Rings)) 459 | 460 | lr := polygon.Rings[0] 461 | 462 | assert.Equal(t, 5, len(lr.Coordinates)) 463 | 464 | assert.InDelta(t, 35, lr.Coordinates[0][0], 1e-9) 465 | assert.InDelta(t, 10, lr.Coordinates[0][1], 1e-9) 466 | 467 | assert.InDelta(t, 45, lr.Coordinates[1][0], 1e-9) 468 | assert.InDelta(t, 45, lr.Coordinates[1][1], 1e-9) 469 | 470 | assert.InDelta(t, 15, lr.Coordinates[2][0], 1e-9) 471 | assert.InDelta(t, 40, lr.Coordinates[2][1], 1e-9) 472 | 473 | assert.InDelta(t, 10, lr.Coordinates[3][0], 1e-9) 474 | assert.InDelta(t, 20, lr.Coordinates[3][1], 1e-9) 475 | 476 | assert.InDelta(t, 35, lr.Coordinates[4][0], 1e-9) 477 | assert.InDelta(t, 10, lr.Coordinates[4][1], 1e-9) 478 | 479 | lr = polygon.Rings[1] 480 | 481 | assert.Equal(t, 4, len(lr.Coordinates)) 482 | 483 | assert.InDelta(t, 20, lr.Coordinates[0][0], 1e-9) 484 | assert.InDelta(t, 30, lr.Coordinates[0][1], 1e-9) 485 | 486 | assert.InDelta(t, 35, lr.Coordinates[1][0], 1e-9) 487 | assert.InDelta(t, 35, lr.Coordinates[1][1], 1e-9) 488 | 489 | assert.InDelta(t, 30, lr.Coordinates[2][0], 1e-9) 490 | assert.InDelta(t, 20, lr.Coordinates[2][1], 1e-9) 491 | 492 | assert.InDelta(t, 20, lr.Coordinates[3][0], 1e-9) 493 | assert.InDelta(t, 30, lr.Coordinates[3][1], 1e-9) 494 | } 495 | } 496 | 497 | func TestPolygon(t *testing.T) { 498 | datasets := []struct { 499 | data string 500 | srid uint32 501 | dim geom.Dimension 502 | }{ 503 | {"002000000300006c340000000100000005403e0000000000004024000000000000404400000000000040440000000000004034000000000000404400000000000040240000000000004034000000000000403e0000000000004024000000000000", 27700, geom.XY}, // ewkb_xdr 504 | {"0103000020346c000001000000050000000000000000003e4000000000000024400000000000004440000000000000444000000000000034400000000000004440000000000000244000000000000034400000000000003e400000000000002440", 27700, geom.XY}, // ewkb_hdr 505 | {"00000000030000000100000005403e0000000000004024000000000000404400000000000040440000000000004034000000000000404400000000000040240000000000004034000000000000403e0000000000004024000000000000", 0, geom.XY}, // wkb_hdr 506 | {"010300000001000000050000000000000000003e4000000000000024400000000000004440000000000000444000000000000034400000000000004440000000000000244000000000000034400000000000003e400000000000002440", 0, geom.XY}, // wkb_xdr 507 | } 508 | 509 | for _, dataset := range datasets { 510 | t.Log("Decoding HEX String: DIM = ", dataset.dim, ", Data = ", dataset.data) 511 | data, err := hex.DecodeString(dataset.data) 512 | 513 | if err != nil { 514 | t.Fatal("Failed to decode HEX string: err = ", err) 515 | } 516 | 517 | r := bytes.NewReader(data) 518 | 519 | g, err := Decode(r) 520 | 521 | if err != nil { 522 | t.Fatal("Failed to parse Polygon: err = ", err) 523 | } 524 | 525 | assert.Equal(t, "polygon", g.Type()) 526 | 527 | polygon := g.(*geom.Polygon) 528 | 529 | t.Log(polygon) 530 | 531 | assert.Equal(t, dataset.dim, polygon.Dimension(), "Expected dim %v, but got %v ", dataset.dim, g.Dimension()) 532 | assert.Equal(t, dataset.srid, polygon.SRID(), "Expected srid %v, but got %v ", dataset.srid, g.SRID()) 533 | 534 | assert.Equal(t, 1, len(polygon.Rings)) 535 | 536 | lr := polygon.Rings[0] 537 | 538 | assert.Equal(t, 5, len(lr.Coordinates)) 539 | 540 | assert.InDelta(t, 30, lr.Coordinates[0][0], 1e-9) 541 | assert.InDelta(t, 10, lr.Coordinates[0][1], 1e-9) 542 | 543 | assert.InDelta(t, 40, lr.Coordinates[1][0], 1e-9) 544 | assert.InDelta(t, 40, lr.Coordinates[1][1], 1e-9) 545 | 546 | assert.InDelta(t, 20, lr.Coordinates[2][0], 1e-9) 547 | assert.InDelta(t, 40, lr.Coordinates[2][1], 1e-9) 548 | 549 | assert.InDelta(t, 10, lr.Coordinates[3][0], 1e-9) 550 | assert.InDelta(t, 20, lr.Coordinates[3][1], 1e-9) 551 | 552 | assert.InDelta(t, 30, lr.Coordinates[4][0], 1e-9) 553 | assert.InDelta(t, 10, lr.Coordinates[4][1], 1e-9) 554 | } 555 | } 556 | 557 | func TestLineString(t *testing.T) { 558 | datasets := []struct { 559 | data string 560 | srid uint32 561 | dim geom.Dimension 562 | }{ 563 | {"002000000200006c3400000003403e00000000000040240000000000004024000000000000403e00000000000040440000000000004044000000000000", 27700, geom.XY}, // ewkb_xdr 564 | {"0102000020346c0000030000000000000000003e40000000000000244000000000000024400000000000003e4000000000000044400000000000004440", 27700, geom.XY}, // ewkb_hdr 565 | {"000000000200000003403e00000000000040240000000000004024000000000000403e00000000000040440000000000004044000000000000", 0, geom.XY}, // wkb_hdr 566 | {"0102000000030000000000000000003e40000000000000244000000000000024400000000000003e4000000000000044400000000000004440", 0, geom.XY}, // wkb_xdr 567 | } 568 | 569 | for _, dataset := range datasets { 570 | t.Log("Decoding HEX String: DIM = ", dataset.dim, ", Data = ", dataset.data) 571 | data, err := hex.DecodeString(dataset.data) 572 | 573 | if err != nil { 574 | t.Fatal("Failed to decode HEX string: err = ", err) 575 | } 576 | 577 | r := bytes.NewReader(data) 578 | 579 | g, err := Decode(r) 580 | 581 | if err != nil { 582 | t.Fatal("Failed to parse LineString: err = ", err) 583 | } 584 | 585 | assert.Equal(t, "linestring", g.Type()) 586 | 587 | lstring := g.(*geom.LineString) 588 | 589 | t.Log(lstring) 590 | 591 | assert.Equal(t, dataset.dim, lstring.Dimension(), "Expected dim %v, but got %v ", dataset.dim, g.Dimension) 592 | assert.Equal(t, dataset.srid, lstring.SRID(), "Expected srid %v, but got %v ", dataset.srid, g.SRID()) 593 | assert.Equal(t, 3, len(lstring.Coordinates)) 594 | 595 | assert.InDelta(t, 30, lstring.Coordinates[0][0], 1e-9) 596 | assert.InDelta(t, 10, lstring.Coordinates[0][1], 1e-9) 597 | 598 | assert.InDelta(t, 10, lstring.Coordinates[1][0], 1e-9) 599 | assert.InDelta(t, 30, lstring.Coordinates[1][1], 1e-9) 600 | 601 | assert.InDelta(t, 40, lstring.Coordinates[2][0], 1e-9) 602 | assert.InDelta(t, 40, lstring.Coordinates[2][1], 1e-9) 603 | } 604 | } 605 | 606 | func TestDimensionsAndEndian(t *testing.T) { 607 | 608 | datasets := []struct { 609 | data string 610 | srid uint32 611 | dim geom.Dimension 612 | points []float64 613 | }{ 614 | // XY 615 | {"00000000013ff00000000000003ff0000000000000", 0, geom.XY, []float64{1, 1}}, // EWKB XDR 616 | {"0101000000000000000000f03f000000000000f03f", 0, geom.XY, []float64{1, 1}}, // EWKB HDR 617 | {"00000000013ff00000000000003ff0000000000000", 0, geom.XY, []float64{1, 1}}, // WKB XDR 618 | {"0101000000000000000000f03f000000000000f03f", 0, geom.XY, []float64{1, 1}}, // WKB HDR 619 | 620 | // XYS 621 | {"002000000100006c343ff00000000000003ff0000000000000", 27700, geom.XY, []float64{1, 1}}, // EWKB XDR 622 | {"0101000020346c0000000000000000f03f000000000000f03f", 27700, geom.XY, []float64{1, 1}}, // EWKB HDR 623 | {"00000000013ff00000000000003ff0000000000000", 0, geom.XY, []float64{1, 1}}, // WKB XDR 624 | {"0101000000000000000000f03f000000000000f03f", 0, geom.XY, []float64{1, 1}}, // WKB HDR 625 | 626 | // XYZ 627 | {"00800000013ff00000000000003ff00000000000003ff0000000000000", 0, geom.XYZ, []float64{1, 1, 1}}, // EWKB XDR 628 | {"0101000080000000000000f03f000000000000f03f000000000000f03f", 0, geom.XYZ, []float64{1, 1, 1}}, // EWKB HDR 629 | {"00000003e93ff00000000000003ff00000000000003ff0000000000000", 0, geom.XYZ, []float64{1, 1, 1}}, // WKB XDR 630 | {"01e9030000000000000000f03f000000000000f03f000000000000f03f", 0, geom.XYZ, []float64{1, 1, 1}}, // WKB HDR 631 | 632 | // XYZS 633 | {"00a000000100006c343ff00000000000003ff00000000000003ff0000000000000", 27700, geom.XYZ, []float64{1, 1, 1}}, // EWKB XDR 634 | {"01010000a0346c0000000000000000f03f000000000000f03f000000000000f03f", 27700, geom.XYZ, []float64{1, 1, 1}}, // EWKB HDR 635 | {"00000003e93ff00000000000003ff00000000000003ff0000000000000", 0, geom.XYZ, []float64{1, 1, 1}}, // WKB XDR 636 | {"01e9030000000000000000f03f000000000000f03f000000000000f03f", 0, geom.XYZ, []float64{1, 1, 1}}, // WKB HDR 637 | 638 | // XYM 639 | {"00400000013ff00000000000003ff00000000000003ff0000000000000", 0, geom.XYM, []float64{1, 1, 1}}, // EWKB XDR 640 | {"0101000040000000000000f03f000000000000f03f000000000000f03f", 0, geom.XYM, []float64{1, 1, 1}}, // EWKB HDR 641 | {"00000007d13ff00000000000003ff00000000000003ff0000000000000", 0, geom.XYM, []float64{1, 1, 1}}, // WKB XDR 642 | {"01d1070000000000000000f03f000000000000f03f000000000000f03f", 0, geom.XYM, []float64{1, 1, 1}}, // WKB HDR 643 | 644 | // XYMS 645 | {"006000000100006c343ff00000000000003ff00000000000003ff0000000000000", 27700, geom.XYM, []float64{1, 1, 1}}, // EWKB XDR 646 | {"0101000060346c0000000000000000f03f000000000000f03f000000000000f03f", 27700, geom.XYM, []float64{1, 1, 1}}, // EWKB HDR 647 | {"00000007d13ff00000000000003ff00000000000003ff0000000000000", 0, geom.XYM, []float64{1, 1, 1}}, // WKB XDR 648 | {"01d1070000000000000000f03f000000000000f03f000000000000f03f", 0, geom.XYM, []float64{1, 1, 1}}, // WKB HDR 649 | 650 | // XYZM 651 | {"00c00000013ff00000000000003ff00000000000003ff00000000000003ff0000000000000", 0, geom.XYZM, []float64{1, 1, 1, 1}}, // EWKB XDR 652 | {"01010000c0000000000000f03f000000000000f03f000000000000f03f000000000000f03f", 0, geom.XYZM, []float64{1, 1, 1, 1}}, // EWKB HDR 653 | {"0000000bb93ff00000000000003ff00000000000003ff00000000000003ff0000000000000", 0, geom.XYZM, []float64{1, 1, 1, 1}}, // WKB XDR 654 | {"01b90b0000000000000000f03f000000000000f03f000000000000f03f000000000000f03f", 0, geom.XYZM, []float64{1, 1, 1, 1}}, // WKB HDR 655 | 656 | // XYZM 657 | {"00e000000100006c343ff00000000000003ff00000000000003ff00000000000003ff0000000000000", 27700, geom.XYZM, []float64{1, 1, 1, 1}}, // EWKB XDR 658 | {"01010000e0346c0000000000000000f03f000000000000f03f000000000000f03f000000000000f03f", 27700, geom.XYZM, []float64{1, 1, 1, 1}}, // EWKB HDR 659 | {"0000000bb93ff00000000000003ff00000000000003ff00000000000003ff0000000000000", 0, geom.XYZM, []float64{1, 1, 1, 1}}, // WKB XDR 660 | {"01b90b0000000000000000f03f000000000000f03f000000000000f03f000000000000f03f", 0, geom.XYZM, []float64{1, 1, 1, 1}}, // WKB HDR 661 | } 662 | 663 | for _, dataset := range datasets { 664 | t.Log("Decoding HEX String: DIM = ", dataset.dim, ", Data = ", dataset.data) 665 | data, err := hex.DecodeString(dataset.data) 666 | 667 | if err != nil { 668 | t.Fatal("Failed to decode HEX string: err = ", err) 669 | } 670 | 671 | r := bytes.NewReader(data) 672 | 673 | g, err := Decode(r) 674 | 675 | if err != nil { 676 | t.Fatal("Failed to parse geom: err = ", err) 677 | } 678 | 679 | assert.Equal(t, "point", g.Type()) 680 | 681 | point := g.(*geom.Point) 682 | 683 | t.Log(point) 684 | 685 | assert.Equal(t, dataset.dim, point.Dimension(), "Expected dim %s, but got %s", dataset.dim, g.Dimension()) 686 | assert.Equal(t, dataset.srid, point.SRID(), "Expected srid %v, but got %v ", dataset.srid, g.SRID()) 687 | assert.Equal(t, len(dataset.points), len(point.Coordinate)) 688 | 689 | for idx := 0; idx < len(dataset.points); idx++ { 690 | assert.InDelta(t, dataset.points[idx], point.Coordinate[idx], 1e-9) 691 | } 692 | } 693 | } 694 | -------------------------------------------------------------------------------- /ewkb/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2015] Alex Davies-Moore 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | /* 18 | Package ewkb provides the tools for parsing WKB and Postgis EWKB representations of geometry data. This package 19 | current supports the OGC simple feature types only. 20 | */ 21 | package ewkb 22 | -------------------------------------------------------------------------------- /ewkb/encoder.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2015] Alex Davies-Moore 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ewkb 18 | 19 | import ( 20 | "encoding/binary" 21 | "io" 22 | 23 | "github.com/devork/geom" 24 | ) 25 | 26 | type encoder struct { 27 | w io.Writer 28 | o binary.ByteOrder 29 | } 30 | 31 | func (d *encoder) write(data interface{}) error { 32 | return binary.Write(d.w, d.o, data) 33 | } 34 | 35 | //Encode will take the given geometry and write to the specified writer 36 | func Encode(g geom.Geometry, w io.Writer) error { 37 | 38 | if g == nil { 39 | return geom.ErrNoGeometry 40 | } 41 | 42 | e := &encoder{w, binary.BigEndian} 43 | 44 | err := marshalHdr(g, e) 45 | 46 | if err != nil { 47 | return err 48 | } 49 | 50 | return marshal(g, e) 51 | } 52 | 53 | func marshalPoint(p *geom.Point, e *encoder) error { 54 | return marshalCoord(&(*p).Coordinate, e) 55 | } 56 | 57 | func marshalMultiPoint(mp *geom.MultiPoint, e *encoder) error { 58 | err := e.write(uint32(len(mp.Points))) 59 | 60 | if err != nil { 61 | return err 62 | } 63 | 64 | for _, point := range mp.Points { 65 | err = marshalHdr(&point, e) 66 | 67 | if err != nil { 68 | return err 69 | } 70 | 71 | err = marshalPoint(&point, e) 72 | 73 | if err != nil { 74 | return err 75 | } 76 | } 77 | 78 | return nil 79 | } 80 | 81 | func marshalLineString(l *geom.LineString, e *encoder) error { 82 | err := e.write(uint32(len(l.Coordinates))) 83 | 84 | if err != nil { 85 | return err 86 | } 87 | 88 | for _, coord := range l.Coordinates { 89 | err = marshalCoord(&coord, e) 90 | 91 | if err != nil { 92 | return err 93 | } 94 | } 95 | 96 | return nil 97 | } 98 | 99 | func marshalMultiLineString(ml *geom.MultiLineString, e *encoder) error { 100 | err := e.write(uint32(len(ml.LineStrings))) 101 | if err != nil { 102 | return err 103 | } 104 | 105 | for _, ls := range ml.LineStrings { 106 | err = marshalHdr(&ls, e) 107 | 108 | if err != nil { 109 | return err 110 | } 111 | 112 | err = marshalLineString(&ls, e) 113 | 114 | if err != nil { 115 | return err 116 | } 117 | } 118 | 119 | return nil 120 | } 121 | 122 | func marshalPolygon(p *geom.Polygon, e *encoder) error { 123 | err := e.write(uint32(len(p.Rings))) 124 | 125 | if err != nil { 126 | return err 127 | } 128 | 129 | for _, ring := range p.Rings { 130 | err = marshalLinearRing(&ring, e) 131 | 132 | if err != nil { 133 | return err 134 | } 135 | } 136 | 137 | return nil 138 | } 139 | 140 | func marshalMultiPolygon(mp *geom.MultiPolygon, e *encoder) error { 141 | err := e.write(uint32(len(mp.Polygons))) 142 | 143 | if err != nil { 144 | return err 145 | } 146 | 147 | for _, polygon := range mp.Polygons { 148 | 149 | err = marshalHdr(&polygon, e) 150 | 151 | if err != nil { 152 | return err 153 | } 154 | 155 | err = marshalPolygon(&polygon, e) 156 | 157 | if err != nil { 158 | return err 159 | } 160 | } 161 | 162 | return nil 163 | } 164 | 165 | func marshalGeometryCollection(gc *geom.GeometryCollection, e *encoder) error { 166 | err := e.write(uint32(len(gc.Geometries))) 167 | 168 | if err != nil { 169 | return err 170 | } 171 | 172 | for _, g := range gc.Geometries { 173 | err = marshalHdr(g, e) 174 | 175 | if err != nil { 176 | return err 177 | } 178 | 179 | err = marshal(g, e) 180 | 181 | if err != nil { 182 | return err 183 | } 184 | 185 | } 186 | 187 | return nil 188 | } 189 | 190 | func marshalLinearRing(l *geom.LinearRing, e *encoder) error { 191 | err := e.write(uint32(len(l.Coordinates))) 192 | 193 | if err != nil { 194 | return err 195 | } 196 | 197 | for _, coord := range l.Coordinates { 198 | err = marshalCoord(&coord, e) 199 | 200 | if err != nil { 201 | return err 202 | } 203 | } 204 | 205 | return nil 206 | } 207 | 208 | func marshalCoord(c *geom.Coordinate, e *encoder) error { 209 | var err error 210 | for idx := 0; idx < len(*c); idx++ { 211 | err = e.write((*c)[idx]) 212 | 213 | if err != nil { 214 | return err 215 | } 216 | } 217 | 218 | return nil 219 | } 220 | 221 | func marshalHdr(g geom.Geometry, e *encoder) error { 222 | err := e.write(bigEndian) 223 | 224 | if err != nil { 225 | return err 226 | } 227 | 228 | var field uint32 229 | var gtype geomtype 230 | var writeSrid bool 231 | 232 | switch g.(type) { 233 | case *geom.Point: 234 | gtype = point 235 | case *geom.LineString: 236 | gtype = linestring 237 | case *geom.Polygon: 238 | gtype = polygon 239 | case *geom.MultiPoint: 240 | gtype = multipoint 241 | case *geom.MultiLineString: 242 | gtype = multilinestring 243 | case *geom.MultiPolygon: 244 | gtype = multipolygon 245 | case *geom.GeometryCollection: 246 | gtype = geometrycollection 247 | default: 248 | return geom.ErrUnsupportedGeom 249 | } 250 | 251 | if g.SRID() != 0 { 252 | 253 | writeSrid = true 254 | 255 | switch g.Dimension() { 256 | case geom.XY: 257 | field = uint32(xys) 258 | case geom.XYZ: 259 | field = uint32(xyzs) 260 | case geom.XYM: 261 | field = uint32(xyms) 262 | case geom.XYZM: 263 | field = uint32(xyzms) 264 | default: 265 | return geom.ErrUnknownDim 266 | } 267 | 268 | field <<= 16 269 | field |= uint32(gtype) 270 | } else { 271 | switch g.Dimension() { 272 | case geom.XYZM: 273 | field = uint32(wkbzm) + uint32(gtype) 274 | case geom.XYM: 275 | field = uint32(wkbm) + uint32(gtype) 276 | case geom.XYZ: 277 | field = uint32(wkbz) + uint32(gtype) 278 | case geom.XY: 279 | field = uint32(gtype) 280 | default: 281 | return geom.ErrUnknownDim 282 | } 283 | } 284 | 285 | err = e.write(field) 286 | 287 | if err != nil { 288 | return err 289 | } 290 | 291 | if writeSrid { 292 | return e.write(g.SRID()) 293 | } 294 | 295 | return nil 296 | } 297 | 298 | func marshal(g geom.Geometry, e *encoder) error { 299 | switch g := g.(type) { 300 | case *geom.Point: 301 | return marshalPoint(g, e) 302 | case *geom.LineString: 303 | return marshalLineString(g, e) 304 | case *geom.Polygon: 305 | return marshalPolygon(g, e) 306 | case *geom.MultiPoint: 307 | return marshalMultiPoint(g, e) 308 | case *geom.MultiLineString: 309 | return marshalMultiLineString(g, e) 310 | case *geom.MultiPolygon: 311 | return marshalMultiPolygon(g, e) 312 | case *geom.GeometryCollection: 313 | return marshalGeometryCollection(g, e) 314 | default: 315 | return geom.ErrUnsupportedGeom 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /ewkb/encoder_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2015] Alex Davies-Moore 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ewkb 18 | 19 | import ( 20 | "bytes" 21 | "encoding/hex" 22 | "strings" 23 | "testing" 24 | 25 | "github.com/devork/geom" 26 | "github.com/stretchr/testify/assert" 27 | ) 28 | 29 | func TestEncodePoint(t *testing.T) { 30 | datasets := []struct { 31 | data geom.Geometry 32 | expected string 33 | }{ 34 | {&geom.Point{geom.Hdr{geom.XYZM, 27700}, geom.Coordinate{1, 1, 1, 1}}, "00e000000100006c343ff00000000000003ff00000000000003ff00000000000003ff0000000000000"}, 35 | {&geom.Point{geom.Hdr{geom.XYZM, 0}, geom.Coordinate{1, 1, 1, 1}}, "0000000bb93ff00000000000003ff00000000000003ff00000000000003ff0000000000000"}, 36 | {&geom.Point{geom.Hdr{geom.XYZ, 27700}, geom.Coordinate{1, 1, 1}}, "00a000000100006c343ff00000000000003ff00000000000003ff0000000000000"}, 37 | {&geom.Point{geom.Hdr{geom.XYZ, 0}, geom.Coordinate{1, 1, 1}}, "00000003e93ff00000000000003ff00000000000003ff0000000000000"}, 38 | {&geom.Point{geom.Hdr{geom.XYM, 27700}, geom.Coordinate{1, 1, 1}}, "006000000100006c343ff00000000000003ff00000000000003ff0000000000000"}, 39 | {&geom.Point{geom.Hdr{geom.XYM, 0}, geom.Coordinate{1, 1, 1}}, "00000007d13ff00000000000003ff00000000000003ff0000000000000"}, 40 | {&geom.Point{geom.Hdr{geom.XY, 27700}, geom.Coordinate{1, 1}}, "002000000100006c343ff00000000000003ff0000000000000"}, 41 | {&geom.Point{geom.Hdr{geom.XY, 0}, geom.Coordinate{1, 1}}, "00000000013ff00000000000003ff0000000000000"}, 42 | } 43 | 44 | for _, dataset := range datasets { 45 | var w = new(bytes.Buffer) 46 | err := Encode(dataset.data, w) 47 | 48 | if err != nil { 49 | t.Fatalf("Failed to encode Point geometry: err = %s", err) 50 | } 51 | data := hex.EncodeToString(w.Bytes()) 52 | 53 | assert.Equal(t, dataset.expected, strings.ToLower(data)) 54 | 55 | } 56 | 57 | } 58 | 59 | func TestEncodeLineString(t *testing.T) { 60 | datasets := []struct { 61 | data geom.Geometry 62 | expected string 63 | }{ 64 | {&geom.LineString{geom.Hdr{geom.XY, 27700}, []geom.Coordinate{{30, 10}, {10, 30}, {40, 40}}}, "002000000200006c3400000003403e00000000000040240000000000004024000000000000403e00000000000040440000000000004044000000000000"}, 65 | } 66 | 67 | for _, dataset := range datasets { 68 | var w = new(bytes.Buffer) 69 | err := Encode(dataset.data, w) 70 | 71 | if err != nil { 72 | t.Fatalf("Failed to encode LineString geometry: err = %s", err) 73 | } 74 | data := hex.EncodeToString(w.Bytes()) 75 | 76 | assert.Equal(t, dataset.expected, strings.ToLower(data)) 77 | 78 | } 79 | 80 | } 81 | 82 | func TestEncodePolygon(t *testing.T) { 83 | datasets := []struct { 84 | data geom.Geometry 85 | expected string 86 | }{ 87 | { 88 | &geom.Polygon{ 89 | geom.Hdr{geom.XY, 27700}, 90 | []geom.LinearRing{{ 91 | []geom.Coordinate{ 92 | {30, 10}, 93 | {40, 40}, 94 | {20, 40}, 95 | {10, 20}, 96 | {30, 10}, 97 | }, 98 | }, 99 | }, 100 | }, 101 | "002000000300006c340000000100000005403e0000000000004024000000000000404400000000000040440000000000004034000000000000404400000000000040240000000000004034000000000000403e0000000000004024000000000000"}, 102 | } 103 | 104 | for _, dataset := range datasets { 105 | var w = new(bytes.Buffer) 106 | err := Encode(dataset.data, w) 107 | 108 | if err != nil { 109 | t.Fatalf("Failed to encode Polygon geometry: err = %s", err) 110 | } 111 | data := hex.EncodeToString(w.Bytes()) 112 | 113 | assert.Equal(t, dataset.expected, strings.ToLower(data)) 114 | 115 | } 116 | 117 | } 118 | 119 | func TestEncodeMultiPoint(t *testing.T) { 120 | datasets := []struct { 121 | data geom.Geometry 122 | expected string 123 | }{ 124 | { 125 | &geom.MultiPoint{geom.Hdr{geom.XY, 27700}, []geom.Point{ 126 | geom.Point{geom.Hdr{geom.XY, 0}, geom.Coordinate{10, 40}}, 127 | geom.Point{geom.Hdr{geom.XY, 0}, geom.Coordinate{40, 30}}, 128 | geom.Point{geom.Hdr{geom.XY, 0}, geom.Coordinate{20, 20}}, 129 | geom.Point{geom.Hdr{geom.XY, 0}, geom.Coordinate{30, 10}}, 130 | }}, 131 | "002000000400006c340000000400000000014024000000000000404400000000000000000000014044000000000000403e0000000000000000000001403400000000000040340000000000000000000001403e0000000000004024000000000000", 132 | }, 133 | } 134 | 135 | for _, dataset := range datasets { 136 | var w = new(bytes.Buffer) 137 | err := Encode(dataset.data, w) 138 | 139 | if err != nil { 140 | t.Fatalf("Failed to encode MultiPoint geometry: err = %s", err) 141 | } 142 | data := hex.EncodeToString(w.Bytes()) 143 | 144 | assert.Equal(t, dataset.expected, strings.ToLower(data)) 145 | 146 | } 147 | 148 | } 149 | 150 | func TestEncodeMultiLineString(t *testing.T) { 151 | 152 | datasets := []struct { 153 | data geom.Geometry 154 | expected string 155 | }{ 156 | { 157 | &geom.MultiLineString{geom.Hdr{geom.XY, 27700}, []geom.LineString{ 158 | geom.LineString{geom.Hdr{geom.XY, 0}, []geom.Coordinate{{10, 10}, {20, 20}, {10, 40}}}, 159 | geom.LineString{geom.Hdr{geom.XY, 0}, []geom.Coordinate{{40, 40}, {30, 30}, {40, 20}, {30, 10}}}, 160 | }}, 161 | "002000000500006c340000000200000000020000000340240000000000004024000000000000403400000000000040340000000000004024000000000000404400000000000000000000020000000440440000000000004044000000000000403e000000000000403e00000000000040440000000000004034000000000000403e0000000000004024000000000000", 162 | }, 163 | } 164 | 165 | for _, dataset := range datasets { 166 | var w = new(bytes.Buffer) 167 | err := Encode(dataset.data, w) 168 | 169 | if err != nil { 170 | t.Fatalf("Failed to encode MultiPoint geometry: err = %s", err) 171 | } 172 | data := hex.EncodeToString(w.Bytes()) 173 | 174 | assert.Equal(t, dataset.expected, strings.ToLower(data)) 175 | 176 | } 177 | } 178 | 179 | func TestEncodeMultiPolygon(t *testing.T) { 180 | 181 | datasets := []struct { 182 | data geom.Geometry 183 | expected string 184 | }{ 185 | { 186 | &geom.MultiPolygon{geom.Hdr{geom.XY, 27700}, []geom.Polygon{ 187 | geom.Polygon{ 188 | geom.Hdr{geom.XY, 0}, 189 | []geom.LinearRing{{ 190 | []geom.Coordinate{ 191 | {40, 40}, 192 | {20, 45}, 193 | {45, 30}, 194 | {40, 40}, 195 | }, 196 | }}, 197 | }, 198 | geom.Polygon{ 199 | geom.Hdr{geom.XY, 0}, 200 | []geom.LinearRing{ 201 | { 202 | []geom.Coordinate{ 203 | {20, 35}, {10, 30}, {10, 10}, {30, 5}, {45, 20}, {20, 35}, 204 | }, 205 | }, 206 | { 207 | []geom.Coordinate{ 208 | {30, 20}, {20, 15}, {20, 25}, {30, 20}, 209 | }, 210 | }, 211 | }, 212 | }, 213 | }}, 214 | "002000000600006c34000000020000000003000000010000000440440000000000004044000000000000403400000000000040468000000000004046800000000000403e0000000000004044000000000000404400000000000000000000030000000200000006403400000000000040418000000000004024000000000000403e00000000000040240000000000004024000000000000403e0000000000004014000000000000404680000000000040340000000000004034000000000000404180000000000000000004403e00000000000040340000000000004034000000000000402e00000000000040340000000000004039000000000000403e0000000000004034000000000000", 215 | }, 216 | } 217 | 218 | for _, dataset := range datasets { 219 | var w = new(bytes.Buffer) 220 | err := Encode(dataset.data, w) 221 | 222 | if err != nil { 223 | t.Fatalf("Failed to encode MultiPolygon geometry: err = %s", err) 224 | } 225 | data := hex.EncodeToString(w.Bytes()) 226 | 227 | assert.Equal(t, dataset.expected, strings.ToLower(data)) 228 | 229 | } 230 | } 231 | 232 | func TestEncodeGeometryCollection(t *testing.T) { 233 | 234 | datasets := []struct { 235 | data geom.Geometry 236 | expected string 237 | }{ 238 | { 239 | &geom.GeometryCollection{geom.Hdr{geom.XY, 27700}, []geom.Geometry{ 240 | &geom.Point{ 241 | geom.Hdr{geom.XY, 0}, 242 | geom.Coordinate{4, 6}, 243 | }, 244 | &geom.LineString{ 245 | geom.Hdr{geom.XY, 0}, 246 | []geom.Coordinate{{4, 6}, {7, 10}}, 247 | }, 248 | }}, 249 | "002000000700006c340000000200000000014010000000000000401800000000000000000000020000000240100000000000004018000000000000401c0000000000004024000000000000", 250 | }, 251 | } 252 | 253 | for _, dataset := range datasets { 254 | var w = new(bytes.Buffer) 255 | err := Encode(dataset.data, w) 256 | 257 | if err != nil { 258 | t.Fatalf("Failed to encode MultiPolygon geometry: err = %s", err) 259 | } 260 | data := hex.EncodeToString(w.Bytes()) 261 | 262 | assert.Equal(t, dataset.expected, strings.ToLower(data)) 263 | 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /ewkb/ewkb.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2015] Alex Davies-Moore 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ewkb 18 | 19 | import "github.com/devork/geom" 20 | 21 | // https://trac.osgeo.org/postgis/browser/trunk/doc/ZMSgeoms.txt 22 | // 23 | // Supported dimensions: 24 | // 25 | // XY 2 dimensional 26 | // XYZ 3 dimensional 27 | // XYZM 4 dimensional 28 | // 29 | // 2.1. Definition of ZM-Geometry 30 | // 31 | // a) A geometry can have either 2, 3 or 4 dimensions. 32 | // b) 3rd dimension of a 3d geometry can either represent Z or M (3DZ or 3DM). 33 | // c) 4d geometries contain both Z and M (in this order). 34 | // d) M and Z values are associated with every vertex. 35 | // e) M and Z values are undefined within surface interiors. 36 | // 37 | // Any ZM-Geometry can be converted into a 2D geometry by discarding all its 38 | // Z and M values. The resulting 2D geometry is the "shadow" of the ZM-Geometry. 39 | // 2D geometries cannot be safely converted into ZM-Geometries, since their Z 40 | // and M values are undefined, and not necessarily zero. 41 | // 42 | // These constants also represent the bit masks for the EWKB dims 43 | const ( 44 | xy dimension = 0x0000 45 | xym dimension = 0x4000 46 | xyz dimension = 0x8000 47 | xyzm dimension = 0xC000 48 | xys dimension = 0x2000 49 | xyms dimension = 0x6000 50 | xyzs dimension = 0xA000 51 | xyzms dimension = 0xE000 52 | unknown dimension = 0xFFFF 53 | ) 54 | 55 | // Geometry types 56 | const ( 57 | geometry geomtype = 0x0000 58 | point geomtype = 0x0001 59 | linestring geomtype = 0x0002 60 | polygon geomtype = 0x0003 61 | multipoint geomtype = 0x0004 62 | multilinestring geomtype = 0x0005 63 | multipolygon geomtype = 0x0006 64 | geometrycollection geomtype = 0x0007 65 | circularstring geomtype = 0x0008 66 | compoundcurve geomtype = 0x0009 67 | curvepolygon geomtype = 0x000a 68 | multicurve geomtype = 0x000b 69 | multisurface geomtype = 0x000c 70 | curve geomtype = 0x000d 71 | surface geomtype = 0x000e 72 | polyhedralsurface geomtype = 0x000f 73 | tin geomtype = 0x0010 74 | triangle geomtype = 0x0011 75 | ) 76 | 77 | // ---------------------------------------------------------------------------- 78 | // Dimension 79 | // ---------------------------------------------------------------------------- 80 | 81 | // Dimension of the geometry 82 | type dimension uint16 83 | 84 | func (d dimension) dim() geom.Dimension { 85 | switch d { 86 | case xy, xys: 87 | return geom.XY 88 | case xyz, xyzs: 89 | return geom.XYZ 90 | case xym, xyms: 91 | return geom.XYM 92 | case xyzm, xyzms: 93 | return geom.XYZM 94 | default: 95 | return geom.UNKNOWN 96 | } 97 | } 98 | 99 | // ---------------------------------------------------------------------------- 100 | // geomType 101 | // ---------------------------------------------------------------------------- 102 | 103 | // geomType is the bitmask of the geom 104 | type geomtype uint16 105 | 106 | func (g geomtype) String() string { 107 | switch g { 108 | case geometry: 109 | return "GEOMETRY" 110 | case point: 111 | return "POINT" 112 | case linestring: 113 | return "LINESTRING" 114 | case polygon: 115 | return "POLYGON" 116 | case multipoint: 117 | return "MULTIPOINT" 118 | case multilinestring: 119 | return "MULTILINESTRING" 120 | case multipolygon: 121 | return "MULTIPOLYGON" 122 | case geometrycollection: 123 | return "GEOMETRYCOLLECTION" 124 | // case CIRCULARSTRING: 125 | // return "CIRCULARSTRING" 126 | // case COMPOUNDCURVE: 127 | // return "COMPOUNDCURVE" 128 | // case CURVEPOLYGON: 129 | // return "CURVEPOLYGON" 130 | // case MULTICURVE: 131 | // return "MULTICURVE" 132 | // case MULTISURFACE: 133 | // return "MULTISURFACE" 134 | // case CURVE: 135 | // return "CURVE" 136 | // case SURFACE: 137 | // return "SURFACE" 138 | // case POLYHEDRALSURFACE: 139 | // return "POLYHEDRALSURFACE " 140 | // case TIN: 141 | // return "TIN" 142 | // case TRIANGLE: 143 | // return "TRIANGLE" 144 | default: 145 | return "UNKNOWN" 146 | } 147 | } 148 | 149 | // WKB extensions for Z, M, and ZM. these extensions are applied to the base geometry types, 150 | // such that a ZM version of a Point = 17 + 3000 = 3017 (0xBC9) 151 | const ( 152 | wkbz uint16 = 1000 153 | wkbm uint16 = 2000 154 | wkbzm uint16 = 3000 155 | ) 156 | 157 | // Big or Little endian identifiers 158 | const ( 159 | bigEndian uint8 = 0x00 160 | ) 161 | -------------------------------------------------------------------------------- /geojson/encoder.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2015] Alex Davies-Moore 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package geojson 18 | 19 | import ( 20 | "bytes" 21 | "io" 22 | "strconv" 23 | 24 | "github.com/devork/geom" 25 | ) 26 | 27 | // snippets of geojson 28 | var ( 29 | pointHdr = []byte(`{"type":"Point","coordinates":`) 30 | linestringHdr = []byte(`{"type":"LineString", "coordinates":`) 31 | multiLineStringHdr = []byte(`{"type":"MultiLineString", "coordinates":`) 32 | polygonHdr = []byte(`{"type":"Polygon", "coordinates":`) 33 | multiPolygonHdr = []byte(`{"type":"MultiPolygon", "coordinates":`) 34 | multiPointHdr = []byte(`{"type":"MultiPoint","coordinates":`) 35 | geomCollectionHdr = []byte(`{"type":"GeometryCollection", "geometries":`) 36 | dquote = []byte(`"`) 37 | comma = []byte(`,`) 38 | lbrace = []byte(`{`) 39 | rbrace = []byte(`}`) 40 | lparen = []byte(`[`) 41 | rparen = []byte(`]`) 42 | ) 43 | 44 | func Encode(g geom.Geometry, w io.Writer) error { 45 | switch g := g.(type) { 46 | case *geom.Point: 47 | return marshalPoint(g, w) 48 | case *geom.MultiPoint: 49 | return marshalMultiPoint(g, w) 50 | case *geom.LineString: 51 | return marshalLineString(g, w) 52 | case *geom.MultiLineString: 53 | return marshalMultiLineString(g, w) 54 | case *geom.Polygon: 55 | return marshalPolygon(g, w) 56 | case *geom.MultiPolygon: 57 | return marshalMultiPolygon(g, w) 58 | case *geom.GeometryCollection: 59 | return marshalGeometryCollection(g, w) 60 | default: 61 | return geom.ErrUnsupportedGeom 62 | } 63 | } 64 | 65 | func marshalPolygon(p *geom.Polygon, w io.Writer) error { 66 | var sb bytes.Buffer 67 | sb.Write(polygonHdr) 68 | sb.Write(lparen) 69 | rlimit := len(p.Rings) - 1 70 | for ridx, lring := range p.Rings { 71 | marshalLinearRing(&lring, &sb) 72 | 73 | if ridx < rlimit { 74 | sb.Write(comma) 75 | } 76 | } 77 | sb.Write(rparen) 78 | sb.Write(rbrace) 79 | 80 | _, err := w.Write(sb.Bytes()) 81 | 82 | return err 83 | } 84 | 85 | func marshalMultiPolygon(mp *geom.MultiPolygon, w io.Writer) error { 86 | var sb bytes.Buffer 87 | sb.Write(multiPolygonHdr) 88 | sb.Write(lparen) 89 | 90 | plimit := len(mp.Polygons) - 1 91 | for pidx, polygon := range mp.Polygons { 92 | sb.Write(lparen) 93 | 94 | rlimit := len(polygon.Rings) - 1 95 | for ridx, lring := range polygon.Rings { 96 | marshalLinearRing(&lring, &sb) 97 | 98 | if ridx < rlimit { 99 | sb.Write(comma) 100 | } 101 | } 102 | 103 | sb.Write(rparen) 104 | 105 | if pidx < plimit { 106 | sb.Write(comma) 107 | } 108 | } 109 | 110 | sb.Write(rparen) 111 | sb.Write(rbrace) 112 | 113 | _, err := w.Write(sb.Bytes()) 114 | 115 | return err 116 | } 117 | 118 | func marshalLineString(ls *geom.LineString, w io.Writer) error { 119 | var sb bytes.Buffer 120 | sb.Write(linestringHdr) 121 | sb.Write(lparen) 122 | limit := len(ls.Coordinates) - 1 123 | for idx, coord := range ls.Coordinates { 124 | marshalCoord(&coord, &sb) 125 | 126 | if idx < limit { 127 | sb.Write(comma) 128 | } 129 | } 130 | sb.Write(rparen) 131 | sb.Write(rbrace) 132 | 133 | _, err := w.Write(sb.Bytes()) 134 | 135 | return err 136 | } 137 | 138 | func marshalMultiLineString(mp *geom.MultiLineString, w io.Writer) error { 139 | var sb bytes.Buffer 140 | sb.Write(multiLineStringHdr) 141 | sb.Write(lparen) 142 | 143 | llimit := len(mp.LineStrings) - 1 144 | for lidx, linestring := range mp.LineStrings { 145 | sb.Write(lparen) 146 | 147 | limit := len(linestring.Coordinates) - 1 148 | for idx, coord := range linestring.Coordinates { 149 | marshalCoord(&coord, &sb) 150 | 151 | if idx < limit { 152 | sb.Write(comma) 153 | } 154 | } 155 | 156 | sb.Write(rparen) 157 | 158 | if lidx < llimit { 159 | sb.Write(comma) 160 | } 161 | } 162 | 163 | sb.Write(rparen) 164 | sb.Write(rbrace) 165 | 166 | _, err := w.Write(sb.Bytes()) 167 | 168 | return err 169 | } 170 | 171 | func marshalPoint(g *geom.Point, w io.Writer) error { 172 | 173 | var sb bytes.Buffer 174 | 175 | sb.Write(pointHdr) 176 | marshalCoord(&g.Coordinate, &sb) 177 | sb.Write(rbrace) 178 | 179 | _, err := w.Write(sb.Bytes()) 180 | 181 | return err 182 | } 183 | 184 | func marshalMultiPoint(mp *geom.MultiPoint, w io.Writer) error { 185 | var sb bytes.Buffer 186 | sb.Write(multiPointHdr) 187 | sb.Write(lparen) 188 | 189 | limit := len(mp.Points) - 1 190 | for idx, point := range mp.Points { 191 | marshalCoord(&point.Coordinate, &sb) 192 | 193 | if idx < limit { 194 | sb.Write(comma) 195 | } 196 | } 197 | 198 | sb.Write(rparen) 199 | sb.Write(rbrace) 200 | 201 | _, err := w.Write(sb.Bytes()) 202 | 203 | return err 204 | } 205 | 206 | func marshalGeometryCollection(gc *geom.GeometryCollection, w io.Writer) error { 207 | var sb bytes.Buffer 208 | sb.Write(geomCollectionHdr) 209 | sb.Write(lparen) 210 | limit := len(gc.Geometries) - 1 211 | for idx, g := range gc.Geometries { 212 | 213 | switch g := g.(type) { 214 | case *geom.Point: 215 | marshalPoint(g, &sb) 216 | case *geom.MultiPoint: 217 | marshalMultiPoint(g, &sb) 218 | case *geom.LineString: 219 | marshalLineString(g, &sb) 220 | case *geom.MultiLineString: 221 | marshalMultiLineString(g, &sb) 222 | case *geom.Polygon: 223 | marshalPolygon(g, &sb) 224 | case *geom.MultiPolygon: 225 | marshalMultiPolygon(g, &sb) 226 | case *geom.GeometryCollection: 227 | marshalGeometryCollection(g, &sb) 228 | default: 229 | return geom.ErrUnsupportedGeom 230 | } 231 | 232 | if idx < limit { 233 | sb.Write(comma) 234 | } 235 | } 236 | sb.Write(rparen) 237 | sb.Write(rbrace) 238 | 239 | _, err := w.Write(sb.Bytes()) 240 | 241 | return err 242 | } 243 | 244 | func marshalLinearRing(l *geom.LinearRing, sb *bytes.Buffer) { 245 | 246 | sb.Write(lparen) 247 | limit := len(l.Coordinates) - 1 248 | 249 | for idx, coord := range l.Coordinates { 250 | marshalCoord(&coord, sb) 251 | 252 | if idx < limit { 253 | sb.Write(comma) 254 | } 255 | } 256 | sb.Write(rparen) 257 | 258 | } 259 | 260 | func marshalCoord(c *geom.Coordinate, sb *bytes.Buffer) { 261 | limit := len(*c) - 1 262 | sb.Write(lparen) 263 | for idx, comp := range *c { 264 | sb.Write([]byte(strconv.FormatFloat(comp, 'f', -1, 64))) 265 | if idx < limit { 266 | sb.Write(comma) 267 | } 268 | } 269 | sb.Write(rparen) 270 | } 271 | -------------------------------------------------------------------------------- /geojson/encoder_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2015] Alex Davies-Moore 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package geojson 18 | 19 | import ( 20 | "bytes" 21 | "encoding/json" 22 | "testing" 23 | 24 | "github.com/devork/geom" 25 | "github.com/stretchr/testify/assert" 26 | ) 27 | 28 | func TestEncodePoint(t *testing.T) { 29 | expected := &geom.Point{ 30 | geom.Hdr{ 31 | Dim: geom.XYZ, 32 | Srid: 27700, 33 | }, 34 | []float64{-0.118340, 51.503475, -12.34567890}, 35 | } 36 | 37 | var sb bytes.Buffer 38 | err := Encode(expected, &sb) 39 | 40 | if err != nil { 41 | t.Fatalf("failed to marshal point: %s", err) 42 | } 43 | 44 | got := make(map[string]interface{}) 45 | err = json.Unmarshal(sb.Bytes(), &got) 46 | 47 | if err != nil { 48 | t.Fatalf("Failed to parse generated GeoJSON: error %s", err) 49 | } 50 | 51 | t.Logf("%s\n", string(sb.Bytes())) 52 | 53 | assert.Equal(t, "Point", got["type"]) 54 | 55 | coords := got["coordinates"].([]interface{}) 56 | assert.InDelta(t, -0.118340, coords[0].(float64), 1e-9) 57 | assert.InDelta(t, 51.503475, coords[1].(float64), 1e-9) 58 | assert.InDelta(t, -12.34567890, coords[2].(float64), 1e-9) 59 | } 60 | 61 | func TestEncodeMultiPoint(t *testing.T) { 62 | expected := &geom.MultiPoint{ 63 | geom.Hdr{ 64 | Dim: geom.XY, 65 | Srid: 4326, 66 | }, 67 | []geom.Point{ 68 | { 69 | geom.Hdr{ 70 | Dim: geom.XY, 71 | Srid: 4326, 72 | }, 73 | []float64{-105.01621, 39.57422}, 74 | }, 75 | { 76 | geom.Hdr{ 77 | Dim: geom.XY, 78 | Srid: 4326, 79 | }, 80 | []float64{-80.6665134, 35.0539943}, 81 | }, 82 | }, 83 | } 84 | 85 | var sb bytes.Buffer 86 | err := Encode(expected, &sb) 87 | 88 | if err != nil { 89 | t.Fatalf("failed to marshal MultiPoint: %s", err) 90 | } 91 | 92 | got := make(map[string]interface{}) 93 | err = json.Unmarshal(sb.Bytes(), &got) 94 | 95 | if err != nil { 96 | t.Fatalf("Failed to parse generated GeoJSON: error %s", err) 97 | } 98 | 99 | t.Logf("%s\n", string(sb.Bytes())) 100 | 101 | assert.Equal(t, "MultiPoint", got["type"]) 102 | 103 | coords := got["coordinates"].([]interface{}) 104 | 105 | assert.Equal(t, 2, len(coords)) 106 | 107 | coord := coords[0].([]interface{}) 108 | 109 | for idx, value := range expected.Points[0].Coordinate { 110 | assert.InDelta(t, value, coord[idx].(float64), 1e-9) 111 | } 112 | 113 | coord = coords[1].([]interface{}) 114 | 115 | for idx, value := range expected.Points[1].Coordinate { 116 | assert.InDelta(t, value, coord[idx].(float64), 1e-9) 117 | } 118 | } 119 | 120 | func TestEncodeLineString(t *testing.T) { 121 | expected := &geom.LineString{ 122 | geom.Hdr{ 123 | Dim: geom.XY, 124 | Srid: 4326, 125 | }, 126 | []geom.Coordinate{ 127 | {-101.744384765625, 39.32155002466662}, 128 | {-101.5521240234375, 39.330048552942415}, 129 | {-101.40380859375, 39.330048552942415}, 130 | {-101.33239746093749, 39.364032338047984}, 131 | {-101.041259765625, 39.36827914916011}, 132 | {-100.975341796875, 39.30454987014581}, 133 | {-100.9149169921875, 39.24501680713314}, 134 | {-100.843505859375, 39.16414104768742}, 135 | {-100.8050537109375, 39.104488809440475}, 136 | {-100.491943359375, 39.10022600175347}, 137 | {-100.43701171875, 39.095962936305476}, 138 | {-100.338134765625, 39.095962936305476}, 139 | {-100.1953125, 39.027718840211605}, 140 | {-100.008544921875, 39.01064750994083}, 141 | {-99.86572265625, 39.00211029922512}, 142 | {-99.6844482421875, 38.97222194853654}, 143 | {-99.51416015625, 38.929502416386605}, 144 | {-99.38232421875, 38.92095542046727}, 145 | {-99.3218994140625, 38.89530825492018}, 146 | {-99.1131591796875, 38.86965182408357}, 147 | {-99.0802001953125, 38.85682013474361}, 148 | {-98.82202148437499, 38.85682013474361}, 149 | {-98.44848632812499, 38.84826438869913}, 150 | {-98.20678710937499, 38.84826438869913}, 151 | {-98.02001953125, 38.8782049970615}, 152 | {-97.635498046875, 38.87392853923629}, 153 | }, 154 | } 155 | 156 | var sb bytes.Buffer 157 | err := Encode(expected, &sb) 158 | 159 | if err != nil { 160 | t.Fatalf("failed to marshal linestring: %s", err) 161 | } 162 | 163 | got := make(map[string]interface{}) 164 | err = json.Unmarshal(sb.Bytes(), &got) 165 | 166 | if err != nil { 167 | t.Fatalf("Failed to parse generated GeoJSON: error %s", err) 168 | } 169 | 170 | t.Logf("%s\n", string(sb.Bytes())) 171 | 172 | assert.Equal(t, "LineString", got["type"]) 173 | 174 | coords := got["coordinates"].([]interface{}) 175 | 176 | assert.Equal(t, 26, len(coords)) 177 | 178 | for idx, coord := range expected.Coordinates { 179 | c := coords[idx].([]interface{}) 180 | for jdx, value := range coord { 181 | t.Logf("%f => %f", value, c[jdx].(float64)) 182 | assert.InDelta(t, value, c[jdx].(float64), 1e-9) 183 | } 184 | } 185 | } 186 | 187 | func TestEncodeMultiLineString(t *testing.T) { 188 | expected := &geom.MultiLineString{ 189 | geom.Hdr{ 190 | Dim: geom.XY, 191 | Srid: 4326, 192 | }, 193 | []geom.LineString{ 194 | { 195 | geom.Hdr{Dim: geom.XY, Srid: 4326}, 196 | []geom.Coordinate{ 197 | {-105.0214433670044, 39.57805759162015}, 198 | {-105.02150774002075, 39.57780951131517}, 199 | {-105.02157211303711, 39.57749527498758}, 200 | {-105.02157211303711, 39.57716449836683}, 201 | {-105.02157211303711, 39.57703218727656}, 202 | {-105.02152919769287, 39.57678410330158}, 203 | }, 204 | }, 205 | { 206 | geom.Hdr{Dim: geom.XY, Srid: 4326}, 207 | []geom.Coordinate{ 208 | {-105.01989841461182, 39.574997872470774}, 209 | {-105.01959800720215, 39.57489863607502}, 210 | {-105.01906156539916, 39.57478286010041}, 211 | }, 212 | }, 213 | { 214 | geom.Hdr{Dim: geom.XY, Srid: 4326}, 215 | []geom.Coordinate{ 216 | {-105.01717329025269, 39.5744024519653}, 217 | {-105.01698017120361, 39.574385912433804}, 218 | {-105.0166368484497, 39.574385912433804}, 219 | {-105.01650810241699, 39.5744024519653}, 220 | {-105.0159502029419, 39.574270135602866}, 221 | }, 222 | }, 223 | { 224 | geom.Hdr{Dim: geom.XY, Srid: 4326}, 225 | []geom.Coordinate{ 226 | {-105.0142765045166, 39.57397242286402}, 227 | {-105.01412630081175, 39.57403858136094}, 228 | {-105.0138258934021, 39.57417089816531}, 229 | {-105.01331090927124, 39.57445207053608}, 230 | }, 231 | }, 232 | }, 233 | } 234 | 235 | var sb bytes.Buffer 236 | err := Encode(expected, &sb) 237 | 238 | if err != nil { 239 | t.Fatalf("failed to marshal multilinestring: %s", err) 240 | } 241 | 242 | got := make(map[string]interface{}) 243 | err = json.Unmarshal(sb.Bytes(), &got) 244 | 245 | if err != nil { 246 | t.Fatalf("Failed to parse generated GeoJSON: error %s", err) 247 | } 248 | 249 | t.Logf("%s\n", string(sb.Bytes())) 250 | 251 | assert.Equal(t, "MultiLineString", got["type"]) 252 | 253 | coords := got["coordinates"].([]interface{}) 254 | 255 | assert.Equal(t, 4, len(coords)) 256 | 257 | for idx, linestring := range coords { 258 | ls := linestring.([]interface{}) 259 | 260 | for jdx, coord := range ls { 261 | c := coord.([]interface{}) 262 | for kdx, value := range c { 263 | assert.InDelta(t, value.(float64), expected.LineStrings[idx].Coordinates[jdx][kdx], 1e-9) 264 | } 265 | } 266 | 267 | for jdx, coord := range expected.LineStrings[idx].Coordinates { 268 | c := ls[jdx].([]interface{}) 269 | for kdx, value := range coord { 270 | t.Logf("%f => %f", value, c[kdx].(float64)) 271 | assert.InDelta(t, value, c[kdx].(float64), 1e-9) 272 | } 273 | } 274 | } 275 | } 276 | 277 | func TestEncodePolygon(t *testing.T) { 278 | expected := &geom.Polygon{ 279 | geom.Hdr{ 280 | Dim: geom.XY, 281 | Srid: 4326, 282 | }, 283 | []geom.LinearRing{ 284 | { 285 | []geom.Coordinate{ 286 | {-84.32281494140625, 34.9895035675793}, 287 | {-84.29122924804688, 35.21981940793435}, 288 | {-84.24041748046875, 35.25459097465022}, 289 | {-84.22531127929688, 35.266925688950074}, 290 | {-84.20745849609375, 35.26580442886754}, 291 | {-84.19921875, 35.24674063355999}, 292 | {-84.16213989257812, 35.24113278166642}, 293 | {-84.12368774414062, 35.24898366572645}, 294 | {-84.09072875976562, 35.24898366572645}, 295 | {-84.08798217773438, 35.264683153268116}, 296 | {-84.04266357421875, 35.27701633139884}, 297 | {-84.03030395507812, 35.291589484566124}, 298 | {-84.0234375, 35.306160014550784}, 299 | {-84.03305053710936, 35.32745068492882}, 300 | {-84.03579711914062, 35.34313496028189}, 301 | {-84.03579711914062, 35.348735749472546}, 302 | {-84.01657104492188, 35.35545618392078}, 303 | {-84.01107788085938, 35.37337460834958}, 304 | {-84.00970458984374, 35.39128905521763}, 305 | {-84.01931762695312, 35.41479572901859}, 306 | {-84.00283813476562, 35.429344044107154}, 307 | {-83.93692016601562, 35.47409160773029}, 308 | {-83.91220092773438, 35.47632833265728}, 309 | {-83.88885498046875, 35.504282143299655}, 310 | {-83.88473510742186, 35.516578738902936}, 311 | {-83.8751220703125, 35.52104976129943}, 312 | {-83.85314941406249, 35.52104976129943}, 313 | {-83.82843017578125, 35.52104976129943}, 314 | {-83.8092041015625, 35.53446133418443}, 315 | {-83.80233764648438, 35.54116627999813}, 316 | {-83.76800537109374, 35.56239491058853}, 317 | {-83.7432861328125, 35.56239491058853}, 318 | {-83.71994018554688, 35.56239491058853}, 319 | {-83.67050170898438, 35.569097520776054}, 320 | {-83.6334228515625, 35.570214567965984}, 321 | {-83.61007690429688, 35.576916524038616}, 322 | {-83.59634399414061, 35.574682600980914}, 323 | {-83.5894775390625, 35.55904339525896}, 324 | {-83.55239868164062, 35.56574628576276}, 325 | {-83.49746704101562, 35.563512051219696}, 326 | {-83.47000122070312, 35.586968406786475}, 327 | {-83.4466552734375, 35.60818490437746}, 328 | {-83.37936401367188, 35.63609277863135}, 329 | {-83.35739135742188, 35.65618041632016}, 330 | {-83.32305908203124, 35.66622234103479}, 331 | {-83.3148193359375, 35.65394870599763}, 332 | {-83.29971313476561, 35.660643649881614}, 333 | {-83.28598022460938, 35.67180064238771}, 334 | {-83.26126098632811, 35.6907639509368}, 335 | {-83.25714111328125, 35.69968630125201}, 336 | {-83.25576782226562, 35.715298012125295}, 337 | {-83.23516845703125, 35.72310272092263}, 338 | {-83.19808959960936, 35.72756221127198}, 339 | {-83.16238403320312, 35.753199435570316}, 340 | {-83.15826416015625, 35.76322914549896}, 341 | {-83.10333251953125, 35.76991491635478}, 342 | {-83.08685302734375, 35.7843988251953}, 343 | {-83.0511474609375, 35.787740890986576}, 344 | {-83.01681518554688, 35.78328477203738}, 345 | {-83.001708984375, 35.77882840327371}, 346 | {-82.96737670898438, 35.793310688351724}, 347 | {-82.94540405273438, 35.820040281161}, 348 | {-82.9193115234375, 35.85121343450061}, 349 | {-82.9083251953125, 35.86902116501695}, 350 | {-82.90557861328125, 35.87792352995116}, 351 | {-82.91244506835938, 35.92353244718235}, 352 | {-82.88360595703125, 35.94688293218141}, 353 | {-82.85614013671875, 35.951329861522666}, 354 | {-82.8424072265625, 35.94243575255426}, 355 | {-82.825927734375, 35.92464453144099}, 356 | {-82.80670166015625, 35.927980690382704}, 357 | {-82.80532836914062, 35.94243575255426}, 358 | {-82.77923583984375, 35.97356075349624}, 359 | {-82.78060913085938, 35.99245209055831}, 360 | {-82.76138305664062, 36.00356252895066}, 361 | {-82.69546508789062, 36.04465753921525}, 362 | {-82.64465332031249, 36.060201412392914}, 363 | {-82.61306762695312, 36.060201412392914}, 364 | {-82.60620117187499, 36.033552893400376}, 365 | {-82.60620117187499, 35.991340960635405}, 366 | {-82.60620117187499, 35.97911749857497}, 367 | {-82.5787353515625, 35.96133453736691}, 368 | {-82.5677490234375, 35.951329861522666}, 369 | {-82.53067016601562, 35.97244935753683}, 370 | {-82.46475219726562, 36.006895355244666}, 371 | {-82.41668701171875, 36.070192281208456}, 372 | {-82.37960815429686, 36.10126686921446}, 373 | {-82.35488891601562, 36.117908916563685}, 374 | {-82.34115600585936, 36.113471382052175}, 375 | {-82.29583740234375, 36.13343831245866}, 376 | {-82.26287841796874, 36.13565654678543}, 377 | {-82.23403930664062, 36.13565654678543}, 378 | {-82.2216796875, 36.154509006695}, 379 | {-82.20382690429688, 36.15561783381855}, 380 | {-82.19009399414062, 36.144528857027744}, 381 | {-82.15438842773438, 36.15007354140755}, 382 | {-82.14065551757812, 36.134547437460064}, 383 | {-82.1337890625, 36.116799556445024}, 384 | {-82.12142944335938, 36.10570509327921}, 385 | {-82.08984375, 36.10792411128649}, 386 | {-82.05276489257811, 36.12678323326429}, 387 | {-82.03628540039062, 36.12900165569652}, 388 | {-81.91268920898438, 36.29409768373033}, 389 | {-81.89071655273438, 36.30959215409138}, 390 | {-81.86325073242188, 36.33504067209607}, 391 | {-81.83029174804688, 36.34499652561904}, 392 | {-81.80145263671875, 36.35605709240176}, 393 | {-81.77947998046874, 36.34610265300638}, 394 | {-81.76162719726562, 36.33835943134047}, 395 | {-81.73690795898438, 36.33835943134047}, 396 | {-81.71905517578125, 36.33835943134047}, 397 | {-81.70669555664062, 36.33504067209607}, 398 | {-81.70669555664062, 36.342784223707234}, 399 | {-81.72317504882812, 36.357163062654365}, 400 | {-81.73278808593749, 36.379279167407965}, 401 | {-81.73690795898438, 36.40028364332352}, 402 | {-81.73690795898438, 36.41354670392876}, 403 | {-81.72454833984374, 36.423492513472326}, 404 | {-81.71768188476562, 36.445589751779174}, 405 | {-81.69845581054688, 36.47541104282962}, 406 | {-81.69845581054688, 36.51073994146672}, 407 | {-81.705322265625, 36.53060536411363}, 408 | {-81.69158935546875, 36.55929085774001}, 409 | {-81.68060302734375, 36.56480607840351}, 410 | {-81.68197631835938, 36.58686302344181}, 411 | {-81.04202270507812, 36.56370306576917}, 412 | {-80.74264526367186, 36.561496993252575}, 413 | {-79.89120483398438, 36.54053616262899}, 414 | {-78.68408203124999, 36.53943280355122}, 415 | {-77.88345336914062, 36.54053616262899}, 416 | {-76.91665649414062, 36.54163950596125}, 417 | {-76.91665649414062, 36.55046568575947}, 418 | {-76.31103515625, 36.551568887374}, 419 | {-75.79605102539062, 36.54936246839778}, 420 | {-75.6298828125, 36.07574221562703}, 421 | {-75.4925537109375, 35.82226734114509}, 422 | {-75.3936767578125, 35.639441068973916}, 423 | {-75.41015624999999, 35.43829554739668}, 424 | {-75.43212890625, 35.263561862152095}, 425 | {-75.487060546875, 35.18727767598896}, 426 | {-75.5914306640625, 35.17380831799959}, 427 | {-75.9210205078125, 35.04798673426734}, 428 | {-76.17919921875, 34.867904962568744}, 429 | {-76.41540527343749, 34.62868797377061}, 430 | {-76.4593505859375, 34.57442951865274}, 431 | {-76.53076171875, 34.53371242139567}, 432 | {-76.5911865234375, 34.551811369170494}, 433 | {-76.651611328125, 34.615126683462194}, 434 | {-76.761474609375, 34.63320791137959}, 435 | {-77.069091796875, 34.59704151614417}, 436 | {-77.376708984375, 34.45674800347809}, 437 | {-77.5909423828125, 34.3207552752374}, 438 | {-77.8326416015625, 33.97980872872457}, 439 | {-77.9150390625, 33.80197351806589}, 440 | {-77.9754638671875, 33.73804486328907}, 441 | {-78.11279296875, 33.8521697014074}, 442 | {-78.2830810546875, 33.8521697014074}, 443 | {-78.4808349609375, 33.815666308702774}, 444 | {-79.6728515625, 34.8047829195724}, 445 | {-80.782470703125, 34.836349990763864}, 446 | {-80.782470703125, 34.91746688928252}, 447 | {-80.9307861328125, 35.092945313732635}, 448 | {-81.0516357421875, 35.02999636902566}, 449 | {-81.0516357421875, 35.05248370662468}, 450 | {-81.0516357421875, 35.137879119634185}, 451 | {-82.3150634765625, 35.19625600786368}, 452 | {-82.3590087890625, 35.19625600786368}, 453 | {-82.40295410156249, 35.22318504970181}, 454 | {-82.4688720703125, 35.16931803601131}, 455 | {-82.6885986328125, 35.1154153142536}, 456 | {-82.781982421875, 35.06147690849717}, 457 | {-83.1060791015625, 35.003003395276714}, 458 | {-83.616943359375, 34.99850370014629}, 459 | {-84.05639648437499, 34.985003130171066}, 460 | {-84.22119140625, 34.985003130171066}, 461 | {-84.32281494140625, 34.9895035675793}, 462 | }, 463 | }, 464 | }, 465 | } 466 | 467 | var sb bytes.Buffer 468 | err := Encode(expected, &sb) 469 | 470 | if err != nil { 471 | t.Fatalf("failed to marshal polygon: %s", err) 472 | } 473 | 474 | got := make(map[string]interface{}) 475 | err = json.Unmarshal(sb.Bytes(), &got) 476 | 477 | if err != nil { 478 | t.Fatalf("Failed to parse generated GeoJSON: error %s", err) 479 | } 480 | 481 | t.Logf("%s\n", string(sb.Bytes())) 482 | assert.Equal(t, "Polygon", got["type"]) 483 | 484 | coords := got["coordinates"].([]interface{}) 485 | 486 | assert.Equal(t, 1, len(coords)) 487 | 488 | lring := coords[0].([]interface{}) 489 | assert.Equal(t, 176, len(lring)) 490 | 491 | for idx, coord := range expected.Rings[0].Coordinates { 492 | coords := lring[idx].([]interface{}) 493 | for jdx, value := range coord { 494 | t.Logf("%f => %f", value, coords[jdx].(float64)) 495 | assert.InDelta(t, value, coords[jdx].(float64), 1e-9) 496 | } 497 | } 498 | } 499 | 500 | func TestEncodePolygonHoles(t *testing.T) { 501 | expected := &geom.Polygon{ 502 | geom.Hdr{ 503 | Dim: geom.XY, 504 | Srid: 4326, 505 | }, 506 | []geom.LinearRing{ 507 | { 508 | []geom.Coordinate{ 509 | {-84.32281494140625, 34.9895035675793}, 510 | {-84.29122924804688, 35.21981940793435}, 511 | {-84.24041748046875, 35.25459097465022}, 512 | {-84.22531127929688, 35.266925688950074}, 513 | {-84.20745849609375, 35.26580442886754}, 514 | {-84.19921875, 35.24674063355999}, 515 | {-84.16213989257812, 35.24113278166642}, 516 | {-84.12368774414062, 35.24898366572645}, 517 | {-84.09072875976562, 35.24898366572645}, 518 | {-84.08798217773438, 35.264683153268116}, 519 | {-84.04266357421875, 35.27701633139884}, 520 | {-84.03030395507812, 35.291589484566124}, 521 | {-84.0234375, 35.306160014550784}, 522 | {-84.03305053710936, 35.32745068492882}, 523 | {-84.03579711914062, 35.34313496028189}, 524 | {-84.03579711914062, 35.348735749472546}, 525 | {-84.01657104492188, 35.35545618392078}, 526 | {-84.01107788085938, 35.37337460834958}, 527 | {-84.00970458984374, 35.39128905521763}, 528 | {-84.01931762695312, 35.41479572901859}, 529 | {-84.00283813476562, 35.429344044107154}, 530 | {-83.93692016601562, 35.47409160773029}, 531 | {-83.91220092773438, 35.47632833265728}, 532 | {-83.88885498046875, 35.504282143299655}, 533 | {-83.88473510742186, 35.516578738902936}, 534 | {-83.8751220703125, 35.52104976129943}, 535 | {-83.85314941406249, 35.52104976129943}, 536 | {-83.82843017578125, 35.52104976129943}, 537 | {-83.8092041015625, 35.53446133418443}, 538 | {-83.80233764648438, 35.54116627999813}, 539 | {-83.76800537109374, 35.56239491058853}, 540 | {-83.7432861328125, 35.56239491058853}, 541 | {-83.71994018554688, 35.56239491058853}, 542 | {-83.67050170898438, 35.569097520776054}, 543 | {-83.6334228515625, 35.570214567965984}, 544 | {-83.61007690429688, 35.576916524038616}, 545 | {-83.59634399414061, 35.574682600980914}, 546 | {-83.5894775390625, 35.55904339525896}, 547 | {-83.55239868164062, 35.56574628576276}, 548 | {-83.49746704101562, 35.563512051219696}, 549 | {-83.47000122070312, 35.586968406786475}, 550 | {-83.4466552734375, 35.60818490437746}, 551 | {-83.37936401367188, 35.63609277863135}, 552 | {-83.35739135742188, 35.65618041632016}, 553 | {-83.32305908203124, 35.66622234103479}, 554 | {-83.3148193359375, 35.65394870599763}, 555 | {-83.29971313476561, 35.660643649881614}, 556 | {-83.28598022460938, 35.67180064238771}, 557 | {-83.26126098632811, 35.6907639509368}, 558 | {-83.25714111328125, 35.69968630125201}, 559 | {-83.25576782226562, 35.715298012125295}, 560 | {-83.23516845703125, 35.72310272092263}, 561 | {-83.19808959960936, 35.72756221127198}, 562 | {-83.16238403320312, 35.753199435570316}, 563 | {-83.15826416015625, 35.76322914549896}, 564 | {-83.10333251953125, 35.76991491635478}, 565 | {-83.08685302734375, 35.7843988251953}, 566 | {-83.0511474609375, 35.787740890986576}, 567 | {-83.01681518554688, 35.78328477203738}, 568 | {-83.001708984375, 35.77882840327371}, 569 | {-82.96737670898438, 35.793310688351724}, 570 | {-82.94540405273438, 35.820040281161}, 571 | {-82.9193115234375, 35.85121343450061}, 572 | {-82.9083251953125, 35.86902116501695}, 573 | {-82.90557861328125, 35.87792352995116}, 574 | {-82.91244506835938, 35.92353244718235}, 575 | {-82.88360595703125, 35.94688293218141}, 576 | {-82.85614013671875, 35.951329861522666}, 577 | {-82.8424072265625, 35.94243575255426}, 578 | {-82.825927734375, 35.92464453144099}, 579 | {-82.80670166015625, 35.927980690382704}, 580 | {-82.80532836914062, 35.94243575255426}, 581 | {-82.77923583984375, 35.97356075349624}, 582 | {-82.78060913085938, 35.99245209055831}, 583 | {-82.76138305664062, 36.00356252895066}, 584 | {-82.69546508789062, 36.04465753921525}, 585 | {-82.64465332031249, 36.060201412392914}, 586 | {-82.61306762695312, 36.060201412392914}, 587 | {-82.60620117187499, 36.033552893400376}, 588 | {-82.60620117187499, 35.991340960635405}, 589 | {-82.60620117187499, 35.97911749857497}, 590 | {-82.5787353515625, 35.96133453736691}, 591 | {-82.5677490234375, 35.951329861522666}, 592 | {-82.53067016601562, 35.97244935753683}, 593 | {-82.46475219726562, 36.006895355244666}, 594 | {-82.41668701171875, 36.070192281208456}, 595 | {-82.37960815429686, 36.10126686921446}, 596 | {-82.35488891601562, 36.117908916563685}, 597 | {-82.34115600585936, 36.113471382052175}, 598 | {-82.29583740234375, 36.13343831245866}, 599 | {-82.26287841796874, 36.13565654678543}, 600 | {-82.23403930664062, 36.13565654678543}, 601 | {-82.2216796875, 36.154509006695}, 602 | {-82.20382690429688, 36.15561783381855}, 603 | {-82.19009399414062, 36.144528857027744}, 604 | {-82.15438842773438, 36.15007354140755}, 605 | {-82.14065551757812, 36.134547437460064}, 606 | {-82.1337890625, 36.116799556445024}, 607 | {-82.12142944335938, 36.10570509327921}, 608 | {-82.08984375, 36.10792411128649}, 609 | {-82.05276489257811, 36.12678323326429}, 610 | {-82.03628540039062, 36.12900165569652}, 611 | {-81.91268920898438, 36.29409768373033}, 612 | {-81.89071655273438, 36.30959215409138}, 613 | {-81.86325073242188, 36.33504067209607}, 614 | {-81.83029174804688, 36.34499652561904}, 615 | {-81.80145263671875, 36.35605709240176}, 616 | {-81.77947998046874, 36.34610265300638}, 617 | {-81.76162719726562, 36.33835943134047}, 618 | {-81.73690795898438, 36.33835943134047}, 619 | {-81.71905517578125, 36.33835943134047}, 620 | {-81.70669555664062, 36.33504067209607}, 621 | {-81.70669555664062, 36.342784223707234}, 622 | {-81.72317504882812, 36.357163062654365}, 623 | {-81.73278808593749, 36.379279167407965}, 624 | {-81.73690795898438, 36.40028364332352}, 625 | {-81.73690795898438, 36.41354670392876}, 626 | {-81.72454833984374, 36.423492513472326}, 627 | {-81.71768188476562, 36.445589751779174}, 628 | {-81.69845581054688, 36.47541104282962}, 629 | {-81.69845581054688, 36.51073994146672}, 630 | {-81.705322265625, 36.53060536411363}, 631 | {-81.69158935546875, 36.55929085774001}, 632 | {-81.68060302734375, 36.56480607840351}, 633 | {-81.68197631835938, 36.58686302344181}, 634 | {-81.04202270507812, 36.56370306576917}, 635 | {-80.74264526367186, 36.561496993252575}, 636 | {-79.89120483398438, 36.54053616262899}, 637 | {-78.68408203124999, 36.53943280355122}, 638 | {-77.88345336914062, 36.54053616262899}, 639 | {-76.91665649414062, 36.54163950596125}, 640 | {-76.91665649414062, 36.55046568575947}, 641 | {-76.31103515625, 36.551568887374}, 642 | {-75.79605102539062, 36.54936246839778}, 643 | {-75.6298828125, 36.07574221562703}, 644 | {-75.4925537109375, 35.82226734114509}, 645 | {-75.3936767578125, 35.639441068973916}, 646 | {-75.41015624999999, 35.43829554739668}, 647 | {-75.43212890625, 35.263561862152095}, 648 | {-75.487060546875, 35.18727767598896}, 649 | {-75.5914306640625, 35.17380831799959}, 650 | {-75.9210205078125, 35.04798673426734}, 651 | {-76.17919921875, 34.867904962568744}, 652 | {-76.41540527343749, 34.62868797377061}, 653 | {-76.4593505859375, 34.57442951865274}, 654 | {-76.53076171875, 34.53371242139567}, 655 | {-76.5911865234375, 34.551811369170494}, 656 | {-76.651611328125, 34.615126683462194}, 657 | {-76.761474609375, 34.63320791137959}, 658 | {-77.069091796875, 34.59704151614417}, 659 | {-77.376708984375, 34.45674800347809}, 660 | {-77.5909423828125, 34.3207552752374}, 661 | {-77.8326416015625, 33.97980872872457}, 662 | {-77.9150390625, 33.80197351806589}, 663 | {-77.9754638671875, 33.73804486328907}, 664 | {-78.11279296875, 33.8521697014074}, 665 | {-78.2830810546875, 33.8521697014074}, 666 | {-78.4808349609375, 33.815666308702774}, 667 | {-79.6728515625, 34.8047829195724}, 668 | {-80.782470703125, 34.836349990763864}, 669 | {-80.782470703125, 34.91746688928252}, 670 | {-80.9307861328125, 35.092945313732635}, 671 | {-81.0516357421875, 35.02999636902566}, 672 | {-81.0516357421875, 35.05248370662468}, 673 | {-81.0516357421875, 35.137879119634185}, 674 | {-82.3150634765625, 35.19625600786368}, 675 | {-82.3590087890625, 35.19625600786368}, 676 | {-82.40295410156249, 35.22318504970181}, 677 | {-82.4688720703125, 35.16931803601131}, 678 | {-82.6885986328125, 35.1154153142536}, 679 | {-82.781982421875, 35.06147690849717}, 680 | {-83.1060791015625, 35.003003395276714}, 681 | {-83.616943359375, 34.99850370014629}, 682 | {-84.05639648437499, 34.985003130171066}, 683 | {-84.22119140625, 34.985003130171066}, 684 | {-84.32281494140625, 34.9895035675793}, 685 | }, 686 | }, 687 | { 688 | []geom.Coordinate{ 689 | {-75.69030761718749, 35.74205383068037}, 690 | {-75.5914306640625, 35.74205383068037}, 691 | {-75.5419921875, 35.585851593232356}, 692 | {-75.56396484375, 35.32633026307483}, 693 | {-75.69030761718749, 35.285984736065735}, 694 | {-75.970458984375, 35.16482750605027}, 695 | {-76.2066650390625, 34.994003757575776}, 696 | {-76.300048828125, 35.02999636902566}, 697 | {-76.409912109375, 35.07946034047981}, 698 | {-76.5252685546875, 35.10642805736423}, 699 | {-76.4208984375, 35.25907654252574}, 700 | {-76.3385009765625, 35.294952147406576}, 701 | {-76.0858154296875, 35.29943548054543}, 702 | {-75.948486328125, 35.44277092585766}, 703 | {-75.8660888671875, 35.53669637839501}, 704 | {-75.772705078125, 35.567980458012094}, 705 | {-75.706787109375, 35.634976650677295}, 706 | {-75.706787109375, 35.74205383068037}, 707 | {-75.69030761718749, 35.74205383068037}, 708 | }, 709 | }, 710 | }, 711 | } 712 | 713 | var sb bytes.Buffer 714 | err := Encode(expected, &sb) 715 | 716 | if err != nil { 717 | t.Fatalf("failed to marshal polygon: %s", err) 718 | } 719 | 720 | got := make(map[string]interface{}) 721 | err = json.Unmarshal(sb.Bytes(), &got) 722 | 723 | if err != nil { 724 | t.Fatalf("Failed to parse generated GeoJSON: error %s", err) 725 | } 726 | 727 | t.Logf("%s", string(sb.Bytes())) 728 | assert.Equal(t, "Polygon", got["type"]) 729 | 730 | coords := got["coordinates"].([]interface{}) 731 | 732 | assert.Equal(t, 2, len(coords)) 733 | 734 | // casing 735 | lring := coords[0].([]interface{}) 736 | assert.Equal(t, 176, len(lring)) 737 | 738 | for idx, coord := range expected.Rings[0].Coordinates { 739 | coords := lring[idx].([]interface{}) 740 | for jdx, value := range coord { 741 | t.Logf("%f => %f", value, coords[jdx].(float64)) 742 | assert.InDelta(t, value, coords[jdx].(float64), 1e-9) 743 | } 744 | } 745 | 746 | // hole 747 | lring = coords[1].([]interface{}) 748 | assert.Equal(t, 19, len(lring)) 749 | 750 | for idx, coord := range expected.Rings[1].Coordinates { 751 | coords := lring[idx].([]interface{}) 752 | for jdx, value := range coord { 753 | t.Logf("%f => %f", value, coords[jdx].(float64)) 754 | assert.InDelta(t, value, coords[jdx].(float64), 1e-9) 755 | } 756 | } 757 | } 758 | 759 | func TestEncodeMultiPolygon(t *testing.T) { 760 | expected := &geom.MultiPolygon{ 761 | geom.Hdr{ 762 | Dim: geom.XY, 763 | Srid: 4326, 764 | }, 765 | []geom.Polygon{ 766 | { 767 | geom.Hdr{ 768 | Dim: geom.XY, 769 | Srid: 4326, 770 | }, 771 | []geom.LinearRing{ 772 | { 773 | []geom.Coordinate{ 774 | {-84.32281494140625, 34.9895035675793}, 775 | {-84.29122924804688, 35.21981940793435}, 776 | {-84.24041748046875, 35.25459097465022}, 777 | {-84.22531127929688, 35.266925688950074}, 778 | {-84.20745849609375, 35.26580442886754}, 779 | {-84.19921875, 35.24674063355999}, 780 | {-84.16213989257812, 35.24113278166642}, 781 | {-84.12368774414062, 35.24898366572645}, 782 | {-84.09072875976562, 35.24898366572645}, 783 | {-84.08798217773438, 35.264683153268116}, 784 | {-84.04266357421875, 35.27701633139884}, 785 | {-84.03030395507812, 35.291589484566124}, 786 | {-84.0234375, 35.306160014550784}, 787 | {-84.03305053710936, 35.32745068492882}, 788 | {-84.03579711914062, 35.34313496028189}, 789 | {-84.03579711914062, 35.348735749472546}, 790 | {-84.01657104492188, 35.35545618392078}, 791 | {-84.01107788085938, 35.37337460834958}, 792 | {-84.00970458984374, 35.39128905521763}, 793 | {-84.01931762695312, 35.41479572901859}, 794 | {-84.00283813476562, 35.429344044107154}, 795 | {-83.93692016601562, 35.47409160773029}, 796 | {-83.91220092773438, 35.47632833265728}, 797 | {-83.88885498046875, 35.504282143299655}, 798 | {-83.88473510742186, 35.516578738902936}, 799 | {-83.8751220703125, 35.52104976129943}, 800 | {-83.85314941406249, 35.52104976129943}, 801 | {-83.82843017578125, 35.52104976129943}, 802 | {-83.8092041015625, 35.53446133418443}, 803 | {-83.80233764648438, 35.54116627999813}, 804 | {-83.76800537109374, 35.56239491058853}, 805 | {-83.7432861328125, 35.56239491058853}, 806 | {-83.71994018554688, 35.56239491058853}, 807 | {-83.67050170898438, 35.569097520776054}, 808 | {-83.6334228515625, 35.570214567965984}, 809 | {-83.61007690429688, 35.576916524038616}, 810 | {-83.59634399414061, 35.574682600980914}, 811 | {-83.5894775390625, 35.55904339525896}, 812 | {-83.55239868164062, 35.56574628576276}, 813 | {-83.49746704101562, 35.563512051219696}, 814 | {-83.47000122070312, 35.586968406786475}, 815 | {-83.4466552734375, 35.60818490437746}, 816 | {-83.37936401367188, 35.63609277863135}, 817 | {-83.35739135742188, 35.65618041632016}, 818 | {-83.32305908203124, 35.66622234103479}, 819 | {-83.3148193359375, 35.65394870599763}, 820 | {-83.29971313476561, 35.660643649881614}, 821 | {-83.28598022460938, 35.67180064238771}, 822 | {-83.26126098632811, 35.6907639509368}, 823 | {-83.25714111328125, 35.69968630125201}, 824 | {-83.25576782226562, 35.715298012125295}, 825 | {-83.23516845703125, 35.72310272092263}, 826 | {-83.19808959960936, 35.72756221127198}, 827 | {-83.16238403320312, 35.753199435570316}, 828 | {-83.15826416015625, 35.76322914549896}, 829 | {-83.10333251953125, 35.76991491635478}, 830 | {-83.08685302734375, 35.7843988251953}, 831 | {-83.0511474609375, 35.787740890986576}, 832 | {-83.01681518554688, 35.78328477203738}, 833 | {-83.001708984375, 35.77882840327371}, 834 | {-82.96737670898438, 35.793310688351724}, 835 | {-82.94540405273438, 35.820040281161}, 836 | {-82.9193115234375, 35.85121343450061}, 837 | {-82.9083251953125, 35.86902116501695}, 838 | {-82.90557861328125, 35.87792352995116}, 839 | {-82.91244506835938, 35.92353244718235}, 840 | {-82.88360595703125, 35.94688293218141}, 841 | {-82.85614013671875, 35.951329861522666}, 842 | {-82.8424072265625, 35.94243575255426}, 843 | {-82.825927734375, 35.92464453144099}, 844 | {-82.80670166015625, 35.927980690382704}, 845 | {-82.80532836914062, 35.94243575255426}, 846 | {-82.77923583984375, 35.97356075349624}, 847 | {-82.78060913085938, 35.99245209055831}, 848 | {-82.76138305664062, 36.00356252895066}, 849 | {-82.69546508789062, 36.04465753921525}, 850 | {-82.64465332031249, 36.060201412392914}, 851 | {-82.61306762695312, 36.060201412392914}, 852 | {-82.60620117187499, 36.033552893400376}, 853 | {-82.60620117187499, 35.991340960635405}, 854 | {-82.60620117187499, 35.97911749857497}, 855 | {-82.5787353515625, 35.96133453736691}, 856 | {-82.5677490234375, 35.951329861522666}, 857 | {-82.53067016601562, 35.97244935753683}, 858 | {-82.46475219726562, 36.006895355244666}, 859 | {-82.41668701171875, 36.070192281208456}, 860 | {-82.37960815429686, 36.10126686921446}, 861 | {-82.35488891601562, 36.117908916563685}, 862 | {-82.34115600585936, 36.113471382052175}, 863 | {-82.29583740234375, 36.13343831245866}, 864 | {-82.26287841796874, 36.13565654678543}, 865 | {-82.23403930664062, 36.13565654678543}, 866 | {-82.2216796875, 36.154509006695}, 867 | {-82.20382690429688, 36.15561783381855}, 868 | {-82.19009399414062, 36.144528857027744}, 869 | {-82.15438842773438, 36.15007354140755}, 870 | {-82.14065551757812, 36.134547437460064}, 871 | {-82.1337890625, 36.116799556445024}, 872 | {-82.12142944335938, 36.10570509327921}, 873 | {-82.08984375, 36.10792411128649}, 874 | {-82.05276489257811, 36.12678323326429}, 875 | {-82.03628540039062, 36.12900165569652}, 876 | {-81.91268920898438, 36.29409768373033}, 877 | {-81.89071655273438, 36.30959215409138}, 878 | {-81.86325073242188, 36.33504067209607}, 879 | {-81.83029174804688, 36.34499652561904}, 880 | {-81.80145263671875, 36.35605709240176}, 881 | {-81.77947998046874, 36.34610265300638}, 882 | {-81.76162719726562, 36.33835943134047}, 883 | {-81.73690795898438, 36.33835943134047}, 884 | {-81.71905517578125, 36.33835943134047}, 885 | {-81.70669555664062, 36.33504067209607}, 886 | {-81.70669555664062, 36.342784223707234}, 887 | {-81.72317504882812, 36.357163062654365}, 888 | {-81.73278808593749, 36.379279167407965}, 889 | {-81.73690795898438, 36.40028364332352}, 890 | {-81.73690795898438, 36.41354670392876}, 891 | {-81.72454833984374, 36.423492513472326}, 892 | {-81.71768188476562, 36.445589751779174}, 893 | {-81.69845581054688, 36.47541104282962}, 894 | {-81.69845581054688, 36.51073994146672}, 895 | {-81.705322265625, 36.53060536411363}, 896 | {-81.69158935546875, 36.55929085774001}, 897 | {-81.68060302734375, 36.56480607840351}, 898 | {-81.68197631835938, 36.58686302344181}, 899 | {-81.04202270507812, 36.56370306576917}, 900 | {-80.74264526367186, 36.561496993252575}, 901 | {-79.89120483398438, 36.54053616262899}, 902 | {-78.68408203124999, 36.53943280355122}, 903 | {-77.88345336914062, 36.54053616262899}, 904 | {-76.91665649414062, 36.54163950596125}, 905 | {-76.91665649414062, 36.55046568575947}, 906 | {-76.31103515625, 36.551568887374}, 907 | {-75.79605102539062, 36.54936246839778}, 908 | {-75.6298828125, 36.07574221562703}, 909 | {-75.4925537109375, 35.82226734114509}, 910 | {-75.3936767578125, 35.639441068973916}, 911 | {-75.41015624999999, 35.43829554739668}, 912 | {-75.43212890625, 35.263561862152095}, 913 | {-75.487060546875, 35.18727767598896}, 914 | {-75.5914306640625, 35.17380831799959}, 915 | {-75.9210205078125, 35.04798673426734}, 916 | {-76.17919921875, 34.867904962568744}, 917 | {-76.41540527343749, 34.62868797377061}, 918 | {-76.4593505859375, 34.57442951865274}, 919 | {-76.53076171875, 34.53371242139567}, 920 | {-76.5911865234375, 34.551811369170494}, 921 | {-76.651611328125, 34.615126683462194}, 922 | {-76.761474609375, 34.63320791137959}, 923 | {-77.069091796875, 34.59704151614417}, 924 | {-77.376708984375, 34.45674800347809}, 925 | {-77.5909423828125, 34.3207552752374}, 926 | {-77.8326416015625, 33.97980872872457}, 927 | {-77.9150390625, 33.80197351806589}, 928 | {-77.9754638671875, 33.73804486328907}, 929 | {-78.11279296875, 33.8521697014074}, 930 | {-78.2830810546875, 33.8521697014074}, 931 | {-78.4808349609375, 33.815666308702774}, 932 | {-79.6728515625, 34.8047829195724}, 933 | {-80.782470703125, 34.836349990763864}, 934 | {-80.782470703125, 34.91746688928252}, 935 | {-80.9307861328125, 35.092945313732635}, 936 | {-81.0516357421875, 35.02999636902566}, 937 | {-81.0516357421875, 35.05248370662468}, 938 | {-81.0516357421875, 35.137879119634185}, 939 | {-82.3150634765625, 35.19625600786368}, 940 | {-82.3590087890625, 35.19625600786368}, 941 | {-82.40295410156249, 35.22318504970181}, 942 | {-82.4688720703125, 35.16931803601131}, 943 | {-82.6885986328125, 35.1154153142536}, 944 | {-82.781982421875, 35.06147690849717}, 945 | {-83.1060791015625, 35.003003395276714}, 946 | {-83.616943359375, 34.99850370014629}, 947 | {-84.05639648437499, 34.985003130171066}, 948 | {-84.22119140625, 34.985003130171066}, 949 | {-84.32281494140625, 34.9895035675793}, 950 | }, 951 | }, 952 | { 953 | []geom.Coordinate{ 954 | {-75.69030761718749, 35.74205383068037}, 955 | {-75.5914306640625, 35.74205383068037}, 956 | {-75.5419921875, 35.585851593232356}, 957 | {-75.56396484375, 35.32633026307483}, 958 | {-75.69030761718749, 35.285984736065735}, 959 | {-75.970458984375, 35.16482750605027}, 960 | {-76.2066650390625, 34.994003757575776}, 961 | {-76.300048828125, 35.02999636902566}, 962 | {-76.409912109375, 35.07946034047981}, 963 | {-76.5252685546875, 35.10642805736423}, 964 | {-76.4208984375, 35.25907654252574}, 965 | {-76.3385009765625, 35.294952147406576}, 966 | {-76.0858154296875, 35.29943548054543}, 967 | {-75.948486328125, 35.44277092585766}, 968 | {-75.8660888671875, 35.53669637839501}, 969 | {-75.772705078125, 35.567980458012094}, 970 | {-75.706787109375, 35.634976650677295}, 971 | {-75.706787109375, 35.74205383068037}, 972 | {-75.69030761718749, 35.74205383068037}, 973 | }, 974 | }, 975 | }, 976 | }, 977 | { 978 | geom.Hdr{Dim: geom.XY, Srid: 4326}, 979 | []geom.LinearRing{ 980 | { 981 | []geom.Coordinate{ 982 | {-109.0283203125, 36.98500309285596}, 983 | {-109.0283203125, 40.97989806962013}, 984 | {-102.06298828125, 40.97989806962013}, 985 | {-102.06298828125, 37.00255267215955}, 986 | {-109.0283203125, 36.98500309285596}, 987 | }, 988 | }, 989 | }, 990 | }, 991 | }, 992 | } 993 | 994 | var sb bytes.Buffer 995 | err := Encode(expected, &sb) 996 | 997 | if err != nil { 998 | t.Fatalf("failed to marshal polygon: %s", err) 999 | } 1000 | 1001 | got := make(map[string]interface{}) 1002 | err = json.Unmarshal(sb.Bytes(), &got) 1003 | 1004 | if err != nil { 1005 | t.Fatalf("Failed to parse generated GeoJSON: error %s", err) 1006 | } 1007 | 1008 | t.Logf("%s", string(sb.Bytes())) 1009 | assert.Equal(t, "MultiPolygon", got["type"]) 1010 | 1011 | polys := got["coordinates"].([]interface{}) 1012 | 1013 | assert.Equal(t, 2, len(polys)) 1014 | 1015 | poly := polys[0].([]interface{}) 1016 | 1017 | assert.Equal(t, 2, len(poly)) 1018 | 1019 | lring := poly[0].([]interface{}) 1020 | 1021 | for idx, coord := range lring { 1022 | c := coord.([]interface{}) 1023 | for jdx, value := range c { 1024 | t.Logf("%f => %f", expected.Polygons[0].Rings[0].Coordinates[idx][jdx], value.(float64)) 1025 | assert.InDelta(t, expected.Polygons[0].Rings[0].Coordinates[idx][jdx], value.(float64), 1e-9) 1026 | } 1027 | 1028 | } 1029 | 1030 | lring = poly[1].([]interface{}) 1031 | 1032 | for idx, coord := range lring { 1033 | c := coord.([]interface{}) 1034 | for jdx, value := range c { 1035 | t.Logf("%f => %f", expected.Polygons[0].Rings[1].Coordinates[idx][jdx], value.(float64)) 1036 | assert.InDelta(t, expected.Polygons[0].Rings[1].Coordinates[idx][jdx], value.(float64), 1e-9) 1037 | } 1038 | 1039 | } 1040 | 1041 | poly = polys[1].([]interface{}) 1042 | t.Logf("%+v\n", poly) 1043 | assert.Equal(t, 1, len(poly)) 1044 | 1045 | lring = poly[0].([]interface{}) 1046 | 1047 | for idx, coord := range lring { 1048 | c := coord.([]interface{}) 1049 | for jdx, value := range c { 1050 | t.Logf("%f => %f", expected.Polygons[1].Rings[0].Coordinates[idx][jdx], value.(float64)) 1051 | assert.InDelta(t, expected.Polygons[1].Rings[0].Coordinates[idx][jdx], value.(float64), 1e-9) 1052 | } 1053 | 1054 | } 1055 | } 1056 | 1057 | func TestEncodeGeometryCollection(t *testing.T) { 1058 | expected := &geom.GeometryCollection{ 1059 | geom.Hdr{ 1060 | Dim: geom.XY, 1061 | Srid: 4326, 1062 | }, 1063 | []geom.Geometry{ 1064 | &geom.Point{ 1065 | geom.Hdr{Dim: geom.XY, Srid: 4326}, 1066 | []float64{-80.66080570220947, 35.04939206472683}, 1067 | }, 1068 | &geom.Polygon{ 1069 | geom.Hdr{ 1070 | Dim: geom.XY, 1071 | Srid: 4326, 1072 | }, 1073 | []geom.LinearRing{ 1074 | { 1075 | []geom.Coordinate{ 1076 | {-80.66458225250244, 35.04496519190309}, 1077 | {-80.66344499588013, 35.04603679820616}, 1078 | {-80.66258668899536, 35.045580049697556}, 1079 | {-80.66387414932251, 35.044280059194946}, 1080 | {-80.66458225250244, 35.04496519190309}, 1081 | }, 1082 | }, 1083 | }, 1084 | }, 1085 | &geom.LineString{ 1086 | geom.Hdr{ 1087 | Dim: geom.XY, 1088 | Srid: 4326, 1089 | }, 1090 | []geom.Coordinate{ 1091 | {-80.66237211227417, 35.05950973022538}, 1092 | {-80.66269397735596, 35.0592638296087}, 1093 | {-80.66284418106079, 35.05893010615862}, 1094 | {-80.66308021545409, 35.05833291342246}, 1095 | {-80.66359519958496, 35.057753281001425}, 1096 | {-80.66387414932251, 35.05740198662245}, 1097 | {-80.66441059112549, 35.05703312589789}, 1098 | {-80.66486120223999, 35.056787217822475}, 1099 | {-80.66541910171509, 35.05650617911516}, 1100 | {-80.66563367843628, 35.05631296444281}, 1101 | {-80.66601991653441, 35.055891403570705}, 1102 | {-80.66619157791138, 35.05545227534804}, 1103 | {-80.66619157791138, 35.05517123204622}, 1104 | {-80.66625595092773, 35.05489018777713}, 1105 | {-80.6662130355835, 35.054222703761525}, 1106 | {-80.6662130355835, 35.05392409072499}, 1107 | {-80.66595554351807, 35.05290528508858}, 1108 | {-80.66569805145262, 35.052044560077285}, 1109 | {-80.66550493240356, 35.0514824490509}, 1110 | {-80.665762424469, 35.05048117920187}, 1111 | {-80.66617012023926, 35.04972582715769}, 1112 | {-80.66651344299316, 35.049286665781096}, 1113 | {-80.66692113876343, 35.0485313026898}, 1114 | {-80.66700696945189, 35.048215102112344}, 1115 | {-80.66707134246826, 35.04777593261294}, 1116 | {-80.66704988479614, 35.04738946150025}, 1117 | {-80.66696405410767, 35.04698542156371}, 1118 | {-80.66681385040283, 35.046353007216055}, 1119 | {-80.66659927368164, 35.04596652937105}, 1120 | {-80.66640615463257, 35.04561518428889}, 1121 | {-80.6659984588623, 35.045193568195565}, 1122 | {-80.66552639007568, 35.044877354697526}, 1123 | {-80.6649899482727, 35.04454357245502}, 1124 | {-80.66449642181396, 35.04417465365292}, 1125 | {-80.66385269165039, 35.04387600387859}, 1126 | {-80.66303730010986, 35.043717894732545}, 1127 | }, 1128 | }, 1129 | }, 1130 | } 1131 | 1132 | var sb bytes.Buffer 1133 | err := Encode(expected, &sb) 1134 | 1135 | if err != nil { 1136 | t.Fatalf("failed to marshal polygon: %s", err) 1137 | } 1138 | 1139 | got := make(map[string]interface{}) 1140 | err = json.Unmarshal(sb.Bytes(), &got) 1141 | 1142 | if err != nil { 1143 | t.Fatalf("Failed to parse generated GeoJSON: error %s", err) 1144 | } 1145 | 1146 | t.Logf("%s\n", string(sb.Bytes())) 1147 | assert.Equal(t, "GeometryCollection", got["type"]) 1148 | 1149 | geoms := got["geometries"].([]interface{}) 1150 | 1151 | assert.Equal(t, 3, len(geoms)) 1152 | 1153 | point := geoms[0].(map[string]interface{}) 1154 | 1155 | assert.Equal(t, "Point", point["type"]) 1156 | coords := point["coordinates"].([]interface{}) 1157 | assert.Equal(t, expected.Geometries[0].(*geom.Point).Coordinate[0], coords[0].(float64)) 1158 | assert.Equal(t, expected.Geometries[0].(*geom.Point).Coordinate[1], coords[1].(float64)) 1159 | 1160 | poly := geoms[1].(map[string]interface{}) 1161 | assert.Equal(t, "Polygon", poly["type"]) 1162 | lrings := poly["coordinates"].([]interface{}) 1163 | assert.Equal(t, 1, len(lrings)) 1164 | lring := lrings[0].([]interface{}) 1165 | 1166 | for idx, coord := range lring { 1167 | c := coord.([]interface{}) 1168 | 1169 | assert.Equal(t, 2, len(c)) 1170 | assert.Equal(t, expected.Geometries[1].(*geom.Polygon).Rings[0].Coordinates[idx][0], c[0].(float64)) 1171 | assert.Equal(t, expected.Geometries[1].(*geom.Polygon).Rings[0].Coordinates[idx][1], c[1].(float64)) 1172 | } 1173 | 1174 | lstring := geoms[2].(map[string]interface{}) 1175 | assert.Equal(t, "LineString", lstring["type"]) 1176 | 1177 | coords = lstring["coordinates"].([]interface{}) 1178 | 1179 | assert.Equal(t, len(expected.Geometries[2].(*geom.LineString).Coordinates), len(coords)) 1180 | 1181 | for idx, coord := range coords { 1182 | c := coord.([]interface{}) 1183 | 1184 | assert.Equal(t, 2, len(c)) 1185 | assert.Equal(t, expected.Geometries[2].(*geom.LineString).Coordinates[idx][0], c[0].(float64)) 1186 | assert.Equal(t, expected.Geometries[2].(*geom.LineString).Coordinates[idx][1], c[1].(float64)) 1187 | } 1188 | } 1189 | -------------------------------------------------------------------------------- /geom.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2015] Alex Davies-Moore 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package geom 18 | 19 | import ( 20 | "errors" 21 | "io" 22 | ) 23 | 24 | // Common error types 25 | var ( 26 | ErrNoGeometry = errors.New("no geometry specified") 27 | ErrUnsupportedGeom = errors.New("cannot encode unknown geometry") 28 | ErrUnknownDim = errors.New("unknown dimension") 29 | ) 30 | 31 | type Encoder interface { 32 | Encode(g *Geometry, w io.Writer) error 33 | } 34 | 35 | type Decoder interface { 36 | Decode(r io.Reader) (Geometry, error) 37 | } 38 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2015] Alex Davies-Moore 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package geom 18 | 19 | type Dimension uint32 20 | 21 | const ( 22 | XY Dimension = iota 23 | XYM 24 | XYZ 25 | XYZM 26 | UNKNOWN 27 | ) 28 | 29 | func (d Dimension) String() string { 30 | switch d { 31 | case XY: 32 | return "XY" 33 | case XYM: 34 | return "XYM" 35 | case XYZ: 36 | return "XYZ" 37 | case XYZM: 38 | return "XYZM" 39 | default: 40 | return "UNKNOWN" 41 | } 42 | } 43 | 44 | // Geometry interface 45 | type Geometry interface { 46 | Dimension() Dimension 47 | SRID() uint32 48 | Type() string 49 | } 50 | 51 | // Hdr represents core information about the geometry 52 | type Hdr struct { 53 | Dim Dimension 54 | Srid uint32 55 | } 56 | 57 | func (h *Hdr) Dimension() Dimension { 58 | return h.Dim 59 | } 60 | 61 | func (h *Hdr) SRID() uint32 { 62 | return h.Srid 63 | } 64 | 65 | // Point 66 | type Point struct { 67 | Hdr 68 | Coordinate 69 | } 70 | 71 | func (p *Point) Type() string { 72 | return "point" 73 | } 74 | 75 | // MultiPoint 76 | type MultiPoint struct { 77 | Hdr 78 | Points []Point 79 | } 80 | 81 | func (m *MultiPoint) Type() string { 82 | return "multipoint" 83 | } 84 | 85 | // LineString 86 | type LineString struct { 87 | Hdr 88 | Coordinates []Coordinate 89 | } 90 | 91 | func (l *LineString) Type() string { 92 | return "linestring" 93 | } 94 | 95 | // MultiLineString 96 | type MultiLineString struct { 97 | Hdr 98 | LineStrings []LineString 99 | } 100 | 101 | func (l *MultiLineString) Type() string { 102 | return "multilinestring" 103 | } 104 | 105 | // Polygon 106 | type Polygon struct { 107 | Hdr 108 | Rings []LinearRing 109 | } 110 | 111 | func (l *Polygon) Type() string { 112 | return "polygon" 113 | } 114 | 115 | // MultiPolygon 116 | type MultiPolygon struct { 117 | Hdr 118 | Polygons []Polygon 119 | } 120 | 121 | func (l *MultiPolygon) Type() string { 122 | return "multipolygon" 123 | } 124 | 125 | // GeometryCollection (a misnomer IMHO - should be called MultiGeometry) 126 | type GeometryCollection struct { 127 | Hdr 128 | Geometries []Geometry 129 | } 130 | 131 | func (l *GeometryCollection) Type() string { 132 | return "geometrycollection" 133 | } 134 | 135 | // LinearRing 136 | type LinearRing struct { 137 | Coordinates []Coordinate 138 | } 139 | 140 | // Coordinate 141 | type Coordinate []float64 142 | -------------------------------------------------------------------------------- /types_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright [2015] Alex Davies-Moore 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package geom 18 | 19 | // 20 | // func TestMultiLineString_GeoJSON(t *testing.T) { 21 | // expected := &MultiLineString{ 22 | // Hdr{ 23 | // dim: XYZ, 24 | // srid: 27700, 25 | // gtype: MULTILINESTRING, 26 | // }, 27 | // []LineString{ 28 | // LineString{ 29 | // Hdr{ 30 | // dim: XYZ, 31 | // srid: 27700, 32 | // gtype: LINESTRING, 33 | // }, 34 | // []Coordinate{ 35 | // []float64{100.0, 0.0}, 36 | // []float64{101.0, 1.0}, 37 | // }, 38 | // }, 39 | // LineString{ 40 | // Hdr{ 41 | // dim: XYZ, 42 | // srid: 27700, 43 | // gtype: LINESTRING, 44 | // }, 45 | // []Coordinate{ 46 | // []float64{102.0, 2.0}, 47 | // []float64{103.0, 3.0}, 48 | // }, 49 | // }, 50 | // }, 51 | // } 52 | // 53 | // geojson := expected.GeoJSON(true, true) 54 | // 55 | // t.Logf("%s\n", geojson) 56 | // } 57 | // 58 | // func TestMultiPolygon_GeoJSON(t *testing.T) { 59 | // expected := &MultiPolygon{ 60 | // Hdr{ 61 | // dim: XYZ, 62 | // srid: 27700, 63 | // gtype: MULTIPOLYGON, 64 | // }, 65 | // []Polygon{ 66 | // Polygon{ 67 | // Hdr{ 68 | // dim: XYZ, 69 | // srid: 27700, 70 | // gtype: POLYGON, 71 | // }, 72 | // []LinearRing{ 73 | // LinearRing{ 74 | // []Coordinate{ 75 | // []float64{100.0, 0.0}, 76 | // []float64{101.0, 0.0}, 77 | // []float64{101.0, 1.0}, 78 | // []float64{100.0, 1.0}, 79 | // []float64{100.0, 0.0}, 80 | // }, 81 | // }, 82 | // }, 83 | // }, 84 | // Polygon{ 85 | // Hdr{ 86 | // dim: XYZ, 87 | // srid: 27700, 88 | // gtype: POLYGON, 89 | // }, 90 | // []LinearRing{ 91 | // LinearRing{ 92 | // []Coordinate{ 93 | // []float64{100.0, 0.0}, 94 | // []float64{101.0, 0.0}, 95 | // []float64{101.0, 1.0}, 96 | // []float64{100.0, 1.0}, 97 | // []float64{100.0, 0.0}, 98 | // }, 99 | // }, 100 | // LinearRing{ 101 | // []Coordinate{ 102 | // []float64{100.2, 0.2}, 103 | // []float64{100.8, 0.2}, 104 | // []float64{100.8, 0.8}, 105 | // []float64{100.2, 0.8}, 106 | // []float64{100.2, 0.2}, 107 | // }, 108 | // }, 109 | // }, 110 | // }, 111 | // }, 112 | // } 113 | // 114 | // geojson := expected.GeoJSON(true, true) 115 | // 116 | // t.Logf("%s\n", geojson) 117 | // } 118 | // 119 | // func TestGeometryCollection_GeoJSON(t *testing.T) { 120 | // expected := &GeometryCollection{ 121 | // Hdr{ 122 | // dim: XYZ, 123 | // srid: 27700, 124 | // gtype: LINESTRING, 125 | // }, 126 | // []Geometry{ 127 | // &Point{ 128 | // Hdr{ 129 | // dim: XYZ, 130 | // srid: 27700, 131 | // gtype: POINT, 132 | // }, 133 | // []float64{100.0, 0.0}, 134 | // }, 135 | // &LineString{ 136 | // Hdr{ 137 | // dim: XYZ, 138 | // srid: 27700, 139 | // gtype: LINESTRING, 140 | // }, 141 | // []Coordinate{ 142 | // []float64{100.0, 0.0}, 143 | // []float64{101.0, 1.0}, 144 | // }, 145 | // }, 146 | // }, 147 | // } 148 | // 149 | // geojson := expected.GeoJSON(true, true) 150 | // 151 | // t.Logf("%s\n", geojson) 152 | // } 153 | --------------------------------------------------------------------------------