SickGear/lib/hachoir/parser/misc/mapsforge_map.py

410 lines
13 KiB
Python
Raw Permalink Normal View History

"""
Mapsforge map file parser (for version 3 files).
Author: Oliver Gerlich
References:
- http://code.google.com/p/mapsforge/wiki/SpecificationBinaryMapFile
- http://mapsforge.org/
"""
from hachoir.parser import Parser
from hachoir.field import (Bit, Bits, UInt8, UInt16, UInt32, Int32, UInt64, String,
PaddingBits,
Enum, Field, FieldSet, SeekableFieldSet, RootSeekableFieldSet)
from hachoir.core.endian import BIG_ENDIAN
# micro-degrees factor:
UDEG = float(1000 * 1000)
CoordinateEncoding = {
0: "single delta encoding",
1: "double delta encoding",
}
class UIntVbe(Field):
def __init__(self, parent, name, description=None):
Field.__init__(self, parent, name, description=description)
value = 0
size = 0
while True:
byteValue = self._parent.stream.readBytes(
self.absolute_address + (size * 8), 1)[0]
haveMoreData = (byteValue & 0x80)
value = value | ((byteValue & 0x7f) << (size * 7))
size += 1
assert size < 100, "UIntVBE is too large"
if not haveMoreData:
break
self._size = size * 8
self.createValue = lambda: value
class IntVbe(Field):
def __init__(self, parent, name, description=None):
Field.__init__(self, parent, name, description=description)
value = 0
size = 0
shift = 0
while True:
byteValue = self._parent.stream.readBytes(
self.absolute_address + (size * 8), 1)[0]
haveMoreData = (byteValue & 0x80)
if size == 0:
isNegative = (byteValue & 0x40)
value = (byteValue & 0x3f)
shift += 6
else:
value = value | ((byteValue & 0x7f) << shift)
shift += 7
size += 1
assert size < 100, "IntVBE is too large"
if not haveMoreData:
break
if isNegative:
value *= -1
self._size = size * 8
self.createValue = lambda: value
class VbeString(FieldSet):
def createFields(self):
yield UIntVbe(self, "length")
yield String(self, "chars", self["length"].value, charset="UTF-8")
def createDescription(self):
return '(%d B) "%s"' % (self["length"].value, self["chars"].value)
class TagStringList(FieldSet):
def createFields(self):
yield UInt16(self, "num_tags")
for i in range(self["num_tags"].value):
yield VbeString(self, "tag[]")
def createDescription(self):
return "%d tag strings" % self["num_tags"].value
class ZoomIntervalCfg(FieldSet):
def createFields(self):
yield UInt8(self, "base_zoom_level")
yield UInt8(self, "min_zoom_level")
yield UInt8(self, "max_zoom_level")
yield UInt64(self, "subfile_start")
yield UInt64(self, "subfile_size")
def createDescription(self):
return "zoom level around %d (%d - %d)" % (self["base_zoom_level"].value,
self["min_zoom_level"].value, self["max_zoom_level"].value)
class TileIndexEntry(FieldSet):
def createFields(self):
yield Bit(self, "is_water_tile")
yield Bits(self, "offset", 39)
class TileZoomTable(FieldSet):
def createFields(self):
yield UIntVbe(self, "num_pois")
yield UIntVbe(self, "num_ways")
def createDescription(self):
return "%d POIs, %d ways" % (self["num_pois"].value, self["num_ways"].value)
class TileHeader(FieldSet):
def __init__(self, parent, name, zoomIntervalCfg, **kw):
FieldSet.__init__(self, parent, name, **kw)
self.zoomIntervalCfg = zoomIntervalCfg
def createFields(self):
numLevels = int(self.zoomIntervalCfg[
"max_zoom_level"].value - self.zoomIntervalCfg["min_zoom_level"].value) + 1
assert (numLevels < 50)
for i in range(numLevels):
yield TileZoomTable(self, "zoom_table_entry[]")
yield UIntVbe(self, "first_way_offset")
class POIData(FieldSet):
def createFields(self):
if self["/have_debug"].value:
yield String(self, "signature", 32)
if not self['signature'].value.startswith("***POIStart"):
raise ValueError
yield IntVbe(self, "lat_diff")
yield IntVbe(self, "lon_diff")
yield Bits(self, "layer", 4)
yield Bits(self, "num_tags", 4)
for i in range(self["num_tags"].value):
yield UIntVbe(self, "tag_id[]")
yield Bit(self, "have_name")
yield Bit(self, "have_house_number")
yield Bit(self, "have_ele")
yield PaddingBits(self, "pad[]", 5)
if self["have_name"].value:
yield VbeString(self, "name")
if self["have_house_number"].value:
yield VbeString(self, "house_number")
if self["have_ele"].value:
yield IntVbe(self, "ele")
def createDescription(self):
s = "POI"
if self["have_name"].value:
s += ' "%s"' % self["name"]["chars"].value
s += " @ %f/%f" % (self["lat_diff"].value / UDEG,
self["lon_diff"].value / UDEG)
return s
class SubTileBitmap(FieldSet):
static_size = 2 * 8
def createFields(self):
for y in range(4):
for x in range(4):
yield Bit(self, "is_used[%d,%d]" % (x, y))
class WayProperties(FieldSet):
def createFields(self):
if self["/have_debug"].value:
yield String(self, "signature", 32)
if not self['signature'].value.startswith("---WayStart"):
raise ValueError
yield UIntVbe(self, "way_data_size")
# WayProperties is split into an outer and an inner field, to allow
# specifying data size for inner part:
yield WayPropertiesInner(self, "inner", size=self["way_data_size"].value * 8)
class WayPropertiesInner(FieldSet):
def createFields(self):
yield SubTileBitmap(self, "sub_tile_bitmap")
# yield Bits(self, "sub_tile_bitmap", 16)
yield Bits(self, "layer", 4)
yield Bits(self, "num_tags", 4)
for i in range(self["num_tags"].value):
yield UIntVbe(self, "tag_id[]")
yield Bit(self, "have_name")
yield Bit(self, "have_house_number")
yield Bit(self, "have_ref")
yield Bit(self, "have_label_position")
yield Bit(self, "have_num_way_blocks")
yield Enum(Bit(self, "coord_encoding"), CoordinateEncoding)
yield PaddingBits(self, "pad[]", 2)
if self["have_name"].value:
yield VbeString(self, "name")
if self["have_house_number"].value:
yield VbeString(self, "house_number")
if self["have_ref"].value:
yield VbeString(self, "ref")
if self["have_label_position"].value:
yield IntVbe(self, "label_lat_diff")
yield IntVbe(self, "label_lon_diff")
numWayDataBlocks = 1
if self["have_num_way_blocks"].value:
yield UIntVbe(self, "num_way_blocks")
numWayDataBlocks = self["num_way_blocks"].value
for i in range(numWayDataBlocks):
yield WayData(self, "way_data[]")
def createDescription(self):
s = "way"
if self["have_name"].value:
s += ' "%s"' % self["name"]["chars"].value
return s
class WayData(FieldSet):
def createFields(self):
yield UIntVbe(self, "num_coord_blocks")
for i in range(self["num_coord_blocks"].value):
yield WayCoordBlock(self, "way_coord_block[]")
class WayCoordBlock(FieldSet):
def createFields(self):
yield UIntVbe(self, "num_way_nodes")
yield IntVbe(self, "first_lat_diff")
yield IntVbe(self, "first_lon_diff")
for i in range(self["num_way_nodes"].value - 1):
yield IntVbe(self, "lat_diff[]")
yield IntVbe(self, "lon_diff[]")
class TileData(FieldSet):
def __init__(self, parent, name, zoomIntervalCfg, **kw):
FieldSet.__init__(self, parent, name, **kw)
self.zoomIntervalCfg = zoomIntervalCfg
def createFields(self):
if self["/have_debug"].value:
yield String(self, "signature", 32)
if not self['signature'].value.startswith("###TileStart"):
raise ValueError
yield TileHeader(self, "tile_header", self.zoomIntervalCfg)
numLevels = int(self.zoomIntervalCfg[
"max_zoom_level"].value - self.zoomIntervalCfg["min_zoom_level"].value) + 1
for zoomLevel in range(numLevels):
zoomTableEntry = self["tile_header"][
"zoom_table_entry[%d]" % zoomLevel]
for poiIndex in range(zoomTableEntry["num_pois"].value):
yield POIData(self, "poi_data[%d,%d]" % (zoomLevel, poiIndex))
for zoomLevel in range(numLevels):
zoomTableEntry = self["tile_header"][
"zoom_table_entry[%d]" % zoomLevel]
for wayIndex in range(zoomTableEntry["num_ways"].value):
yield WayProperties(self, "way_props[%d,%d]" % (zoomLevel, wayIndex))
class ZoomSubFile(SeekableFieldSet):
def __init__(self, parent, name, zoomIntervalCfg, **kw):
SeekableFieldSet.__init__(self, parent, name, **kw)
self.zoomIntervalCfg = zoomIntervalCfg
def createFields(self):
if self["/have_debug"].value:
yield String(self, "signature", 16)
if self['signature'].value != "+++IndexStart+++":
raise ValueError
indexEntries = []
numTiles = None
i = 0
while True:
entry = TileIndexEntry(self, "tile_index_entry[]")
indexEntries.append(entry)
yield entry
i += 1
if numTiles is None:
# calculate number of tiles (TODO: better calc this from map
# bounding box)
firstOffset = self["tile_index_entry[0]"]["offset"].value
if self["/have_debug"].value:
firstOffset -= 16
numTiles = firstOffset / 5
if i >= numTiles:
break
for i, indexEntry in enumerate(indexEntries):
offset = indexEntry["offset"].value
self.seekByte(offset, relative=True)
if i != len(indexEntries) - 1:
next_offset = indexEntries[i + 1]["offset"].value
size = (next_offset - offset) * 8
else:
size = self.size - offset * 8
if size == 0:
# hachoir doesn't support empty field.
continue
yield TileData(self, "tile_data[%d]" % i, zoomIntervalCfg=self.zoomIntervalCfg, size=size)
class MapsforgeMapFile(Parser, RootSeekableFieldSet):
PARSER_TAGS = {
"id": "mapsforge_map",
"category": "misc",
"file_ext": ("map",),
"min_size": 62 * 8,
"description": "Mapsforge map file",
}
endian = BIG_ENDIAN
def validate(self):
return self["file_magic"].value == "mapsforge binary OSM" and self["file_version"].value == 3
def createFields(self):
yield String(self, "file_magic", 20)
yield UInt32(self, "header_size")
yield UInt32(self, "file_version")
yield UInt64(self, "file_size")
yield UInt64(self, "creation_date")
yield Int32(self, "min_lat")
yield Int32(self, "min_lon")
yield Int32(self, "max_lat")
yield Int32(self, "max_lon")
yield UInt16(self, "tile_size")
yield VbeString(self, "projection")
# flags
yield Bit(self, "have_debug")
yield Bit(self, "have_map_start")
yield Bit(self, "have_start_zoom")
yield Bit(self, "have_language_preference")
yield Bit(self, "have_comment")
yield Bit(self, "have_created_by")
yield Bits(self, "reserved[]", 2)
if self["have_map_start"].value:
yield UInt32(self, "start_lat")
yield UInt32(self, "start_lon")
if self["have_start_zoom"].value:
yield UInt8(self, "start_zoom")
if self["have_language_preference"].value:
yield VbeString(self, "language_preference")
if self["have_comment"].value:
yield VbeString(self, "comment")
if self["have_created_by"].value:
yield VbeString(self, "created_by")
yield TagStringList(self, "poi_tags")
yield TagStringList(self, "way_tags")
yield UInt8(self, "num_zoom_intervals")
for i in range(self["num_zoom_intervals"].value):
yield ZoomIntervalCfg(self, "zoom_interval_cfg[]")
for i in range(self["num_zoom_intervals"].value):
zoomIntervalCfg = self["zoom_interval_cfg[%d]" % i]
self.seekByte(zoomIntervalCfg[
"subfile_start"].value, relative=False)
yield ZoomSubFile(self, "subfile[]", size=zoomIntervalCfg["subfile_size"].value * 8, zoomIntervalCfg=zoomIntervalCfg)