Added location dependent data
This commit is contained in:
parent
9a482ff828
commit
c9673741de
@ -51,6 +51,10 @@
|
|||||||
- Fix a pre-processing bug where incorrect directions could be issued when two turns would have similar instructions and we tried to give them distinct values (https://github.com/Project-OSRM/osrm-backend/pull/4375)
|
- Fix a pre-processing bug where incorrect directions could be issued when two turns would have similar instructions and we tried to give them distinct values (https://github.com/Project-OSRM/osrm-backend/pull/4375)
|
||||||
- The entry bearing for correct the cardinality of a direction value (https://github.com/Project-OSRM/osrm-backend/pull/4353
|
- The entry bearing for correct the cardinality of a direction value (https://github.com/Project-OSRM/osrm-backend/pull/4353
|
||||||
- Change timezones in West Africa to the WAT zone so they're recognized on the Windows platform
|
- Change timezones in West Africa to the WAT zone so they're recognized on the Windows platform
|
||||||
|
- Profiles
|
||||||
|
- Added an optional argument `location_data` to `process_way`function that is a table containing OSM tags in a GeoJSON file specified by `--location-dependent-data` command line argument of `osrm-extract` (the option requires `osmium add-locations-to-ways` preparation step)
|
||||||
|
- `left_hand_driving` flag is no more global but a local Boolean flag `is_left_hand_driving` in `ExtractionWay` and `ExtractionTurn`
|
||||||
|
|
||||||
|
|
||||||
# 5.10.0
|
# 5.10.0
|
||||||
- Changes from 5.9:
|
- Changes from 5.9:
|
||||||
|
@ -61,8 +61,13 @@ class DB {
|
|||||||
});
|
});
|
||||||
|
|
||||||
w.nodes.forEach((k) => {
|
w.nodes.forEach((k) => {
|
||||||
way.ele('nd')
|
|
||||||
|
let nd = way.ele('nd')
|
||||||
.att('ref', k.id);
|
.att('ref', k.id);
|
||||||
|
if (w.add_locations) {
|
||||||
|
nd.att('lon', k.lon);
|
||||||
|
nd.att('lat', k.lat);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (var k in w.tags) {
|
for (var k in w.tags) {
|
||||||
@ -120,13 +125,14 @@ class Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Way {
|
class Way {
|
||||||
constructor (id, OSM_USER, OSM_TIMESTAMP, OSM_UID) {
|
constructor (id, OSM_USER, OSM_TIMESTAMP, OSM_UID, add_locations) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.OSM_USER = OSM_USER;
|
this.OSM_USER = OSM_USER;
|
||||||
this.OSM_TIMESTAMP = OSM_TIMESTAMP;
|
this.OSM_TIMESTAMP = OSM_TIMESTAMP;
|
||||||
this.OSM_UID = OSM_UID;
|
this.OSM_UID = OSM_UID;
|
||||||
this.tags = {};
|
this.tags = {};
|
||||||
this.nodes = [];
|
this.nodes = [];
|
||||||
|
this.add_locations = add_locations;
|
||||||
}
|
}
|
||||||
|
|
||||||
addNode (node) {
|
addNode (node) {
|
||||||
|
@ -6,10 +6,6 @@ Feature: osrm-extract lua ways:get_nodes()
|
|||||||
"""
|
"""
|
||||||
a b
|
a b
|
||||||
"""
|
"""
|
||||||
And the ways
|
|
||||||
| nodes |
|
|
||||||
| ab |
|
|
||||||
And the data has been saved to disk
|
|
||||||
|
|
||||||
Scenario: osrm-extract - Passing base file
|
Scenario: osrm-extract - Passing base file
|
||||||
Given the profile file
|
Given the profile file
|
||||||
@ -27,7 +23,50 @@ Feature: osrm-extract lua ways:get_nodes()
|
|||||||
functions.process_way = way_function
|
functions.process_way = way_function
|
||||||
return functions
|
return functions
|
||||||
"""
|
"""
|
||||||
|
And the ways
|
||||||
|
| nodes |
|
||||||
|
| ab |
|
||||||
|
And the data has been saved to disk
|
||||||
|
|
||||||
When I run "osrm-extract --profile {profile_file} {osm_file}"
|
When I run "osrm-extract --profile {profile_file} {osm_file}"
|
||||||
Then it should exit successfully
|
Then it should exit successfully
|
||||||
And stdout should contain "node id 1"
|
And stdout should contain "node id 1"
|
||||||
And stdout should contain "node id 2"
|
And stdout should contain "node id 2"
|
||||||
|
|
||||||
|
|
||||||
|
Scenario: osrm-extract location-dependent data without add-locations-to-ways preprocessing
|
||||||
|
Given the profile "testbot"
|
||||||
|
And the ways
|
||||||
|
| nodes |
|
||||||
|
| ab |
|
||||||
|
And the data has been saved to disk
|
||||||
|
|
||||||
|
When I try to run "osrm-extract --profile {profile_file} {osm_file} --location-dependent-data test/data/regions/null-island.geojson"
|
||||||
|
Then it should exit with an error
|
||||||
|
And stderr should contain "invalid location"
|
||||||
|
|
||||||
|
|
||||||
|
Scenario: osrm-extract location-dependent data
|
||||||
|
Given the profile file
|
||||||
|
"""
|
||||||
|
functions = require('testbot')
|
||||||
|
|
||||||
|
function way_function(profile, way, result, location_data)
|
||||||
|
assert(location_data)
|
||||||
|
for k, v in pairs(location_data) do print (k .. ' ' .. tostring(v)) end
|
||||||
|
result.forward_mode = mode.driving
|
||||||
|
result.forward_speed = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
functions.process_way = way_function
|
||||||
|
return functions
|
||||||
|
"""
|
||||||
|
And the ways with locations
|
||||||
|
| nodes |
|
||||||
|
| ab |
|
||||||
|
And the data has been saved to disk
|
||||||
|
|
||||||
|
When I run "osrm-extract --profile {profile_file} {osm_file} --location-dependent-data test/data/regions/null-island.geojson"
|
||||||
|
Then it should exit successfully
|
||||||
|
And stdout should contain "answer 42"
|
||||||
|
And stdout should not contain "array"
|
||||||
|
@ -129,13 +129,13 @@ module.exports = function () {
|
|||||||
q.awaitAll(callback);
|
q.awaitAll(callback);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.Given(/^the ways$/, (table, callback) => {
|
this.Given(/^the ways( with locations)?$/, (add_locations, table, callback) => {
|
||||||
if (this.osm_str) throw new Error('*** Map data already defined - did you pass an input file in this scenario?');
|
if (this.osm_str) throw new Error('*** Map data already defined - did you pass an input file in this scenario?');
|
||||||
|
|
||||||
let q = d3.queue();
|
let q = d3.queue();
|
||||||
|
|
||||||
let addWay = (row, cb) => {
|
let addWay = (row, cb) => {
|
||||||
let way = new OSM.Way(this.makeOSMId(), this.OSM_USER, this.OSM_TIMESTAMP, this.OSM_UID);
|
let way = new OSM.Way(this.makeOSMId(), this.OSM_USER, this.OSM_TIMESTAMP, this.OSM_UID, !!add_locations);
|
||||||
|
|
||||||
let nodes = row.nodes;
|
let nodes = row.nodes;
|
||||||
if (this.nameWayHash.nodes) throw new Error(util.format('*** duplicate way %s', nodes));
|
if (this.nameWayHash.nodes) throw new Error(util.format('*** duplicate way %s', nodes));
|
||||||
|
@ -79,6 +79,7 @@ struct ExtractorConfig final : storage::IOConfig
|
|||||||
|
|
||||||
boost::filesystem::path input_path;
|
boost::filesystem::path input_path;
|
||||||
boost::filesystem::path profile_path;
|
boost::filesystem::path profile_path;
|
||||||
|
boost::filesystem::path location_dependent_data_path;
|
||||||
|
|
||||||
unsigned requested_num_threads;
|
unsigned requested_num_threads;
|
||||||
unsigned small_component_size;
|
unsigned small_component_size;
|
||||||
|
44
include/extractor/location_dependent_data.hpp
Normal file
44
include/extractor/location_dependent_data.hpp
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#ifndef OSRM_LOCATION_DEPENDENT_DATA_HPP
|
||||||
|
#define OSRM_LOCATION_DEPENDENT_DATA_HPP
|
||||||
|
|
||||||
|
#include <boost/filesystem/path.hpp>
|
||||||
|
#include <boost/geometry.hpp>
|
||||||
|
#include <boost/geometry/index/rtree.hpp>
|
||||||
|
|
||||||
|
#include <osmium/osm/way.hpp>
|
||||||
|
|
||||||
|
#include <sol2/sol.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace osrm
|
||||||
|
{
|
||||||
|
namespace extractor
|
||||||
|
{
|
||||||
|
struct LocationDependentData
|
||||||
|
{
|
||||||
|
using point_t = boost::geometry::model::
|
||||||
|
point<double, 2, boost::geometry::cs::spherical_equatorial<boost::geometry::degree>>;
|
||||||
|
using polygon_t = boost::geometry::model::polygon<point_t>;
|
||||||
|
using box_t = boost::geometry::model::box<point_t>;
|
||||||
|
|
||||||
|
using polygon_position_t = std::size_t;
|
||||||
|
using rtree_t = boost::geometry::index::rtree<std::pair<box_t, polygon_position_t>,
|
||||||
|
boost::geometry::index::rstar<8>>;
|
||||||
|
|
||||||
|
using property_t = boost::variant<boost::blank, double, std::string, bool>;
|
||||||
|
using properties_t = std::unordered_map<std::string, property_t>;
|
||||||
|
|
||||||
|
LocationDependentData(const boost::filesystem::path &file_path);
|
||||||
|
|
||||||
|
sol::table operator()(sol::state &state, const osmium::Way &way) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
rtree_t rtree;
|
||||||
|
std::vector<std::pair<polygon_t, properties_t>> polygons;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -2,6 +2,7 @@
|
|||||||
#define SCRIPTING_ENVIRONMENT_LUA_HPP
|
#define SCRIPTING_ENVIRONMENT_LUA_HPP
|
||||||
|
|
||||||
#include "extractor/extraction_relation.hpp"
|
#include "extractor/extraction_relation.hpp"
|
||||||
|
#include "extractor/location_dependent_data.hpp"
|
||||||
#include "extractor/raster_source.hpp"
|
#include "extractor/raster_source.hpp"
|
||||||
#include "extractor/scripting_environment.hpp"
|
#include "extractor/scripting_environment.hpp"
|
||||||
|
|
||||||
@ -20,6 +21,11 @@ namespace extractor
|
|||||||
|
|
||||||
struct LuaScriptingContext final
|
struct LuaScriptingContext final
|
||||||
{
|
{
|
||||||
|
LuaScriptingContext(const LocationDependentData &location_dependent_data)
|
||||||
|
: location_dependent_data(location_dependent_data)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void ProcessNode(const osmium::Node &,
|
void ProcessNode(const osmium::Node &,
|
||||||
ExtractionNode &result,
|
ExtractionNode &result,
|
||||||
const ExtractionRelationContainer::RelationList &relations);
|
const ExtractionRelationContainer::RelationList &relations);
|
||||||
@ -46,6 +52,7 @@ struct LuaScriptingContext final
|
|||||||
|
|
||||||
int api_version;
|
int api_version;
|
||||||
sol::table profile_table;
|
sol::table profile_table;
|
||||||
|
const LocationDependentData &location_dependent_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -61,7 +68,8 @@ class Sol2ScriptingEnvironment final : public ScriptingEnvironment
|
|||||||
static const constexpr int SUPPORTED_MIN_API_VERSION = 0;
|
static const constexpr int SUPPORTED_MIN_API_VERSION = 0;
|
||||||
static const constexpr int SUPPORTED_MAX_API_VERSION = 3;
|
static const constexpr int SUPPORTED_MAX_API_VERSION = 3;
|
||||||
|
|
||||||
explicit Sol2ScriptingEnvironment(const std::string &file_name);
|
explicit Sol2ScriptingEnvironment(const std::string &file_name,
|
||||||
|
const boost::filesystem::path &location_dependent_data_path);
|
||||||
~Sol2ScriptingEnvironment() override = default;
|
~Sol2ScriptingEnvironment() override = default;
|
||||||
|
|
||||||
const ProfileProperties &GetProfileProperties() override;
|
const ProfileProperties &GetProfileProperties() override;
|
||||||
@ -93,6 +101,7 @@ class Sol2ScriptingEnvironment final : public ScriptingEnvironment
|
|||||||
std::mutex init_mutex;
|
std::mutex init_mutex;
|
||||||
std::string file_name;
|
std::string file_name;
|
||||||
tbb::enumerable_thread_specific<std::unique_ptr<LuaScriptingContext>> script_contexts;
|
tbb::enumerable_thread_specific<std::unique_ptr<LuaScriptingContext>> script_contexts;
|
||||||
|
const LocationDependentData location_dependent_data;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
147
src/extractor/location_dependent_data.cpp
Normal file
147
src/extractor/location_dependent_data.cpp
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
#include "extractor/location_dependent_data.hpp"
|
||||||
|
|
||||||
|
#include "util/exception.hpp"
|
||||||
|
#include "util/geojson_validation.hpp"
|
||||||
|
|
||||||
|
#include <rapidjson/document.h>
|
||||||
|
#include <rapidjson/error/en.h>
|
||||||
|
#include <rapidjson/istreamwrapper.h>
|
||||||
|
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace osrm
|
||||||
|
{
|
||||||
|
namespace extractor
|
||||||
|
{
|
||||||
|
|
||||||
|
LocationDependentData::LocationDependentData(const boost::filesystem::path &file_path)
|
||||||
|
{
|
||||||
|
if (file_path.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!boost::filesystem::exists(file_path) || !boost::filesystem::is_regular_file(file_path))
|
||||||
|
{
|
||||||
|
throw osrm::util::exception(std::string("File with location-dependent data ") +
|
||||||
|
file_path.string() + " does not exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ifstream file(file_path.string());
|
||||||
|
if (!file.is_open())
|
||||||
|
throw osrm::util::exception("failed to open " + file_path.string());
|
||||||
|
|
||||||
|
rapidjson::IStreamWrapper isw(file);
|
||||||
|
rapidjson::Document geojson;
|
||||||
|
geojson.ParseStream(isw);
|
||||||
|
if (geojson.HasParseError())
|
||||||
|
{
|
||||||
|
throw osrm::util::exception(std::string("Failed to parse ") + file_path.string() + ":" +
|
||||||
|
std::to_string(geojson.GetErrorOffset()) + " error: " +
|
||||||
|
rapidjson::GetParseError_En(geojson.GetParseError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_ASSERT(geojson.HasMember("type"));
|
||||||
|
BOOST_ASSERT(geojson["type"].IsString());
|
||||||
|
BOOST_ASSERT(std::strcmp(geojson["type"].GetString(), "FeatureCollection") == 0);
|
||||||
|
BOOST_ASSERT(geojson.HasMember("features"));
|
||||||
|
BOOST_ASSERT(geojson["features"].IsArray());
|
||||||
|
|
||||||
|
const auto &features_array = geojson["features"].GetArray();
|
||||||
|
std::vector<rtree_t::value_type> bounding_boxes;
|
||||||
|
for (rapidjson::SizeType i = 0; i < features_array.Size(); i++)
|
||||||
|
{
|
||||||
|
util::validateFeature(features_array[i]);
|
||||||
|
const auto &feature = features_array[i].GetObject();
|
||||||
|
const auto &geometry = feature["geometry"].GetObject();
|
||||||
|
BOOST_ASSERT(geometry.HasMember("type"));
|
||||||
|
|
||||||
|
// Case-sensitive check of type https://tools.ietf.org/html/rfc7946#section-1.4
|
||||||
|
if (std::strcmp(geometry["type"].GetString(), "Polygon"))
|
||||||
|
{
|
||||||
|
util::Log(logDEBUG) << "Skipping non-polygon shape in geojson file";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first array of polygon coords is the exterior ring
|
||||||
|
// https://tools.ietf.org/html/rfc7946#section-3.1.6
|
||||||
|
polygon_t polygon;
|
||||||
|
const auto &coords_outer_array = geometry["coordinates"].GetArray()[0].GetArray();
|
||||||
|
for (rapidjson::SizeType i = 0; i < coords_outer_array.Size(); ++i)
|
||||||
|
{
|
||||||
|
util::validateCoordinate(coords_outer_array[i]);
|
||||||
|
const auto &coords = coords_outer_array[i].GetArray();
|
||||||
|
polygon.outer().emplace_back(coords[0].GetDouble(), coords[1].GetDouble());
|
||||||
|
}
|
||||||
|
bounding_boxes.emplace_back(boost::geometry::return_envelope<box_t>(polygon),
|
||||||
|
polygons.size());
|
||||||
|
|
||||||
|
// Collect feature properties and store in polygons vector
|
||||||
|
auto convert_value = [](const auto &property) -> property_t {
|
||||||
|
if (property.IsString())
|
||||||
|
return std::string(property.GetString());
|
||||||
|
if (property.IsNumber())
|
||||||
|
return property.GetDouble();
|
||||||
|
if (property.IsBool())
|
||||||
|
return property.GetBool();
|
||||||
|
return {};
|
||||||
|
};
|
||||||
|
properties_t properties;
|
||||||
|
for (const auto &property : feature["properties"].GetObject())
|
||||||
|
{
|
||||||
|
properties.insert({property.name.GetString(), convert_value(property.value)});
|
||||||
|
}
|
||||||
|
polygons.push_back(std::make_pair(polygon, properties));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create R-tree for bounding boxes of collected polygons
|
||||||
|
rtree = rtree_t(bounding_boxes);
|
||||||
|
util::Log() << "Parsed " << polygons.size() << " geojson polygons with location-dependent data";
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
struct table_setter : public boost::static_visitor<>
|
||||||
|
{
|
||||||
|
table_setter(sol::table &table, const std::string &key) : table(table), key(key) {}
|
||||||
|
template <typename T> void operator()(const T &value) const { table.set(key, value); }
|
||||||
|
void operator()(const boost::blank &) const { /* ignore */}
|
||||||
|
|
||||||
|
sol::table &table;
|
||||||
|
const std::string &key;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
sol::table LocationDependentData::operator()(sol::state &state, const osmium::Way &way) const
|
||||||
|
{
|
||||||
|
if (rtree.empty())
|
||||||
|
return sol::make_object(state, sol::nil);
|
||||||
|
|
||||||
|
// HEURISTIC: use a single node (last) of the way to localize the way
|
||||||
|
// For more complicated scenarios a proper merging of multiple tags
|
||||||
|
// at one or many locations must be provided
|
||||||
|
const auto &nodes = way.nodes();
|
||||||
|
const auto &location = nodes.back().location();
|
||||||
|
const point_t point(location.lon(), location.lat());
|
||||||
|
|
||||||
|
auto table = sol::table(state, sol::create);
|
||||||
|
|
||||||
|
// Search the R-tree and collect a Lua table of tags that correspond to the location
|
||||||
|
for (auto it = rtree.qbegin(
|
||||||
|
boost::geometry::index::intersects(point) &&
|
||||||
|
boost::geometry::index::satisfies([this, &point](const rtree_t::value_type &v) {
|
||||||
|
return boost::geometry::within(point, polygons[v.second].first);
|
||||||
|
}));
|
||||||
|
it != rtree.qend();
|
||||||
|
++it)
|
||||||
|
{
|
||||||
|
for (const auto &pair : polygons[it->second].second)
|
||||||
|
{
|
||||||
|
boost::apply_visitor(table_setter(table, pair.first), pair.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -80,8 +80,9 @@ template <class T> double lonToDouble(T const &object)
|
|||||||
return static_cast<double>(util::toFloating(object.lon));
|
return static_cast<double>(util::toFloating(object.lon));
|
||||||
}
|
}
|
||||||
|
|
||||||
Sol2ScriptingEnvironment::Sol2ScriptingEnvironment(const std::string &file_name)
|
Sol2ScriptingEnvironment::Sol2ScriptingEnvironment(
|
||||||
: file_name(file_name)
|
const std::string &file_name, const boost::filesystem::path &location_dependent_data_path)
|
||||||
|
: file_name(file_name), location_dependent_data(location_dependent_data_path)
|
||||||
{
|
{
|
||||||
util::Log() << "Using script " << file_name;
|
util::Log() << "Using script " << file_name;
|
||||||
}
|
}
|
||||||
@ -666,7 +667,7 @@ LuaScriptingContext &Sol2ScriptingEnvironment::GetSol2Context()
|
|||||||
auto &ref = script_contexts.local(initialized);
|
auto &ref = script_contexts.local(initialized);
|
||||||
if (!initialized)
|
if (!initialized)
|
||||||
{
|
{
|
||||||
ref = std::make_unique<LuaScriptingContext>();
|
ref = std::make_unique<LuaScriptingContext>(location_dependent_data);
|
||||||
InitContext(*ref);
|
InitContext(*ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -983,7 +984,7 @@ void LuaScriptingContext::ProcessWay(const osmium::Way &way,
|
|||||||
way_function(profile_table, way, result, relations);
|
way_function(profile_table, way, result, relations);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
way_function(profile_table, way, result);
|
way_function(profile_table, way, result, location_dependent_data(state, way));
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -10,7 +10,8 @@ namespace osrm
|
|||||||
|
|
||||||
void extract(const extractor::ExtractorConfig &config)
|
void extract(const extractor::ExtractorConfig &config)
|
||||||
{
|
{
|
||||||
extractor::Sol2ScriptingEnvironment scripting_environment(config.profile_path.string().c_str());
|
extractor::Sol2ScriptingEnvironment scripting_environment(config.profile_path.string(),
|
||||||
|
config.location_dependent_data_path);
|
||||||
extractor::Extractor(config).run(scripting_environment);
|
extractor::Extractor(config).run(scripting_environment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +62,11 @@ return_code parseArguments(int argc,
|
|||||||
->implicit_value(true)
|
->implicit_value(true)
|
||||||
->default_value(false),
|
->default_value(false),
|
||||||
"Save conditional restrictions found during extraction to disk for use "
|
"Save conditional restrictions found during extraction to disk for use "
|
||||||
"during contraction");
|
"during contraction")("location-dependent-data",
|
||||||
|
boost::program_options::value<boost::filesystem::path>(
|
||||||
|
&extractor_config.location_dependent_data_path)
|
||||||
|
->composing(),
|
||||||
|
"GeoJSON files with location-dependent data");
|
||||||
|
|
||||||
bool dummy;
|
bool dummy;
|
||||||
// hidden options, will be allowed on command line, but will not be
|
// hidden options, will be allowed on command line, but will not be
|
||||||
|
14
test/data/regions/null-island.geojson
Normal file
14
test/data/regions/null-island.geojson
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": [ {
|
||||||
|
"type": "Feature",
|
||||||
|
"properties": {
|
||||||
|
"driving_side": "left",
|
||||||
|
"answer": 42,
|
||||||
|
"boolean": true,
|
||||||
|
"object": { "hello": "world" },
|
||||||
|
"array": [4, 8, 15, 16, 23, 42]
|
||||||
|
},
|
||||||
|
"geometry": { "type": "Polygon", "coordinates": [ [ [0, 0], [0, 2], [2, 2], [2, 0], [0, 0] ] ] }
|
||||||
|
} ]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user