Skip to content

Commit

Permalink
refactor: rewrite node encoding for clarity and ease of extension
Browse files Browse the repository at this point in the history
  • Loading branch information
missinglink committed Jul 21, 2021
1 parent b217f99 commit dc26fe3
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 65 deletions.
65 changes: 65 additions & 0 deletions codec.go
Original file line number Diff line number Diff line change
@@ -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
}
51 changes: 51 additions & 0 deletions entrance.go
Original file line number Diff line number Diff line change
@@ -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
}
80 changes: 15 additions & 65 deletions pbf2json.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,38 +463,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)
Expand Down Expand Up @@ -573,28 +541,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
Expand All @@ -604,27 +565,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 {
Expand Down

0 comments on commit dc26fe3

Please sign in to comment.