From 4c1c5cddee0705c3eb17cfb2d0bdd5d992dfdeca Mon Sep 17 00:00:00 2001 From: Gert Cuykens Date: Tue, 25 Jun 2019 20:11:29 +0200 Subject: [PATCH] topojson --- .travis.yml | 1 + encoding/topojson/README.md | 4 + encoding/topojson/bounds.go | 96 ++ encoding/topojson/bounds_test.go | 27 + encoding/topojson/cut.go | 59 ++ encoding/topojson/cut_test.go | 1279 +++++++++++++++++++++++ encoding/topojson/dedup.go | 215 ++++ encoding/topojson/dedup_test.go | 1322 ++++++++++++++++++++++++ encoding/topojson/delta.go | 23 + encoding/topojson/delta_test.go | 53 + encoding/topojson/doc.go | 9 + encoding/topojson/extract.go | 118 +++ encoding/topojson/extract_test.go | 141 +++ encoding/topojson/filter.go | 115 +++ encoding/topojson/filter_test.go | 50 + encoding/topojson/geojson.go | 155 +++ encoding/topojson/geojson_test.go | 279 +++++ encoding/topojson/geometry.go | 237 +++++ encoding/topojson/join.go | 114 ++ encoding/topojson/join_test.go | 912 ++++++++++++++++ encoding/topojson/pointfeature_test.go | 35 + encoding/topojson/postquantize.go | 77 ++ encoding/topojson/prequantize.go | 61 ++ encoding/topojson/prequantize_test.go | 283 +++++ encoding/topojson/quantize.go | 129 +++ encoding/topojson/removeempty.go | 94 ++ encoding/topojson/simplify.go | 46 + encoding/topojson/topojson_test.go | 23 + encoding/topojson/topology.go | 140 +++ encoding/topojson/topology_test.go | 86 ++ encoding/topojson/unpackarcs.go | 13 + encoding/topojson/unpackobjects.go | 88 ++ 32 files changed, 6284 insertions(+) create mode 100644 encoding/topojson/README.md create mode 100644 encoding/topojson/bounds.go create mode 100644 encoding/topojson/bounds_test.go create mode 100644 encoding/topojson/cut.go create mode 100644 encoding/topojson/cut_test.go create mode 100644 encoding/topojson/dedup.go create mode 100644 encoding/topojson/dedup_test.go create mode 100644 encoding/topojson/delta.go create mode 100644 encoding/topojson/delta_test.go create mode 100644 encoding/topojson/doc.go create mode 100644 encoding/topojson/extract.go create mode 100644 encoding/topojson/extract_test.go create mode 100644 encoding/topojson/filter.go create mode 100644 encoding/topojson/filter_test.go create mode 100644 encoding/topojson/geojson.go create mode 100644 encoding/topojson/geojson_test.go create mode 100644 encoding/topojson/geometry.go create mode 100644 encoding/topojson/join.go create mode 100644 encoding/topojson/join_test.go create mode 100644 encoding/topojson/pointfeature_test.go create mode 100644 encoding/topojson/postquantize.go create mode 100644 encoding/topojson/prequantize.go create mode 100644 encoding/topojson/prequantize_test.go create mode 100644 encoding/topojson/quantize.go create mode 100644 encoding/topojson/removeempty.go create mode 100644 encoding/topojson/simplify.go create mode 100644 encoding/topojson/topojson_test.go create mode 100644 encoding/topojson/topology.go create mode 100644 encoding/topojson/topology_test.go create mode 100644 encoding/topojson/unpackarcs.go create mode 100644 encoding/topojson/unpackobjects.go diff --git a/.travis.yml b/.travis.yml index ad3e52f..6e22f22 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ go: - 'tip' install: + - go get -v github.com/cheekybits/is - go get -v github.com/paulmach/orb/... - go install -v - go test -i ./... diff --git a/encoding/topojson/README.md b/encoding/topojson/README.md new file mode 100644 index 0000000..5d191bf --- /dev/null +++ b/encoding/topojson/README.md @@ -0,0 +1,4 @@ +# topojson - TopoJSON implementation in Go + +Implements the TopoJSON specification: +https://github.com/mbostock/topojson-specification diff --git a/encoding/topojson/bounds.go b/encoding/topojson/bounds.go new file mode 100644 index 0000000..21b1bc5 --- /dev/null +++ b/encoding/topojson/bounds.go @@ -0,0 +1,96 @@ +package topojson + +import ( + "math" + + "github.com/paulmach/orb" +) + +func (t *Topology) bounds() { + t.BBox = []float64{ + math.MaxFloat64, + math.MaxFloat64, + -math.MaxFloat64, + -math.MaxFloat64, + } + + for _, f := range t.input { + t.boundGeometry(f.Geometry) + } + +} + +func (t *Topology) boundGeometry(g orb.Geometry) { + switch c := g.(type) { + case orb.Point: + t.Bound(c.Bound()) + case orb.MultiPoint: + t.Bound(c.Bound()) + case orb.LineString: + t.Bound(c.Bound()) + case orb.MultiLineString: + t.Bound(c.Bound()) + case orb.Polygon: + t.Bound(c.Bound()) + case orb.MultiPolygon: + t.Bound(c.Bound()) + case orb.Collection: + t.Bound(c.Bound()) + // for _, geo := range c { + // t.boundGeometry(geo) + // } + } +} + +func (t *Topology) Bound(b orb.Bound) { + xx := []float64{b.Min[0], b.Max[0]} + yy := []float64{b.Min[1], b.Max[1]} + for _, x := range xx { + if x < t.BBox[0] { + t.BBox[0] = x + } + if x > t.BBox[2] { + t.BBox[2] = x + } + } + for _, y := range yy { + if y < t.BBox[1] { + t.BBox[1] = y + } + if y > t.BBox[3] { + t.BBox[3] = y + } + } +} + +func (t *Topology) boundPoint(p []float64) { + x := p[0] + y := p[1] + + if x < t.BBox[0] { + t.BBox[0] = x + } + if x > t.BBox[2] { + t.BBox[2] = x + } + if y < t.BBox[1] { + t.BBox[1] = y + } + if y > t.BBox[3] { + t.BBox[3] = y + } +} + +func (t *Topology) boundPoints(l [][]float64) { + for _, p := range l { + t.boundPoint(p) + } +} + +func (t *Topology) boundMultiPoints(ml [][][]float64) { + for _, l := range ml { + for _, p := range l { + t.boundPoint(p) + } + } +} diff --git a/encoding/topojson/bounds_test.go b/encoding/topojson/bounds_test.go new file mode 100644 index 0000000..a6258ff --- /dev/null +++ b/encoding/topojson/bounds_test.go @@ -0,0 +1,27 @@ +package topojson + +import ( + "testing" + + "github.com/cheekybits/is" + orb "github.com/paulmach/orb" + geojson "github.com/paulmach/orb/geojson" +) + +func TestBBox(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("foo", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("bar", orb.LineString{ + orb.Point{-1, 0}, orb.Point{1, 0}, orb.Point{-2, 3}, + }), + } + + topo := &Topology{input: in} + topo.bounds() + + is.Equal(topo.BBox, []float64{-2, 0, 2, 3}) +} diff --git a/encoding/topojson/cut.go b/encoding/topojson/cut.go new file mode 100644 index 0000000..6d7e55d --- /dev/null +++ b/encoding/topojson/cut.go @@ -0,0 +1,59 @@ +package topojson + +func (t *Topology) cut() { + junctions := t.join() + + for _, line := range t.lines { + mid := line.Start + end := line.End + + mid += 1 + for mid < end { + if junctions.Has(t.coordinates[mid]) { + next := &arc{Start: mid, End: line.End} + line.End = mid + line.Next = next + line = next + } + mid += 1 + } + } + + for _, ring := range t.rings { + start := ring.Start + mid := start + end := ring.End + fixed := junctions.Has(t.coordinates[start]) + + mid += 1 + for mid < end { + if junctions.Has(t.coordinates[mid]) { + if fixed { + next := &arc{Start: mid, End: ring.End} + ring.End = mid + ring.Next = next + ring = next + } else { + // For the first junction, we can rotate rather than cut + t.rotateCoordinates(start, end, end-mid) + t.coordinates[end] = t.coordinates[start] + fixed = true + mid = start // restart; we may have skipped junctions + } + } + mid += 1 + } + } +} + +func (t *Topology) rotateCoordinates(start, end, offset int) { + t.reverseCoordinates(start, end) + t.reverseCoordinates(start, start+offset) + t.reverseCoordinates(start+offset, end) +} + +func (t *Topology) reverseCoordinates(start, end int) { + for i, j := start, end; i < j; i, j = i+1, j-1 { + t.coordinates[i], t.coordinates[j] = t.coordinates[j], t.coordinates[i] + } +} diff --git a/encoding/topojson/cut_test.go b/encoding/topojson/cut_test.go new file mode 100644 index 0000000..5cc6f96 --- /dev/null +++ b/encoding/topojson/cut_test.go @@ -0,0 +1,1279 @@ +package topojson + +import ( + "reflect" + "testing" + + "github.com/cheekybits/is" + orb "github.com/paulmach/orb" + "github.com/paulmach/orb/geojson" +) + +// See https://github.com/mbostock/topojson/blob/master/test/topology/cut-test.js + +// cut exact duplicate lines ABC & ABC have no cuts +func TestCutDuplicates(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("abc2", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abc") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 2) + is.Nil(o1.Arc.Next) + + o2 := GetFeature(topo, "abc2") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 3) + is.Equal(o2.Arc.End, 5) + is.Nil(o2.Arc.Next) +} + +// cut reversed duplicate lines ABC & CBA have no cuts +func TestCutReversedDuplicates(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("cba", orb.LineString{ + orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abc") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 2) + is.Nil(o1.Arc.Next) + + o2 := GetFeature(topo, "cba") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 3) + is.Equal(o2.Arc.End, 5) + is.Nil(o2.Arc.Next) +} + +// cut exact duplicate rings ABCA & ABCA have no cuts +func TestCutDuplicateRings(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }, + }), + NewTestFeature("abca2", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 3) + is.Nil(o1.Arcs[0].Next) + + o2 := GetFeature(topo, "abca2") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 4) + is.Equal(o2.Arcs[0].End, 7) + is.Nil(o2.Arcs[0].Next) +} + +// cut reversed duplicate rings ACBA & ABCA have no cuts +func TestCutReversedDuplicateRings(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }, + }), + NewTestFeature("acba", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 3) + is.Nil(o1.Arcs[0].Next) + + o2 := GetFeature(topo, "acba") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 4) + is.Equal(o2.Arcs[0].End, 7) + is.Nil(o2.Arcs[0].Next) +} + +// cut rotated duplicate rings BCAB & ABCA have no cuts +func TestCutRotatedDuplicateRings(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }, + }), + NewTestFeature("bcab", orb.Polygon{ + orb.Ring{ + orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, orb.Point{1, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 3) + is.Nil(o1.Arcs[0].Next) + + o2 := GetFeature(topo, "bcab") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 4) + is.Equal(o2.Arcs[0].End, 7) + is.Nil(o2.Arcs[0].Next) +} + +// cut ring ABCA & line ABCA have no cuts +func TestCutRingLine(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcaLine", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }), + NewTestFeature("abcaPolygon", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abcaLine") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 3) + is.Nil(o1.Arc.Next) + + o2 := GetFeature(topo, "abcaPolygon") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 4) + is.Equal(o2.Arcs[0].End, 7) + is.Nil(o2.Arcs[0].Next) +} + +// cut ring BCAB & line ABCA have no cuts +func TestCutRingLineReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcaLine", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }), + NewTestFeature("bcabPolygon", orb.Polygon{ + orb.Ring{ + orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, orb.Point{1, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abcaLine") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 3) + is.Nil(o1.Arc.Next) + + o2 := GetFeature(topo, "bcabPolygon") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 4) + is.Equal(o2.Arcs[0].End, 7) + is.Nil(o2.Arcs[0].Next) +} + +// cut ring ABCA & line BCAB have no cuts +func TestCutRingLineReversed2(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("bcabLine", orb.LineString{ + orb.Point{1, 0}, {2, 0}, orb.Point{0, 0}, orb.Point{1, 0}, + }), + NewTestFeature("abcaPolygon", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "bcabLine") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 3) + is.Nil(o1.Arc.Next) + + o2 := GetFeature(topo, "abcaPolygon") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 4) + is.Equal(o2.Arcs[0].End, 7) + is.Nil(o2.Arcs[0].Next) +} + +// cut when an old arc ABC extends a new arc AB, ABC is cut into AB-BC +func TestCutOldArcExtends(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("ab", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abc") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Equal(o1.Arc.Next.Start, 1) + is.Equal(o1.Arc.Next.End, 2) + is.Nil(o1.Arc.Next.Next) + + o2 := GetFeature(topo, "ab") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 3) + is.Equal(o2.Arc.End, 4) + is.Nil(o2.Arc.Next) +} + +// cut when a reversed old arc CBA extends a new arc AB, CBA is cut into CB-BA +func TestCutReversedOldArcExtends(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("cba", orb.LineString{ + orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("ab", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "cba") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Equal(o1.Arc.Next.Start, 1) + is.Equal(o1.Arc.Next.End, 2) + is.Nil(o1.Arc.Next.Next) + + o2 := GetFeature(topo, "ab") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 3) + is.Equal(o2.Arc.End, 4) + is.Nil(o2.Arc.Next) +} + +// cut when a new arc ADE shares its start with an old arc ABC, there are no cuts +func TestCutNewArcSharesStart(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("ade", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 1}, orb.Point{2, 1}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "ade") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 2) + is.Nil(o1.Arc.Next) + + o2 := GetFeature(topo, "abc") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 3) + is.Equal(o2.Arc.End, 5) + is.Nil(o2.Arc.Next) +} + +// cut ring ABA has no junctions +func TestCutRingNoCuts(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("aba", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "aba") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 2) + is.Nil(o1.Arcs[0].Next) +} + +// cut ring AA has no cuts +func TestCutRingAANoCuts(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("aa", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "aa") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 1) + is.Nil(o1.Arcs[0].Next) +} + +// cut degenerate ring A has no cuts +func TestCutRingANoCuts(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("a", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "a") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 0) + is.Nil(o1.Arcs[0].Next) +} + +// cut when a new line DEC shares its end with an old line ABC, there are no cuts +func TestCutNewLineSharesEnd(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("dec", orb.LineString{ + orb.Point{0, 1}, orb.Point{1, 1}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abc") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 2) + is.Nil(o1.Arc.Next) + + o2 := GetFeature(topo, "dec") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 3) + is.Equal(o2.Arc.End, 5) + is.Nil(o2.Arc.Next) +} + +// cut when a new line ABC extends an old line AB, ABC is cut into AB-BC +func TestCutNewLineExtends(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("ab", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, + }), + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "ab") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Nil(o1.Arc.Next) + + o2 := GetFeature(topo, "abc") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 2) + is.Equal(o2.Arc.End, 3) + is.Equal(o2.Arc.Next.Start, 3) + is.Equal(o2.Arc.Next.End, 4) + is.Nil(o2.Arc.Next.Next) +} + +// cut when a new line ABC extends a reversed old line BA, ABC is cut into AB-BC +func TestCutNewLineExtendsReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("ba", orb.LineString{ + orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "ba") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Nil(o1.Arc.Next) + + o2 := GetFeature(topo, "abc") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 2) + is.Equal(o2.Arc.End, 3) + is.Equal(o2.Arc.Next.Start, 3) + is.Equal(o2.Arc.Next.End, 4) + is.Nil(o2.Arc.Next.Next) +} + +// cut when a new line starts BC in the middle of an old line ABC, ABC is cut into AB-BC +func TestCutNewStartsMiddle(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("bc", orb.LineString{ + orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abc") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Equal(o1.Arc.Next.Start, 1) + is.Equal(o1.Arc.Next.End, 2) + is.Nil(o1.Arc.Next.Next) + + o2 := GetFeature(topo, "bc") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 3) + is.Equal(o2.Arc.End, 4) + is.Nil(o2.Arc.Next) +} + +// cut when a new line BC starts in the middle of a reversed old line CBA, CBA is cut into CB-BA +func TestCutNewStartsMiddleReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("cba", orb.LineString{ + orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("bc", orb.LineString{ + orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "cba") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Equal(o1.Arc.Next.Start, 1) + is.Equal(o1.Arc.Next.End, 2) + is.Nil(o1.Arc.Next.Next) + + o2 := GetFeature(topo, "bc") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 3) + is.Equal(o2.Arc.End, 4) + is.Nil(o2.Arc.Next) +} + +// cut when a new line ABD deviates from an old line ABC, ABD is cut into AB-BD and ABC is cut into AB-BC +func TestCutNewLineDeviates(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("abd", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{3, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abc") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Equal(o1.Arc.Next.Start, 1) + is.Equal(o1.Arc.Next.End, 2) + is.Nil(o1.Arc.Next.Next) + + o2 := GetFeature(topo, "abd") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 3) + is.Equal(o2.Arc.End, 4) + is.Equal(o2.Arc.Next.Start, 4) + is.Equal(o2.Arc.Next.End, 5) + is.Nil(o2.Arc.Next.Next) +} + +// cut when a new line ABD deviates from a reversed old line CBA, CBA is cut into CB-BA and ABD is cut into AB-BD +func TestCutNewLineDeviatesReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("cba", orb.LineString{ + orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("abd", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{3, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "cba") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Equal(o1.Arc.Next.Start, 1) + is.Equal(o1.Arc.Next.End, 2) + is.Nil(o1.Arc.Next.Next) + + o2 := GetFeature(topo, "abd") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 3) + is.Equal(o2.Arc.End, 4) + is.Equal(o2.Arc.Next.Start, 4) + is.Equal(o2.Arc.Next.End, 5) + is.Nil(o2.Arc.Next.Next) +} + +// cut when a new line DBC merges into an old line ABC, DBC is cut into DB-BC and ABC is cut into AB-BC +func TestCutNewLineMerges(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("dbc", orb.LineString{ + orb.Point{3, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abc") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Equal(o1.Arc.Next.Start, 1) + is.Equal(o1.Arc.Next.End, 2) + is.Nil(o1.Arc.Next.Next) + + o2 := GetFeature(topo, "dbc") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 3) + is.Equal(o2.Arc.End, 4) + is.Equal(o2.Arc.Next.Start, 4) + is.Equal(o2.Arc.Next.End, 5) + is.Nil(o2.Arc.Next.Next) +} + +// cut when a new line DBC merges into a reversed old line CBA, DBC is cut into DB-BC and CBA is cut into CB-BA +func TestCutNewLineMergesReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("cba", orb.LineString{ + orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("dbc", orb.LineString{ + orb.Point{3, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "cba") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Equal(o1.Arc.Next.Start, 1) + is.Equal(o1.Arc.Next.End, 2) + is.Nil(o1.Arc.Next.Next) + + o2 := GetFeature(topo, "dbc") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 3) + is.Equal(o2.Arc.End, 4) + is.Equal(o2.Arc.Next.Start, 4) + is.Equal(o2.Arc.Next.End, 5) + is.Nil(o2.Arc.Next.Next) +} + +// cut when a new line DBE shares a single midpoint with an old line ABC, DBE is cut into DB-BE and ABC is cut into AB-BC +func TestCutNewLineSharesMidpoint(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("dbe", orb.LineString{ + orb.Point{0, 1}, orb.Point{1, 0}, orb.Point{2, 1}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abc") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Equal(o1.Arc.Next.Start, 1) + is.Equal(o1.Arc.Next.End, 2) + is.Nil(o1.Arc.Next.Next) + + o2 := GetFeature(topo, "dbe") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 3) + is.Equal(o2.Arc.End, 4) + is.Equal(o2.Arc.Next.Start, 4) + is.Equal(o2.Arc.Next.End, 5) + is.Nil(o2.Arc.Next.Next) +} + +// cut when a new line ABDE skips a point with an old line ABCDE, ABDE is cut into AB-BD-DE and ABCDE is cut into AB-BCD-DE +func TestCutNewLineSkipsPoint(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcde", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{3, 0}, orb.Point{4, 0}, + }), + NewTestFeature("adbe", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{3, 0}, orb.Point{4, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abcde") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + o1Next := o1.Arc.Next + is.Equal(o1Next.Start, 1) + is.Equal(o1Next.End, 3) + is.Equal(o1Next.Next.Start, 3) + is.Equal(o1Next.Next.End, 4) + is.Nil(o1Next.Next.Next) + + o2 := GetFeature(topo, "adbe") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 5) + is.Equal(o2.Arc.End, 6) + o2Next := o2.Arc.Next + is.Equal(o2Next.Start, 6) + is.Equal(o2Next.End, 7) + is.Equal(o2Next.Next.Start, 7) + is.Equal(o2Next.Next.End, 8) + is.Nil(o2Next.Next.Next) +} + +// cut when a new line ABDE skips a point with a reversed old line EDCBA, ABDE is cut into AB-BD-DE and EDCBA is cut into ED-DCB-BA +func TestCutNewLineSkipsPointReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("edcba", orb.LineString{ + orb.Point{4, 0}, orb.Point{3, 0}, orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("adbe", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{3, 0}, orb.Point{4, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "edcba") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + o1Next := o1.Arc.Next + is.Equal(o1Next.Start, 1) + is.Equal(o1Next.End, 3) + is.Equal(o1Next.Next.Start, 3) + is.Equal(o1Next.Next.End, 4) + is.Nil(o1Next.Next.Next) + + o2 := GetFeature(topo, "adbe") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 5) + is.Equal(o2.Arc.End, 6) + o2Next := o2.Arc.Next + is.Equal(o2Next.Start, 6) + is.Equal(o2Next.End, 7) + is.Equal(o2Next.Next.Start, 7) + is.Equal(o2Next.Next.End, 8) + is.Nil(o2Next.Next.Next) +} + +// cut when a line ABCDBE self-intersects with its middle, it is not cut +func TestCutSelfIntersectsMiddle(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcdbe", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{3, 0}, orb.Point{1, 0}, orb.Point{4, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abcdbe") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 5) + is.Nil(o1.Arc.Next) +} + +// cut when a line ABACD self-intersects with its start, it is cut into ABA-ACD +func TestCutSelfIntersectsStart(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abacd", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 0}, orb.Point{3, 0}, orb.Point{4, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abacd") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 2) + is.Equal(o1.Arc.Next.Start, 2) + is.Equal(o1.Arc.Next.End, 4) + is.Nil(o1.Arc.Next.Next) +} + +// cut when a line ABDCD self-intersects with its end, it is cut into ABD-DCD +func TestCutSelfIntersectsEnd(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcdbd", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{4, 0}, orb.Point{3, 0}, orb.Point{4, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abcdbd") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 2) + is.Equal(o1.Arc.Next.Start, 2) + is.Equal(o1.Arc.Next.End, 4) + is.Nil(o1.Arc.Next.Next) +} + +// cut when an old line ABCDBE self-intersects and shares a point B, ABCDBE is cut into AB-BCDB-BE and FBG is cut into FB-BG +func TestCutSelfIntersectsShares(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcdbe", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{3, 0}, orb.Point{1, 0}, orb.Point{4, 0}, + }), + NewTestFeature("fbg", orb.LineString{ + orb.Point{0, 1}, orb.Point{1, 0}, orb.Point{2, 1}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abcdbe") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + o1Next := o1.Arc.Next + is.Equal(o1Next.Start, 1) + is.Equal(o1Next.End, 4) + is.Equal(o1Next.Next.Start, 4) + is.Equal(o1Next.Next.End, 5) + is.Nil(o1Next.Next.Next) + + o2 := GetFeature(topo, "fbg") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 6) + is.Equal(o2.Arc.End, 7) + is.Equal(o2.Arc.Next.Start, 7) + is.Equal(o2.Arc.Next.End, 8) + is.Nil(o2.Arc.Next.Next) +} + +// cut when a line ABCA is closed, there are no cuts +func TestCutLineClosed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 3) + is.Nil(o1.Arc.Next) +} + +// cut when a ring ABCA is closed, there are no cuts +func TestCutRingClosed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 3) + is.Nil(o1.Arcs[0].Next) +} + +// cut exact duplicate rings ABCA & ABCA have no cuts +func TestCutDuplicateRingsShare(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + NewTestFeature("abca2", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 3) + is.Nil(o1.Arcs[0].Next) + + o2 := GetFeature(topo, "abca2") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 4) + is.Equal(o2.Arcs[0].End, 7) + is.Nil(o2.Arcs[0].Next) +} + +// cut reversed duplicate rings ABCA & ACBA have no cuts +func TestCutDuplicateRingsReversedShare(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + NewTestFeature("acba", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{0, 1}, orb.Point{1, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 3) + is.Nil(o1.Arcs[0].Next) + + o2 := GetFeature(topo, "acba") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 4) + is.Equal(o2.Arcs[0].End, 7) + is.Nil(o2.Arcs[0].Next) +} + +// cut coincident rings ABCA & BCAB have no cuts +func TestCutCoincidentRings(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + NewTestFeature("bcab", orb.Polygon{ + orb.Ring{ + orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, orb.Point{1, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 3) + is.Nil(o1.Arcs[0].Next) + + o2 := GetFeature(topo, "bcab") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 4) + is.Equal(o2.Arcs[0].End, 7) + is.Nil(o2.Arcs[0].Next) +} + +// cut coincident rings ABCA & BACB have no cuts +func TestCutCoincidentRings2(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + NewTestFeature("bacb", orb.Polygon{ + orb.Ring{ + orb.Point{1, 0}, orb.Point{0, 0}, orb.Point{0, 1}, orb.Point{1, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 3) + is.Nil(o1.Arcs[0].Next) + + o2 := GetFeature(topo, "bacb") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 4) + is.Equal(o2.Arcs[0].End, 7) + is.Nil(o2.Arcs[0].Next) +} + +// cut coincident rings ABCDA, EFAE & GHCG are cut into ABC-CDA, EFAE and GHCG +func TestCutCoincidentRings3(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcda", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{1, 1}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + NewTestFeature("efae", orb.Polygon{ + orb.Ring{ + orb.Point{0, -1}, orb.Point{1, -1}, orb.Point{0, 0}, orb.Point{0, -1}, + }, + }), + NewTestFeature("ghcg", orb.Polygon{ + orb.Ring{ + orb.Point{0, 2}, orb.Point{1, 2}, orb.Point{1, 1}, orb.Point{0, 2}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abcda") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 2) + is.Equal(o1.Arcs[0].Next.Start, 2) + is.Equal(o1.Arcs[0].Next.End, 4) + is.Nil(o1.Arcs[0].Next.Next) + + o2 := GetFeature(topo, "efae") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 5) + is.Equal(o2.Arcs[0].End, 8) + is.Nil(o2.Arcs[0].Next) + + o3 := GetFeature(topo, "ghcg") + is.Equal(o3.Type, geojson.TypePolygon) + is.Equal(len(o3.Arcs), 1) + is.Equal(o3.Arcs[0].Start, 9) + is.Equal(o3.Arcs[0].End, 12) + is.Nil(o3.Arcs[0].Next) +} + +// cut coincident rings ABCA & DBED have no cuts, but are rotated to share B +func TestCutNoCutsButRotated(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + NewTestFeature("dbed", orb.Polygon{ + orb.Ring{ + orb.Point{2, 1}, orb.Point{1, 0}, orb.Point{2, 2}, orb.Point{2, 1}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 3) + is.Nil(o1.Arcs[0].Next) + + o2 := GetFeature(topo, "dbed") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 4) + is.Equal(o2.Arcs[0].End, 7) + is.Nil(o2.Arcs[0].Next) + + is.True(reflect.DeepEqual(topo.coordinates[0:4], [][]float64{ + {1, 0}, {0, 1}, {0, 0}, {1, 0}, + })) + is.True(reflect.DeepEqual(topo.coordinates[4:8], [][]float64{ + {1, 0}, {2, 2}, {2, 1}, {1, 0}, + })) +} + +// cut overlapping rings ABCDA and BEFCB are cut into BC-CDAB and BEFC-CB +func TestCutOverlapping(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcda", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{1, 1}, orb.Point{0, 1}, orb.Point{0, 0}, // rotated to BCDAB, cut BC-CDAB + }, + }), + NewTestFeature("befcb", orb.Polygon{ + orb.Ring{ + orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{2, 1}, orb.Point{1, 1}, orb.Point{1, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + + o1 := GetFeature(topo, "abcda") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 1) + is.Equal(o1.Arcs[0].Next.Start, 1) + is.Equal(o1.Arcs[0].Next.End, 4) + is.Nil(o1.Arcs[0].Next.Next) + + o2 := GetFeature(topo, "befcb") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 5) + is.Equal(o2.Arcs[0].End, 8) + is.Equal(o2.Arcs[0].Next.Start, 8) + is.Equal(o2.Arcs[0].Next.End, 9) + is.Nil(o2.Arcs[0].Next.Next) +} diff --git a/encoding/topojson/dedup.go b/encoding/topojson/dedup.go new file mode 100644 index 0000000..9751287 --- /dev/null +++ b/encoding/topojson/dedup.go @@ -0,0 +1,215 @@ +package topojson + +func (t *Topology) dedup() { + arcsByEnd := make(map[point][]*arc) + + t.arcs = make([]*arc, 0) + + dedupLine := func(arc *arc) { + // Does this arc match an existing arc in order? + startPoint := newPoint(t.coordinates[arc.Start]) + startArcs, startOk := arcsByEnd[startPoint] + if startOk { + for _, startArc := range startArcs { + if t.lineEqual(arc, startArc) { + arc.Start = startArc.Start + arc.End = startArc.End + return + } + } + } + + // Does this arc match an existing arc in reverse order? + endPoint := newPoint(t.coordinates[arc.End]) + endArcs, endOk := arcsByEnd[endPoint] + if endOk { + for _, endArc := range endArcs { + if t.lineEqualReverse(arc, endArc) { + arc.Start = endArc.End + arc.End = endArc.Start + return + } + } + } + + arcsByEnd[startPoint] = append(startArcs, arc) + arcsByEnd[endPoint] = append(endArcs, arc) + t.arcs = append(t.arcs, arc) + } + + dedupRing := func(arc *arc) { + // Does this arc match an existing line in order, or reverse order? + // Rings are closed, so their start point and end point is the same. + endPoint := newPoint(t.coordinates[arc.Start]) + endArcs, endOk := arcsByEnd[endPoint] + if endOk { + for _, endArc := range endArcs { + if t.ringEqual(arc, endArc) { + arc.Start = endArc.Start + arc.End = endArc.End + return + } + + if t.ringEqualReverse(arc, endArc) { + arc.Start = endArc.End + arc.End = endArc.Start + return + } + } + } + + // Otherwise, does this arc match an existing ring in order, or reverse order? + endPoint = newPoint(t.coordinates[arc.Start+t.findMinimumOffset(arc)]) + endArcs, endOk = arcsByEnd[endPoint] + if endOk { + for _, endArc := range endArcs { + if t.ringEqual(arc, endArc) { + arc.Start = endArc.Start + arc.End = endArc.End + return + } + + if t.ringEqualReverse(arc, endArc) { + arc.Start = endArc.End + arc.End = endArc.Start + return + } + } + } + + arcsByEnd[endPoint] = append(endArcs, arc) + t.arcs = append(t.arcs, arc) + } + + for _, line := range t.lines { + for line != nil { + dedupLine(line) + line = line.Next + } + } + + for _, ring := range t.rings { + if ring.Next != nil { + // arc is no longer closed + for ring != nil { + dedupLine(ring) + ring = ring.Next + } + } else { + dedupRing(ring) + } + } + + t.lines = nil + t.rings = nil +} + +func (t *Topology) lineEqual(a, b *arc) bool { + ia := a.Start + ib := b.Start + ja := a.End + jb := b.End + if ia-ja != ib-jb { + return false + } + + for ia <= ja { + if !pointEquals(t.coordinates[ia], t.coordinates[ib]) { + return false + } + ia += 1 + ib += 1 + } + + return true +} + +func (t *Topology) lineEqualReverse(a, b *arc) bool { + ia := a.Start + ib := b.Start + ja := a.End + jb := b.End + if ia-ja != ib-jb { + return false + } + + for ia <= ja { + if !pointEquals(t.coordinates[ia], t.coordinates[jb]) { + return false + } + ia += 1 + jb -= 1 + } + + return true +} + +func (t *Topology) ringEqual(a, b *arc) bool { + ia := a.Start + ib := b.Start + ja := a.End + jb := b.End + n := ja - ia + if n != jb-ib { + return false + } + + ka := t.findMinimumOffset(a) + kb := t.findMinimumOffset(b) + + for i := 0; i < n; i++ { + pa := t.coordinates[ia+(i+ka)%n] + pb := t.coordinates[ib+(i+kb)%n] + if !pointEquals(pa, pb) { + return false + } + } + + return true +} + +func (t *Topology) ringEqualReverse(a, b *arc) bool { + ia := a.Start + ib := b.Start + ja := a.End + jb := b.End + n := ja - ia + if n != jb-ib { + return false + } + + ka := t.findMinimumOffset(a) + kb := n - t.findMinimumOffset(b) + + for i := 0; i < n; i++ { + pa := t.coordinates[ia+(i+ka)%n] + pb := t.coordinates[jb-(i+kb)%n] + if !pointEquals(pa, pb) { + return false + } + } + + return true +} + +// Rings are rotated to a consistent, but arbitrary, start point. +// This is necessary to detect when a ring and a rotated copy are dupes. +func (t *Topology) findMinimumOffset(arc *arc) int { + start := arc.Start + end := arc.End + mid := start + minimum := mid + minimumPoint := t.coordinates[mid] + + mid += 1 + for mid < end { + point := t.coordinates[mid] + if point[0] < minimumPoint[0] || point[0] == minimumPoint[0] && point[1] < minimumPoint[1] { + minimum = mid + minimumPoint = point + } + mid += 1 + } + + return minimum - start +} diff --git a/encoding/topojson/dedup_test.go b/encoding/topojson/dedup_test.go new file mode 100644 index 0000000..ae55dcf --- /dev/null +++ b/encoding/topojson/dedup_test.go @@ -0,0 +1,1322 @@ +package topojson + +import ( + "reflect" + "testing" + + "github.com/cheekybits/is" + orb "github.com/paulmach/orb" + "github.com/paulmach/orb/geojson" +) + +// See https://github.com/mbostock/topojson/blob/master/test/topology/dedup-test.js + +// dedup exact duplicate lines ABC & ABC share an arc +func TestDedupDuplicates(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("abc2", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abc") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 2) + is.Nil(o1.Arc.Next) + + o2 := GetFeature(topo, "abc2") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 0) + is.Equal(o2.Arc.End, 2) + is.Nil(o2.Arc.Next) +} + +// dedup reversed duplicate lines ABC & CBA share an arc +func TestDedupReversedDuplicates(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("cba", orb.LineString{ + orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abc") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 2) + is.Nil(o1.Arc.Next) + + o2 := GetFeature(topo, "cba") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 2) + is.Equal(o2.Arc.End, 0) + is.Nil(o2.Arc.Next) +} + +// dedup exact duplicate rings ABCA & ABCA share an arc +func TestDedupDuplicateRings(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }, + }), + NewTestFeature("abca2", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 3) + is.Nil(o1.Arcs[0].Next) + + o2 := GetFeature(topo, "abca2") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 0) + is.Equal(o2.Arcs[0].End, 3) + is.Nil(o2.Arcs[0].Next) +} + +// dedup reversed duplicate rings ACBA & ABCA share an arc +func TestDedupReversedDuplicateRings(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }, + }), + NewTestFeature("acba", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 3) + is.Nil(o1.Arcs[0].Next) + + o2 := GetFeature(topo, "acba") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 3) + is.Equal(o2.Arcs[0].End, 0) + is.Nil(o2.Arcs[0].Next) +} + +// dedup rotated duplicate rings BCAB & ABCA share an arc +func TestDedupRotatedDuplicateRings(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }, + }), + NewTestFeature("bcab", orb.Polygon{ + orb.Ring{ + orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, orb.Point{1, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 3) + is.Nil(o1.Arcs[0].Next) + + o2 := GetFeature(topo, "bcab") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 0) + is.Equal(o2.Arcs[0].End, 3) + is.Nil(o2.Arcs[0].Next) +} + +// dedup ring ABCA & line ABCA have no cuts +func TestDedupRingLine(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcaLine", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }), + NewTestFeature("abcaPolygon", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abcaLine") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 3) + is.Nil(o1.Arc.Next) + + o2 := GetFeature(topo, "abcaPolygon") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 0) + is.Equal(o2.Arcs[0].End, 3) + is.Nil(o2.Arcs[0].Next) +} + +// dedup ring BCAB & line ABCA have no cuts +func TestDedupRingLineReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcaLine", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }), + NewTestFeature("bcabPolygon", orb.Polygon{ + orb.Ring{ + orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, orb.Point{1, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abcaLine") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 3) + is.Nil(o1.Arc.Next) + + o2 := GetFeature(topo, "bcabPolygon") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 0) + is.Equal(o2.Arcs[0].End, 3) + is.Nil(o2.Arcs[0].Next) + + is.True(reflect.DeepEqual(topo.coordinates[4:8], [][]float64{ + {0, 0}, {1, 0}, {2, 0}, {0, 0}, + })) +} + +// dedup ring ABCA & line BCAB have no cuts +func TestDedupRingLineReversed2(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("bcabLine", orb.LineString{ + orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, orb.Point{1, 0}, + }), + NewTestFeature("abcaPolygon", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "bcabLine") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 3) + is.Nil(o1.Arc.Next) + + o2 := GetFeature(topo, "abcaPolygon") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 0) + is.Equal(o2.Arcs[0].End, 3) + is.Nil(o2.Arcs[0].Next) +} + +// dedup when an old arc ABC extends a new arc AB, ABC is cut into AB-BC +func TestDedupOldArcExtends(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("ab", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abc") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Equal(o1.Arc.Next.Start, 1) + is.Equal(o1.Arc.Next.End, 2) + is.Nil(o1.Arc.Next.Next) + + o2 := GetFeature(topo, "ab") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 0) + is.Equal(o2.Arc.End, 1) + is.Nil(o2.Arc.Next) +} + +// dedup when a reversed old arc CBA extends a new arc AB, CBA is cut into CB-BA +func TestDedupReversedOldArcExtends(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("cba", orb.LineString{ + orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("ab", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "cba") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Equal(o1.Arc.Next.Start, 1) + is.Equal(o1.Arc.Next.End, 2) + is.Nil(o1.Arc.Next.Next) + + o2 := GetFeature(topo, "ab") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 2) + is.Equal(o2.Arc.End, 1) + is.Nil(o2.Arc.Next) +} + +// dedup when a new arc ADE shares its start with an old arc ABC, there are no cuts +func TestDedupNewArcSharesStart(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("ade", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 1}, orb.Point{2, 1}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "ade") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 2) + is.Nil(o1.Arc.Next) + + o2 := GetFeature(topo, "abc") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 3) + is.Equal(o2.Arc.End, 5) + is.Nil(o2.Arc.Next) +} + +// dedup ring ABA has no cuts +func TestDedupRingNoCuts(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("aba", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "aba") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 2) + is.Nil(o1.Arcs[0].Next) +} + +// dedup ring AA has no cuts +func TestDedupRingAANoCuts(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("aa", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "aa") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 1) + is.Nil(o1.Arcs[0].Next) +} + +// dedup degenerate ring A has no cuts +func TestDedupRingANoCuts(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("a", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "a") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 0) + is.Nil(o1.Arcs[0].Next) +} + +// dedup when a new line DEC shares its end with an old line ABC, there are no cuts +func TestDedupNewLineSharesEnd(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("dec", orb.LineString{ + orb.Point{0, 1}, orb.Point{1, 1}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abc") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 2) + is.Nil(o1.Arc.Next) + + o2 := GetFeature(topo, "dec") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 3) + is.Equal(o2.Arc.End, 5) + is.Nil(o2.Arc.Next) +} + +// dedup when a new line ABC extends an old line AB, ABC is cut into AB-BC +func TestDedupNewLineExtends(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("ab", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, + }), + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "ab") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Nil(o1.Arc.Next) + + o2 := GetFeature(topo, "abc") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 0) + is.Equal(o2.Arc.End, 1) + is.Equal(o2.Arc.Next.Start, 3) + is.Equal(o2.Arc.Next.End, 4) + is.Nil(o2.Arc.Next.Next) +} + +// dedup when a new line ABC extends a reversed old line BA, ABC is cut into AB-BC +func TestDedupNewLineExtendsReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("ba", orb.LineString{ + orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "ba") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Nil(o1.Arc.Next) + + o2 := GetFeature(topo, "abc") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 1) + is.Equal(o2.Arc.End, 0) + is.Equal(o2.Arc.Next.Start, 3) + is.Equal(o2.Arc.Next.End, 4) + is.Nil(o2.Arc.Next.Next) +} + +// dedup when a new line starts BC in the middle of an old line ABC, ABC is cut into AB-BC +func TestDedupNewStartsMiddle(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("bc", orb.LineString{ + orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abc") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Equal(o1.Arc.Next.Start, 1) + is.Equal(o1.Arc.Next.End, 2) + is.Nil(o1.Arc.Next.Next) + + o2 := GetFeature(topo, "bc") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 1) + is.Equal(o2.Arc.End, 2) + is.Nil(o2.Arc.Next) +} + +// dedup when a new line BC starts in the middle of a reversed old line CBA, CBA is cut into CB-BA +func TestDedupNewStartsMiddleReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("cba", orb.LineString{ + orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("bc", orb.LineString{ + orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "cba") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Equal(o1.Arc.Next.Start, 1) + is.Equal(o1.Arc.Next.End, 2) + is.Nil(o1.Arc.Next.Next) + + o2 := GetFeature(topo, "bc") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 1) + is.Equal(o2.Arc.End, 0) + is.Nil(o2.Arc.Next) +} + +// dedup when a new line ABD deviates from an old line ABC, ABD is cut into AB-BD and ABC is cut into AB-BC +func TestDedupNewLineDeviates(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("abd", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{3, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abc") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Equal(o1.Arc.Next.Start, 1) + is.Equal(o1.Arc.Next.End, 2) + is.Nil(o1.Arc.Next.Next) + + o2 := GetFeature(topo, "abd") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 0) + is.Equal(o2.Arc.End, 1) + is.Equal(o2.Arc.Next.Start, 4) + is.Equal(o2.Arc.Next.End, 5) + is.Nil(o2.Arc.Next.Next) +} + +// dedup when a new line ABD deviates from a reversed old line CBA, CBA is cut into CB-BA and ABD is cut into AB-BD +func TestDedupNewLineDeviatesReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("cba", orb.LineString{ + orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("abd", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{3, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "cba") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Equal(o1.Arc.Next.Start, 1) + is.Equal(o1.Arc.Next.End, 2) + is.Nil(o1.Arc.Next.Next) + + o2 := GetFeature(topo, "abd") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 2) + is.Equal(o2.Arc.End, 1) + is.Equal(o2.Arc.Next.Start, 4) + is.Equal(o2.Arc.Next.End, 5) + is.Nil(o2.Arc.Next.Next) +} + +// dedup when a new line DBC merges into an old line ABC, DBC is cut into DB-BC and ABC is cut into AB-BC +func TestDedupNewLineMerges(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("dbc", orb.LineString{ + orb.Point{3, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abc") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Equal(o1.Arc.Next.Start, 1) + is.Equal(o1.Arc.Next.End, 2) + is.Nil(o1.Arc.Next.Next) + + o2 := GetFeature(topo, "dbc") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 3) + is.Equal(o2.Arc.End, 4) + is.Equal(o2.Arc.Next.Start, 1) + is.Equal(o2.Arc.Next.End, 2) + is.Nil(o2.Arc.Next.Next) +} + +// dedup when a new line DBC merges into a reversed old line CBA, DBC is cut into DB-BC and CBA is cut into CB-BA +func TestDedupNewLineMergesReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("cba", orb.LineString{ + orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("dbc", orb.LineString{ + orb.Point{3, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "cba") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Equal(o1.Arc.Next.Start, 1) + is.Equal(o1.Arc.Next.End, 2) + is.Nil(o1.Arc.Next.Next) + + o2 := GetFeature(topo, "dbc") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 3) + is.Equal(o2.Arc.End, 4) + is.Equal(o2.Arc.Next.Start, 1) + is.Equal(o2.Arc.Next.End, 0) + is.Nil(o2.Arc.Next.Next) +} + +// dedup when a new line DBE shares a single midpoint with an old line ABC, DBE is cut into DB-BE and ABC is cut into AB-BC +func TestDedupNewLineSharesMidpoint(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("dbe", orb.LineString{ + orb.Point{0, 1}, orb.Point{1, 0}, orb.Point{2, 1}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abc") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + is.Equal(o1.Arc.Next.Start, 1) + is.Equal(o1.Arc.Next.End, 2) + is.Nil(o1.Arc.Next.Next) + + o2 := GetFeature(topo, "dbe") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 3) + is.Equal(o2.Arc.End, 4) + is.Equal(o2.Arc.Next.Start, 4) + is.Equal(o2.Arc.Next.End, 5) + is.Nil(o2.Arc.Next.Next) +} + +// dedup when a new line ABDE skips a point with an old line ABCDE, ABDE is cut into AB-BD-DE and ABCDE is cut into AB-BCD-DE +func TestDedupNewLineSkipsPoint(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcde", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{3, 0}, orb.Point{4, 0}, + }), + NewTestFeature("adbe", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{3, 0}, orb.Point{4, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abcde") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + o1Next := o1.Arc.Next + is.Equal(o1Next.Start, 1) + is.Equal(o1Next.End, 3) + is.Equal(o1Next.Next.Start, 3) + is.Equal(o1Next.Next.End, 4) + is.Nil(o1Next.Next.Next) + + o2 := GetFeature(topo, "adbe") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 0) + is.Equal(o2.Arc.End, 1) + o2Next := o2.Arc.Next + is.Equal(o2Next.Start, 6) + is.Equal(o2Next.End, 7) + is.Equal(o2Next.Next.Start, 3) + is.Equal(o2Next.Next.End, 4) + is.Nil(o2Next.Next.Next) +} + +// dedup when a new line ABDE skips a point with a reversed old line EDCBA, ABDE is cut into AB-BD-DE and EDCBA is cut into ED-DCB-BA +func TestDedupNewLineSkipsPointReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("edcba", orb.LineString{ + orb.Point{4, 0}, orb.Point{3, 0}, orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("adbe", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{3, 0}, orb.Point{4, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "edcba") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + o1Next := o1.Arc.Next + is.Equal(o1Next.Start, 1) + is.Equal(o1Next.End, 3) + is.Equal(o1Next.Next.Start, 3) + is.Equal(o1Next.Next.End, 4) + is.Nil(o1Next.Next.Next) + + o2 := GetFeature(topo, "adbe") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 4) + is.Equal(o2.Arc.End, 3) + o2Next := o2.Arc.Next + is.Equal(o2Next.Start, 6) + is.Equal(o2Next.End, 7) + is.Equal(o2Next.Next.Start, 1) + is.Equal(o2Next.Next.End, 0) + is.Nil(o2Next.Next.Next) +} + +// dedup when a line ABCDBE self-intersects with its middle, it is not cut +func TestDedupSelfIntersectsMiddle(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcdbe", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{3, 0}, orb.Point{1, 0}, orb.Point{4, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abcdbe") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 5) + is.Nil(o1.Arc.Next) +} + +// dedup when a line ABACD self-intersects with its start, it is cut into ABA-ACD +func TestDedupSelfIntersectsStart(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abacd", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 0}, orb.Point{3, 0}, orb.Point{4, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abacd") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 2) + is.Equal(o1.Arc.Next.Start, 2) + is.Equal(o1.Arc.Next.End, 4) + is.Nil(o1.Arc.Next.Next) +} + +// dedup when a line ABDCD self-intersects with its end, it is cut into ABD-DCD +func TestDedupSelfIntersectsEnd(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcdbd", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{4, 0}, orb.Point{3, 0}, orb.Point{4, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abcdbd") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 2) + is.Equal(o1.Arc.Next.Start, 2) + is.Equal(o1.Arc.Next.End, 4) + is.Nil(o1.Arc.Next.Next) +} + +// dedup when an old line ABCDBE self-intersects and shares a point B, ABCDBE is cut into AB-BCDB-BE and FBG is cut into FB-BG +func TestDedupSelfIntersectsShares(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcdbe", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{3, 0}, orb.Point{1, 0}, orb.Point{4, 0}, + }), + NewTestFeature("fbg", orb.LineString{ + orb.Point{0, 1}, orb.Point{1, 0}, orb.Point{2, 1}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abcdbe") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 1) + o1Next := o1.Arc.Next + is.Equal(o1Next.Start, 1) + is.Equal(o1Next.End, 4) + is.Equal(o1Next.Next.Start, 4) + is.Equal(o1Next.Next.End, 5) + is.Nil(o1Next.Next.Next) + + o2 := GetFeature(topo, "fbg") + is.Equal(o2.Type, geojson.TypeLineString) + is.Equal(o2.Arc.Start, 6) + is.Equal(o2.Arc.End, 7) + is.Equal(o2.Arc.Next.Start, 7) + is.Equal(o2.Arc.Next.End, 8) + is.Nil(o2.Arc.Next.Next) +} + +// dedup when a line ABCA is closed, there are no cuts +func TestDedupLineClosed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypeLineString) + is.Equal(o1.Arc.Start, 0) + is.Equal(o1.Arc.End, 3) + is.Nil(o1.Arc.Next) +} + +// dedup when a ring ABCA is closed, there are no cuts +func TestDedupRingClosed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 3) + is.Nil(o1.Arcs[0].Next) +} + +// dedup exact duplicate rings ABCA & ABCA have no cuts +func TestDedupDuplicateRingsShare(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + NewTestFeature("abca2", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 3) + is.Nil(o1.Arcs[0].Next) + + o2 := GetFeature(topo, "abca2") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 0) + is.Equal(o2.Arcs[0].End, 3) + is.Nil(o2.Arcs[0].Next) +} + +// dedup reversed duplicate rings ABCA & ACBA have no cuts +func TestDedupDuplicateRingsReversedShare(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + NewTestFeature("acba", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{0, 1}, orb.Point{1, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 3) + is.Nil(o1.Arcs[0].Next) + + o2 := GetFeature(topo, "acba") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 3) + is.Equal(o2.Arcs[0].End, 0) + is.Nil(o2.Arcs[0].Next) +} + +// dedup coincident rings ABCA & BCAB have no cuts +func TestDedupCoincidentRings(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + NewTestFeature("bcab", orb.Polygon{ + orb.Ring{ + orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, orb.Point{1, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 3) + is.Nil(o1.Arcs[0].Next) + + o2 := GetFeature(topo, "bcab") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 0) + is.Equal(o2.Arcs[0].End, 3) + is.Nil(o2.Arcs[0].Next) +} + +// dedup coincident reversed rings ABCA & BACB have no cuts +func TestDedupCoincidentRings2(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + NewTestFeature("bacb", orb.Polygon{ + orb.Ring{ + orb.Point{1, 0}, orb.Point{0, 0}, orb.Point{0, 1}, orb.Point{1, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 3) + is.Nil(o1.Arcs[0].Next) + + o2 := GetFeature(topo, "bacb") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 3) + is.Equal(o2.Arcs[0].End, 0) + is.Nil(o2.Arcs[0].Next) +} + +// dedup coincident rings ABCDA, EFAE & GHCG are cut into ABC-CDA, EFAE and GHCG +func TestDedupCoincidentRings3(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcda", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{1, 1}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + NewTestFeature("efae", orb.Polygon{ + orb.Ring{ + orb.Point{0, -1}, orb.Point{1, -1}, orb.Point{0, 0}, orb.Point{0, -1}, + }, + }), + NewTestFeature("ghcg", orb.Polygon{ + orb.Ring{ + orb.Point{0, 2}, orb.Point{1, 2}, orb.Point{1, 1}, orb.Point{0, 2}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abcda") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 2) + is.Equal(o1.Arcs[0].Next.Start, 2) + is.Equal(o1.Arcs[0].Next.End, 4) + is.Nil(o1.Arcs[0].Next.Next) + + o2 := GetFeature(topo, "efae") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 5) + is.Equal(o2.Arcs[0].End, 8) + is.Nil(o2.Arcs[0].Next) + + o3 := GetFeature(topo, "ghcg") + is.Equal(o3.Type, geojson.TypePolygon) + is.Equal(len(o3.Arcs), 1) + is.Equal(o3.Arcs[0].Start, 9) + is.Equal(o3.Arcs[0].End, 12) + is.Nil(o3.Arcs[0].Next) +} + +// dedup coincident rings ABCA & DBED have no cuts, but are rotated to share B +func TestDedupNoCutsButRotated(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + NewTestFeature("dbed", orb.Polygon{ + orb.Ring{ + orb.Point{2, 1}, orb.Point{1, 0}, orb.Point{2, 2}, orb.Point{2, 1}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abca") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 3) + is.Nil(o1.Arcs[0].Next) + + o2 := GetFeature(topo, "dbed") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 4) + is.Equal(o2.Arcs[0].End, 7) + is.Nil(o2.Arcs[0].Next) + + is.True(reflect.DeepEqual(topo.coordinates[0:4], [][]float64{ + {1, 0}, {0, 1}, {0, 0}, {1, 0}, + })) + is.True(reflect.DeepEqual(topo.coordinates[4:8], [][]float64{ + {1, 0}, {2, 2}, {2, 1}, {1, 0}, + })) +} + +// dedup overlapping rings ABCDA and BEFCB are cut into BC-CDAB and BEFC-CB +func TestDedupOverlapping(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcda", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{1, 1}, orb.Point{0, 1}, orb.Point{0, 0}, // rotated to BCDAB, cut BC-CDAB + }, + }), + NewTestFeature("befcb", orb.Polygon{ + orb.Ring{ + orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{2, 1}, orb.Point{1, 1}, orb.Point{1, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + topo.cut() + topo.dedup() + + o1 := GetFeature(topo, "abcda") + is.Equal(o1.Type, geojson.TypePolygon) + is.Equal(len(o1.Arcs), 1) + is.Equal(o1.Arcs[0].Start, 0) + is.Equal(o1.Arcs[0].End, 1) + is.Equal(o1.Arcs[0].Next.Start, 1) + is.Equal(o1.Arcs[0].Next.End, 4) + is.Nil(o1.Arcs[0].Next.Next) + + o2 := GetFeature(topo, "befcb") + is.Equal(o2.Type, geojson.TypePolygon) + is.Equal(len(o2.Arcs), 1) + is.Equal(o2.Arcs[0].Start, 5) + is.Equal(o2.Arcs[0].End, 8) + is.Equal(o2.Arcs[0].Next.Start, 1) + is.Equal(o2.Arcs[0].Next.End, 0) + is.Nil(o2.Arcs[0].Next.Next) +} diff --git a/encoding/topojson/delta.go b/encoding/topojson/delta.go new file mode 100644 index 0000000..8e68adc --- /dev/null +++ b/encoding/topojson/delta.go @@ -0,0 +1,23 @@ +package topojson + +func (t *Topology) delta() { + if t.opts.PostQuantize == 0 { + return + } + + for i, arc := range t.Arcs { + x0 := arc[0][0] + y0 := arc[0][1] + for j, point := range arc { + x1 := point[0] + y1 := point[1] + if j == 0 { + t.Arcs[i][j] = []float64{x1, y1} + } else { + t.Arcs[i][j] = []float64{x1 - x0, y1 - y0} + } + x0 = x1 + y0 = y1 + } + } +} diff --git a/encoding/topojson/delta_test.go b/encoding/topojson/delta_test.go new file mode 100644 index 0000000..96f3045 --- /dev/null +++ b/encoding/topojson/delta_test.go @@ -0,0 +1,53 @@ +package topojson + +import ( + "testing" + + "github.com/cheekybits/is" +) + +// Converts arcs to delta encoding +func TestDeltaConverts(t *testing.T) { + is := is.New(t) + + topo := &Topology{ + Arcs: [][][]float64{ + { + {0, 0}, {9999, 0}, {0, 9999}, {0, 0}, + }, + }, + opts: &TopologyOptions{PostQuantize: 1e4}, + } + + expected := [][][]float64{ + { + {0, 0}, {9999, 0}, {-9999, 9999}, {0, -9999}, + }, + } + + topo.delta() + is.Equal(topo.Arcs, expected) +} + +// Does not skip coincident points +func TestDeltaDoesntSkip(t *testing.T) { + is := is.New(t) + + topo := &Topology{ + Arcs: [][][]float64{ + { + {0, 0}, {9999, 0}, {9999, 0}, {0, 9999}, {0, 0}, + }, + }, + opts: &TopologyOptions{PostQuantize: 1e4}, + } + + expected := [][][]float64{ + { + {0, 0}, {9999, 0}, {0, 0}, {-9999, 9999}, {0, -9999}, + }, + } + + topo.delta() + is.Equal(topo.Arcs, expected) +} diff --git a/encoding/topojson/doc.go b/encoding/topojson/doc.go new file mode 100644 index 0000000..1a064a3 --- /dev/null +++ b/encoding/topojson/doc.go @@ -0,0 +1,9 @@ +// Package topojson contains all the needed logic for converting GeoJSON +// from/to TopoJSON. +// +// Implements the TopoJSON specification: +// https://github.com/mbostock/topojson-specification +// +// Uses the GeoJSON implementation of paulmach: +// https://github.com/paulmach/orb/geojson +package topojson diff --git a/encoding/topojson/extract.go b/encoding/topojson/extract.go new file mode 100644 index 0000000..55d79c4 --- /dev/null +++ b/encoding/topojson/extract.go @@ -0,0 +1,118 @@ +package topojson + +import ( + "fmt" + + "github.com/paulmach/orb" + "github.com/paulmach/orb/geojson" +) + +func (t *Topology) extract() { + t.objects = make([]*topologyObject, 0, len(t.input)) + + for i, g := range t.input { + feature := t.extractFeature(g) + if len(feature.ID) == 0 { + // if multiple features exist without ids only one will be retained, so provide a synthetic id + feature.ID = fmt.Sprintf("feature_%d", i) + } + t.objects = append(t.objects, feature) + } + t.input = nil // no longer needed +} + +func (t *Topology) extractFeature(f *geojson.Feature) *topologyObject { + g := f.Geometry + o := t.extractGeometry(geojson.NewGeometry(g)) + + // TODO + // idProp := "id" + // if t.opts != nil && t.opts.IDProperty != "" { + // idProp = t.opts.IDProperty + // } + + if f.ID != nil { + o.ID = fmt.Sprint(f.ID) + } + o.Properties = f.Properties + o.BBox = f.BBox + return o +} + +func (t *Topology) extractGeometry(g *geojson.Geometry) *topologyObject { + o := &topologyObject{ + Type: g.Type, + } + + // TODO + // if g.Coordinates != nil { + // o.BBox = []float64{ + // g.Coordinates.Bound().Min[0], + // g.Coordinates.Bound().Min[1], + // g.Coordinates.Bound().Max[0], + // g.Coordinates.Bound().Max[1], + // } + // } + + switch g.Type { + default: + for _, geom := range g.Geometries { + o.Geometries = append(o.Geometries, t.extractGeometry(geom)) + } + case geojson.TypeLineString: + o.Arc = t.extractLine(g.Coordinates.(orb.LineString)) + case geojson.TypeMultiLineString: + o.Arcs = make([]*arc, len(g.Coordinates.(orb.MultiLineString))) + for i, l := range g.Coordinates.(orb.MultiLineString) { + o.Arcs[i] = t.extractLine(l) + } + case geojson.TypePolygon: + o.Arcs = make([]*arc, len(g.Coordinates.(orb.Polygon))) + for i, r := range g.Coordinates.(orb.Polygon) { + o.Arcs[i] = t.extractRing(r) + } + case geojson.TypeMultiPolygon: + o.MultiArcs = make([][]*arc, len(g.Coordinates.(orb.MultiPolygon))) + for i, p := range g.Coordinates.(orb.MultiPolygon) { + arcs := make([]*arc, len(p)) + for j, r := range p { + arcs[j] = t.extractRing(r) + } + o.MultiArcs[i] = arcs + } + case geojson.TypePoint: + o.Point = []float64{g.Coordinates.(orb.Point)[0], g.Coordinates.(orb.Point)[1]} + case geojson.TypeMultiPoint: + for _, v := range g.Coordinates.(orb.MultiPoint) { + o.MultiPoint = append(o.MultiPoint, []float64{v[0], v[1]}) + } + } + + return o +} + +func (t *Topology) extractLine(line orb.LineString) *arc { + n := len(line) + for i := 0; i < n; i++ { + t.coordinates = append(t.coordinates, []float64{line[i][0], line[i][1]}) + } + + index := len(t.coordinates) - 1 + arc := &arc{Start: index - n + 1, End: index} + t.lines = append(t.lines, arc) + + return arc +} + +func (t *Topology) extractRing(ring orb.Ring) *arc { + n := len(ring) + for i := 0; i < n; i++ { + t.coordinates = append(t.coordinates, []float64{ring[i][0], ring[i][1]}) + } + + index := len(t.coordinates) - 1 + arc := &arc{Start: index - n + 1, End: index} + t.rings = append(t.rings, arc) + + return arc +} diff --git a/encoding/topojson/extract_test.go b/encoding/topojson/extract_test.go new file mode 100644 index 0000000..d350fd0 --- /dev/null +++ b/encoding/topojson/extract_test.go @@ -0,0 +1,141 @@ +package topojson + +import ( + "reflect" + "testing" + + "github.com/cheekybits/is" + "github.com/paulmach/orb" + "github.com/paulmach/orb/geojson" +) + +// See https://github.com/mbostock/topojson/blob/master/test/topology/extract-test.js + +// extract copies coordinates sequentially into a buffer +func TestCopiesCoordinates(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("foo", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("bar", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + expected := [][]float64{ + {0, 0}, {1, 0}, {2, 0}, + {0, 0}, {1, 0}, {2, 0}, + } + + topo := &Topology{input: in} + topo.extract() + is.Equal(len(topo.coordinates), len(expected)) + for k, v := range topo.coordinates { + is.Equal(v, expected[k]) + } +} + +// extract includes closing coordinates in polygons +func TestClosingCoordinates(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("foo", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }), + } + + expected := [][]float64{ + {0, 0}, {1, 0}, {2, 0}, {0, 0}, + } + + topo := &Topology{input: in} + topo.extract() + is.Equal(len(topo.coordinates), len(expected)) + for k, v := range topo.coordinates { + is.Equal(v, expected[k]) + } +} + +// extract represents lines as contiguous slices of the coordinate buffer +func TestLineSlices(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("foo", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("bar", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + + foo := GetFeature(topo, "foo") + is.Equal(foo.Type, geojson.TypeLineString) + is.True(reflect.DeepEqual(foo.Arc, &arc{Start: 0, End: 2})) + + bar := GetFeature(topo, "bar") + is.Equal(bar.Type, geojson.TypeLineString) + is.True(reflect.DeepEqual(bar.Arc, &arc{Start: 3, End: 5})) +} + +// extract exposes the constructed lines and rings in the order of construction +func TestExtractRingsOrder(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("line", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("multiline", orb.MultiLineString{ + orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }, + }), + NewTestFeature("polygon", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + + is.True(reflect.DeepEqual(topo.lines, []*arc{ + {Start: 0, End: 2}, + {Start: 3, End: 5}, + })) + is.True(reflect.DeepEqual(topo.rings, []*arc{ + {Start: 6, End: 9}, + })) +} + +// extract supports nested geometry collections +func TestExtractNested(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("foo", orb.Collection{ + orb.LineString{ + orb.Point{0, 0}, orb.Point{0, 1}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + + foo := GetFeature(topo, "foo") + is.Equal(foo.Type, "GeometryCollection") + + geometries := foo.Geometries + is.Equal(len(geometries), 1) + is.Equal(geometries[0].Type, geojson.TypeLineString) + is.True(reflect.DeepEqual(geometries[0].Arc, &arc{Start: 0, End: 1})) +} diff --git a/encoding/topojson/filter.go b/encoding/topojson/filter.go new file mode 100644 index 0000000..2b20d02 --- /dev/null +++ b/encoding/topojson/filter.go @@ -0,0 +1,115 @@ +package topojson + +import ( + geojson "github.com/paulmach/orb/geojson" +) + +// Filter topology into a new topology that only contains features with the given IDs +func (t *Topology) Filter(ids []string) *Topology { + result := &Topology{ + Type: t.Type, + Transform: t.Transform, + BBox: t.BBox, + Objects: make(map[string]*Geometry), + } + + arcMap := make(map[int]int) + + for _, g := range t.Objects { + geom := remapGeometry(arcMap, ids, g) + if geom != nil { + result.Objects[geom.ID] = geom + } + } + + result.Arcs = make([][][]float64, len(arcMap)) + for k, v := range arcMap { + result.Arcs[v] = t.Arcs[k] + } + + return result +} + +func remapGeometry(arcMap map[int]int, ids []string, g *Geometry) *Geometry { + found := false + for _, id := range ids { + if g.ID == id { + found = true + break + } + } + if !found { + return nil + } + + geom := &Geometry{ + ID: g.ID, + Type: g.Type, + Properties: g.Properties, + BBox: g.BBox, + } + + switch g.Type { + case geojson.TypePoint: + geom.Point = g.Point + case geojson.TypeMultiPoint: + geom.MultiPoint = g.MultiPoint + case geojson.TypeLineString: + geom.LineString = remapLineString(arcMap, g.LineString) + case geojson.TypeMultiLineString: + geom.MultiLineString = remapMultiLineString(arcMap, g.MultiLineString) + case geojson.TypePolygon: + geom.Polygon = remapMultiLineString(arcMap, g.Polygon) + case geojson.TypeMultiPolygon: + polygons := make([][][]int, len(g.MultiPolygon)) + for i, poly := range g.MultiPolygon { + polygons[i] = remapMultiLineString(arcMap, poly) + } + geom.MultiPolygon = polygons + default: + geometries := make([]*Geometry, 0) + for _, geometry := range g.Geometries { + out := remapGeometry(arcMap, ids, geometry) + if out != nil { + geometries = append(geometries, out) + } + } + geom.Geometries = geometries + } + + return geom +} + +func remapLineString(arcMap map[int]int, in []int) []int { + out := make([]int, len(in)) + + for i, arc := range in { + a := arc + reverse := false + if a < 0 { + a = ^a + reverse = true + } + + idx, ok := arcMap[a] + if !ok { + idx = len(arcMap) + arcMap[a] = idx + } + if reverse { + out[i] = ^idx + } else { + out[i] = idx + } + } + + return out +} + +func remapMultiLineString(arcMap map[int]int, in [][]int) [][]int { + lines := make([][]int, len(in)) + for i, line := range in { + lines[i] = remapLineString(arcMap, line) + } + return lines +} diff --git a/encoding/topojson/filter_test.go b/encoding/topojson/filter_test.go new file mode 100644 index 0000000..0c04ee7 --- /dev/null +++ b/encoding/topojson/filter_test.go @@ -0,0 +1,50 @@ +package topojson + +import ( + "testing" + + "github.com/cheekybits/is" + "github.com/paulmach/orb" + geojson "github.com/paulmach/orb/geojson" +) + +func TestFilter(t *testing.T) { + is := is.New(t) + + fc := geojson.NewFeatureCollection() + fc.Append(NewTestFeature("one", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{1, 1}, orb.Point{0, 1}, orb.Point{0, 0}, + })) + fc.Append(NewTestFeature("two", orb.LineString{ + orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{2, 1}, orb.Point{1, 1}, orb.Point{1, 0}, + })) + fc.Append(NewTestFeature("three", orb.LineString{ + orb.Point{1, 1}, orb.Point{2, 1}, orb.Point{2, 2}, orb.Point{1, 2}, orb.Point{1, 1}, + })) + + topo := NewTopology(fc, nil) + is.NotNil(topo) + + al := len(topo.Arcs) + is.True(al > 0) + + topo2 := topo.Filter([]string{"one", "two"}) + is.NotNil(topo2) + + al2 := len(topo2.Arcs) + is.True(al > al2) // Arc has been eliminated + + expected := map[string][]orb.Point{ + "one": {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 0}}, + "two": {{1, 0}, {2, 0}, {2, 1}, {1, 1}, {1, 0}}, + } + + fc2 := topo2.ToGeoJSON() + is.NotNil(fc2) + + for _, feat := range fc2.Features { + exp, ok := expected[feat.ID.(string)] + is.True(ok) + is.Equal(feat.Geometry, exp) + } +} diff --git a/encoding/topojson/geojson.go b/encoding/topojson/geojson.go new file mode 100644 index 0000000..4f738d0 --- /dev/null +++ b/encoding/topojson/geojson.go @@ -0,0 +1,155 @@ +package topojson + +import ( + "github.com/paulmach/orb" + geojson "github.com/paulmach/orb/geojson" +) + +func (t *Topology) ToGeoJSON() *geojson.FeatureCollection { + fc := geojson.NewFeatureCollection() + + for _, obj := range t.Objects { + switch obj.Type { + case "GeometryCollection": + for _, geometry := range obj.Geometries { + feat := geojson.NewFeature(t.toGeometry(geometry)) + feat.ID = geometry.ID + feat.Properties = geometry.Properties + feat.BBox = geometry.BBox + fc.Append(feat) + } + default: + feat := geojson.NewFeature(t.toGeometry(obj)) + feat.ID = obj.ID + feat.Properties = obj.Properties + feat.BBox = obj.BBox + fc.Append(feat) + } + } + + return fc +} + +func (t *Topology) toGeometry(g *Geometry) orb.Geometry { + switch g.Type { + case geojson.TypePoint: + return t.packPoint(g.Point) + case geojson.TypeMultiPoint: + return t.packPoints(g.MultiPoint) + case geojson.TypeLineString: + return t.packLinestring(g.LineString) + case geojson.TypeMultiLineString: + return t.packMultiLinestring(g.MultiLineString) + case geojson.TypePolygon: + return t.packPolygon(g.Polygon) + case geojson.TypeMultiPolygon: + return t.packMultiPolygon(g.MultiPolygon) + default: + geometries := make([]orb.Geometry, len(g.Geometries)) + for i, geometry := range g.Geometries { + geometries[i] = t.toGeometry(geometry) + } + return orb.Collection(geometries) + } + return nil +} + +func (t *Topology) packPoint(in []float64) orb.Geometry { + if t.Transform == nil { + return orb.Point{in[0], in[1]} + } + + out := make([]float64, len(in)) + for i, v := range in { + out[i] = v + if i < 2 { + out[i] = v*t.Transform.Scale[i] + t.Transform.Translate[i] + } + } + + return orb.Point{out[0], out[1]} +} + +func (t *Topology) packPoints(in [][]float64) orb.Geometry { + out := make(orb.Collection, len(in)) + for i, p := range in { + out[i] = t.packPoint(p) + } + return out +} + +func (t *Topology) packLinestring(ls []int) orb.Geometry { + result := orb.LineString{} + for _, a := range ls { + reverse := false + if a < 0 { + a = ^a + reverse = true + } + arc := t.Arcs[a] + + // Copy arc + newArc := make([][]float64, len(arc)) + for i, point := range arc { + newArc[i] = append([]float64{}, point...) + } + + if t.Transform != nil { + x := float64(0) + y := float64(0) + + for k, p := range newArc { + x += p[0] + y += p[1] + + newArc[k][0] = x*t.Transform.Scale[0] + t.Transform.Translate[0] + newArc[k][1] = y*t.Transform.Scale[1] + t.Transform.Translate[1] + } + } + + if reverse { + for j := len(newArc) - 1; j >= 0; j-- { + if len(result) > 0 && pointEquals([]float64{result[len(result)-1][0], result[len(result)-1][1]}, newArc[j]) { + continue + } + result = append(result, orb.Point{newArc[j][0], newArc[j][1]}) + } + } else { + for j := 0; j < len(newArc); j++ { + if len(result) > 0 && pointEquals([]float64{result[len(result)-1][0], result[len(result)-1][1]}, newArc[j]) { + continue + } + result = append(result, orb.Point{newArc[j][0], newArc[j][1]}) + } + } + } + return result +} + +func (t *Topology) packMultiLinestring(ls [][]int) orb.Geometry { + result := make(orb.MultiLineString, len(ls)) + for i, l := range ls { + result[i] = t.packLinestring(l).(orb.LineString) + } + return result +} + +func (t *Topology) packPolygon(ls [][]int) orb.Geometry { + result := make(orb.Polygon, len(ls)) + for i, l := range ls { + s := t.packLinestring(l).(orb.LineString) + result[i] = make(orb.Ring, len(s)) + for j, l := range s { + result[i][j] = l + } + } + return result +} + +func (t *Topology) packMultiPolygon(ls [][][]int) orb.Geometry { + result := make(orb.MultiPolygon, len(ls)) + for i, l := range ls { + result[i] = t.packPolygon(l).(orb.Polygon) + } + return result +} diff --git a/encoding/topojson/geojson_test.go b/encoding/topojson/geojson_test.go new file mode 100644 index 0000000..2f5c6c7 --- /dev/null +++ b/encoding/topojson/geojson_test.go @@ -0,0 +1,279 @@ +package topojson + +import ( + "testing" + + "github.com/cheekybits/is" + orb "github.com/paulmach/orb" + geojson "github.com/paulmach/orb/geojson" +) + +func TestGeoJSON(t *testing.T) { + is := is.New(t) + + poly := geojson.NewFeature(orb.Polygon{ + orb.Ring{ + {0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}, + }, + }) + poly.ID = "poly" + + fc := geojson.NewFeatureCollection() + fc.Append(poly) + + topo := NewTopology(fc, nil) + is.NotNil(topo) + is.Equal(len(topo.Objects), 1) + is.Equal(len(topo.Arcs), 1) + + fc2 := topo.ToGeoJSON() + is.NotNil(fc2) + is.Equal(fc, fc2) +} + +func TestGeoJSONMultiArc(t *testing.T) { + is := is.New(t) + + fc := geojson.NewFeatureCollection() + fc.Append(NewTestFeature("one", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{1, 1}, orb.Point{0, 1}, orb.Point{0, 0}, + })) + fc.Append(NewTestFeature("two", orb.LineString{ + orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{2, 1}, orb.Point{1, 1}, orb.Point{1, 0}, + })) + + topo := NewTopology(fc, nil) + is.NotNil(topo) + is.NotNil(topo) + is.Equal(len(topo.Objects), 2) + + fc2 := topo.ToGeoJSON() + is.NotNil(fc2) + + expected := map[string][]orb.Point{ + "one": {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 0}}, + "two": {{1, 0}, {2, 0}, {2, 1}, {1, 1}, {1, 0}}, + } + for _, feat := range fc2.Features { + exp, ok := expected[feat.ID.(string)] + is.True(ok) + is.Equal(feat.Geometry, exp) + } +} + +func TestGeoJSONWithGeometryCollections(t *testing.T) { + is := is.New(t) + + source, _ := UnmarshalTopology([]byte(sourceTopojson)) + + expected, _ := geojson.UnmarshalFeatureCollection(([]byte(targetGeojson))) + + result := source.ToGeoJSON() + + is.NotNil(result) + + is.Equal(result.Type, expected.Type) + is.Equal(len(result.Features), len(expected.Features)) + for i := 0; i < len(result.Features); i++ { + is.Equal(result.Features[i].Type, expected.Features[i].Type) + is.Equal(result.Features[i].Properties, expected.Features[i].Properties) + // can't accurately compare floats for equality, so just check the array lengths + switch v := result.Features[i].Geometry.(type) { + case orb.Polygon: + is.Equal(len(v), len(expected.Features[i].Geometry.(orb.Polygon))) + case orb.MultiPolygon: + is.Equal(len(v), len(expected.Features[i].Geometry.(orb.MultiPolygon))) + } + } +} + +var sourceTopojson = `{ + "type":"Topology", + "arcs":[[[10361,14391],[-79,-49],[-23,-39],[-14,-131],[-46,29],[-135,-28],[-27,29],[-66,17],[-63,-27],[-107,39],[-60,-25],[-86,13],[-103,28],[-110,-90],[-106,-58],[-54,25],[-3,37],[-67,17],[0,-35],[-100,56],[11,-46],[-76,-20],[-108,32],[-51,38],[1,48],[-85,2],[-10,36],[-44,28],[-48,-3],[-71,27],[-22,-45],[-63,14],[-18,-21],[-30,-127],[-55,33],[-26,40],[-40,-14],[-6,-53],[-61,-60],[-91,-42],[-63,19],[-155,67],[-51,-40],[-156,-31]],[[7794,14081],[21,42],[-6,99],[-19,88],[-47,1],[-22,59],[-68,41],[-47,54],[-62,43],[-39,110],[69,58],[-9,41],[-64,22],[-45,46],[-4,38],[81,229],[-9,64],[-62,45],[-46,63],[-37,22],[-17,52],[-94,-60],[-81,-67],[-63,-13],[-72,54],[-27,69],[17,24],[-60,94],[54,39],[19,70],[35,22],[-37,45],[-2,54],[-61,40],[48,42],[1,82],[66,54],[105,35],[-35,55],[15,25],[-12,75],[-97,-27],[-74,71],[-26,68],[-95,30],[-49,95],[-12,69]],[[6825,16343],[43,101],[60,12],[8,60],[-26,41],[65,78],[67,42],[56,70],[48,16],[54,84],[83,23],[54,-1],[29,-33],[111,55],[10,53],[-18,51],[58,22],[98,81],[55,-14],[69,52],[19,59],[-34,75],[-49,65],[-7,110],[-99,102],[-4,72],[-76,81],[7,49],[48,12],[73,-7],[61,57],[16,40],[71,84],[6,47],[48,13],[28,55],[36,-6],[17,27],[5,13],[-2,27],[27,7],[68,-16],[76,45],[101,-119],[143,-120],[52,-121],[45,-52],[49,-13],[29,38],[39,-45],[-18,-53],[20,-33],[48,44],[94,-38],[89,-110],[30,-51],[-17,-42],[38,-58],[11,-81],[36,-14],[10,-130],[21,-57],[-10,-75],[-52,-70],[37,-120],[73,-73],[-38,-93],[-2,-50],[31,-87],[46,-57],[41,-134],[-32,-72],[10,-62],[36,-45],[10,-79],[78,-101],[8,-57],[60,-114],[17,-58],[85,-74],[16,-44],[-27,-71],[25,-78],[-14,-35],[31,-134],[33,-49],[27,-91],[23,-124],[110,-145],[73,-52],[7,-78],[26,-62],[38,-41],[-18,-44],[42,-17],[61,5],[125,-42],[50,-46],[121,-50],[64,5],[50,-44],[86,-2],[110,-41]],[[7952,18119],[-1,94],[96,56],[65,-78],[-27,-36],[-76,-50],[-57,14]],[[5776,13129],[-23,-29],[25,-49],[-34,-53],[-14,103],[46,28]],[[7794,14081],[-36,-29],[-146,-5],[-75,-83],[3,-67],[27,-45],[-47,-25],[-79,-81],[93,-80],[-25,-41],[-2,-96],[16,-30],[-13,-77],[-48,-24],[-91,5],[-12,-43],[-107,4],[-36,-76],[-91,-101],[-14,-40],[-39,-21],[-11,-84],[34,-17],[39,-70],[81,-29],[28,-42],[-7,-79],[87,-17],[91,26],[22,-26],[18,-142],[48,-3],[79,-62],[101,24],[8,-63],[56,26],[19,-90],[41,-32],[59,-11],[46,-58],[8,-53],[53,-88],[54,-38],[-2,-64],[-71,-34],[-80,-62],[-3,-136],[-35,-63],[-47,-34],[23,-79],[50,-37],[30,-45],[63,6],[42,30],[46,-63],[-7,-67],[34,-77],[50,-33],[27,-49],[66,-56],[68,-102]],[[8280,11233],[-48,-101],[-70,-10],[-28,-66],[5,-79],[-76,-80],[4,-29],[56,-35],[-43,-97],[23,-99],[-18,-18],[9,-186],[18,-108],[44,-54],[-21,-44]],[[8135,10227],[-27,-63],[-83,-1],[-43,-64],[-73,-12],[-63,43],[2,-79],[-66,-34],[-87,-129],[-43,-38],[-154,-47],[-37,-64],[-61,-10],[-5,-82],[25,-36],[-19,-51],[-107,-41],[4,-35],[-73,-34],[-89,24],[-23,-69],[-49,50],[-31,-26],[-36,23],[-19,78],[-47,26],[-90,-33],[-34,29],[-51,-38]],[[6756,9514],[-59,9],[-16,27],[-64,-12],[-63,24],[-17,59],[-32,16],[10,45],[-37,137],[-44,39],[-8,54],[-77,33],[-35,32],[-39,80],[106,40],[14,33],[0,3],[-55,87],[-161,126],[-71,5],[-1,1],[-15,5],[-10,7],[-7,6],[-1,1],[-2,2],[0,0],[-3,4],[0,0],[-12,16],[29,46],[-42,27],[-26,99],[-142,164],[-3,36],[55,47],[131,60],[63,48],[47,14],[46,-93],[19,-91],[72,-109],[34,-74],[39,-47],[94,-54],[69,23],[37,28],[61,-32],[68,52],[-20,43],[-59,-16],[-45,33],[-67,-17],[-90,54],[-124,96],[-29,33],[-34,80],[-13,87],[-58,78],[2,27],[-39,76],[-5,65],[-70,64],[7,105],[60,99],[64,127],[25,20],[77,110],[50,33],[18,74],[-75,32],[-66,13],[-44,27],[-30,61],[-7,51],[5,150],[18,172],[-8,95],[230,99],[43,-32],[49,23],[41,59],[-70,74],[-24,52],[-10,83],[47,96],[112,66],[3,47],[45,42],[-36,25],[-33,62],[-38,33],[-22,74],[52,86],[-122,-38],[-66,-116],[-47,-30],[-69,32],[-12,71],[-43,53],[-34,8],[-16,-52],[2,-62],[-32,-25],[-18,-55],[-82,-105],[-76,-64],[-41,70],[-62,-31],[-32,70],[13,121],[-37,31],[75,35],[9,97],[29,51],[-6,39],[-43,43],[-19,-71],[-39,-28],[26,-51],[-23,-30],[-52,9],[-35,-25],[-43,7],[-185,291],[8,90],[-25,88],[-57,70],[-59,113],[-131,156],[-56,95],[-50,27],[-36,82],[48,24],[61,145],[-4,58],[23,39],[-10,134],[53,84],[40,37],[29,77],[37,53],[48,33],[54,69],[8,63],[-13,58],[53,124],[19,86],[62,74],[33,21],[70,-31],[59,46],[-63,49],[53,70],[70,9],[68,31],[47,-14],[90,-70],[12,57],[51,79],[53,-11],[41,78],[8,51],[36,47],[-25,94],[158,-17],[34,61],[89,49],[76,73],[73,86],[204,114],[26,49]],[[11875,11193],[-65,-32],[-55,15],[-39,-12],[-14,-83],[-61,-60],[48,-58],[-93,-47],[-23,15],[-28,84],[-42,2],[-38,56],[-3,78],[-30,64],[61,96],[-66,14],[-23,54],[-103,78],[-85,-161],[-38,-8],[-118,24],[-18,33],[-71,-79],[68,-8],[42,-44],[6,-45],[-88,-28],[-44,1],[-24,-74],[31,-32],[-149,-47],[-145,-4],[-8,84],[17,85],[-95,-1],[-117,23]],[[10465,11176],[0,91],[-13,67],[28,33],[2,73],[44,5],[12,11],[-3,24],[-20,70],[31,38],[18,59],[-11,76],[149,55],[29,-38],[99,-73],[73,7],[62,54],[89,-3],[174,44],[75,5],[69,-111],[89,-120],[96,-71],[77,-79],[57,-36],[36,13],[55,-51],[93,-126]],[[10354,10976],[-138,34],[-53,27],[-66,85],[-33,-56],[-61,-40],[-23,-101],[-109,-42],[-56,1],[-54,-86],[-25,-13],[-3,-56],[-27,-108],[-108,-104],[-51,4],[-44,-21],[-74,38],[-10,58],[-90,-43],[-79,-1],[-2,54],[-97,4],[-23,-47],[-103,-18],[-87,-33],[-10,32],[-69,-14],[-24,99],[-34,40],[-63,33],[20,56],[-53,52],[-43,10],[-78,66],[3,110],[-43,9],[-62,50],[10,69],[-58,63],[-35,4],[-46,45],[-73,-3]],[[10361,14391],[81,-51],[11,-42],[56,16],[74,-85],[39,-26],[121,-29],[61,-72],[67,-58],[-22,-53],[18,-40],[59,-26],[51,-58],[76,-155],[10,-89],[38,-103],[55,-72],[102,-77],[50,-18],[2,-62],[24,-66],[101,-74],[85,-20],[82,-50],[85,-40],[8,-31],[-63,-28],[-52,3],[-59,-26],[-49,-56],[-27,-75],[0,-141],[56,-182],[49,-141],[65,-123],[177,-258],[105,-136],[32,-53],[132,-178],[46,-132],[-116,67],[-84,25],[-157,-53],[-101,10],[-50,43],[-100,116],[-80,68],[-19,56],[-67,41],[-41,8],[-78,-16],[-86,-39],[-120,-34],[-49,11],[-148,-24],[-68,57],[-51,10],[-100,-28],[-60,-57],[24,-95],[-47,-96],[0,-45],[22,-32],[-1,-22],[-47,-6],[-33,-110],[15,-144],[-9,-46],[-18,-32],[-18,1],[-6,-2],[-6,-8],[-1,-11],[-13,-18],[9,-24],[-24,-12],[-25,-67]],[[12344,8996],[-44,-97],[50,-36],[-73,-39],[-87,-77],[-27,8]],[[12163,8755],[44,86],[22,69],[18,102],[23,9],[74,-25]],[[11875,11193],[116,-44],[18,-53],[101,-38],[20,26],[55,-96],[6,-61],[33,-30],[64,-157],[65,-144],[29,-84],[56,-105],[33,-97],[35,-168],[-17,-184],[5,-39],[-25,-76],[-31,7],[-88,-75],[-61,-67],[-77,-42],[-115,-114],[-97,-141],[-48,-104],[63,-69],[44,-1],[112,-61],[9,-39],[63,-120],[-17,-109],[-19,-62],[-45,-90]],[[12162,8756],[-72,3],[-82,-48],[-46,11],[-37,-40],[7,-103],[-50,-50],[-98,-11],[-53,41],[-57,-25],[-22,17],[-73,-64],[-121,52],[-103,-52],[-53,59],[-58,14],[-67,-47],[-91,-36],[-88,19],[-77,-44],[43,-51],[-37,-102],[22,-53],[101,27],[10,-72],[34,-63],[-23,-40],[108,-61],[-7,-50],[32,-64],[-40,-58],[-29,-70],[-71,-50],[-54,-88],[-72,-5],[-28,-51],[60,-83],[-16,-63],[-57,6],[-63,-88],[17,-66],[-21,-44],[-43,-8],[-68,78],[-96,-32],[30,-127],[-45,-59],[-12,-41]],[[10596,7074],[-63,-5],[-61,-62],[-99,-59],[-37,8],[-39,-75],[-76,-6],[-33,-51],[75,-70],[21,-37],[-6,-48],[-35,0],[-25,-63],[-66,-70],[-45,68],[-28,104],[-42,-18],[-109,14],[-50,-49],[-129,-46],[-26,-77],[31,-11],[-28,-59],[-55,-12],[-63,-50],[-82,28],[-36,-17],[-49,27],[17,76],[-36,89],[-29,26],[14,64],[-29,49],[63,15],[64,35],[-44,41],[-103,159]],[[9358,6992],[35,68],[108,23],[-11,56],[-29,22],[41,76],[36,5],[36,46],[-24,105],[37,53],[-97,39],[6,29],[70,39],[52,6],[38,30],[-128,244],[-125,168],[-283,178],[-118,58],[-28,59],[-43,20],[12,57],[-31,60],[45,26],[-11,81],[-117,100],[-73,-3],[-4,68],[-70,16],[-6,52],[59,128],[72,-17],[70,83],[-29,25],[4,82],[-74,47],[-113,32],[-37,-19],[-53,43],[-111,20],[-85,133],[52,78],[10,92],[113,65],[5,122],[-35,12],[-29,141],[12,64],[-24,19],[-38,81],[16,32],[-36,65],[-82,70],[-62,11],[-49,38],[-97,7]],[[10354,10976],[24,20],[5,48],[18,2],[3,15],[-7,18],[13,18],[4,17],[9,2],[12,-3],[6,3],[17,30],[7,30]],[[9358,6992],[-17,-73],[-78,-54],[47,-81],[-52,-22],[-73,74],[-54,-16],[12,-47],[-62,-13],[-40,-68],[8,-33],[-25,-122],[-34,-90],[-38,-36],[-79,-32],[-38,-47],[-33,45],[-67,55]],[[8735,6432],[0,30],[44,40],[29,86],[-56,-18],[-71,26],[9,39],[-76,63],[5,46],[-50,34],[-24,41],[-65,-49],[-55,-73],[-27,19],[-42,-17],[22,-67],[42,-28],[16,-43],[-17,-62],[-30,-3],[-42,50],[-74,62],[-33,-44],[-38,23],[-61,-6],[-126,-84],[-112,19],[-73,-25],[21,65],[40,45],[-82,26],[-34,-28],[-7,-86],[38,-51],[-40,-21],[-66,15],[-57,-89],[-115,31],[-31,93],[-43,21],[-75,-2],[-19,-52],[-61,6],[3,44],[-74,36],[-33,-54],[38,-36],[-48,-58],[12,-65],[48,-20],[-3,-64],[33,-28],[15,-51],[-100,-69],[-27,16],[-26,-63],[-73,-2],[-80,-30],[-58,-42],[-28,-46]],[[6898,5932],[-29,29],[-51,-5],[-42,21],[-43,-13],[-74,91],[17,45],[-61,32],[-35,46],[-101,85],[-99,-59],[-75,-29],[-15,71],[-42,-13],[-50,105],[-58,56],[-79,147],[24,57],[-8,30],[-66,85],[6,77],[30,48],[-66,28],[12,31],[78,20],[-1,34],[-52,25],[48,53],[-6,67],[30,25],[25,59],[49,31],[-3,43],[124,62],[-61,13],[48,143],[46,38],[13,41],[-37,17],[-124,-32],[-96,57],[-34,45],[-82,33],[-9,32],[-133,100],[-5,64],[72,78],[26,-8],[75,55],[50,21],[108,5],[42,12],[2,62],[51,17],[14,107],[-32,36],[-66,-46],[-115,-60],[-46,139],[35,1],[124,188],[16,123],[37,61],[4,47],[66,3],[27,37],[-73,48],[-6,44],[-109,18],[-38,51],[-74,2],[-50,19],[-18,80],[31,72],[-19,17],[63,59],[-46,36],[9,35],[57,30],[39,94],[36,-15],[85,19],[40,76],[52,10],[87,-54],[69,26],[108,-26],[3,-30],[48,-53],[23,-60],[85,90],[50,2],[3,179]],[[12873,5056],[11,3],[146,-69],[-22,-17],[-96,-26],[-37,8],[-36,62],[14,11],[3,4],[-1,5],[3,12],[1,18],[14,-11]],[[13630,5293],[-16,-36],[-77,-85],[-77,-65],[-50,6],[-14,44],[11,57],[52,3],[14,59],[157,17]],[[13660,5866],[57,-20],[-29,-34],[-81,-39],[-78,-3],[-5,37],[47,49],[71,22],[18,-12]],[[14903,8290],[16,-26],[-22,-14],[-5,-5],[-4,-8],[0,-30],[-42,-31],[-28,27],[12,23],[11,4],[3,6],[-3,11],[24,-2],[38,45]],[[15042,8363],[30,-97],[5,-95],[54,-211],[-27,-26],[-50,15],[-74,-2],[2,57],[-21,27],[-28,-10],[-17,39],[3,1],[-8,7],[-10,3],[-15,11],[-5,21],[-16,21],[-26,25],[51,58],[-1,29],[6,11],[14,1],[12,6],[0,55],[121,54]],[[12235,4892],[99,86],[3,44],[124,27],[-35,77],[-42,-5],[-34,73],[-14,64],[-76,72],[-103,-32],[-61,7],[-91,-59],[-45,7],[-77,68],[2,38],[-66,16],[3,107],[-103,10],[-74,24],[-108,-29],[-51,-57],[-106,-27],[-12,-34],[-85,-26],[-31,14],[-92,-59],[-63,-25],[-72,21],[-35,-24],[-58,45],[-23,-84]],[[10909,5231],[-33,2],[-23,54],[-13,78],[60,93],[-11,21],[-66,2],[-2,70],[-29,27],[24,58],[-42,17],[-25,50],[-107,6],[-61,53],[-12,80],[-50,73],[-18,-14],[-48,67],[51,60],[47,0],[4,-49],[43,-79],[61,9],[69,-14],[29,-22],[63,89],[-54,93],[-60,30],[-20,31],[-65,7],[-35,42],[-53,24],[38,88],[49,71],[-12,121],[31,44],[4,73],[-49,35],[81,111],[37,23],[24,57],[-26,73],[-51,12],[-1,94],[14,41],[-76,42]],[[12162,8756],[1,-1]],[[12344,8996],[110,8],[56,-18],[165,140],[-2,50],[50,107],[33,120],[97,88],[67,-16],[156,23],[164,-13],[43,16],[72,-18],[57,13],[17,-58],[35,-5],[94,27],[49,-21],[45,14],[89,-21],[10,39],[189,-56],[236,-29],[93,-28],[79,-39],[168,-120],[63,-65],[223,-185],[100,-99],[83,-105],[27,-48],[51,-156],[35,-132],[-35,-104],[-19,75],[-40,3],[-67,-43],[-73,-94],[-13,5],[-11,-3],[3,-12],[-14,-9],[-8,-9],[-4,-15],[-30,-11],[90,-86],[8,-24],[15,-11],[13,-6],[2,-7],[-4,-13],[23,-25],[46,-16],[-1,-65],[79,7],[40,-35],[-19,-49],[-19,-174],[-61,-137],[-31,-110],[-51,-47],[-33,-93],[-15,-254],[-33,-90],[-41,-209],[-28,26],[36,67],[20,74],[-79,66],[-34,-27],[98,-61],[-45,-113],[-55,-38],[-48,-56],[-90,-66],[-51,-123],[-121,-136],[-47,-24],[-41,-67],[-18,53],[-57,42],[1,74],[-47,31],[-130,47],[43,-57],[104,-27],[22,-85],[-18,-19],[-56,21],[-36,-26],[-61,9],[-43,37],[-60,-31],[-62,-10],[39,-44],[95,24],[33,-23],[83,0],[52,23],[39,-40],[-47,-67],[-44,-30],[-17,-73],[22,-57],[102,5],[-100,-135],[-59,-49],[-171,-96],[-129,-10],[-21,27],[1,69],[-74,49],[-92,-19],[-92,-69],[-1,-67],[-62,-63],[-44,-24],[-86,20],[-105,-47],[-33,-41],[86,-30],[53,11],[52,34],[26,-19],[78,33],[44,59],[59,7],[33,-40],[-15,-98],[18,-59],[-27,-102],[3,-44],[-31,-29],[-114,-17],[10,-74],[-82,4],[-28,-39],[44,-82],[35,-26],[-58,-37],[-55,-53],[-53,18],[-215,41],[-70,-19],[-115,39],[-3,-11],[1,-8],[-3,-22],[-8,-2],[-9,-9],[-20,6],[-13,-14],[12,-38],[-26,-34],[-91,5],[-35,-44],[-12,-96],[-57,-24],[-117,-4],[-29,59],[-30,9],[-52,-40],[-64,35],[-68,54]],[[12235,4892],[-14,-100]],[[12221,4792],[-56,-46],[-41,-70],[-6,-85],[10,-46],[-30,-105],[-97,-87],[-6,-75],[-43,-8],[-48,56],[-34,-48],[-23,115],[-75,30],[-49,-62],[-39,-9],[-18,-51],[-55,-39],[-58,45],[-15,90],[-64,41],[-37,0],[-10,42],[-72,70],[-50,-55],[-58,-105],[-21,63],[41,84],[-94,113],[-112,63],[-70,18],[-4,56],[-66,20],[-22,30],[41,110],[-22,94],[35,61],[-44,129]],[[9421,2647],[47,3],[61,-68],[55,-28],[92,-5],[97,-37],[23,-95],[44,-29],[-46,-50],[-50,-4],[-64,-41],[-28,-79],[-9,-75],[-118,-56],[-123,-17],[-19,37],[-132,116],[-108,52],[-71,72],[-72,4],[-47,-14],[-2,43],[52,92],[97,8],[83,53],[52,6],[94,45],[62,74],[30,-7]],[[10120,2850],[21,-10],[2,-74],[-59,37],[36,47]],[[10035,2860],[30,-17],[-13,-116],[-21,-29],[-94,15],[57,48],[16,89],[25,10]],[[9875,2859],[25,-29],[9,-119],[-98,-25],[-39,43],[30,53],[-2,57],[21,9],[13,21],[41,-10]],[[13231,4759],[88,-25],[45,-35],[161,-35],[67,-83],[24,-50],[-79,-56],[-36,-1],[-39,31],[-89,12],[-80,-4],[-17,41],[-32,40],[-27,118],[14,47]],[[12221,4792],[32,67],[101,-80],[75,38],[26,-47],[112,-23],[95,13],[33,20],[9,66],[37,37],[198,-10],[51,-28],[57,12],[107,-24],[32,-35],[6,-51],[-44,-31],[-41,-1],[-29,46],[-63,-21],[-33,-73],[-122,-17],[14,-36],[45,-30],[82,-5],[68,11],[16,-40],[89,16],[23,46],[68,-48],[4,-66],[136,-28],[69,1],[37,-41],[79,19],[42,-6],[91,15],[47,48],[248,28],[62,16],[97,-9],[72,18],[71,-2],[105,33],[107,-17],[14,-30],[-13,-85],[-52,-70],[-70,7],[1,-131],[51,-178],[2,-133],[-9,-67],[-35,-64],[-63,-35],[-67,-58],[-84,-40],[-77,-6],[-38,-21],[-31,-48],[-177,-30],[-62,-25],[-101,-86],[-42,-57],[-38,-140],[31,-166],[-81,-7],[-260,62],[-31,3],[-117,-80],[-92,-105],[-145,-61],[-128,-17],[-79,-33],[-127,-23],[-117,-63],[-39,-78],[-35,-17],[-72,-83],[-25,-46],[-99,6],[-95,57],[-31,-7],[-55,26],[-58,48],[-77,5],[-238,95],[-135,35],[-121,17],[-91,-5],[-94,-25],[-77,-31],[-136,-20],[-186,-2],[-95,-30],[-101,-16],[-87,-24],[-105,-48],[-26,-67],[-55,-58],[-72,77],[-57,42],[-73,34],[-6,29],[51,79],[-34,76],[-66,26],[-74,5],[-35,-14],[-41,21],[-24,-40],[-101,18],[-15,-20],[-49,16],[-73,16],[19,-75],[34,-34],[14,-56],[-49,-44],[-79,44],[-54,66],[-155,82],[-134,148],[-132,76],[72,-112],[56,-55],[54,-30],[12,-56],[44,-44],[-56,-81],[-86,0],[-51,-58],[-76,-16],[-129,-41],[-11,-37],[-84,-63],[-132,52],[-85,12]],[[8685,2561],[20,45],[-86,-3],[-39,89],[-62,-45],[-55,135],[15,66],[23,22],[-31,85],[-40,-28],[-40,10],[4,97],[60,43],[3,59],[-48,28],[-62,-46],[-66,87],[-22,50],[-54,65],[41,47],[77,6],[24,-47],[39,64],[32,14],[52,-54],[127,-48],[38,1],[56,-69],[52,-29],[92,80],[-31,87],[58,27],[-68,106],[10,121],[-16,34],[21,78],[-69,39],[17,80],[-34,70],[6,38],[-47,41],[8,33],[68,18],[37,-9],[51,106],[53,12],[65,-32],[16,129],[17,62],[-15,28],[11,69],[56,-16],[20,50],[-16,57],[-112,86],[18,84],[41,58],[-109,163],[-11,60],[-28,15],[-20,56],[-77,124],[-40,-22],[-26,112],[44,33],[13,58],[-37,31],[-36,76],[4,36],[91,9],[-71,81],[-1,57],[-26,130],[-35,39],[48,34],[25,40],[-12,85],[15,46],[-9,60],[29,23],[19,65],[41,15],[-14,67],[44,43],[-17,48],[-63,34],[-6,73]],[[91,85],[44,-46],[-22,-39],[-47,10],[-1,44],[26,31]],[[0,179],[51,-44],[-34,-20],[-17,64]],[[8685,2561],[-60,-10],[-83,-54],[-64,12],[-101,-6],[-57,-16],[-82,-53],[-49,60],[-26,-16],[-83,2],[-43,25],[-43,-8],[-29,-72],[24,-20],[45,60],[43,-77],[62,-35],[80,1],[-19,-44],[36,-78],[-37,-26],[6,-55],[-18,-43],[-125,-6],[-58,-37],[-11,50],[-77,11],[-55,49],[-90,9],[-30,23],[-76,-21],[-63,19],[-273,44],[-61,-69],[4,-40],[-42,-52],[-48,37],[-36,66],[-26,-1],[133,-144],[50,-10],[33,-57],[-50,-89],[-31,-19],[21,132],[-93,112],[-88,81],[-153,125],[-211,133],[-100,48],[-31,-1],[-157,46],[-62,-11],[-48,-46],[-57,-30],[-83,-18],[-96,11],[-30,-53],[-57,5],[-124,-8],[-71,-15],[-67,-30],[-68,-124],[-85,-30],[-68,-41],[-57,28],[0,43],[-31,51],[-24,74],[-43,34],[42,-174],[6,-90],[-49,-67],[-51,-100],[-31,-109],[-3,-77],[50,-65],[-52,-21],[-33,28],[-44,-63],[2,-105],[127,-33],[-53,-84],[2,-63],[-39,-43],[-54,-2],[-12,-29],[-76,-25],[-53,-82],[-34,-154],[17,-64],[-59,2],[-66,-52],[-62,42],[-38,9],[-33,-38],[-65,23],[-69,53],[-1,62],[-92,124],[-66,10],[-91,29],[-49,-38],[-45,-4],[-76,76],[-50,-6],[-31,83],[15,57],[-69,-1],[-52,19],[-12,74],[-25,26],[37,108],[-48,-32],[10,-50],[-11,-47],[18,-17],[16,-104],[42,-30],[-92,-55],[-46,50],[-71,45],[-47,14],[-111,-3],[-37,13],[-91,-11],[-30,-50],[-84,-23],[-71,-39],[-78,21],[-77,-24],[-40,14],[-79,-19],[1,50],[-45,7],[-87,-26],[-27,-35],[24,-46],[-50,-50],[-13,-148],[-27,-72],[-32,40],[-78,10],[-92,-92],[-88,-9],[-31,-69],[4,-48],[-68,-78],[-37,61],[16,54],[-21,74],[-44,-8],[-4,-155],[-68,-67],[15,-52],[-36,-46],[56,-40],[28,-83],[-19,-62],[-50,-25],[-9,-69],[-103,10],[-55,-87],[1,-51],[-56,-8],[-6,33],[-50,14],[1,31],[-43,63],[22,55],[-63,131],[-71,71],[-55,1],[-63,44],[-53,-14],[-80,75],[-62,17],[-52,-8],[-44,-56],[27,-60],[-40,-89],[-40,-24],[-81,-3],[-65,-41],[-57,0],[-20,53],[-36,25],[11,45],[30,16],[-23,159],[62,92],[80,12],[26,43],[44,23],[66,60],[108,35],[115,-82],[66,73],[18,37],[104,34],[108,106],[51,24],[31,71],[-3,42],[65,7],[36,62],[48,22],[17,85],[-4,79],[65,25],[48,38],[52,10],[33,62],[5,92],[21,110],[-3,33],[28,74],[64,-1],[16,43],[45,-7],[74,90],[51,-4],[21,38],[84,-2],[54,11],[48,74],[6,106],[17,47],[48,4],[98,101],[46,30],[2,76],[67,41],[104,88],[16,157],[-10,135],[-14,33],[2,75],[36,55],[-6,33],[30,81],[12,166],[117,5],[72,-21],[94,-73],[56,-4],[77,26],[76,113],[46,24],[31,72],[79,-45],[-3,50],[-63,27],[-13,29],[-1,103],[-31,48],[50,83],[-5,92],[25,34],[44,-10],[79,36],[70,8],[33,20],[55,-33],[42,27],[151,17],[16,22],[65,3],[22,-19],[70,33],[90,-5],[39,42],[29,-27],[241,-45],[73,-8],[43,33],[84,-2],[92,-35],[21,-30],[58,-9],[56,-70],[141,-2],[113,-11],[35,25],[143,37],[45,26],[57,8],[92,-29],[97,36],[46,74],[-41,87],[18,130],[36,42],[18,80],[38,111],[42,49],[50,-7],[33,34],[30,76],[57,75],[92,92],[49,29],[74,-1],[62,71],[52,88],[16,85],[40,43],[27,57],[54,38],[37,53],[39,98],[114,83],[29,100],[56,42],[36,-8],[93,67],[-3,52],[-78,-34],[-84,-61],[-51,-63],[-13,-42],[-171,-99],[-53,-56],[3,-20],[-58,-100],[-28,78],[22,92],[-26,16],[6,79],[-19,21],[-2,33],[-11,12],[1,10],[19,8],[8,8],[3,18],[12,35],[-33,46],[17,81],[39,96]]], + "transform": { + "scale": [0.0005360715937289712, 0.00032300856994129236 ], + "translate": [-6.348383337142886, 49.91002832975399 ] + }, + "objects": { + "Eregion": { + "type": "GeometryCollection", + "geometries": [ + { + "arcs": [[[0, 1, 2 ] ], [[3 ] ] ], + "type": "MultiPolygon", + "properties": { + "AREACD": "E12000001", + "AREANM": "North East" + } + }, + { + "arcs": [[[4 ] ], [[5, 6, 7, 8, -2 ] ] ], + "type": "MultiPolygon", + "properties": { + "AREACD": "E12000002", + "AREANM": "North West" + } + }, + { + "arcs": [[[9, 10 ] ], [[11, -6, -1, 12 ] ] ], + "type": "MultiPolygon", + "properties": { + "AREACD": "E12000003", + "AREANM": "Yorkshire and The Humber" + } + }, + {"arcs": [[[13, 14 ] ], [[15, 16, 17, 18, -7, -12, 19, -10 ] ] ], + "type": "MultiPolygon", + "properties": { + "AREACD": "E12000004", + "AREANM": "East Midlands" + } + }, + { + "arcs": [[20, 21, 22, -8, -19 ] ], + "type": "Polygon", + "properties": { + "AREACD": "E12000005", + "AREANM": "West Midlands" + } + }, + {"arcs": [[[23 ] ], [[24 ] ], [[25 ] ], [[26 ] ], [[27 ] ], [[28, 29, -17, 30, -14, 31 ] ] ], + "type": "MultiPolygon", + "properties": { + "AREACD": "E12000006", + "AREANM": "East of England" + } + }, + { + "arcs": [[32, 33, -29 ] ], + "type": "Polygon", + "properties": { + "AREACD": "E12000007", + "AREANM": "London" + } + }, + { + "arcs": [[[34 ] ], [[35 ] ], [[36 ] ], [[37 ] ], [[38 ] ], [[-30, -34, 39, 40, -21, -18 ] ] ], + "type": "MultiPolygon", + "properties": { + "AREACD": "E12000008", + "AREANM": "South East" + } + }, + { + "arcs": [[[41 ] ], [[42 ] ], [[-41, 43, -22 ] ] ], + "type": "MultiPolygon", + "properties": { + "AREACD": "E12000009", + "AREANM": "South West" + } + } + ] + } + } +}` + +var targetGeojson = `{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "AREACD": "E12000001", + "AREANM": "North East" + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [[[[-0.7941455545170157, 54.55844465977913 ], [-0.8364952104216039, 54.54261723985201 ], [-0.8488248570773704, 54.5300199056243 ], [-0.8563298593895761, 54.48770578296199 ], [-0.8809891527011091, 54.49707303149029 ], [-0.9533588178545198, 54.48802879153193 ], [-0.9678327508852025, 54.497396040060224 ], [-1.0032134760713145, 54.50288718574923 ], [-1.0369859864762399, 54.49416595436081 ], [-1.0943456470052393, 54.50676328858852 ], [-1.126509942628978, 54.49868807433999 ], [-1.172612099689669, 54.50288718574923 ], [-1.2278274738437531, 54.51193142570759 ], [-1.2867953491539401, 54.48286065441287 ], [-1.343618938089211, 54.464126157356276 ], [-1.3725668041505754, 54.472201371604804 ], [-1.374175018931762, 54.48415268869263 ], [-1.4100918157116036, 54.48964383438164 ], [-1.4100918157116036, 54.47833853443369 ], [-1.4636989750845002, 54.4964270143504 ], [-1.4578021875534821, 54.4815686201331 ], [-1.4985436286768836, 54.475108448734275 ], [-1.5564393607996125, 54.485444722972396 ], [-1.5837790120797903, 54.49771904863017 ], [-1.5832429404860608, 54.51322345998735 ], [-1.6288090259530232, 54.51386947712723 ], [-1.6341697418903136, 54.525497785645115 ], [-1.6577568920143877, 54.53454202560347 ], [-1.6834883285133788, 54.53357299989365 ], [-1.7215494116681356, 54.542294231282064 ], [-1.7333429867301735, 54.52775884563471 ], [-1.767115497135098, 54.532280965613886 ], [-1.7767647858222197, 54.525497785645115 ], [-1.792846933634089, 54.484475697262575 ], [-1.8223308712891821, 54.49513498007064 ], [-1.8362687327261353, 54.50805532286829 ], [-1.8577115964752942, 54.50353320288911 ], [-1.8609280260376684, 54.486413748682224 ], [-1.8936283932551357, 54.46703323448575 ], [-1.9424109082844723, 54.45346687454821 ], [-1.9761834186893967, 54.4596040373771 ], [-2.0592745157173873, 54.48124561156316 ], [-2.086614166997565, 54.46832526876551 ], [-2.1702413356192842, 54.45831200309733 ], [-2.158983832150976, 54.47187836303486 ], [-2.16220026171335, 54.50385621145905 ], [-2.1723856219942004, 54.532280965613886 ], [-2.197580986899462, 54.53260397418383 ], [-2.209374561961499, 54.55166147981036 ], [-2.245827430335069, 54.564904831177955 ], [-2.271022795240331, 54.58234729395478 ], [-2.3042592340515275, 54.59623666246226 ], [-2.325166026206957, 54.6317676051558 ], [-2.288177086239658, 54.6505021022124 ], [-2.293001730583219, 54.66374545357999 ], [-2.327310312581873, 54.6708516421187 ], [-2.351433534299677, 54.685710036336 ], [-2.3535778206745928, 54.69798436199377 ], [-2.3101560215825465, 54.77195332451032 ], [-2.3149806659261065, 54.79262587298657 ], [-2.3482171047373033, 54.807161258633926 ], [-2.372876398048836, 54.827510798540224 ], [-2.3927110470168076, 54.83461698707893 ], [-2.4018242641102003, 54.85141343271588 ], [-2.4522149939207236, 54.8320329185194 ], [-2.4956367930127703, 54.81039134433334 ], [-2.529409303417695, 54.806192232924104 ], [-2.5680064581661814, 54.82363469570093 ], [-2.5824803911968632, 54.84592228702688 ], [-2.573367174103471, 54.85367449270547 ], [-2.605531469727209, 54.88403729827995 ], [-2.5765836036658447, 54.89663463250766 ], [-2.5663982433849943, 54.919245232403554 ], [-2.5476357376044803, 54.92635142094226 ], [-2.5674703865724524, 54.94088680658962 ], [-2.56854252975991, 54.95832926936645 ], [-2.6012428969773773, 54.9712496121641 ], [-2.5755114604783866, 54.984815972101636 ], [-2.574975388884658, 55.01130267483682 ], [-2.5395946636985456, 55.028745137613654 ], [-2.483307146357004, 55.0400504375616 ], [-2.502069652137518, 55.05781590890837 ], [-2.494028578231583, 55.0658911231569 ], [-2.5004614373563308, 55.09011676590249 ], [-2.552460381948041, 55.08139553451408 ], [-2.592129679883985, 55.104329142979914 ], [-2.606067541320938, 55.12629372573592 ], [-2.6569943427251905, 55.13598398283416 ], [-2.6832618508179102, 55.16666979697858 ], [-2.689694709942658, 55.18895738830453 ], [-2.666643631412312, 55.22158125386861 ], [-2.6344793357885736, 55.2254573567079 ], [-2.6301907630387418, 55.24483787090438 ], [-2.644128624475695, 55.25808122227197 ], [-2.609283970883312, 55.28327589072739 ], [-2.573367174103471, 55.29684225066492 ], [-2.5433471648546484, 55.319452850560815 ], [-2.5176157283556577, 55.32462098767988 ], [-2.4886678622942937, 55.35175370755494 ], [-2.444173920014789, 55.35918290466359 ], [-2.4152260539534245, 55.358859896093655 ], [-2.399679977735284, 55.34820061328559 ], [-2.340176030831368, 55.36596608463236 ], [-2.3348153148940787, 55.38308553883925 ], [-2.3444646035812005, 55.39955897590625 ], [-2.31337245114492, 55.406665164444966 ], [-2.2608374349594804, 55.43282885861021 ], [-2.2313534973043874, 55.42830673863103 ], [-2.1943645573370887, 55.44510318426798 ], [-2.1841791970562374, 55.46416068989451 ], [-2.202405631243023, 55.48838633264011 ], [-2.2286731393357426, 55.509381889686296 ], [-2.2324256404918454, 55.544912832379836 ], [-2.2854967282710135, 55.57785970651385 ], [-2.2876410146459296, 55.60111632354962 ], [-2.328382455769331, 55.62728001771487 ], [-2.3246299546132283, 55.64310743764199 ], [-2.298898518114238, 55.64698354048129 ], [-2.2597652917720223, 55.644722480491694 ], [-2.227064924554556, 55.66313396897835 ], [-2.2184877790548923, 55.676054311776 ], [-2.1804266959001346, 55.70318703165107 ], [-2.1772102663377613, 55.71836843443831 ], [-2.151478829838771, 55.72256754584755 ], [-2.1364688252143598, 55.74033301719432 ], [-2.117170247840116, 55.738394965774674 ], [-2.108057030746724, 55.74711619716309 ], [-2.105376672778079, 55.75131530857232 ], [-2.1064488159655372, 55.76003653996074 ], [-2.0919748829348546, 55.76229759995033 ], [-2.0555220145612845, 55.757129462831266 ], [-2.014780573437883, 55.77166484847862 ], [-1.9606373424712569, 55.73322682865561 ], [-1.8839791045680139, 55.694465800262655 ], [-1.8561033816941075, 55.65538176329976 ], [-1.831980159976304, 55.63858531766281 ], [-1.8057126518835842, 55.63438620625357 ], [-1.7901665756654443, 55.646660531911344 ], [-1.7692597835100141, 55.63212514626399 ], [-1.778909072197136, 55.615005692057096 ], [-1.768187640322556, 55.60434640924903 ], [-1.7424562038235658, 55.61855878632645 ], [-1.6920654740130425, 55.60628446066868 ], [-1.644355102171164, 55.57075351797514 ], [-1.6282729543592946, 55.554280080908136 ], [-1.637386171452687, 55.5407137209706 ], [-1.6170154508909862, 55.521979223914 ], [-1.6111186633599672, 55.49581552974876 ], [-1.5918200859857246, 55.49129340976958 ], [-1.586459370048435, 55.449302295677214 ], [-1.5752018665801266, 55.43089080719056 ], [-1.580562582517416, 55.406665164444966 ], [-1.6084383053913225, 55.384054564549075 ], [-1.5886036564233512, 55.34529353615612 ], [-1.5494704300811355, 55.3217139105504 ], [-1.5698411506428371, 55.29167411354587 ], [-1.5709132938302952, 55.275523685048796 ], [-1.5542950744246964, 55.247421939463905 ], [-1.5296357811131642, 55.229010450977256 ], [-1.507656845770276, 55.185727302605116 ], [-1.5248111367696033, 55.162470685569346 ], [-1.5194504208323139, 55.14244415423299 ], [-1.5001518434580703, 55.12790876858563 ], [-1.4947911275207808, 55.102391091560264 ], [-1.4529775432099212, 55.069767225996195 ], [-1.448688970460089, 55.05135573750954 ], [-1.4165246748363511, 55.014532760536234 ], [-1.4074114577429588, 54.99579826347964 ], [-1.3618453722759964, 54.97189562930399 ], [-1.3532682267763327, 54.957683252226566 ], [-1.3677421598070145, 54.93474964376074 ], [-1.3543403699637908, 54.90955497530531 ], [-1.3618453722759964, 54.89824967535737 ], [-1.3452271528703976, 54.85496652698524 ], [-1.3275367902773416, 54.83913910705812 ], [-1.3130628572466598, 54.809745327193454 ], [-1.3007332105908933, 54.769692264520735 ], [-1.2417653352807063, 54.722856021879245 ], [-1.2026321089384915, 54.7060595762423 ], [-1.1988796077823887, 54.680864907786884 ], [-1.1849417463454355, 54.66083837645052 ], [-1.1645710257837347, 54.64759502508293 ], [-1.1742203144708565, 54.63338264800551 ], [-1.1517053075342396, 54.62789150231651 ], [-1.1190049403167723, 54.629506545166215 ], [-1.0519959911006502, 54.61594018522868 ], [-1.025192411414202, 54.60108179101138 ], [-0.9603277485729969, 54.58493136251432 ], [-0.926019166574342, 54.586546405364025 ], [-0.8992155868878937, 54.572334028286605 ], [-0.8531134298272027, 54.57168801114672 ], [-0.7941455545170157, 54.55844465977913 ] ] ],[[[-2.085542023810107, 55.762620608520265 ], [-2.0860780954038365, 55.79298341409475 ], [-2.034615222405855, 55.81107189401146 ], [-1.9997705688134717, 55.78587722555604 ], [-2.0142445018441544, 55.77424891703816 ], [-2.054985942967556, 55.75809848854109 ], [-2.085542023810107, 55.762620608520265 ] ] ] ] + } + }, + { + "type": "Feature", + "properties": { + "AREACD": "E12000002", + "AREANM": "North West" + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [[[[-3.2520338117643486, 54.15080784451322 ], [-3.2643634584201147, 54.14144059598492 ], [-3.2509616685768905, 54.1256131760578 ], [-3.2691881027636756, 54.10849372185091 ], [-3.276693105075881, 54.14176360455486 ], [-3.2520338117643486, 54.15080784451322 ] ] ], [[[-2.1702413356192842, 54.45831200309733 ], [-2.189539912993528, 54.448944754569034 ], [-2.2678063656779575, 54.44732971171933 ], [-2.3080117352076304, 54.4205200004142 ], [-2.3064035204264437, 54.39887842622813 ], [-2.291929587395761, 54.384343040580774 ], [-2.3171249523010227, 54.37626782633224 ], [-2.3594746082056113, 54.350104132167 ], [-2.309619949988817, 54.32426344657169 ], [-2.3230217398320416, 54.3110200952041 ], [-2.3240938830194997, 54.28001127248974 ], [-2.315516737519836, 54.270321015391495 ], [-2.322485668238312, 54.24544935550602 ], [-2.3482171047373033, 54.237697149827426 ], [-2.3969996197666394, 54.23931219267713 ], [-2.403432478891387, 54.225422824169655 ], [-2.460792139420387, 54.22671485844943 ], [-2.48009071679463, 54.202166207133885 ], [-2.528873231823966, 54.169542341569816 ], [-2.536378234136172, 54.15662199877216 ], [-2.5572850262916016, 54.149838818803396 ], [-2.5631818138226206, 54.122706098928326 ], [-2.5449553796358355, 54.117214953239326 ], [-2.5240485874804057, 54.094604353343435 ], [-2.480626788388359, 54.085237104815135 ], [-2.4656167837639478, 54.0716707448776 ], [-2.4693692849200506, 54.04615306785224 ], [-2.42273105626563, 54.04066192216324 ], [-2.3739485412362935, 54.04906014498171 ], [-2.3621549661742565, 54.04066192216324 ], [-2.3525056774871347, 53.99479470523158 ], [-2.3267742409881444, 53.99382567952175 ], [-2.2844245850835554, 53.97379914818539 ], [-2.2302813541169293, 53.981551353863985 ], [-2.225992781367098, 53.96120181395768 ], [-2.1959727721182754, 53.96960003677616 ], [-2.185787411837425, 53.94052926548144 ], [-2.1638084764945367, 53.93019299124332 ], [-2.1321802524645275, 53.92663989697396 ], [-2.1075209591529953, 53.90790539991737 ], [-2.103232386403163, 53.890785945710476 ], [-2.074820591935528, 53.86236119155564 ], [-2.0458727258741636, 53.85008686589788 ], [-2.0469448690616217, 53.82941431742163 ], [-2.0850059522163784, 53.81843202604363 ], [-2.127891679714696, 53.79840549470727 ], [-2.1294998944958827, 53.754476329195256 ], [-2.148262400276397, 53.73412678928895 ], [-2.1734577651816585, 53.72314449791095 ], [-2.161128118525892, 53.69762682088559 ], [-2.1343245388394436, 53.68567550379776 ], [-2.1182423910275743, 53.6711401181504 ], [-2.084469880622649, 53.67307816957005 ], [-2.061954873686032, 53.68276842666829 ], [-2.0372955803745, 53.662418886761984 ], [-2.0410480815306027, 53.64077731257592 ], [-2.022821647343817, 53.61590565269044 ], [-1.9960180676573689, 53.60524636988238 ], [-1.981544134626687, 53.58941894995525 ], [-1.9461634094405742, 53.57133047003854 ], [-1.909710541067005, 53.53838359590453 ], [-1.9354419775659952, 53.50575973034046 ], [-1.9729669891270234, 53.502529644641044 ], [-1.9879769937514347, 53.481211079024916 ], [-1.98529663578279, 53.45569340199956 ], [-2.0260380769061914, 53.429852716404255 ], [-2.0238937905312753, 53.420485467875956 ], [-1.9938737812824527, 53.409180167928014 ], [-2.016924859812799, 53.37784833664371 ], [-2.0045952131570326, 53.34587048821952 ], [-2.0142445018441544, 53.340056333960575 ], [-2.0094198575005935, 53.27997673995149 ], [-1.9997705688134717, 53.24509181439784 ], [-1.9761834186893967, 53.227649351621004 ], [-1.9874409221577052, 53.21343697454359 ], [-2.001914855188388, 53.193087434637285 ], [-2.046408797467892, 53.19276442606735 ], [-2.0694598759982377, 53.1720918775911 ], [-2.1085931023404525, 53.16821577475181 ], [-2.142365612745378, 53.182105143259285 ], [-2.1412934695579198, 53.15658746623392 ], [-2.1766741947440327, 53.145605174855916 ], [-2.223312423398453, 53.10393706933349 ], [-2.2463635019287986, 53.09166274367572 ], [-2.3289185273630597, 53.07648134088848 ], [-2.348753176331032, 53.055808792412236 ], [-2.381453543548499, 53.05257870671282 ], [-2.3841339015171443, 53.02609200397764 ], [-2.3707321116739197, 53.01446369545975 ], [-2.38091747195477, 52.99799025839275 ], [-2.43827713248377, 52.984746907025155 ], [-2.4361328461088543, 52.973441607077206 ], [-2.475266072451069, 52.962459315699206 ], [-2.5229764442929477, 52.9702115213778 ], [-2.535306090948714, 52.94792393005184 ], [-2.5615735990414334, 52.96407435854891 ], [-2.578191818447032, 52.955676135730435 ], [-2.5974903958212745, 52.963105332839085 ], [-2.6076757561021253, 52.988300001294505 ], [-2.632871121007387, 52.99669822411298 ], [-2.681117564442994, 52.98603894130492 ], [-2.699343998629779, 52.99540618983322 ], [-2.7266836499099565, 52.98313186417545 ], [-2.758311873939966, 52.98603894130492 ], [-2.7668890194396294, 52.99476017269333 ], [-2.801197601438284, 52.99088406985404 ], [-2.8349701118432087, 52.99863627553263 ], [-2.8440833289366014, 53.01769378115917 ], [-2.8612376199359284, 53.022861918278224 ], [-2.855876903998639, 53.03739730392559 ], [-2.8757115529666106, 53.08164947800754 ], [-2.8992987030906856, 53.094246812235255 ], [-2.903587275840517, 53.11168927501208 ], [-2.944864788557648, 53.122348557820146 ], [-2.963627294338162, 53.13268483205827 ], [-2.984534086493592, 53.15852551765357 ], [-2.927710497558321, 53.17144586045122 ], [-2.9202054952461154, 53.182105143259285 ], [-2.9202054952461154, 53.18307416896911 ], [-2.949689432901209, 53.211175914554 ], [-3.035996959491573, 53.2518749943666 ], [-3.07405804264633, 53.25349003721631 ], [-3.074594114240059, 53.25381304578625 ], [-3.0826351881459937, 53.25542808863596 ], [-3.087995904083283, 53.257689148625545 ], [-3.091748405239386, 53.259627200045195 ], [-3.092284476833115, 53.25995020861514 ], [-3.093356620020573, 53.260596225755016 ], [-3.093356620020573, 53.260596225755016 ], [-3.0949648348017598, 53.26188826003478 ], [-3.0949648348017598, 53.26188826003478 ], [-3.1013976939265078, 53.267056397153844 ], [-3.0858516177083675, 53.28191479137114 ], [-3.1083666246449844, 53.29063602275956 ], [-3.1223044860819376, 53.32261387118375 ], [-3.1984266523914515, 53.375587276654116 ], [-3.200034867172638, 53.387215585172 ], [-3.1705509295175447, 53.40239698795924 ], [-3.1003255507390497, 53.42177750215572 ], [-3.0665530403341243, 53.437281913512905 ], [-3.0413576754288627, 53.44180403349208 ], [-3.01669838211733, 53.41176423648754 ], [-3.0065130218364797, 53.38237045662289 ], [-2.967915867087994, 53.34716252249928 ], [-2.949689432901209, 53.323259888323626 ], [-2.9287826407457787, 53.308078485536385 ], [-2.8783919109352554, 53.29063602275956 ], [-2.8414029709679567, 53.29806521986821 ], [-2.8215683219999845, 53.30710945982656 ], [-2.7888679547825173, 53.29677318558844 ], [-2.752415086408947, 53.31356963122539 ], [-2.7631365182835266, 53.32745899973286 ], [-2.794764742313536, 53.322290862613805 ], [-2.81888796403134, 53.33295014542187 ], [-2.854804760811181, 53.32745899973286 ], [-2.9030512042467884, 53.344901462509696 ], [-2.9695240818691806, 53.37591028522406 ], [-2.985070158087321, 53.38656956803212 ], [-3.003296592274106, 53.41241025362743 ], [-3.0102655229925825, 53.44051199921232 ], [-3.0413576754288627, 53.46570666766774 ], [-3.040285532241405, 53.47442789905615 ], [-3.061192324396835, 53.498976550371694 ], [-3.0638726823654796, 53.51997210741788 ], [-3.1013976939265078, 53.54064465589412 ], [-3.097645192770405, 53.57456055573795 ], [-3.0654808971466667, 53.606538404162144 ], [-3.0311723151480123, 53.647560492544685 ], [-3.017770525304788, 53.65402066394351 ], [-2.976493012587657, 53.68955160663705 ], [-2.949689432901209, 53.70021088944512 ], [-2.940040144214087, 53.72411352362077 ], [-2.98024551374376, 53.73444979785889 ], [-3.015626238929872, 53.73864890926813 ], [-3.039213389053947, 53.74737014065654 ], [-3.055295536865816, 53.76707366342296 ], [-3.0590480380219187, 53.78354710048997 ], [-3.056367680053274, 53.831998385981166 ], [-3.0467183913661526, 53.88755586001106 ], [-3.0510069641159845, 53.91824167415549 ], [-2.927710497558321, 53.95021952257967 ], [-2.904659419027975, 53.93988324834156 ], [-2.8783919109352554, 53.94731244545021 ], [-2.856412975592368, 53.96636995107674 ], [-2.8939379871533957, 53.9902725852524 ], [-2.9068037054028912, 54.00706903088934 ], [-2.9121644213401807, 54.033878742194474 ], [-2.886969056434919, 54.06488756490884 ], [-2.8269290379372745, 54.08620613052496 ], [-2.8253208231560873, 54.1013875333122 ], [-2.801197601438284, 54.114953893249734 ], [-2.8204961788125265, 54.12302910749827 ], [-2.838186541405583, 54.143055638834625 ], [-2.8585572619672837, 54.15371492164269 ], [-2.870350837029321, 54.177617555818344 ], [-2.8424751141554143, 54.2053962928333 ], [-2.907875848590349, 54.19312196717553 ], [-2.943256573776461, 54.15565297306234 ], [-2.9684519386817225, 54.1459627159641 ], [-3.0054408786490217, 54.156298990202224 ], [-3.011873737773769, 54.17923259866805 ], [-3.034924816304115, 54.19635205287494 ], [-3.0531512504909, 54.19893612143447 ], [-3.061728395990564, 54.18213967579752 ], [-3.060656252803106, 54.162113144461166 ], [-3.077810543802433, 54.15403793021263 ], [-3.087459832489554, 54.13627245886586 ], [-3.13141770317533, 54.10235655902203 ], [-3.172159144298732, 54.081684010545786 ], [-3.1941380796416197, 54.10429461044167 ], [-3.227374518452816, 54.09428134477349 ], [-3.244528809452143, 54.11689194466938 ], [-3.2375598787336664, 54.15597598163228 ], [-3.257394527701638, 54.16598924730046 ], [-3.217189158171965, 54.17729454724841 ], [-3.2123645138284047, 54.20862637853271 ], [-3.1968184376102644, 54.22509981559972 ], [-3.200034867172638, 54.237697149827426 ], [-3.223085945702984, 54.2515865183349 ], [-3.2332713059838345, 54.22865290986907 ], [-3.2541780981392643, 54.21960866991071 ], [-3.240240236702311, 54.20313523284371 ], [-3.2525698833580776, 54.19344497574547 ], [-3.280445606231984, 54.19635205287494 ], [-3.299208112012498, 54.18827683862641 ], [-3.3222591905428436, 54.190537898616 ], [-3.4214324353827035, 54.284533392468916 ], [-3.4171438626328716, 54.31360416376363 ], [-3.4305456524760958, 54.34202891791846 ], [-3.4611017333186473, 54.364639517814354 ], [-3.4927299573486565, 54.40113948621772 ], [-3.562955336127152, 54.45152882312856 ], [-3.592975345375974, 54.48221463727299 ], [-3.619778925062423, 54.4909358686614 ], [-3.6390775024366655, 54.51742257139659 ], [-3.6133460659376753, 54.52517477707518 ], [-3.580645698720208, 54.57201101971666 ], [-3.5827899850951237, 54.59074551677326 ], [-3.570460338439357, 54.60334285100097 ], [-3.575821054376647, 54.6466259993731 ], [-3.5474092599090117, 54.67375871924817 ], [-3.525966396159853, 54.685710036336 ], [-3.5104203199417126, 54.71058169622148 ], [-3.490585670973741, 54.727701150428366 ], [-3.46485423447475, 54.73836043323643 ], [-3.4359063684133857, 54.76064802456238 ], [-3.431617795663554, 54.78099756446868 ], [-3.4385867263820304, 54.799732061525276 ], [-3.410174931914395, 54.839785124197995 ], [-3.3999895716335446, 54.86756386121294 ], [-3.3667531328223483, 54.8914664953886 ], [-3.3490627702292923, 54.89824967535737 ], [-3.311537758668264, 54.88823640968919 ], [-3.279909534638255, 54.90309480390649 ], [-3.3136820450431803, 54.91892222383361 ], [-3.285270250575545, 54.9415328237295 ], [-3.2477452390145167, 54.944439900858974 ], [-3.2112923706409466, 54.95445316652715 ], [-3.186097005735685, 54.949931046547974 ], [-3.1378505623000774, 54.92732044665209 ], [-3.13141770317533, 54.94573193513874 ], [-3.1040780518951525, 54.9712496121641 ], [-3.075666257427517, 54.96769651789475 ], [-3.0536873220846292, 54.99289118635017 ], [-3.0493987493347974, 55.00936462341718 ], [-3.0301001719605543, 55.02454602620441 ], [-3.043501961803779, 55.054908831778896 ], [-2.958802649994601, 55.049417686089896 ], [-2.940576215807816, 55.069121208856316 ], [-2.8928658439659376, 55.08494862878344 ], [-2.852124402842536, 55.10852825438915 ], [-2.8129911765003213, 55.136306991404105 ], [-2.703632571379611, 55.17312996837741 ], [-2.689694709942658, 55.18895738830453 ], [-2.6832618508179102, 55.16666979697858 ], [-2.6569943427251905, 55.13598398283416 ], [-2.606067541320938, 55.12629372573592 ], [-2.592129679883985, 55.104329142979914 ], [-2.552460381948041, 55.08139553451408 ], [-2.5004614373563308, 55.09011676590249 ], [-2.494028578231583, 55.0658911231569 ], [-2.502069652137518, 55.05781590890837 ], [-2.483307146357004, 55.0400504375616 ], [-2.5395946636985456, 55.028745137613654 ], [-2.574975388884658, 55.01130267483682 ], [-2.5755114604783866, 54.984815972101636 ], [-2.6012428969773773, 54.9712496121641 ], [-2.56854252975991, 54.95832926936645 ], [-2.5674703865724524, 54.94088680658962 ], [-2.5476357376044803, 54.92635142094226 ], [-2.5663982433849943, 54.919245232403554 ], [-2.5765836036658447, 54.89663463250766 ], [-2.605531469727209, 54.88403729827995 ], [-2.573367174103471, 54.85367449270547 ], [-2.5824803911968632, 54.84592228702688 ], [-2.5680064581661814, 54.82363469570093 ], [-2.529409303417695, 54.806192232924104 ], [-2.4956367930127703, 54.81039134433334 ], [-2.4522149939207236, 54.8320329185194 ], [-2.4018242641102003, 54.85141343271588 ], [-2.3927110470168076, 54.83461698707893 ], [-2.372876398048836, 54.827510798540224 ], [-2.3482171047373033, 54.807161258633926 ], [-2.3149806659261065, 54.79262587298657 ], [-2.3101560215825465, 54.77195332451032 ], [-2.3535778206745928, 54.69798436199377 ], [-2.351433534299677, 54.685710036336 ], [-2.327310312581873, 54.6708516421187 ], [-2.293001730583219, 54.66374545357999 ], [-2.288177086239658, 54.6505021022124 ], [-2.325166026206957, 54.6317676051558 ], [-2.3042592340515275, 54.59623666246226 ], [-2.271022795240331, 54.58234729395478 ], [-2.245827430335069, 54.564904831177955 ], [-2.209374561961499, 54.55166147981036 ], [-2.197580986899462, 54.53260397418383 ], [-2.1723856219942004, 54.532280965613886 ], [-2.16220026171335, 54.50385621145905 ], [-2.158983832150976, 54.47187836303486 ], [-2.1702413356192842, 54.45831200309733 ] ] ] ] + } + }, + { + "type": "Feature", + "properties": { + "AREACD": "E12000003", + "AREANM": "Yorkshire and The Humber" + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [[[[0.017466838388647155, 53.52546325310688 ], [-0.01737781520373627, 53.51512697886876 ], [-0.04686175285882932, 53.51997210741788 ], [-0.06776854501425955, 53.51609600457858 ], [-0.07527354732646518, 53.48928629327345 ], [-0.10797391454393246, 53.469905779076974 ], [-0.08224247804494134, 53.45117128202038 ], [-0.13209713626173603, 53.43598987923314 ], [-0.14442678291750255, 53.44083500778226 ], [-0.15943678754191382, 53.467967727657324 ], [-0.18195179447853072, 53.46861374479721 ], [-0.20232251504023147, 53.48670222471392 ], [-0.20393072982141813, 53.51189689316934 ], [-0.22001287763328747, 53.532569441645585 ], [-0.1873125104158202, 53.56357826435995 ], [-0.22269323560193222, 53.568100384339125 ], [-0.23502288225769874, 53.58554284711596 ], [-0.2902382564117829, 53.61073751557138 ], [-0.3358043418787453, 53.55873313581083 ], [-0.35617506244044606, 53.5561490672513 ], [-0.41943151050046446, 53.56390127292989 ], [-0.42908079918758624, 53.57456055573795 ], [-0.467141882342343, 53.54904287871259 ], [-0.4306890139687729, 53.54645881015306 ], [-0.408174007032156, 53.53224643307564 ], [-0.40495757746978267, 53.517711047428286 ], [-0.45213187771793173, 53.50866680746993 ], [-0.4757190278420067, 53.50898981603987 ], [-0.4885847460915018, 53.485087181864216 ], [-0.4719665266859039, 53.474750907626095 ], [-0.5518411941515202, 53.45956950483885 ], [-0.6295715752422213, 53.45827747055909 ], [-0.6338601479920527, 53.48541019043416 ], [-0.6247469308986604, 53.512865918879164 ], [-0.6756737323029132, 53.51254291030923 ], [-0.7383941087692021, 53.51997210741788 ], [-0.7383941087692021, 53.54936588728253 ], [-0.7453630394876791, 53.5710074614686 ], [-0.7303530348632679, 53.58166674427666 ], [-0.7292808916758098, 53.60524636988238 ], [-0.7056937415517357, 53.60686141273208 ], [-0.6992608824269873, 53.61041450700144 ], [-0.7008690972081748, 53.61816671268003 ], [-0.7115905290827538, 53.64077731257592 ], [-0.6949723096771558, 53.65305163823369 ], [-0.6853230209900341, 53.672109143860226 ], [-0.691219808521053, 53.69665779517576 ], [-0.6113451410554367, 53.71442326652253 ], [-0.5957990648372959, 53.70214894086477 ], [-0.5427279770581279, 53.67856931525905 ], [-0.5035947507159131, 53.68083037524864 ], [-0.4703583119047172, 53.69827283802547 ], [-0.4226479400628387, 53.697303812315646 ], [-0.32937148275399775, 53.71151618939306 ], [-0.28916611322432484, 53.713131232242766 ], [-0.25217717325702527, 53.67727728097928 ], [-0.20446680141514761, 53.63851625258633 ], [-0.15300392841716626, 53.6155826441205 ], [-0.11172641570003528, 53.59006496709514 ], [-0.08117033485748415, 53.578436658577246 ], [-0.061871757483240586, 53.58263576998649 ], [-0.032387819828147535, 53.56616233291948 ], [0.017466838388647155, 53.52546325310688 ] ] ], [[[-0.7978980556731186, 53.45537039342962 ], [-0.8718759356077159, 53.46635268480762 ], [-0.9002877300753518, 53.47507391619604 ], [-0.9356684552614638, 53.502529644641044 ], [-0.9533588178545198, 53.48444116472433 ], [-0.9860591850719871, 53.47152082192668 ], [-0.9983888317277536, 53.43889695636261 ], [-1.0568206354442111, 53.42533059642508 ], [-1.0868406446930337, 53.42565360499502 ], [-1.115788510754398, 53.397874867980065 ], [-1.1291903005976227, 53.39367575657083 ], [-1.1307985153788094, 53.375587276654116 ], [-1.145272448409492, 53.34070235110046 ], [-1.203168180532221, 53.30710945982656 ], [-1.2305078318123979, 53.30840149410633 ], [-1.2540949819364728, 53.30161831413756 ], [-1.2937642798724163, 53.31389263979533 ], [-1.2991249958097066, 53.332627136851926 ], [-1.3473714392453138, 53.31873776834445 ], [-1.3897210951499028, 53.31841475977451 ], [-1.390793238337361, 53.33585722255134 ], [-1.4427921829290709, 53.337149256831104 ], [-1.4551218295848374, 53.32196785404386 ], [-1.5103372037389207, 53.31615369978492 ], [-1.5569754323933411, 53.305494416976856 ], [-1.5623361483306315, 53.31583069121498 ], [-1.5993250882979302, 53.3113085712358 ], [-1.6121908065474253, 53.34328641965999 ], [-1.6304172407342108, 53.35620676245764 ], [-1.6641897511391361, 53.3668660452657 ], [-1.6534683192645563, 53.384954525182415 ], [-1.6818801137321922, 53.401750970819364 ], [-1.7049311922625376, 53.40498105651878 ], [-1.7467447765733972, 53.4262996221349 ], [-1.7451365617922105, 53.461830564828446 ], [-1.768187640322556, 53.46473764195792 ], [-1.8014240791337528, 53.48088807045498 ], [-1.7960633631964624, 53.50317566178093 ], [-1.827155515632743, 53.52352520168723 ], [-1.8459180214132571, 53.52481723596699 ], [-1.8705773147247893, 53.539352621614356 ], [-1.909710541067005, 53.53838359590453 ], [-1.9461634094405742, 53.57133047003854 ], [-1.981544134626687, 53.58941894995525 ], [-1.9960180676573689, 53.60524636988238 ], [-2.022821647343817, 53.61590565269044 ], [-2.0410480815306027, 53.64077731257592 ], [-2.0372955803745, 53.662418886761984 ], [-2.061954873686032, 53.68276842666829 ], [-2.084469880622649, 53.67307816957005 ], [-2.1182423910275743, 53.6711401181504 ], [-2.1343245388394436, 53.68567550379776 ], [-2.161128118525892, 53.69762682088559 ], [-2.1734577651816585, 53.72314449791095 ], [-2.148262400276397, 53.73412678928895 ], [-2.1294998944958827, 53.754476329195256 ], [-2.127891679714696, 53.79840549470727 ], [-2.0850059522163784, 53.81843202604363 ], [-2.0469448690616217, 53.82941431742163 ], [-2.0458727258741636, 53.85008686589788 ], [-2.074820591935528, 53.86236119155564 ], [-2.103232386403163, 53.890785945710476 ], [-2.1075209591529953, 53.90790539991737 ], [-2.1321802524645275, 53.92663989697396 ], [-2.1638084764945367, 53.93019299124332 ], [-2.185787411837425, 53.94052926548144 ], [-2.1959727721182754, 53.96960003677616 ], [-2.225992781367098, 53.96120181395768 ], [-2.2302813541169293, 53.981551353863985 ], [-2.2844245850835554, 53.97379914818539 ], [-2.3267742409881444, 53.99382567952175 ], [-2.3525056774871347, 53.99479470523158 ], [-2.3621549661742565, 54.04066192216324 ], [-2.3739485412362935, 54.04906014498171 ], [-2.42273105626563, 54.04066192216324 ], [-2.4693692849200506, 54.04615306785224 ], [-2.4656167837639478, 54.0716707448776 ], [-2.480626788388359, 54.085237104815135 ], [-2.5240485874804057, 54.094604353343435 ], [-2.5449553796358355, 54.117214953239326 ], [-2.5631818138226206, 54.122706098928326 ], [-2.5572850262916016, 54.149838818803396 ], [-2.536378234136172, 54.15662199877216 ], [-2.528873231823966, 54.169542341569816 ], [-2.48009071679463, 54.202166207133885 ], [-2.460792139420387, 54.22671485844943 ], [-2.403432478891387, 54.225422824169655 ], [-2.3969996197666394, 54.23931219267713 ], [-2.3482171047373033, 54.237697149827426 ], [-2.322485668238312, 54.24544935550602 ], [-2.315516737519836, 54.270321015391495 ], [-2.3240938830194997, 54.28001127248974 ], [-2.3230217398320416, 54.3110200952041 ], [-2.309619949988817, 54.32426344657169 ], [-2.3594746082056113, 54.350104132167 ], [-2.3171249523010227, 54.37626782633224 ], [-2.291929587395761, 54.384343040580774 ], [-2.3064035204264437, 54.39887842622813 ], [-2.3080117352076304, 54.4205200004142 ], [-2.2678063656779575, 54.44732971171933 ], [-2.189539912993528, 54.448944754569034 ], [-2.1702413356192842, 54.45831200309733 ], [-2.086614166997565, 54.46832526876551 ], [-2.0592745157173873, 54.48124561156316 ], [-1.9761834186893967, 54.4596040373771 ], [-1.9424109082844723, 54.45346687454821 ], [-1.8936283932551357, 54.46703323448575 ], [-1.8609280260376684, 54.486413748682224 ], [-1.8577115964752942, 54.50353320288911 ], [-1.8362687327261353, 54.50805532286829 ], [-1.8223308712891821, 54.49513498007064 ], [-1.792846933634089, 54.484475697262575 ], [-1.7767647858222197, 54.525497785645115 ], [-1.767115497135098, 54.532280965613886 ], [-1.7333429867301735, 54.52775884563471 ], [-1.7215494116681356, 54.542294231282064 ], [-1.6834883285133788, 54.53357299989365 ], [-1.6577568920143877, 54.53454202560347 ], [-1.6341697418903136, 54.525497785645115 ], [-1.6288090259530232, 54.51386947712723 ], [-1.5832429404860608, 54.51322345998735 ], [-1.5837790120797903, 54.49771904863017 ], [-1.5564393607996125, 54.485444722972396 ], [-1.4985436286768836, 54.475108448734275 ], [-1.4578021875534821, 54.4815686201331 ], [-1.4636989750845002, 54.4964270143504 ], [-1.4100918157116036, 54.47833853443369 ], [-1.4100918157116036, 54.48964383438164 ], [-1.374175018931762, 54.48415268869263 ], [-1.3725668041505754, 54.472201371604804 ], [-1.343618938089211, 54.464126157356276 ], [-1.2867953491539401, 54.48286065441287 ], [-1.2278274738437531, 54.51193142570759 ], [-1.172612099689669, 54.50288718574923 ], [-1.126509942628978, 54.49868807433999 ], [-1.0943456470052393, 54.50676328858852 ], [-1.0369859864762399, 54.49416595436081 ], [-1.0032134760713145, 54.50288718574923 ], [-0.9678327508852025, 54.497396040060224 ], [-0.9533588178545198, 54.48802879153193 ], [-0.8809891527011091, 54.49707303149029 ], [-0.8563298593895761, 54.48770578296199 ], [-0.8488248570773704, 54.5300199056243 ], [-0.8364952104216039, 54.54261723985201 ], [-0.7941455545170157, 54.55844465977913 ], [-0.7507237554249686, 54.54197122271212 ], [-0.7448269678939505, 54.52840486277459 ], [-0.714806958645128, 54.53357299989365 ], [-0.6751376607091837, 54.506117271448645 ], [-0.6542308685537543, 54.49771904863017 ], [-0.5893662057125484, 54.48835180010187 ], [-0.5566658384950811, 54.4650951830661 ], [-0.5207490417152405, 54.4463606860095 ], [-0.5325426167772775, 54.429241231802614 ], [-0.5228933280901558, 54.416320889004965 ], [-0.49126510406014656, 54.40792266618649 ], [-0.46392545277996966, 54.389188169129895 ], [-0.42318401165656727, 54.33912184078899 ], [-0.4178232957192778, 54.310374078064214 ], [-0.39745257515757704, 54.277104195360266 ], [-0.3679686375024831, 54.25384757832449 ], [-0.3132893349421284, 54.22897591843901 ], [-0.2864857552556801, 54.22316176418007 ], [-0.285413612068222, 54.20313523284371 ], [-0.2725478938187269, 54.18181666722758 ], [-0.2184046628521008, 54.157914033051924 ], [-0.17283857738513753, 54.1514538616531 ], [-0.1288807066993627, 54.13530343315604 ], [-0.08331462123239941, 54.12238309035838 ], [-0.079026048482568, 54.112369824690205 ], [-0.11279855888749335, 54.10332558473185 ], [-0.14067428176139973, 54.10429461044167 ], [-0.17230250579140893, 54.0958963876232 ], [-0.19857001388412865, 54.077807907706486 ], [-0.21304394691481043, 54.05358226496089 ], [-0.21304394691481043, 54.00803805659917 ], [-0.1830239376659879, 53.94925049686985 ], [-0.15675642957326907, 53.90370628850813 ], [-0.12191177598088565, 53.86397623440535 ], [-0.02702710389085805, 53.7806400233605 ], [0.029260413450684197, 53.73671085784848 ], [0.04641470445001161, 53.719591403641594 ], [0.11717615482223565, 53.66209587819204 ], [0.1418354481337678, 53.61945874695979 ], [0.07965114326120748, 53.641100321145856 ], [0.03462112938797368, 53.64917553539439 ], [-0.04954211082747495, 53.6320560811875 ], [-0.10368534179410016, 53.635286166886914 ], [-0.13048892148054936, 53.64917553539439 ], [-0.18409608085344598, 53.68664452950758 ], [-0.22698180835176363, 53.70860911226359 ], [-0.237167168632614, 53.7266975921803 ], [-0.2730839654124555, 53.73994094354789 ], [-0.2950629007553429, 53.74252501210743 ], [-0.3368764850662034, 53.737356874988365 ], [-0.38297864212689436, 53.72475954076066 ], [-0.44730723337437084, 53.71377724938265 ], [-0.47357474146709055, 53.717330343652 ], [-0.5529133373389783, 53.709578137973416 ], [-0.5893662057125484, 53.727989626460065 ], [-0.6167058569927262, 53.73121971215948 ], [-0.6703130163656228, 53.72217547220112 ], [-0.7024773119893615, 53.70376398371447 ], [-0.6896115937398664, 53.67307816957005 ], [-0.714806958645128, 53.642069346855685 ], [-0.714806958645128, 53.62753396120833 ], [-0.7030133835830901, 53.6171976869702 ], [-0.7035494551768195, 53.610091498431494 ], [-0.7287448200820812, 53.608153447011844 ], [-0.7464351826751372, 53.5726225043183 ], [-0.7383941087692021, 53.52610927024676 ], [-0.743218753112763, 53.51125087602946 ], [-0.7528680417998848, 53.50091460179134 ], [-0.7625173304870065, 53.50123761036128 ], [-0.7657337600493799, 53.5005915932214 ], [-0.7689501896117541, 53.498007524661865 ], [-0.7694862612054827, 53.494454430392516 ], [-0.7764551919239597, 53.48864027613357 ], [-0.7716305475803988, 53.48088807045498 ], [-0.784496265829894, 53.47701196761568 ], [-0.7978980556731186, 53.45537039342962 ] ] ] ] + } + }, + { + "type": "Feature", + "properties": { + "AREACD": "E12000004", + "AREANM": "East Midlands" + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [[[[0.2688844158475341, 52.81581342494586 ], [0.24529726572346, 52.78448159366155 ], [0.2721008454099083, 52.77285328514367 ], [0.23296761906769348, 52.760255950915955 ], [0.186329390413273, 52.73538429103048 ], [0.17185545738259034, 52.737968359590006 ], [0.1954426075066653, 52.765747096604954 ], [0.20723618256870235, 52.7880346879309 ], [0.21688547125582414, 52.820981562064915 ], [0.22921511791159066, 52.823888639194394 ], [0.2688844158475341, 52.81581342494586 ] ] ], [[[0.017466838388647155, 53.52546325310688 ], [0.07965114326120748, 53.51125087602946 ], [0.08930043194832926, 53.49413142182257 ], [0.14344366291495536, 53.4818570961648 ], [0.15416509478953433, 53.49025531898327 ], [0.18364903244462827, 53.45924649626891 ], [0.1868654620070016, 53.43954297350249 ], [0.2045558246000576, 53.429852716404255 ], [0.23886440659871155, 53.37914037092347 ], [0.273709060191095, 53.332627136851926 ], [0.2892551364092357, 53.305494416976856 ], [0.31927514565805737, 53.27157851713302 ], [0.3369655082511134, 53.24024668584872 ], [0.35572801403162746, 53.18598124609858 ], [0.34661479693823516, 53.12654766922938 ], [0.3492951549068799, 53.11395033500167 ], [0.3358933650636562, 53.089401683686134 ], [0.31927514565805737, 53.09166274367572 ], [0.2721008454099083, 53.06743710093012 ], [0.23940047819244104, 53.04579552674406 ], [0.19812296547531005, 53.03222916680652 ], [0.13647473219647832, 52.99540618983322 ], [0.08447578760476837, 52.94986198147149 ], [0.05874435110577725, 52.9162690901976 ], [0.0925168615107026, 52.89398149887165 ], [0.11610401163477757, 52.89365849030171 ], [0.17614403013242264, 52.87395496753529 ], [0.18096867447598353, 52.86135763330758 ], [0.214741184880908, 52.82259660491462 ], [0.2056279677875157, 52.787388670791024 ], [0.1954426075066653, 52.76736213945466 ], [0.17131938578886174, 52.73829136815995 ], [0.1327222310403755, 52.73926039386977 ], [0.08876436035459978, 52.72375598251259 ], [0.06410506704306762, 52.72730907678194 ], [0.04427041807509546, 52.71438873398429 ], [0.04802291923119828, 52.68111885128034 ], [0.02121933954474997, 52.664968422783275 ], [-0.03131567664068946, 52.66141532851392 ], [-0.059727471108325325, 52.67465867988151 ], [-0.09028355195087645, 52.66658346563298 ], [-0.1020771270129135, 52.67207461132198 ], [-0.14121035335512833, 52.65140206284574 ], [-0.20607501619633428, 52.66819850848269 ], [-0.26129039035041846, 52.65140206284574 ], [-0.28970218481805343, 52.670459568472275 ], [-0.32079433725433404, 52.67498168845145 ], [-0.35671113403417465, 52.65980028566421 ], [-0.40549364906351126, 52.648171977146326 ], [-0.4526679493116612, 52.65430913997521 ], [-0.4939454620287913, 52.6400967628978 ], [-0.4708943834984458, 52.62362332583079 ], [-0.49072903246641797, 52.59067645169678 ], [-0.47893545740438004, 52.57355699748989 ], [-0.42479222643775394, 52.5822782288783 ], [-0.41943151050046446, 52.55902161184253 ], [-0.40120507631367985, 52.538672071936226 ], [-0.4135347229694464, 52.52575172913858 ], [-0.35563899084671746, 52.50604820637216 ], [-0.3593914920028203, 52.48989777787509 ], [-0.34223720100349286, 52.46922522939885 ], [-0.3636800647526517, 52.45049073234225 ], [-0.37922614097079155, 52.42788013244637 ], [-0.4172872241255492, 52.4117297039493 ], [-0.44623509018691276, 52.383304949794464 ], [-0.484832244935399, 52.38168990694476 ], [-0.49984224955981027, 52.36521646987776 ], [-0.4676779539360725, 52.33840675857263 ], [-0.4762550994357353, 52.318057218666326 ], [-0.5068111802782873, 52.319995270085975 ], [-0.5405836906832118, 52.29157051593114 ], [-0.5314704735898195, 52.270251950315014 ], [-0.5427279770581279, 52.2560395732376 ], [-0.5657790555884734, 52.253455504678065 ], [-0.6022319239620435, 52.278650173133485 ], [-0.6536947969600249, 52.268313898895364 ], [-0.6376126491481555, 52.227291810512824 ], [-0.66173587086596, 52.20823430488629 ], [-0.6681687299907075, 52.1949909535187 ], [-0.7019412403956329, 52.19337591066899 ], [-0.7346416076130993, 52.17334937933263 ], [-0.7877126953922682, 52.15429187370609 ], [-0.8075473443602394, 52.15687594226562 ], [-0.8284541365156697, 52.13265029952002 ], [-0.8691955776390712, 52.13071224810038 ], [-0.8868859402321272, 52.114238811033374 ], [-0.8466805707024543, 52.09162821113748 ], [-0.8354230672341467, 52.079676894049655 ], [-0.8386394967965201, 52.06417248269247 ], [-0.8574020025770341, 52.06417248269247 ], [-0.8708037924202587, 52.04382294278617 ], [-0.9061845176063708, 52.02121234289028 ], [-0.9303077393241743, 52.043176925646286 ], [-0.9453177439485856, 52.076769816920184 ], [-0.9678327508852025, 52.070955662661234 ], [-1.02626455460166, 52.07547778264042 ], [-1.0530681342881083, 52.05965036271329 ], [-1.1222213698791457, 52.04479196849599 ], [-1.1361592313160989, 52.019920308610516 ], [-1.119541011910501, 52.01636721434116 ], [-1.1345510165349122, 51.997309708714624 ], [-1.1640349541900052, 51.993433605875325 ], [-1.1978074645949306, 51.97728317737826 ], [-1.2417653352807063, 51.98632741733662 ], [-1.261063912654949, 51.98083627164762 ], [-1.2873314207476687, 51.98955750303603 ], [-1.2782182036542764, 52.01410615435157 ], [-1.297516781028519, 52.04285391707634 ], [-1.3130628572466598, 52.05125213989482 ], [-1.3055578549344542, 52.07192468837106 ], [-1.321103931152594, 52.08775210829818 ], [-1.2873314207476687, 52.092597236847304 ], [-1.2530228387490148, 52.103902536795246 ], [-1.2766099888730897, 52.117145888162845 ], [-1.331825363027174, 52.168504250783506 ], [-1.3130628572466598, 52.19046883353951 ], [-1.255167125123931, 52.19789803064817 ], [-1.261063912654949, 52.215986510564875 ], [-1.2766099888730897, 52.22309269910359 ], [-1.2546310535302014, 52.24764135041912 ], [-1.2353324761559588, 52.24925639326883 ], [-1.216033898781716, 52.26411478748613 ], [-1.2288996170312112, 52.29803068732997 ], [-1.209064968063239, 52.315150141536854 ], [-1.261063912654949, 52.32774747576457 ], [-1.2578474830925757, 52.33711472429286 ], [-1.2203224715315475, 52.34971205852057 ], [-1.192446748657641, 52.35165010994022 ], [-1.1720760280959404, 52.36134036703846 ], [-1.2406931920932482, 52.44015445810413 ], [-1.3077021413093703, 52.49441989785427 ], [-1.4594104023346688, 52.551915423303825 ], [-1.5226668503946872, 52.57064992036042 ], [-1.5376768550190985, 52.58970742598695 ], [-1.560727933549444, 52.59616759738578 ], [-1.5542950744246964, 52.614579085872435 ], [-1.5709132938302952, 52.63395960006891 ], [-1.5467900721124908, 52.642357822887384 ], [-1.5526868596435097, 52.668521517052625 ], [-1.6154072361097995, 52.70082237404676 ], [-1.6545404624520144, 52.69985334833694 ], [-1.6566847488269305, 52.72181793109294 ], [-1.6942097603879578, 52.726986068212 ], [-1.697426189950332, 52.74378251384895 ], [-1.6657979659203228, 52.78512761080143 ], [-1.6272008111718366, 52.77963646511243 ], [-1.5896757996108084, 52.80644617641756 ], [-1.6052218758289492, 52.814521390666094 ], [-1.603077589454033, 52.84100809340128 ], [-1.6427468873899773, 52.85618949618852 ], [-1.703322977481351, 52.86652577042664 ], [-1.7231576264493222, 52.860388607597756 ], [-1.751569420916958, 52.87427797610523 ], [-1.8110733678208737, 52.880738147504054 ], [-1.856639453287836, 52.92369828730625 ], [-1.8287637304139297, 52.94889295576167 ], [-1.8234030144766402, 52.97860974419627 ], [-1.7628269243852666, 52.999605301242454 ], [-1.7601465664166218, 53.039012346775294 ], [-1.778909072197136, 53.04288844961459 ], [-1.7944551484152758, 53.088432657976306 ], [-1.7880222892905282, 53.109105206452554 ], [-1.8008880075400233, 53.11524236928143 ], [-1.821258728101724, 53.14140606344668 ], [-1.8126815826020604, 53.1517423376848 ], [-1.831980159976304, 53.172737894730986 ], [-1.8759380306620796, 53.19534849462688 ], [-1.9091744694732755, 53.198901588896234 ], [-1.9354419775659952, 53.211175914554 ], [-1.9874409221577052, 53.21343697454359 ], [-1.9761834186893967, 53.227649351621004 ], [-1.9997705688134717, 53.24509181439784 ], [-2.0094198575005935, 53.27997673995149 ], [-2.0142445018441544, 53.340056333960575 ], [-2.0045952131570326, 53.34587048821952 ], [-2.016924859812799, 53.37784833664371 ], [-1.9938737812824527, 53.409180167928014 ], [-2.0238937905312753, 53.420485467875956 ], [-2.0260380769061914, 53.429852716404255 ], [-1.98529663578279, 53.45569340199956 ], [-1.9879769937514347, 53.481211079024916 ], [-1.9729669891270234, 53.502529644641044 ], [-1.9354419775659952, 53.50575973034046 ], [-1.909710541067005, 53.53838359590453 ], [-1.8705773147247893, 53.539352621614356 ], [-1.8459180214132571, 53.52481723596699 ], [-1.827155515632743, 53.52352520168723 ], [-1.7960633631964624, 53.50317566178093 ], [-1.8014240791337528, 53.48088807045498 ], [-1.768187640322556, 53.46473764195792 ], [-1.7451365617922105, 53.461830564828446 ], [-1.7467447765733972, 53.4262996221349 ], [-1.7049311922625376, 53.40498105651878 ], [-1.6818801137321922, 53.401750970819364 ], [-1.6534683192645563, 53.384954525182415 ], [-1.6641897511391361, 53.3668660452657 ], [-1.6304172407342108, 53.35620676245764 ], [-1.6121908065474253, 53.34328641965999 ], [-1.5993250882979302, 53.3113085712358 ], [-1.5623361483306315, 53.31583069121498 ], [-1.5569754323933411, 53.305494416976856 ], [-1.5103372037389207, 53.31615369978492 ], [-1.4551218295848374, 53.32196785404386 ], [-1.4427921829290709, 53.337149256831104 ], [-1.390793238337361, 53.33585722255134 ], [-1.3897210951499028, 53.31841475977451 ], [-1.3473714392453138, 53.31873776834445 ], [-1.2991249958097066, 53.332627136851926 ], [-1.2937642798724163, 53.31389263979533 ], [-1.2540949819364728, 53.30161831413756 ], [-1.2305078318123979, 53.30840149410633 ], [-1.203168180532221, 53.30710945982656 ], [-1.145272448409492, 53.34070235110046 ], [-1.1307985153788094, 53.375587276654116 ], [-1.1291903005976227, 53.39367575657083 ], [-1.115788510754398, 53.397874867980065 ], [-1.0868406446930337, 53.42565360499502 ], [-1.0568206354442111, 53.42533059642508 ], [-0.9983888317277536, 53.43889695636261 ], [-0.9860591850719871, 53.47152082192668 ], [-0.9533588178545198, 53.48444116472433 ], [-0.9356684552614638, 53.502529644641044 ], [-0.9002877300753518, 53.47507391619604 ], [-0.8718759356077159, 53.46635268480762 ], [-0.7978980556731186, 53.45537039342962 ], [-0.7850323374236234, 53.461830564828446 ], [-0.7823519794549778, 53.477334976185624 ], [-0.7727026907678569, 53.47798099332551 ], [-0.7710944759866694, 53.48282612187462 ], [-0.7748469771427722, 53.48864027613357 ], [-0.767878046424296, 53.494454430392516 ], [-0.7657337600493799, 53.499945576081515 ], [-0.760909115705819, 53.5005915932214 ], [-0.7544762565810714, 53.49962256751157 ], [-0.7512598270186981, 53.5005915932214 ], [-0.7421466099253049, 53.510281850319636 ], [-0.7383941087692021, 53.51997210741788 ], [-0.6756737323029132, 53.51254291030923 ], [-0.6247469308986604, 53.512865918879164 ], [-0.6338601479920527, 53.48541019043416 ], [-0.6295715752422213, 53.45827747055909 ], [-0.5518411941515202, 53.45956950483885 ], [-0.4719665266859039, 53.474750907626095 ], [-0.4885847460915018, 53.485087181864216 ], [-0.4757190278420067, 53.50898981603987 ], [-0.45213187771793173, 53.50866680746993 ], [-0.40495757746978267, 53.517711047428286 ], [-0.408174007032156, 53.53224643307564 ], [-0.4306890139687729, 53.54645881015306 ], [-0.467141882342343, 53.54904287871259 ], [-0.42908079918758624, 53.57456055573795 ], [-0.41943151050046446, 53.56390127292989 ], [-0.35617506244044606, 53.5561490672513 ], [-0.3358043418787453, 53.55873313581083 ], [-0.2902382564117829, 53.61073751557138 ], [-0.23502288225769874, 53.58554284711596 ], [-0.22269323560193222, 53.568100384339125 ], [-0.1873125104158202, 53.56357826435995 ], [-0.22001287763328747, 53.532569441645585 ], [-0.20393072982141813, 53.51189689316934 ], [-0.20232251504023147, 53.48670222471392 ], [-0.18195179447853072, 53.46861374479721 ], [-0.15943678754191382, 53.467967727657324 ], [-0.14442678291750255, 53.44083500778226 ], [-0.13209713626173603, 53.43598987923314 ], [-0.08224247804494134, 53.45117128202038 ], [-0.10797391454393246, 53.469905779076974 ], [-0.07527354732646518, 53.48928629327345 ], [-0.06776854501425955, 53.51609600457858 ], [-0.04686175285882932, 53.51997210741788 ], [-0.01737781520373627, 53.51512697886876 ], [0.017466838388647155, 53.52546325310688 ] ] ] ] + } + }, + { + "type": "Feature", + "properties": { + "AREACD": "E12000005", + "AREANM": "West Midlands" + }, + "geometry": { + "type": "Polygon", + "coordinates": [[[-1.331825363027174, 52.168504250783506 ], [-1.3409385801205662, 52.144924625177794 ], [-1.3827521644314258, 52.127482162400966 ], [-1.3575567995261641, 52.10131846823572 ], [-1.3854325224000705, 52.09421227969701 ], [-1.4245657487422854, 52.11811491387267 ], [-1.4535136148036498, 52.1129467767536 ], [-1.4470807556789023, 52.09776537396637 ], [-1.480317194490099, 52.093566262557125 ], [-1.5017600582392578, 52.07160167980112 ], [-1.4974714854894255, 52.060942396993056 ], [-1.5108732753326501, 52.02153535146022 ], [-1.5290997095194347, 51.9924645801655 ], [-1.5494704300811355, 51.98083627164762 ], [-1.5918200859857246, 51.9704999974095 ], [-1.6121908065474253, 51.955318594622256 ], [-1.6298811691404813, 51.96985398026961 ], [-1.6657979659203228, 51.98761945161638 ], [-1.6657979659203228, 51.997309708714624 ], [-1.6422108157962478, 52.01023005151227 ], [-1.626664739578108, 52.03800878852723 ], [-1.6566847488269305, 52.03219463426828 ], [-1.6947458319816873, 52.04059285708676 ], [-1.6899211876381264, 52.053190191314464 ], [-1.7306626287615279, 52.07353973122077 ], [-1.7279822707928831, 52.08839812543807 ], [-1.7547858504793323, 52.099380416816075 ], [-1.7676515687288274, 52.11262376818367 ], [-1.80249622232121, 52.09679634825654 ], [-1.831980159976304, 52.07321672265083 ], [-1.8464540930069857, 52.07935388547971 ], [-1.8689690999436026, 52.07386273979071 ], [-1.8571755248815656, 52.05222116560464 ], [-1.8346605179449487, 52.043176925646286 ], [-1.826083372445285, 52.02928755713881 ], [-1.8351965895386773, 52.00926102580245 ], [-1.8512787373505466, 52.008292000092624 ], [-1.8737937442871635, 52.024442428589694 ], [-1.9134630422231078, 52.04446895992605 ], [-1.9311534048161638, 52.03025658284864 ], [-1.9515241253778646, 52.037685779957286 ], [-1.9842244925953318, 52.035747728537636 ], [-2.0517695134051817, 52.00861500866257 ], [-2.1118095319028267, 52.01475217149145 ], [-2.1509427582450416, 52.006676957242924 ], [-2.139685254776733, 52.02767251428911 ], [-2.1182423910275743, 52.042207899936464 ], [-2.16220026171335, 52.050606122754935 ], [-2.1804266959001346, 52.04156188279658 ], [-2.1841791970562374, 52.01378314578163 ], [-2.1638084764945367, 51.997309708714624 ], [-2.1852513402436955, 51.99052652874585 ], [-2.2206320654298075, 51.995371657294974 ], [-2.2511881462723595, 51.9666238945702 ], [-2.3128363795511913, 51.97663716023838 ], [-2.329454598956789, 52.006676957242924 ], [-2.3525056774871347, 52.01346013721169 ], [-2.3927110470168076, 52.0128141200718 ], [-2.402896407297658, 51.99601767443486 ], [-2.4355967745151252, 51.9979557258545 ], [-2.4339885597339386, 52.01216810293192 ], [-2.4736578576698824, 52.02379641144981 ], [-2.4913482202629385, 52.00635394867298 ], [-2.4709774997012373, 51.994725640155096 ], [-2.496708936200228, 51.9759911430985 ], [-2.4902760770754804, 51.95499558605231 ], [-2.4645446405764897, 51.948535414653485 ], [-2.466152855357677, 51.92786286617724 ], [-2.448462492764621, 51.918818626218886 ], [-2.440421418858686, 51.90234518915188 ], [-2.494028578231583, 51.88005759782593 ], [-2.5085025112622654, 51.885225734944996 ], [-2.5224403726992186, 51.86487619503869 ], [-2.5615735990414334, 51.86423017789881 ], [-2.604459326539751, 51.85453992080057 ], [-2.6355514789760317, 51.840973560863034 ], [-2.650561483600443, 51.826115166645735 ], [-2.666107559818583, 51.835482415174035 ], [-2.6934472110987606, 51.83386737232433 ], [-2.715962218035377, 51.8406505522931 ], [-2.739013296565723, 51.836451440883856 ], [-2.778682594501667, 51.86584522074852 ], [-2.7695693774082746, 51.880380606395875 ], [-2.802269744625742, 51.890716880633995 ], [-2.8210322504062555, 51.905575274851294 ], [-2.8751754813728816, 51.93303100329631 ], [-2.92824656915205, 51.91397349766977 ], [-2.9684519386817225, 51.90460624914147 ], [-2.976493012587657, 51.9275398576073 ], [-2.999008019524274, 51.923340746198065 ], [-3.025811599210723, 51.957256646041905 ], [-3.056903751647003, 51.97534512595861 ], [-3.0992534075515916, 52.02282738573999 ], [-3.0863876893020965, 52.041238874226636 ], [-3.0906762620519284, 52.05092913132488 ], [-3.1260569872380404, 52.07838485976989 ], [-3.1228405576756666, 52.10325651965537 ], [-3.1067584098637973, 52.118760931012545 ], [-3.1421391350499093, 52.1278051709709 ], [-3.1357062759251617, 52.13781843663909 ], [-3.093892691614302, 52.14427860803791 ], [-3.0944287632080307, 52.155260899415914 ], [-3.1223044860819376, 52.16333611366445 ], [-3.096573049582947, 52.180455567871334 ], [-3.0997894791453207, 52.202097142057404 ], [-3.0837073313334513, 52.21017235630593 ], [-3.070305541490227, 52.229229861932474 ], [-3.0440380333975074, 52.23924312760065 ], [-3.0456462481786946, 52.25313249610813 ], [-2.979173370556302, 52.273159027444486 ], [-3.011873737773769, 52.27735813885372 ], [-2.986142301274779, 52.323548364355325 ], [-2.961483007963246, 52.335822690013096 ], [-2.9545140772447693, 52.34906604138069 ], [-2.9743487262127415, 52.354557187069695 ], [-3.0408216038351337, 52.344220912831574 ], [-3.092284476833115, 52.36263240131822 ], [-3.1105109110199, 52.377167786965586 ], [-3.154468781705676, 52.38782706977364 ], [-3.1592934260492367, 52.39816334401176 ], [-3.2305909480151898, 52.4304642010059 ], [-3.2332713059838345, 52.45113674948214 ], [-3.1946741512353487, 52.47633141793756 ], [-3.1807362897983955, 52.47374734937803 ], [-3.1405309202687226, 52.4915128207248 ], [-3.113727340582274, 52.498296000693564 ], [-3.055831608459545, 52.49991104354327 ], [-3.033316601522928, 52.50378714638257 ], [-3.0322444583354704, 52.52381367771893 ], [-3.0049048070552926, 52.529304823407934 ], [-2.997399804743087, 52.56386674039165 ], [-3.0145540957424144, 52.57549504890954 ], [-3.0499348209285264, 52.56063665469224 ], [-3.111583054207358, 52.54125614049576 ], [-3.1362423475188907, 52.5861543317176 ], [-3.1174798417383767, 52.58647734028754 ], [-3.0510069641159845, 52.647202951436505 ], [-3.042429818616321, 52.68693300553928 ], [-3.0225951696483486, 52.7066365283057 ], [-3.020450883273433, 52.72181793109294 ], [-2.985070158087321, 52.722786956802764 ], [-2.9705962250566387, 52.73473827389059 ], [-3.0097294513988535, 52.75024268524778 ], [-3.0129458809612273, 52.76445506232519 ], [-3.0713776846776852, 52.77026921658413 ], [-3.091748405239386, 52.78674265365114 ], [-3.13141770317533, 52.787388670791024 ], [-3.1582212828617786, 52.79352583361991 ], [-3.1678705715489, 52.819366519215215 ], [-3.151252352143302, 52.842623136250985 ], [-3.1614377124241524, 52.848114281939985 ], [-3.127665202019227, 52.86717178756653 ], [-3.1523244953307596, 52.87880009608441 ], [-3.147499850987199, 52.890105396032354 ], [-3.1169437701446476, 52.899795653130596 ], [-3.096036977989218, 52.93015845870507 ], [-3.0767384006149747, 52.92531333015596 ], [-3.0311723151480123, 52.93145049298484 ], [-3.0097294513988535, 52.95599914430038 ], [-2.981853728524947, 52.95922922999979 ], [-2.9352154998705267, 52.941786767222965 ], [-2.8982265599032275, 52.950184990041436 ], [-2.8403308277804986, 52.941786767222965 ], [-2.8387226129993115, 52.93209651012472 ], [-2.8129911765003213, 52.91497705591784 ], [-2.8006615298445547, 52.89559654172136 ], [-2.7550954443775924, 52.92466731301607 ], [-2.7282918646911436, 52.92531333015596 ], [-2.7266836499099565, 52.98313186417545 ], [-2.699343998629779, 52.99540618983322 ], [-2.681117564442994, 52.98603894130492 ], [-2.632871121007387, 52.99669822411298 ], [-2.6076757561021253, 52.988300001294505 ], [-2.5974903958212745, 52.963105332839085 ], [-2.578191818447032, 52.955676135730435 ], [-2.5615735990414334, 52.96407435854891 ], [-2.535306090948714, 52.94792393005184 ], [-2.5229764442929477, 52.9702115213778 ], [-2.475266072451069, 52.962459315699206 ], [-2.4361328461088543, 52.973441607077206 ], [-2.43827713248377, 52.984746907025155 ], [-2.38091747195477, 52.99799025839275 ], [-2.3707321116739197, 53.01446369545975 ], [-2.3841339015171443, 53.02609200397764 ], [-2.381453543548499, 53.05257870671282 ], [-2.348753176331032, 53.055808792412236 ], [-2.3289185273630597, 53.07648134088848 ], [-2.2463635019287986, 53.09166274367572 ], [-2.223312423398453, 53.10393706933349 ], [-2.1766741947440327, 53.145605174855916 ], [-2.1412934695579198, 53.15658746623392 ], [-2.142365612745378, 53.182105143259285 ], [-2.1085931023404525, 53.16821577475181 ], [-2.0694598759982377, 53.1720918775911 ], [-2.046408797467892, 53.19276442606735 ], [-2.001914855188388, 53.193087434637285 ], [-1.9874409221577052, 53.21343697454359 ], [-1.9354419775659952, 53.211175914554 ], [-1.9091744694732755, 53.198901588896234 ], [-1.8759380306620796, 53.19534849462688 ], [-1.831980159976304, 53.172737894730986 ], [-1.8126815826020604, 53.1517423376848 ], [-1.821258728101724, 53.14140606344668 ], [-1.8008880075400233, 53.11524236928143 ], [-1.7880222892905282, 53.109105206452554 ], [-1.7944551484152758, 53.088432657976306 ], [-1.778909072197136, 53.04288844961459 ], [-1.7601465664166218, 53.039012346775294 ], [-1.7628269243852666, 52.999605301242454 ], [-1.8234030144766402, 52.97860974419627 ], [-1.8287637304139297, 52.94889295576167 ], [-1.856639453287836, 52.92369828730625 ], [-1.8110733678208737, 52.880738147504054 ], [-1.751569420916958, 52.87427797610523 ], [-1.7231576264493222, 52.860388607597756 ], [-1.703322977481351, 52.86652577042664 ], [-1.6427468873899773, 52.85618949618852 ], [-1.603077589454033, 52.84100809340128 ], [-1.6052218758289492, 52.814521390666094 ], [-1.5896757996108084, 52.80644617641756 ], [-1.6272008111718366, 52.77963646511243 ], [-1.6657979659203228, 52.78512761080143 ], [-1.697426189950332, 52.74378251384895 ], [-1.6942097603879578, 52.726986068212 ], [-1.6566847488269305, 52.72181793109294 ], [-1.6545404624520144, 52.69985334833694 ], [-1.6154072361097995, 52.70082237404676 ], [-1.5526868596435097, 52.668521517052625 ], [-1.5467900721124908, 52.642357822887384 ], [-1.5709132938302952, 52.63395960006891 ], [-1.5542950744246964, 52.614579085872435 ], [-1.560727933549444, 52.59616759738578 ], [-1.5376768550190985, 52.58970742598695 ], [-1.5226668503946872, 52.57064992036042 ], [-1.4594104023346688, 52.551915423303825 ], [-1.3077021413093703, 52.49441989785427 ], [-1.2406931920932482, 52.44015445810413 ], [-1.1720760280959404, 52.36134036703846 ], [-1.192446748657641, 52.35165010994022 ], [-1.2203224715315475, 52.34971205852057 ], [-1.2578474830925757, 52.33711472429286 ], [-1.261063912654949, 52.32774747576457 ], [-1.209064968063239, 52.315150141536854 ], [-1.2288996170312112, 52.29803068732997 ], [-1.216033898781716, 52.26411478748613 ], [-1.2353324761559588, 52.24925639326883 ], [-1.2546310535302014, 52.24764135041912 ], [-1.2766099888730897, 52.22309269910359 ], [-1.261063912654949, 52.215986510564875 ], [-1.255167125123931, 52.19789803064817 ], [-1.3130628572466598, 52.19046883353951 ], [-1.331825363027174, 52.168504250783506 ] ] ] + } + }, + { + "type": "Feature", + "properties": { + "AREACD": "E12000006", + "AREANM": "East of England" + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [[[[0.5524662889301606, 51.54315965937717 ], [0.5583630764611787, 51.54412868508699 ], [0.6366295291456083, 51.52184109376104 ], [0.6248359540835713, 51.51634994807204 ], [0.5733730810855899, 51.50795172525356 ], [0.5535384321176178, 51.5105357938131 ], [0.5342398547433751, 51.530562325149454 ], [0.5417448570555807, 51.53411541941881 ], [0.5433530718367674, 51.535407453698575 ], [0.5428170002430388, 51.53702249654828 ], [0.5444252150242255, 51.540898599387575 ], [0.544961286617955, 51.54671275364652 ], [0.5524662889301606, 51.54315965937717 ] ] ], [[[0.9582724853829916, 51.619712690453255 ], [0.9496953398833279, 51.60808438193536 ], [0.9084178271661969, 51.58062865349036 ], [0.8671403144490659, 51.55963309644417 ], [0.8403367347626176, 51.561571147863816 ], [0.832831732450412, 51.57578352494124 ], [0.8387285199814309, 51.59419501342789 ], [0.8666042428553373, 51.595164039137714 ], [0.874109245167543, 51.61422154476425 ], [0.9582724853829916, 51.619712690453255 ] ] ], [[[0.9743546331948609, 51.804796601029615 ], [1.004910714037412, 51.79833642963079 ], [0.9893646378192713, 51.78735413825278 ], [0.9459428387272251, 51.774756804025074 ], [0.9041292544163655, 51.773787778315246 ], [0.9014488964477207, 51.78573909540307 ], [0.9266442613529824, 51.8015665153302 ], [0.9647053445077391, 51.80867270386891 ], [0.9743546331948609, 51.804796601029615 ] ] ], [[[1.640691624199972, 52.58776937456731 ], [1.6492687696996349, 52.57937115174883 ], [1.6374751946375978, 52.57484903176965 ], [1.634794836668953, 52.573233988919945 ], [1.632650550294037, 52.57064992036042 ], [1.632650550294037, 52.56095966326218 ], [1.61013554335742, 52.550946397594 ], [1.5951255387330088, 52.55966762898241 ], [1.6015583978577572, 52.56709682609106 ], [1.6074551853887753, 52.56838886037083 ], [1.609063400169962, 52.570326911790474 ], [1.6074551853887753, 52.57388000605983 ], [1.6203209036382704, 52.573233988919945 ], [1.640691624199972, 52.58776937456731 ] ] ], [[[1.715205575728298, 52.61134900017302 ], [1.7312877235401682, 52.580017168888716 ], [1.733968081508813, 52.54933135474429 ], [1.7629159475701766, 52.48117654648668 ], [1.7484420145394957, 52.47277832366821 ], [1.7216384348530465, 52.47762345221732 ], [1.6819691369171021, 52.476977435077444 ], [1.683041280104561, 52.49538892356409 ], [1.6717837766362518, 52.504110154952514 ], [1.6567737720118405, 52.5008800692531 ], [1.6476605549184482, 52.513477403480806 ], [1.6492687696996349, 52.51380041205075 ], [1.6449801969498035, 52.51606147204034 ], [1.639619481012514, 52.51703049775016 ], [1.6315784071065789, 52.52058359201952 ], [1.6288980491379341, 52.527366771988284 ], [1.6203209036382704, 52.53414995195705 ], [1.6063830422013172, 52.54222516620558 ], [1.633722693481495, 52.56095966326218 ], [1.6331866218877664, 52.570326911790474 ], [1.6364030514501398, 52.57388000605983 ], [1.6439080537623454, 52.574203014629774 ], [1.650340912887093, 52.576141066049416 ], [1.650340912887093, 52.59390653739619 ], [1.715205575728298, 52.61134900017302 ] ] ], [[[0.21045261213107658, 51.49018625390679 ], [0.2635236999102446, 51.51796499092175 ], [0.26513191469143127, 51.53217736799916 ], [0.3316047923138239, 51.540898599387575 ], [0.3128422865333098, 51.56577025927306 ], [0.2903272795966929, 51.56415521642335 ], [0.2721008454099083, 51.587734842029064 ], [0.2645958430977027, 51.608407390505306 ], [0.22385440197430118, 51.63166400754108 ], [0.168639027820217, 51.621327733302955 ], [0.13593866060274973, 51.62358879329255 ], [0.08715614557341311, 51.60453128766601 ], [0.06303292385560955, 51.6067923476556 ], [0.021755411138478564, 51.62875693041161 ], [0.02282755432593664, 51.641031256069375 ], [-0.012553170860175378, 51.64619939318844 ], [-0.01094495607898871, 51.68076131017216 ], [-0.06616033023307288, 51.68399139587157 ], [-0.10582962816901631, 51.69174360155016 ], [-0.16372536029174523, 51.682376353021866 ], [-0.19106501157192302, 51.66396486453521 ], [-0.24788860050719386, 51.655243633146796 ], [-0.2543214596319414, 51.64426134176879 ], [-0.2998875450989038, 51.63586311895032 ], [-0.31650576450450174, 51.6403852389295 ], [-0.36582435112756784, 51.621327733302955 ], [-0.3995968615324923, 51.61325251905443 ], [-0.43819401628097854, 51.62003569902319 ], [-0.4569565220614926, 51.612283493344606 ], [-0.4880486744977732, 51.62681887899196 ], [-0.5003783211535389, 51.59968615911689 ], [-0.5180686837465958, 51.60033217625678 ], [-0.5303983304023614, 51.617774639033605 ], [-0.5373672611208384, 51.642969307489025 ], [-0.5052029654970998, 51.673009104493566 ], [-0.5110997530281187, 51.67979228446233 ], [-0.5464804782142307, 51.680438301602216 ], [-0.5475526214016888, 51.70304890149811 ], [-0.5630986976198287, 51.71177013288652 ], [-0.5502329793703336, 51.73050462994311 ], [-0.5727479863069505, 51.73599577563212 ], [-0.586149776150175, 51.75214620412918 ], [-0.6435094366791745, 51.75408425554883 ], [-0.6762098038966418, 51.77120370975572 ], [-0.6826426630213893, 51.79704439535102 ], [-0.7094462427078376, 51.820624020956735 ], [-0.7190955313949594, 51.81610190097756 ], [-0.7448269678939505, 51.83774347516363 ], [-0.7174873166137727, 51.857123989360105 ], [-0.6922919517085111, 51.857123989360105 ], [-0.690147665333595, 51.84129656943298 ], [-0.6670965868032495, 51.815778892407614 ], [-0.6343962195857822, 51.818685969537086 ], [-0.5974072796184835, 51.81416384955791 ], [-0.5818612034003428, 51.8070576610192 ], [-0.5480886929954174, 51.83580542374398 ], [-0.5770365590567819, 51.86584522074852 ], [-0.6092008546805205, 51.87553547784675 ], [-0.6199222865550995, 51.88554874351494 ], [-0.6547669401474829, 51.887809803504524 ], [-0.673529445927997, 51.90137616344206 ], [-0.7019412403956329, 51.90912836912065 ], [-0.6815705198339312, 51.937553123275485 ], [-0.6553030117412115, 51.96048673174131 ], [-0.66173587086596, 51.99957076870421 ], [-0.6451176514603612, 52.01378314578163 ], [-0.6429733650854459, 52.03736277138734 ], [-0.6692408731781656, 52.048668071335285 ], [-0.6258190740861185, 52.08452202259877 ], [-0.6059844251181463, 52.09195121970742 ], [-0.5931187068686512, 52.110362708194074 ], [-0.6070565683056044, 52.13394233379979 ], [-0.6343962195857822, 52.13781843663909 ], [-0.6349322911795108, 52.168181242213564 ], [-0.6274272888673051, 52.18142459358116 ], [-0.6681687299907075, 52.1949909535187 ], [-0.66173587086596, 52.20823430488629 ], [-0.6376126491481555, 52.227291810512824 ], [-0.6536947969600249, 52.268313898895364 ], [-0.6022319239620435, 52.278650173133485 ], [-0.5657790555884734, 52.253455504678065 ], [-0.5427279770581279, 52.2560395732376 ], [-0.5314704735898195, 52.270251950315014 ], [-0.5405836906832118, 52.29157051593114 ], [-0.5068111802782873, 52.319995270085975 ], [-0.4762550994357353, 52.318057218666326 ], [-0.4676779539360725, 52.33840675857263 ], [-0.49984224955981027, 52.36521646987776 ], [-0.484832244935399, 52.38168990694476 ], [-0.44623509018691276, 52.383304949794464 ], [-0.4172872241255492, 52.4117297039493 ], [-0.37922614097079155, 52.42788013244637 ], [-0.3636800647526517, 52.45049073234225 ], [-0.34223720100349286, 52.46922522939885 ], [-0.3593914920028203, 52.48989777787509 ], [-0.35563899084671746, 52.50604820637216 ], [-0.4135347229694464, 52.52575172913858 ], [-0.40120507631367985, 52.538672071936226 ], [-0.41943151050046446, 52.55902161184253 ], [-0.42479222643775394, 52.5822782288783 ], [-0.47893545740438004, 52.57355699748989 ], [-0.49072903246641797, 52.59067645169678 ], [-0.4708943834984458, 52.62362332583079 ], [-0.4939454620287913, 52.6400967628978 ], [-0.4526679493116612, 52.65430913997521 ], [-0.40549364906351126, 52.648171977146326 ], [-0.35671113403417465, 52.65980028566421 ], [-0.32079433725433404, 52.67498168845145 ], [-0.28970218481805343, 52.670459568472275 ], [-0.26129039035041846, 52.65140206284574 ], [-0.20607501619633428, 52.66819850848269 ], [-0.14121035335512833, 52.65140206284574 ], [-0.1020771270129135, 52.67207461132198 ], [-0.09028355195087645, 52.66658346563298 ], [-0.059727471108325325, 52.67465867988151 ], [-0.03131567664068946, 52.66141532851392 ], [0.02121933954474997, 52.664968422783275 ], [0.04802291923119828, 52.68111885128034 ], [0.04427041807509546, 52.71438873398429 ], [0.06410506704306762, 52.72730907678194 ], [0.08876436035459978, 52.72375598251259 ], [0.1327222310403755, 52.73926039386977 ], [0.17131938578886174, 52.73829136815995 ], [0.17185545738259034, 52.737968359590006 ], [0.186329390413273, 52.73538429103048 ], [0.23296761906769348, 52.760255950915955 ], [0.2721008454099083, 52.77285328514367 ], [0.24529726572346, 52.78448159366155 ], [0.2688844158475341, 52.81581342494586 ], [0.3278522911577211, 52.81839749350539 ], [0.3578723004065436, 52.812583339246444 ], [0.44632411337182365, 52.85780453903823 ], [0.4452519701843656, 52.87395496753529 ], [0.4720555498708148, 52.90851688451901 ], [0.4897459124638708, 52.947277912911964 ], [0.5417448570555807, 52.9757026670668 ], [0.5776616538354222, 52.970534529947734 ], [0.6612888224571414, 52.977963727056384 ], [0.7492045638286928, 52.97376461564715 ], [0.7722556423590383, 52.97893275276621 ], [0.8108527971075246, 52.97311859850727 ], [0.8414088779500757, 52.977317709916505 ], [0.850522095043468, 52.95858321285991 ], [0.8692846008239821, 52.9569681700102 ], [0.9196753306345054, 52.96568940139862 ], [0.9459428387272251, 52.95890622142985 ], [0.9700660604450286, 52.96342834140903 ], [1.0177764322869072, 52.956645161440264 ], [1.0231371482241967, 52.96924249566797 ], [1.1244546794389727, 52.95115401575126 ], [1.2509675755590095, 52.941786767222965 ], [1.3008222337758042, 52.93274252726461 ], [1.3431718896803924, 52.920145193036895 ], [1.43323191742686, 52.88138416464394 ], [1.4670044278317853, 52.860388607597756 ], [1.586548393233346, 52.80063202215862 ], [1.6401555526062426, 52.76865417373443 ], [1.684649494885747, 52.73473827389059 ], [1.6991234279164296, 52.719233862533414 ], [1.7264630791966074, 52.66884452562257 ], [1.7452255849771205, 52.62620739439032 ], [1.7264630791966074, 52.59261450311642 ], [1.716277718915757, 52.61684014586202 ], [1.6948348551665973, 52.61780917157184 ], [1.6589180583867567, 52.60391980306437 ], [1.6197848320445418, 52.57355699748989 ], [1.6128159013260648, 52.575172040339595 ], [1.6069191137950467, 52.574203014629774 ], [1.6085273285762334, 52.570326911790474 ], [1.6010223262640277, 52.567419834661 ], [1.5967337535141963, 52.56451275753153 ], [1.5945894671392802, 52.55966762898241 ], [1.5785073193274108, 52.55611453471306 ], [1.6267537627630189, 52.528335797698105 ], [1.6310423355128503, 52.52058359201952 ], [1.6390834094187845, 52.51703049775016 ], [1.6460523401372615, 52.51509244633051 ], [1.6471244833247196, 52.51283138634093 ], [1.6449801969498035, 52.50863227493169 ], [1.657309843605569, 52.50055706068316 ], [1.6819691369171021, 52.49538892356409 ], [1.6814330653233736, 52.47439336651791 ], [1.7237827212279626, 52.4766544265075 ], [1.7452255849771205, 52.46534912655956 ], [1.7350402246962702, 52.44952170663243 ], [1.7248548644154198, 52.39331821546265 ], [1.6921544971979525, 52.34906604138069 ], [1.6755362777923555, 52.31353509868715 ], [1.6481966265121777, 52.298353695899905 ], [1.6305062639191217, 52.268313898895364 ], [1.6224651900131866, 52.18626972213028 ], [1.6047748274201306, 52.157198950835564 ], [1.5827958920772431, 52.08969015971783 ], [1.5677858874528319, 52.098088382536304 ], [1.5870844648270745, 52.119729956722374 ], [1.5978058967016544, 52.14363259089803 ], [1.5554562407970653, 52.16495115651416 ], [1.5372298066102799, 52.156229925125736 ], [1.5897648227957193, 52.13652640235932 ], [1.5656416010779157, 52.100026433955954 ], [1.5361576634228227, 52.08775210829818 ], [1.5104262269238315, 52.06966362838147 ], [1.4621797834882244, 52.04834506276535 ], [1.4348401322080466, 52.00861500866257 ], [1.3699754693668416, 51.964685843150555 ], [1.34478010446158, 51.95693363747196 ], [1.3228011691186916, 51.93529206328589 ], [1.3131518804315707, 51.952411517492784 ], [1.2825957995890187, 51.96597787743032 ], [1.2831318711827482, 51.989880511605975 ], [1.2579365062774865, 51.99989377727415 ], [1.1882471990927197, 52.015075180061395 ], [1.211298277623066, 51.99666369157474 ], [1.2670497233708788, 51.987942460186325 ], [1.2788432984329159, 51.96048673174131 ], [1.269194009745795, 51.954349568912434 ], [1.2391740004969725, 51.9611327488812 ], [1.219875423122729, 51.95273452606273 ], [1.1871750559052616, 51.9556416031922 ], [1.1641239773749161, 51.967592920280026 ], [1.1319596817511783, 51.95757965461184 ], [1.0987232429399816, 51.954349568912434 ], [1.1196300350954118, 51.940137191835014 ], [1.1705568364996637, 51.947889397513606 ], [1.1882471990927197, 51.94046020040496 ], [1.232741141372225, 51.94046020040496 ], [1.2606168642461313, 51.947889397513606 ], [1.2815236564015606, 51.93496905471596 ], [1.256328291496299, 51.91332748052989 ], [1.232741141372225, 51.903637223431645 ], [1.2236279242788317, 51.88005759782593 ], [1.2354214993408696, 51.86164610933928 ], [1.2901008019012243, 51.86326115218899 ], [1.2364936425283277, 51.819654995246914 ], [1.2048654184983185, 51.80382757531979 ], [1.1131971759706643, 51.772818752605424 ], [1.044043940379627, 51.76958866690601 ], [1.0327864369113184, 51.778309898294424 ], [1.033322508505047, 51.80059748962037 ], [0.9936532105691036, 51.8164249095475 ], [0.9443346239460384, 51.810287746718615 ], [0.8950160373229732, 51.788000155392666 ], [0.8944799657292437, 51.766358581206596 ], [0.8612435269180478, 51.7460090413003 ], [0.8376563767939729, 51.738256835621705 ], [0.791554219733281, 51.74471700702053 ], [0.7352667023917396, 51.72953560423329 ], [0.7175763397986836, 51.7162922528657 ], [0.7636784968593746, 51.70660199576746 ], [0.7920902913270105, 51.710155090036814 ], [0.8199660142009169, 51.72113738141482 ], [0.83390387563787, 51.715000218585935 ], [0.8757174599487296, 51.725659501394 ], [0.8993046100728046, 51.74471700702053 ], [0.9309328341028138, 51.74697806701012 ], [0.9486231966958698, 51.73405772421247 ], [0.9405821227899356, 51.70240288435822 ], [0.9502314114770565, 51.68334537873169 ], [0.9357574784463747, 51.650398504597675 ], [0.9373656932275614, 51.63618612752026 ], [0.9207474738219634, 51.62681887899196 ], [0.8596353121368603, 51.621327733302955 ], [0.8649960280741507, 51.5974250991273 ], [0.8210381573883749, 51.59871713340707 ], [0.8060281527639637, 51.58611979917936 ], [0.8296153028880386, 51.55963309644417 ], [0.8483778086685518, 51.551234873625695 ], [0.8172856562322721, 51.53928355653787 ], [0.7878017185771782, 51.52216410233098 ], [0.7593899241095432, 51.527978256589925 ], [0.644134531457814, 51.54122160795752 ], [0.6066095198967867, 51.53508444512863 ], [0.544961286617955, 51.547681779356346 ], [0.5433530718367674, 51.54412868508699 ], [0.5438891434304969, 51.54154461652746 ], [0.5422809286493093, 51.53443842798875 ], [0.5379923558994779, 51.53379241084887 ], [0.533167711555917, 51.5308853337194 ], [0.522446279681338, 51.53282338513905 ], [0.515477348962861, 51.52830126515987 ], [0.5219102080876086, 51.5160269395021 ], [0.5079723466506554, 51.50504464812409 ], [0.45918983162131877, 51.5066596909738 ], [0.4404273258408056, 51.492447313896385 ], [0.4339944667160571, 51.46143849118202 ], [0.403438385873506, 51.45368628550343 ], [0.3407180094072162, 51.452394251223666 ], [0.32517193318907633, 51.4714517568502 ], [0.309089785377207, 51.47435883397967 ], [0.2812140625033006, 51.46143849118202 ], [0.24690548050464667, 51.472743791129965 ], [0.21045261213107658, 51.49018625390679 ] ] ] ] + } + }, + { + "type": "Feature", + "properties": { + "AREACD": "E12000007", + "AREANM": "London" + }, + "geometry": { + "type": "Polygon", + "coordinates": [[[0.21045261213107658, 51.49018625390679 ], [0.20294760981887094, 51.457885396912665 ], [0.1729276005700484, 51.443027002695366 ], [0.150948665227161, 51.420416402799475 ], [0.14773223566478677, 51.39296067435446 ], [0.15309295160207625, 51.37810228013716 ], [0.1370108037902078, 51.34418638029333 ], [0.08501185919849696, 51.31608463470844 ], [0.08179542963612363, 51.29185899196284 ], [0.05874435110577725, 51.28927492340331 ], [0.033012914606787014, 51.307363403320025 ], [0.014786480420002412, 51.29185899196284 ], [0.0024568337642358884, 51.32900497750609 ], [-0.03774853576543702, 51.33869523460433 ], [-0.06401604385815673, 51.31866870326797 ], [-0.08492283601358697, 51.315761626138496 ], [-0.09457212470070786, 51.29928818907149 ], [-0.1240560623558018, 51.286690854843776 ], [-0.15514821479208152, 51.30122624049114 ], [-0.16318928869801663, 51.33029701178585 ], [-0.19749787069667057, 51.343540363153444 ], [-0.21733251966464273, 51.343540363153444 ], [-0.22269323560193222, 51.35710672309098 ], [-0.26129039035041846, 51.37971732298687 ], [-0.28809397003686676, 51.3619518516401 ], [-0.31918612247314737, 51.32803595179627 ], [-0.3304436259414558, 51.348385491702565 ], [-0.3084646905985675, 51.375518211577635 ], [-0.3588554204090908, 51.412018179981004 ], [-0.41889543890673586, 51.4323677198873 ], [-0.456420450467764, 51.438181874146245 ], [-0.4585647368426793, 51.45627035406296 ], [-0.4939454620287913, 51.46273052546179 ], [-0.5057390370908292, 51.47242078256002 ], [-0.48376010174794093, 51.50795172525356 ], [-0.49555367680997886, 51.538314530828046 ], [-0.4767911710294648, 51.558018053594466 ], [-0.5003783211535389, 51.59968615911689 ], [-0.4880486744977732, 51.62681887899196 ], [-0.4569565220614926, 51.612283493344606 ], [-0.43819401628097854, 51.62003569902319 ], [-0.3995968615324923, 51.61325251905443 ], [-0.36582435112756784, 51.621327733302955 ], [-0.31650576450450174, 51.6403852389295 ], [-0.2998875450989038, 51.63586311895032 ], [-0.2543214596319414, 51.64426134176879 ], [-0.24788860050719386, 51.655243633146796 ], [-0.19106501157192302, 51.66396486453521 ], [-0.16372536029174523, 51.682376353021866 ], [-0.10582962816901631, 51.69174360155016 ], [-0.06616033023307288, 51.68399139587157 ], [-0.01094495607898871, 51.68076131017216 ], [-0.012553170860175378, 51.64619939318844 ], [0.02282755432593664, 51.641031256069375 ], [0.021755411138478564, 51.62875693041161 ], [0.06303292385560955, 51.6067923476556 ], [0.08715614557341311, 51.60453128766601 ], [0.13593866060274973, 51.62358879329255 ], [0.168639027820217, 51.621327733302955 ], [0.22385440197430118, 51.63166400754108 ], [0.2645958430977027, 51.608407390505306 ], [0.2721008454099083, 51.587734842029064 ], [0.2903272795966929, 51.56415521642335 ], [0.3128422865333098, 51.56577025927306 ], [0.3316047923138239, 51.540898599387575 ], [0.26513191469143127, 51.53217736799916 ], [0.2635236999102446, 51.51796499092175 ], [0.21045261213107658, 51.49018625390679 ] ] ] + } + }, + { + "type": "Feature", + "properties": { + "AREACD": "E12000008", + "AREANM": "South East" + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [[[[-1.2980528526222486, 50.765032014388595 ], [-1.272857487716987, 50.766001040098416 ], [-1.2401571204995196, 50.74403645734241 ], [-1.2106731828444257, 50.734992217384054 ], [-1.1613545962213605, 50.73337717453435 ], [-1.1093556516296506, 50.72142585744652 ], [-1.097026004973884, 50.69074004330209 ], [-1.07343885484981, 50.6813727947738 ], [-1.098098148161342, 50.66522236627673 ], [-1.1249017278477904, 50.663930331996966 ], [-1.1592103098464452, 50.650686980629374 ], [-1.1742203144708565, 50.62516930360401 ], [-1.1790449588144165, 50.60094366085842 ], [-1.2423014068744358, 50.582855180941706 ], [-1.308238212903099, 50.5773640352527 ], [-1.3184235731839493, 50.58931535234053 ], [-1.3891850235561733, 50.62678434645372 ], [-1.4470807556789023, 50.64358079209067 ], [-1.485141838833659, 50.66683740912644 ], [-1.5237389935821453, 50.66812944340621 ], [-1.548934358487407, 50.66360732342703 ], [-1.550006501674865, 50.6774966919345 ], [-1.5221307788009586, 50.7072134803691 ], [-1.4701318342092486, 50.709797548928634 ], [-1.4256378919297434, 50.72691700313552 ], [-1.397762169055837, 50.72885505455517 ], [-1.3473714392453138, 50.743390440202525 ], [-1.314135000434118, 50.76729307437818 ], [-1.2980528526222486, 50.765032014388595 ] ] ], [[[-0.9233388086056973, 50.83060275408668 ], [-0.9120813051373888, 50.82737266838726 ], [-0.9110091619499316, 50.80347003421161 ], [-0.9426373859799408, 50.815421351299435 ], [-0.9233388086056973, 50.83060275408668 ] ] ], [[[-0.9689048940726606, 50.83383283978609 ], [-0.9528227462607912, 50.828341694097084 ], [-0.9597916769792674, 50.79087269998389 ], [-0.9710491804475758, 50.7815054514556 ], [-1.021439910258099, 50.786350580004715 ], [-0.990883829415548, 50.8018549913619 ], [-0.9823066839158843, 50.83060275408668 ], [-0.9689048940726606, 50.83383283978609 ] ] ], [[[-1.0546763490692959, 50.83350983121615 ], [-1.0412745592260713, 50.82414258268785 ], [-1.0364499148825104, 50.78570456286484 ], [-1.0889849310679498, 50.7776293486163 ], [-1.1098917232233791, 50.79151871712378 ], [-1.0938095754115107, 50.808638171330664 ], [-1.0948817185989688, 50.82704965981732 ], [-1.0836242151306603, 50.82995673694679 ], [-1.0766552844121833, 50.83673991691556 ], [-1.0546763490692959, 50.83350983121615 ] ] ], [[[0.7443799194851319, 51.4472261141046 ], [0.791554219733281, 51.43915089985607 ], [0.8156774414510854, 51.427845599908125 ], [0.9019849680414493, 51.41654029996018 ], [0.9379017648212908, 51.389730588655055 ], [0.950767483070786, 51.373580160157985 ], [0.9084178271661969, 51.35549168024127 ], [0.8891192497919542, 51.355168671671336 ], [0.868212457636524, 51.365181937339514 ], [0.8205020857946455, 51.36905804017881 ], [0.7776163582963278, 51.36776600589904 ], [0.7685031412029355, 51.381009357266635 ], [0.7513488502036081, 51.39392970006429 ], [0.7368749171729263, 51.43204471131736 ], [0.7443799194851319, 51.4472261141046 ] ] ], [[[-0.6681687299907075, 52.1949909535187 ], [-0.6274272888673051, 52.18142459358116 ], [-0.6349322911795108, 52.168181242213564 ], [-0.6343962195857822, 52.13781843663909 ], [-0.6070565683056044, 52.13394233379979 ], [-0.5931187068686512, 52.110362708194074 ], [-0.6059844251181463, 52.09195121970742 ], [-0.6258190740861185, 52.08452202259877 ], [-0.6692408731781656, 52.048668071335285 ], [-0.6429733650854459, 52.03736277138734 ], [-0.6451176514603612, 52.01378314578163 ], [-0.66173587086596, 51.99957076870421 ], [-0.6553030117412115, 51.96048673174131 ], [-0.6815705198339312, 51.937553123275485 ], [-0.7019412403956329, 51.90912836912065 ], [-0.673529445927997, 51.90137616344206 ], [-0.6547669401474829, 51.887809803504524 ], [-0.6199222865550995, 51.88554874351494 ], [-0.6092008546805205, 51.87553547784675 ], [-0.5770365590567819, 51.86584522074852 ], [-0.5480886929954174, 51.83580542374398 ], [-0.5818612034003428, 51.8070576610192 ], [-0.5974072796184835, 51.81416384955791 ], [-0.6343962195857822, 51.818685969537086 ], [-0.6670965868032495, 51.815778892407614 ], [-0.690147665333595, 51.84129656943298 ], [-0.6922919517085111, 51.857123989360105 ], [-0.7174873166137727, 51.857123989360105 ], [-0.7448269678939505, 51.83774347516363 ], [-0.7190955313949594, 51.81610190097756 ], [-0.7094462427078376, 51.820624020956735 ], [-0.6826426630213893, 51.79704439535102 ], [-0.6762098038966418, 51.77120370975572 ], [-0.6435094366791745, 51.75408425554883 ], [-0.586149776150175, 51.75214620412918 ], [-0.5727479863069505, 51.73599577563212 ], [-0.5502329793703336, 51.73050462994311 ], [-0.5630986976198287, 51.71177013288652 ], [-0.5475526214016888, 51.70304890149811 ], [-0.5464804782142307, 51.680438301602216 ], [-0.5110997530281187, 51.67979228446233 ], [-0.5052029654970998, 51.673009104493566 ], [-0.5373672611208384, 51.642969307489025 ], [-0.5303983304023614, 51.617774639033605 ], [-0.5180686837465958, 51.60033217625678 ], [-0.5003783211535389, 51.59968615911689 ], [-0.4767911710294648, 51.558018053594466 ], [-0.49555367680997886, 51.538314530828046 ], [-0.48376010174794093, 51.50795172525356 ], [-0.5057390370908292, 51.47242078256002 ], [-0.4939454620287913, 51.46273052546179 ], [-0.4585647368426793, 51.45627035406296 ], [-0.456420450467764, 51.438181874146245 ], [-0.41889543890673586, 51.4323677198873 ], [-0.3588554204090908, 51.412018179981004 ], [-0.3084646905985675, 51.375518211577635 ], [-0.3304436259414558, 51.348385491702565 ], [-0.31918612247314737, 51.32803595179627 ], [-0.28809397003686676, 51.3619518516401 ], [-0.26129039035041846, 51.37971732298687 ], [-0.22269323560193222, 51.35710672309098 ], [-0.21733251966464273, 51.343540363153444 ], [-0.19749787069667057, 51.343540363153444 ], [-0.16318928869801663, 51.33029701178585 ], [-0.15514821479208152, 51.30122624049114 ], [-0.1240560623558018, 51.286690854843776 ], [-0.09457212470070786, 51.29928818907149 ], [-0.08492283601358697, 51.315761626138496 ], [-0.06401604385815673, 51.31866870326797 ], [-0.03774853576543702, 51.33869523460433 ], [0.0024568337642358884, 51.32900497750609 ], [0.014786480420002412, 51.29185899196284 ], [0.033012914606787014, 51.307363403320025 ], [0.05874435110577725, 51.28927492340331 ], [0.08179542963612363, 51.29185899196284 ], [0.08501185919849696, 51.31608463470844 ], [0.1370108037902078, 51.34418638029333 ], [0.15309295160207625, 51.37810228013716 ], [0.14773223566478677, 51.39296067435446 ], [0.150948665227161, 51.420416402799475 ], [0.1729276005700484, 51.443027002695366 ], [0.20294760981887094, 51.457885396912665 ], [0.22010190081819836, 51.47952697109873 ], [0.27424513178482446, 51.45368628550343 ], [0.31445050131449737, 51.465960611161194 ], [0.32838836275145056, 51.45077920837396 ], [0.38842838124909473, 51.44335001126531 ], [0.4393551826533475, 51.447549122674545 ], [0.4570455452464035, 51.454009294073366 ], [0.4618701895899644, 51.47532785968949 ], [0.48170483855793567, 51.48727917677732 ], [0.5878470141162726, 51.48404909107791 ], [0.6151866653964495, 51.47500485111955 ], [0.6457427462390015, 51.47888095395885 ], [0.703102406768001, 51.47112874828026 ], [0.7202566977673284, 51.459823448332315 ], [0.7234731273297017, 51.44335001126531 ], [0.6998859772056276, 51.433336745597124 ], [0.6779070418627393, 51.43301373702719 ], [0.6623609656445995, 51.44787213124449 ], [0.6285884552396741, 51.44108895127572 ], [0.6108980926466181, 51.417509325670004 ], [0.5454973582116835, 51.412018179981004 ], [0.5530023605238892, 51.40038987146311 ], [0.5771255822416927, 51.39069961436488 ], [0.6210834529274685, 51.38908457151517 ], [0.6575363213010386, 51.39263766578453 ], [0.6661134668007023, 51.37971732298687 ], [0.7138238386425808, 51.384885460105934 ], [0.7261534852983464, 51.39974385432323 ], [0.7626063536719165, 51.38423944296605 ], [0.7647506400468327, 51.36292087734992 ], [0.8376563767939729, 51.353876637391565 ], [0.8746453167612716, 51.35419964596151 ], [0.8944799657292437, 51.340956294593916 ], [0.9368296216338328, 51.3470934574228 ], [0.9593446285704497, 51.34515540600315 ], [1.0081271435997854, 51.35000053455227 ], [1.033322508505047, 51.36550494590946 ], [1.1662682637498323, 51.374549185867814 ], [1.1995047025610281, 51.37971732298687 ], [1.251503647152739, 51.3768102458574 ], [1.2901008019012243, 51.38262440011634 ], [1.328161885055981, 51.38197838297646 ], [1.3844494023975233, 51.39263766578453 ], [1.4418090629265237, 51.38714652009552 ], [1.4493140652387293, 51.377456262997285 ], [1.4423451345202523, 51.35000053455227 ], [1.4144694116463459, 51.32738993465638 ], [1.3769444000853177, 51.329650994645974 ], [1.3774804716790472, 51.28733687198366 ], [1.404820122959224, 51.229841346534116 ], [1.4058922661466822, 51.18688120673192 ], [1.4010676218031213, 51.165239632545855 ], [1.3823051160226072, 51.144567084069614 ], [1.3485326056176827, 51.133261784121665 ], [1.3126158088378412, 51.11452728706507 ], [1.2675857949646074, 51.10160694426742 ], [1.2263082822474773, 51.099668892847774 ], [1.2059375616857757, 51.092885712879 ], [1.1893193422801778, 51.077381301521825 ], [1.0944346701901502, 51.06769104442358 ], [1.0611982313789534, 51.05961583017505 ], [1.0070550004123273, 51.0318370931601 ], [0.9845399934757113, 51.013425604673444 ], [0.9641692729140097, 50.96820440488167 ], [0.9807874923196085, 50.914584982271414 ], [0.9373656932275614, 50.91232392228182 ], [0.7979870788580286, 50.932350453618184 ], [0.7813688594524306, 50.933319479328006 ], [0.7186484829861408, 50.9074787937327 ], [0.6693298963630756, 50.87356289388887 ], [0.5915995152723754, 50.85385937112245 ], [0.5229823512750666, 50.84836822543345 ], [0.4806326953704776, 50.83770894262538 ], [0.4125516029668983, 50.830279745516734 ], [0.3498312265006094, 50.809930205610435 ], [0.32892443434517915, 50.78473553715501 ], [0.31016192856466507, 50.77924439146601 ], [0.27156477381617883, 50.75243468016088 ], [0.2581629839729551, 50.73757628594358 ], [0.2050918961937871, 50.73951433736323 ], [0.15416509478953433, 50.75792582584988 ], [0.1375468753839364, 50.755664765860296 ], [0.10806293772884334, 50.764062988678766 ], [0.07697078529256274, 50.77956740003595 ], [0.035693272575431756, 50.78118244288566 ], [-0.09189176673206312, 50.81186825703008 ], [-0.1642614318854747, 50.82317355697803 ], [-0.22912609472667977, 50.82866470266703 ], [-0.2779086097560164, 50.82704965981732 ], [-0.32829933956653967, 50.81897444556879 ], [-0.36957685228367065, 50.80896117990061 ], [-0.44248258903081084, 50.802501008501785 ], [-0.5421919054643993, 50.8018549913619 ], [-0.5931187068686512, 50.792164734263665 ], [-0.6472619378352773, 50.7869965971446 ], [-0.6939001664896978, 50.77924439146601 ], [-0.75018768383124, 50.76373998010883 ], [-0.7641255452681932, 50.74209840592276 ], [-0.7936094829232863, 50.72336390886617 ], [-0.8322066376717725, 50.748235568751646 ], [-0.8627627185143236, 50.76180192868918 ], [-0.9018959448565385, 50.77278422006718 ], [-0.9051123744189127, 50.78215146859548 ], [-0.8777727231387349, 50.80766914562084 ], [-0.8959991573255204, 50.83221779693638 ], [-0.9313798825116324, 50.840616019754854 ], [-0.9710491804475758, 50.84223106260456 ], [-0.9898116862280899, 50.83770894262538 ], [-1.0117906215709782, 50.84449212259415 ], [-1.0246563398204733, 50.8315717797965 ], [-1.0787995707870994, 50.83738593405544 ], [-1.0868406446930337, 50.83092576265662 ], [-1.1131081527857534, 50.836093899775676 ], [-1.1522413791279682, 50.84126203689474 ], [-1.1420560188471178, 50.81703639414914 ], [-1.1238295846603332, 50.806054102771135 ], [-1.1163245823481276, 50.78796562285442 ], [-1.1425920904408464, 50.77375324577701 ], [-1.1849417463454355, 50.78796562285442 ], [-1.2138896124068, 50.80928418847055 ], [-1.2969807094347905, 50.83577089120573 ], [-1.3688143029944726, 50.883576159557045 ], [-1.4395757533666966, 50.908124810872586 ], [-1.4009785986182113, 50.87194785103916 ], [-1.3709585893693887, 50.85418237969239 ], [-1.3420107233080243, 50.84449212259415 ], [-1.3355778641832767, 50.82640364267744 ], [-1.3119907140592018, 50.81219126560002 ], [-1.3420107233080243, 50.78602757143478 ], [-1.3881128803687153, 50.78602757143478 ], [-1.415452531648893, 50.76729307437818 ], [-1.4561939727722946, 50.762124937259124 ], [-1.525347208363332, 50.748881585891525 ], [-1.531243995894351, 50.7369302688037 ], [-1.5762740097675847, 50.7165807288974 ], [-1.6470354601398087, 50.73337717453435 ], [-1.6926015456067711, 50.73725327737364 ], [-1.6818801137321922, 50.751788663021 ], [-1.7279822707928831, 50.750819637311174 ], [-1.7488890629483134, 50.77956740003595 ], [-1.7821255017595092, 50.765032014388595 ], [-1.8116094394146032, 50.808638171330664 ], [-1.803568365508668, 50.82995673694679 ], [-1.7912387188529015, 50.8370629254855 ], [-1.8078569382585004, 50.86451865393051 ], [-1.8292998020076592, 50.85547441397215 ], [-1.850742665756818, 50.85870449967157 ], [-1.8485983793819019, 50.89003633095587 ], [-1.8164340837581632, 50.90392569946335 ], [-1.8148258689769765, 50.922983205089885 ], [-1.8405573054759676, 50.93202744504824 ], [-1.8737937442871635, 50.91716905083094 ], [-1.9091744694732755, 50.945270796415834 ], [-1.9209680445353126, 50.9614212249129 ], [-1.949915910596677, 50.98241678195908 ], [-1.9279369752537896, 50.99759818474632 ], [-1.8866594625366586, 50.99953623616597 ], [-1.8737937442871635, 50.98435483337873 ], [-1.8528869521317333, 51.00502738185497 ], [-1.8357326611324067, 51.00954950183415 ], [-1.8078569382585004, 50.99210703905732 ], [-1.739775845854921, 50.97660262770014 ], [-1.7194051252932194, 50.97692563627008 ], [-1.6893851160443978, 50.95463804494413 ], [-1.6615093931704905, 50.945270796415834 ], [-1.6121908065474253, 50.97111148201114 ], [-1.6288090259530232, 50.99921322759603 ], [-1.5977168735167435, 51.007934458984444 ], [-1.6341697418903136, 51.04217336739822 ], [-1.6288090259530232, 51.08125740436112 ], [-1.637386171452687, 51.092239695739124 ], [-1.6261286679843785, 51.117434364194544 ], [-1.663117607951678, 51.13003169842225 ], [-1.6540043908582858, 51.155872384017556 ], [-1.6722308250450704, 51.17848298391345 ], [-1.6690143954826961, 51.19075730957122 ], [-1.6942097603879578, 51.20400066093881 ], [-1.6899211876381264, 51.214659943746874 ], [-1.6534683192645563, 51.220474098005816 ], [-1.6336336702965841, 51.217567020876345 ], [-1.6062940190164072, 51.25180592929012 ], [-1.5778822245487714, 51.255682032129414 ], [-1.543037570956388, 51.24534575789129 ], [-1.5344604254567251, 51.28701386341372 ], [-1.525347208363332, 51.30704039475008 ], [-1.533388282269267, 51.31608463470844 ], [-1.527491494738248, 51.33837222603439 ], [-1.4974714854894255, 51.33320408891532 ], [-1.4867500536148466, 51.349354517412394 ], [-1.4953271991145103, 51.36776600589904 ], [-1.5553672176121545, 51.395544742914 ], [-1.5457179289250336, 51.42267746278906 ], [-1.5237389935821453, 51.44141195984566 ], [-1.5821707972986028, 51.49406235674609 ], [-1.5880675848296217, 51.51344287094257 ], [-1.603077589454033, 51.51828799949168 ], [-1.6137990213286129, 51.536376479408396 ], [-1.655076534045743, 51.57642954208112 ], [-1.6765193977949018, 51.56932335354241 ], [-1.690457259231855, 51.605500313375835 ], [-1.6668701091077809, 51.6161595961839 ], [-1.6599011783893038, 51.63489409324049 ], [-1.679735827357276, 51.644907358908675 ], [-1.6990344047315187, 51.66945601022421 ], [-1.6968901183566034, 51.681084318742094 ], [-1.6481076033272668, 51.68399139587157 ], [-1.6861686864820236, 51.710155090036814 ], [-1.6867047580757522, 51.72856657852347 ], [-1.7006426195127062, 51.77055769261584 ], [-1.7194051252932194, 51.783155026843545 ], [-1.6936736887942292, 51.79413731822155 ], [-1.6802718989510046, 51.8070576610192 ], [-1.6867047580757522, 51.83451338946421 ], [-1.678663684169818, 51.84937178368151 ], [-1.6834883285133788, 51.86875229787799 ], [-1.667942252295239, 51.87618149498664 ], [-1.6577568920143877, 51.89717705203282 ], [-1.6357779566715003, 51.902022180581945 ], [-1.643282958983706, 51.92366375476801 ], [-1.619695808859631, 51.937553123275485 ], [-1.6288090259530232, 51.95305753463266 ], [-1.6625815363579486, 51.96403982601067 ], [-1.6657979659203228, 51.98761945161638 ], [-1.6298811691404813, 51.96985398026961 ], [-1.6121908065474253, 51.955318594622256 ], [-1.5918200859857246, 51.9704999974095 ], [-1.5494704300811355, 51.98083627164762 ], [-1.5290997095194347, 51.9924645801655 ], [-1.5108732753326501, 52.02153535146022 ], [-1.4974714854894255, 52.060942396993056 ], [-1.5017600582392578, 52.07160167980112 ], [-1.480317194490099, 52.093566262557125 ], [-1.4470807556789023, 52.09776537396637 ], [-1.4535136148036498, 52.1129467767536 ], [-1.4245657487422854, 52.11811491387267 ], [-1.3854325224000705, 52.09421227969701 ], [-1.3575567995261641, 52.10131846823572 ], [-1.3827521644314258, 52.127482162400966 ], [-1.3409385801205662, 52.144924625177794 ], [-1.331825363027174, 52.168504250783506 ], [-1.2766099888730897, 52.117145888162845 ], [-1.2530228387490148, 52.103902536795246 ], [-1.2873314207476687, 52.092597236847304 ], [-1.321103931152594, 52.08775210829818 ], [-1.3055578549344542, 52.07192468837106 ], [-1.3130628572466598, 52.05125213989482 ], [-1.297516781028519, 52.04285391707634 ], [-1.2782182036542764, 52.01410615435157 ], [-1.2873314207476687, 51.98955750303603 ], [-1.261063912654949, 51.98083627164762 ], [-1.2417653352807063, 51.98632741733662 ], [-1.1978074645949306, 51.97728317737826 ], [-1.1640349541900052, 51.993433605875325 ], [-1.1345510165349122, 51.997309708714624 ], [-1.119541011910501, 52.01636721434116 ], [-1.1361592313160989, 52.019920308610516 ], [-1.1222213698791457, 52.04479196849599 ], [-1.0530681342881083, 52.05965036271329 ], [-1.02626455460166, 52.07547778264042 ], [-0.9678327508852025, 52.070955662661234 ], [-0.9453177439485856, 52.076769816920184 ], [-0.9303077393241743, 52.043176925646286 ], [-0.9061845176063708, 52.02121234289028 ], [-0.8708037924202587, 52.04382294278617 ], [-0.8574020025770341, 52.06417248269247 ], [-0.8386394967965201, 52.06417248269247 ], [-0.8354230672341467, 52.079676894049655 ], [-0.8466805707024543, 52.09162821113748 ], [-0.8868859402321272, 52.114238811033374 ], [-0.8691955776390712, 52.13071224810038 ], [-0.8284541365156697, 52.13265029952002 ], [-0.8075473443602394, 52.15687594226562 ], [-0.7877126953922682, 52.15429187370609 ], [-0.7346416076130993, 52.17334937933263 ], [-0.7019412403956329, 52.19337591066899 ], [-0.6681687299907075, 52.1949909535187 ] ] ] ] + } + }, + { + "type": "Feature", + "properties": { + "AREACD": "E12000009", + "AREANM": "South West" + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [[[[-6.2996008221135495, 49.937484058199004 ], [-6.2760136719894755, 49.922625663981705 ], [-6.2878072470515125, 49.91002832975399 ], [-6.313002611956774, 49.913258415453406 ], [-6.313538683550503, 49.92747079253082 ], [-6.2996008221135495, 49.937484058199004 ] ] ], [[[-6.348383337142886, 49.96784686377348 ], [-6.321043685862708, 49.95363448669607 ], [-6.339270120049494, 49.94717431529724 ], [-6.348383337142886, 49.96784686377348 ] ] ], [[[-1.6657979659203228, 51.98761945161638 ], [-1.6625815363579486, 51.96403982601067 ], [-1.6288090259530232, 51.95305753463266 ], [-1.619695808859631, 51.937553123275485 ], [-1.643282958983706, 51.92366375476801 ], [-1.6357779566715003, 51.902022180581945 ], [-1.6577568920143877, 51.89717705203282 ], [-1.667942252295239, 51.87618149498664 ], [-1.6834883285133788, 51.86875229787799 ], [-1.678663684169818, 51.84937178368151 ], [-1.6867047580757522, 51.83451338946421 ], [-1.6802718989510046, 51.8070576610192 ], [-1.6936736887942292, 51.79413731822155 ], [-1.7194051252932194, 51.783155026843545 ], [-1.7006426195127062, 51.77055769261584 ], [-1.6867047580757522, 51.72856657852347 ], [-1.6861686864820236, 51.710155090036814 ], [-1.6481076033272668, 51.68399139587157 ], [-1.6968901183566034, 51.681084318742094 ], [-1.6990344047315187, 51.66945601022421 ], [-1.679735827357276, 51.644907358908675 ], [-1.6599011783893038, 51.63489409324049 ], [-1.6668701091077809, 51.6161595961839 ], [-1.690457259231855, 51.605500313375835 ], [-1.6765193977949018, 51.56932335354241 ], [-1.655076534045743, 51.57642954208112 ], [-1.6137990213286129, 51.536376479408396 ], [-1.603077589454033, 51.51828799949168 ], [-1.5880675848296217, 51.51344287094257 ], [-1.5821707972986028, 51.49406235674609 ], [-1.5237389935821453, 51.44141195984566 ], [-1.5457179289250336, 51.42267746278906 ], [-1.5553672176121545, 51.395544742914 ], [-1.4953271991145103, 51.36776600589904 ], [-1.4867500536148466, 51.349354517412394 ], [-1.4974714854894255, 51.33320408891532 ], [-1.527491494738248, 51.33837222603439 ], [-1.533388282269267, 51.31608463470844 ], [-1.525347208363332, 51.30704039475008 ], [-1.5344604254567251, 51.28701386341372 ], [-1.543037570956388, 51.24534575789129 ], [-1.5778822245487714, 51.255682032129414 ], [-1.6062940190164072, 51.25180592929012 ], [-1.6336336702965841, 51.217567020876345 ], [-1.6534683192645563, 51.220474098005816 ], [-1.6899211876381264, 51.214659943746874 ], [-1.6942097603879578, 51.20400066093881 ], [-1.6690143954826961, 51.19075730957122 ], [-1.6722308250450704, 51.17848298391345 ], [-1.6540043908582858, 51.155872384017556 ], [-1.663117607951678, 51.13003169842225 ], [-1.6261286679843785, 51.117434364194544 ], [-1.637386171452687, 51.092239695739124 ], [-1.6288090259530232, 51.08125740436112 ], [-1.6341697418903136, 51.04217336739822 ], [-1.5977168735167435, 51.007934458984444 ], [-1.6288090259530232, 50.99921322759603 ], [-1.6121908065474253, 50.97111148201114 ], [-1.6615093931704905, 50.945270796415834 ], [-1.6893851160443978, 50.95463804494413 ], [-1.7194051252932194, 50.97692563627008 ], [-1.739775845854921, 50.97660262770014 ], [-1.8078569382585004, 50.99210703905732 ], [-1.8357326611324067, 51.00954950183415 ], [-1.8528869521317333, 51.00502738185497 ], [-1.8737937442871635, 50.98435483337873 ], [-1.8866594625366586, 50.99953623616597 ], [-1.9279369752537896, 50.99759818474632 ], [-1.949915910596677, 50.98241678195908 ], [-1.9209680445353126, 50.9614212249129 ], [-1.9091744694732755, 50.945270796415834 ], [-1.8737937442871635, 50.91716905083094 ], [-1.8405573054759676, 50.93202744504824 ], [-1.8148258689769765, 50.922983205089885 ], [-1.8164340837581632, 50.90392569946335 ], [-1.8485983793819019, 50.89003633095587 ], [-1.850742665756818, 50.85870449967157 ], [-1.8292998020076592, 50.85547441397215 ], [-1.8078569382585004, 50.86451865393051 ], [-1.7912387188529015, 50.8370629254855 ], [-1.803568365508668, 50.82995673694679 ], [-1.8116094394146032, 50.808638171330664 ], [-1.7821255017595092, 50.765032014388595 ], [-1.7488890629483134, 50.77956740003595 ], [-1.7279822707928831, 50.750819637311174 ], [-1.6818801137321922, 50.751788663021 ], [-1.6926015456067711, 50.73725327737364 ], [-1.7247658412305098, 50.734023191674225 ], [-1.7692597835100141, 50.7165807288974 ], [-1.803568365508668, 50.7204568317367 ], [-1.8577115964752942, 50.71851878031705 ], [-1.8882676773178462, 50.713350643197984 ], [-1.932225548003621, 50.6962311889911 ], [-1.9584930560963407, 50.71561170318758 ], [-1.972430917533294, 50.71044356606851 ], [-2.016924859812799, 50.7110895832084 ], [-2.0399759383431446, 50.719164797456926 ], [-2.06302701687349, 50.7165807288974 ], [-2.078573093091631, 50.69332411186163 ], [-2.065707374842135, 50.6868639404628 ], [-2.0415841531243313, 50.70624445465928 ], [-2.018533074593986, 50.6813727947738 ], [-1.98529663578279, 50.67006749482585 ], [-1.9424109082844723, 50.670390503395794 ], [-1.9525962685653226, 50.65617812631838 ], [-1.933297691191079, 50.63098345786295 ], [-1.9531323401590512, 50.62258523504448 ], [-1.949915910596677, 50.60481976369771 ], [-1.9595651992837988, 50.590930395190234 ], [-2.02657414849992, 50.58899234377059 ], [-2.0576663009362006, 50.577041026682764 ], [-2.0635630884672196, 50.59319145517983 ], [-2.1048406011843497, 50.59674454944918 ], [-2.1343245388394436, 50.612571969376305 ], [-2.1825709822750508, 50.615479046505776 ], [-2.19865313008692, 50.622908243614425 ], [-2.2393945712103216, 50.616125063645654 ], [-2.273167081615247, 50.62226222647454 ], [-2.4195146267032563, 50.63647460355196 ], [-2.4522149939207236, 50.61418701222601 ], [-2.4500707075458075, 50.601266669428355 ], [-2.4725857144824244, 50.58447022379141 ], [-2.498317150981415, 50.59642154087924 ], [-2.5176157283556577, 50.61774010649536 ], [-2.5315535897926114, 50.61741709792542 ], [-2.460256067826658, 50.57090386385388 ], [-2.4334524881402095, 50.567673778154465 ], [-2.4157621255471535, 50.54926228966781 ], [-2.442565705233602, 50.52051452694303 ], [-2.4591839246392, 50.51437736411415 ], [-2.4479264211708918, 50.5570144953464 ], [-2.497781079387686, 50.59319145517983 ], [-2.5449553796358355, 50.61935514934507 ], [-2.626974333476368, 50.65973122058773 ], [-2.740085439753181, 50.70269136038992 ], [-2.793692599126078, 50.718195771747105 ], [-2.810310818531676, 50.71787276317716 ], [-2.8944740587471247, 50.73273115739446 ], [-2.927710497558321, 50.72917806312511 ], [-2.9534419340573117, 50.71431966890781 ], [-2.983998014899863, 50.70462941180957 ], [-3.0284919571793676, 50.69881525755063 ], [-3.079954830177349, 50.702368351819985 ], [-3.096036977989218, 50.68524889761309 ], [-3.1265930588317694, 50.6868639404628 ], [-3.1930659364541616, 50.68427987190327 ], [-3.231127019608919, 50.67943474335415 ], [-3.26704381638876, 50.66974448625591 ], [-3.30349668476233, 50.62969142358319 ], [-3.3490627702292923, 50.620001166484954 ], [-3.3855156386028624, 50.60675781511736 ], [-3.4160717194454135, 50.61580205507572 ], [-3.4160717194454135, 50.62969142358319 ], [-3.432689938851012, 50.646164860650195 ], [-3.445555657100507, 50.67006749482585 ], [-3.468606735630853, 50.68104978620386 ], [-3.446091728694236, 50.624846295034075 ], [-3.4428752991318623, 50.595775523739356 ], [-3.469142807224582, 50.574133949553286 ], [-3.4964824585047594, 50.54183309255916 ], [-3.5131006779103573, 50.50662515843556 ], [-3.5147088926915444, 50.48175349855008 ], [-3.4879053130050957, 50.46075794150389 ], [-3.5157810358790025, 50.45397476153513 ], [-3.5334713984720585, 50.463019001493485 ], [-3.557058548596133, 50.44266946158718 ], [-3.555986405408675, 50.408753561743346 ], [-3.4879053130050957, 50.39809427893528 ], [-3.516317107472731, 50.37096155906021 ], [-3.5152449642852734, 50.350612019153914 ], [-3.5361517564407032, 50.336722650646436 ], [-3.5650996225020677, 50.33607663350656 ], [-3.5715324816268152, 50.32670938497826 ], [-3.612273922750217, 50.31863417072972 ], [-3.6406857172178526, 50.29214746799454 ], [-3.6589121514046377, 50.242404148223585 ], [-3.649798934311245, 50.221731599747336 ], [-3.6814271583412546, 50.22237761688722 ], [-3.7168078835273666, 50.20558117125027 ], [-3.750044322338563, 50.21914753118781 ], [-3.7704150429002636, 50.22205460831728 ], [-3.7881054054933196, 50.20978028265951 ], [-3.8229500590857026, 50.21720947976816 ], [-3.8599389990530018, 50.23432893397505 ], [-3.860475070646731, 50.25435546531141 ], [-3.909793657269796, 50.29440852798413 ], [-3.9451743824559085, 50.297638613683546 ], [-3.9939568974852446, 50.30700586221184 ], [-4.020224405577965, 50.29473153655407 ], [-4.0443476272957675, 50.2934395022743 ], [-4.08508906841917, 50.317988153589845 ], [-4.111892648105618, 50.316050102170195 ], [-4.128510867511217, 50.34285981347532 ], [-4.120469793605282, 50.36127130196198 ], [-4.157458733572581, 50.360948293392035 ], [-4.185334456446487, 50.36708545622092 ], [-4.191767315571235, 50.390988090396576 ], [-4.205169105414459, 50.39938631321505 ], [-4.185334456446487, 50.43427123876871 ], [-4.211065892945478, 50.42393496453059 ], [-4.205705177008188, 50.407784536033525 ], [-4.2116019645392075, 50.39260313324628 ], [-4.201952675852086, 50.38711198755728 ], [-4.193375530352422, 50.353519096283385 ], [-4.170860523415805, 50.34382883918515 ], [-4.22017911003887, 50.32606336783838 ], [-4.244838403350403, 50.34221379633544 ], [-4.28289948650516, 50.3567491819828 ], [-4.308094851410422, 50.36127130196198 ], [-4.367598798314337, 50.360302276252156 ], [-4.3874334472823096, 50.36450138766139 ], [-4.436215962311646, 50.360948293392035 ], [-4.452298110123515, 50.34479786489497 ], [-4.497328123996748, 50.33736866778632 ], [-4.535389207151505, 50.32477133355861 ], [-4.577202791462366, 50.33155451352738 ], [-4.618480304179496, 50.32380230784879 ], [-4.6399231679286554, 50.328324427827965 ], [-4.682272823833244, 50.32218726499908 ], [-4.681736752239515, 50.33833769349614 ], [-4.705859973957319, 50.340598753485736 ], [-4.752498202611739, 50.33220053066726 ], [-4.766972135642421, 50.320895230719316 ], [-4.754106417392926, 50.30603683650202 ], [-4.780909997079374, 50.28988640800495 ], [-4.787878927797851, 50.24208113965364 ], [-4.802352860828533, 50.218824522617865 ], [-4.81950715182786, 50.23174486541552 ], [-4.86132073613872, 50.23497495111493 ], [-4.910639322761785, 50.20525816268033 ], [-4.957813623009935, 50.20235108555086 ], [-4.974431842415533, 50.18006349422491 ], [-4.972287556040617, 50.16455908286773 ], [-5.008740424414187, 50.139364414412306 ], [-5.028575073382159, 50.159067937178726 ], [-5.019997927882495, 50.17651039995556 ], [-5.031255431350804, 50.200413034131216 ], [-5.054842581474879, 50.19782896557168 ], [-5.056986867849794, 50.147762637230784 ], [-5.093439736223365, 50.126121063044714 ], [-5.08539866231743, 50.10932461740777 ], [-5.1046972396916726, 50.09446622319047 ], [-5.074677230442851, 50.08154588039282 ], [-5.05966722581844, 50.05473616908769 ], [-5.06985258609929, 50.034709637751334 ], [-5.096656165785738, 50.0266344235028 ], [-5.101480810129299, 50.00434683217685 ], [-5.156696184283383, 50.007576917876264 ], [-5.186180121938476, 49.97947517229137 ], [-5.185644050344748, 49.96300173522437 ], [-5.2156640595935695, 49.96041766666483 ], [-5.218880489155944, 49.971076949472895 ], [-5.245684068842392, 49.97559906945207 ], [-5.245147997248663, 49.98561233512025 ], [-5.268199075779009, 50.00596187502656 ], [-5.256405500716972, 50.02372734637333 ], [-5.290178011121897, 50.06604146903564 ], [-5.328239094276654, 50.088975077501466 ], [-5.357723031931747, 50.08929808607141 ], [-5.391495542336672, 50.10351046314882 ], [-5.419907336804308, 50.09898834316965 ], [-5.462793064302626, 50.12321398591524 ], [-5.496029503113822, 50.12870513160425 ], [-5.523905225987728, 50.126121063044714 ], [-5.547492376111803, 50.108032583128 ], [-5.533018443081121, 50.08865206893152 ], [-5.55446130683028, 50.059904306206754 ], [-5.575904170579439, 50.05215210052816 ], [-5.619325969671485, 50.05118307481834 ], [-5.654170623263869, 50.03793972345074 ], [-5.68472670410642, 50.03793972345074 ], [-5.695448135981, 50.05505917765763 ], [-5.714746713355242, 50.06313439190617 ], [-5.708849925824223, 50.077669777553524 ], [-5.692767778012354, 50.08283791467258 ], [-5.7050974246681205, 50.13419627729325 ], [-5.671860985856925, 50.16391306572785 ], [-5.628975258358607, 50.16778916856714 ], [-5.615037396921654, 50.18167853707462 ], [-5.591450246797579, 50.18910773418327 ], [-5.556069521611467, 50.208488248379744 ], [-5.498173789488738, 50.219793548327694 ], [-5.436525556209906, 50.1933068455925 ], [-5.401144831023794, 50.21688647119822 ], [-5.391495542336672, 50.22883778828605 ], [-5.33574409658886, 50.23982007966405 ], [-5.277848364466131, 50.274058988077826 ], [-5.250508713185953, 50.28181119375642 ], [-5.233890493780355, 50.30474480222225 ], [-5.235498708561542, 50.31831116215979 ], [-5.200654054969159, 50.32057222214937 ], [-5.1813554775949155, 50.340598753485736 ], [-5.155624041095925, 50.34770494202444 ], [-5.146510824002533, 50.375160670469455 ], [-5.148655110377448, 50.40067834749482 ], [-5.113810456785066, 50.408753561743346 ], [-5.088079020286075, 50.42102788740112 ], [-5.060203297412168, 50.42425797310053 ], [-5.042512934819112, 50.44428450443689 ], [-5.0398325768504675, 50.474001292871485 ], [-5.028575073382159, 50.50953223556503 ], [-5.030183288163346, 50.520191518373096 ], [-5.015173283538935, 50.54409415254875 ], [-4.9808647015402805, 50.54377114397881 ], [-4.972287556040617, 50.55766051248628 ], [-4.948164334322813, 50.555399452496694 ], [-4.90849503638687, 50.58447022379141 ], [-4.881155385106692, 50.58317818951164 ], [-4.8698978816383836, 50.59545251516941 ], [-4.82486786776515, 50.594806498029534 ], [-4.795920001703785, 50.598359592298884 ], [-4.770188565204795, 50.62226222647454 ], [-4.766972135642421, 50.656501134888316 ], [-4.7578589185490285, 50.67168253767556 ], [-4.732127482050038, 50.67297457195532 ], [-4.679592465864599, 50.70559843751939 ], [-4.654933172553066, 50.715288694617634 ], [-4.653861029365608, 50.739837345933175 ], [-4.617944232585767, 50.75308069730077 ], [-4.562192786837954, 50.7815054514556 ], [-4.553615641338291, 50.83221779693638 ], [-4.55897635727558, 50.87582395387846 ], [-4.566481359587786, 50.886483236686516 ], [-4.565409216400328, 50.910708879432114 ], [-4.546110639026085, 50.928474350778885 ], [-4.549327068588459, 50.93913363358695 ], [-4.53324492077659, 50.9652973277522 ], [-4.526812061651842, 51.01891675036245 ], [-4.4640916851855525, 51.02053179321216 ], [-4.425494530437066, 51.013748613243386 ], [-4.375103800626543, 50.990168987637674 ], [-4.345083791377721, 50.98887695335791 ], [-4.3038062786605895, 50.99727517617638 ], [-4.263064837537188, 51.03377514457975 ], [-4.238405544225655, 51.04152735025834 ], [-4.221787324820058, 51.06478396729411 ], [-4.179437668915469, 51.050248581646755 ], [-4.1810458836966555, 51.06639901014382 ], [-4.214818394101581, 51.07512024153223 ], [-4.221787324820058, 51.08448749006053 ], [-4.2223233964137865, 51.11775737276449 ], [-4.2389416158193844, 51.133261784121665 ], [-4.212138036132936, 51.16007149542679 ], [-4.214818394101581, 51.18978828386139 ], [-4.201416604258356, 51.200770575239396 ], [-4.177829454134281, 51.19754048953998 ], [-4.135479798229693, 51.20916879805787 ], [-4.097954786668665, 51.2117528666174 ], [-4.080264424075609, 51.21821303801622 ], [-4.050780486420516, 51.20755375520816 ], [-4.028265479483899, 51.21627498659658 ], [-3.947318668830824, 51.22176613228558 ], [-3.9387415233311605, 51.22887232082429 ], [-3.9038968697387775, 51.229841346534116 ], [-3.89210329467674, 51.22370418370523 ], [-3.8545782831157123, 51.23436346651329 ], [-3.8063318396801047, 51.23274842366359 ], [-3.785425047524675, 51.24631478360112 ], [-3.7698789713065346, 51.2375935522127 ], [-3.6406857172178526, 51.223058166565345 ], [-3.601552490875638, 51.220474098005816 ], [-3.578501412345292, 51.23113338081388 ], [-3.5334713984720585, 51.230487363673994 ], [-3.484152811848993, 51.21918206372605 ], [-3.472895308380685, 51.20949180662781 ], [-3.4418031559444042, 51.20658472949834 ], [-3.4117831466955817, 51.18397412960245 ], [-3.3361970519797968, 51.18332811246257 ], [-3.275620961888423, 51.17977501819321 ], [-3.256858456107909, 51.18785023244175 ], [-3.1802002182046665, 51.199801549529575 ], [-3.1560769964868625, 51.208199772348046 ], [-3.1255209156443113, 51.210783840907574 ], [-3.076202329021246, 51.20141659237928 ], [-3.0242033844295357, 51.21304490089717 ], [-2.999544091118003, 51.23694753507282 ], [-3.021523026460891, 51.26504928065771 ], [-3.011873737773769, 51.30704039475008 ], [-2.9925751603995265, 51.32060675468762 ], [-2.9829258717124048, 51.346447440282915 ], [-2.962555151150704, 51.3823013915464 ], [-2.940040144214087, 51.398128811473526 ], [-2.913236564527639, 51.395867751483934 ], [-2.895546201934583, 51.40685004286194 ], [-2.8794640541227134, 51.43139869417748 ], [-2.8489079732801623, 51.45562433692307 ], [-2.7995893866570967, 51.48534112535768 ], [-2.7733218785643774, 51.49470837388597 ], [-2.7336525806284335, 51.49438536531603 ], [-2.700416141817237, 51.51731897378186 ], [-2.672540418943331, 51.545743727936696 ], [-2.663963273443667, 51.57319945638171 ], [-2.6425204096945083, 51.58708882488918 ], [-2.628046476663826, 51.605500313375835 ], [-2.5990986106024616, 51.617774639033605 ], [-2.5792639616344895, 51.63489409324049 ], [-2.5583571694790597, 51.66654893309474 ], [-2.497245007793957, 51.693358644399865 ], [-2.4816989315758167, 51.725659501394 ], [-2.4516789223269946, 51.73922586133153 ], [-2.4323803449527515, 51.736641792772 ], [-2.382525686735957, 51.75828336695807 ], [-2.3841339015171443, 51.77507981259502 ], [-2.425947485828004, 51.76409752121701 ], [-2.4709774997012373, 51.74439399845059 ], [-2.498317150981415, 51.72404445854429 ], [-2.5052860816998916, 51.71047809860676 ], [-2.5969543242275455, 51.678500250182566 ], [-2.6253661186951813, 51.66041177026585 ], [-2.623757903913994, 51.65395159886703 ], [-2.6548500563502744, 51.6216507418729 ], [-2.6698600609746856, 51.64684541032832 ], [-2.6580664859126486, 51.67656219876292 ], [-2.672004347349602, 51.68173033588198 ], [-2.668787917787228, 51.70724801290734 ], [-2.6789732780680784, 51.71403119287611 ], [-2.680045421255536, 51.72469047568417 ], [-2.685942208786555, 51.72856657852347 ], [-2.685406137192826, 51.731796664222884 ], [-2.6752207769119756, 51.73438073278241 ], [-2.6709322041621437, 51.73696480134194 ], [-2.6693239893809566, 51.74277895560088 ], [-2.662891130256209, 51.75408425554883 ], [-2.680581492849265, 51.76894264976613 ], [-2.6714682757558728, 51.79510634393137 ], [-2.650561483600443, 51.826115166645735 ], [-2.6355514789760317, 51.840973560863034 ], [-2.604459326539751, 51.85453992080057 ], [-2.5615735990414334, 51.86423017789881 ], [-2.5224403726992186, 51.86487619503869 ], [-2.5085025112622654, 51.885225734944996 ], [-2.494028578231583, 51.88005759782593 ], [-2.440421418858686, 51.90234518915188 ], [-2.448462492764621, 51.918818626218886 ], [-2.466152855357677, 51.92786286617724 ], [-2.4645446405764897, 51.948535414653485 ], [-2.4902760770754804, 51.95499558605231 ], [-2.496708936200228, 51.9759911430985 ], [-2.4709774997012373, 51.994725640155096 ], [-2.4913482202629385, 52.00635394867298 ], [-2.4736578576698824, 52.02379641144981 ], [-2.4339885597339386, 52.01216810293192 ], [-2.4355967745151252, 51.9979557258545 ], [-2.402896407297658, 51.99601767443486 ], [-2.3927110470168076, 52.0128141200718 ], [-2.3525056774871347, 52.01346013721169 ], [-2.329454598956789, 52.006676957242924 ], [-2.3128363795511913, 51.97663716023838 ], [-2.2511881462723595, 51.9666238945702 ], [-2.2206320654298075, 51.995371657294974 ], [-2.1852513402436955, 51.99052652874585 ], [-2.1638084764945367, 51.997309708714624 ], [-2.1841791970562374, 52.01378314578163 ], [-2.1804266959001346, 52.04156188279658 ], [-2.16220026171335, 52.050606122754935 ], [-2.1182423910275743, 52.042207899936464 ], [-2.139685254776733, 52.02767251428911 ], [-2.1509427582450416, 52.006676957242924 ], [-2.1118095319028267, 52.01475217149145 ], [-2.0517695134051817, 52.00861500866257 ], [-1.9842244925953318, 52.035747728537636 ], [-1.9515241253778646, 52.037685779957286 ], [-1.9311534048161638, 52.03025658284864 ], [-1.9134630422231078, 52.04446895992605 ], [-1.8737937442871635, 52.024442428589694 ], [-1.8512787373505466, 52.008292000092624 ], [-1.8351965895386773, 52.00926102580245 ], [-1.826083372445285, 52.02928755713881 ], [-1.8346605179449487, 52.043176925646286 ], [-1.8571755248815656, 52.05222116560464 ], [-1.8689690999436026, 52.07386273979071 ], [-1.8464540930069857, 52.07935388547971 ], [-1.831980159976304, 52.07321672265083 ], [-1.80249622232121, 52.09679634825654 ], [-1.7676515687288274, 52.11262376818367 ], [-1.7547858504793323, 52.099380416816075 ], [-1.7279822707928831, 52.08839812543807 ], [-1.7306626287615279, 52.07353973122077 ], [-1.6899211876381264, 52.053190191314464 ], [-1.6947458319816873, 52.04059285708676 ], [-1.6566847488269305, 52.03219463426828 ], [-1.626664739578108, 52.03800878852723 ], [-1.6422108157962478, 52.01023005151227 ], [-1.6657979659203228, 51.997309708714624 ], [-1.6657979659203228, 51.98761945161638 ] ] ] ] + } + } + ] +}` diff --git a/encoding/topojson/geometry.go b/encoding/topojson/geometry.go new file mode 100644 index 0000000..12d9d58 --- /dev/null +++ b/encoding/topojson/geometry.go @@ -0,0 +1,237 @@ +package topojson + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/paulmach/orb/geojson" +) + +type Geometry struct { + ID string `json:"id,omitempty"` + Type string `json:"type"` + Properties map[string]interface{} `json:"properties"` + BBox []float64 `json:"bbox,omitempty"` + + Point []float64 + MultiPoint [][]float64 + LineString []int + MultiLineString [][]int + Polygon [][]int + MultiPolygon [][][]int + Geometries []*Geometry +} + +// MarshalJSON converts the geometry object into the correct JSON. +// This fulfills the json.Marshaler interface. +func (g *Geometry) MarshalJSON() ([]byte, error) { + // defining a struct here lets us define the order of the JSON elements. + type geometry struct { + ID string `json:"id,omitempty"` + Type string `json:"type"` + Properties map[string]interface{} `json:"properties"` + BBox []float64 `json:"bbox,omitempty"` + Coordinates interface{} `json:"coordinates,omitempty"` + Arcs interface{} `json:"arcs,omitempty"` + Geometries interface{} `json:"geometries,omitempty"` + } + + geo := &geometry{ + ID: g.ID, + Type: g.Type, + Properties: g.Properties, + BBox: g.BBox, + } + + switch g.Type { + case geojson.TypePoint: + geo.Coordinates = g.Point + case geojson.TypeMultiPoint: + geo.Coordinates = g.MultiPoint + case geojson.TypeLineString: + geo.Arcs = g.LineString + case geojson.TypeMultiLineString: + geo.Arcs = g.MultiLineString + case geojson.TypePolygon: + geo.Arcs = g.Polygon + case geojson.TypeMultiPolygon: + geo.Arcs = g.MultiPolygon + default: + geo.Geometries = g.Geometries + } + + return json.Marshal(geo) +} + +// UnmarshalJSON decodes the data into a TopoJSON geometry. +// This fulfills the json.Unmarshaler interface. +func (g *Geometry) UnmarshalJSON(data []byte) error { + var object map[string]interface{} + err := json.Unmarshal(data, &object) + if err != nil { + return err + } + + return decodeGeometry(g, object) +} + +func decodeGeometry(g *Geometry, object map[string]interface{}) error { + t, ok := object["type"] + if !ok { + return errors.New("type property not defined") + } + + if s, ok := t.(string); ok { + g.Type = string(s) + } else { + return errors.New("type property not string") + } + + if p, ok := object["properties"].(map[string]interface{}); ok { + g.Properties = p + } + + if id, ok := object["id"].(string); ok { + g.ID = id + } + + var err error + switch g.Type { + case geojson.TypePoint: + g.Point, err = decodePosition(object["coordinates"]) + case geojson.TypeMultiPoint: + g.MultiPoint, err = decodePositionSet(object["coordinates"]) + case geojson.TypeLineString: + g.LineString, err = decodeArcs(object["arcs"]) + case geojson.TypeMultiLineString: + g.MultiLineString, err = decodeArcsSet(object["arcs"]) + case geojson.TypePolygon: + g.Polygon, err = decodeArcsSet(object["arcs"]) + case geojson.TypeMultiPolygon: + g.MultiPolygon, err = decodePolygonArcs(object["arcs"]) + default: + g.Geometries, err = decodeGeometries(object["geometries"]) + } + + return err +} + +func decodePosition(data interface{}) ([]float64, error) { + coords, ok := data.([]interface{}) + if !ok { + return nil, fmt.Errorf("not a valid position, got %v", data) + } + + result := make([]float64, 0, len(coords)) + for _, coord := range coords { + if f, ok := coord.(float64); ok { + result = append(result, f) + } else { + return nil, fmt.Errorf("not a valid coordinate, got %v", coord) + } + } + + return result, nil +} + +func decodePositionSet(data interface{}) ([][]float64, error) { + points, ok := data.([]interface{}) + if !ok { + return nil, fmt.Errorf("not a valid set of positions, got %v", data) + } + + result := make([][]float64, 0, len(points)) + for _, point := range points { + if p, err := decodePosition(point); err == nil { + result = append(result, p) + } else { + return nil, err + } + } + + return result, nil +} + +func decodeArcs(data interface{}) ([]int, error) { + arcs, ok := data.([]interface{}) + if !ok { + return nil, fmt.Errorf("not a valid set of arcs, got %v", data) + } + + result := make([]int, 0, len(arcs)) + for _, arc := range arcs { + if i, ok := arc.(int); ok { + result = append(result, i) + } else if i, ok := arc.(float64); ok { + result = append(result, int(i)) + } else { + return nil, fmt.Errorf("not a valid arc index, got %#v", arc) + } + } + + return result, nil +} + +func decodeArcsSet(data interface{}) ([][]int, error) { + sets, ok := data.([]interface{}) + if !ok { + return nil, fmt.Errorf("not a valid set of arcs, got %v", data) + } + + result := make([][]int, 0, len(sets)) + for _, arcs := range sets { + if s, err := decodeArcs(arcs); err == nil { + result = append(result, s) + } else { + return nil, err + } + } + + return result, nil +} + +func decodePolygonArcs(data interface{}) ([][][]int, error) { + rings, ok := data.([]interface{}) + if !ok { + return nil, fmt.Errorf("not a valid set of rings, got %v", data) + } + + result := make([][][]int, 0, len(rings)) + for _, sets := range rings { + if s, err := decodeArcsSet(sets); err == nil { + result = append(result, s) + } else { + return nil, err + } + } + + return result, nil +} + +func decodeGeometries(data interface{}) ([]*Geometry, error) { + if vs, ok := data.([]interface{}); ok { + geometries := make([]*Geometry, 0, len(vs)) + for _, v := range vs { + g := &Geometry{} + + vmap, ok := v.(map[string]interface{}) + if !ok { + break + } + + err := decodeGeometry(g, vmap) + if err != nil { + return nil, err + } + + geometries = append(geometries, g) + } + + if len(geometries) == len(vs) { + return geometries, nil + } + } + + return nil, fmt.Errorf("not a valid set of geometries, got %v", data) +} diff --git a/encoding/topojson/join.go b/encoding/topojson/join.go new file mode 100644 index 0000000..bec2848 --- /dev/null +++ b/encoding/topojson/join.go @@ -0,0 +1,114 @@ +package topojson + +type junctionMap map[point]bool + +func (j junctionMap) Has(c []float64) bool { + _, ok := j[point{c[0], c[1]}] + return ok +} + +func (t *Topology) join() junctionMap { + n := len(t.coordinates) + visited := make([]int, n) + left := make([]int, n) + right := make([]int, n) + junctions := make([]bool, n) + indexes := make([]int, n) + + junctionCount := 0 + + indexByPoint := make(map[point]int) + for i, c := range t.coordinates { + p := point{c[0], c[1]} + if v, ok := indexByPoint[p]; !ok { + indexByPoint[p] = i + indexes[i] = i + } else { + indexes[i] = v + } + } + indexByPoint = nil + + sequence := func(i, previousIndex, currentIndex, nextIndex int) { + if visited[currentIndex] == i { + return // Ignore self-intersection + } + visited[currentIndex] = i + leftIndex := left[currentIndex] + if leftIndex >= 0 { + rightIndex := right[currentIndex] + if (leftIndex != previousIndex || rightIndex != nextIndex) && + (leftIndex != nextIndex || rightIndex != previousIndex) { + junctionCount += 1 + junctions[currentIndex] = true + } + } else { + left[currentIndex] = previousIndex + right[currentIndex] = nextIndex + } + } + + for i := 0; i < n; i++ { + visited[i] = -1 + left[i] = -1 + right[i] = -1 + } + + for i, l := range t.lines { + start := l.Start + end := l.End + + previous := 0 + current := indexes[start] + start += 1 + next := indexes[start] + + junctionCount += 1 + junctions[current] = true + + start += 1 + for start <= end { + previous = current + current = next + next = indexes[start] + sequence(i, previous, current, next) + start += 1 + } + + junctionCount += 1 + junctions[next] = true + } + + for i := 0; i < n; i++ { + visited[i] = -1 + } + + for i, r := range t.rings { + start := r.Start + 1 + end := r.End + + previous := indexes[(end-1)%n] + current := indexes[start-1] + next := indexes[start%n] + + sequence(i, previous, current, next) + + start += 1 + for start <= end { + previous = current + current = next + next = indexes[start] + sequence(i, previous, current, next) + start += 1 + } + } + + result := junctionMap{} + for k, v := range junctions { + if v { + c := t.coordinates[k] + result[point{c[0], c[1]}] = true + } + } + return result +} diff --git a/encoding/topojson/join_test.go b/encoding/topojson/join_test.go new file mode 100644 index 0000000..dda22af --- /dev/null +++ b/encoding/topojson/join_test.go @@ -0,0 +1,912 @@ +package topojson + +import ( + "testing" + + "github.com/cheekybits/is" + orb "github.com/paulmach/orb" + "github.com/paulmach/orb/geojson" +) + +// See https://github.com/mbostock/topojson/blob/master/test/topology/join-test.js + +// join the returned hashmap has true for junction points +func TestHasJunctions(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("cba", orb.LineString{ + orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("ab", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.True(junctions.Has([]float64{2, 0})) + is.True(junctions.Has([]float64{0, 0})) +} + +// join the returned hashmap has undefined for non-junction points +func TestNonJunctions(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("cba", orb.LineString{ + orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("ab", orb.LineString{ + orb.Point{0, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.False(junctions.Has([]float64{1, 0})) +} + +// join exact duplicate lines ABC & ABC have junctions at their end points +func TestJoinDuplicate(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("abc2", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 2) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{2, 0})) +} + +// join reversed duplicate lines ABC & CBA have junctions at their end points" +func TestJoinReversedDuplicate(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("cba", orb.LineString{ + orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 2) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{2, 0})) +} + +// join exact duplicate rings ABCA & ABCA have no junctions +func TestJoinDuplicateRings(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }, + }), + NewTestFeature("abca2", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 0) +} + +// join reversed duplicate rings ACBA & ABCA have no junctions +func TestJoinReversedDuplicateRings(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }, + }), + NewTestFeature("acba", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 0) +} + +// join rotated duplicate rings BCAB & ABCA have no junctions +func TestJoinRotatedDuplicateRings(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }, + }), + NewTestFeature("bcab", orb.Polygon{ + orb.Ring{ + orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, orb.Point{1, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 0) +} + +// join ring ABCA & line ABCA have a junction at A +func TestJoinRingLine(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcaLine", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }), + NewTestFeature("abcaPolygon", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 1) + is.True(junctions.Has([]float64{0, 0})) +} + +// join ring BCAB & line ABCA have a junction at A +func TestJoinLineRingReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcaLine", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }), + NewTestFeature("bcabPolygon", orb.Polygon{ + orb.Ring{ + orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, orb.Point{1, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 1) + is.True(junctions.Has([]float64{0, 0})) +} + +// join ring ABCA & line BCAB have a junction at B +func TestJoinRingLineReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("bcabLine", orb.LineString{ + orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, orb.Point{1, 0}, + }), + NewTestFeature("abcaPolygon", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 1) + is.True(junctions.Has([]float64{1, 0})) +} + +// join when an old arc ABC extends a new arc AB, there is a junction at B +func TestJoinOldArcExtends(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("ab", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 3) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{1, 0})) + is.True(junctions.Has([]float64{2, 0})) +} + +// join when a reversed old arc CBA extends a new arc AB, there is a junction at B +func TestJoinOldArcExtendsReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("cba", orb.LineString{ + orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("ab", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 3) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{1, 0})) + is.True(junctions.Has([]float64{2, 0})) +} + +// join when a new arc ADE shares its start with an old arc ABC, there is a junction at A +func TestJoinNewArcSharesStart(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("ade", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 1}, orb.Point{2, 1}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 3) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{2, 0})) + is.True(junctions.Has([]float64{2, 1})) +} + +// join ring ABA has no junctions +func TestJoinRingNoJunctions(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("aba", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 0) +} + +// join ring AA has no junctions +func TestJoinRingAANoJunctions(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("aa", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 0) +} + +// join degenerate ring A has no junctions +func TestJoinRingANoJunctions(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("a", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 0) +} + +// join when a new line DEC shares its end with an old line ABC, there is a junction at C +func TestJoinNewLineSharesEnd(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("dec", orb.LineString{ + orb.Point{0, 1}, orb.Point{1, 1}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 3) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{2, 0})) + is.True(junctions.Has([]float64{0, 1})) +} + +// join when a new line ABC extends an old line AB, there is a junction at B +func TestJoinNewLineExtends(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("ab", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, + }), + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 3) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{1, 0})) + is.True(junctions.Has([]float64{2, 0})) +} + +// join when a new line ABC extends a reversed old line BA, there is a junction at B +func TestJoinNewLineExtendsReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("ba", orb.LineString{ + orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 3) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{1, 0})) + is.True(junctions.Has([]float64{2, 0})) +} + +// join when a new line starts BC in the middle of an old line ABC, there is a junction at B +func TestJoinNewStartsMiddle(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("bc", orb.LineString{ + orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 3) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{1, 0})) + is.True(junctions.Has([]float64{2, 0})) +} + +// join when a new line BC starts in the middle of a reversed old line CBA, there is a junction at B +func TestJoinNewStartsMiddleReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("cba", orb.LineString{ + orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("bc", orb.LineString{ + orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 3) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{1, 0})) + is.True(junctions.Has([]float64{2, 0})) +} + +// join when a new line ABD deviates from an old line ABC, there is a junction at B +func TestJoinNewLineDeviates(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("abd", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{3, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 4) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{1, 0})) + is.True(junctions.Has([]float64{2, 0})) + is.True(junctions.Has([]float64{3, 0})) +} + +// join when a new line ABD deviates from a reversed old line CBA, there is a junction at B +func TestJoinNewLineDeviatesReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("cba", orb.LineString{ + orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("abd", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{3, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 4) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{1, 0})) + is.True(junctions.Has([]float64{2, 0})) + is.True(junctions.Has([]float64{3, 0})) +} + +// join when a new line DBC merges into an old line ABC, there is a junction at B +func TestJoinNewLineMerges(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("dbc", orb.LineString{ + orb.Point{3, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 4) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{1, 0})) + is.True(junctions.Has([]float64{2, 0})) + is.True(junctions.Has([]float64{3, 0})) +} + +// join when a new line DBC merges into a reversed old line CBA, there is a junction at B +func TestJoinNewLineMergesReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("cba", orb.LineString{ + orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("dbc", orb.LineString{ + orb.Point{3, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 4) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{1, 0})) + is.True(junctions.Has([]float64{2, 0})) + is.True(junctions.Has([]float64{3, 0})) +} + +// join when a new line DBE shares a single midpoint with an old line ABC, there is a junction at B +func TestJoinNewLineSharesMidpoint(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abc", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, + }), + NewTestFeature("dbe", orb.LineString{ + orb.Point{0, 1}, orb.Point{1, 0}, orb.Point{2, 1}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 5) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{1, 0})) + is.True(junctions.Has([]float64{2, 0})) + is.True(junctions.Has([]float64{0, 1})) + is.True(junctions.Has([]float64{2, 1})) +} + +// join when a new line ABDE skips a point with an old line ABCDE, there is a junction at B and D +func TestJoinNewLineSkipsPoint(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcde", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{3, 0}, orb.Point{4, 0}, + }), + NewTestFeature("adbe", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{3, 0}, orb.Point{4, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 4) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{1, 0})) + is.True(junctions.Has([]float64{3, 0})) + is.True(junctions.Has([]float64{4, 0})) +} + +// join when a new line ABDE skips a point with a reversed old line EDCBA, there is a junction at B and D +func TestJoinNewLineSkipsPointReversed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("edcba", orb.LineString{ + orb.Point{4, 0}, orb.Point{3, 0}, orb.Point{2, 0}, orb.Point{1, 0}, orb.Point{0, 0}, + }), + NewTestFeature("adbe", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{3, 0}, orb.Point{4, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 4) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{1, 0})) + is.True(junctions.Has([]float64{3, 0})) + is.True(junctions.Has([]float64{4, 0})) +} + +// join when a line ABCDBE self-intersects with its middle, there are no junctions +func TestJoinSelfIntersectsMiddle(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcdbe", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{3, 0}, orb.Point{1, 0}, orb.Point{4, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 2) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{4, 0})) +} + +// join when a line ABACD self-intersects with its start, there are no junctions +func TestJoinSelfIntersectsStart(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abacd", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 0}, orb.Point{3, 0}, orb.Point{4, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 2) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{4, 0})) +} + +// join when a line ABCDBD self-intersects with its end, there are no junctions +func TestJoinSelfIntersectsEnd(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcdbd", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{4, 0}, orb.Point{3, 0}, orb.Point{4, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 2) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{4, 0})) +} + +// join when an old line ABCDBE self-intersects and shares a point B, there is a junction at B +func TestJoinSelfIntersectsShares(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abcdbe", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{2, 0}, orb.Point{3, 0}, orb.Point{1, 0}, orb.Point{4, 0}, + }), + NewTestFeature("fbg", orb.LineString{ + orb.Point{0, 1}, orb.Point{1, 0}, orb.Point{2, 1}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 5) + is.True(junctions.Has([]float64{0, 0})) + is.True(junctions.Has([]float64{0, 1})) + is.True(junctions.Has([]float64{1, 0})) + is.True(junctions.Has([]float64{2, 1})) + is.True(junctions.Has([]float64{4, 0})) +} + +// join when a line ABCA is closed, there is a junction at A +func TestJoinLineClosed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 1) + is.True(junctions.Has([]float64{0, 0})) +} + +// join when a ring ABCA is closed, there are no junctions +func TestJoinRingClosed(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 0) +} + +// join exact duplicate rings ABCA & ABCA share the arc ABCA +func TestJoinDuplicateRingsShare(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 0) +} + +// join reversed duplicate rings ABCA & ACBA share the arc ABCA +func TestJoinDuplicateRingsReversedShare(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + NewTestFeature("acba", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{0, 1}, orb.Point{1, 0}, orb.Point{0, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 0) +} + +// join coincident rings ABCA & BCAB share the arc BCAB +func TestJoinCoincidentRings(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + NewTestFeature("bcab", orb.Polygon{ + orb.Ring{ + orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, orb.Point{1, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 0) +} + +// join coincident rings ABCA & BACB share the arc BCAB +func TestJoinCoincidentRings2(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + NewTestFeature("bacb", orb.Polygon{ + orb.Ring{ + orb.Point{1, 0}, orb.Point{0, 0}, orb.Point{0, 1}, orb.Point{1, 0}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 0) +} + +// join coincident rings ABCA & DBED share the point B +func TestJoinCoincidentRingsShare(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + NewTestFeature("dbed", orb.Polygon{ + orb.Ring{ + orb.Point{2, 1}, orb.Point{1, 0}, orb.Point{2, 2}, orb.Point{2, 1}, + }, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 1) + is.True(junctions.Has([]float64{1, 0})) +} + +// join coincident ring ABCA & line DBE share the point B +func TestJoinCoincidentRingLine(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("abca", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }, + }), + NewTestFeature("dbe", orb.LineString{ + orb.Point{2, 1}, orb.Point{1, 0}, orb.Point{2, 2}, + }), + } + + topo := &Topology{input: in} + topo.extract() + junctions := topo.join() + + is.Equal(len(junctions), 3) + is.True(junctions.Has([]float64{1, 0})) + is.True(junctions.Has([]float64{2, 1})) + is.True(junctions.Has([]float64{2, 2})) +} diff --git a/encoding/topojson/pointfeature_test.go b/encoding/topojson/pointfeature_test.go new file mode 100644 index 0000000..a9bee2a --- /dev/null +++ b/encoding/topojson/pointfeature_test.go @@ -0,0 +1,35 @@ +package topojson + +import ( + "testing" + + "github.com/cheekybits/is" + "github.com/paulmach/orb" + "github.com/paulmach/orb/geojson" +) + +func TestPointFeature(t *testing.T) { + is := is.New(t) + + fc := geojson.NewFeatureCollection() + f := geojson.NewFeature(orb.Point{0, 0}) + f.ID = "point" + fc.Append(f) + + topo := NewTopology(fc, nil) + + is.Equal([]float64{0, 0}, topo.Objects["point"].Point) +} + +func TestMultiPointFeature(t *testing.T) { + is := is.New(t) + + fc := geojson.NewFeatureCollection() + f := geojson.NewFeature(orb.MultiPoint{orb.Point{0, 0}, orb.Point{1, 1}}) + f.ID = "multipoint" + fc.Append(f) + + topo := NewTopology(fc, nil) + + is.Equal([][]float64{{0, 0}, {1, 1}}, topo.Objects["multipoint"].MultiPoint) +} diff --git a/encoding/topojson/postquantize.go b/encoding/topojson/postquantize.go new file mode 100644 index 0000000..9bfc206 --- /dev/null +++ b/encoding/topojson/postquantize.go @@ -0,0 +1,77 @@ +package topojson + +import ( + "github.com/paulmach/orb" +) + +func (t *Topology) postQuantize() { + q0 := t.opts.PreQuantize + q1 := t.opts.PostQuantize + + if q1 == 0 { + return + } + + var q *quantize + + if q0 != 0 { + if q0 == q1 { + return + } + + k := q1 / q0 + + q = newQuantize(0, 0, k, k) + + t.Transform.Scale[0] /= k + t.Transform.Scale[1] /= k + } else { + x0 := t.BBox[0] + y0 := t.BBox[1] + x1 := t.BBox[2] + y1 := t.BBox[3] + + kx := float64(1) + if x1-x0 != 0 { + kx = (q1 - 1) / (x1 - x0) + } + + ky := float64(1) + if y1-y0 != 0 { + ky = (q1 - 1) / (y1 - y0) + } + + q = newQuantize(-x0, -y0, kx, ky) + t.Transform = q.Transform + } + + for _, f := range t.input { + t.postQuantizeGeometry(q, f.Geometry) + } + + for i, arc := range t.Arcs { + a := make(orb.LineString, len(arc)) + for i, v := range arc { + a[i] = orb.Point{v[0], v[1]} + } + b := q.quantizeLine(a, true) + c := make([][]float64, len(b)) + for i, v := range b { + c[i] = []float64{v[0], v[1]} + } + t.Arcs[i] = c + } +} + +func (t *Topology) postQuantizeGeometry(q *quantize, g orb.Geometry) { + switch v := g.(type) { + default: + for _, geom := range g.(orb.Collection) { + t.postQuantizeGeometry(q, geom) + } + case orb.Point: + v = q.quantizePoint(v) + case orb.LineString: + v = q.quantizeLine(v, false) + } +} diff --git a/encoding/topojson/prequantize.go b/encoding/topojson/prequantize.go new file mode 100644 index 0000000..f26c221 --- /dev/null +++ b/encoding/topojson/prequantize.go @@ -0,0 +1,61 @@ +package topojson + +import ( + "github.com/paulmach/orb" +) + +func (t *Topology) preQuantize() { + if t.opts.PreQuantize == 0 { + return + } + if t.opts.PostQuantize == 0 { + t.opts.PostQuantize = t.opts.PreQuantize + } + + q0 := t.opts.PreQuantize + q1 := t.opts.PostQuantize + + x0 := t.BBox[0] + y0 := t.BBox[1] + x1 := t.BBox[2] + y1 := t.BBox[3] + + kx := float64(1) + if x1-x0 != 0 { + kx = (q1 - 1) / (x1 - x0) * q0 / q1 + } + + ky := float64(1) + if y1-y0 != 0 { + ky = (q1 - 1) / (y1 - y0) * q0 / q1 + } + + q := newQuantize(-x0, -y0, kx, ky) + + for _, f := range t.input { + t.preQuantizeGeometry(q, &f.Geometry) + } + + t.Transform = q.Transform +} + +func (t *Topology) preQuantizeGeometry(q *quantize, g *orb.Geometry) { + switch v := (*g).(type) { + case orb.Collection: + for _, g := range v { + t.preQuantizeGeometry(q, &g) + } + case orb.Point: + *g = q.quantizePoint(v) + case orb.MultiPoint: + *g = q.quantizeMultiPoint(v, false) + case orb.LineString: + *g = q.quantizeLine(v, true) + case orb.MultiLineString: + *g = q.quantizeMultiLine(v, true) + case orb.Polygon: + *g = q.quantizePolygon(v, true) + case orb.MultiPolygon: + *g = q.quantizeMultiPolygon(v, true) + } +} diff --git a/encoding/topojson/prequantize_test.go b/encoding/topojson/prequantize_test.go new file mode 100644 index 0000000..5f0673c --- /dev/null +++ b/encoding/topojson/prequantize_test.go @@ -0,0 +1,283 @@ +package topojson + +import ( + "testing" + + "github.com/cheekybits/is" + orb "github.com/paulmach/orb" + geojson "github.com/paulmach/orb/geojson" +) + +// Sets the quantization transform +func TestPreQuantize(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{} + topo := &Topology{ + BBox: []float64{0, 0, 1, 1}, + input: in, + opts: &TopologyOptions{ + PreQuantize: 1e4, + PostQuantize: 1e4, + }, + } + + topo.preQuantize() + + is.Equal(topo.Transform, &Transform{ + Scale: [2]float64{float64(1) / 9999, float64(1) / 9999}, + Translate: [2]float64{0, 0}, + }) +} + +// Converts coordinates to fixed precision +func TestPreQuantizeConverts(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("foo", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }), + } + + expected := orb.LineString{ + orb.Point{0, 0}, orb.Point{9999, 0}, orb.Point{0, 9999}, orb.Point{0, 0}, + } + + topo := &Topology{ + input: in, + opts: &TopologyOptions{ + PreQuantize: 1e4, + PostQuantize: 1e4, + }, + } + + topo.bounds() + topo.preQuantize() + + is.Equal(topo.Transform, &Transform{ + Scale: [2]float64{float64(1) / 9999, float64(1) / 9999}, + Translate: [2]float64{0, 0}, + }) + is.Equal(topo.input[0].Geometry, expected) +} + +// Observes the quantization parameter +func TestPreQuantizeObserves(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("foo", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }), + } + + expected := orb.LineString{ + {0, 0}, {9, 0}, {0, 9}, {0, 0}, + } + + topo := &Topology{ + input: in, + opts: &TopologyOptions{ + PreQuantize: 10, + PostQuantize: 10, + }, + } + + topo.bounds() + topo.preQuantize() + is.Equal(topo.input[0].Geometry, expected) +} + +// Observes the bounding box +func TestPreQuantizeObservesBB(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("foo", orb.LineString{ + orb.Point{0, 0}, orb.Point{1, 0}, orb.Point{0, 1}, orb.Point{0, 0}, + }), + } + topo := &Topology{ + BBox: []float64{-1, -1, 2, 2}, + input: in, + opts: &TopologyOptions{ + PreQuantize: 10, + PostQuantize: 10, + }, + } + + topo.preQuantize() + + expected := orb.LineString{ + {3, 3}, {6, 3}, {3, 6}, {3, 3}, + } + is.Equal(topo.input[0].Geometry, expected) +} + +// Applies to points as well as arcs +func TestPreQuantizeAppliesToPoints(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("foo", orb.MultiPoint{ + {0, 0}, {1, 0}, {0, 1}, {0, 0}, + })} + topo := &Topology{ + input: in, + opts: &TopologyOptions{ + PreQuantize: 1e4, + PostQuantize: 1e4, + }, + } + + topo.bounds() + topo.preQuantize() + + expected := orb.MultiPoint{ + {0, 0}, {9999, 0}, {0, 9999}, {0, 0}, + } + is.Equal(topo.input[0].Geometry, expected) +} + +// Skips coincident points in lines +func TestPreQuantizeSkipsCoincidencesInLines(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("foo", orb.LineString{ + orb.Point{0, 0}, orb.Point{0.9, 0.9}, orb.Point{1.1, 1.1}, orb.Point{2, 2}, + }), + } + topo := &Topology{ + input: in, + opts: &TopologyOptions{ + PreQuantize: 3, + PostQuantize: 3, + }, + } + + topo.bounds() + topo.preQuantize() + + expected := orb.LineString{ + {0, 0}, {1, 1}, {2, 2}, + } + is.Equal(topo.input[0].Geometry, expected) +} + +// Skips coincident points in polygons +func TestPreQuantizeSkipsCoincidencesInPolygons(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("polygon", orb.Polygon{ + orb.Ring{ + orb.Point{0, 0}, orb.Point{0.9, 0.9}, orb.Point{1.1, 1.1}, orb.Point{2, 2}, orb.Point{0, 0}, + }, + }), + } + topo := &Topology{ + input: in, + opts: &TopologyOptions{ + PreQuantize: 3, + PostQuantize: 3, + }, + } + + topo.bounds() + topo.preQuantize() + + expected := orb.Polygon{ + orb.Ring{ + {0, 0}, {1, 1}, {2, 2}, {0, 0}, + }, + } + + is.Equal(topo.input[0].Geometry, expected) +} + +// Does not skip coincident points in points +func TestPreQuantizeDoesntSkipInPoints(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("multipoint", orb.MultiPoint{ + {0, 0}, {0.9, 0.9}, {1.1, 1.1}, {2, 2}, {0, 0}, + }), + } + topo := &Topology{ + input: in, + opts: &TopologyOptions{ + PreQuantize: 3, + PostQuantize: 3, + }, + } + + topo.bounds() + topo.preQuantize() + + expected := orb.MultiPoint{ + {0, 0}, {1, 1}, {1, 1}, {2, 2}, {0, 0}, + } + + is.Equal(topo.input[0].Geometry, expected) +} + +// Includes closing point in degenerate lines +func TestPreQuantizeIncludesClosingLine(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("foo", orb.LineString{ + orb.Point{1, 1}, orb.Point{1, 1}, orb.Point{1, 1}, + }), + } + topo := &Topology{ + BBox: []float64{0, 0, 2, 2}, + input: in, + opts: &TopologyOptions{ + PreQuantize: 3, + PostQuantize: 3, + }, + } + + topo.preQuantize() + + expected := []orb.Point{ + {1, 1}, {1, 1}, + } + + is.Equal(topo.input[0].Geometry, expected) +} + +// Includes closing point in degenerate polygons +func TestPreQuantizeIncludesClosingPolygon(t *testing.T) { + is := is.New(t) + + in := []*geojson.Feature{ + NewTestFeature("polygon", orb.Polygon{ + orb.Ring{ + orb.Point{0.9, 1}, orb.Point{1.1, 1}, orb.Point{1.01, 1}, orb.Point{0.9, 1}, + }, + }), + } + topo := &Topology{ + BBox: []float64{0, 0, 2, 2}, + input: in, + opts: &TopologyOptions{ + PreQuantize: 3, + PostQuantize: 3, + }, + } + + topo.preQuantize() + + expected := orb.Polygon{ + orb.Ring{ + {1, 1}, {1, 1}, + }, + } + + is.Equal(topo.input[0].Geometry, expected) +} diff --git a/encoding/topojson/quantize.go b/encoding/topojson/quantize.go new file mode 100644 index 0000000..2d6739e --- /dev/null +++ b/encoding/topojson/quantize.go @@ -0,0 +1,129 @@ +package topojson + +import ( + "math" + + "github.com/paulmach/orb" +) + +type quantize struct { + Transform *Transform + + dx, dy, kx, ky float64 +} + +func newQuantize(dx, dy, kx, ky float64) *quantize { + return &quantize{ + dx: dx, + dy: dy, + kx: kx, + ky: ky, + + Transform: &Transform{ + Scale: [2]float64{1 / kx, 1 / ky}, + Translate: [2]float64{-dx, -dy}, + }, + } +} + +func (q *quantize) quantizePoint(p orb.Point) orb.Point { + x := round((p[0] + q.dx) * q.kx) + y := round((p[1] + q.dy) * q.ky) + return orb.Point{x, y} +} + +func (q *quantize) quantizeMultiPoint(in orb.MultiPoint, skipEqual bool) orb.MultiPoint { + out := orb.MultiPoint{} + + var last []float64 + + for _, p := range in { + pt := q.quantizePoint(p) + if !pointEquals([]float64{pt[0], pt[1]}, last) || !skipEqual { + out = append(out, pt) + last = []float64{pt[0], pt[1]} + } + } + + if len(out) < 2 { + out = append(out, out[0]) + } + + return out +} + +func (q *quantize) quantizeLine(in orb.LineString, skipEqual bool) orb.LineString { + out := orb.LineString{} + + var last []float64 + + for _, p := range in { + pt := q.quantizePoint(p) + if !pointEquals([]float64{pt[0], pt[1]}, last) || !skipEqual { + out = append(out, pt) + last = []float64{pt[0], pt[1]} + } + } + + if len(out) < 2 { + out = append(out, out[0]) + } + + return out +} + +func (q *quantize) quantizeRing(in orb.Ring, skipEqual bool) orb.Ring { + out := orb.Ring{} + + var last []float64 + + for _, p := range in { + pt := q.quantizePoint(p) + if !pointEquals([]float64{pt[0], pt[1]}, last) || !skipEqual { + out = append(out, pt) + last = []float64{pt[0], pt[1]} + } + } + + if len(out) < 2 { + out = append(out, out[0]) + } + + return out +} + +func (q *quantize) quantizeMultiLine(in orb.MultiLineString, skipEqual bool) orb.MultiLineString { + out := make(orb.MultiLineString, len(in)) + for i, line := range in { + line = q.quantizeLine(line, skipEqual) + for len(line) < 4 { + line = append(line, line[0]) + } + out[i] = line + } + return out +} + +func (q *quantize) quantizePolygon(in orb.Polygon, skipEqual bool) orb.Polygon { + out := make(orb.Polygon, len(in)) + for i, ring := range in { + out[i] = q.quantizeRing(ring, skipEqual) + } + return out +} + +func (q *quantize) quantizeMultiPolygon(in orb.MultiPolygon, skipEqual bool) orb.MultiPolygon { + out := make(orb.MultiPolygon, len(in)) + for i, ring := range in { + out[i] = q.quantizePolygon(ring, skipEqual) + } + return out +} + +func round(v float64) float64 { + if v < 0 { + return math.Ceil(v - 0.5) + } else { + return math.Floor(v + 0.5) + } +} diff --git a/encoding/topojson/removeempty.go b/encoding/topojson/removeempty.go new file mode 100644 index 0000000..25ab80c --- /dev/null +++ b/encoding/topojson/removeempty.go @@ -0,0 +1,94 @@ +package topojson + +import ( + geojson "github.com/paulmach/orb/geojson" +) + +func (t *Topology) removeEmpty() { + objs := make(map[string]*Geometry, len(t.Objects)) + for _, o := range t.Objects { + obj := t.removeEmptyObjects(o) + if obj != nil { + objs[obj.ID] = obj + } + } + t.Objects = objs +} + +func (t *Topology) removeEmptyObjects(obj *Geometry) *Geometry { + switch obj.Type { + case "GeometryCollection": + geoms := make([]*Geometry, 0, len(obj.Geometries)) + for _, g := range obj.Geometries { + geom := t.removeEmptyObjects(g) + if geom != nil { + geoms = append(geoms, geom) + } + } + if len(geoms) == 0 { + return nil + } + obj.Geometries = geoms + case geojson.TypeLineString: + if len(obj.LineString) == 0 { + return nil + } + case geojson.TypeMultiLineString: + linestrings := make([][]int, 0, len(obj.MultiLineString)) + for _, ls := range obj.MultiLineString { + if len(ls) > 0 { + linestrings = append(linestrings, ls) + } + } + if len(linestrings) == 0 { + return nil + } + + if len(linestrings) == 1 { + obj.LineString = linestrings[0] + obj.MultiLineString = nil + obj.Type = geojson.TypeLineString + } else { + obj.MultiLineString = linestrings + } + case geojson.TypePolygon: + rings := t.removeEmptyPolygon(obj.Polygon) + if rings == nil { + return nil + } + obj.Polygon = rings + case geojson.TypeMultiPolygon: + polygons := make([][][]int, 0, len(obj.MultiPolygon)) + for _, polygon := range obj.MultiPolygon { + rings := t.removeEmptyPolygon(polygon) + if rings != nil { + polygons = append(polygons, rings) + } + } + if len(polygons) == 0 { + return nil + } + if len(polygons) == 1 { + obj.Polygon = polygons[0] + obj.MultiPolygon = nil + obj.Type = geojson.TypePolygon + } else { + obj.MultiPolygon = polygons + } + } + + return obj +} + +func (t *Topology) removeEmptyPolygon(polygon [][]int) [][]int { + rings := make([][]int, 0, len(polygon)) + for i, ring := range polygon { + if i == 0 && len(ring) == 0 { + return nil // Outer ring empty + } + if len(ring) > 0 { + rings = append(rings, ring) + } + } + return rings +} diff --git a/encoding/topojson/simplify.go b/encoding/topojson/simplify.go new file mode 100644 index 0000000..1964f58 --- /dev/null +++ b/encoding/topojson/simplify.go @@ -0,0 +1,46 @@ +package topojson + +import ( + geo "github.com/paulmach/go.geo" + "github.com/paulmach/go.geo/reducers" +) + +func (t *Topology) simplify() { + t.deletedArcs = make(map[int]bool) + t.shiftArcs = make(map[int]int) + + if t.opts.Simplify == 0 { + for i := range t.Arcs { + t.deletedArcs[i] = false + t.shiftArcs[i] = 0 + } + return + } + + newArcs := make([][][]float64, 0) + for i, arc := range t.Arcs { + path := geo.NewPathFromYXSlice(arc) + path = reducers.VisvalingamThreshold(path, t.opts.Simplify) + points := path.Points() + newArc := make([][]float64, len(points)) + for j, p := range points { + newArc[j] = []float64{p[1], p[0]} + } + + if i == 0 { + t.shiftArcs[i] = 0 + } else { + t.shiftArcs[i] = t.shiftArcs[i-1] + } + + remove := len(newArc) <= 2 && pointEquals(newArc[0], newArc[1]) + if remove { + // Zero-length arc, remove it! + t.deletedArcs[i] = true + t.shiftArcs[i] += 1 + } else { + newArcs = append(newArcs, newArc) + } + } + t.Arcs = newArcs +} diff --git a/encoding/topojson/topojson_test.go b/encoding/topojson/topojson_test.go new file mode 100644 index 0000000..d097e2a --- /dev/null +++ b/encoding/topojson/topojson_test.go @@ -0,0 +1,23 @@ +package topojson + +import ( + "fmt" + + orb "github.com/paulmach/orb" + "github.com/paulmach/orb/geojson" +) + +func NewTestFeature(id string, geom orb.Geometry) *geojson.Feature { + feature := geojson.NewFeature(geom) + feature.ID = id + return feature +} + +func GetFeature(topo *Topology, id string) *topologyObject { + for _, o := range topo.objects { + if o.ID == id { + return o + } + } + panic(fmt.Sprintf("No such object: %s", id)) +} diff --git a/encoding/topojson/topology.go b/encoding/topojson/topology.go new file mode 100644 index 0000000..e0a4206 --- /dev/null +++ b/encoding/topojson/topology.go @@ -0,0 +1,140 @@ +package topojson + +import ( + "encoding/json" + + "github.com/paulmach/orb/geojson" +) + +type Topology struct { + Type string `json:"type"` + Transform *Transform `json:"transform,omitempty"` + + BBox []float64 `json:"bbox,omitempty"` + Objects map[string]*Geometry `json:"objects"` + Arcs [][][]float64 `json:"arcs"` + + // For internal use only + opts *TopologyOptions + input []*geojson.Feature + coordinates [][]float64 + objects []*topologyObject + lines []*arc + rings []*arc + arcs []*arc + arcIndexes map[arcEntry]int + deletedArcs map[int]bool + shiftArcs map[int]int +} + +type Transform struct { + Scale [2]float64 `json:"scale"` + Translate [2]float64 `json:"translate"` +} + +type TopologyOptions struct { + // Pre-quantization precision + PreQuantize float64 + + // Post-quantization precision + PostQuantize float64 + + // Maximum simplification error, set to 0 to disable + Simplify float64 + + // ID property key + IDProperty string +} + +func NewTopology(fc *geojson.FeatureCollection, opts *TopologyOptions) *Topology { + if opts == nil { + opts = &TopologyOptions{} + } + + if opts.IDProperty == "" { + opts.IDProperty = "id" + } + + topo := &Topology{ + Type: "Topology", + input: fc.Features, + opts: opts, + Objects: make(map[string]*Geometry), + } + + topo.bounds() + topo.preQuantize() + topo.extract() + topo.join() + topo.cut() + topo.dedup() + topo.unpackArcs() + topo.simplify() + topo.unpackObjects() + topo.removeEmpty() + topo.postQuantize() + topo.delta() + + // No longer needed + topo.opts = nil + + return topo +} + +// MarshalJSON converts the topology object into the proper JSON. +// It will handle the encoding of all the child geometries. +// Alternately one can call json.Marshal(t) directly for the same result. +func (t *Topology) MarshalJSON() ([]byte, error) { + t.Type = "Topology" + if t.Objects == nil { + t.Objects = make(map[string]*Geometry) // TopoJSON requires the objects attribute to be at least {} + } + if t.Arcs == nil { + t.Arcs = make([][][]float64, 0) // TopoJSON requires the arcs attribute to be at least [] + } + return json.Marshal(*t) +} + +// UnmarshalTopology decodes the data into a TopoJSON topology. +// Alternately one can call json.Unmarshal(topo) directly for the same result. +func UnmarshalTopology(data []byte) (*Topology, error) { + topo := &Topology{} + err := json.Unmarshal(data, topo) + if err != nil { + return nil, err + } + + return topo, nil +} + +// Internal structs + +type arc struct { + Start int + End int + Next *arc +} + +type point [2]float64 + +func newPoint(coords []float64) point { + return point{coords[0], coords[1]} +} + +func pointEquals(a, b []float64) bool { + return a != nil && b != nil && len(a) == len(b) && a[0] == b[0] && a[1] == b[1] +} + +type topologyObject struct { + ID string + Type string + Properties map[string]interface{} + BBox []float64 + + Geometries []*topologyObject // For geometry collections + Arc *arc // For lines + Arcs []*arc // For multi lines and polygons + MultiArcs [][]*arc // For multi polygons + Point []float64 // for points + MultiPoint [][]float64 // for multi points +} diff --git a/encoding/topojson/topology_test.go b/encoding/topojson/topology_test.go new file mode 100644 index 0000000..0b90f2e --- /dev/null +++ b/encoding/topojson/topology_test.go @@ -0,0 +1,86 @@ +package topojson + +import ( + "testing" + + "github.com/paulmach/orb" + + "github.com/cheekybits/is" + "github.com/paulmach/orb/geojson" +) + +func TestTopology(t *testing.T) { + is := is.New(t) + + poly := geojson.NewFeature(orb.Polygon{ + orb.Ring{ + {0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}, + }, + }) + poly.ID = "poly" + + fc := geojson.NewFeatureCollection() + fc.Append(poly) + + topo := NewTopology(fc, nil) + is.NotNil(topo) + is.Equal(len(topo.Objects), 1) + is.Equal(len(topo.Arcs), 1) +} + +// Full roundtrip test +func TestFull(t *testing.T) { + is := is.New(t) + + in := []byte(`{"type":"FeatureCollection","features":[{"type":"Feature", "id":"road", "properties":{"id":"road"},"geometry":{"type":"LineString","coordinates":[[4.707126617431641,50.88714360752515],[4.708242416381835,50.886683361842216],[4.708693027496338,50.886514152727386],[4.70914363861084,50.886321253586765],[4.709406495094299,50.88633140619302],[4.709567427635193,50.88636524819791],[4.709604978561401,50.88647354244835],[4.709545969963074,50.88664275171071],[4.708666205406189,50.88698116839158],[4.707368016242981,50.88743464288961]]}}]}`) + out := []byte(`{"type":"Topology","bbox":[4.707126617431641,50.886321253586765,4.709604978561401,50.88743464288961],"objects":{"road":{"id":"road","properties":{"id":"road"},"type":"LineString","arcs":[0]}},"arcs":[[[0,7385],[4502,-4133],[1818,-1520],[1818,-1732],[1060,91],[650,304],[151,973],[-238,1519],[-3549,3039],[-5238,4073]]],"transform":{"scale":[2.478608990659808e-7,1.1135006529096161e-7],"translate":[4.707126617431641,50.886321253586765]}}`) + + fc, err := geojson.UnmarshalFeatureCollection(in) + is.NoErr(err) + + topo := NewTopology(fc, &TopologyOptions{ + IDProperty: "id", + PreQuantize: 1000000, + PostQuantize: 10000, + }) + + expected, err := UnmarshalTopology(out) + is.NoErr(err) + is.Equal(topo, expected) +} + +// https://github.com/rubenv/topojson/issues/2 +func TestMultiFeatures(t *testing.T) { + is := is.New(t) + + fc := geojson.NewFeatureCollection() + f1 := geojson.NewFeature(orb.Polygon{ + {{0, 0}, {1, 1}, {2, 0}, {1, -1}, {0, 0}}, + {{0.25, 0}, {0.75, -0.25}, {0.75, 0.25}, {0.25, 0}}, + }) + fc.Append(f1) + f2 := geojson.NewFeature(orb.Polygon{ + {{0, 0}, {1, 1}, {2, 0}, {1, 2}, {0, 0}}, + }) + fc.Append(f2) + + topo := NewTopology(fc, nil) + + is.Equal(len(topo.Objects), len(fc.Features)) +} + +func TestCopyBounds(t *testing.T) { + is := is.New(t) + + fc := geojson.NewFeatureCollection() + f := NewTestFeature("one", orb.Polygon{ + orb.Ring{{0, 0}, {1, 1}, {2, 0}, {1, 2}, {0, 0}}, + }) + f.BBox = []float64{0, 0, 2, 2} + fc.Append(f) + + topo := NewTopology(fc, nil) + is.NotNil(topo) + + is.Equal(topo.Objects["one"].BBox, []float64{0, 0, 2, 2}) +} diff --git a/encoding/topojson/unpackarcs.go b/encoding/topojson/unpackarcs.go new file mode 100644 index 0000000..dfd06d9 --- /dev/null +++ b/encoding/topojson/unpackarcs.go @@ -0,0 +1,13 @@ +package topojson + +func (t *Topology) unpackArcs() { + t.arcIndexes = make(map[arcEntry]int) + + // Unpack arcs + for i, a := range t.arcs { + t.arcIndexes[arcEntry{a.Start, a.End}] = i + t.Arcs = append(t.Arcs, t.coordinates[a.Start:a.End+1]) + } + t.arcs = nil + t.coordinates = nil +} diff --git a/encoding/topojson/unpackobjects.go b/encoding/topojson/unpackobjects.go new file mode 100644 index 0000000..deaa62d --- /dev/null +++ b/encoding/topojson/unpackobjects.go @@ -0,0 +1,88 @@ +package topojson + +import ( + "github.com/paulmach/orb/geojson" +) + +type arcEntry struct { + Start int + End int +} + +func (t *Topology) unpackObjects() { + for _, o := range t.objects { + obj := t.unpackObject(o) + t.Objects[obj.ID] = obj + } + t.objects = nil + t.deletedArcs = nil + t.shiftArcs = nil + t.arcIndexes = nil +} + +func (t *Topology) unpackObject(o *topologyObject) *Geometry { + obj := &Geometry{ + ID: o.ID, + Type: o.Type, + Properties: o.Properties, + BBox: o.BBox, + } + + switch o.Type { + default: + for _, geom := range o.Geometries { + obj.Geometries = append(obj.Geometries, t.unpackObject(geom)) + } + case geojson.TypeLineString: + obj.LineString = t.lookupArc(o.Arc) + case geojson.TypeMultiLineString: + obj.MultiLineString = t.lookupArcs(o.Arcs) + case geojson.TypePolygon: + obj.Polygon = t.lookupArcs(o.Arcs) + case geojson.TypeMultiPolygon: + obj.MultiPolygon = t.lookupMultiArcs(o.MultiArcs) + case geojson.TypePoint: + obj.Point = o.Point + case geojson.TypeMultiPoint: + obj.MultiPoint = o.MultiPoint + } + + return obj +} + +func (t *Topology) lookupArc(a *arc) []int { + result := make([]int, 0) + + for a != nil { + if a.Start < a.End { + index := t.arcIndexes[arcEntry{a.Start, a.End}] + if !t.deletedArcs[index] { + result = append(result, index-t.shiftArcs[index]) + } + } else { + index := t.arcIndexes[arcEntry{a.End, a.Start}] + if !t.deletedArcs[index] { + result = append(result, ^(index - t.shiftArcs[index])) + } + } + a = a.Next + } + + return result +} + +func (t *Topology) lookupArcs(a []*arc) [][]int { + result := make([][]int, 0) + for _, arc := range a { + result = append(result, t.lookupArc(arc)) + } + return result +} + +func (t *Topology) lookupMultiArcs(a [][]*arc) [][][]int { + result := make([][][]int, 0) + for _, s := range a { + result = append(result, t.lookupArcs(s)) + } + return result +}