Skip to content

Commit

Permalink
columns: add new categorize, geojson_intersects and geojson_intersect…
Browse files Browse the repository at this point in the history
…s_field column types
  • Loading branch information
olt committed Dec 3, 2019
1 parent e2a9136 commit d29a56e
Show file tree
Hide file tree
Showing 5 changed files with 458 additions and 0 deletions.
41 changes: 41 additions & 0 deletions docs/mapping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,47 @@ Use ``default`` to set the default rank.
A ``motorway`` will have a ``zorder`` value of 5, a ``residential`` with ``bridge=yes`` will be 8 (3+5).


``categorize``
^^^^^^^^^^^^^^

Stores a number depending on a value, similar to ``enumerate``. However, ``categorize`` allows you to explicitly configure the number for each value, multiple values can have the same number and it can search in multiple keys. You can use this to implement a scale rank for sorting elements depending on their relative importance.


::
- args:
default: 0
values: {
FR: 10, NL: 8, LU: 3,
}
keys:
- country_code_iso3166_1_alpha_2
- ISO3166-1:alpha2
- ISO3166-1
name: scalerank
type: categorize_int


``geojson_intersects`` and ``geojson_intersects_field``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Checks whether the geometry of the element intersects geometries from a provided GeoJSON file. ``geojson_intersects`` returns true if it intersects any geometry. ``geojson_intersects_field`` returns a string property of the intersected feature.


::
- args:
geojson: special_interest_areas.geojson
name: in_special_interest_area
type: geojson_intersects


::
- args:
geojson: special_interest_areas.geojson
property: area
name: special_interest_area_name
type: geojson_intersects_field


Element types
~~~~~~~~~~~~~

Expand Down
4 changes: 4 additions & 0 deletions mapping/columns.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ func init() {
"zorder": {"zorder", "int32", nil, MakeZOrder, nil, false},
"enumerate": {"enumerate", "int32", nil, MakeEnumerate, nil, false},
"string_suffixreplace": {"string_suffixreplace", "string", nil, MakeSuffixReplace, nil, false},

"categorize_int": {Name: "categorize_int", GoType: "int32", MakeFunc: MakeCategorizeInt},
"geojson_intersects": {Name: "geojson_intersects", GoType: "bool", MakeFunc: MakeIntersectsField},
"geojson_intersects_feature": {Name: "geojson_intersects_feature", GoType: "string", MakeFunc: MakeIntersectsFeatureField},
}
}

Expand Down
67 changes: 67 additions & 0 deletions mapping/columns_categorize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package mapping

import (
"errors"
"fmt"

osm "github.com/omniscale/go-osm"
"github.com/omniscale/imposm3/geom"
"github.com/omniscale/imposm3/mapping/config"
)

func MakeCategorizeInt(fieldName string, fieldType ColumnType, field config.Column) (MakeValue, error) {
_values, ok := field.Args["values"]
if !ok {
return nil, errors.New("missing 'values' in 'args' for categorize_int")
}

values, ok := _values.(map[interface{}]interface{})
if !ok {
return nil, errors.New("'values' in 'args' for categorize_int not a dictionary")
}

valuesCategory := make(map[string]int)
for value, category := range values {
v, ok := value.(string)
if !ok {
return nil, fmt.Errorf("category in values not an string key but %t", value)
}
c, ok := category.(int)
if !ok {
return nil, fmt.Errorf("category in values not an int but %t", category)
}
valuesCategory[v] = int(c)
}

_defaultCategory, ok := field.Args["default"]
if !ok {
return nil, errors.New("missing 'default' in categorize_int")
}

defaultCategoryF, ok := _defaultCategory.(int)
if !ok {
return nil, fmt.Errorf("'default' in 'args' for categorize_int not an int but %t", _defaultCategory)
}
defaultCategory := int(defaultCategoryF)

makeValue := func(val string, elem *osm.Element, geom *geom.Geometry, m Match) interface{} {
if val != "" {
if cat, ok := valuesCategory[val]; ok {
return cat
}
}
for _, k := range field.Keys {
v, ok := elem.Tags[string(k)]
if !ok {
continue
}
if cat, ok := valuesCategory[v]; ok {
return cat
}
}

return defaultCategory
}

return makeValue, nil
}
198 changes: 198 additions & 0 deletions mapping/columns_intersection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package mapping

