Merge remote-tracking branch 'origin/master' into lua-interop-3

This commit is contained in:
Colin Dellow
2024-01-07 13:51:46 -05:00
39 changed files with 2901 additions and 188 deletions
+9 -1
View File
@@ -145,6 +145,14 @@ jobs:
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
if: ${{ github.ref != 'refs/heads/master'}}
with:
context: .
push: false
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Build and push Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
if: ${{ github.ref == 'refs/heads/master'}}
@@ -152,4 +160,4 @@ jobs:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
labels: ${{ steps.meta.outputs.labels }}
+12 -6
View File
@@ -61,7 +61,7 @@ endif()
if(MSVC)
find_package(unofficial-sqlite3 CONFIG REQUIRED)
add_library(SQLite::SQLite3 ALIAS unofficial::sqlite3::sqlite3)
add_definitions(-D_USE_MATH_DEFINES -DWIN32_LEAN_AND_MEAN -DNOGDI)
add_definitions(-D_USE_MATH_DEFINES -DWIN32_LEAN_AND_MEAN -DNOGDI -D__restrict__=__restrict)
set(THREAD_LIB "")
else()
find_package(SQLite3 REQUIRED)
@@ -72,9 +72,10 @@ file(GLOB tilemaker_src_files
src/attribute_store.cpp
src/coordinates.cpp
src/coordinates_geom.cpp
src/external/streamvbyte_decode.cc
src/external/streamvbyte_encode.cc
src/external/streamvbyte_zigzag.cc
src/external/streamvbyte_decode.c
src/external/streamvbyte_encode.c
src/external/streamvbyte_zigzag.c
src/geojson_processor.cpp
src/geom.cpp
src/helpers.cpp
src/mbtiles.cpp
@@ -89,11 +90,12 @@ file(GLOB tilemaker_src_files
src/pbf_reader.cpp
src/pmtiles.cpp
src/pooled_string.cpp
src/read_shp.cpp
src/relation_roles.cpp
src/sharded_node_store.cpp
src/sharded_way_store.cpp
src/shared_data.cpp
src/shp_mem_tiles.cpp
src/shp_processor.cpp
src/sorted_node_store.cpp
src/sorted_way_store.cpp
src/tag_map.cpp
@@ -122,4 +124,8 @@ endif()
install(FILES docs/man/tilemaker.1 DESTINATION share/man/man1)
install(TARGETS tilemaker RUNTIME DESTINATION bin)
add_executable(tilemaker-server server/server.cpp)
target_include_directories(tilemaker-server PRIVATE include)
target_link_libraries(tilemaker-server ${THREAD_LIB} ${CMAKE_DL_LIBS} SQLite::SQLite3 Boost::filesystem Boost::program_options)
install(TARGETS tilemaker tilemaker-server RUNTIME DESTINATION bin)
+4 -1
View File
@@ -17,18 +17,21 @@ RUN apt-get update && \
libboost-system-dev \
libboost-iostreams-dev \
rapidjson-dev \
cmake
cmake \
zlib1g-dev
COPY CMakeLists.txt /
COPY cmake /cmake
COPY src /src
COPY include /include
COPY server /server
WORKDIR /build
RUN cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=g++ ..
RUN cmake --build .
RUN strip tilemaker
RUN strip tilemaker-server
FROM debian:bullseye-slim
RUN apt-get update && \
+19 -5
View File
@@ -84,13 +84,14 @@ prefix = /usr/local
MANPREFIX := /usr/share/man
TM_VERSION ?= $(shell git describe --tags --abbrev=0)
CXXFLAGS ?= -O3 -Wall -Wno-unknown-pragmas -Wno-sign-compare -std=c++14 -pthread -fPIE -DTM_VERSION=$(TM_VERSION) $(CONFIG)
CFLAGS ?= -O3 -Wall -Wno-unknown-pragmas -Wno-sign-compare -std=c99 -fPIE -DTM_VERSION=$(TM_VERSION) $(CONFIG)
LIB := -L$(PLATFORM_PATH)/lib -lz $(LUA_LIBS) -lboost_program_options -lsqlite3 -lboost_filesystem -lboost_system -lboost_iostreams -lshp -pthread
INC := -I$(PLATFORM_PATH)/include -isystem ./include -I./src $(LUA_CFLAGS)
# Targets
.PHONY: test
all: tilemaker
all: tilemaker server
tilemaker: \
src/attribute_store.o \
@@ -99,6 +100,7 @@ tilemaker: \
src/external/streamvbyte_decode.o \
src/external/streamvbyte_encode.o \
src/external/streamvbyte_zigzag.o \
src/geojson_processor.o \
src/geom.o \
src/helpers.o \
src/mbtiles.o \
@@ -113,11 +115,12 @@ tilemaker: \
src/pbf_reader.o \
src/pmtiles.o \
src/pooled_string.o \
src/read_shp.o \
src/relation_roles.o \
src/sharded_node_store.o \
src/sharded_way_store.o \
src/shared_data.o \
src/shp_mem_tiles.o \
src/shp_processor.o \
src/sorted_node_store.o \
src/sorted_way_store.o \
src/tag_map.o \
@@ -133,6 +136,7 @@ test: \
test_deque_map \
test_pbf_reader \
test_pooled_string \
test_relation_roles \
test_sorted_node_store \
test_sorted_way_store
@@ -163,6 +167,11 @@ test_pooled_string: \
test/pooled_string.test.o
$(CXX) $(CXXFLAGS) -o test.pooled_string $^ $(INC) $(LIB) $(LDFLAGS) && ./test.pooled_string
test_relation_roles: \
src/relation_roles.o \
test/relation_roles.test.o
$(CXX) $(CXXFLAGS) -o test.relation_roles $^ $(INC) $(LIB) $(LDFLAGS) && ./test.relation_roles
test_sorted_node_store: \
src/external/streamvbyte_decode.o \
src/external/streamvbyte_encode.o \
@@ -187,19 +196,24 @@ test_pbf_reader: \
test/pbf_reader.test.o
$(CXX) $(CXXFLAGS) -o test.pbf_reader $^ $(INC) $(LIB) $(LDFLAGS) && ./test.pbf_reader
server: \
server/server.o
$(CXX) $(CXXFLAGS) -o tilemaker-server $^ $(INC) $(LIB) $(LDFLAGS)
%.o: %.cpp
$(CXX) $(CXXFLAGS) -o $@ -c $< $(INC)
%.o: %.cc
$(CXX) $(CXXFLAGS) -o $@ -c $< $(INC)
%.o: %.c
$(CC) $(CFLAGS) -o $@ -c $< $(INC)
install:
install -m 0755 -d $(DESTDIR)$(prefix)/bin/
install -m 0755 tilemaker $(DESTDIR)$(prefix)/bin/
install -m 0755 tilemaker-server $(DESTDIR)$(prefix)/bin/
install -m 0755 -d ${DESTDIR}${MANPREFIX}/man1/
install docs/man/tilemaker.1 ${DESTDIR}${MANPREFIX}/man1/
clean:
rm -f tilemaker src/*.o src/external/*.o include/*.o include/*.pb.h test/*.o
rm -f tilemaker tilemaker-server src/*.o src/external/*.o include/*.o include/*.pb.h server/*.o test/*.o
.PHONY: install
+2 -2
View File
@@ -15,7 +15,7 @@ Then:
Start with:
sudo apt install build-essential libboost-dev libboost-filesystem-dev libboost-iostreams-dev libboost-program-options-dev libboost-system-dev liblua5.1-0-dev libshp-dev libsqlite3-dev rapidjson-dev
sudo apt install build-essential libboost-dev libboost-filesystem-dev libboost-iostreams-dev libboost-program-options-dev libboost-system-dev liblua5.1-0-dev libshp-dev libsqlite3-dev rapidjson-dev zlib1g-dev
Once you've installed those, then `cd` back to your Tilemaker directory and simply:
@@ -28,7 +28,7 @@ If it fails, check that the LIB and INC lines in the Makefile correspond with yo
Start with:
dnf install lua-devel luajit-devel sqlite-devel shapelib-devel rapidjson
dnf install lua-devel luajit-devel sqlite-devel shapelib-devel rapidjson-devel boost-devel zlib-devel
then build either with lua:
+46
View File
@@ -0,0 +1,46 @@
/*! \file */
#ifndef _GEOJSON_PROCESSOR_H
#define _GEOJSON_PROCESSOR_H
#include <unordered_map>
#include <string>
#include <vector>
#include <map>
#include "geom.h"
#include "output_object.h"
#include "osm_lua_processing.h"
#include "attribute_store.h"
class GeoJSONProcessor {
public:
GeoJSONProcessor(Box &clippingBox,
uint threadNum,
class ShpMemTiles &shpMemTiles,
OsmLuaProcessing &osmLuaProcessing) :
clippingBox(clippingBox), threadNum(threadNum),
shpMemTiles(shpMemTiles), osmLuaProcessing(osmLuaProcessing)
{}
void read(class LayerDef &layer, uint layerNum);
private:
Box clippingBox;
unsigned threadNum;
ShpMemTiles &shpMemTiles;
OsmLuaProcessing &osmLuaProcessing;
std::mutex attributeMutex;
template <bool Flag, typename T>
void processFeature(rapidjson::GenericObject<Flag, T> feature, class LayerDef &layer, uint layerNum);
template <bool Flag, typename T>
Polygon polygonFromGeoJSONArray(const rapidjson::GenericArray<Flag, T> &coords);
template <bool Flag, typename T>
std::vector<Point> pointsFromGeoJSONArray(const rapidjson::GenericArray<Flag, T> &arr);
AttributeIndex readProperties(const rapidjson::Value &pr, bool &hasName, std::string &name, LayerDef &layer, unsigned &minzoom);
};
#endif //_GEOJSON_PROCESSOR_H
@@ -1,6 +1,6 @@
/*! \file */
#ifndef _GEOJSON_H
#define _GEOJSON_H
#ifndef _GEOJSON_WRITER_H
#define _GEOJSON_WRITER_H
/*
GeoJSON writer for boost::geometry objects, using RapidJSON.
@@ -8,7 +8,7 @@
As yet it only outputs (Multi)Polygons but can be extended for more types.
Example:
auto gj = GeoJSON()
auto gj = GeoJSONWriter();
gj.addGeometry(myMultiPolygon);
gj.finalise();
std::cout << gj.toString() << std::endl;
@@ -32,11 +32,11 @@ typedef boost::variant<Point,Linestring,MultiLinestring,Polygon,MultiPolygon,Rin
using namespace rapidjson;
struct GeoJSON {
struct GeoJSONWriter {
Document document;
std::vector<AnyGeometry> geometries;
GeoJSON() {
GeoJSONWriter() {
document.SetObject();
document.AddMember("type", Value().SetString("FeatureCollection"), document.GetAllocator());
}
@@ -134,4 +134,4 @@ struct GeoJSON {
}
};
#endif //_GEOJSON_H
#endif //_GEOJSON_WRITER_H
+1
View File
@@ -1,6 +1,7 @@
#ifndef OPTIONS_PARSER_H
#define OPTIONS_PARSER_H
#include <cstdint>
#include <exception>
#include <string>
#include <vector>
+30 -6
View File
@@ -13,6 +13,7 @@
#include "shp_mem_tiles.h"
#include "osm_mem_tiles.h"
#include "helpers.h"
#include "pbf_reader.h"
#include <protozero/data_view.hpp>
#include <boost/container/flat_map.hpp>
@@ -102,7 +103,15 @@ public:
* (note that we store relations as ways with artificial IDs, and that
* we use decrementing positive IDs to give a bit more space for way IDs)
*/
void setRelation(int64_t relationId, WayVec const &outerWayVec, WayVec const &innerWayVec, const TagMap& tags, bool isNativeMP, bool isInnerOuter);
void setRelation(
const std::vector<protozero::data_view>& stringTable,
const PbfReader::Relation& relation,
const WayVec& outerWayVec,
const WayVec& innerWayVec,
const TagMap& tags,
bool isNativeMP,
bool isInnerOuter
);
// ---- Metadata queries called from Lua
@@ -133,8 +142,12 @@ public:
double Length();
// Return centroid lat/lon
std::vector<double> Centroid();
Point calculateCentroid();
std::vector<double> Centroid(kaguya::VariadicArgType algorithm);
enum class CentroidAlgorithm: char { Centroid = 0, Polylabel = 1 };
CentroidAlgorithm defaultCentroidAlgorithm() const { return CentroidAlgorithm::Polylabel; }
CentroidAlgorithm parseCentroidAlgorithm(const std::string& algorithm) const;
Point calculateCentroid(CentroidAlgorithm algorithm);
enum class CorrectGeometryResult: char { Invalid = 0, Valid = 1, Corrected = 2 };
// ---- Requests from Lua to write this way/node to a vector tile's Layer
@@ -167,7 +180,7 @@ public:
// Add layer
void Layer(const std::string &layerName, bool area);
void LayerAsCentroid(const std::string &layerName);
void LayerAsCentroid(const std::string &layerName, kaguya::VariadicArgType nodeSources);
// Set attributes in a vector tile's Attributes table
void Attribute(const std::string &key, const std::string &val);
@@ -180,7 +193,13 @@ public:
void ZOrder(const double z);
// Relation scan support
kaguya::optional<int> NextRelation();
struct OptionalRelation {
bool done;
lua_Integer id;
std::string role;
};
OptionalRelation NextRelation();
void RestartRelations();
std::string FindInRelation(const std::string &key);
void Accept();
@@ -215,6 +234,8 @@ private:
/// Internal: clear current cached state
inline void reset() {
outputs.clear();
currentRelation = nullptr;
stringTable = nullptr;
llVecPtr = nullptr;
outerWayVecPtr = nullptr;
innerWayVecPtr = nullptr;
@@ -223,6 +244,7 @@ private:
polygonInited = false;
multiPolygonInited = false;
relationAccepted = false;
relationList.clear();
relationSubscript = -1;
lastStoredGeometryId = 0;
}
@@ -247,7 +269,7 @@ private:
bool isWay, isRelation, isClosed; ///< Way, node, relation?
bool relationAccepted; // in scanRelation, whether we're using a non-MP relation
std::vector<WayID> relationList; // in processWay, list of relations this way is in
std::vector<std::pair<WayID, uint16_t>> relationList; // in processNode/processWay, list of relations this entity is in, and its role
int relationSubscript = -1; // in processWay, position in the relation list
int32_t lon,latp; ///< Node coordinates
@@ -272,6 +294,8 @@ private:
std::vector<std::pair<OutputObject, AttributeSet>> outputs; // All output objects that have been created
std::vector<std::string> outputKeys;
const PbfReader::Relation* currentRelation;
const std::vector<protozero::data_view>* stringTable;
std::vector<OutputObject> finalizeOutputs();
+25 -12
View File
@@ -5,6 +5,7 @@
#include "geom.h"
#include "coordinates.h"
#include "mmap_allocator.h"
#include "relation_roles.h"
#include <utility>
#include <vector>
@@ -81,17 +82,25 @@ class RelationScanStore {
private:
using tag_map_t = boost::container::flat_map<std::string, std::string>;
std::vector<std::map<WayID, std::vector<WayID>>> relationsForWays;
std::vector<std::map<WayID, std::vector<std::pair<WayID, uint16_t>>>> relationsForWays;
std::vector<std::map<NodeID, std::vector<std::pair<WayID, uint16_t>>>> relationsForNodes;
std::vector<std::map<WayID, tag_map_t>> relationTags;
mutable std::vector<std::mutex> mutex;
RelationRoles relationRoles;
public:
RelationScanStore(): relationsForWays(128), relationTags(128), mutex(128) {}
void relation_contains_way(WayID relid, WayID wayid) {
RelationScanStore(): relationsForWays(128), relationsForNodes(128), relationTags(128), mutex(128) {}
void relation_contains_way(WayID relid, WayID wayid, std::string role) {
uint16_t roleId = relationRoles.getOrAddRole(role);
const size_t shard = wayid % mutex.size();
std::lock_guard<std::mutex> lock(mutex[shard]);
relationsForWays[shard][wayid].emplace_back(relid);
relationsForWays[shard][wayid].emplace_back(std::make_pair(relid, roleId));
}
void relation_contains_node(WayID relid, NodeID nodeId, std::string role) {
uint16_t roleId = relationRoles.getOrAddRole(role);
const size_t shard = nodeId % mutex.size();
std::lock_guard<std::mutex> lock(mutex[shard]);
relationsForNodes[shard][nodeId].emplace_back(std::make_pair(relid, roleId));
}
void store_relation_tags(WayID relid, const tag_map_t &tags) {
const size_t shard = relid % mutex.size();
@@ -102,10 +111,19 @@ public:
const size_t shard = wayid % mutex.size();
return relationsForWays[shard].find(wayid) != relationsForWays[shard].end();
}
std::vector<WayID> relations_for_way(WayID wayid) {
bool node_in_any_relations(NodeID nodeId) {
const size_t shard = nodeId % mutex.size();
return relationsForNodes[shard].find(nodeId) != relationsForNodes[shard].end();
}
std::string getRole(uint16_t roleId) const { return relationRoles.getRole(roleId); }
const std::vector<std::pair<WayID, uint16_t>>& relations_for_way(WayID wayid) {
const size_t shard = wayid % mutex.size();
return relationsForWays[shard][wayid];
}
const std::vector<std::pair<WayID, uint16_t>>& relations_for_node(NodeID nodeId) {
const size_t shard = nodeId % mutex.size();
return relationsForNodes[shard][nodeId];
}
std::string get_relation_tag(WayID relid, const std::string &key) {
const size_t shard = relid % mutex.size();
auto it = relationTags[shard].find(relid);
@@ -178,6 +196,7 @@ class OSMStore
public:
NodeStore& nodes;
WayStore& ways;
RelationScanStore scannedRelations;
protected:
bool use_compact_nodes = false;
@@ -185,7 +204,6 @@ protected:
RelationStore relations; // unused
UsedWays used_ways;
RelationScanStore scanned_relations;
public:
@@ -213,11 +231,6 @@ public:
void ensureUsedWaysInited();
using tag_map_t = boost::container::flat_map<std::string, std::string>;
void relation_contains_way(WayID relid, WayID wayid) { scanned_relations.relation_contains_way(relid,wayid); }
void store_relation_tags(WayID relid, const tag_map_t &tags) { scanned_relations.store_relation_tags(relid,tags); }
bool way_in_any_relations(WayID wayid) { return scanned_relations.way_in_any_relations(wayid); }
std::vector<WayID> relations_for_way(WayID wayid) { return scanned_relations.relations_for_way(wayid); }
std::string get_relation_tag(WayID relid, const std::string &key) { return scanned_relations.get_relation_tag(relid, key); }
void clear();
void reportSize() const;
+209
View File
@@ -0,0 +1,209 @@
#pragma once
// Original source from https://github.com/mapbox/polylabel, licensed
// under ISC.
//
// Adapted to use Boost Geometry instead of MapBox's geometry library.
//
// Changes:
// - Default precision changed from 1 to 0.00001.
// @mourner has some comments about what a reasonable precision value is
// for latitude/longitude coordinates, see
// https://github.com/mapbox/polylabel/issues/68#issuecomment-694906027
// https://github.com/mapbox/polylabel/issues/103#issuecomment-1516623862
//
// Possible future changes:
// - Port the change described in https://github.com/mapbox/polylabel/issues/33,
// implemented in Mapnik's Java renderer, to C++.
// - But see counterexample: https://github.com/mapbox/polylabel/pull/63
// @Fil also proposes an alternative approach there.
// - Pick precision as a function of the input geometry.
#include "geom.h"
#include <algorithm>
#include <cmath>
#include <iostream>
#include <queue>
namespace mapbox {
namespace detail {
// get squared distance from a point to a segment
double getSegDistSq(const Point& p,
const Point& a,
const Point& b) {
auto x = a.get<0>();
auto y = a.get<1>();
auto dx = b.get<0>() - x;
auto dy = b.get<1>() - y;
if (dx != 0 || dy != 0) {
auto t = ((p.get<0>() - x) * dx + (p.get<1>() - y) * dy) / (dx * dx + dy * dy);
if (t > 1) {
x = b.get<0>();
y = b.get<1>();
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}
dx = p.get<0>() - x;
dy = p.get<1>() - y;
return dx * dx + dy * dy;
}
// signed distance from point to polygon outline (negative if point is outside)
auto pointToPolygonDist(const Point& point, const Polygon& polygon) {
bool inside = false;
auto minDistSq = std::numeric_limits<double>::infinity();
{
const auto& ring = polygon.outer();
for (std::size_t i = 0, len = ring.size(), j = len - 1; i < len; j = i++) {
const auto& a = ring[i];
const auto& b = ring[j];
if ((a.get<1>() > point.get<1>()) != (b.get<1>() > point.get<1>()) &&
(point.get<0>() < (b.get<0>() - a.get<0>()) * (point.get<1>() - a.get<1>()) / (b.get<1>() - a.get<1>()) + a.get<0>())) inside = !inside;
minDistSq = std::min(minDistSq, getSegDistSq(point, a, b));
}
}
for (const auto& ring : polygon.inners()) {
for (std::size_t i = 0, len = ring.size(), j = len - 1; i < len; j = i++) {
const auto& a = ring[i];
const auto& b = ring[j];
if ((a.get<1>() > point.get<1>()) != (b.get<1>() > point.get<1>()) &&
(point.get<0>() < (b.get<0>() - a.get<0>()) * (point.get<1>() - a.get<1>()) / (b.get<1>() - a.get<1>()) + a.get<0>())) inside = !inside;
minDistSq = std::min(minDistSq, getSegDistSq(point, a, b));
}
}
return (inside ? 1 : -1) * std::sqrt(minDistSq);
}
struct Cell {
Cell(const Point& c_, double h_, const Polygon& polygon)
: c(c_),
h(h_),
d(pointToPolygonDist(c, polygon)),
max(d + h * std::sqrt(2))
{}
Point c; // cell center
double h; // half the cell size
double d; // distance from cell center to polygon
double max; // max distance to polygon within a cell
};
// get polygon centroid
Cell getCentroidCell(const Polygon& polygon) {
double area = 0;
double cx = 0, cy = 0;
const auto& ring = polygon.outer();
for (std::size_t i = 0, len = ring.size(), j = len - 1; i < len; j = i++) {
const Point& a = ring[i];
const Point& b = ring[j];
auto f = a.get<0>() * b.get<1>() - b.get<0>() * a.get<1>();
cx += (a.get<0>() + b.get<0>()) * f;
cy += (a.get<1>() + b.get<1>()) * f;
area += f * 3;
}
Point c { cx, cy };
return Cell(area == 0 ? ring.at(0) : Point { cx / area, cy / area }, 0, polygon);
}
} // namespace detail
Point polylabel(const Polygon& polygon, double precision = 0.00001, bool debug = false) {
using namespace detail;
// find the bounding box of the outer ring
Box envelope;
geom::envelope(polygon.outer(), envelope);
const Point size {
envelope.max_corner().get<0>() - envelope.min_corner().get<0>(),
envelope.max_corner().get<1>() - envelope.min_corner().get<1>()
};
const double cellSize = std::min(size.get<0>(), size.get<1>());
double h = cellSize / 2;
// a priority queue of cells in order of their "potential" (max distance to polygon)
auto compareMax = [] (const Cell& a, const Cell& b) {
return a.max < b.max;
};
using Queue = std::priority_queue<Cell, std::vector<Cell>, decltype(compareMax)>;
Queue cellQueue(compareMax);
if (cellSize == 0) {
return envelope.min_corner();
}
// cover polygon with initial cells
for (double x = envelope.min_corner().get<0>(); x < envelope.max_corner().get<0>(); x += cellSize) {
for (double y = envelope.min_corner().get<1>(); y < envelope.max_corner().get<1>(); y += cellSize) {
cellQueue.push(Cell({x + h, y + h}, h, polygon));
}
}
// take centroid as the first best guess
auto bestCell = getCentroidCell(polygon);
// second guess: bounding box centroid
Cell bboxCell(
Point {
envelope.min_corner().get<0>() + size.get<0>() / 2.0,
envelope.min_corner().get<1>() + size.get<1>() / 2.0
}, 0, polygon);
if (bboxCell.d > bestCell.d) {
bestCell = bboxCell;
}
auto numProbes = cellQueue.size();
while (!cellQueue.empty()) {
// pick the most promising cell from the queue
auto cell = cellQueue.top();
cellQueue.pop();
// update the best cell if we found a better one
if (cell.d > bestCell.d) {
bestCell = cell;
if (debug) std::cout << "found best " << ::round(1e4 * cell.d) / 1e4 << " after " << numProbes << " probes" << std::endl;
}
// do not drill down further if there's no chance of a better solution
if (cell.max - bestCell.d <= precision) continue;
// split the cell into four cells
h = cell.h / 2;
cellQueue.push(Cell({cell.c.get<0>() - h, cell.c.get<1>() - h}, h, polygon));
cellQueue.push(Cell({cell.c.get<0>() + h, cell.c.get<1>() - h}, h, polygon));
cellQueue.push(Cell({cell.c.get<0>() - h, cell.c.get<1>() + h}, h, polygon));
cellQueue.push(Cell({cell.c.get<0>() + h, cell.c.get<1>() + h}, h, polygon));
numProbes += 4;
}
if (debug) {
std::cout << "num probes: " << numProbes << std::endl;
std::cout << "best distance: " << bestCell.d << std::endl;
}
return bestCell.c;
}
} // namespace mapbox
+1
View File
@@ -29,6 +29,7 @@
// Note that the pointer mode is not safe to be stored. It exists just to allow
// lookups in the AttributePair map before deciding to allocate a string.
#include <cstdint>
#include <vector>
#include <string>
-39
View File
@@ -1,39 +0,0 @@
/*! \file */
#ifndef _READ_SHP_H
#define _READ_SHP_H
#include <unordered_map>
#include <string>
#include <vector>
#include <map>
#include "geom.h"
#include "output_object.h"
#include "osm_lua_processing.h"
#include "attribute_store.h"
// Shapelib
#include "shapefil.h"
void fillPointArrayFromShapefile(std::vector<Point> *points, SHPObject *shape, uint part);
/// Read requested attributes from a shapefile, and encode into an OutputObject
AttributeIndex readShapefileAttributes(DBFHandle &dbf, int recordNum,
std::unordered_map<int,std::string> &columnMap,
std::unordered_map<int,int> &columnTypeMap,
LayerDef &layer,
OsmLuaProcessing &osmLuaProcessing, uint &minzoom);
/// Read shapefile, and create OutputObjects for all objects within the specified bounding box
void readShapefile(const Box &clippingBox,
class LayerDefinition &layers,
uint baseZoom, uint layerNum,
uint threadNum,
class ShpMemTiles &shpMemTiles,
OsmLuaProcessing &osmLuaProcessing);
// Process an individual shapefile record
void processShapeGeometry(SHPObject* shape, AttributeIndex attrIdx, ShpMemTiles &shpMemTiles,
const Box &clippingBox, const LayerDef &layer, uint layerNum, bool hasName, const std::string &name);
#endif //_READ_SHP_H
+22
View File
@@ -0,0 +1,22 @@
#ifndef RELATION_ROLES_H
#define RELATION_ROLES_H
#include <boost/container/flat_map.hpp>
#include <mutex>
#include <vector>
#include <string>
class RelationRoles {
public:
RelationRoles();
uint16_t getOrAddRole(const std::string& role);
std::string getRole(uint16_t role) const;
private:
std::vector<std::string> popularRoleStrings;
std::vector<std::string> rareRoleStrings;
std::mutex mutex;
boost::container::flat_map<std::string, uint16_t> popularRoles;
boost::container::flat_map<std::string, uint16_t> rareRoles;
};
#endif
+4
View File
@@ -36,6 +36,10 @@ struct LayerDef {
std::string indexName;
std::map<std::string, uint> attributeMap;
bool writeTo;
const bool useColumn(std::string &col) {
return allSourceColumns || (std::find(sourceColumns.begin(), sourceColumns.end(), col) != sourceColumns.end());
}
};
///\brief Defines layers used in map rendering
+2 -1
View File
@@ -16,7 +16,7 @@ public:
void CreateNamedLayerIndex(const std::string& layerName);
// Used in shape file loading
void StoreShapefileGeometry(
void StoreGeometry(
uint_least8_t layerNum,
const std::string& layerName,
enum OutputGeometryType geomType,
@@ -64,6 +64,7 @@ private:
std::vector<OutputObject> indexedGeometries; // prepared boost::geometry objects (from shapefiles)
std::map<uint, std::string> indexedGeometryNames; // | optional names for each one
std::map<std::string, RTree> indices; // Spatial indices, boost::geometry::index objects for shapefile indices
std::mutex indexMutex;
};
#endif //_OSM_MEM_TILES
+52
View File
@@ -0,0 +1,52 @@
/*! \file */
#ifndef _SHP_PROCESSOR_H
#define _SHP_PROCESSOR_H
#include <unordered_map>
#include <string>
#include <vector>
#include <map>
#include "geom.h"
#include "output_object.h"
#include "osm_lua_processing.h"
#include "attribute_store.h"
// Shapelib
#include "shapefil.h"
class ShpProcessor {
public:
ShpProcessor(Box &clippingBox,
uint threadNum,
class ShpMemTiles &shpMemTiles,
OsmLuaProcessing &osmLuaProcessing) :
clippingBox(clippingBox), threadNum(threadNum),
shpMemTiles(shpMemTiles), osmLuaProcessing(osmLuaProcessing)
{}
// Read shapefile, and create OutputObjects for all objects within the specified bounding box
void read(class LayerDef &layer, uint layerNum);
private:
Box clippingBox;
unsigned threadNum;
ShpMemTiles &shpMemTiles;
OsmLuaProcessing &osmLuaProcessing;
std::mutex attributeMutex;
void fillPointArrayFromShapefile(std::vector<Point> *points, SHPObject *shape, uint part);
// Read requested attributes from a shapefile, and encode into an OutputObject
AttributeIndex readShapefileAttributes(DBFHandle &dbf, int recordNum,
std::unordered_map<int,std::string> &columnMap,
std::unordered_map<int,int> &columnTypeMap,
LayerDef &layer, uint &minzoom);
// Process an individual shapefile record
void processShapeGeometry(SHPObject* shape, AttributeIndex attrIdx,
const LayerDef &layer, uint layerNum, bool hasName, const std::string &name);
};
#endif //_SHP_PROCESSOR_H
+12 -2
View File
@@ -172,7 +172,17 @@ function node_function()
MinZoom(mz)
if rank then AttributeNumeric("rank", rank) end
if capital then AttributeNumeric("capital", capital) end
if place=="country" then Attribute("iso_a2", Find("ISO3166-1:alpha2")) end
if place=="country" then
local iso_a2 = Find("ISO3166-1:alpha2")
while iso_a2 == "" do
local rel, role = NextRelation()
if not rel then break end
if role == 'label' then
iso_a2 = FindInRelation("ISO3166-1:alpha2")
end
end
Attribute("iso_a2", iso_a2)
end
SetNameAttributes()
return
end
@@ -566,7 +576,7 @@ function way_function()
-- Set 'housenumber'
if housenumber~="" then
LayerAsCentroid("housenumber", false)
LayerAsCentroid("housenumber")
Attribute("housenumber", housenumber)
end
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-2020 Ole Christian Eidheim
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,85 @@
#ifndef SIMPLE_WEB_ASIO_COMPATIBILITY_HPP
#define SIMPLE_WEB_ASIO_COMPATIBILITY_HPP
#include <memory>
#ifdef ASIO_STANDALONE
#include <asio.hpp>
#include <asio/steady_timer.hpp>
namespace SimpleWeb {
namespace error = asio::error;
using error_code = std::error_code;
using errc = std::errc;
using system_error = std::system_error;
namespace make_error_code = std;
} // namespace SimpleWeb
#else
#include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>
namespace SimpleWeb {
namespace asio = boost::asio;
namespace error = asio::error;
using error_code = boost::system::error_code;
namespace errc = boost::system::errc;
using system_error = boost::system::system_error;
namespace make_error_code = boost::system::errc;
} // namespace SimpleWeb
#endif
namespace SimpleWeb {
#if(ASIO_STANDALONE && ASIO_VERSION >= 101300) || BOOST_ASIO_VERSION >= 101300
using io_context = asio::io_context;
using resolver_results = asio::ip::tcp::resolver::results_type;
using async_connect_endpoint = asio::ip::tcp::endpoint;
template <typename handler_type>
inline void post(io_context &context, handler_type &&handler) {
asio::post(context, std::forward<handler_type>(handler));
}
inline void restart(io_context &context) noexcept {
context.restart();
}
inline asio::ip::address make_address(const std::string &str) noexcept {
return asio::ip::make_address(str);
}
template <typename socket_type, typename duration_type>
std::unique_ptr<asio::steady_timer> make_steady_timer(socket_type &socket, std::chrono::duration<duration_type> duration) {
return std::unique_ptr<asio::steady_timer>(new asio::steady_timer(socket.get_executor(), duration));
}
template <typename handler_type>
void async_resolve(asio::ip::tcp::resolver &resolver, const std::pair<std::string, std::string> &host_port, handler_type &&handler) {
resolver.async_resolve(host_port.first, host_port.second, std::forward<handler_type>(handler));
}
inline asio::executor_work_guard<io_context::executor_type> make_work_guard(io_context &context) {
return asio::make_work_guard(context);
}
#else
using io_context = asio::io_service;
using resolver_results = asio::ip::tcp::resolver::iterator;
using async_connect_endpoint = asio::ip::tcp::resolver::iterator;
template <typename handler_type>
inline void post(io_context &context, handler_type &&handler) {
context.post(std::forward<handler_type>(handler));
}
inline void restart(io_context &context) noexcept {
context.reset();
}
inline asio::ip::address make_address(const std::string &str) noexcept {
return asio::ip::address::from_string(str);
}
template <typename socket_type, typename duration_type>
std::unique_ptr<asio::steady_timer> make_steady_timer(socket_type &socket, std::chrono::duration<duration_type> duration) {
return std::unique_ptr<asio::steady_timer>(new asio::steady_timer(socket.get_io_service(), duration));
}
template <typename handler_type>
void async_resolve(asio::ip::tcp::resolver &resolver, const std::pair<std::string, std::string> &host_port, handler_type &&handler) {
resolver.async_resolve(asio::ip::tcp::resolver::query(host_port.first, host_port.second), std::forward<handler_type>(handler));
}
inline io_context::work make_work_guard(io_context &context) {
return io_context::work(context);
}
#endif
} // namespace SimpleWeb
#endif /* SIMPLE_WEB_ASIO_COMPATIBILITY_HPP */
+107
View File
@@ -0,0 +1,107 @@
// Based on https://clang.llvm.org/docs/ThreadSafetyAnalysis.html
#ifndef SIMPLE_WEB_MUTEX_HPP
#define SIMPLE_WEB_MUTEX_HPP
#include <mutex>
// Enable thread safety attributes only with clang.
#if defined(__clang__) && (!defined(SWIG))
#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x))
#else
#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op
#endif
#define CAPABILITY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(capability(x))
#define SCOPED_CAPABILITY \
THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable)
#define GUARDED_BY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x))
#define PT_GUARDED_BY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x))
#define ACQUIRED_BEFORE(...) \
THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__))
#define ACQUIRED_AFTER(...) \
THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__))
#define REQUIRES(...) \
THREAD_ANNOTATION_ATTRIBUTE__(requires_capability(__VA_ARGS__))
#define REQUIRES_SHARED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(requires_shared_capability(__VA_ARGS__))
#define ACQUIRE(...) \
THREAD_ANNOTATION_ATTRIBUTE__(acquire_capability(__VA_ARGS__))
#define ACQUIRE_SHARED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(acquire_shared_capability(__VA_ARGS__))
#define RELEASE(...) \
THREAD_ANNOTATION_ATTRIBUTE__(release_capability(__VA_ARGS__))
#define RELEASE_SHARED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(release_shared_capability(__VA_ARGS__))
#define TRY_ACQUIRE(...) \
THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_capability(__VA_ARGS__))
#define TRY_ACQUIRE_SHARED(...) \
THREAD_ANNOTATION_ATTRIBUTE__(try_acquire_shared_capability(__VA_ARGS__))
#define EXCLUDES(...) \
THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__))
#define ASSERT_CAPABILITY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(assert_capability(x))
#define ASSERT_SHARED_CAPABILITY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_capability(x))
#define RETURN_CAPABILITY(x) \
THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x))
#define NO_THREAD_SAFETY_ANALYSIS \
THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis)
namespace SimpleWeb {
/// Mutex class that is annotated for Clang Thread Safety Analysis.
class CAPABILITY("mutex") Mutex {
std::mutex mutex;
public:
void lock() ACQUIRE() {
mutex.lock();
}
void unlock() RELEASE() {
mutex.unlock();
}
};
/// Scoped mutex guard class that is annotated for Clang Thread Safety Analysis.
class SCOPED_CAPABILITY LockGuard {
Mutex &mutex;
bool locked = true;
public:
LockGuard(Mutex &mutex_) ACQUIRE(mutex_) : mutex(mutex_) {
mutex.lock();
}
void unlock() RELEASE() {
mutex.unlock();
locked = false;
}
~LockGuard() RELEASE() {
if(locked)
mutex.unlock();
}
};
} // namespace SimpleWeb
#endif // SIMPLE_WEB_MUTEX_HPP
+827
View File
@@ -0,0 +1,827 @@
#ifndef SIMPLE_WEB_SERVER_HTTP_HPP
#define SIMPLE_WEB_SERVER_HTTP_HPP
#include "asio_compatibility.hpp"
#include "mutex.hpp"
#include "utility.hpp"
#include <functional>
#include <iostream>
#include <limits>
#include <list>
#include <map>
#include <sstream>
#include <thread>
#include <unordered_set>
// Late 2017 TODO: remove the following checks and always use std::regex
#ifdef USE_BOOST_REGEX
#include <boost/regex.hpp>
namespace SimpleWeb {
namespace regex = boost;
}
#else
#include <regex>
namespace SimpleWeb {
namespace regex = std;
}
#endif
namespace SimpleWeb {
template <class socket_type>
class Server;
template <class socket_type>
class ServerBase {
protected:
class Connection;
class Session;
public:
/// Response class where the content of the response is sent to client when the object is about to be destroyed.
class Response : public std::enable_shared_from_this<Response>, public std::ostream {
friend class ServerBase<socket_type>;
friend class Server<socket_type>;
std::unique_ptr<asio::streambuf> streambuf = std::unique_ptr<asio::streambuf>(new asio::streambuf());
std::shared_ptr<Session> session;
long timeout_content;
Mutex send_queue_mutex;
std::list<std::pair<std::shared_ptr<asio::streambuf>, std::function<void(const error_code &)>>> send_queue GUARDED_BY(send_queue_mutex);
Response(std::shared_ptr<Session> session_, long timeout_content) noexcept : std::ostream(nullptr), session(std::move(session_)), timeout_content(timeout_content) {
rdbuf(streambuf.get());
}
template <typename size_type>
void write_header(const CaseInsensitiveMultimap &header, size_type size) {
bool content_length_written = false;
bool chunked_transfer_encoding = false;
for(auto &field : header) {
if(!content_length_written && case_insensitive_equal(field.first, "content-length"))
content_length_written = true;
else if(!chunked_transfer_encoding && case_insensitive_equal(field.first, "transfer-encoding") && case_insensitive_equal(field.second, "chunked"))
chunked_transfer_encoding = true;
*this << field.first << ": " << field.second << "\r\n";
}
if(!content_length_written && !chunked_transfer_encoding && !close_connection_after_response)
*this << "Content-Length: " << size << "\r\n\r\n";
else
*this << "\r\n";
}
void send_from_queue() REQUIRES(send_queue_mutex) {
auto self = this->shared_from_this();
asio::async_write(*self->session->connection->socket, *send_queue.begin()->first, [self](const error_code &ec, std::size_t /*bytes_transferred*/) {
auto lock = self->session->connection->handler_runner->continue_lock();
if(!lock)
return;
{
LockGuard lock(self->send_queue_mutex);
if(!ec) {
auto it = self->send_queue.begin();
auto callback = std::move(it->second);
self->send_queue.erase(it);
if(self->send_queue.size() > 0)
self->send_from_queue();
lock.unlock();
if(callback)
callback(ec);
}
else {
// All handlers in the queue is called with ec:
std::vector<std::function<void(const error_code &)>> callbacks;
for(auto &pair : self->send_queue) {
if(pair.second)
callbacks.emplace_back(std::move(pair.second));
}
self->send_queue.clear();
lock.unlock();
for(auto &callback : callbacks)
callback(ec);
}
}
});
}
void send_on_delete(const std::function<void(const error_code &)> &callback = nullptr) noexcept {
auto self = this->shared_from_this(); // Keep Response instance alive through the following async_write
asio::async_write(*session->connection->socket, *streambuf, [self, callback](const error_code &ec, std::size_t /*bytes_transferred*/) {
auto lock = self->session->connection->handler_runner->continue_lock();
if(!lock)
return;
if(callback)
callback(ec);
});
}
public:
std::size_t size() noexcept {
return streambuf->size();
}
/// Send the content of the response stream to client. The callback is called when the send has completed.
///
/// Use this function if you need to recursively send parts of a longer message, or when using server-sent events.
void send(std::function<void(const error_code &)> callback = nullptr) noexcept {
std::shared_ptr<asio::streambuf> streambuf = std::move(this->streambuf);
this->streambuf = std::unique_ptr<asio::streambuf>(new asio::streambuf());
rdbuf(this->streambuf.get());
LockGuard lock(send_queue_mutex);
send_queue.emplace_back(std::move(streambuf), std::move(callback));
if(send_queue.size() == 1)
send_from_queue();
}
/// Write directly to stream buffer using std::ostream::write.
void write(const char_type *ptr, std::streamsize n) {
std::ostream::write(ptr, n);
}
/// Convenience function for writing status line, potential header fields, and empty content.
void write(StatusCode status_code = StatusCode::success_ok, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
*this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n";
write_header(header, 0);
}
/// Convenience function for writing status line, header fields, and content.
void write(StatusCode status_code, string_view content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
*this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n";
write_header(header, content.size());
if(!content.empty())
*this << content;
}
/// Convenience function for writing status line, header fields, and content.
void write(StatusCode status_code, std::istream &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
*this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n";
content.seekg(0, std::ios::end);
auto size = content.tellg();
content.seekg(0, std::ios::beg);
write_header(header, size);
if(size)
*this << content.rdbuf();
}
/// Convenience function for writing success status line, header fields, and content.
void write(string_view content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
write(StatusCode::success_ok, content, header);
}
/// Convenience function for writing success status line, header fields, and content.
void write(std::istream &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) {
write(StatusCode::success_ok, content, header);
}
/// Convenience function for writing success status line, and header fields.
void write(const CaseInsensitiveMultimap &header) {
write(StatusCode::success_ok, std::string(), header);
}
/// If set to true, force server to close the connection after the response have been sent.
///
/// This is useful when implementing a HTTP/1.0-server sending content
/// without specifying the content length.
bool close_connection_after_response = false;
};
class Content : public std::istream {
friend class ServerBase<socket_type>;
public:
std::size_t size() noexcept {
return streambuf.size();
}
/// Convenience function to return content as std::string.
std::string string() noexcept {
return std::string(asio::buffers_begin(streambuf.data()), asio::buffers_end(streambuf.data()));
}
private:
asio::streambuf &streambuf;
Content(asio::streambuf &streambuf) noexcept : std::istream(&streambuf), streambuf(streambuf) {}
};
class Request {
friend class ServerBase<socket_type>;
friend class Server<socket_type>;
friend class Session;
asio::streambuf streambuf;
std::weak_ptr<Connection> connection;
std::string optimization = std::to_string(0); // TODO: figure out what goes wrong in gcc optimization without this line
Request(std::size_t max_request_streambuf_size, const std::shared_ptr<Connection> &connection_) noexcept : streambuf(max_request_streambuf_size), connection(connection_), content(streambuf) {}
public:
std::string method, path, query_string, http_version;
Content content;
CaseInsensitiveMultimap header;
/// The result of the resource regular expression match of the request path.
regex::smatch path_match;
/// The time point when the request header was fully read.
std::chrono::system_clock::time_point header_read_time;
asio::ip::tcp::endpoint remote_endpoint() const noexcept {
try {
if(auto connection = this->connection.lock())
return connection->socket->lowest_layer().remote_endpoint();
}
catch(...) {
}
return asio::ip::tcp::endpoint();
}
asio::ip::tcp::endpoint local_endpoint() const noexcept {
try {
if(auto connection = this->connection.lock())
return connection->socket->lowest_layer().local_endpoint();
}
catch(...) {
}
return asio::ip::tcp::endpoint();
}
/// Deprecated, please use remote_endpoint().address().to_string() instead.
DEPRECATED std::string remote_endpoint_address() const noexcept {
try {
if(auto connection = this->connection.lock())
return connection->socket->lowest_layer().remote_endpoint().address().to_string();
}
catch(...) {
}
return std::string();
}
/// Deprecated, please use remote_endpoint().port() instead.
DEPRECATED unsigned short remote_endpoint_port() const noexcept {
try {
if(auto connection = this->connection.lock())
return connection->socket->lowest_layer().remote_endpoint().port();
}
catch(...) {
}
return 0;
}
/// Returns query keys with percent-decoded values.
CaseInsensitiveMultimap parse_query_string() const noexcept {
return SimpleWeb::QueryString::parse(query_string);
}
};
protected:
class Connection : public std::enable_shared_from_this<Connection> {
public:
template <typename... Args>
Connection(std::shared_ptr<ScopeRunner> handler_runner_, Args &&...args) noexcept : handler_runner(std::move(handler_runner_)), socket(new socket_type(std::forward<Args>(args)...)) {}
std::shared_ptr<ScopeRunner> handler_runner;
std::unique_ptr<socket_type> socket; // Socket must be unique_ptr since asio::ssl::stream<asio::ip::tcp::socket> is not movable
std::unique_ptr<asio::steady_timer> timer;
void close() noexcept {
error_code ec;
socket->lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ec);
socket->lowest_layer().cancel(ec);
}
void set_timeout(long seconds) noexcept {
if(seconds == 0) {
timer = nullptr;
return;
}
timer = make_steady_timer(*socket, std::chrono::seconds(seconds));
std::weak_ptr<Connection> self_weak(this->shared_from_this()); // To avoid keeping Connection instance alive longer than needed
timer->async_wait([self_weak](const error_code &ec) {
if(!ec) {
if(auto self = self_weak.lock())
self->close();
}
});
}
void cancel_timeout() noexcept {
if(timer) {
try {
timer->cancel();
}
catch(...) {
}
}
}
};
class Session {
public:
Session(std::size_t max_request_streambuf_size, std::shared_ptr<Connection> connection_) noexcept : connection(std::move(connection_)), request(new Request(max_request_streambuf_size, connection)) {}
std::shared_ptr<Connection> connection;
std::shared_ptr<Request> request;
};
public:
class Config {
friend class ServerBase<socket_type>;
Config(unsigned short port) noexcept : port(port) {}
public:
/// Port number to use. Defaults to 80 for HTTP and 443 for HTTPS. Set to 0 get an assigned port.
unsigned short port;
/// If io_service is not set, number of threads that the server will use when start() is called.
/// Defaults to 1 thread.
std::size_t thread_pool_size = 1;
/// Timeout on request completion. Defaults to 5 seconds.
long timeout_request = 5;
/// Timeout on request/response content completion. Defaults to 300 seconds.
long timeout_content = 300;
/// Maximum size of request stream buffer. Defaults to architecture maximum.
/// Reaching this limit will result in a message_size error code.
std::size_t max_request_streambuf_size = (std::numeric_limits<std::size_t>::max)();
/// IPv4 address in dotted decimal form or IPv6 address in hexadecimal notation.
/// If empty, the address will be any address.
std::string address;
/// Set to false to avoid binding the socket to an address that is already in use. Defaults to true.
bool reuse_address = true;
/// Make use of RFC 7413 or TCP Fast Open (TFO)
bool fast_open = false;
};
/// Set before calling start().
Config config;
private:
class regex_orderable : public regex::regex {
public:
std::string str;
regex_orderable(const char *regex_cstr) : regex::regex(regex_cstr), str(regex_cstr) {}
regex_orderable(std::string regex_str_) : regex::regex(regex_str_), str(std::move(regex_str_)) {}
bool operator<(const regex_orderable &rhs) const noexcept {
return str < rhs.str;
}
};
public:
/// Use this container to add resources for specific request paths depending on the given regex and method.
/// Warning: do not add or remove resources after start() is called
std::map<regex_orderable, std::map<std::string, std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)>>> resource;
/// If the request path does not match a resource regex, this function is called.
std::map<std::string, std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)>> default_resource;
/// Called when an error occurs.
std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Request>, const error_code &)> on_error;
/// Called on upgrade requests.
std::function<void(std::unique_ptr<socket_type> &, std::shared_ptr<typename ServerBase<socket_type>::Request>)> on_upgrade;
/// If you want to reuse an already created asio::io_service, store its pointer here before calling start().
std::shared_ptr<io_context> io_service;
/// Start the server.
/// If io_service is not set, an internal io_service is created instead.
/// The callback argument is called after the server is accepting connections,
/// where its parameter contains the assigned port.
void start(const std::function<void(unsigned short /*port*/)> &callback = nullptr) {
std::unique_lock<std::mutex> lock(start_stop_mutex);
asio::ip::tcp::endpoint endpoint;
if(!config.address.empty())
endpoint = asio::ip::tcp::endpoint(make_address(config.address), config.port);
else
endpoint = asio::ip::tcp::endpoint(asio::ip::tcp::v6(), config.port);
if(!io_service) {
io_service = std::make_shared<io_context>();
internal_io_service = true;
}
if(!acceptor)
acceptor = std::unique_ptr<asio::ip::tcp::acceptor>(new asio::ip::tcp::acceptor(*io_service));
try {
acceptor->open(endpoint.protocol());
}
catch(const system_error &error) {
if(error.code() == asio::error::address_family_not_supported && config.address.empty()) {
endpoint = asio::ip::tcp::endpoint(asio::ip::tcp::v4(), config.port);
acceptor->open(endpoint.protocol());
}
else
throw;
}
acceptor->set_option(asio::socket_base::reuse_address(config.reuse_address));
if(config.fast_open) {
#if defined(__linux__) && defined(TCP_FASTOPEN)
const int qlen = 5; // This seems to be the value that is used in other examples.
error_code ec;
acceptor->set_option(asio::detail::socket_option::integer<IPPROTO_TCP, TCP_FASTOPEN>(qlen), ec);
#endif // End Linux
}
acceptor->bind(endpoint);
after_bind();
auto port = acceptor->local_endpoint().port();
acceptor->listen();
accept();
if(internal_io_service && io_service->stopped())
restart(*io_service);
if(callback)
post(*io_service, [callback, port] {
callback(port);
});
if(internal_io_service) {
// If thread_pool_size>1, start m_io_service.run() in (thread_pool_size-1) threads for thread-pooling
threads.clear();
for(std::size_t c = 1; c < config.thread_pool_size; c++) {
threads.emplace_back([this]() {
this->io_service->run();
});
}
lock.unlock();
// Main thread
if(config.thread_pool_size > 0)
io_service->run();
lock.lock();
// Wait for the rest of the threads, if any, to finish as well
for(auto &t : threads)
t.join();
}
}
/// Stop accepting new requests, and close current connections.
void stop() noexcept {
std::lock_guard<std::mutex> lock(start_stop_mutex);
if(acceptor) {
error_code ec;
acceptor->close(ec);
{
LockGuard lock(connections->mutex);
for(auto &connection : connections->set)
connection->close();
connections->set.clear();
}
if(internal_io_service)
io_service->stop();
}
}
virtual ~ServerBase() noexcept {
handler_runner->stop();
stop();
}
protected:
std::mutex start_stop_mutex;
bool internal_io_service = false;
std::unique_ptr<asio::ip::tcp::acceptor> acceptor;
std::vector<std::thread> threads;
struct Connections {
Mutex mutex;
std::unordered_set<Connection *> set GUARDED_BY(mutex);
};
std::shared_ptr<Connections> connections;
std::shared_ptr<ScopeRunner> handler_runner;
ServerBase(unsigned short port) noexcept : config(port), connections(new Connections()), handler_runner(new ScopeRunner()) {}
virtual void after_bind() {}
virtual void accept() = 0;
template <typename... Args>
std::shared_ptr<Connection> create_connection(Args &&...args) noexcept {
auto connections = this->connections;
auto connection = std::shared_ptr<Connection>(new Connection(handler_runner, std::forward<Args>(args)...), [connections](Connection *connection) {
{
LockGuard lock(connections->mutex);
auto it = connections->set.find(connection);
if(it != connections->set.end())
connections->set.erase(it);
}
delete connection;
});
{
LockGuard lock(connections->mutex);
connections->set.emplace(connection.get());
}
return connection;
}
void read(const std::shared_ptr<Session> &session) {
session->connection->set_timeout(config.timeout_request);
asio::async_read_until(*session->connection->socket, session->request->streambuf, "\r\n\r\n", [this, session](const error_code &ec, std::size_t bytes_transferred) {
session->connection->set_timeout(config.timeout_content);
auto lock = session->connection->handler_runner->continue_lock();
if(!lock)
return;
session->request->header_read_time = std::chrono::system_clock::now();
if(!ec) {
// request->streambuf.size() is not necessarily the same as bytes_transferred, from Boost-docs:
// "After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter"
// The chosen solution is to extract lines from the stream directly when parsing the header. What is left of the
// streambuf (maybe some bytes of the content) is appended to in the async_read-function below (for retrieving content).
std::size_t num_additional_bytes = session->request->streambuf.size() - bytes_transferred;
if(!RequestMessage::parse(session->request->content, session->request->method, session->request->path,
session->request->query_string, session->request->http_version, session->request->header)) {
if(this->on_error)
this->on_error(session->request, make_error_code::make_error_code(errc::protocol_error));
return;
}
// If content, read that as well
auto header_it = session->request->header.find("Content-Length");
if(header_it != session->request->header.end()) {
unsigned long long content_length = 0;
try {
content_length = std::stoull(header_it->second);
}
catch(const std::exception &) {
if(this->on_error)
this->on_error(session->request, make_error_code::make_error_code(errc::protocol_error));
return;
}
if(content_length > session->request->streambuf.max_size()) {
auto response = std::shared_ptr<Response>(new Response(session, this->config.timeout_content));
response->write(StatusCode::client_error_payload_too_large);
if(this->on_error)
this->on_error(session->request, make_error_code::make_error_code(errc::message_size));
return;
}
if(content_length > num_additional_bytes) {
asio::async_read(*session->connection->socket, session->request->streambuf, asio::transfer_exactly(content_length - num_additional_bytes), [this, session](const error_code &ec, std::size_t /*bytes_transferred*/) {
auto lock = session->connection->handler_runner->continue_lock();
if(!lock)
return;
if(!ec)
this->find_resource(session);
else if(this->on_error)
this->on_error(session->request, ec);
});
}
else
this->find_resource(session);
}
else if((header_it = session->request->header.find("Transfer-Encoding")) != session->request->header.end() && header_it->second == "chunked") {
// Expect hex number to not exceed 16 bytes (64-bit number), but take into account previous additional read bytes
auto chunk_size_streambuf = std::make_shared<asio::streambuf>(std::max<std::size_t>(16 + 2, session->request->streambuf.size()));
// Move leftover bytes
auto &source = session->request->streambuf;
auto &target = *chunk_size_streambuf;
target.commit(asio::buffer_copy(target.prepare(source.size()), source.data()));
source.consume(source.size());
this->read_chunked_transfer_encoded(session, chunk_size_streambuf);
}
else
this->find_resource(session);
}
else if(this->on_error)
this->on_error(session->request, ec);
});
}
void read_chunked_transfer_encoded(const std::shared_ptr<Session> &session, const std::shared_ptr<asio::streambuf> &chunk_size_streambuf) {
asio::async_read_until(*session->connection->socket, *chunk_size_streambuf, "\r\n", [this, session, chunk_size_streambuf](const error_code &ec, size_t bytes_transferred) {
auto lock = session->connection->handler_runner->continue_lock();
if(!lock)
return;
if(!ec) {
std::istream istream(chunk_size_streambuf.get());
std::string line;
std::getline(istream, line);
bytes_transferred -= line.size() + 1;
unsigned long chunk_size = 0;
try {
chunk_size = std::stoul(line, 0, 16);
}
catch(...) {
if(this->on_error)
this->on_error(session->request, make_error_code::make_error_code(errc::protocol_error));
return;
}
if(chunk_size == 0) {
this->find_resource(session);
return;
}
if(chunk_size + session->request->streambuf.size() > session->request->streambuf.max_size()) {
auto response = std::shared_ptr<Response>(new Response(session, this->config.timeout_content));
response->write(StatusCode::client_error_payload_too_large);
if(this->on_error)
this->on_error(session->request, make_error_code::make_error_code(errc::message_size));
return;
}
auto num_additional_bytes = chunk_size_streambuf->size() - bytes_transferred;
auto bytes_to_move = std::min<std::size_t>(chunk_size, num_additional_bytes);
if(bytes_to_move > 0) {
// Move leftover bytes
auto &source = *chunk_size_streambuf;
auto &target = session->request->streambuf;
target.commit(asio::buffer_copy(target.prepare(bytes_to_move), source.data(), bytes_to_move));
source.consume(bytes_to_move);
}
if(chunk_size > num_additional_bytes) {
asio::async_read(*session->connection->socket, session->request->streambuf, asio::transfer_exactly(chunk_size - num_additional_bytes), [this, session, chunk_size_streambuf](const error_code &ec, size_t /*bytes_transferred*/) {
auto lock = session->connection->handler_runner->continue_lock();
if(!lock)
return;
if(!ec) {
// Remove "\r\n"
auto null_buffer = std::make_shared<asio::streambuf>(2);
asio::async_read(*session->connection->socket, *null_buffer, asio::transfer_exactly(2), [this, session, chunk_size_streambuf, null_buffer](const error_code &ec, size_t /*bytes_transferred*/) {
auto lock = session->connection->handler_runner->continue_lock();
if(!lock)
return;
if(!ec)
read_chunked_transfer_encoded(session, chunk_size_streambuf);
else
this->on_error(session->request, ec);
});
}
else if(this->on_error)
this->on_error(session->request, ec);
});
}
else if(2 + chunk_size > num_additional_bytes) { // If only end of chunk remains unread (\n or \r\n)
// Remove "\r\n"
if(2 + chunk_size - num_additional_bytes == 1)
istream.get();
auto null_buffer = std::make_shared<asio::streambuf>(2);
asio::async_read(*session->connection->socket, *null_buffer, asio::transfer_exactly(2 + chunk_size - num_additional_bytes), [this, session, chunk_size_streambuf, null_buffer](const error_code &ec, size_t /*bytes_transferred*/) {
auto lock = session->connection->handler_runner->continue_lock();
if(!lock)
return;
if(!ec)
read_chunked_transfer_encoded(session, chunk_size_streambuf);
else
this->on_error(session->request, ec);
});
}
else {
// Remove "\r\n"
istream.get();
istream.get();
read_chunked_transfer_encoded(session, chunk_size_streambuf);
}
}
else if(this->on_error)
this->on_error(session->request, ec);
});
}
void find_resource(const std::shared_ptr<Session> &session) {
// Upgrade connection
if(on_upgrade) {
auto it = session->request->header.find("Upgrade");
if(it != session->request->header.end()) {
// remove connection from connections
{
LockGuard lock(connections->mutex);
auto it = connections->set.find(session->connection.get());
if(it != connections->set.end())
connections->set.erase(it);
}
on_upgrade(session->connection->socket, session->request);
return;
}
}
// Find path- and method-match, and call write
for(auto &regex_method : resource) {
auto it = regex_method.second.find(session->request->method);
if(it != regex_method.second.end()) {
regex::smatch sm_res;
if(regex::regex_match(session->request->path, sm_res, regex_method.first)) {
session->request->path_match = std::move(sm_res);
write(session, it->second);
return;
}
}
}
auto it = default_resource.find(session->request->method);
if(it != default_resource.end())
write(session, it->second);
}
void write(const std::shared_ptr<Session> &session,
std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)> &resource_function) {
auto response = std::shared_ptr<Response>(new Response(session, config.timeout_content), [this](Response *response_ptr) {
auto response = std::shared_ptr<Response>(response_ptr);
response->send_on_delete([this, response](const error_code &ec) {
response->session->connection->cancel_timeout();
if(!ec) {
if(response->close_connection_after_response)
return;
auto range = response->session->request->header.equal_range("Connection");
for(auto it = range.first; it != range.second; it++) {
if(case_insensitive_equal(it->second, "close"))
return;
else if(case_insensitive_equal(it->second, "keep-alive")) {
auto new_session = std::make_shared<Session>(this->config.max_request_streambuf_size, response->session->connection);
this->read(new_session);
return;
}
}
if(response->session->request->http_version >= "1.1") {
auto new_session = std::make_shared<Session>(this->config.max_request_streambuf_size, response->session->connection);
this->read(new_session);
return;
}
}
else if(this->on_error)
this->on_error(response->session->request, ec);
});
});
try {
resource_function(response, session->request);
}
catch(const std::exception &) {
if(on_error)
on_error(session->request, make_error_code::make_error_code(errc::operation_canceled));
return;
}
}
};
template <class socket_type>
class Server : public ServerBase<socket_type> {};
using HTTP = asio::ip::tcp::socket;
template <>
class Server<HTTP> : public ServerBase<HTTP> {
public:
/// Constructs a server object.
Server() noexcept : ServerBase<HTTP>::ServerBase(80) {}
protected:
void accept() override {
auto connection = create_connection(*io_service);
acceptor->async_accept(*connection->socket, [this, connection](const error_code &ec) {
auto lock = connection->handler_runner->continue_lock();
if(!lock)
return;
// Immediately start accepting a new connection (unless io_service has been stopped)
if(ec != error::operation_aborted)
this->accept();
auto session = std::make_shared<Session>(config.max_request_streambuf_size, connection);
if(!ec) {
asio::ip::tcp::no_delay option(true);
error_code ec;
session->connection->socket->set_option(option, ec);
this->read(session);
}
else if(this->on_error)
this->on_error(session->request, ec);
});
}
};
} // namespace SimpleWeb
#endif /* SIMPLE_WEB_SERVER_HTTP_HPP */
+176
View File
@@ -0,0 +1,176 @@
#ifndef SIMPLE_WEB_STATUS_CODE_HPP
#define SIMPLE_WEB_STATUS_CODE_HPP
#include <cstdlib>
#include <map>
#include <string>
#include <unordered_map>
#include <vector>
namespace SimpleWeb {
enum class StatusCode {
unknown = 0,
information_continue = 100,
information_switching_protocols,
information_processing,
success_ok = 200,
success_created,
success_accepted,
success_non_authoritative_information,
success_no_content,
success_reset_content,
success_partial_content,
success_multi_status,
success_already_reported,
success_im_used = 226,
redirection_multiple_choices = 300,
redirection_moved_permanently,
redirection_found,
redirection_see_other,
redirection_not_modified,
redirection_use_proxy,
redirection_switch_proxy,
redirection_temporary_redirect,
redirection_permanent_redirect,
client_error_bad_request = 400,
client_error_unauthorized,
client_error_payment_required,
client_error_forbidden,
client_error_not_found,
client_error_method_not_allowed,
client_error_not_acceptable,
client_error_proxy_authentication_required,
client_error_request_timeout,
client_error_conflict,
client_error_gone,
client_error_length_required,
client_error_precondition_failed,
client_error_payload_too_large,
client_error_uri_too_long,
client_error_unsupported_media_type,
client_error_range_not_satisfiable,
client_error_expectation_failed,
client_error_im_a_teapot,
client_error_misdirection_required = 421,
client_error_unprocessable_entity,
client_error_locked,
client_error_failed_dependency,
client_error_upgrade_required = 426,
client_error_precondition_required = 428,
client_error_too_many_requests,
client_error_request_header_fields_too_large = 431,
client_error_unavailable_for_legal_reasons = 451,
server_error_internal_server_error = 500,
server_error_not_implemented,
server_error_bad_gateway,
server_error_service_unavailable,
server_error_gateway_timeout,
server_error_http_version_not_supported,
server_error_variant_also_negotiates,
server_error_insufficient_storage,
server_error_loop_detected,
server_error_not_extended = 510,
server_error_network_authentication_required
};
inline const std::map<StatusCode, std::string> &status_code_strings() {
static const std::map<StatusCode, std::string> status_code_strings = {
{StatusCode::unknown, ""},
{StatusCode::information_continue, "100 Continue"},
{StatusCode::information_switching_protocols, "101 Switching Protocols"},
{StatusCode::information_processing, "102 Processing"},
{StatusCode::success_ok, "200 OK"},
{StatusCode::success_created, "201 Created"},
{StatusCode::success_accepted, "202 Accepted"},
{StatusCode::success_non_authoritative_information, "203 Non-Authoritative Information"},
{StatusCode::success_no_content, "204 No Content"},
{StatusCode::success_reset_content, "205 Reset Content"},
{StatusCode::success_partial_content, "206 Partial Content"},
{StatusCode::success_multi_status, "207 Multi-Status"},
{StatusCode::success_already_reported, "208 Already Reported"},
{StatusCode::success_im_used, "226 IM Used"},
{StatusCode::redirection_multiple_choices, "300 Multiple Choices"},
{StatusCode::redirection_moved_permanently, "301 Moved Permanently"},
{StatusCode::redirection_found, "302 Found"},
{StatusCode::redirection_see_other, "303 See Other"},
{StatusCode::redirection_not_modified, "304 Not Modified"},
{StatusCode::redirection_use_proxy, "305 Use Proxy"},
{StatusCode::redirection_switch_proxy, "306 Switch Proxy"},
{StatusCode::redirection_temporary_redirect, "307 Temporary Redirect"},
{StatusCode::redirection_permanent_redirect, "308 Permanent Redirect"},
{StatusCode::client_error_bad_request, "400 Bad Request"},
{StatusCode::client_error_unauthorized, "401 Unauthorized"},
{StatusCode::client_error_payment_required, "402 Payment Required"},
{StatusCode::client_error_forbidden, "403 Forbidden"},
{StatusCode::client_error_not_found, "404 Not Found"},
{StatusCode::client_error_method_not_allowed, "405 Method Not Allowed"},
{StatusCode::client_error_not_acceptable, "406 Not Acceptable"},
{StatusCode::client_error_proxy_authentication_required, "407 Proxy Authentication Required"},
{StatusCode::client_error_request_timeout, "408 Request Timeout"},
{StatusCode::client_error_conflict, "409 Conflict"},
{StatusCode::client_error_gone, "410 Gone"},
{StatusCode::client_error_length_required, "411 Length Required"},
{StatusCode::client_error_precondition_failed, "412 Precondition Failed"},
{StatusCode::client_error_payload_too_large, "413 Payload Too Large"},
{StatusCode::client_error_uri_too_long, "414 URI Too Long"},
{StatusCode::client_error_unsupported_media_type, "415 Unsupported Media Type"},
{StatusCode::client_error_range_not_satisfiable, "416 Range Not Satisfiable"},
{StatusCode::client_error_expectation_failed, "417 Expectation Failed"},
{StatusCode::client_error_im_a_teapot, "418 I'm a teapot"},
{StatusCode::client_error_misdirection_required, "421 Misdirected Request"},
{StatusCode::client_error_unprocessable_entity, "422 Unprocessable Entity"},
{StatusCode::client_error_locked, "423 Locked"},
{StatusCode::client_error_failed_dependency, "424 Failed Dependency"},
{StatusCode::client_error_upgrade_required, "426 Upgrade Required"},
{StatusCode::client_error_precondition_required, "428 Precondition Required"},
{StatusCode::client_error_too_many_requests, "429 Too Many Requests"},
{StatusCode::client_error_request_header_fields_too_large, "431 Request Header Fields Too Large"},
{StatusCode::client_error_unavailable_for_legal_reasons, "451 Unavailable For Legal Reasons"},
{StatusCode::server_error_internal_server_error, "500 Internal Server Error"},
{StatusCode::server_error_not_implemented, "501 Not Implemented"},
{StatusCode::server_error_bad_gateway, "502 Bad Gateway"},
{StatusCode::server_error_service_unavailable, "503 Service Unavailable"},
{StatusCode::server_error_gateway_timeout, "504 Gateway Timeout"},
{StatusCode::server_error_http_version_not_supported, "505 HTTP Version Not Supported"},
{StatusCode::server_error_variant_also_negotiates, "506 Variant Also Negotiates"},
{StatusCode::server_error_insufficient_storage, "507 Insufficient Storage"},
{StatusCode::server_error_loop_detected, "508 Loop Detected"},
{StatusCode::server_error_not_extended, "510 Not Extended"},
{StatusCode::server_error_network_authentication_required, "511 Network Authentication Required"}};
return status_code_strings;
}
inline StatusCode status_code(const std::string &status_code_string) noexcept {
if(status_code_string.size() < 3)
return StatusCode::unknown;
auto number = status_code_string.substr(0, 3);
if(number[0] < '0' || number[0] > '9' || number[1] < '0' || number[1] > '9' || number[2] < '0' || number[2] > '9')
return StatusCode::unknown;
class StringToStatusCode : public std::unordered_map<std::string, SimpleWeb::StatusCode> {
public:
StringToStatusCode() {
for(auto &status_code : status_code_strings())
emplace(status_code.second.substr(0, 3), status_code.first);
}
};
static StringToStatusCode string_to_status_code;
auto pos = string_to_status_code.find(number);
if(pos == string_to_status_code.end())
return static_cast<StatusCode>(atoi(number.c_str()));
return pos->second;
}
inline const std::string &status_code(StatusCode status_code_enum) noexcept {
auto pos = status_code_strings().find(status_code_enum);
if(pos == status_code_strings().end()) {
static std::string empty_string;
return empty_string;
}
return pos->second;
}
} // namespace SimpleWeb
#endif // SIMPLE_WEB_STATUS_CODE_HPP
+480
View File
@@ -0,0 +1,480 @@
#ifndef SIMPLE_WEB_UTILITY_HPP
#define SIMPLE_WEB_UTILITY_HPP
#include "status_code.hpp"
#include <atomic>
#include <chrono>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#ifndef DEPRECATED
#if defined(__GNUC__) || defined(__clang__)
#define DEPRECATED __attribute__((deprecated))
#elif defined(_MSC_VER)
#define DEPRECATED __declspec(deprecated)
#else
#define DEPRECATED
#endif
#endif
#if __cplusplus > 201402L || _MSVC_LANG > 201402L
#include <string_view>
namespace SimpleWeb {
using string_view = std::string_view;
}
#elif !defined(ASIO_STANDALONE)
#include <boost/utility/string_ref.hpp>
namespace SimpleWeb {
using string_view = boost::string_ref;
}
#else
namespace SimpleWeb {
using string_view = const std::string &;
}
#endif
namespace SimpleWeb {
inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) noexcept {
return str1.size() == str2.size() &&
std::equal(str1.begin(), str1.end(), str2.begin(), [](char a, char b) {
return tolower(a) == tolower(b);
});
}
class CaseInsensitiveEqual {
public:
bool operator()(const std::string &str1, const std::string &str2) const noexcept {
return case_insensitive_equal(str1, str2);
}
};
// Based on https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/2595226#2595226
class CaseInsensitiveHash {
public:
std::size_t operator()(const std::string &str) const noexcept {
std::size_t h = 0;
std::hash<int> hash;
for(auto c : str)
h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2);
return h;
}
};
using CaseInsensitiveMultimap = std::unordered_multimap<std::string, std::string, CaseInsensitiveHash, CaseInsensitiveEqual>;
/// Percent encoding and decoding
class Percent {
public:
/// Returns percent-encoded string
static std::string encode(const std::string &value) noexcept {
static auto hex_chars = "0123456789ABCDEF";
std::string result;
result.reserve(value.size()); // Minimum size of result
for(auto &chr : value) {
if(!((chr >= '0' && chr <= '9') || (chr >= 'A' && chr <= 'Z') || (chr >= 'a' && chr <= 'z') || chr == '-' || chr == '.' || chr == '_' || chr == '~'))
result += std::string("%") + hex_chars[static_cast<unsigned char>(chr) >> 4] + hex_chars[static_cast<unsigned char>(chr) & 15];
else
result += chr;
}
return result;
}
/// Returns percent-decoded string
static std::string decode(const std::string &value) noexcept {
std::string result;
result.reserve(value.size() / 3 + (value.size() % 3)); // Minimum size of result
for(std::size_t i = 0; i < value.size(); ++i) {
auto &chr = value[i];
if(chr == '%' && i + 2 < value.size()) {
auto hex = value.substr(i + 1, 2);
auto decoded_chr = static_cast<char>(std::strtol(hex.c_str(), nullptr, 16));
result += decoded_chr;
i += 2;
}
else if(chr == '+')
result += ' ';
else
result += chr;
}
return result;
}
};
/// Query string creation and parsing
class QueryString {
public:
/// Returns query string created from given field names and values
static std::string create(const CaseInsensitiveMultimap &fields) noexcept {
std::string result;
bool first = true;
for(auto &field : fields) {
result += (!first ? "&" : "") + field.first + '=' + Percent::encode(field.second);
first = false;
}
return result;
}
/// Returns query keys with percent-decoded values.
static CaseInsensitiveMultimap parse(const std::string &query_string) noexcept {
CaseInsensitiveMultimap result;
if(query_string.empty())
return result;
std::size_t name_pos = 0;
auto name_end_pos = std::string::npos;
auto value_pos = std::string::npos;
for(std::size_t c = 0; c < query_string.size(); ++c) {
if(query_string[c] == '&') {
auto name = query_string.substr(name_pos, (name_end_pos == std::string::npos ? c : name_end_pos) - name_pos);
if(!name.empty()) {
auto value = value_pos == std::string::npos ? std::string() : query_string.substr(value_pos, c - value_pos);
result.emplace(std::move(name), Percent::decode(value));
}
name_pos = c + 1;
name_end_pos = std::string::npos;
value_pos = std::string::npos;
}
else if(query_string[c] == '=' && name_end_pos == std::string::npos) {
name_end_pos = c;
value_pos = c + 1;
}
}
if(name_pos < query_string.size()) {
auto name = query_string.substr(name_pos, (name_end_pos == std::string::npos ? std::string::npos : name_end_pos - name_pos));
if(!name.empty()) {
auto value = value_pos >= query_string.size() ? std::string() : query_string.substr(value_pos);
result.emplace(std::move(name), Percent::decode(value));
}
}
return result;
}
};
class HttpHeader {
public:
/// Parse header fields from stream
static CaseInsensitiveMultimap parse(std::istream &stream) noexcept {
CaseInsensitiveMultimap result;
std::string line;
std::size_t param_end;
while(getline(stream, line) && (param_end = line.find(':')) != std::string::npos) {
std::size_t value_start = param_end + 1;
while(value_start + 1 < line.size() && line[value_start] == ' ')
++value_start;
if(value_start < line.size())
result.emplace(line.substr(0, param_end), line.substr(value_start, line.size() - value_start - (line.back() == '\r' ? 1 : 0)));
}
return result;
}
class FieldValue {
public:
class SemicolonSeparatedAttributes {
public:
/// Parse Set-Cookie or Content-Disposition from given header field value.
/// Attribute values are percent-decoded.
static CaseInsensitiveMultimap parse(const std::string &value) {
CaseInsensitiveMultimap result;
std::size_t name_start_pos = std::string::npos;
std::size_t name_end_pos = std::string::npos;
std::size_t value_start_pos = std::string::npos;
for(std::size_t c = 0; c < value.size(); ++c) {
if(name_start_pos == std::string::npos) {
if(value[c] != ' ' && value[c] != ';')
name_start_pos = c;
}
else {
if(name_end_pos == std::string::npos) {
if(value[c] == ';') {
result.emplace(value.substr(name_start_pos, c - name_start_pos), std::string());
name_start_pos = std::string::npos;
}
else if(value[c] == '=')
name_end_pos = c;
}
else {
if(value_start_pos == std::string::npos) {
if(value[c] == '"' && c + 1 < value.size())
value_start_pos = c + 1;
else
value_start_pos = c;
}
else if(value[c] == '"' || value[c] == ';') {
result.emplace(value.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(value.substr(value_start_pos, c - value_start_pos)));
name_start_pos = std::string::npos;
name_end_pos = std::string::npos;
value_start_pos = std::string::npos;
}
}
}
}
if(name_start_pos != std::string::npos) {
if(name_end_pos == std::string::npos)
result.emplace(value.substr(name_start_pos), std::string());
else if(value_start_pos != std::string::npos) {
if(value.back() == '"')
result.emplace(value.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(value.substr(value_start_pos, value.size() - 1)));
else
result.emplace(value.substr(name_start_pos, name_end_pos - name_start_pos), Percent::decode(value.substr(value_start_pos)));
}
}
return result;
}
};
};
};
class RequestMessage {
public:
/** Parse request line and header fields from a request stream.
*
* @param[in] stream Stream to parse.
* @param[out] method HTTP method.
* @param[out] path Path from request URI.
* @param[out] query_string Query string from request URI.
* @param[out] version HTTP version.
* @param[out] header Header fields.
*
* @return True if stream is parsed successfully, false if not.
*/
static bool parse(std::istream &stream, std::string &method, std::string &path, std::string &query_string, std::string &version, CaseInsensitiveMultimap &header) noexcept {
std::string line;
std::size_t method_end;
if(getline(stream, line) && (method_end = line.find(' ')) != std::string::npos) {
method = line.substr(0, method_end);
std::size_t query_start = std::string::npos;
std::size_t path_and_query_string_end = std::string::npos;
for(std::size_t i = method_end + 1; i < line.size(); ++i) {
if(line[i] == '?' && (i + 1) < line.size() && query_start == std::string::npos)
query_start = i + 1;
else if(line[i] == ' ') {
path_and_query_string_end = i;
break;
}
}
if(path_and_query_string_end != std::string::npos) {
if(query_start != std::string::npos) {
path = line.substr(method_end + 1, query_start - method_end - 2);
query_string = line.substr(query_start, path_and_query_string_end - query_start);
}
else
path = line.substr(method_end + 1, path_and_query_string_end - method_end - 1);
std::size_t protocol_end;
if((protocol_end = line.find('/', path_and_query_string_end + 1)) != std::string::npos) {
if(line.compare(path_and_query_string_end + 1, protocol_end - path_and_query_string_end - 1, "HTTP") != 0)
return false;
version = line.substr(protocol_end + 1, line.size() - protocol_end - 2);
}
else
return false;
header = HttpHeader::parse(stream);
}
else
return false;
}
else
return false;
return true;
}
};
class ResponseMessage {
public:
/** Parse status line and header fields from a response stream.
*
* @param[in] stream Stream to parse.
* @param[out] version HTTP version.
* @param[out] status_code HTTP status code.
* @param[out] header Header fields.
*
* @return True if stream is parsed successfully, false if not.
*/
static bool parse(std::istream &stream, std::string &version, std::string &status_code, CaseInsensitiveMultimap &header) noexcept {
std::string line;
std::size_t version_end;
if(getline(stream, line) && (version_end = line.find(' ')) != std::string::npos) {
if(5 < line.size())
version = line.substr(5, version_end - 5);
else
return false;
if((version_end + 1) < line.size())
status_code = line.substr(version_end + 1, line.size() - (version_end + 1) - (line.back() == '\r' ? 1 : 0));
else
return false;
header = HttpHeader::parse(stream);
}
else
return false;
return true;
}
};
/// Date class working with formats specified in RFC 7231 Date/Time Formats
class Date {
public:
/// Returns the given std::chrono::system_clock::time_point as a string with the following format: Wed, 31 Jul 2019 11:34:23 GMT.
static std::string to_string(const std::chrono::system_clock::time_point time_point) noexcept {
static std::string result_cache;
static std::chrono::system_clock::time_point last_time_point;
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
if(std::chrono::duration_cast<std::chrono::seconds>(time_point - last_time_point).count() == 0 && !result_cache.empty())
return result_cache;
last_time_point = time_point;
std::string result;
result.reserve(29);
auto time = std::chrono::system_clock::to_time_t(time_point);
tm tm;
#if defined(_MSC_VER) || defined(__MINGW32__)
if(gmtime_s(&tm, &time) != 0)
return {};
auto gmtime = &tm;
#else
auto gmtime = gmtime_r(&time, &tm);
if(!gmtime)
return {};
#endif
switch(gmtime->tm_wday) {
case 0: result += "Sun, "; break;
case 1: result += "Mon, "; break;
case 2: result += "Tue, "; break;
case 3: result += "Wed, "; break;
case 4: result += "Thu, "; break;
case 5: result += "Fri, "; break;
case 6: result += "Sat, "; break;
}
result += gmtime->tm_mday < 10 ? '0' : static_cast<char>(gmtime->tm_mday / 10 + 48);
result += static_cast<char>(gmtime->tm_mday % 10 + 48);
switch(gmtime->tm_mon) {
case 0: result += " Jan "; break;
case 1: result += " Feb "; break;
case 2: result += " Mar "; break;
case 3: result += " Apr "; break;
case 4: result += " May "; break;
case 5: result += " Jun "; break;
case 6: result += " Jul "; break;
case 7: result += " Aug "; break;
case 8: result += " Sep "; break;
case 9: result += " Oct "; break;
case 10: result += " Nov "; break;
case 11: result += " Dec "; break;
}
auto year = gmtime->tm_year + 1900;
result += static_cast<char>(year / 1000 + 48);
result += static_cast<char>((year / 100) % 10 + 48);
result += static_cast<char>((year / 10) % 10 + 48);
result += static_cast<char>(year % 10 + 48);
result += ' ';
result += gmtime->tm_hour < 10 ? '0' : static_cast<char>(gmtime->tm_hour / 10 + 48);
result += static_cast<char>(gmtime->tm_hour % 10 + 48);
result += ':';
result += gmtime->tm_min < 10 ? '0' : static_cast<char>(gmtime->tm_min / 10 + 48);
result += static_cast<char>(gmtime->tm_min % 10 + 48);
result += ':';
result += gmtime->tm_sec < 10 ? '0' : static_cast<char>(gmtime->tm_sec / 10 + 48);
result += static_cast<char>(gmtime->tm_sec % 10 + 48);
result += " GMT";
result_cache = result;
return result;
}
};
} // namespace SimpleWeb
#ifdef __SSE2__
#include <emmintrin.h>
namespace SimpleWeb {
inline void spin_loop_pause() noexcept { _mm_pause(); }
} // namespace SimpleWeb
// TODO: need verification that the following checks are correct:
#elif defined(_MSC_VER) && _MSC_VER >= 1800 && (defined(_M_X64) || defined(_M_IX86))
#include <intrin.h>
namespace SimpleWeb {
inline void spin_loop_pause() noexcept { _mm_pause(); }
} // namespace SimpleWeb
#else
namespace SimpleWeb {
inline void spin_loop_pause() noexcept {}
} // namespace SimpleWeb
#endif
namespace SimpleWeb {
/// Makes it possible to for instance cancel Asio handlers without stopping asio::io_service.
class ScopeRunner {
/// Scope count that is set to -1 if scopes are to be canceled.
std::atomic<long> count;
public:
class SharedLock {
friend class ScopeRunner;
std::atomic<long> &count;
SharedLock(std::atomic<long> &count) noexcept : count(count) {}
SharedLock &operator=(const SharedLock &) = delete;
SharedLock(const SharedLock &) = delete;
public:
~SharedLock() noexcept {
count.fetch_sub(1);
}
};
ScopeRunner() noexcept : count(0) {}
/// Returns nullptr if scope should be exited, or a shared lock otherwise.
/// The shared lock ensures that a potential destructor call is delayed until all locks are released.
std::unique_ptr<SharedLock> continue_lock() noexcept {
long expected = count;
while(expected >= 0 && !count.compare_exchange_weak(expected, expected + 1))
spin_loop_pause();
if(expected < 0)
return nullptr;
else
return std::unique_ptr<SharedLock>(new SharedLock(count));
}
/// Blocks until all shared locks are released, then prevents future shared locks.
void stop() noexcept {
long expected = 0;
while(!count.compare_exchange_weak(expected, -1)) {
if(expected < 0)
return;
expected = 0;
spin_loop_pause();
}
}
};
} // namespace SimpleWeb
#endif // SIMPLE_WEB_UTILITY_HPP
+149
View File
@@ -0,0 +1,149 @@
#include "Simple-Web-Server/server_http.hpp"
#include "external/sqlite_modern_cpp.h"
#include <boost/filesystem.hpp>
#include <boost/program_options.hpp>
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
using namespace std;
namespace po = boost::program_options;
using HttpServer = SimpleWeb::Server<SimpleWeb::HTTP>;
#include <iostream>
#include <fstream>
inline unsigned char from_hex (unsigned char ch) {
if (ch <= '9' && ch >= '0')
ch -= '0';
else if (ch <= 'f' && ch >= 'a')
ch -= 'a' - 10;
else if (ch <= 'F' && ch >= 'A')
ch -= 'A' - 10;
else
ch = 0;
return ch;
}
const std::string urldecode (const std::string& str) {
string result;
string::size_type i;
for (i = 0; i < str.size(); ++i)
{
if (str[i] == '+')
{
result += ' ';
}
else if (str[i] == '%' && str.size() > i+2)
{
const unsigned char ch1 = from_hex(str[i+1]);
const unsigned char ch2 = from_hex(str[i+2]);
const unsigned char ch = (ch1 << 4) | ch2;
result += ch;
i += 2;
}
else
{
result += str[i];
}
}
return result;
}
int main(int argc, char* argv[]) {
string input;
string staticPath;
unsigned port;
po::options_description desc("tilemaker-server\nServe tiles from an .mbtiles archive\n\nAvailable options");
desc.add_options()
("help","show help message")
("input",po::value< string >(&input),"source .mbtiles")
("static", po::value< string >(&staticPath)->default_value("static"), "path of static files")
("port",po::value< unsigned >(&port)->default_value(8080), "port to serve tiles");
po::positional_options_description p;
p.add("input", -1);
po::variables_map vm;
try {
po::store(po::command_line_parser(argc, argv).options(desc).positional(p).run(), vm);
} catch (const po::unknown_option& ex) {
cerr << "Unknown option: " << ex.get_option_name() << endl;
return -1;
}
po::notify(vm);
if (vm.count("help")) { std::cout << desc << std::endl; return 1; }
if (vm.count("input") == 0) { std::cerr << "You must specify an .mbtiles file. Run with --help to find out more." << std::endl; return -1; }
HttpServer server;
server.config.port = port;
sqlite::database db;
db.init(input);
cout << "Starting local server on port " << server.config.port << endl;
server.resource["^/([0-9]+)/([0-9]+)/([0-9]+).pbf$"]["GET"] = [&db](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
int32_t zoom = stoi(request->path_match[1]);
int32_t col = stoi(request->path_match[2]);
int32_t y = stoi(request->path_match[3]);
vector<char> pbfBlob;
int tmsY = pow(2,zoom) - 1 - y;
db << "SELECT tile_data FROM tiles WHERE zoom_level=? AND tile_column=? AND tile_row=?" << zoom << col << tmsY >> pbfBlob;
std::string outStr(pbfBlob.begin(), pbfBlob.end());
SimpleWeb::CaseInsensitiveMultimap header;
header.emplace("Content-Encoding", "gzip");
response->write(outStr,header);
};
server.resource["^/metadata$"]["GET"] = [&db](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
rapidjson::Document document;
document.SetObject();
rapidjson::Document::AllocatorType& allocator = document.GetAllocator();
db << "SELECT name, value FROM metadata;" >> [&](string name, string value) {
if (name == "json") {
rapidjson::Document subDocument;
subDocument.Parse(value.c_str());
document.AddMember("json",subDocument,allocator);
} else {
rapidjson::Value nameVal;
nameVal.SetString(name.c_str(), allocator);
rapidjson::Value valueVal;
valueVal.SetString(value.c_str(), allocator);
document.AddMember(nameVal, valueVal, allocator);
}
};
rapidjson::StringBuffer stringbuf;
rapidjson::Writer<rapidjson::StringBuffer> writer(stringbuf);
document.Accept(writer);
response->write(stringbuf.GetString());
};
server.default_resource["GET"] = [&staticPath](shared_ptr<HttpServer::Response> response, shared_ptr<HttpServer::Request> request) {
try {
auto pathstr = urldecode(request->path);
if (pathstr == "/") pathstr = "/index.html";
auto web_root_path = boost::filesystem::canonical(staticPath);
auto path = boost::filesystem::canonical(web_root_path / pathstr);
if(distance(web_root_path.begin(), web_root_path.end()) > distance(path.begin(), path.end()) ||
!equal(web_root_path.begin(), web_root_path.end(), path.begin()))
throw invalid_argument("path must be within root path");
SimpleWeb::CaseInsensitiveMultimap header;
auto ifs = make_shared<ifstream>();
ifs->open(path.string(), ifstream::in | ios::binary | ios::ate);
if(*ifs) {
auto length = ifs->tellg();
ifs->seekg(0, ios::beg);
header.emplace("Content-Length", to_string(length));
response->write(header);
vector<char> buffer(length);
ifs->read(&buffer[0],length);
response->write(&buffer[0],length);
} else {
throw invalid_argument("could not read file");
}
} catch(const exception &e) {
response->write(SimpleWeb::StatusCode::client_error_bad_request, "Could not open path " + request->path + ": " + e.what());
}
};
server.start();
}
@@ -11,7 +11,7 @@
#include "streamvbyte_x64_encode.c"
#endif
static uint8_t svb_encode_data(uint32_t val, uint8_t * *dataPtrPtr) {
static uint8_t svb_encode_data(uint32_t val, uint8_t *__restrict__ *dataPtrPtr) {
uint8_t *dataPtr = *dataPtrPtr;
uint8_t code;
@@ -37,8 +37,8 @@ static uint8_t svb_encode_data(uint32_t val, uint8_t * *dataPtrPtr) {
}
static uint8_t *svb_encode_scalar(const uint32_t *in,
uint8_t * keyPtr,
uint8_t * dataPtr,
uint8_t *__restrict__ keyPtr,
uint8_t *__restrict__ dataPtr,
uint32_t count) {
if (count == 0)
return dataPtr; // exit immediately if no data
+3 -3
View File
@@ -8,7 +8,7 @@
#ifdef STREAMVBYTE_X64
STREAMVBYTE_TARGET_SSE41
static inline __m128i svb_decode_sse41(uint32_t key,
const uint8_t * *dataPtrPtr) {
const uint8_t *__restrict__ *dataPtrPtr) {
uint8_t len;
__m128i Data = _mm_loadu_si128((const __m128i *)*dataPtrPtr);
uint8_t *pshuf = (uint8_t *) &shuffleTable[key];
@@ -37,8 +37,8 @@ STREAMVBYTE_UNTARGET_REGION
STREAMVBYTE_TARGET_SSE41
static inline const uint8_t *svb_decode_sse41_simple(uint32_t *out,
const uint8_t * keyPtr,
const uint8_t * dataPtr,
const uint8_t *__restrict__ keyPtr,
const uint8_t *__restrict__ dataPtr,
uint64_t count) {
uint64_t keybytes = count / 4; // number of key bytes
+2 -5
View File
@@ -54,11 +54,8 @@ STREAMVBYTE_UNTARGET_REGION
STREAMVBYTE_TARGET_SSE41
static size_t streamvbyte_encode_SSE41 (const uint32_t* in, uint32_t count, uint8_t* out) {
uint32_t keyLen = (count >> 2) + (((count & 3) + 3) >> 2); // 2-bits per each rounded up to byte boundary
// cldellow: NB `restrict` is only available in C99, not c++14.
// uint8_t *restrict keyPtr = &out[0];
// uint8_t *restrict dataPtr = &out[keyLen]; // variable length data after keys
uint8_t * keyPtr = &out[0];
uint8_t * dataPtr = &out[keyLen]; // variable length data after keys
uint8_t *restrict keyPtr = &out[0];
uint8_t *restrict dataPtr = &out[keyLen]; // variable length data after keys
for (const uint32_t* end = &in[(count & ~7U)]; in != end; in += 8)
{
+245
View File
@@ -0,0 +1,245 @@
#include "geojson_processor.h"
#include <boost/asio/thread_pool.hpp>
#include <boost/asio/post.hpp>
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
#include "rapidjson/filereadstream.h"
extern bool verbose;
namespace geom = boost::geometry;
// Read GeoJSON, and create OutputObjects for all objects within the specified bounding box
void GeoJSONProcessor::read(class LayerDef &layer, uint layerNum) {
// Parse the JSON file into a RapidJSON document
rapidjson::Document doc;
FILE* fp = fopen(layer.source.c_str(), "r");
char readBuffer[65536];
rapidjson::FileReadStream is(fp, readBuffer, sizeof(readBuffer));
doc.ParseStream(is);
if (doc.HasParseError()) { throw std::runtime_error("Invalid JSON file."); }
fclose(fp);
if (strcmp(doc["type"].GetString(), "FeatureCollection") != 0) {
throw std::runtime_error("Top-level GeoJSON object must be a FeatureCollection.");
}
// Process each feature
boost::asio::thread_pool pool(threadNum);
for (auto &feature : doc["features"].GetArray()) {
boost::asio::post(pool, [&]() {
processFeature(std::move(feature.GetObject()), layer, layerNum);
});
}
pool.join();
}
template <bool Flag, typename T>
void GeoJSONProcessor::processFeature(rapidjson::GenericObject<Flag, T> feature, class LayerDef &layer, uint layerNum) {
// Recurse if it's a FeatureCollection
std::string type = feature["type"].GetString();
if (type == "FeatureCollection") {
for (auto &f : feature["features"].GetArray()) {
processFeature(std::move(f.GetObject()), layer, layerNum);
}
return;
}
// Read properties
bool hasName = false;
std::string name;
const rapidjson::Value &pr = feature["properties"];
unsigned minzoom = layer.minzoom;
AttributeIndex attrIdx = readProperties(pr, hasName, name, layer, minzoom);
// Parse geometry
auto geometry = feature["geometry"].GetObject();
std::string geomType = geometry["type"].GetString();
if (geomType=="GeometryCollection") {
// TODO: handle GeometryCollection (just put normal geometries in a list, then we can iterate through whatever we have)
// (maybe do it with a lambda)
std::cerr << "GeometryCollection not currently supported." << std::endl;
return;
}
auto coords = geometry["coordinates"].GetArray();
// Convert each type of GeoJSON geometry into Boost.Geometry equivalent
if (geomType=="Point") {
// coordinates is [x,y]
Point p( coords[0].GetDouble(), lat2latp(coords[1].GetDouble()) );
if (geom::within(p, clippingBox)) {
shpMemTiles.StoreGeometry(layerNum, layer.name, POINT_, p, layer.indexed, hasName, name, minzoom, attrIdx);
}
} else if (geomType=="LineString") {
// coordinates is [[x,y],[x,y],[x,y]...]
Linestring ls;
geom::assign_points(ls, pointsFromGeoJSONArray(coords));
MultiLinestring out;
geom::intersection(ls, clippingBox, out);
if (!geom::is_empty(out)) {
shpMemTiles.StoreGeometry(layerNum, layer.name, MULTILINESTRING_, out, layer.indexed, hasName, name, minzoom, attrIdx);
}
} else if (geomType=="Polygon") {
// coordinates is [ Ring, Ring, Ring... ]
// where Ring is [[x,y],[x,y],[x,y]...]
Polygon polygon = polygonFromGeoJSONArray(coords);
geom::correct(polygon);
MultiPolygon out;
geom::intersection(polygon, clippingBox, out);
if (!geom::is_empty(out)) {
shpMemTiles.StoreGeometry(layerNum, layer.name, POLYGON_, out, layer.indexed, hasName, name, minzoom, attrIdx);
}
} else if (geomType=="MultiPoint") {
// coordinates is [[x,y],[x,y],[x,y]...]
for (auto &pt : coords) {
Point p( pt[0].GetDouble(), lat2latp(pt[1].GetDouble()) );
if (geom::within(p, clippingBox)) {
shpMemTiles.StoreGeometry(layerNum, layer.name, POINT_, p, layer.indexed, hasName, name, minzoom, attrIdx);
}
}
} else if (geomType=="MultiLineString") {
// coordinates is [ LineString, LineString, LineString... ]
MultiLinestring mls;
for (auto &pts : coords) {
Linestring ls;
geom::assign_points(ls, pointsFromGeoJSONArray(pts.GetArray()));
mls.emplace_back(ls);
}
MultiLinestring out;
geom::intersection(mls, clippingBox, out);
if (!geom::is_empty(out)) {
shpMemTiles.StoreGeometry(layerNum, layer.name, MULTILINESTRING_, out, layer.indexed, hasName, name, minzoom, attrIdx);
}
} else if (geomType=="MultiPolygon") {
// coordinates is [ Polygon, Polygon, Polygon... ]
MultiPolygon mp;
for (auto &p : coords) {
mp.emplace_back(polygonFromGeoJSONArray(p.GetArray()));
}
geom::correct(mp);
MultiPolygon out;
geom::intersection(mp, clippingBox, out);
if (!geom::is_empty(out)) {
shpMemTiles.StoreGeometry(layerNum, layer.name, POLYGON_, out, layer.indexed, hasName, name, minzoom, attrIdx);
}
}
}
template <bool Flag, typename T>
Polygon GeoJSONProcessor::polygonFromGeoJSONArray(const rapidjson::GenericArray<Flag, T> &coords) {
Polygon poly;
bool first = true;
for (auto &r : coords) {
Ring ring;
geom::assign_points(ring, pointsFromGeoJSONArray(r.GetArray()));
if (first) { poly.outer() = std::move(ring); first = false; }
else { poly.inners().emplace_back(std::move(ring)); }
}
return poly;
}
template <bool Flag, typename T>
std::vector<Point> GeoJSONProcessor::pointsFromGeoJSONArray(const rapidjson::GenericArray<Flag, T> &arr) {
std::vector<Point> points;
for (auto &pt : arr) {
points.emplace_back(Point( pt[0].GetDouble(), lat2latp(pt[1].GetDouble()) ));
}
return points;
}
// Read properties and generate an AttributeIndex
AttributeIndex GeoJSONProcessor::readProperties(const rapidjson::Value &pr, bool &hasName, std::string &name, LayerDef &layer, unsigned &minzoom) {
std::lock_guard<std::mutex> lock(attributeMutex);
AttributeStore& attributeStore = osmLuaProcessing.getAttributeStore();
AttributeSet attributes;
// Name for indexing?
if (layer.indexName.length()>0) {
auto n = pr.FindMember(layer.indexName.c_str());
if (n != pr.MemberEnd()) {
hasName = true;
name = n->value.GetString();
}
}
if (osmLuaProcessing.canRemapShapefiles()) {
// Create table object
kaguya::LuaTable in_table = osmLuaProcessing.newTable();
for (rapidjson::Value::ConstMemberIterator it = pr.MemberBegin(); it != pr.MemberEnd(); ++it) {
std::string key = it->name.GetString();
if (!layer.useColumn(key)) continue;
if (it->value.IsString()) { in_table[key] = it->value.GetString(); }
else if (it->value.IsDouble()) { in_table[key] = it->value.GetDouble(); }
else if (it->value.IsNumber()) { in_table[key] = it->value.GetInt(); }
else {
// something different, so coerce to string
rapidjson::StringBuffer strbuf;
rapidjson::Writer<rapidjson::StringBuffer> writer(strbuf);
it->value.Accept(writer);
in_table[key] = strbuf.GetString();
}
}
// Call remap function
kaguya::LuaTable out_table = osmLuaProcessing.remapAttributes(in_table, layer.name);
// Write values to vector tiles
// (c&p from shp_processor, could be refactored)
for (auto key : out_table.keys()) {
kaguya::LuaRef val = out_table[key];
if (val.isType<std::string>()) {
attributeStore.addAttribute(attributes, key, static_cast<const std::string&>(val), 0);
layer.attributeMap[key] = 0;
} else if (val.isType<int>()) {
if (key=="_minzoom") { minzoom=val; continue; }
attributeStore.addAttribute(attributes, key, (float)val, 0);
layer.attributeMap[key] = 1;
} else if (val.isType<double>()) {
attributeStore.addAttribute(attributes, key, (float)val, 0);
layer.attributeMap[key] = 1;
} else if (val.isType<bool>()) {
attributeStore.addAttribute(attributes, key, (bool)val, 0);
layer.attributeMap[key] = 2;
} else {
// don't even think about trying to write nested tables, thank you
std::cout << "Didn't recognise Lua output type: " << val << std::endl;
}
}
} else {
// No remapping, so just copy across
for (rapidjson::Value::ConstMemberIterator it = pr.MemberBegin(); it != pr.MemberEnd(); ++it) {
std::string key = it->name.GetString();
if (!layer.useColumn(key)) continue;
if (it->value.IsString()) {
attributeStore.addAttribute(attributes, key, it->value.GetString(), 0);
layer.attributeMap[key] = 0;
} else if (it->value.IsBool()) {
attributeStore.addAttribute(attributes, key, it->value.GetBool(), 0);
layer.attributeMap[key] = 2;
} else if (it->value.IsNumber()) {
attributeStore.addAttribute(attributes, key, it->value.GetFloat(), 0);
layer.attributeMap[key] = 1;
} else {
// something different, so coerce to string
rapidjson::StringBuffer strbuf;
rapidjson::Writer<rapidjson::StringBuffer> writer(strbuf);
it->value.Accept(writer);
attributeStore.addAttribute(attributes, key, strbuf.GetString(), 0);
layer.attributeMap[key] = 0;
}
}
}
return attributeStore.add(attributes);
}
+184 -29
View File
@@ -6,6 +6,8 @@
#include "coordinates_geom.h"
#include "osm_mem_tiles.h"
#include "tag_map.h"
#include "node_store.h"
#include "polylabel.h"
using namespace std;
@@ -114,17 +116,16 @@ bool rawCoveredBy(const std::string& layerName) { return osmLuaProcessing->Cover
bool rawIsClosed() { return osmLuaProcessing->IsClosed(); }
double rawArea() { return osmLuaProcessing->Area(); }
double rawLength() { return osmLuaProcessing->Length(); }
std::vector<double> Centroid() { return osmLuaProcessing->Centroid(); }
std::vector<double> rawCentroid(kaguya::VariadicArgType algorithm) { return osmLuaProcessing->Centroid(algorithm); }
void rawLayer(const std::string& layerName, bool area) { return osmLuaProcessing->Layer(layerName, area); }
void rawLayerAsCentroid(const std::string &layerName) { return osmLuaProcessing->LayerAsCentroid(layerName); }
void rawLayerAsCentroid(const std::string &layerName, kaguya::VariadicArgType nodeSources) { return osmLuaProcessing->LayerAsCentroid(layerName, nodeSources); }
void rawMinZoom(const double z) { return osmLuaProcessing->MinZoom(z); }
void rawZOrder(const double z) { return osmLuaProcessing->ZOrder(z); }
kaguya::optional<int> rawNextRelation() { return osmLuaProcessing->NextRelation(); }
OsmLuaProcessing::OptionalRelation rawNextRelation() { return osmLuaProcessing->NextRelation(); }
void rawRestartRelations() { return osmLuaProcessing->RestartRelations(); }
std::string rawFindInRelation(const std::string& key) { return osmLuaProcessing->FindInRelation(key); }
void rawAccept() { return osmLuaProcessing->Accept(); }
double rawAreaIntersecting(const std::string& layerName) { return osmLuaProcessing->AreaIntersecting(layerName); }
std::vector<double> rawCentroid() { return osmLuaProcessing->Centroid(); }
bool supportsRemappingShapefiles = false;
@@ -557,17 +558,79 @@ void OsmLuaProcessing::Layer(const string &layerName, bool area) {
}
}
void OsmLuaProcessing::LayerAsCentroid(const string &layerName) {
// LayerAsCentroid(layerName, [centroid-algorithm, [role, [role, ...]]])
//
// Emit a point. This function can be called for nodes, ways or relations.
//
// When called for a 2D geometry, you can pass a preferred centroid algorithm
// in `centroid-algorithm`. Currently `polylabel` and `centroid` are supported.
//
// When called for a relation, you can pass a list of roles. The point of a node
// with that role will be used if available.
void OsmLuaProcessing::LayerAsCentroid(const string &layerName, kaguya::VariadicArgType varargs) {
outputKeys.clear();
if (layers.layerMap.count(layerName) == 0) {
throw out_of_range("ERROR: LayerAsCentroid(): a layer named as \"" + layerName + "\" doesn't exist.");
}
CentroidAlgorithm algorithm = defaultCentroidAlgorithm();
for (auto needleRef : varargs) {
const std::string needle = needleRef.get<std::string>();
algorithm = parseCentroidAlgorithm(needle);
break;
}
// This will be non-zero if we ultimately used a node from a relation to
// label the point.
NodeID relationNode = 0;
uint layerMinZoom = layers.layers[layers.layerMap[layerName]].minzoom;
AttributeSet attributes;
Point geomp;
bool centroidFound = false;
try {
geomp = calculateCentroid();
// If we're a relation, see if the user would prefer we use one of its members
// to label the point.
if (isRelation) {
size_t i = 0;
for (auto needleRef : varargs) {
// Skip the first vararg, it's the algorithm.
if (i == 0) continue;
i++;
const std::string needle = needleRef.get<std::string>();
// We do a linear search of the relation's members. This is not very efficient
// for relations like Tongass National Park (ID 6535292, 29,000 members),
// but in general, it's probably fine.
//
// I note that relation members seem to be sorted nodes first, then ways,
// then relations. I'm not sure if we can rely on that, so I don't
// short-circuit on the first non-node.
for (int i = 0; i < currentRelation->memids.size(); i++) {
if (currentRelation->types[i] != PbfReader::Relation::MemberType::NODE)
continue;
const protozero::data_view role = stringTable->at(currentRelation->roles_sid[i]);
if (role.size() == needle.size() && 0 == memcmp(role.data(), needle.data(), role.size())) {
relationNode = currentRelation->memids[i];
const auto ll = osmStore.nodes.at(relationNode);
geomp = Point(ll.lon, ll.latp);
centroidFound = true;
break;
}
}
if (relationNode != 0)
break;
}
}
if (!centroidFound)
geomp = calculateCentroid(algorithm);
// TODO: I think geom::is_empty always returns false for Points?
// See https://github.com/boostorg/geometry/blob/fa3623528ea27ba2c3c1327e4b67408a2b567038/include/boost/geometry/algorithms/is_empty.hpp#L103
if(geom::is_empty(geomp)) {
cerr << "Geometry is empty in OsmLuaProcessing::LayerAsCentroid (" << (isRelation ? "relation " : isWay ? "way " : "node ") << originalOsmID << ")" << endl;
return;
@@ -585,11 +648,19 @@ void OsmLuaProcessing::LayerAsCentroid(const string &layerName) {
}
NodeID id = 0;
// We don't do lazy centroids for relations - calculating their centroid
// can be quite expensive, and there's not as many of them as there are
// ways.
if (materializeGeometries || isRelation) {
// We don't do lazy geometries for centroids in these cases:
//
// - --materialize-geometries is set
// - the geometry is a relation - calculating their centroid can be quite expensive,
// and there's not as many of them as there are ways
// - when the algorithm chosen is polylabel
// We can extend lazy geometries to this, it just needs some fiddling to
// express it in the ID and measure if there's a runtime impact in computing
// the polylabel twice.
if (materializeGeometries || (isRelation && relationNode == 0) || (isWay && algorithm != CentroidAlgorithm::Centroid)) {
id = osmMemTiles.storePoint(geomp);
} else if (relationNode != 0) {
id = USE_NODE_STORE | relationNode;
} else if (!isRelation && !isWay) {
// Sometimes people call LayerAsCentroid(...) on a node, because they're
// writing a generic handler that doesn't know if it's a node or a way,
@@ -603,25 +674,65 @@ void OsmLuaProcessing::LayerAsCentroid(const string &layerName) {
outputs.push_back(std::make_pair(std::move(oo), attributes));
}
Point OsmLuaProcessing::calculateCentroid() {
Point OsmLuaProcessing::calculateCentroid(CentroidAlgorithm algorithm) {
Point centroid;
if (isRelation) {
Geometry tmp;
MultiPolygon tmp;
tmp = multiPolygonCached();
geom::centroid(tmp, centroid);
if (algorithm == CentroidAlgorithm::Polylabel) {
int index = 0;
// CONSIDER: pick precision intelligently
// Polylabel works on polygons, so for multipolygons we'll label the biggest outer.
double biggestSize = 0;
for (int i = 0; i < tmp.size(); i++) {
double size = geom::area(tmp[i]);
if (size > biggestSize) {
biggestSize = size;
index = i;
}
}
if (tmp.size() == 0)
throw geom::centroid_exception();
centroid = mapbox::polylabel(tmp[index]);
} else {
geom::centroid(tmp, centroid);
}
return Point(centroid.x()*10000000.0, centroid.y()*10000000.0);
} else if (isWay) {
Polygon p;
geom::assign_points(p, linestringCached());
geom::centroid(p, centroid);
if (algorithm == CentroidAlgorithm::Polylabel) {
// CONSIDER: pick precision intelligently
centroid = mapbox::polylabel(p);
} else {
geom::centroid(p, centroid);
}
return Point(centroid.x()*10000000.0, centroid.y()*10000000.0);
} else {
return Point(lon, latp);
}
}
std::vector<double> OsmLuaProcessing::Centroid() {
Point c = calculateCentroid();
OsmLuaProcessing::CentroidAlgorithm OsmLuaProcessing::parseCentroidAlgorithm(const std::string& algorithm) const {
if (algorithm == "polylabel") return OsmLuaProcessing::CentroidAlgorithm::Polylabel;
if (algorithm == "centroid") return OsmLuaProcessing::CentroidAlgorithm::Centroid;
throw std::runtime_error("unknown centroid algorithm " + algorithm);
}
std::vector<double> OsmLuaProcessing::Centroid(kaguya::VariadicArgType algorithmArgs) {
CentroidAlgorithm algorithm = defaultCentroidAlgorithm();
for (auto needleRef : algorithmArgs) {
const std::string needle = needleRef.get<std::string>();
algorithm = parseCentroidAlgorithm(needle);
break;
}
Point c = calculateCentroid(algorithm);
return std::vector<double> { latp2lat(c.y()/10000000.0), c.x()/10000000.0 };
}
@@ -682,10 +793,44 @@ void OsmLuaProcessing::ZOrder(const double z) {
}
// Read scanned relations
kaguya::optional<int> OsmLuaProcessing::NextRelation() {
// Kaguya doesn't support optional<tuple<int,string>>, so we write a custom serializer
// to either return nil or a tuple.
template<> struct kaguya::lua_type_traits<OsmLuaProcessing::OptionalRelation> {
typedef OsmLuaProcessing::OptionalRelation get_type;
typedef const OsmLuaProcessing::OptionalRelation& push_type;
static bool strictCheckType(lua_State* l, int index)
{
throw std::runtime_error("Lua code doesn't know how to send OptionalRelation");
}
static bool checkType(lua_State* l, int index)
{
throw std::runtime_error("Lua code doesn't know how to send OptionalRelation");
}
static get_type get(lua_State* l, int index)
{
throw std::runtime_error("Lua code doesn't know how to send OptionalRelation");
}
static int push(lua_State* l, push_type s)
{
if (s.done)
return 0;
lua_pushinteger(l, s.id);
lua_pushlstring(l, s.role.data(), s.role.size());
return 2;
}
};
OsmLuaProcessing::OptionalRelation OsmLuaProcessing::NextRelation() {
relationSubscript++;
if (relationSubscript >= relationList.size()) return kaguya::nullopt_t();
return relationList[relationSubscript];
if (relationSubscript >= relationList.size()) return { true };
return {
false,
static_cast<lua_Integer>(relationList[relationSubscript].first),
osmStore.scannedRelations.getRole(relationList[relationSubscript].second)
};
}
void OsmLuaProcessing::RestartRelations() {
@@ -693,7 +838,7 @@ void OsmLuaProcessing::RestartRelations() {
}
std::string OsmLuaProcessing::FindInRelation(const std::string &key) {
return osmStore.get_relation_tag(relationList[relationSubscript], key);
return osmStore.scannedRelations.get_relation_tag(relationList[relationSubscript].first, key);
}
// Record attribute name/type for vector_layers table
@@ -719,7 +864,7 @@ bool OsmLuaProcessing::scanRelation(WayID id, const TagMap& tags) {
// If we're persisting, we need to make a real map that owns its
// own keys and values.
osmStore.store_relation_tags(id, tags.exportToBoostMap());
osmStore.scannedRelations.store_relation_tags(id, tags.exportToBoostMap());
return true;
}
@@ -733,6 +878,10 @@ void OsmLuaProcessing::setNode(NodeID id, LatpLon node, const TagMap& tags) {
latp= node.latp;
currentTags = &tags;
if (supportsReadingRelations && osmStore.scannedRelations.node_in_any_relations(id)) {
relationList = osmStore.scannedRelations.relations_for_node(id);
}
//Start Lua processing for node
try {
luaState["node_function"]();
@@ -762,10 +911,8 @@ bool OsmLuaProcessing::setWay(WayID wayId, LatpLonVec const &llVec, const TagMap
innerWayVecPtr = nullptr;
linestringInited = polygonInited = multiPolygonInited = false;
if (supportsReadingRelations && osmStore.way_in_any_relations(wayId)) {
relationList = osmStore.relations_for_way(wayId);
} else {
relationList.clear();
if (supportsReadingRelations && osmStore.scannedRelations.way_in_any_relations(wayId)) {
relationList = osmStore.scannedRelations.relations_for_way(wayId);
}
try {
@@ -801,11 +948,19 @@ bool OsmLuaProcessing::setWay(WayID wayId, LatpLonVec const &llVec, const TagMap
}
// We are now processing a relation
void OsmLuaProcessing::setRelation(int64_t relationId, WayVec const &outerWayVec, WayVec const &innerWayVec, const TagMap& tags,
bool isNativeMP, // only OSM type=multipolygon
bool isInnerOuter) { // any OSM relation with "inner" and "outer" roles (e.g. type=multipolygon|boundary)
void OsmLuaProcessing::setRelation(
const std::vector<protozero::data_view>& stringTable,
const PbfReader::Relation& relation,
const WayVec& outerWayVec,
const WayVec& innerWayVec,
const TagMap& tags,
bool isNativeMP, // only OSM type=multipolygon
bool isInnerOuter // any OSM relation with "inner" and "outer" roles (e.g. type=multipolygon|boundary)
) {
reset();
originalOsmID = relationId;
this->stringTable = &stringTable;
currentRelation = &relation;
originalOsmID = relation.id;
isWay = true;
isRelation = true;
isClosed = isNativeMP || isInnerOuter;
+18 -6
View File
@@ -184,10 +184,22 @@ bool PbfProcessor::ScanRelations(OsmLuaProcessing& output, PbfReader::PrimitiveG
}
for (int n=0; n < pbfRelation.memids.size(); n++) {
uint64_t lastID = pbfRelation.memids[n];
if (pbfRelation.types[n] != PbfReader::Relation::MemberType::WAY) { continue; }
if (lastID >= pow(2,42)) throw std::runtime_error("Way ID in relation "+std::to_string(relid)+" negative or too large: "+std::to_string(lastID));
osmStore.mark_way_used(static_cast<WayID>(lastID));
if (isAccepted) { osmStore.relation_contains_way(relid, lastID); }
if (pbfRelation.types[n] == PbfReader::Relation::MemberType::NODE) {
if (isAccepted) {
const auto& roleView = pb.stringTable[pbfRelation.roles_sid[n]];
std::string role(roleView.data(), roleView.size());
osmStore.scannedRelations.relation_contains_node(relid, lastID, role);
}
} else if (pbfRelation.types[n] == PbfReader::Relation::MemberType::WAY) {
if (lastID >= pow(2,42)) throw std::runtime_error("Way ID in relation "+std::to_string(relid)+" negative or too large: "+std::to_string(lastID));
osmStore.mark_way_used(static_cast<WayID>(lastID));
if (isAccepted) {
const auto& roleView = pb.stringTable[pbfRelation.roles_sid[n]];
std::string role(roleView.data(), roleView.size());
osmStore.scannedRelations.relation_contains_way(relid, lastID, role);
}
}
}
}
return true;
@@ -251,7 +263,7 @@ bool PbfProcessor::ReadRelations(
try {
tags.reset();
readTags(pbfRelation, pb, tags);
output.setRelation(pbfRelation.id, outerWayVec, innerWayVec, tags, isMultiPolygon, isInnerOuter);
output.setRelation(pb.stringTable, pbfRelation, outerWayVec, innerWayVec, tags, isMultiPolygon, isInnerOuter);
} catch (std::out_of_range &err) {
// Relation is missing a member?
@@ -499,7 +511,7 @@ int PbfProcessor::ReadPbfFile(
}
std::vector<ReadPhase> all_phases = { ReadPhase::Nodes, ReadPhase::RelationScan, ReadPhase::Ways, ReadPhase::Relations };
std::vector<ReadPhase> all_phases = { ReadPhase::RelationScan, ReadPhase::Nodes, ReadPhase::Ways, ReadPhase::Relations };
for(auto phase: all_phases) {
uint effectiveShards = 1;
+1
View File
@@ -1,4 +1,5 @@
#include "pooled_string.h"
#include <stdexcept>
#include <mutex>
#include <cstring>
+57
View File
@@ -0,0 +1,57 @@
#include "relation_roles.h"
RelationRoles::RelationRoles() {
// Computed in early 2024 from popular roles: https://gist.github.com/systemed/29ea4c8d797a20dcdffee8ba907d62ea
// This is just an optimization to avoid taking a lock in the common case.
//
// The list should be refreshed if the set of popular roles dramatically changes,
// but tilemaker will still be correct, just slower.
popularRoleStrings = {
"",
"1700","1800","1900","2700","2800","2900","3000","3100","3200","above","accessfrom",
"accessto","accessvia","across","address","admin_centre","alternative","associated",
"attached_to","backward","basket","both","branch_circuit","building","buildingpart",
"builidingpart","camera","claimed","connection","contains","crossing","destination",
"device","de_facto","east","edge","empty role","end","endpoint","entrance","entry",
"ex-camera","exit","extent","facility","force","forward","from","generator","give_way",
"graph","guidepost","hidden","highway","Hole","hole","house","inner","intersection",
"in_tunnel","junction","label","lable","landuse","lateral","left","line","location_hint",
"lower","main","main_stream","marker","member","memorial","mirror","negative",
"negative:entry","negative:exit","negative:parking","object","on_bridge","outer",
"outline","part","pedestrian","pin","pit_lane","platform","positive","positive:entry",
"positive:exit","positive:parking","priority","ridge","right","road_marking","road_sign",
"room","section","sector","shell","side_stream","sign","signal","start","stop","street",
"sub-relation","subarea","subbasin","substation","switch","target","tee","through","to",
"tomb","track","tracksection","traffic_sign","tributary","trunk_circuit","under","upper",
"via","visible","walk","ways","west"
};
for (const auto& s : popularRoleStrings) {
popularRoles[s] = popularRoles.size();
}
}
std::string RelationRoles::getRole(uint16_t role) const {
if (role < popularRoleStrings.size())
return popularRoleStrings[role];
return rareRoleStrings[role - popularRoleStrings.size()];
}
uint16_t RelationRoles::getOrAddRole(const std::string& role) {
{
const auto& pos = popularRoles.find(role);
if (pos != popularRoles.end())
return pos->second;
}
std::lock_guard<std::mutex> lock(mutex);
const auto& pos = rareRoles.find(role);
if (pos != rareRoles.end())
return pos->second;
uint16_t rv = popularRoleStrings.size() + rareRoleStrings.size();
rareRoles[role] = rv;
rareRoleStrings.push_back(role);
return rv;
}
+30 -23
View File
@@ -1,5 +1,7 @@
#include "shp_mem_tiles.h"
#include <iostream>
#include <mutex>
using namespace std;
namespace geom = boost::geometry;
extern bool verbose;
@@ -55,7 +57,7 @@ void ShpMemTiles::CreateNamedLayerIndex(const std::string& layerName) {
indices[layerName]=RTree();
}
void ShpMemTiles::StoreShapefileGeometry(
void ShpMemTiles::StoreGeometry(
uint_least8_t layerNum,
const std::string& layerName,
enum OutputGeometryType geomType,
@@ -70,38 +72,27 @@ void ShpMemTiles::StoreShapefileGeometry(
geom::model::box<Point> box;
geom::envelope(geometry, box);
uint id = indexedGeometries.size();
if (isIndexed) {
indices.at(layerName).insert(std::make_pair(box, id));
if (hasName)
indexedGeometryNames[id] = name;
}
uint tilex = 0, tiley = 0;
std::shared_ptr<OutputObject> oo;
switch(geomType) {
case POINT_:
{
Point* p = boost::get<Point>(&geometry);
if (p != nullptr) {
Point sp(p->x()*10000000.0, p->y()*10000000.0);
NodeID oid = storePoint(sp);
OutputObject oo(geomType, layerNum, oid, attrIdx, minzoom);
if (isIndexed) indexedGeometries.push_back(oo);
oo = std::make_shared<OutputObject>(geomType, layerNum, oid, attrIdx, minzoom);
tilex = lon2tilex(p->x(), baseZoom);
tiley = latp2tiley(p->y(), baseZoom);
addObjectToSmallIndex(TileCoordinates(tilex, tiley), oo, 0);
}
addObjectToSmallIndex(TileCoordinates(tilex, tiley), *oo, 0);
} else { return; }
} break;
case LINESTRING_:
{
NodeID oid = storeLinestring(boost::get<Linestring>(geometry));
OutputObject oo(geomType, layerNum, oid, attrIdx, minzoom);
if (isIndexed) indexedGeometries.push_back(oo);
std::vector<OutputObject> oolist { oo };
oo = std::make_shared<OutputObject>(geomType, layerNum, oid, attrIdx, minzoom);
std::vector<OutputObject> oolist { *oo };
addGeometryToIndex(boost::get<Linestring>(geometry), oolist, 0);
} break;
@@ -109,14 +100,30 @@ void ShpMemTiles::StoreShapefileGeometry(
case POLYGON_:
{
NodeID oid = storeMultiPolygon(boost::get<MultiPolygon>(geometry));
OutputObject oo(geomType, layerNum, oid, attrIdx, minzoom);
if (isIndexed) indexedGeometries.push_back(oo);
std::vector<OutputObject> oolist { oo };
oo = std::make_shared<OutputObject>(geomType, layerNum, oid, attrIdx, minzoom);
std::vector<OutputObject> oolist { *oo };
addGeometryToIndex(boost::get<MultiPolygon>(geometry), oolist, 0);
} break;
case MULTILINESTRING_:
{
NodeID oid = storeMultiLinestring(boost::get<MultiLinestring>(geometry));
oo = std::make_shared<OutputObject>(geomType, layerNum, oid, attrIdx, minzoom);
std::vector<OutputObject> oolist { *oo };
addGeometryToIndex(boost::get<MultiLinestring>(geometry), oolist, 0);
} break;
default:
break;
throw std::runtime_error("Unknown geometry type");
}
// Add to index
if (!isIndexed) return;
std::lock_guard<std::mutex> indexLock(indexMutex);
uint id = indexedGeometries.size();
indices.at(layerName).insert(std::make_pair(box, id));
if (hasName) { indexedGeometryNames[id] = name; }
indexedGeometries.push_back(*oo);
}
+13 -22
View File
@@ -1,4 +1,4 @@
#include "read_shp.h"
#include "shp_processor.h"
#include <boost/asio/thread_pool.hpp>
#include <boost/asio/post.hpp>
@@ -12,9 +12,7 @@ namespace geom = boost::geometry;
Read shapefiles into Boost geometries
*/
std::mutex attributeMutex;
void fillPointArrayFromShapefile(vector<Point> *points, SHPObject *shape, uint part) {
void ShpProcessor::fillPointArrayFromShapefile(vector<Point> *points, SHPObject *shape, uint part) {
uint start = shape->panPartStart[part];
uint end = (int(part)==shape->nParts-1) ? shape->nVertices : shape->panPartStart[part+1];
double* const x = shape->padfX;
@@ -37,11 +35,10 @@ void fillPointArrayFromShapefile(vector<Point> *points, SHPObject *shape, uint p
}
// Read requested attributes from a shapefile, and encode into an OutputObject
AttributeIndex readShapefileAttributes(
AttributeIndex ShpProcessor::readShapefileAttributes(
DBFHandle &dbf,
int recordNum, unordered_map<int,string> &columnMap, unordered_map<int,int> &columnTypeMap,
LayerDef &layer,
OsmLuaProcessing &osmLuaProcessing, uint &minzoom) {
LayerDef &layer, uint &minzoom) {
std::lock_guard<std::mutex> lock(attributeMutex);
AttributeStore& attributeStore = osmLuaProcessing.getAttributeStore();
@@ -105,14 +102,8 @@ AttributeIndex readShapefileAttributes(
}
// Read shapefile, and create OutputObjects for all objects within the specified bounding box
void readShapefile(const Box &clippingBox,
class LayerDefinition &layers,
uint baseZoom, uint layerNum,
uint threadNum,
class ShpMemTiles &shpMemTiles,
OsmLuaProcessing &osmLuaProcessing)
void ShpProcessor::read(class LayerDef &layer, uint layerNum)
{
LayerDef &layer = layers.layers[layerNum];
const string &filename = layer.source;
const vector<string> &columns = layer.sourceColumns;
const string &indexName = layer.indexName;
@@ -162,14 +153,14 @@ void readShapefile(const Box &clippingBox,
continue;
}
boost::asio::post(pool, [&, shape]() {
boost::asio::post(pool, [&, i, shape]() {
// process attributes
string name;
bool hasName = false;
if (indexField>-1) { name=DBFReadStringAttribute(dbf, i, indexField); hasName = true;}
AttributeIndex attrIdx = readShapefileAttributes(dbf, i, columnMap, columnTypeMap, layer, osmLuaProcessing, layer.minzoom);
AttributeIndex attrIdx = readShapefileAttributes(dbf, i, columnMap, columnTypeMap, layer, layer.minzoom);
// process geometry
processShapeGeometry(shape, attrIdx, shpMemTiles, clippingBox, layer, layerNum, hasName, name);
processShapeGeometry(shape, attrIdx, layer, layerNum, hasName, name);
SHPDestroyObject(shape);
});
}
@@ -178,8 +169,8 @@ void readShapefile(const Box &clippingBox,
DBFClose(dbf);
}
void processShapeGeometry(SHPObject* shape, AttributeIndex attrIdx, ShpMemTiles &shpMemTiles,
const Box &clippingBox, const LayerDef &layer, uint layerNum, bool hasName, const string &name) {
void ShpProcessor::processShapeGeometry(SHPObject* shape, AttributeIndex attrIdx,
const LayerDef &layer, uint layerNum, bool hasName, const string &name) {
int shapeType = shape->nSHPType; // 1=point, 3=polyline, 5=(multi)polygon [8=multipoint, 11+=3D]
int minzoom = layer.minzoom;
@@ -187,7 +178,7 @@ void processShapeGeometry(SHPObject* shape, AttributeIndex attrIdx, ShpMemTiles
// Points
Point p( shape->padfX[0], lat2latp(shape->padfY[0]) );
if (geom::within(p, clippingBox)) {
shpMemTiles.StoreShapefileGeometry(layerNum, layer.name, POINT_, p, layer.indexed, hasName, name, minzoom, attrIdx);
shpMemTiles.StoreGeometry(layerNum, layer.name, POINT_, p, layer.indexed, hasName, name, minzoom, attrIdx);
}
} else if (shapeType==3) {
@@ -202,7 +193,7 @@ void processShapeGeometry(SHPObject* shape, AttributeIndex attrIdx, ShpMemTiles
MultiLinestring out;
geom::intersection(ls, clippingBox, out);
for (MultiLinestring::const_iterator it = out.begin(); it != out.end(); ++it) {
shpMemTiles.StoreShapefileGeometry(layerNum, layer.name, LINESTRING_, *it, layer.indexed, hasName, name, minzoom, attrIdx);
shpMemTiles.StoreGeometry(layerNum, layer.name, LINESTRING_, *it, layer.indexed, hasName, name, minzoom, attrIdx);
}
}
@@ -261,7 +252,7 @@ void processShapeGeometry(SHPObject* shape, AttributeIndex attrIdx, ShpMemTiles
MultiPolygon out;
geom::intersection(multi, clippingBox, out);
if (boost::size(out)>0) {
shpMemTiles.StoreShapefileGeometry(layerNum, layer.name, POLYGON_, out, layer.indexed, hasName, name, minzoom, attrIdx);
shpMemTiles.StoreGeometry(layerNum, layer.name, POLYGON_, out, layer.indexed, hasName, name, minzoom, attrIdx);
}
} else {
+20 -16
View File
@@ -51,7 +51,8 @@
#include "options_parser.h"
#include "shared_data.h"
#include "pbf_processor.h"
#include "read_shp.h"
#include "geojson_processor.h"
#include "shp_processor.h"
#include "tile_worker.h"
#include "osm_mem_tiles.h"
#include "shp_mem_tiles.h"
@@ -237,24 +238,27 @@ int main(const int argc, const char* argv[]) {
OsmLuaProcessing osmLuaProcessing(osmStore, config, layers, options.luaFile,
shpMemTiles, osmMemTiles, attributeStore, options.osm.materializeGeometries);
// ---- Load external shp files
// ---- Load external sources (shp/geojson)
for (size_t layerNum=0; layerNum<layers.layers.size(); layerNum++) {
// External layer sources
LayerDef &layer = layers.layers[layerNum];
if(layer.indexed) { shpMemTiles.CreateNamedLayerIndex(layer.name); }
{
ShpProcessor shpProcessor(clippingBox, options.threadNum, shpMemTiles, osmLuaProcessing);
GeoJSONProcessor geoJSONProcessor(clippingBox, options.threadNum, shpMemTiles, osmLuaProcessing);
for (size_t layerNum=0; layerNum<layers.layers.size(); layerNum++) {
LayerDef &layer = layers.layers[layerNum];
if(layer.indexed) { shpMemTiles.CreateNamedLayerIndex(layer.name); }
if (layer.source.size()>0) {
if (!hasClippingBox) {
cerr << "Can't read shapefiles unless a bounding box is provided." << endl;
exit(EXIT_FAILURE);
if (layer.source.size()>0) {
if (!hasClippingBox) {
cerr << "Can't read shapefiles unless a bounding box is provided." << endl;
exit(EXIT_FAILURE);
} else if (ends_with(layer.source, "json") || ends_with(layer.source, "JSON")) {
cout << "Reading GeoJSON " << layer.name << endl;
geoJSONProcessor.read(layers.layers[layerNum], layerNum);
} else {
cout << "Reading shapefile " << layer.name << endl;
shpProcessor.read(layers.layers[layerNum], layerNum);
}
}
cout << "Reading .shp " << layer.name << endl;
readShapefile(clippingBox,
layers,
config.baseZoom, layerNum,
options.threadNum,
shpMemTiles, osmLuaProcessing);
}
}
shpMemTiles.reportSize();
+24
View File
@@ -0,0 +1,24 @@
#include <iostream>
#include "external/minunit.h"
#include "relation_roles.h"
MU_TEST(test_relation_roles) {
RelationRoles rr;
mu_check(rr.getRole(0) == "");
mu_check(rr.getOrAddRole("inner") == rr.getOrAddRole("inner"));
mu_check(rr.getOrAddRole("never before seen") == rr.getOrAddRole("never before seen"));
mu_check(rr.getRole(rr.getOrAddRole("inner")) == "inner");
mu_check(rr.getRole(rr.getOrAddRole("never before seen")) == "never before seen");
}
MU_TEST_SUITE(test_suite_relation_roles) {
MU_RUN_TEST(test_relation_roles);
}
int main() {
MU_RUN_SUITE(test_suite_relation_roles);
MU_REPORT();
return MU_EXIT_CODE;
}