From ade3605db2e0d14c279585576ac6515063d362f9 Mon Sep 17 00:00:00 2001 From: missinglink Date: Wed, 21 Jul 2021 10:39:05 +0200 Subject: [PATCH] refactor: rewrite node encoding for clarity and ease of extension --- codec.go | 65 ++++++++++++++++++++++++++++++++++++++++++ entrance.go | 51 +++++++++++++++++++++++++++++++++ pbf2json.go | 82 +++++++++++------------------------------------------ 3 files changed, 132 insertions(+), 66 deletions(-) create mode 100644 codec.go create mode 100644 entrance.go diff --git a/codec.go b/codec.go new file mode 100644 index 0000000..00fd51e --- /dev/null +++ b/codec.go @@ -0,0 +1,65 @@ +package main + +import ( + "encoding/binary" + "math" +) + +// an empty byte slice used for zeroing +var zeros = make([]byte, 64) + +// node encoding +type encoding []byte + +type encodingMetadata struct { + EntranceType uint8 + AccessibilityType uint8 +} + +// encode lat/lon as 64 bit floats packed in to 8 bytes, +// each float is then truncated to 6 bytes because we don't +// need the additional precision (> 8 decimal places) +func (e encoding) setCoords(lat float64, lon float64) { + binary.BigEndian.PutUint64(e[0:8], math.Float64bits(lat)) + binary.BigEndian.PutUint64(e[6:14], math.Float64bits(lon)) + copy(e[12:14], zeros) +} + +// decode lat/lon as truncated 64 bit floats from the first 12 bytes +func (e encoding) getCoords() (float64, float64) { + buffer := make([]byte, 8) + + copy(buffer, e[:6]) + lat := math.Float64frombits(binary.BigEndian.Uint64(buffer)) + + copy(buffer, e[6:12]) + lon := math.Float64frombits(binary.BigEndian.Uint64(buffer)) + + return lat, lon +} + +// leftmost two bits are for the entrance, next two bits are accessibility +// remaining 4 rightmost bits are reserved for future use. +func (e encoding) setMetadata(meta encodingMetadata) int { + if meta.EntranceType > 0 { + e[12] = ((meta.EntranceType & 0b00000011) << 6) // values [0,1,2] (stored in leftmost two bits) + e[12] |= ((meta.AccessibilityType & 0b00000011) << 4) // values [0,1,2] (stored in next two bits) + + // 13 byte encoding + return 13 + } + + // 12 byte encoding + return 12 +} + +func (e encoding) getMetadata() encodingMetadata { + meta := encodingMetadata{} + + if len(e) > 12 { + meta.EntranceType = (e[12] & 0b11000000) >> 6 + meta.AccessibilityType = (e[12] & 0b00110000) >> 4 + } + + return meta +} diff --git a/entrance.go b/entrance.go new file mode 100644 index 0000000..10e5942 --- /dev/null +++ b/entrance.go @@ -0,0 +1,51 @@ +package main + +import ( + "strings" + + "github.com/qedus/osmpbf" +) + +const ( + EntranceNone uint8 = iota + EntranceNormal + EntranceMain +) + +const ( + WheelchairAccessibleNo uint8 = iota + WheelchairAccessibleImplicitYes + WheelchairAccessibleExplicitYes +) + +// determine if the node is for an entrance +// https://wiki.openstreetmap.org/wiki/Key:entrance +func entranceType(node *osmpbf.Node) uint8 { + if val, ok := node.Tags["entrance"]; ok { + var norm = strings.ToLower(val) + switch norm { + case "main": + return EntranceMain + case "yes", "home", "staircase": + return EntranceNormal + } + } + return EntranceNone +} + +// determine if the node is accessible for wheelchair users +// https://wiki.openstreetmap.org/wiki/Key:entrance +func accessibilityType(node *osmpbf.Node) uint8 { + if val, ok := node.Tags["wheelchair"]; ok { + var norm = strings.ToLower(val) + switch norm { + case "yes": + return WheelchairAccessibleExplicitYes + case "no": + return WheelchairAccessibleNo + default: + return WheelchairAccessibleImplicitYes + } + } + return WheelchairAccessibleNo +} diff --git a/pbf2json.go b/pbf2json.go index 0736630..dec4e8d 100644 --- a/pbf2json.go +++ b/pbf2json.go @@ -13,7 +13,7 @@ import ( "strconv" "strings" - "github.com/paulmach/go.geo" + geo "github.com/paulmach/go.geo" "github.com/qedus/osmpbf" "github.com/syndtr/goleveldb/leveldb" "github.com/syndtr/goleveldb/leveldb/opt" @@ -448,38 +448,6 @@ func onRelation(relation *osmpbf.Relation, centroid map[string]string, bounds *g fmt.Println(string(json)) } -// determine if the node is for an entrance -// https://wiki.openstreetmap.org/wiki/Key:entrance -func isEntranceNode(node *osmpbf.Node) uint8 { - if val, ok := node.Tags["entrance"]; ok { - var norm = strings.ToLower(val) - switch norm { - case "main": - return 2 - case "yes", "home", "staircase": - return 1 - } - } - return 0 -} - -// determine if the node is accessible for wheelchair users -// https://wiki.openstreetmap.org/wiki/Key:entrance -func isWheelchairAccessibleNode(node *osmpbf.Node) uint8 { - if val, ok := node.Tags["wheelchair"]; ok { - var norm = strings.ToLower(val) - switch norm { - case "yes": - return 2 - case "no": - return 0 - default: - return 1 - } - } - return 0 -} - // queue a leveldb write in a batch func cacheQueueNode(batch *leveldb.Batch, node *osmpbf.Node) { id, val := nodeToBytes(node) @@ -546,28 +514,21 @@ func cacheLookupWayNodes(db *leveldb.DB, wayid int64) ([]map[string]string, erro return cacheLookupNodes(db, way) } -// decode bytes to a 'latlon' type object +// decode bytes to a 'latlon' type object (map[string]string) func bytesToLatLon(data []byte) map[string]string { - buf := make([]byte, 8) latlon := make(map[string]string, 4) + enc := encoding(data) - // first 6 bytes are the latitude - // buf = append(buf, data[0:6]...) - copy(buf, data[:6]) - lat64 := math.Float64frombits(binary.BigEndian.Uint64(buf[:8])) + lat64, lon64 := enc.getCoords() latlon["lat"] = strconv.FormatFloat(lat64, 'f', 7, 64) - - // next 6 bytes are the longitude - // buf = append(buf[:0], data[6:12]...) - copy(buf, data[6:12]) - lon64 := math.Float64frombits(binary.BigEndian.Uint64(buf[:8])) latlon["lon"] = strconv.FormatFloat(lon64, 'f', 7, 64) // check for the bitmask byte which indicates things like an // entrance and the level of wheelchair accessibility - if len(data) > 12 { - latlon["entrance"] = fmt.Sprintf("%d", (data[12]&0xC0)>>6) - latlon["wheelchair"] = fmt.Sprintf("%d", (data[12]&0x30)>>4) + meta := enc.getMetadata() + if meta.EntranceType > 0 { + latlon["entrance"] = fmt.Sprintf("%d", meta.EntranceType) + latlon["wheelchair"] = fmt.Sprintf("%d", meta.AccessibilityType) } return latlon @@ -577,27 +538,16 @@ func bytesToLatLon(data []byte) map[string]string { func nodeToBytes(node *osmpbf.Node) (string, []byte) { stringid := strconv.FormatInt(node.ID, 10) - buf := make([]byte, 14) - // encode lat/lon as 64 bit floats packed in to 8 bytes, - // each float is then truncated to 6 bytes because we don't - // need the additional precision (> 8 decimal places) - - binary.BigEndian.PutUint64(buf, math.Float64bits(node.Lat)) - binary.BigEndian.PutUint64(buf[6:], math.Float64bits(node.Lon)) - - // generate a bitmask for relevant tag features - isEntrance := isEntranceNode(node) - if isEntrance == 0 { - return stringid, buf[:12] - } + enc := make(encoding, 14) + enc.setCoords(node.Lat, node.Lon) - // leftmost two bits are for the entrance, next two bits are accessibility - // remaining 4 rightmost bits are reserved for future use. - bitmask := isEntrance << 6 - bitmask |= isWheelchairAccessibleNode(node) << 4 - buf[12] = bitmask + len := enc.setMetadata(encodingMetadata{ + EntranceType: entranceType(node), + AccessibilityType: accessibilityType(node), + }) - return stringid, buf[:13] + // return variable length encoding + return stringid, enc[:len] } func idSliceToBytes(ids []int64) []byte {