import (
"errors"
"os"
"sync"

osm "github.com/omniscale/go-osm"
"github.com/omniscale/imposm3/geom"
"github.com/omniscale/imposm3/geom/geojson"
"github.com/omniscale/imposm3/geom/geos"
"github.com/omniscale/imposm3/mapping/config"
"github.com/omniscale/imposm3/proj"
)

type syncedPreparedGeom struct {
sync.Mutex
geom *geos.PreparedGeom
}

type feature struct {
geom *geos.Geom
properties map[string]string
}

func loadFeatures(field config.Column) (*geos.Index, []feature, []syncedPreparedGeom, error) {
_geojsonFileName, ok := field.Args["geojson"]
if !ok {
return nil, nil, nil, errors.New("missing geojson in args for geojson_feature_intersections")
}
geojsonFileName, ok := _geojsonFileName.(string)
if !ok {
return nil, nil, nil, errors.New("geojson in args for geojson_feature_intersections not a string")
}

g := geos.NewGeos()
defer g.Finish()

idx := g.CreateIndex()

f, err := os.Open(geojsonFileName)
if err != nil {
return nil, nil, nil, err
}
defer f.Close()

jsonFeatures, err := geojson.ParseGeoJSON(f)
if err != nil {
return nil, nil, nil, err
}
preparedGeoms := make([]syncedPreparedGeom, len(jsonFeatures))

features := make([]feature, len(jsonFeatures))

for i, f := range jsonFeatures {
// TODO make SRID configurable
transformPolygon(f.Polygon, 3857)
geom, err := geosPolygon(g, f.Polygon)
if err != nil {
return nil, nil, nil, err
}

g.IndexAdd(idx, geom)
preparedGeoms[i] = syncedPreparedGeom{geom: g.Prepare(geom)}
features[i] = feature{geom: geom, properties: f.Properties}
}
return idx, features, preparedGeoms, nil
}

func MakeIntersectsFeatureField(fieldName string, fieldType ColumnType, field config.Column) (MakeValue, error) {
idx, features, preparedGeoms, err := loadFeatures(field)
if err != nil {
return nil, err
}

_propertyName, ok := field.Args["property"]
if !ok {
return nil, errors.New("missing property in args for geojson_intersects_feature")
}
propertyName, ok := _propertyName.(string)
if !ok {
return nil, errors.New("property in args for geojson_intersects_feature not a string")
}

g := geos.NewGeos()

makeValue := func(val string, elem *osm.Element, geom *geom.Geometry, m Match) interface{} {
indices := g.IndexQuery(idx, geom.Geom)

for _, idx := range indices {
preparedGeom := &preparedGeoms[idx]
preparedGeom.Lock()
if g.PreparedIntersects(preparedGeom.geom, geom.Geom) {
if v, ok := features[idx].properties[propertyName]; ok {
preparedGeom.Unlock()
return v
}
}
preparedGeom.Unlock()
}
return nil
}

return makeValue, nil
}

func MakeIntersectsField(fieldName string, fieldType ColumnType, field config.Column) (MakeValue, error) {
idx, _, preparedGeoms, err := loadFeatures(field)
if err != nil {
return nil, err
}

g := geos.NewGeos()

makeValue := func(val string, elem *osm.Element, geom *geom.Geometry, m Match) interface{} {
indices := g.IndexQuery(idx, geom.Geom)

for _, idx := range indices {
preparedGeom := &preparedGeoms[idx]
preparedGeom.Lock()
if g.PreparedIntersects(preparedGeom.geom, geom.Geom) {
preparedGeom.Unlock()
return true
}
preparedGeom.Unlock()
}
return false
}

return makeValue, nil
}

// TODO duplicate of imposm3/geom/limit
func geosRing(g *geos.Geos, ls geojson.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
}

// TODO duplicate of imposm3/geom/limit
func geosPolygon(g *geos.Geos, polygon geojson.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
}

// TODO duplicate of imposm3/geom/limit
func transformPolygon(p geojson.Polygon, targetSRID int) {
if targetSRID != 3857 {
panic("transformation to non-4326/3856 not implemented")
}
for _, ls := range p {
for i := range ls {
ls[i].Long, ls[i].Lat = proj.WgsToMerc(ls[i].Long, ls[i].Lat)
}
}
}
Loading

0 comments on commit d29a56e

Please sign in to comment.