mirror of
https://github.com/tumic0/GPXSee.git
synced 2025-06-28 12:09:15 +02:00
Added support for Mapsforge maps
This commit is contained in:
22
src/map/mapsforge/cmp.h
Normal file
22
src/map/mapsforge/cmp.h
Normal file
@ -0,0 +1,22 @@
|
||||
#ifndef MAPSFORGE_CMP_H
|
||||
#define MAPSFORGE_CMP_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <cstring>
|
||||
|
||||
namespace Mapsforge {
|
||||
|
||||
inline bool cmp(const QByteArray &b1, const QByteArray &b2)
|
||||
{
|
||||
int len = b1.length();
|
||||
|
||||
if (!len)
|
||||
return true;
|
||||
if (len != b2.length())
|
||||
return false;
|
||||
return !memcmp(b1.constData(), b2.constData(), len);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // MAPSFORGE_CMP_H
|
714
src/map/mapsforge/mapdata.cpp
Normal file
714
src/map/mapsforge/mapdata.cpp
Normal file
@ -0,0 +1,714 @@
|
||||
#include <cstring>
|
||||
#include <QtEndian>
|
||||
#include <QFile>
|
||||
#include <QDataStream>
|
||||
#include <QColor>
|
||||
#include "map/osm.h"
|
||||
#include "subfile.h"
|
||||
#include "mapdata.h"
|
||||
|
||||
using namespace Mapsforge;
|
||||
|
||||
#define MAGIC "mapsforge binary OSM"
|
||||
#define MD(val) ((val) / 1e6)
|
||||
#define OFFSET_MASK 0x7FFFFFFFFFL
|
||||
|
||||
static uint pointType(const QVector<MapData::Tag> &tags)
|
||||
{
|
||||
for (int i = 0; i < tags.size(); i++) {
|
||||
const MapData::Tag &tag = tags.at(i);
|
||||
if (tag.key == "place") {
|
||||
if (tag.value == "country")
|
||||
return 4;
|
||||
else if (tag.value == "city")
|
||||
return 3;
|
||||
else if (tag.value == "town")
|
||||
return 2;
|
||||
else if (tag.value == "village")
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void setPointId(MapData::Point &p)
|
||||
{
|
||||
uint hash = (uint)qHash(QPair<double,double>((uint)qHash(
|
||||
QPair<double, double>(p.coordinates.lon(), p.coordinates.lat())),
|
||||
(uint)qHash(p.label)));
|
||||
uint type = pointType(p.tags);
|
||||
|
||||
p.id = ((quint64)type)<<32 | hash;
|
||||
}
|
||||
|
||||
static void copyPaths(const RectC &rect, const QList<MapData::Path> *src,
|
||||
QList<MapData::Path> *dst)
|
||||
{
|
||||
for (int i = 0; i < src->size(); i++)
|
||||
if (rect.intersects(src->at(i).poly.boundingRect()))
|
||||
dst->append(src->at(i));
|
||||
}
|
||||
|
||||
static void copyPoints(const RectC &rect, const QList<MapData::Point> *src,
|
||||
QList<MapData::Point> *dst)
|
||||
{
|
||||
for (int i = 0; i < src->size(); i++)
|
||||
if (rect.contains(src->at(i).coordinates))
|
||||
dst->append(src->at(i));
|
||||
}
|
||||
|
||||
static double distance(const Coordinates &c1, const Coordinates &c2)
|
||||
{
|
||||
return hypot(c1.lon() - c2.lon(), c1.lat() - c2.lat());
|
||||
}
|
||||
|
||||
static bool isClosed(const Polygon &poly)
|
||||
{
|
||||
if (poly.isEmpty() || poly.first().isEmpty())
|
||||
return false;
|
||||
return (distance(poly.first().first(), poly.first().last()) < 0.000000001);
|
||||
}
|
||||
|
||||
static bool readTags(SubFile &subfile, int count,
|
||||
const QVector<MapData::Tag> &tags, QVector<MapData::Tag> &list)
|
||||
{
|
||||
QVector<quint32> ids(count);
|
||||
|
||||
list.resize(count);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (!subfile.readVUInt32(ids[i]))
|
||||
return false;
|
||||
if (ids[i] >= (quint32)tags.size())
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
const MapData::Tag &tag = tags.at(ids.at(i));
|
||||
|
||||
if (tag.value.length() == 2 && tag.value.at(0) == '%') {
|
||||
QByteArray value;
|
||||
|
||||
if (tag.value.at(1) == 'b') {
|
||||
quint8 b;
|
||||
if (!subfile.readByte(b))
|
||||
return false;
|
||||
value.setNum(b);
|
||||
} else if (tag.value.at(1) == 'i') {
|
||||
qint32 u;
|
||||
if (!subfile.readInt32(u))
|
||||
return false;
|
||||
|
||||
if (tag.key.contains(":colour"))
|
||||
value = QColor((quint32)u).name().toLatin1();
|
||||
else
|
||||
value.setNum(u);
|
||||
} else if (tag.value.at(1) == 'f') {
|
||||
quint32 u;
|
||||
if (!subfile.readUInt32(u))
|
||||
return false;
|
||||
float *f = (float *)&u;
|
||||
value.setNum(*f);
|
||||
} else if (tag.value.at(1) == 'h') {
|
||||
quint16 s;
|
||||
if (!subfile.readUInt16(s))
|
||||
return false;
|
||||
value.setNum(s);
|
||||
} else if (tag.value.at(1) == 's') {
|
||||
if (!subfile.readString(value))
|
||||
return false;
|
||||
} else
|
||||
value = tag.value;
|
||||
|
||||
list[i] = MapData::Tag(tag.key, value);
|
||||
} else
|
||||
list[i] = tag;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool readSingleDelta(SubFile &subfile, const Coordinates &c,
|
||||
int count, QVector<Coordinates> &nodes)
|
||||
{
|
||||
qint32 mdLat, mdLon;
|
||||
|
||||
if (!(subfile.readVInt32(mdLat) && subfile.readVInt32(mdLon)))
|
||||
return false;
|
||||
|
||||
double lat = c.lat() + MD(mdLat);
|
||||
double lon = c.lon() + MD(mdLon);
|
||||
|
||||
nodes.reserve(count);
|
||||
nodes.append(Coordinates(lon, lat));
|
||||
|
||||
for (int i = 1; i < count; i++) {
|
||||
if (!(subfile.readVInt32(mdLat) && subfile.readVInt32(mdLon)))
|
||||
return false;
|
||||
|
||||
lat = lat + MD(mdLat);
|
||||
lon = lon + MD(mdLon);
|
||||
|
||||
nodes.append(Coordinates(lon, lat));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool readDoubleDelta(SubFile &subfile, const Coordinates &c,
|
||||
int count, QVector<Coordinates> &nodes)
|
||||
{
|
||||
qint32 mdLat, mdLon;
|
||||
|
||||
if (!(subfile.readVInt32(mdLat) && subfile.readVInt32(mdLon)))
|
||||
return false;
|
||||
|
||||
double lat = c.lat() + MD(mdLat);
|
||||
double lon = c.lon() + MD(mdLon);
|
||||
double prevLat = 0;
|
||||
double prevLon = 0;
|
||||
|
||||
nodes.reserve(count);
|
||||
nodes.append(Coordinates(lon, lat));
|
||||
|
||||
for (int i = 1; i < count; i++) {
|
||||
if (!(subfile.readVInt32(mdLat) && subfile.readVInt32(mdLon)))
|
||||
return false;
|
||||
|
||||
double singleLat = MD(mdLat) + prevLat;
|
||||
double singleLon = MD(mdLon) + prevLon;
|
||||
|
||||
lat += singleLat;
|
||||
lon += singleLon;
|
||||
|
||||
nodes.append(Coordinates(lon, lat));
|
||||
|
||||
prevLat = singleLat;
|
||||
prevLon = singleLon;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool readPolygon(SubFile &subfile, const Coordinates &c,
|
||||
bool doubleDelta, Polygon &polygon)
|
||||
{
|
||||
quint32 blocks, nodes;
|
||||
|
||||
if (!subfile.readVUInt32(blocks))
|
||||
return false;
|
||||
|
||||
polygon.reserve(blocks);
|
||||
for (quint32 i = 0; i < blocks; i++) {
|
||||
if (!subfile.readVUInt32(nodes) || !nodes)
|
||||
return false;
|
||||
|
||||
QVector<Coordinates> path;
|
||||
|
||||
if (doubleDelta) {
|
||||
if (!readDoubleDelta(subfile, c, nodes, path))
|
||||
return false;
|
||||
} else {
|
||||
if (!readSingleDelta(subfile, c, nodes, path))
|
||||
return false;
|
||||
}
|
||||
|
||||
polygon.append(path);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool readOffset(QDataStream &stream, quint64 &offset)
|
||||
{
|
||||
quint8 b0, b1, b2, b3, b4;
|
||||
|
||||
stream >> b0 >> b1 >> b2 >> b3 >> b4;
|
||||
offset = b4 | ((quint64)b3) << 8 | ((quint64)b2) << 16
|
||||
| ((quint64)b1) << 24 | ((quint64)b0) << 32;
|
||||
|
||||
return (stream.status() == QDataStream::Ok);
|
||||
}
|
||||
|
||||
bool MapData::readSubFiles()
|
||||
{
|
||||
QDataStream stream(&_file);
|
||||
|
||||
for (int i = 0; i < _subFiles.size(); i++) {
|
||||
const SubFileInfo &f = _subFiles.at(i);
|
||||
quint64 offset, nextOffset;
|
||||
|
||||
stream.device()->seek(f.offset);
|
||||
|
||||
QPoint tl(OSM::ll2tile(_bounds.topLeft(), f.base));
|
||||
QPoint br(OSM::ll2tile(_bounds.bottomRight(), f.base));
|
||||
|
||||
if (!readOffset(stream, offset) || (offset & OFFSET_MASK) > f.size)
|
||||
return false;
|
||||
|
||||
_tiles.append(new TileTree());
|
||||
|
||||
for (int h = tl.y(); h <= br.y(); h++) {
|
||||
for (int w = tl.x(); w <= br.x(); w++) {
|
||||
if (!(h == br.y() && w == br.x())) {
|
||||
if (!readOffset(stream, nextOffset)
|
||||
|| (nextOffset & OFFSET_MASK) > f.size)
|
||||
return false;
|
||||
if (nextOffset == offset)
|
||||
continue;
|
||||
}
|
||||
|
||||
Coordinates ttl(OSM::tile2ll(QPoint(w, h), f.base));
|
||||
ttl = Coordinates(ttl.lon(), -ttl.lat());
|
||||
Coordinates tbr(OSM::tile2ll(QPoint(w + 1, h + 1), f.base));
|
||||
tbr = Coordinates(tbr.lon(), -tbr.lat());
|
||||
RectC bounds(ttl, tbr);
|
||||
|
||||
double min[2], max[2];
|
||||
min[0] = bounds.left();
|
||||
min[1] = bounds.bottom();
|
||||
max[0] = bounds.right();
|
||||
max[1] = bounds.top();
|
||||
_tiles.last()->Insert(min, max, new VectorTile(offset, bounds));
|
||||
|
||||
offset = nextOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MapData::readHeader()
|
||||
{
|
||||
char magic[sizeof(MAGIC) - 1];
|
||||
quint32 hdrSize, version;
|
||||
quint64 fileSize, date;
|
||||
qint32 minLat, minLon, maxLat, maxLon;
|
||||
quint16 tags;
|
||||
quint8 flags, zooms;
|
||||
QByteArray projection, tag;
|
||||
|
||||
|
||||
if (_file.read(magic, sizeof(magic)) < (int)sizeof(magic)
|
||||
|| memcmp(magic, MAGIC, sizeof(magic)))
|
||||
return false;
|
||||
if (_file.read((char*)&hdrSize, sizeof(hdrSize)) < (qint64)sizeof(hdrSize))
|
||||
return false;
|
||||
|
||||
SubFile subfile(_file, sizeof(magic) + sizeof(hdrSize),
|
||||
qFromBigEndian(hdrSize));
|
||||
if (!subfile.seek(0))
|
||||
return false;
|
||||
if (!(subfile.readUInt32(version) && subfile.readUInt64(fileSize)
|
||||
&& subfile.readUInt64(date) && subfile.readInt32(minLat)
|
||||
&& subfile.readInt32(minLon) && subfile.readInt32(maxLat)
|
||||
&& subfile.readInt32(maxLon) && subfile.readUInt16(_tileSize)
|
||||
&& subfile.readString(projection) && subfile.readByte(flags)))
|
||||
return false;
|
||||
|
||||
if (projection != "Mercator") {
|
||||
_errorString = projection + ": invalid/unsupported projection";
|
||||
return false;
|
||||
}
|
||||
if (flags & 0x80) {
|
||||
_errorString = "DEBUG maps not supported";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (flags & 0x40) {
|
||||
qint32 startLon, startLat;
|
||||
if (!(subfile.readInt32(startLat) && subfile.readInt32(startLon)))
|
||||
return false;
|
||||
}
|
||||
if (flags & 0x20) {
|
||||
quint8 startZoom;
|
||||
if (!subfile.readByte(startZoom))
|
||||
return false;
|
||||
}
|
||||
if (flags & 0x10) {
|
||||
QByteArray lang;
|
||||
if (!subfile.readString(lang))
|
||||
return false;
|
||||
}
|
||||
if (flags & 0x08) {
|
||||
QByteArray comment;
|
||||
if (!subfile.readString(comment))
|
||||
return false;
|
||||
}
|
||||
if (flags & 0x04) {
|
||||
QByteArray createdBy;
|
||||
if (!subfile.readString(createdBy))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!subfile.readUInt16(tags))
|
||||
return false;
|
||||
_pointTags.resize(tags);
|
||||
for (quint16 i = 0; i < tags; i++) {
|
||||
if (!subfile.readString(tag))
|
||||
return false;
|
||||
_pointTags[i] = tag;
|
||||
}
|
||||
|
||||
if (!subfile.readUInt16(tags))
|
||||
return false;
|
||||
_pathTags.resize(tags);
|
||||
for (quint16 i = 0; i < tags; i++) {
|
||||
if (!subfile.readString(tag))
|
||||
return false;
|
||||
_pathTags[i] = tag;
|
||||
}
|
||||
|
||||
if (!subfile.readByte(zooms))
|
||||
return false;
|
||||
_subFiles.resize(zooms);
|
||||
for (quint8 i = 0; i < zooms; i++) {
|
||||
if (!(subfile.readByte(_subFiles[i].base)
|
||||
&& subfile.readByte(_subFiles[i].min)
|
||||
&& subfile.readByte(_subFiles[i].max)
|
||||
&& subfile.readUInt64(_subFiles[i].offset)
|
||||
&& subfile.readUInt64(_subFiles[i].size)))
|
||||
return false;
|
||||
}
|
||||
|
||||
_bounds = RectC(Coordinates(MD(minLon), MD(maxLat)),
|
||||
Coordinates(MD(maxLon), MD(minLat)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MapData::MapData(const QString &fileName) : _file(fileName)
|
||||
{
|
||||
if (!_file.open(QFile::ReadOnly)) {
|
||||
_errorString = _file.errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!readHeader())
|
||||
return;
|
||||
|
||||
_file.close();
|
||||
|
||||
_pathCache.setMaxCost(256);
|
||||
_pointCache.setMaxCost(256);
|
||||
|
||||
_valid = true;
|
||||
}
|
||||
|
||||
MapData::~MapData()
|
||||
{
|
||||
clearTiles();
|
||||
}
|
||||
|
||||
RectC MapData::bounds() const
|
||||
{
|
||||
/* Align the map bounds with the OSM tiles to prevent area overlap artifacts
|
||||
at least when using EPSG:3857 projection. */
|
||||
|
||||
int zoom = _subFiles.last().base;
|
||||
|
||||
QPoint tl(OSM::mercator2tile(OSM::ll2m(_bounds.topLeft()), zoom));
|
||||
QPoint br(OSM::mercator2tile(OSM::ll2m(_bounds.bottomRight()), zoom));
|
||||
Coordinates ctl(OSM::tile2ll(tl, zoom));
|
||||
ctl.rlat() = -ctl.lat();
|
||||
Coordinates cbr(OSM::tile2ll(br, zoom));
|
||||
cbr.rlat() = -cbr.lat();
|
||||
|
||||
return RectC(ctl, cbr);
|
||||
}
|
||||
|
||||
void MapData::load()
|
||||
{
|
||||
if (_file.open(QIODevice::ReadOnly))
|
||||
readSubFiles();
|
||||
}
|
||||
|
||||
void MapData::clear()
|
||||
{
|
||||
_file.close();
|
||||
|
||||
_pathCache.clear();
|
||||
_pointCache.clear();
|
||||
|
||||
clearTiles();
|
||||
}
|
||||
|
||||
void MapData::clearTiles()
|
||||
{
|
||||
TileTree::Iterator it;
|
||||
|
||||
for (int i = 0; i < _tiles.size(); i++) {
|
||||
TileTree *t = _tiles.at(i);
|
||||
for (t->GetFirst(it); !t->IsNull(it); t->GetNext(it))
|
||||
delete t->GetAt(it);
|
||||
}
|
||||
|
||||
qDeleteAll(_tiles);
|
||||
_tiles.clear();
|
||||
}
|
||||
|
||||
bool MapData::pathCb(VectorTile *tile, void *context)
|
||||
{
|
||||
PathCTX *ctx = (PathCTX*)context;
|
||||
ctx->data->paths(tile, ctx->rect, ctx->zoom, ctx->list);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MapData::pointCb(VectorTile *tile, void *context)
|
||||
{
|
||||
PointCTX *ctx = (PointCTX*)context;
|
||||
ctx->data->points(tile, ctx->rect, ctx->zoom, ctx->list);
|
||||
return true;
|
||||
}
|
||||
|
||||
int MapData::level(int zoom) const
|
||||
{
|
||||
for (int i = 0; i < _subFiles.size(); i++)
|
||||
if (zoom <= _subFiles.at(i).max)
|
||||
return i;
|
||||
|
||||
return _subFiles.size() - 1;
|
||||
}
|
||||
|
||||
void MapData::points(const RectC &rect, int zoom, QList<Point> *list)
|
||||
{
|
||||
int l(level(zoom));
|
||||
PointCTX ctx(this, rect, zoom, list);
|
||||
double min[2], max[2];
|
||||
|
||||
min[0] = rect.left();
|
||||
min[1] = rect.bottom();
|
||||
max[0] = rect.right();
|
||||
max[1] = rect.top();
|
||||
|
||||
_tiles.at(l)->Search(min, max, pointCb, &ctx);
|
||||
}
|
||||
|
||||
void MapData::points(const VectorTile *tile, const RectC &rect, int zoom,
|
||||
QList<Point> *list)
|
||||
{
|
||||
Key key(tile, zoom);
|
||||
QList<Point> *cached = _pointCache.object(key);
|
||||
|
||||
if (!cached) {
|
||||
QList<Point> *p = new QList<Point>();
|
||||
if (readPoints(tile, zoom, p)) {
|
||||
copyPoints(rect, p, list);
|
||||
_pointCache.insert(key, p);
|
||||
} else
|
||||
delete p;
|
||||
} else
|
||||
copyPoints(rect, cached, list);
|
||||
}
|
||||
|
||||
void MapData::paths(const RectC &rect, int zoom, QList<Path> *list)
|
||||
{
|
||||
int l(level(zoom));
|
||||
PathCTX ctx(this, rect, zoom, list);
|
||||
double min[2], max[2];
|
||||
|
||||
min[0] = rect.left();
|
||||
min[1] = rect.bottom();
|
||||
max[0] = rect.right();
|
||||
max[1] = rect.top();
|
||||
|
||||
_tiles.at(l)->Search(min, max, pathCb, &ctx);
|
||||
}
|
||||
|
||||
void MapData::paths(const VectorTile *tile, const RectC &rect, int zoom,
|
||||
QList<Path> *list)
|
||||
{
|
||||
Key key(tile, zoom);
|
||||
QList<Path> *cached = _pathCache.object(key);
|
||||
|
||||
if (!cached) {
|
||||
QList<Path> *p = new QList<Path>();
|
||||
if (readPaths(tile, zoom, p)) {
|
||||
copyPaths(rect, p, list);
|
||||
_pathCache.insert(key, p);
|
||||
} else
|
||||
delete p;
|
||||
} else
|
||||
copyPaths(rect, cached, list);
|
||||
}
|
||||
|
||||
bool MapData::readPaths(const VectorTile *tile, int zoom, QList<Path> *list)
|
||||
{
|
||||
const SubFileInfo &info = _subFiles.at(level(zoom));
|
||||
SubFile subfile(_file, info.offset, info.size);
|
||||
int rows = info.max - info.min + 1;
|
||||
QVector<unsigned> paths(rows);
|
||||
quint32 blocks, unused, val, cnt = 0;
|
||||
quint16 bitmap;
|
||||
quint8 flags;
|
||||
QByteArray label, houseNumber, reference;
|
||||
|
||||
|
||||
if (!subfile.seek(tile->offset & OFFSET_MASK))
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < rows; i++) {
|
||||
if (!(subfile.readVUInt32(unused) && subfile.readVUInt32(val)))
|
||||
return false;
|
||||
cnt += val;
|
||||
paths[i] = cnt;
|
||||
}
|
||||
|
||||
if (!subfile.readVUInt32(val))
|
||||
return false;
|
||||
if (!subfile.seek(subfile.pos() + val))
|
||||
return false;
|
||||
|
||||
for (unsigned i = 0; i < paths[zoom - info.min]; i++) {
|
||||
Path p;
|
||||
|
||||
if (!(subfile.readVUInt32(unused) && subfile.readUInt16(bitmap)
|
||||
&& subfile.readByte(flags)))
|
||||
return false;
|
||||
|
||||
p.layer = flags >> 4;
|
||||
int tags = flags & 0x0F;
|
||||
if (!readTags(subfile, tags, _pathTags, p.tags))
|
||||
return false;
|
||||
|
||||
if (!subfile.readByte(flags))
|
||||
return false;
|
||||
if (flags & 0x80) {
|
||||
if (!subfile.readString(label))
|
||||
return false;
|
||||
p.label = label.split('\r').first();
|
||||
}
|
||||
if (flags & 0x40) {
|
||||
if (!subfile.readString(houseNumber))
|
||||
return false;
|
||||
}
|
||||
if (flags & 0x20) {
|
||||
if (!subfile.readString(reference))
|
||||
return false;
|
||||
p.reference = reference;
|
||||
}
|
||||
if (flags & 0x10) {
|
||||
qint32 unused;
|
||||
if (!(subfile.readVInt32(unused) && subfile.readVInt32(unused)))
|
||||
return false;
|
||||
}
|
||||
if (flags & 0x08) {
|
||||
if (!subfile.readVUInt32(blocks))
|
||||
return false;
|
||||
} else
|
||||
blocks = 1;
|
||||
|
||||
Q_ASSERT(blocks);
|
||||
for (unsigned j = 0; j < blocks; j++) {
|
||||
if (!readPolygon(subfile, tile->pos, flags & 0x04, p.poly))
|
||||
return false;
|
||||
p.closed = isClosed(p.poly);
|
||||
|
||||
list->append(p);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MapData::readPoints(const VectorTile *tile, int zoom, QList<Point> *list)
|
||||
{
|
||||
const SubFileInfo &info = _subFiles.at(level(zoom));
|
||||
SubFile subfile(_file, info.offset, info.size);
|
||||
int rows = info.max - info.min + 1;
|
||||
QVector<unsigned> points(rows);
|
||||
quint32 val, unused, cnt = 0;
|
||||
quint8 flags;
|
||||
QByteArray label, houseNumber;
|
||||
|
||||
|
||||
if (!subfile.seek(tile->offset & OFFSET_MASK))
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < rows; i++) {
|
||||
if (!(subfile.readVUInt32(val) && subfile.readVUInt32(unused)))
|
||||
return false;
|
||||
cnt += val;
|
||||
points[i] = cnt;
|
||||
}
|
||||
|
||||
if (!subfile.readVUInt32(unused))
|
||||
return false;
|
||||
|
||||
for (unsigned i = 0; i < points[zoom - info.min]; i++) {
|
||||
qint32 lat, lon;
|
||||
|
||||
if (!(subfile.readVInt32(lat) && subfile.readVInt32(lon)))
|
||||
return false;
|
||||
Point p(Coordinates(tile->pos.lon() + MD(lon),
|
||||
tile->pos.lat() + MD(lat)));
|
||||
|
||||
if (!subfile.readByte(flags))
|
||||
return false;
|
||||
p.layer = flags >> 4;
|
||||
int tags = flags & 0x0F;
|
||||
if (!readTags(subfile, tags, _pointTags, p.tags))
|
||||
return false;
|
||||
|
||||
if (!subfile.readByte(flags))
|
||||
return false;
|
||||
if (flags & 0x80) {
|
||||
if (!subfile.readString(label))
|
||||
return false;
|
||||
p.label = label.split('\r').first();
|
||||
}
|
||||
if (flags & 0x40) {
|
||||
if (!subfile.readString(houseNumber))
|
||||
return false;
|
||||
}
|
||||
if (flags & 0x20) {
|
||||
qint32 unused;
|
||||
if (!subfile.readVInt32(unused))
|
||||
return false;
|
||||
}
|
||||
|
||||
setPointId(p);
|
||||
list->append(p);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MapData::isMapsforge(const QString &path)
|
||||
{
|
||||
QFile file(path);
|
||||
char magic[sizeof(MAGIC) - 1];
|
||||
|
||||
if (!file.open(QFile::ReadOnly))
|
||||
return false;
|
||||
if (file.read(magic, sizeof(magic)) < (qint64)sizeof(magic))
|
||||
return false;
|
||||
|
||||
return !memcmp(magic, MAGIC, sizeof(magic));
|
||||
}
|
||||
|
||||
#ifndef QT_NO_DEBUG
|
||||
QDebug operator<<(QDebug dbg, const Mapsforge::MapData::Tag &tag)
|
||||
{
|
||||
dbg.nospace() << "Tag(" << tag.key << ", " << tag.value << ")";
|
||||
return dbg.space();
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug dbg, const MapData::Path &path)
|
||||
{
|
||||
dbg.nospace() << "Path(" << path.poly.boundingRect() << ", " << path.label
|
||||
<< ", " << path.tags << ")";
|
||||
return dbg.space();
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug dbg, const MapData::Point &point)
|
||||
{
|
||||
dbg.nospace() << "Point(" << point.coordinates << "," << point.label
|
||||
<< ", " << point.tags << ")";
|
||||
return dbg.space();
|
||||
}
|
||||
#endif // QT_NO_DEBUG
|
183
src/map/mapsforge/mapdata.h
Normal file
183
src/map/mapsforge/mapdata.h
Normal file
@ -0,0 +1,183 @@
|
||||
#ifndef MAPSFORGE_MAPDATA_H
|
||||
#define MAPSFORGE_MAPDATA_H
|
||||
|
||||
#include <QFile>
|
||||
#include <QCache>
|
||||
#include <QPainterPath>
|
||||
#include "common/config.h"
|
||||
#include "common/rectc.h"
|
||||
#include "common/rtree.h"
|
||||
#include "common/range.h"
|
||||
#include "common/polygon.h"
|
||||
#include "cmp.h"
|
||||
|
||||
|
||||
namespace Mapsforge {
|
||||
|
||||
class MapData
|
||||
{
|
||||
public:
|
||||
MapData(const QString &path);
|
||||
~MapData();
|
||||
|
||||
struct Tag {
|
||||
Tag() {}
|
||||
Tag(const QByteArray &key, const QByteArray &value)
|
||||
: key(key), value(value) {}
|
||||
Tag(const QByteArray &str)
|
||||
{
|
||||
QList<QByteArray> l(str.split('='));
|
||||
if (l.size() == 2) {
|
||||
key = l.at(0);
|
||||
value = l.at(1);
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(const Tag &other) const
|
||||
{return cmp(key, other.key) && cmp(value, other.value);}
|
||||
|
||||
QByteArray key;
|
||||
QByteArray value;
|
||||
};
|
||||
|
||||
struct Point {
|
||||
Point(const Coordinates &c) : coordinates(c) {}
|
||||
|
||||
Coordinates coordinates;
|
||||
QString label;
|
||||
QVector<Tag> tags;
|
||||
int layer;
|
||||
quint64 id;
|
||||
|
||||
bool operator<(const Point &other) const
|
||||
{return id > other.id;}
|
||||
};
|
||||
|
||||
struct Path {
|
||||
Polygon poly;
|
||||
QString label;
|
||||
QString reference;
|
||||
QVector<Tag> tags;
|
||||
int layer;
|
||||
bool closed;
|
||||
|
||||
QPainterPath path;
|
||||
|
||||
bool operator<(const Path &other) const
|
||||
{return layer < other.layer;}
|
||||
};
|
||||
|
||||
RectC bounds() const;
|
||||
Range zooms() const
|
||||
{return Range(_subFiles.first().min, _subFiles.last().max);}
|
||||
int tileSize() const {return _tileSize;}
|
||||
|
||||
void points(const RectC &rect, int zoom, QList<Point> *list);
|
||||
void paths(const RectC &rect, int zoom, QList<Path> *list);
|
||||
|
||||
void load();
|
||||
void clear();
|
||||
|
||||
bool isValid() const {return _valid;}
|
||||
QString errorString() const {return _errorString;}
|
||||
|
||||
static bool isMapsforge(const QString &path);
|
||||
|
||||
private:
|
||||
struct SubFileInfo {
|
||||
quint8 base;
|
||||
quint8 min;
|
||||
quint8 max;
|
||||
quint64 offset;
|
||||
quint64 size;
|
||||
};
|
||||
|
||||
struct VectorTile {
|
||||
VectorTile(size_t offset, const RectC &rect)
|
||||
: offset(offset), pos(rect.topLeft()) {}
|
||||
|
||||
size_t offset;
|
||||
Coordinates pos;
|
||||
};
|
||||
|
||||
struct PathCTX {
|
||||
PathCTX(MapData *data, const RectC &rect, int zoom, QList<Path> *list)
|
||||
: data(data), rect(rect), zoom(zoom), list(list) {}
|
||||
|
||||
MapData *data;
|
||||
const RectC ▭
|
||||
int zoom;
|
||||
QList<Path> *list;
|
||||
};
|
||||
|
||||
struct PointCTX {
|
||||
PointCTX(MapData *data, const RectC &rect, int zoom, QList<Point> *list)
|
||||
: data(data), rect(rect), zoom(zoom), list(list) {}
|
||||
|
||||
MapData *data;
|
||||
const RectC ▭
|
||||
int zoom;
|
||||
QList<Point> *list;
|
||||
};
|
||||
|
||||
struct Key {
|
||||
Key(const VectorTile *tile, int zoom) : tile(tile), zoom(zoom) {}
|
||||
bool operator==(const Key &other) const
|
||||
{return tile == other.tile && zoom == other.zoom;}
|
||||
|
||||
const VectorTile *tile;
|
||||
int zoom;
|
||||
};
|
||||
|
||||
typedef RTree<VectorTile *, double, 2> TileTree;
|
||||
|
||||
bool readHeader();
|
||||
bool readSubFiles();
|
||||
void clearTiles();
|
||||
|
||||
int level(int zoom) const;
|
||||
void paths(const VectorTile *tile, const RectC &rect, int zoom,
|
||||
QList<Path> *list);
|
||||
void points(const VectorTile *tile, const RectC &rect, int zoom,
|
||||
QList<Point> *list);
|
||||
bool readPaths(const VectorTile *tile, int zoom, QList<Path> *list);
|
||||
bool readPoints(const VectorTile *tile, int zoom, QList<Point> *list);
|
||||
|
||||
static bool pathCb(VectorTile *tile, void *context);
|
||||
static bool pointCb(VectorTile *tile, void *context);
|
||||
|
||||
friend HASH_T qHash(const MapData::Key &key);
|
||||
|
||||
QFile _file;
|
||||
RectC _bounds;
|
||||
quint16 _tileSize;
|
||||
QVector<Tag> _pointTags, _pathTags;
|
||||
QVector<SubFileInfo> _subFiles;
|
||||
QList<TileTree*> _tiles;
|
||||
|
||||
QCache<Key, QList<Path> > _pathCache;
|
||||
QCache<Key, QList<Point> > _pointCache;
|
||||
|
||||
bool _valid;
|
||||
QString _errorString;
|
||||
};
|
||||
|
||||
inline HASH_T qHash(const MapData::Key &key)
|
||||
{
|
||||
return ::qHash(key.tile) ^ ::qHash(key.zoom);
|
||||
}
|
||||
|
||||
inline HASH_T qHash(const MapData::Tag &tag)
|
||||
{
|
||||
return ::qHash(tag.key) ^ ::qHash(tag.value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#ifndef QT_NO_DEBUG
|
||||
QDebug operator<<(QDebug dbg, const Mapsforge::MapData::Tag &tag);
|
||||
QDebug operator<<(QDebug dbg, const Mapsforge::MapData::Path &path);
|
||||
QDebug operator<<(QDebug dbg, const Mapsforge::MapData::Point &point);
|
||||
#endif // QT_NO_DEBUG
|
||||
|
||||
#endif // MAPSFORGE_MAPDATA_H
|
184
src/map/mapsforge/rastertile.cpp
Normal file
184
src/map/mapsforge/rastertile.cpp
Normal file
@ -0,0 +1,184 @@
|
||||
#include <QPainter>
|
||||
#include <QCache>
|
||||
#include "common/programpaths.h"
|
||||
#include "map/mapsforgemap.h"
|
||||
#include "map/textpathitem.h"
|
||||
#include "map/textpointitem.h"
|
||||
#include "rastertile.h"
|
||||
|
||||
using namespace Mapsforge;
|
||||
|
||||
static const Style& style()
|
||||
{
|
||||
static Style s(ProgramPaths::renderthemeFile());
|
||||
return s;
|
||||
}
|
||||
|
||||
void RasterTile::processPoints(QList<TextItem*> &textItems)
|
||||
{
|
||||
const Style &s = style();
|
||||
|
||||
for (int i = 0; i < _points.size(); i++) {
|
||||
const MapData::Point &point = _points.at(i);
|
||||
const QString *label = point.label.isEmpty() ? 0 : &(point.label);
|
||||
const Style::TextRender *ti = 0;
|
||||
const Style::Symbol *si = 0;
|
||||
|
||||
if (label) {
|
||||
for (int i = 0; i < s.pointLabels().size(); i++) {
|
||||
const Style::TextRender &ri = s.pointLabels().at(i);
|
||||
if (ri.rule().match(_zoom, point.tags)) {
|
||||
ti = &ri;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < s.symbols().size(); i++) {
|
||||
const Style::Symbol &ri = s.symbols().at(i);
|
||||
if (ri.rule().match(_zoom, point.tags)) {
|
||||
si = &ri;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ti && !si)
|
||||
continue;
|
||||
|
||||
const QImage *img = si ? &si->img() : 0;
|
||||
const QFont *font = ti ? &ti->font() : 0;
|
||||
const QColor *color = ti ? &ti->fillColor() : 0;
|
||||
|
||||
TextPointItem *item = new TextPointItem(
|
||||
ll2xy(point.coordinates).toPoint(), label, font, img, color, 0, false);
|
||||
if (item->isValid() && !item->collides(textItems))
|
||||
textItems.append(item);
|
||||
else
|
||||
delete item;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void RasterTile::processStreetNames(const QRect &tileRect,
|
||||
QList<TextItem*> &textItems)
|
||||
{
|
||||
const Style &s = style();
|
||||
|
||||
for (int i = 0; i < s.pathLabels().size(); i++) {
|
||||
const Style::TextRender &ri = s.pathLabels().at(i);
|
||||
|
||||
for (int j = 0; j < _paths.size(); j++) {
|
||||
MapData::Path &path = _paths[j];
|
||||
|
||||
if (path.label.isEmpty())
|
||||
continue;
|
||||
if (!path.path.elementCount())
|
||||
continue;
|
||||
if (!ri.rule().match(_zoom, path.closed, path.tags))
|
||||
continue;
|
||||
|
||||
TextPathItem *item = new TextPathItem(path.path,
|
||||
&path.label, tileRect, &ri.font(), &ri.fillColor(),
|
||||
&ri.strokeColor());
|
||||
if (item->isValid() && !item->collides(textItems))
|
||||
textItems.append(item);
|
||||
else
|
||||
delete item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RasterTile::drawTextItems(QPainter *painter,
|
||||
const QList<TextItem*> &textItems)
|
||||
{
|
||||
for (int i = 0; i < textItems.size(); i++)
|
||||
textItems.at(i)->paint(painter);
|
||||
}
|
||||
|
||||
QPainterPath RasterTile::painterPath(const Polygon &polygon) const
|
||||
{
|
||||
QPainterPath path;
|
||||
|
||||
for (int i = 0; i < polygon.size(); i++) {
|
||||
const QVector<Coordinates> &subpath = polygon.at(i);
|
||||
|
||||
path.moveTo(ll2xy(subpath.first()));
|
||||
for (int j = 1; j < subpath.size(); j++)
|
||||
path.lineTo(ll2xy(subpath.at(j)));
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
QVector<RasterTile::PathInstruction> RasterTile::pathInstructions()
|
||||
{
|
||||
QCache<Key, QVector<const Style::PathRender *> > cache(1024);
|
||||
QVector<PathInstruction> instructions;
|
||||
const Style &s = style();
|
||||
|
||||
for (int i = 0 ; i < _paths.size(); i++) {
|
||||
MapData::Path &path = _paths[i];
|
||||
|
||||
Key key(_zoom, path.closed, path.tags);
|
||||
QVector<const Style::PathRender*> *cached = cache.object(key);
|
||||
if (!cached) {
|
||||
QVector<const Style::PathRender*> *ri
|
||||
= new QVector<const Style::PathRender*>();
|
||||
s.match(_zoom, path.closed, path.tags, ri);
|
||||
for (int j = 0; j < ri->size(); j++)
|
||||
instructions.append(PathInstruction(ri->at(j), &path));
|
||||
cache.insert(key, ri);
|
||||
} else {
|
||||
for (int j = 0; j < cached->size(); j++)
|
||||
instructions.append(PathInstruction(cached->at(j), &path));
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(instructions.begin(), instructions.end());
|
||||
|
||||
return instructions;
|
||||
}
|
||||
|
||||
void RasterTile::drawPaths(QPainter *painter)
|
||||
{
|
||||
QVector<PathInstruction> instructions(pathInstructions());
|
||||
|
||||
for (int i = 0; i < instructions.size(); i++) {
|
||||
PathInstruction &is = instructions[i];
|
||||
const Style::PathRender *ri = is.render();
|
||||
|
||||
painter->setPen(ri->pen(_zoom));
|
||||
painter->setBrush(ri->brush());
|
||||
|
||||
if (!is.path()->path.elementCount())
|
||||
is.path()->path = painterPath(is.path()->poly);
|
||||
|
||||
painter->drawPath(is.path()->path);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterTile::render()
|
||||
{
|
||||
std::sort(_points.begin(), _points.end());
|
||||
|
||||
QList<TextItem*> textItems;
|
||||
QRect tileRect(_xy, _img.size());
|
||||
|
||||
_img.fill(Qt::transparent);
|
||||
|
||||
QPainter painter(&_img);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.translate(-_xy.x(), -_xy.y());
|
||||
|
||||
drawPaths(&painter);
|
||||
|
||||
processPoints(textItems);
|
||||
processStreetNames(tileRect, textItems);
|
||||
drawTextItems(&painter, textItems);
|
||||
|
||||
//painter.setPen(Qt::red);
|
||||
//painter.setBrush(Qt::NoBrush);
|
||||
//painter.drawRect(QRect(_xy, _img.size()));
|
||||
|
||||
qDeleteAll(textItems);
|
||||
}
|
98
src/map/mapsforge/rastertile.h
Normal file
98
src/map/mapsforge/rastertile.h
Normal file
@ -0,0 +1,98 @@
|
||||
#ifndef MAPSFORGE_RASTERTILE_H
|
||||
#define MAPSFORGE_RASTERTILE_H
|
||||
|
||||
#include <QImage>
|
||||
#include "map/projection.h"
|
||||
#include "map/transform.h"
|
||||
#include "style.h"
|
||||
#include "mapdata.h"
|
||||
#include "cmp.h"
|
||||
|
||||
class MapsforgeMap;
|
||||
class TextItem;
|
||||
|
||||
namespace Mapsforge {
|
||||
|
||||
class RasterTile
|
||||
{
|
||||
public:
|
||||
RasterTile(const Projection &proj, const Transform &transform, int zoom,
|
||||
const QRect &rect, const QString &key, const QList<MapData::Path> &paths,
|
||||
const QList<MapData::Point> &points)
|
||||
: _proj(proj), _transform(transform), _zoom(zoom), _xy(rect.topLeft()),
|
||||
_key(key), _img(rect.size(), QImage::Format_ARGB32_Premultiplied),
|
||||
_paths(paths), _points(points) {}
|
||||
|
||||
const QString &key() const {return _key;}
|
||||
const QPoint &xy() const {return _xy;}
|
||||
const QImage &img() const {return _img;}
|
||||
|
||||
void render();
|
||||
|
||||
private:
|
||||
class PathInstruction
|
||||
{
|
||||
public:
|
||||
PathInstruction(const Style::PathRender *render, MapData::Path *path)
|
||||
: _render(render), _path(path) {}
|
||||
|
||||
bool operator<(const PathInstruction &other) const
|
||||
{
|
||||
if (_path->layer == other._path->layer)
|
||||
return _render->zOrder() < other._render->zOrder();
|
||||
else
|
||||
return (_path->layer < other._path->layer);
|
||||
}
|
||||
|
||||
const Style::PathRender *render() const {return _render;}
|
||||
MapData::Path *path() {return _path;}
|
||||
|
||||
private:
|
||||
const Style::PathRender *_render;
|
||||
MapData::Path *_path;
|
||||
};
|
||||
|
||||
struct Key {
|
||||
Key(int zoom, bool closed, const QVector<MapData::Tag> &tags)
|
||||
: zoom(zoom), closed(closed), tags(tags) {}
|
||||
bool operator==(const Key &other) const
|
||||
{
|
||||
return zoom == other.zoom && closed == other.closed
|
||||
&& tags == other.tags;
|
||||
}
|
||||
|
||||
int zoom;
|
||||
bool closed;
|
||||
const QVector<MapData::Tag> &tags;
|
||||
};
|
||||
|
||||
friend HASH_T qHash(const RasterTile::Key &key);
|
||||
friend HASH_T qHash(const RasterTile::PathInstruction &pi);
|
||||
|
||||
QVector<PathInstruction> pathInstructions();
|
||||
QPointF ll2xy(const Coordinates &c) const
|
||||
{return _transform.proj2img(_proj.ll2xy(c));}
|
||||
void processPoints(QList<TextItem*> &textItems);
|
||||
void processStreetNames(const QRect &tileRect, QList<TextItem*> &textItems);
|
||||
QPainterPath painterPath(const Polygon &polygon) const;
|
||||
void drawTextItems(QPainter *painter, const QList<TextItem*> &textItems);
|
||||
void drawPaths(QPainter *painter);
|
||||
|
||||
Projection _proj;
|
||||
Transform _transform;
|
||||
int _zoom;
|
||||
QPoint _xy;
|
||||
QString _key;
|
||||
QImage _img;
|
||||
QList<MapData::Path> _paths;
|
||||
QList<MapData::Point> _points;
|
||||
};
|
||||
|
||||
inline HASH_T qHash(const RasterTile::Key &key)
|
||||
{
|
||||
return ::qHash(key.zoom) ^ ::qHash(key.tags);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // MAPSFORGE_RASTERTILE_H
|
357
src/map/mapsforge/style.cpp
Normal file
357
src/map/mapsforge/style.cpp
Normal file
@ -0,0 +1,357 @@
|
||||
#include <QFile>
|
||||
#include <QXmlStreamReader>
|
||||
#include <QUrl>
|
||||
#include <QFileInfo>
|
||||
#include <QImageReader>
|
||||
#include "style.h"
|
||||
|
||||
using namespace Mapsforge;
|
||||
|
||||
static QString resourcePath(const QString &src, const QString &dir)
|
||||
{
|
||||
QUrl url(src);
|
||||
if (url.scheme().isEmpty())
|
||||
return src;
|
||||
else
|
||||
return dir + "/" + url.toLocalFile();
|
||||
}
|
||||
|
||||
static QImage image(const QString &path, int width, int height)
|
||||
{
|
||||
QImageReader ir(path, "svg");
|
||||
|
||||
if (ir.canRead()) {
|
||||
if (!height && !width) {
|
||||
height = 20;
|
||||
width = 20;
|
||||
} else if (!width) {
|
||||
width = ir.size().height() / (ir.size().height() / (double)height);
|
||||
} else if (!height)
|
||||
height = ir.size().width() / (ir.size().width() / (double)width);
|
||||
|
||||
ir.setScaledSize(QSize(width, height));
|
||||
return ir.read();
|
||||
} else
|
||||
return QImage(path);
|
||||
}
|
||||
|
||||
bool Style::Rule::match(int zoom, bool closed,
|
||||
const QVector<MapData::Tag> &tags) const
|
||||
{
|
||||
Closed cl = closed ? YesClosed : NoClosed;
|
||||
|
||||
if (_type && WayType != _type)
|
||||
return false;
|
||||
if (!_zooms.contains(zoom))
|
||||
return false;
|
||||
if (_closed && cl != _closed)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < _filters.size(); i++)
|
||||
if (!_filters.at(i).match(tags))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Style::Rule::match(int zoom, const QVector<MapData::Tag> &tags) const
|
||||
{
|
||||
if (_type && NodeType != _type)
|
||||
return false;
|
||||
if (!_zooms.contains(zoom))
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < _filters.size(); i++)
|
||||
if (!_filters.at(i).match(tags))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Style::area(QXmlStreamReader &reader, const QString &dir, const Rule &rule)
|
||||
{
|
||||
PathRender ri(rule, _paths.size());
|
||||
const QXmlStreamAttributes &attr = reader.attributes();
|
||||
QString file;
|
||||
int height = 0, width = 0;
|
||||
|
||||
if (attr.hasAttribute("fill"))
|
||||
ri._fillColor = QColor(attr.value("fill").toString());
|
||||
if (attr.hasAttribute("stroke"))
|
||||
ri._strokeColor = QColor(attr.value("stroke").toString());
|
||||
if (attr.hasAttribute("stroke-width"))
|
||||
ri._strokeWidth = attr.value("stroke-width").toFloat();
|
||||
if (attr.hasAttribute("src"))
|
||||
file = resourcePath(attr.value("src").toString(), dir);
|
||||
if (attr.hasAttribute("symbol-height"))
|
||||
height = attr.value("symbol-height").toInt();
|
||||
if (attr.hasAttribute("symbol-width"))
|
||||
width = attr.value("symbol-width").toInt();
|
||||
|
||||
if (!file.isNull())
|
||||
ri._fillImage = image(file, width, height);
|
||||
|
||||
_paths.append(ri);
|
||||
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
|
||||
void Style::line(QXmlStreamReader &reader, const Rule &rule)
|
||||
{
|
||||
PathRender ri(rule, _paths.size());
|
||||
const QXmlStreamAttributes &attr = reader.attributes();
|
||||
|
||||
if (attr.hasAttribute("stroke"))
|
||||
ri._strokeColor = QColor(attr.value("stroke").toString());
|
||||
if (attr.hasAttribute("stroke-width"))
|
||||
ri._strokeWidth = attr.value("stroke-width").toFloat();
|
||||
if (attr.hasAttribute("stroke-dasharray")) {
|
||||
QStringList l(attr.value("stroke-dasharray").toString().split(','));
|
||||
ri._strokeDasharray.resize(l.size());
|
||||
for (int i = 0; i < l.size(); i++)
|
||||
ri._strokeDasharray[i] = l.at(i).toDouble();
|
||||
}
|
||||
if (attr.hasAttribute("stroke-linecap")) {
|
||||
QString cap(attr.value("stroke-linecap").toString());
|
||||
if (cap == "butt")
|
||||
ri._strokeCap = Qt::FlatCap;
|
||||
else if (cap == "round")
|
||||
ri._strokeCap = Qt::RoundCap;
|
||||
else if (cap == "square")
|
||||
ri._strokeCap = Qt::SquareCap;
|
||||
}
|
||||
if (attr.hasAttribute("stroke-linejoin")) {
|
||||
QString join(attr.value("stroke-linejoin").toString());
|
||||
if (join == "miter")
|
||||
ri._strokeJoin = Qt::MiterJoin;
|
||||
else if (join == "round")
|
||||
ri._strokeJoin = Qt::RoundJoin;
|
||||
else if (join == "bevel")
|
||||
ri._strokeJoin = Qt::BevelJoin;
|
||||
}
|
||||
|
||||
_paths.append(ri);
|
||||
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
|
||||
void Style::text(QXmlStreamReader &reader, const Rule &rule,
|
||||
QList<TextRender> &list)
|
||||
{
|
||||
TextRender ri(rule);
|
||||
const QXmlStreamAttributes &attr = reader.attributes();
|
||||
int fontSize = 9;
|
||||
bool bold = false, italic = false;
|
||||
|
||||
if (attr.hasAttribute("fill"))
|
||||
ri._fillColor = QColor(attr.value("fill").toString());
|
||||
if (attr.hasAttribute("stroke"))
|
||||
ri._strokeColor = QColor(attr.value("stroke").toString());
|
||||
if (attr.hasAttribute("font-size"))
|
||||
fontSize = attr.value("font-size").toInt();
|
||||
if (attr.hasAttribute("font-style")) {
|
||||
QString style(attr.value("font-style").toString());
|
||||
if (style == "bold")
|
||||
bold = true;
|
||||
else if (style == "italic")
|
||||
italic = true;
|
||||
else if (style == "bold_italic") {
|
||||
bold = true;
|
||||
italic = true;
|
||||
}
|
||||
}
|
||||
|
||||
ri._font.setPixelSize(fontSize);
|
||||
ri._font.setBold(bold);
|
||||
ri._font.setItalic(italic);
|
||||
|
||||
list.append(ri);
|
||||
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
|
||||
void Style::symbol(QXmlStreamReader &reader, const QString &dir,
|
||||
const Rule &rule)
|
||||
{
|
||||
Symbol ri(rule);
|
||||
const QXmlStreamAttributes &attr = reader.attributes();
|
||||
QString file;
|
||||
int height = 0, width = 0;
|
||||
|
||||
if (attr.hasAttribute("src"))
|
||||
file = resourcePath(attr.value("src").toString(), dir);
|
||||
if (attr.hasAttribute("symbol-height"))
|
||||
height = attr.value("symbol-height").toInt();
|
||||
if (attr.hasAttribute("symbol-width"))
|
||||
width = attr.value("symbol-width").toInt();
|
||||
|
||||
if (!file.isNull())
|
||||
ri._img = image(file, width, height);
|
||||
|
||||
_symbols.append(ri);
|
||||
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
|
||||
void Style::rule(QXmlStreamReader &reader, const QString &dir,
|
||||
const QSet<QString> &cats, const Rule &parent)
|
||||
{
|
||||
Rule r(parent);
|
||||
const QXmlStreamAttributes &attr = reader.attributes();
|
||||
|
||||
if (attr.hasAttribute("cat")
|
||||
&& !cats.contains(attr.value("cat").toString())) {
|
||||
reader.skipCurrentElement();
|
||||
return;
|
||||
}
|
||||
|
||||
if (attr.value("e") == "way")
|
||||
r.setType(Rule::WayType);
|
||||
else if (attr.value("e") == "node")
|
||||
r.setType(Rule::NodeType);
|
||||
|
||||
if (attr.hasAttribute("zoom-min"))
|
||||
r.setMinZoom(attr.value("zoom-min").toInt());
|
||||
if (attr.hasAttribute("zoom-max"))
|
||||
r.setMaxZoom(attr.value("zoom-max").toInt());
|
||||
|
||||
if (attr.hasAttribute("closed")) {
|
||||
if (attr.value("closed").toString() == "yes")
|
||||
r.setClosed(Rule::YesClosed);
|
||||
else if (attr.value("closed").toString() == "no")
|
||||
r.setClosed(Rule::NoClosed);
|
||||
}
|
||||
|
||||
QList<QByteArray> keys(attr.value("k").toLatin1().split('|'));
|
||||
QList<QByteArray> vals(attr.value("v").toLatin1().split('|'));
|
||||
r.addFilter(Rule::Filter(keys, vals));
|
||||
|
||||
while (reader.readNextStartElement()) {
|
||||
if (reader.name() == QLatin1String("rule"))
|
||||
rule(reader, dir, cats, r);
|
||||
else if (reader.name() == QLatin1String("area"))
|
||||
area(reader, dir, r);
|
||||
else if (reader.name() == QLatin1String("line"))
|
||||
line(reader, r);
|
||||
else if (reader.name() == QLatin1String("pathText"))
|
||||
text(reader, r, _pathLabels);
|
||||
else if (reader.name() == QLatin1String("caption"))
|
||||
text(reader, r, _pointLabels);
|
||||
else if (reader.name() == QLatin1String("symbol"))
|
||||
symbol(reader, dir, r);
|
||||
else
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
void Style::cat(QXmlStreamReader &reader, QSet<QString> &cats)
|
||||
{
|
||||
const QXmlStreamAttributes &attr = reader.attributes();
|
||||
|
||||
cats.insert(attr.value("id").toString());
|
||||
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
|
||||
void Style::layer(QXmlStreamReader &reader, QSet<QString> &cats)
|
||||
{
|
||||
const QXmlStreamAttributes &attr = reader.attributes();
|
||||
bool enabled = (attr.value("enabled").toString() == "true");
|
||||
|
||||
while (reader.readNextStartElement()) {
|
||||
if (enabled && reader.name() == QLatin1String("cat"))
|
||||
cat(reader, cats);
|
||||
else
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
void Style::stylemenu(QXmlStreamReader &reader, QSet<QString> &cats)
|
||||
{
|
||||
while (reader.readNextStartElement()) {
|
||||
if (reader.name() == QLatin1String("layer"))
|
||||
layer(reader, cats);
|
||||
else
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
void Style::rendertheme(QXmlStreamReader &reader, const QString &dir)
|
||||
{
|
||||
Rule r;
|
||||
QSet<QString> cats;
|
||||
|
||||
while (reader.readNextStartElement()) {
|
||||
if (reader.name() == QLatin1String("rule"))
|
||||
rule(reader, dir, cats, r);
|
||||
else if (reader.name() == QLatin1String("stylemenu"))
|
||||
stylemenu(reader, cats);
|
||||
else
|
||||
reader.skipCurrentElement();
|
||||
}
|
||||
}
|
||||
|
||||
bool Style::loadXml(const QString &path)
|
||||
{
|
||||
QFile file(path);
|
||||
if (!file.open(QFile::ReadOnly))
|
||||
return false;
|
||||
QXmlStreamReader reader(&file);
|
||||
QFileInfo fi(path);
|
||||
|
||||
if (reader.readNextStartElement()) {
|
||||
if (reader.name() == QLatin1String("rendertheme"))
|
||||
rendertheme(reader, fi.absolutePath());
|
||||
else
|
||||
reader.raiseError("Not a Mapsforge style file");
|
||||
}
|
||||
|
||||
if (reader.error())
|
||||
qWarning("%s:%lld %s", qPrintable(path), reader.lineNumber(),
|
||||
qPrintable(reader.errorString()));
|
||||
|
||||
return !reader.error();
|
||||
}
|
||||
|
||||
Style::Style(const QString &path)
|
||||
{
|
||||
if (!QFileInfo::exists(path) || !loadXml(path))
|
||||
loadXml(":/mapsforge/default.xml");
|
||||
}
|
||||
|
||||
void Style::match(int zoom, bool closed, const QVector<MapData::Tag> &tags,
|
||||
QVector<const Style::PathRender*> *ri) const
|
||||
{
|
||||
for (int i = 0; i < _paths.size(); i++)
|
||||
if (_paths.at(i).rule().match(zoom, closed, tags))
|
||||
ri->append(&_paths.at(i));
|
||||
}
|
||||
|
||||
QPen Style::PathRender::pen(int zoom) const
|
||||
{
|
||||
qreal width = (zoom >= 12)
|
||||
? pow(1.5, zoom - 12) * _strokeWidth : _strokeWidth;
|
||||
|
||||
if (_strokeColor.isValid()) {
|
||||
QPen p(QBrush(_strokeColor), width, Qt::SolidLine, _strokeCap,
|
||||
_strokeJoin);
|
||||
if (!_strokeDasharray.isEmpty()) {
|
||||
QVector<qreal>pattern(_strokeDasharray);
|
||||
for (int i = 0; i < _strokeDasharray.size(); i++)
|
||||
pattern[i] /= width;
|
||||
p.setDashPattern(pattern);
|
||||
}
|
||||
return p;
|
||||
} else
|
||||
return Qt::NoPen;
|
||||
}
|
||||
|
||||
QBrush Style::PathRender::brush() const
|
||||
{
|
||||
if (!_fillImage.isNull())
|
||||
return QBrush(_fillImage);
|
||||
else if (_fillColor.isValid())
|
||||
return QBrush(_fillColor);
|
||||
else
|
||||
return Qt::NoBrush;
|
||||
}
|
230
src/map/mapsforge/style.h
Normal file
230
src/map/mapsforge/style.h
Normal file
@ -0,0 +1,230 @@
|
||||
#ifndef MAPSFORGE_STYLE_H
|
||||
#define MAPSFORGE_STYLE_H
|
||||
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include <QPen>
|
||||
#include <QFont>
|
||||
#include "mapdata.h"
|
||||
|
||||
class QXmlStreamReader;
|
||||
|
||||
namespace Mapsforge {
|
||||
|
||||
class Style
|
||||
{
|
||||
public:
|
||||
class Rule {
|
||||
public:
|
||||
Rule() : _type(AnyType), _closed(AnyClosed), _zooms(0, 127) {}
|
||||
|
||||
bool match(int zoom, bool closed,
|
||||
const QVector<MapData::Tag> &tags) const;
|
||||
bool match(int zoom, const QVector<MapData::Tag> &tags) const;
|
||||
|
||||
private:
|
||||
enum Type {
|
||||
AnyType = 0,
|
||||
NodeType = 1,
|
||||
WayType = 2,
|
||||
InvalidType = 3
|
||||
};
|
||||
|
||||
enum Closed {
|
||||
AnyClosed = 0,
|
||||
YesClosed = 1,
|
||||
NoClosed = 2,
|
||||
InvalidClosed = 3
|
||||
};
|
||||
|
||||
class Filter {
|
||||
public:
|
||||
Filter(const QList<QByteArray> &keys, const QList<QByteArray> &vals)
|
||||
: _neg(false)
|
||||
{
|
||||
_keys = list(keys);
|
||||
|
||||
QList<QByteArray> vc(vals);
|
||||
if (vc.removeAll("~"))
|
||||
_neg = true;
|
||||
_vals = list(vals);
|
||||
}
|
||||
|
||||
bool match(const QVector<MapData::Tag> &tags) const
|
||||
{
|
||||
if (_neg) {
|
||||
if (!keyMatches(tags))
|
||||
return true;
|
||||
return valueMatches(tags);
|
||||
} else
|
||||
return (keyMatches(tags) && valueMatches(tags));
|
||||
}
|
||||
|
||||
bool isTautology() const
|
||||
{
|
||||
return (!_neg && _keys.contains(QByteArray())
|
||||
&& _vals.contains(QByteArray()));
|
||||
}
|
||||
|
||||
private:
|
||||
static QList<QByteArray> list(const QList<QByteArray> &in)
|
||||
{
|
||||
QList<QByteArray> out;
|
||||
|
||||
for (int i = 0; i < in.size(); i++) {
|
||||
if (in.at(i) == "*")
|
||||
out.append(QByteArray());
|
||||
else
|
||||
out.append(in.at(i));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
bool keyMatches(const QVector<MapData::Tag> &tags) const
|
||||
{
|
||||
for (int i = 0; i < _keys.size(); i++)
|
||||
for (int j = 0; j < tags.size(); j++)
|
||||
if (cmp(_keys.at(i), tags.at(j).key))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool valueMatches(const QVector<MapData::Tag> &tags) const
|
||||
{
|
||||
for (int i = 0; i < _vals.size(); i++)
|
||||
for (int j = 0; j < tags.size(); j++)
|
||||
if (cmp(_vals.at(i), tags.at(j).value))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QList<QByteArray> _keys;
|
||||
QList<QByteArray> _vals;
|
||||
bool _neg;
|
||||
};
|
||||
|
||||
void setType(Type type)
|
||||
{
|
||||
_type = static_cast<Type>(static_cast<int>(type)
|
||||
| static_cast<int>(_type));
|
||||
}
|
||||
void setMinZoom(int zoom) {_zooms.setMin(qMax(zoom, _zooms.min()));}
|
||||
void setMaxZoom(int zoom) {_zooms.setMax(qMin(zoom, _zooms.max()));}
|
||||
void setClosed(Closed closed)
|
||||
{
|
||||
_closed = static_cast<Closed>(static_cast<int>(closed)
|
||||
| static_cast<int>(_closed));
|
||||
}
|
||||
void addFilter(const Filter &filter)
|
||||
{
|
||||
if (!filter.isTautology())
|
||||
_filters.append(filter);
|
||||
}
|
||||
bool match(int zoom, Type type, Closed closed,
|
||||
const QVector<MapData::Tag> &tags) const;
|
||||
|
||||
friend class Style;
|
||||
|
||||
Type _type;
|
||||
Closed _closed;
|
||||
Range _zooms;
|
||||
QVector<Filter> _filters;
|
||||
};
|
||||
|
||||
class Render
|
||||
{
|
||||
public:
|
||||
Render(const Rule &rule) : _rule(rule) {}
|
||||
|
||||
const Rule &rule() const {return _rule;}
|
||||
|
||||
private:
|
||||
Rule _rule;
|
||||
};
|
||||
|
||||
class PathRender : public Render
|
||||
{
|
||||
public:
|
||||
PathRender(const Rule &rule, int zOrder) : Render(rule),
|
||||
_zOrder(zOrder), _strokeWidth(0), _strokeCap(Qt::RoundCap),
|
||||
_strokeJoin(Qt::RoundJoin) {}
|
||||
|
||||
int zOrder() const {return _zOrder;}
|
||||
QPen pen(int zoom) const;
|
||||
QBrush brush() const;
|
||||
|
||||
private:
|
||||
friend class Style;
|
||||
|
||||
int _zOrder;
|
||||
QColor _fillColor, _strokeColor;
|
||||
qreal _strokeWidth;
|
||||
QVector<qreal> _strokeDasharray;
|
||||
Qt::PenCapStyle _strokeCap;
|
||||
Qt::PenJoinStyle _strokeJoin;
|
||||
QImage _fillImage;
|
||||
};
|
||||
|
||||
class TextRender : public Render
|
||||
{
|
||||
public:
|
||||
TextRender(const Rule &rule)
|
||||
: Render(rule), _fillColor(Qt::black), _strokeColor(Qt::white) {}
|
||||
|
||||
const QFont &font() const {return _font;}
|
||||
const QColor &fillColor() const {return _fillColor;}
|
||||
const QColor &strokeColor() const {return _strokeColor;}
|
||||
|
||||
private:
|
||||
friend class Style;
|
||||
|
||||
QColor _fillColor, _strokeColor;
|
||||
QFont _font;
|
||||
};
|
||||
|
||||
class Symbol : public Render
|
||||
{
|
||||
public:
|
||||
Symbol(const Rule &rule) : Render(rule) {}
|
||||
|
||||
const QImage &img() const {return _img;}
|
||||
|
||||
private:
|
||||
friend class Style;
|
||||
|
||||
QImage _img;
|
||||
};
|
||||
|
||||
Style(const QString &path);
|
||||
|
||||
void match(int zoom, bool closed, const QVector<MapData::Tag> &tags,
|
||||
QVector<const PathRender *> *ri) const;
|
||||
const QList<TextRender> &pathLabels() const {return _pathLabels;}
|
||||
const QList<TextRender> &pointLabels() const {return _pointLabels;}
|
||||
const QList<Symbol> &symbols() const {return _symbols;}
|
||||
|
||||
private:
|
||||
QList<PathRender> _paths;
|
||||
QList<TextRender> _pathLabels, _pointLabels;
|
||||
QList<Symbol> _symbols;
|
||||
|
||||
bool loadXml(const QString &path);
|
||||
void rendertheme(QXmlStreamReader &reader, const QString &dir);
|
||||
void layer(QXmlStreamReader &reader, QSet<QString> &cats);
|
||||
void stylemenu(QXmlStreamReader &reader, QSet<QString> &cats);
|
||||
void cat(QXmlStreamReader &reader, QSet<QString> &cats);
|
||||
void rule(QXmlStreamReader &reader, const QString &dir,
|
||||
const QSet<QString> &cats, const Rule &parent);
|
||||
void area(QXmlStreamReader &reader, const QString &dir, const Rule &rule);
|
||||
void line(QXmlStreamReader &reader, const Rule &rule);
|
||||
void text(QXmlStreamReader &reader, const Rule &rule,
|
||||
QList<TextRender> &list);
|
||||
void symbol(QXmlStreamReader &reader, const QString &dir, const Rule &rule);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // MAPSFORGE_STYLE_H
|
51
src/map/mapsforge/subfile.cpp
Normal file
51
src/map/mapsforge/subfile.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
#include <cstring>
|
||||
#include "subfile.h"
|
||||
|
||||
using namespace Mapsforge;
|
||||
|
||||
#define mod2n(x, m) ((x) & ((m) - 1));
|
||||
|
||||
bool SubFile::seek(quint64 pos)
|
||||
{
|
||||
Q_ASSERT(pos < _size);
|
||||
|
||||
int blockNum = pos >> BLOCK_BITS;
|
||||
|
||||
if (_blockNum != blockNum) {
|
||||
quint64 seek = ((quint64)blockNum << BLOCK_BITS) + _offset;
|
||||
|
||||
if (seek >= _offset + _size || !_file.seek(seek))
|
||||
return false;
|
||||
if (_file.read((char*)_data, sizeof(_data)) < 0)
|
||||
return false;
|
||||
_blockNum = blockNum;
|
||||
}
|
||||
|
||||
_blockPos = mod2n(pos, 1U<<BLOCK_BITS);
|
||||
_pos = pos;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SubFile::read(char *buff, quint32 size)
|
||||
{
|
||||
while (size) {
|
||||
quint32 remaining = sizeof(_data) - _blockPos;
|
||||
if (size < remaining) {
|
||||
memcpy(buff, _data + _blockPos, size);
|
||||
_blockPos += size;
|
||||
_pos += size;
|
||||
return true;
|
||||
} else {
|
||||
memcpy(buff, _data + _blockPos, remaining);
|
||||
buff += remaining;
|
||||
size -= remaining;
|
||||
_blockPos = 0;
|
||||
_pos += remaining;
|
||||
if (!seek(_pos))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
130
src/map/mapsforge/subfile.h
Normal file
130
src/map/mapsforge/subfile.h
Normal file
@ -0,0 +1,130 @@
|
||||
#ifndef MAPSFORGE_SUBFILE_H
|
||||
#define MAPSFORGE_SUBFILE_H
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#define BLOCK_BITS 12 /* 4096 bytes */
|
||||
|
||||
namespace Mapsforge {
|
||||
|
||||
class SubFile
|
||||
{
|
||||
public:
|
||||
SubFile(QFile &file, quint64 offset, quint64 size)
|
||||
: _file(file), _offset(offset), _size(size), _pos(-1),
|
||||
_blockNum(-1), _blockPos(-1) {}
|
||||
|
||||
quint64 pos() const {return _pos;}
|
||||
bool seek(quint64 pos);
|
||||
|
||||
bool read(char *buff, quint32 size);
|
||||
|
||||
bool readByte(quint8 &val)
|
||||
{
|
||||
val = _data[_blockPos++];
|
||||
_pos++;
|
||||
return (_blockPos >= (int)sizeof(_data)) ? seek(_pos) : true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool readUInt16(T &val)
|
||||
{
|
||||
quint8 b0, b1;
|
||||
if (!(readByte(b0) && readByte(b1)))
|
||||
return false;
|
||||
val = b1 | ((quint16)b0) << 8;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool readUInt32(quint32 &val)
|
||||
{
|
||||
quint8 b0, b1, b2, b3;
|
||||
if (!(readByte(b0) && readByte(b1) && readByte(b2) && readByte(b3)))
|
||||
return false;
|
||||
val = b3 | ((quint32)b2) << 8 | ((quint32)b1) << 16 | ((quint32)b0) << 24;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool readUInt64(quint64 &val)
|
||||
{
|
||||
quint8 b0, b1, b2, b3, b4, b5, b6, b7;
|
||||
if (!(readByte(b0) && readByte(b1) && readByte(b2) && readByte(b3)
|
||||
&& readByte(b4) && readByte(b5) && readByte(b6) && readByte(b7)))
|
||||
return false;
|
||||
val = b7 | ((quint64)b6) << 8 | ((quint64)b5) << 16
|
||||
| ((quint64)b4) << 24 | ((quint64)b3) << 32 | ((quint64)b2) << 40
|
||||
| ((quint64)b1) << 48 | ((quint64)b0) << 56;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool readInt32(qint32 &val)
|
||||
{
|
||||
return readUInt32(reinterpret_cast<quint32&>(val));
|
||||
}
|
||||
|
||||
bool readVUInt32(quint32 &val)
|
||||
{
|
||||
int shift = 0;
|
||||
quint8 b;
|
||||
|
||||
val = 0;
|
||||
do {
|
||||
if (!readByte(b))
|
||||
return false;
|
||||
val |= (quint32)(b & 0x7F) << shift;
|
||||
shift += 7;
|
||||
} while (b & 0x80);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool readVInt32(qint32 &val)
|
||||
{
|
||||
int shift = 0;
|
||||
quint8 b;
|
||||
|
||||
val = 0;
|
||||
while (true) {
|
||||
if (!readByte(b))
|
||||
return false;
|
||||
if (b & 0x80) {
|
||||
val |= (qint32)(b & 0x7F) << shift;
|
||||
shift += 7;
|
||||
} else {
|
||||
val |= (qint32)(b & 0x3F) << shift;
|
||||
if (b & 0x40)
|
||||
val = -val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool readString(QByteArray &str)
|
||||
{
|
||||
quint32 len;
|
||||
|
||||
if (!readVUInt32(len))
|
||||
return false;
|
||||
|
||||
str.resize(len);
|
||||
if (!read(str.data(), len))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
QFile &_file;
|
||||
quint8 _data[1U<<BLOCK_BITS];
|
||||
quint64 _offset;
|
||||
quint64 _size;
|
||||
qint64 _pos;
|
||||
int _blockNum;
|
||||
int _blockPos;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // MAPSFORGE_SUBFILE_H
|
Reference in New Issue
Block a user