imposm3/geom/geojson/geojson.go

244 lines
5.2 KiB
Go

package geojson
import (
"encoding/json"
"errors"
"fmt"
"github.com/omniscale/imposm3/geom/geos"
"github.com/omniscale/imposm3/proj"
"io"
)
type object struct {
Type string `json:"type"`
Features []object `json:"features"`
Geometry *object `json:"geometry"`
Coordinates []interface{} `json:"coordinates"`
Properties map[string]interface{} `json:"properties"`
}
type geometry struct {
Type string `json:"type"`
Coordinates []interface{} `json:"coordinates"`
}
type point struct {
long float64
lat float64
}
func newPointFromCoords(coords []interface{}) (point, error) {
p := point{}
if len(coords) != 2 && len(coords) != 3 {
return p, errors.New("point list length not 2 or 3")
}
var ok bool
p.long, ok = coords[0].(float64)
if !ok {
return p, errors.New("invalid lon")
}
p.lat, ok = coords[1].(float64)
if !ok {
return p, errors.New("invalid lat")
}
if p.long >= -180.0 && p.long <= 180.0 && p.lat >= -90.0 && p.lat <= 90.0 {
p.long, p.lat = proj.WgsToMerc(p.long, p.lat)
}
return p, nil
}
type lineString []point
func newLineStringFromCoords(coords []interface{}) (lineString, error) {
ls := lineString{}
for _, part := range coords {
coord, ok := part.([]interface{})
if !ok {
return ls, errors.New("point not a list")
}
p, err := newPointFromCoords(coord)
if err != nil {
return ls, err
}
ls = append(ls, p)
}
return ls, nil
}
type polygon []lineString
type polygonFeature struct {
polygon polygon
properties map[string]string
}
type Feature struct {
Geom *geos.Geom
Properties map[string]string
}
func stringProperties(properties map[string]interface{}) map[string]string {
result := make(map[string]string, len(properties))
for k, v := range properties {
result[k] = fmt.Sprintf("%v", v)
}
return result
}
func newPolygonFromCoords(coords []interface{}) (polygon, error) {
poly := polygon{}
for _, part := range coords {
lsCoords, ok := part.([]interface{})
if !ok {
return poly, errors.New("polygon lineString not a list")
}
ls, err := newLineStringFromCoords(lsCoords)
if err != nil {
return poly, err
}
poly = append(poly, ls)
}
return poly, nil
}
func newMultiPolygonFeaturesFromCoords(coords []interface{}) ([]polygonFeature, error) {
features := []polygonFeature{}
for _, part := range coords {
polyCoords, ok := part.([]interface{})
if !ok {
return features, errors.New("multipolygon polygon not a list")
}
poly, err := newPolygonFromCoords(polyCoords)
if err != nil {
return features, err
}
features = append(features, polygonFeature{poly, nil})
}
return features, nil
}
func ParseGeoJson(r io.Reader) ([]Feature, error) {
decoder := json.NewDecoder(r)
obj := &object{}
err := decoder.Decode(obj)
if err != nil {
return nil, err
}
polygons, err := constructPolygonFeatures(obj)
if err != nil {
return nil, err
}
g := geos.NewGeos()
defer g.Finish()
result := []Feature{}
for _, p := range polygons {
geom, err := geosPolygon(g, p.polygon)
if err != nil {
return nil, err
}
result = append(result, Feature{geom, p.properties})
}
return result, err
}
func constructPolygonFeatures(obj *object) ([]polygonFeature, error) {
switch obj.Type {
case "Point":
return nil, errors.New("only polygon or MultiPolygon are supported")
case "LineString":
return nil, errors.New("only polygon or MultiPolygon are supported")
case "Polygon":
poly, err := newPolygonFromCoords(obj.Coordinates)
return []polygonFeature{{poly, nil}}, err
case "MultiPolygon":
poly, err := newMultiPolygonFeaturesFromCoords(obj.Coordinates)
return poly, err
case "Feature":
features, err := constructPolygonFeatures(obj.Geometry)
if err != nil {
return nil, err
}
properties := stringProperties(obj.Properties)
for i, _ := range features {
features[i].properties = properties
}
return features, err
case "FeatureCollection":
features := make([]polygonFeature, 0)
for _, obj := range obj.Features {
f, err := constructPolygonFeatures(&obj)
if err != nil {
return nil, err
}
features = append(features, f...)
}
return features, nil
default:
return nil, errors.New("unknown type: " + obj.Type)
}
}
func geosRing(g *geos.Geos, ls lineString) (*geos.Geom, error) {
coordSeq, err := g.CreateCoordSeq(uint32(len(ls)), 2)
if err != nil {
return nil, err
}
// coordSeq inherited by LinearRing, no destroy
for i, p := range ls {
err := coordSeq.SetXY(g, uint32(i), p.long, p.lat)
if err != nil {
return nil, err
}
}
ring, err := coordSeq.AsLinearRing(g)
if err != nil {
// coordSeq gets Destroy by GEOS
return nil, err
}
return ring, nil
}
func geosPolygon(g *geos.Geos, polygon polygon) (*geos.Geom, error) {
if len(polygon) == 0 {
return nil, errors.New("empty polygon")
}
shell, err := geosRing(g, polygon[0])
if err != nil {
return nil, err
}
holes := make([]*geos.Geom, len(polygon)-1)
for i, ls := range polygon[1:] {
hole, err := geosRing(g, ls)
if err != nil {
return nil, err
}
holes[i] = hole
}
geom := g.Polygon(shell, holes)
if geom == nil {
g.Destroy(shell)
for _, hole := range holes {
g.Destroy(hole)
}
return nil, errors.New("unable to create polygon")
}
return geom, nil
}