mirror of
https://github.com/systemed/tilemaker.git
synced 2026-05-06 08:26:46 -04:00
Merge remote-tracking branch 'origin/master' into lua-interop-3
This commit is contained in:
@@ -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
@@ -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
@@ -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 && \
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
|
||||
@@ -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,6 +1,7 @@
|
||||
#ifndef OPTIONS_PARSER_H
|
||||
#define OPTIONS_PARSER_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
@@ -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 ®ex_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 */
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
@@ -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
@@ -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,4 +1,5 @@
|
||||
#include "pooled_string.h"
|
||||
#include <stdexcept>
|
||||
#include <mutex>
|
||||
#include <cstring>
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user