Use faster method for calculating distances.

fix cmakelist
This commit is contained in:
Daniel Patterson 2018-04-19 16:27:11 -07:00 committed by Patrick Niklaus
parent 6843eb1479
commit a6cf2eee7e
37 changed files with 4016 additions and 2 deletions

View File

@ -424,6 +424,12 @@ include_directories(SYSTEM ${RAPIDJSON_INCLUDE_DIR})
set(MICROTAR_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/microtar/src")
include_directories(SYSTEM ${MICROTAR_INCLUDE_DIR})
set(MBXGEOM_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/geometry.hpp-0.9.2/include")
include_directories(SYSTEM ${MBXGEOM_INCLUDE_DIR})
set(CHEAPRULER_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party/cheap-ruler-cpp-2.5.4/include")
include_directories(SYSTEM ${CHEAPRULER_INCLUDE_DIR})
add_library(MICROTAR OBJECT "${CMAKE_CURRENT_SOURCE_DIR}/third_party/microtar/src/microtar.c")
set_property(TARGET MICROTAR PROPERTY POSITION_INDEPENDENT_CODE ON)
@ -867,4 +873,4 @@ if (ENABLE_NODE_BINDINGS)
endforeach()
add_library(check-headers STATIC EXCLUDE_FROM_ALL ${sources})
set_target_properties(check-headers PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${check_headers_dir})
endif()
endif()

View File

@ -415,7 +415,7 @@ template <typename FacadeT> EdgeDistance computeEdgeDistance(const FacadeT &faca
auto geometry_range = facade.GetUncompressedForwardGeometry(geometry_index.id);
for (auto current = geometry_range.begin(); current < geometry_range.end() - 1; ++current)
{
total_distance += util::coordinate_calculation::haversineDistance(
total_distance += util::coordinate_calculation::fccApproximateDistance(
facade.GetCoordinateOfNode(*current), facade.GetCoordinateOfNode(*std::next(current)));
}

View File

@ -43,6 +43,9 @@ inline double radToDeg(const double radian)
//! Takes the squared euclidean distance of the input coordinates. Does not return meters!
std::uint64_t squaredEuclideanDistance(const Coordinate lhs, const Coordinate rhs);
double fccApproximateDistance(const Coordinate first_coordinate,
const Coordinate second_coordinate);
double haversineDistance(const Coordinate first_coordinate, const Coordinate second_coordinate);
double greatCircleDistance(const Coordinate first_coordinate, const Coordinate second_coordinate);

View File

@ -5,6 +5,8 @@
#include <boost/assert.hpp>
#include <mapbox/cheap_ruler.hpp>
#include <algorithm>
#include <iterator>
#include <limits>
@ -18,6 +20,30 @@ namespace util
namespace coordinate_calculation
{
namespace
{
mapbox::cheap_ruler::CheapRuler cheap_ruler_cache[] = {
mapbox::cheap_ruler::CheapRuler(-90, mapbox::cheap_ruler::CheapRuler::Meters),
mapbox::cheap_ruler::CheapRuler(-80, mapbox::cheap_ruler::CheapRuler::Meters),
mapbox::cheap_ruler::CheapRuler(-70, mapbox::cheap_ruler::CheapRuler::Meters),
mapbox::cheap_ruler::CheapRuler(-60, mapbox::cheap_ruler::CheapRuler::Meters),
mapbox::cheap_ruler::CheapRuler(-50, mapbox::cheap_ruler::CheapRuler::Meters),
mapbox::cheap_ruler::CheapRuler(-40, mapbox::cheap_ruler::CheapRuler::Meters),
mapbox::cheap_ruler::CheapRuler(-30, mapbox::cheap_ruler::CheapRuler::Meters),
mapbox::cheap_ruler::CheapRuler(-20, mapbox::cheap_ruler::CheapRuler::Meters),
mapbox::cheap_ruler::CheapRuler(-10, mapbox::cheap_ruler::CheapRuler::Meters),
mapbox::cheap_ruler::CheapRuler(0, mapbox::cheap_ruler::CheapRuler::Meters),
mapbox::cheap_ruler::CheapRuler(10, mapbox::cheap_ruler::CheapRuler::Meters),
mapbox::cheap_ruler::CheapRuler(20, mapbox::cheap_ruler::CheapRuler::Meters),
mapbox::cheap_ruler::CheapRuler(30, mapbox::cheap_ruler::CheapRuler::Meters),
mapbox::cheap_ruler::CheapRuler(40, mapbox::cheap_ruler::CheapRuler::Meters),
mapbox::cheap_ruler::CheapRuler(50, mapbox::cheap_ruler::CheapRuler::Meters),
mapbox::cheap_ruler::CheapRuler(60, mapbox::cheap_ruler::CheapRuler::Meters),
mapbox::cheap_ruler::CheapRuler(70, mapbox::cheap_ruler::CheapRuler::Meters),
mapbox::cheap_ruler::CheapRuler(80, mapbox::cheap_ruler::CheapRuler::Meters),
mapbox::cheap_ruler::CheapRuler(90, mapbox::cheap_ruler::CheapRuler::Meters)};
}
// Does not project the coordinates!
std::uint64_t squaredEuclideanDistance(const Coordinate lhs, const Coordinate rhs)
{
@ -32,6 +58,19 @@ std::uint64_t squaredEuclideanDistance(const Coordinate lhs, const Coordinate rh
return result;
}
// Uses method described here:
// https://www.gpo.gov/fdsys/pkg/CFR-2005-title47-vol4/pdf/CFR-2005-title47-vol4-sec73-208.pdf
// should be within 0.1% or so of Vincenty method (assuming 19 buckets are enough)
// Should be more faster and more precise than Haversine
double fccApproximateDistance(const Coordinate coordinate_1, const Coordinate coordinate_2)
{
const auto lon1 = static_cast<double>(util::toFloating(coordinate_1.lon));
const auto lat1 = static_cast<double>(util::toFloating(coordinate_1.lat));
const auto lon2 = static_cast<double>(util::toFloating(coordinate_2.lon));
const auto lat2 = static_cast<double>(util::toFloating(coordinate_2.lat));
return cheap_ruler_cache[std::lround(lat1 / 10) + 9].distance({lon1, lat1}, {lon2, lat2});
}
double haversineDistance(const Coordinate coordinate_1, const Coordinate coordinate_2)
{
auto lon1 = static_cast<int>(coordinate_1.lon);

View File

@ -0,0 +1,18 @@
Standard: Cpp11
IndentWidth: 4
AccessModifierOffset: -4
UseTab: Never
BinPackParameters: false
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AllowShortBlocksOnASingleLine: false
AllowShortFunctionsOnASingleLine: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
AlwaysBreakTemplateDeclarations: true
NamespaceIndentation: None
PointerBindsToType: true
SpacesInParentheses: false
BreakBeforeBraces: Attach
ColumnLimit: 100
Cpp11BracedListStyle: false
SpacesBeforeTrailingComments: 1

View File

@ -0,0 +1,2 @@
build/
mason_packages/

View File

@ -0,0 +1,25 @@
language: generic
matrix:
include:
- os: linux
env: CXX=g++-4.9
sudo: required
dist: trusty
addons:
apt:
sources: [ 'ubuntu-toolchain-r-test' ]
packages: [ 'g++-4.9', 'cmake', 'cmake-data' ]
- os: linux
env: CXX=g++-5
sudo: required
dist: trusty
addons:
apt:
sources: [ 'ubuntu-toolchain-r-test' ]
packages: [ 'g++-5', 'cmake', 'cmake-data' ]
cache: apt
script:
- cmake . && make && ./cheap_ruler

View File

@ -0,0 +1,27 @@
cmake_minimum_required(VERSION 3.0.0 FATAL_ERROR)
project(cheap_ruler LANGUAGES CXX C)
include(cmake/build.cmake)
include(cmake/mason.cmake)
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.10)
set(CMAKE_CONFIGURATION_TYPES Debug Release)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -Wall -Wextra -Wpedantic -Wshadow")
mason_use(geometry VERSION 0.9.2 HEADER_ONLY)
mason_use(gtest VERSION 1.8.0)
mason_use(variant VERSION 1.1.4 HEADER_ONLY)
add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0)
add_executable(cheap_ruler
${PROJECT_SOURCE_DIR}/test/cheap_ruler.cpp
)
target_include_directories(cheap_ruler
PUBLIC ${PROJECT_SOURCE_DIR}/include
)
target_add_mason_package(cheap_ruler PRIVATE geometry)
target_add_mason_package(cheap_ruler PRIVATE gtest)
target_add_mason_package(cheap_ruler PRIVATE variant)

View File

@ -0,0 +1,15 @@
ISC License
Copyright (c) 2017, Mapbox
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

View File

@ -0,0 +1,199 @@
# cheap-ruler-cpp
Port to C++ of [Cheap Ruler](https://github.com/mapbox/cheap-ruler), a collection of very fast approximations to common geodesic measurements.
[![Build Status](https://travis-ci.org/mapbox/cheap-ruler-cpp.svg?branch=master)](https://travis-ci.org/mapbox/cheap-ruler-cpp)
# Usage
```cpp
#include <mapbox/cheap_ruler.hpp>
namespace cr = mapbox::cheap_ruler;
```
All `point`, `line_string`, `polygon`, and `box` references are [mapbox::geometry](https://github.com/mapbox/geometry.hpp) data structures.
## Create a ruler object
#### `CheapRuler(double latitude, Unit unit)`
Creates a ruler object that will approximate measurements around the given latitude with an optional distance unit. Once created, the ruler object has access to the [methods](#methods) below.
```cpp
auto ruler = cr::CheapRuler(32.8351);
auto milesRuler = cr::CheapRuler(32.8351, cr::CheapRuler::Miles);
```
Possible units:
* `cheap_ruler::CheapRuler::Unit`
* `cheap_ruler::CheapRuler::Kilometers`
* `cheap_ruler::CheapRuler::Miles`
* `cheap_ruler::CheapRuler::NauticalMiles`
* `cheap_ruler::CheapRuler::Meters`
* `cheap_ruler::CheapRuler::Yards`
* `cheap_ruler::CheapRuler::Feet`
* `cheap_ruler::CheapRuler::Inches`
#### `CheapRuler::fromTile(uint32_t y, uint32_t z)`
Creates a ruler object from tile coordinates (`y` and `z` integers). Convenient in tile-reduce scripts.
```cpp
auto ruler = cr::CheapRuler::fromTile(11041, 15);
```
## Methods
#### `distance(point a, point b)`
Given two points of the form [x = longitude, y = latitude], returns the distance (`double`).
```cpp
cr::point point_a{-96.9148, 32.8351};
cr::point point_b{-96.9146, 32.8386};
auto distance = ruler.distance(point_a, point_b);
std::clog << distance; // 0.388595
```
#### `bearing(point a, point b)`
Returns the bearing (`double`) between two points in angles.
```cpp
cr::point point_a{-96.9148, 32.8351};
cr::point point_b{-96.9146, 32.8386};
auto bearing = ruler.bearing(point_a, point_b);
std::clog << bearing; // 2.76206
```
#### `destination(point origin, double distance, double bearing)`
Returns a new point (`point`) given distance and bearing from the starting point.
```cpp
cr::point point_a{-96.9148, 32.8351};
auto dest = ruler.destination(point_a, 1.0, -175);
std::clog << dest.x << ", " << dest.y; // -96.9148, 32.8261
```
#### `offset(point origin, double dx, double dy)`
Returns a new point (`point`) given easting and northing offsets from the starting point.
```cpp
cr::point point_a{-96.9148, 32.8351};
auto os = ruler.offset(point_a, 10.0, -5.0);
std::clog << os.x << ", " << os.y; // -96.808, 32.79
```
#### `lineDistance(const line_string& points)`
Given a line (an array of points), returns the total line distance (`double`).
```cpp
cr::line_string line_a{{ -96.9, 32.8 }, { -96.8, 32.8 }, { -96.2, 32.3 }};
auto line_distance = ruler.lineDistance(line_a);
std::clog << line_distance; // 88.2962
```
#### `area(polygon poly)`
Given a polygon (an array of rings, where each ring is an array of points), returns the area (`double`).
```cpp
cr::linear_ring ring{{ -96.9, 32.8 }, { -96.8, 32.8 }, { -96.2, 32.3 }, { -96.9, 32.8 }};
auto area = ruler.area(cr::polygon{ ring });
std::clog << area; //
```
#### `along(const line_string& line, double distance)`
Returns the point (`point`) at a specified distance along the line.
```cpp
cr::linear_ring ring{{ -96.9, 32.8 }, { -96.8, 32.8 }, { -96.2, 32.3 }, { -96.9, 32.8 }};
auto area = ruler.area(cr::polygon{ ring });
std::clog << area; // 259.581
```
#### `pointOnLine(const line_string& line, point p)`
Returns a tuple of the form `std::pair<point, unsigned>` where point is closest point on the line from the given point, index is the start index of the segment with the closest point, and t is a parameter from 0 to 1 that indicates where the closest point is on that segment.
```cpp
cr::line_string line{{ -96.9, 32.8 }, { -96.8, 32.8 }, { -96.2, 32.3 }};
cr::point point{-96.9, 32.79};
auto pol = ruler.pointOnLine(line, point);
auto point = std::get<0>(pol);
std::clog << point.x << ", " << point.y; // -96.9, 32.8 (point)
std::clog << std::get<1>(pol); // 0 (index)
std::clog << std::get<2>(pol); // 0. (t)
```
#### `lineSlice(point start, point stop, const line_string& line)`
Returns a part of the given line (`line_string`) between the start and the stop points (or their closest points on the line).
```cpp
cr::line_string line{{ -96.9, 32.8 }, { -96.8, 32.8 }, { -96.2, 32.3 }};
cr::point start_point{-96.9, 32.8};
cr::point stop_point{-96.8, 32.8};
auto slice = ruler.lineSlice(start_point, stop_point, line);
std::clog << slice[0].x << ", " << slice[0].y; // -96.9, 32.8
std::clog << slice[1].x << ", " << slice[1].y; // -96.8, 32.8
```
#### `lineSliceAlong(double start, double stop, const line_string& line)`
Returns a part of the given line (`line_string`) between the start and the stop points indicated by distance along the line.
```cpp
cr::line_string line{{ -96.9, 32.8 }, { -96.8, 32.8 }, { -96.2, 32.3 }};
auto slice = ruler.lineSliceAlong(0.1, 1.2, line);
```
#### `bufferPoint(point p, double buffer)`
Given a point, returns a bounding box object ([w, s, e, n]) created from the given point buffered by a given distance.
```cpp
cr::point point{-96.9, 32.8};
auto box = ruler.bufferPoint(point, 0.1);
```
#### `bufferBBox(box bbox, double buffer)`
Given a bounding box, returns the box buffered by a given distance.
```cpp
cr::box bbox({ 30, 38 }, { 40, 39 });
auto bbox2 = ruler.bufferBBox(bbox, 1);
```
#### `insideBBox(point p, box bbox)`
Returns true (`bool`) if the given point is inside in the given bounding box, otherwise false.
```cpp
cr::box bbox({ 30, 38 }, { 40, 39 });
auto inside = ruler.insideBBox({ 35, 38.5 }, bbox);
std::clog << inside; // true
```
# Develop
```shell
# create targets
cmake .
# build
make
# test
./cheap_ruler
# or just do it all in one!
cmake . && make && ./cheap_ruler
```

View File

@ -0,0 +1,11 @@
# Generate source groups so the files are properly sorted in IDEs like Xcode.
function(create_source_groups target)
get_target_property(sources ${target} SOURCES)
foreach(file ${sources})
get_filename_component(file "${file}" ABSOLUTE)
string(REGEX REPLACE "^${CMAKE_SOURCE_DIR}/" "" group "${file}")
get_filename_component(group "${group}" DIRECTORY)
string(REPLACE "/" "\\" group "${group}")
source_group("${group}" FILES "${file}")
endforeach()
endfunction()

View File

@ -0,0 +1,235 @@
# Mason CMake
include(CMakeParseArguments)
function(mason_detect_platform)
# Determine platform
if(NOT MASON_PLATFORM)
# we call uname -s manually here since
# CMAKE_HOST_SYSTEM_NAME will not be defined before the project() call
execute_process(
COMMAND uname -s
OUTPUT_VARIABLE UNAME
OUTPUT_STRIP_TRAILING_WHITESPACE)
if (UNAME STREQUAL "Darwin")
set(MASON_PLATFORM "osx" PARENT_SCOPE)
else()
set(MASON_PLATFORM "linux" PARENT_SCOPE)
endif()
endif()
# Determine platform version string
if(NOT MASON_PLATFORM_VERSION)
# Android Studio only passes ANDROID_ABI, but we need to adjust that to the Mason
if(MASON_PLATFORM STREQUAL "android" AND NOT MASON_PLATFORM_VERSION)
if (ANDROID_ABI STREQUAL "armeabi")
set(MASON_PLATFORM_VERSION "arm-v5-9" PARENT_SCOPE)
elseif (ANDROID_ABI STREQUAL "armeabi-v7a")
set(MASON_PLATFORM_VERSION "arm-v7-9" PARENT_SCOPE)
elseif (ANDROID_ABI STREQUAL "arm64-v8a")
set(MASON_PLATFORM_VERSION "arm-v8-21" PARENT_SCOPE)
elseif (ANDROID_ABI STREQUAL "x86")
set(MASON_PLATFORM_VERSION "x86-9" PARENT_SCOPE)
elseif (ANDROID_ABI STREQUAL "x86_64")
set(MASON_PLATFORM_VERSION "x86-64-21" PARENT_SCOPE)
elseif (ANDROID_ABI STREQUAL "mips")
set(MASON_PLATFORM_VERSION "mips-9" PARENT_SCOPE)
elseif (ANDROID_ABI STREQUAL "mips64")
set(MASON_PLATFORM_VERSION "mips-64-9" PARENT_SCOPE)
else()
message(FATAL_ERROR "Unknown ANDROID_ABI '${ANDROID_ABI}'.")
endif()
elseif(MASON_PLATFORM STREQUAL "ios")
set(MASON_PLATFORM_VERSION "8.0" PARENT_SCOPE)
else()
execute_process(
COMMAND uname -m
OUTPUT_VARIABLE MASON_PLATFORM_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE)
set(MASON_PLATFORM_VERSION "${MASON_PLATFORM_VERSION}" PARENT_SCOPE)
endif()
endif()
endfunction()
function(mason_use _PACKAGE)
if(NOT _PACKAGE)
message(FATAL_ERROR "[Mason] No package name given")
endif()
cmake_parse_arguments("" "HEADER_ONLY" "VERSION" "" ${ARGN})
if(_UNPARSED_ARGUMENTS)
message(FATAL_ERROR "[Mason] mason_use() called with unrecognized arguments: ${_UNPARSED_ARGUMENTS}")
endif()
if(NOT _VERSION)
message(FATAL_ERROR "[Mason] Specifying a version is required")
endif()
if(MASON_PACKAGE_${_PACKAGE}_INVOCATION STREQUAL "${MASON_INVOCATION}")
# Check that the previous invocation of mason_use didn't select another version of this package
if(NOT MASON_PACKAGE_${_PACKAGE}_VERSION STREQUAL ${_VERSION})
message(FATAL_ERROR "[Mason] Already using ${_PACKAGE} ${MASON_PACKAGE_${_PACKAGE}_VERSION}. Cannot select version ${_VERSION}.")
endif()
else()
if(_HEADER_ONLY)
set(_PLATFORM_ID "headers")
else()
set(_PLATFORM_ID "${MASON_PLATFORM}-${MASON_PLATFORM_VERSION}")
endif()
set(_SLUG "${_PLATFORM_ID}/${_PACKAGE}/${_VERSION}")
set(_INSTALL_PATH "${MASON_PACKAGE_DIR}/${_SLUG}")
file(RELATIVE_PATH _INSTALL_PATH_RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${_INSTALL_PATH}")
if(NOT EXISTS "${_INSTALL_PATH}")
set(_CACHE_PATH "${MASON_PACKAGE_DIR}/.binaries/${_SLUG}.tar.gz")
if (NOT EXISTS "${_CACHE_PATH}")
# Download the package
set(_URL "${MASON_REPOSITORY}/${_SLUG}.tar.gz")
message("[Mason] Downloading package ${_URL}...")
set(_FAILED)
set(_ERROR)
# Note: some CMake versions are compiled without SSL support
get_filename_component(_CACHE_DIR "${_CACHE_PATH}" DIRECTORY)
file(MAKE_DIRECTORY "${_CACHE_DIR}")
execute_process(
COMMAND curl --retry 3 -s -f -S -L "${_URL}" -o "${_CACHE_PATH}.tmp"
RESULT_VARIABLE _FAILED
ERROR_VARIABLE _ERROR)
if(_FAILED)
message(FATAL_ERROR "[Mason] Failed to download ${_URL}: ${_ERROR}")
else()
# We downloaded to a temporary file to prevent half-finished downloads
file(RENAME "${_CACHE_PATH}.tmp" "${_CACHE_PATH}")
endif()
endif()
# Unpack the package
message("[Mason] Unpacking package to ${_INSTALL_PATH_RELATIVE}...")
file(MAKE_DIRECTORY "${_INSTALL_PATH}")
execute_process(
COMMAND ${CMAKE_COMMAND} -E tar xzf "${_CACHE_PATH}"
WORKING_DIRECTORY "${_INSTALL_PATH}")
endif()
# Error out if there is no config file.
if(NOT EXISTS "${_INSTALL_PATH}/mason.ini")
message(FATAL_ERROR "[Mason] Could not find mason.ini for package ${_PACKAGE} ${_VERSION}")
endif()
set(MASON_PACKAGE_${_PACKAGE}_PREFIX "${_INSTALL_PATH}" CACHE STRING "${_PACKAGE} ${_INSTALL_PATH}" FORCE)
mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_PREFIX)
# Load the configuration from the ini file
file(STRINGS "${_INSTALL_PATH}/mason.ini" _CONFIG_FILE)
foreach(_LINE IN LISTS _CONFIG_FILE)
string(REGEX MATCH "^([a-z_]+) *= *" _KEY "${_LINE}")
if (_KEY)
string(LENGTH "${_KEY}" _KEY_LENGTH)
string(SUBSTRING "${_LINE}" ${_KEY_LENGTH} -1 _VALUE)
string(REGEX REPLACE ";.*$" "" _VALUE "${_VALUE}") # Trim trailing commas
string(REPLACE "{prefix}" "${_INSTALL_PATH}" _VALUE "${_VALUE}")
string(STRIP "${_VALUE}" _VALUE)
string(REPLACE "=" "" _KEY "${_KEY}")
string(STRIP "${_KEY}" _KEY)
string(TOUPPER "${_KEY}" _KEY)
if(_KEY STREQUAL "INCLUDE_DIRS" OR _KEY STREQUAL "STATIC_LIBS" )
separate_arguments(_VALUE)
endif()
set(MASON_PACKAGE_${_PACKAGE}_${_KEY} "${_VALUE}" CACHE STRING "${_PACKAGE} ${_KEY}" FORCE)
mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_${_KEY})
endif()
endforeach()
# Compare version in the package to catch errors early on
if(NOT _VERSION STREQUAL MASON_PACKAGE_${_PACKAGE}_VERSION)
message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has version '${MASON_PACKAGE_${_PACKAGE}_VERSION}', but required '${_VERSION}'")
endif()
if(NOT _PACKAGE STREQUAL MASON_PACKAGE_${_PACKAGE}_NAME)
message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has name '${MASON_PACKAGE_${_PACKAGE}_NAME}', but required '${_NAME}'")
endif()
if(NOT _HEADER_ONLY)
if(NOT MASON_PLATFORM STREQUAL MASON_PACKAGE_${_PACKAGE}_PLATFORM)
message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has platform '${MASON_PACKAGE_${_PACKAGE}_PLATFORM}', but required '${MASON_PLATFORM}'")
endif()
if(NOT MASON_PLATFORM_VERSION STREQUAL MASON_PACKAGE_${_PACKAGE}_PLATFORM_VERSION)
message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has platform version '${MASON_PACKAGE_${_PACKAGE}_PLATFORM_VERSION}', but required '${MASON_PLATFORM_VERSION}'")
endif()
endif()
# Concatenate the static libs and libraries
set(_LIBRARIES)
list(APPEND _LIBRARIES ${MASON_PACKAGE_${_PACKAGE}_STATIC_LIBS} ${MASON_PACKAGE_${_PACKAGE}_LDFLAGS})
set(MASON_PACKAGE_${_PACKAGE}_LIBRARIES "${_LIBRARIES}" CACHE STRING "${_PACKAGE} _LIBRARIES" FORCE)
mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_LIBRARIES)
if(NOT _HEADER_ONLY)
string(REGEX MATCHALL "(^| +)-L *([^ ]+)" MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS "${MASON_PACKAGE_${_PACKAGE}_LDFLAGS}")
string(REGEX REPLACE "(^| +)-L *" "\\1" MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS "${MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS}")
set(MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS "${MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS}" CACHE STRING "${_PACKAGE} ${MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS}" FORCE)
mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS)
endif()
# Store invocation ID to prevent different versions of the same package in one invocation
set(MASON_PACKAGE_${_PACKAGE}_INVOCATION "${MASON_INVOCATION}" CACHE INTERNAL "${_PACKAGE} invocation ID" FORCE)
endif()
endfunction()
macro(target_add_mason_package _TARGET _VISIBILITY _PACKAGE)
if (NOT MASON_PACKAGE_${_PACKAGE}_INVOCATION)
message(FATAL_ERROR "[Mason] Package ${_PACKAGE} has not been initialized yet")
endif()
target_include_directories(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_INCLUDE_DIRS}")
target_compile_definitions(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_DEFINITIONS}")
target_compile_options(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_OPTIONS}")
target_link_libraries(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_LIBRARIES}")
endmacro()
# Setup
string(RANDOM LENGTH 16 MASON_INVOCATION)
# Read environment variables if CMake is run in command mode
if (CMAKE_ARGC)
set(MASON_PLATFORM "$ENV{MASON_PLATFORM}")
set(MASON_PLATFORM_VERSION "$ENV{MASON_PLATFORM_VERSION}")
set(MASON_PACKAGE_DIR "$ENV{MASON_PACKAGE_DIR}")
set(MASON_REPOSITORY "$ENV{MASON_REPOSITORY}")
endif()
# Directory where Mason packages are located; typically ends with mason_packages
if (NOT MASON_PACKAGE_DIR)
set(MASON_PACKAGE_DIR "${CMAKE_SOURCE_DIR}/mason_packages")
endif()
# URL prefix of where packages are located.
if (NOT MASON_REPOSITORY)
set(MASON_REPOSITORY "https://mason-binaries.s3.amazonaws.com")
endif()
mason_detect_platform()
# Execute commands if CMake is run in command mode
if (CMAKE_ARGC)
# Collect remaining arguments for passing to mason_use
set(_MASON_ARGS)
foreach(I RANGE 4 ${CMAKE_ARGC})
list(APPEND _MASON_ARGS "${CMAKE_ARGV${I}}")
endforeach()
# Install the package
mason_use(${_MASON_ARGS})
# Optionally print variables
if(DEFINED MASON_PACKAGE_${CMAKE_ARGV4}_${CMAKE_ARGV3})
# CMake can't write to stdout with message()
execute_process(COMMAND ${CMAKE_COMMAND} -E echo "${MASON_PACKAGE_${CMAKE_ARGV4}_${CMAKE_ARGV3}}")
endif()
endif()

View File

@ -0,0 +1,354 @@
#pragma once
#include <mapbox/geometry.hpp>
#include <cmath>
#include <cstdint>
#include <limits>
#include <tuple>
#include <utility>
namespace mapbox {
namespace cheap_ruler {
using box = geometry::box<double>;
using line_string = geometry::line_string<double>;
using linear_ring = geometry::linear_ring<double>;
using multi_line_string = geometry::multi_line_string<double>;
using point = geometry::point<double>;
using polygon = geometry::polygon<double>;
class CheapRuler {
public:
enum Unit {
Kilometers,
Miles,
NauticalMiles,
Meters,
Metres = Meters,
Yards,
Feet,
Inches
};
//
// A collection of very fast approximations to common geodesic measurements. Useful
// for performance-sensitive code that measures things on a city scale. Point coordinates
// are in the [x = longitude, y = latitude] form.
//
explicit CheapRuler(double latitude, Unit unit = Kilometers) {
double m = 0.;
switch (unit) {
case Kilometers:
m = 1.;
break;
case Miles:
m = 1000. / 1609.344;
break;
case NauticalMiles:
m = 1000. / 1852.;
break;
case Meters:
m = 1000.;
break;
case Yards:
m = 1000. / 0.9144;
break;
case Feet:
m = 1000. / 0.3048;
break;
case Inches:
m = 1000. / 0.0254;
break;
}
auto cos = std::cos(latitude * M_PI / 180.);
auto cos2 = 2. * cos * cos - 1.;
auto cos3 = 2. * cos * cos2 - cos;
auto cos4 = 2. * cos * cos3 - cos2;
auto cos5 = 2. * cos * cos4 - cos3;
// multipliers for converting longitude and latitude
// degrees into distance (http://1.usa.gov/1Wb1bv7)
kx = m * (111.41513 * cos - 0.09455 * cos3 + 0.00012 * cos5);
ky = m * (111.13209 - 0.56605 * cos2 + 0.0012 * cos4);
}
static CheapRuler fromTile(uint32_t y, uint32_t z) {
double n = M_PI * (1. - 2. * (y + 0.5) / std::pow(2., z));
double latitude = std::atan(0.5 * (std::exp(n) - std::exp(-n))) * 180. / M_PI;
return CheapRuler(latitude);
}
//
// Given two points of the form [x = longitude, y = latitude], returns the distance.
//
double distance(point a, point b) {
auto dx = (a.x - b.x) * kx;
auto dy = (a.y - b.y) * ky;
return std::sqrt(dx * dx + dy * dy);
}
//
// Returns the bearing between two points in angles.
//
double bearing(point a, point b) {
auto dx = (b.x - a.x) * kx;
auto dy = (b.y - a.y) * ky;
if (!dx && !dy) {
return 0.;
}
auto value = std::atan2(dx, dy) * 180. / M_PI;
if (value > 180.) {
value -= 360.;
}
return value;
}
//
// Returns a new point given distance and bearing from the starting point.
//
point destination(point origin, double dist, double bearing_) {
auto a = (90. - bearing_) * M_PI / 180.;
return offset(origin, std::cos(a) * dist, std::sin(a) * dist);
}
//
// Returns a new point given easting and northing offsets from the starting point.
//
point offset(point origin, double dx, double dy) {
return point(origin.x + dx / kx, origin.y + dy / ky);
}
//
// Given a line (an array of points), returns the total line distance.
//
double lineDistance(const line_string& points) {
double total = 0.;
for (unsigned i = 0; i < points.size() - 1; ++i) {
total += distance(points[i], points[i + 1]);
}
return total;
}
//
// Given a polygon (an array of rings, where each ring is an array of points),
// returns the area.
//
double area(polygon poly) {
double sum = 0.;
for (unsigned i = 0; i < poly.size(); ++i) {
auto& ring = poly[i];
for (unsigned j = 0, len = ring.size(), k = len - 1; j < len; k = j++) {
sum += (ring[j].x - ring[k].x) * (ring[j].y + ring[k].y) * (i ? -1. : 1.);
}
}
return (std::abs(sum) / 2.) * kx * ky;
}
//
// Returns the point at a specified distance along the line.
//
point along(const line_string& line, double dist) {
double sum = 0.;
if (dist <= 0.) {
return line[0];
}
for (unsigned i = 0; i < line.size() - 1; ++i) {
auto p0 = line[i];
auto p1 = line[i + 1];
auto d = distance(p0, p1);
sum += d;
if (sum > dist) {
return interpolate(p0, p1, (dist - (sum - d)) / d);
}
}
return line[line.size() - 1];
}
//
// Returns a tuple of the form <point, index, t> where point is closest point on the line
// from the given point, index is the start index of the segment with the closest point,
// and t is a parameter from 0 to 1 that indicates where the closest point is on that segment.
//
std::tuple<point, unsigned, double> pointOnLine(const line_string& line, point p) {
double minDist = std::numeric_limits<double>::infinity();
double minX = 0., minY = 0., minI = 0., minT = 0.;
for (unsigned i = 0; i < line.size() - 1; ++i) {
auto t = 0.;
auto x = line[i].x;
auto y = line[i].y;
auto dx = (line[i + 1].x - x) * kx;
auto dy = (line[i + 1].y - y) * ky;
if (dx != 0. || dy != 0.) {
t = ((p.x - x) * kx * dx + (p.y - y) * ky * dy) / (dx * dx + dy * dy);
if (t > 1) {
x = line[i + 1].x;
y = line[i + 1].y;
} else if (t > 0) {
x += (dx / kx) * t;
y += (dy / ky) * t;
}
}
dx = (p.x - x) * kx;
dy = (p.y - y) * ky;
auto sqDist = dx * dx + dy * dy;
if (sqDist < minDist) {
minDist = sqDist;
minX = x;
minY = y;
minI = i;
minT = t;
}
}
return std::make_tuple(
point(minX, minY), minI, ::fmax(0., ::fmin(1., minT)));
}
//
// Returns a part of the given line between the start and the stop points (or their closest
// points on the line).
//
line_string lineSlice(point start, point stop, const line_string& line) {
auto getPoint = [](auto tuple) { return std::get<0>(tuple); };
auto getIndex = [](auto tuple) { return std::get<1>(tuple); };
auto getT = [](auto tuple) { return std::get<2>(tuple); };
auto p1 = pointOnLine(line, start);
auto p2 = pointOnLine(line, stop);
if (getIndex(p1) > getIndex(p2) || (getIndex(p1) == getIndex(p2) && getT(p1) > getT(p2))) {
auto tmp = p1;
p1 = p2;
p2 = tmp;
}
line_string slice = { getPoint(p1) };
auto l = getIndex(p1) + 1;
auto r = getIndex(p2);
if (line[l] != slice[0] && l <= r) {
slice.push_back(line[l]);
}
for (unsigned i = l + 1; i <= r; ++i) {
slice.push_back(line[i]);
}
if (line[r] != getPoint(p2)) {
slice.push_back(getPoint(p2));
}
return slice;
};
//
// Returns a part of the given line between the start and the stop points
// indicated by distance along the line.
//
line_string lineSliceAlong(double start, double stop, const line_string& line) {
double sum = 0.;
line_string slice;
for (unsigned i = 0; i < line.size() - 1; ++i) {
auto p0 = line[i];
auto p1 = line[i + 1];
auto d = distance(p0, p1);
sum += d;
if (sum > start && slice.size() == 0) {
slice.push_back(interpolate(p0, p1, (start - (sum - d)) / d));
}
if (sum >= stop) {
slice.push_back(interpolate(p0, p1, (stop - (sum - d)) / d));
return slice;
}
if (sum > start) {
slice.push_back(p1);
}
}
return slice;
};
//
// Given a point, returns a bounding box object ([w, s, e, n])
// created from the given point buffered by a given distance.
//
box bufferPoint(point p, double buffer) {
auto v = buffer / ky;
auto h = buffer / kx;
return box(
point(p.x - h, p.y - v),
point(p.x + h, p.y + v)
);
}
//
// Given a bounding box, returns the box buffered by a given distance.
//
box bufferBBox(box bbox, double buffer) {
auto v = buffer / ky;
auto h = buffer / kx;
return box(
point(bbox.min.x - h, bbox.min.y - v),
point(bbox.max.x + h, bbox.max.y + v)
);
}
//
// Returns true if the given point is inside in the given bounding box, otherwise false.
//
bool insideBBox(point p, box bbox) {
return p.x >= bbox.min.x &&
p.x <= bbox.max.x &&
p.y >= bbox.min.y &&
p.y <= bbox.max.y;
}
static point interpolate(point a, point b, double t) {
double dx = b.x - a.x;
double dy = b.y - a.y;
return point(a.x + dx * t, a.y + dy * t);
}
private:
double ky;
double kx;
};
} // namespace cheap_ruler
} // namespace mapbox

View File

@ -0,0 +1,200 @@
#include <mapbox/cheap_ruler.hpp>
#include <gtest/gtest.h>
#include "fixtures/lines.hpp"
#include "fixtures/turf.hpp"
namespace cr = mapbox::cheap_ruler;
class CheapRulerTest : public ::testing::Test {
protected:
cr::CheapRuler ruler = cr::CheapRuler(32.8351);
cr::CheapRuler milesRuler = cr::CheapRuler(32.8351, cr::CheapRuler::Miles);
};
void assertErr(double expected, double actual, double maxError) {
// Add a negligible fraction to make sure we
// don't divide by zero.
double error = std::abs((actual - expected) /
(expected == 0. ? expected + 0.000001 : expected));
if (error > maxError) {
FAIL() << "expected is " << expected << " but got " << actual;
}
}
TEST_F(CheapRulerTest, distance) {
for (unsigned i = 0; i < points.size() - 1; ++i) {
auto expected = turf_distance[i];
auto actual = ruler.distance(points[i], points[i + 1]);
assertErr(expected, actual, .003);
}
}
TEST_F(CheapRulerTest, distanceInMiles) {
auto d = ruler.distance({ 30.5, 32.8351 }, { 30.51, 32.8451 });
auto d2 = milesRuler.distance({ 30.5, 32.8351 }, { 30.51, 32.8451 });
assertErr(d / d2, 1.609344, 1e-12);
}
TEST_F(CheapRulerTest, bearing) {
for (unsigned i = 0; i < points.size() - 1; ++i) {
auto expected = turf_bearing[i];
auto actual = ruler.bearing(points[i], points[i + 1]);
assertErr(expected, actual, .005);
}
}
TEST_F(CheapRulerTest, destination) {
for (unsigned i = 0; i < points.size(); ++i) {
auto bearing = (i % 360) - 180.;
auto expected = turf_destination[i];
auto actual = ruler.destination(points[i], 1.0, bearing);
assertErr(expected.x, actual.x, 1e-6); // longitude
assertErr(expected.y, actual.y, 1e-6); // latitude
}
}
TEST_F(CheapRulerTest, lineDistance) {
for (unsigned i = 0; i < lines.size(); ++i) {
auto expected = turf_lineDistance[i];
auto actual = ruler.lineDistance(lines[i]);
assertErr(expected, actual, 0.003);
}
}
TEST_F(CheapRulerTest, area) {
for (unsigned i = 0, j = 0; i < lines.size(); ++i) {
if (lines[i].size() < 3) {
continue;
}
cr::linear_ring ring;
for (auto point : lines[i]) {
ring.push_back(point);
}
ring.push_back(lines[i][0]);
auto expected = turf_area[j++];
auto actual = ruler.area(cr::polygon{ ring });
assertErr(expected, actual, 0.003);
}
}
TEST_F(CheapRulerTest, along) {
for (unsigned i = 0; i < lines.size(); ++i) {
auto expected = turf_along[i];
auto actual = ruler.along(lines[i], turf_along_dist[i]);
assertErr(expected.x, actual.x, 1e-6); // along longitude
assertErr(expected.y, actual.y, 1e-6); // along latitude
}
}
TEST_F(CheapRulerTest, alongWithDist) {
ASSERT_EQ(ruler.along(lines[0], -5), lines[0][0]);
}
TEST_F(CheapRulerTest, alongWithDistGreaterThanLength) {
ASSERT_EQ(ruler.along(lines[0], 1000), lines[0][lines[0].size() - 1]);
}
TEST_F(CheapRulerTest, pointOnLine) {
// not Turf comparison because pointOnLine is bugged https://github.com/Turfjs/turf/issues/344
cr::line_string line = {{ -77.031669, 38.878605 }, { -77.029609, 38.881946 }};
auto result = ruler.pointOnLine(line, { -77.034076, 38.882017 });
ASSERT_EQ(std::get<0>(result), cr::point(-77.03052697027461, 38.880457194811896)); // point
ASSERT_EQ(std::get<1>(result), 0u); // index
ASSERT_EQ(std::get<2>(result), 0.5543833618360235); // t
ASSERT_EQ(std::get<2>(ruler.pointOnLine(line, { -80., 38. })), 0.) << "t is not less than 0";
ASSERT_EQ(std::get<2>(ruler.pointOnLine(line, { -75., 38. })), 1.) << "t is not bigger than 1";
}
TEST_F(CheapRulerTest, lineSlice) {
for (unsigned i = 0; i < lines.size(); ++i) {
auto line = lines[i];
auto dist = ruler.lineDistance(line);
auto start = ruler.along(line, dist * 0.3);
auto stop = ruler.along(line, dist * 0.7);
auto expected = turf_lineSlice[i];
auto actual = ruler.lineDistance(ruler.lineSlice(start, stop, line));
assertErr(expected, actual, 1e-5);
}
}
TEST_F(CheapRulerTest, lineSliceAlong) {
for (unsigned i = 0; i < lines.size(); ++i) {
if (i == 46) {
// skip due to Turf bug https://github.com/Turfjs/turf/issues/351
continue;
};
auto line = lines[i];
auto dist = ruler.lineDistance(line);
auto expected = turf_lineSlice[i];
auto actual = ruler.lineDistance(ruler.lineSliceAlong(dist * 0.3, dist * 0.7, line));
assertErr(expected, actual, 1e-5);
}
}
TEST_F(CheapRulerTest, lineSliceReverse) {
auto line = lines[0];
auto dist = ruler.lineDistance(line);
auto start = ruler.along(line, dist * 0.7);
auto stop = ruler.along(line, dist * 0.3);
auto actual = ruler.lineDistance(ruler.lineSlice(start, stop, line));
ASSERT_EQ(actual, 0.018676802802910702);
}
TEST_F(CheapRulerTest, bufferPoint) {
for (unsigned i = 0; i < points.size(); ++i) {
auto expected = turf_bufferPoint[i];
auto actual = milesRuler.bufferPoint(points[i], 0.1);
assertErr(expected.min.x, actual.min.x, 2e-7);
assertErr(expected.min.x, actual.min.x, 2e-7);
assertErr(expected.max.y, actual.max.y, 2e-7);
assertErr(expected.max.y, actual.max.y, 2e-7);
}
}
TEST_F(CheapRulerTest, bufferBBox) {
cr::box bbox({ 30, 38 }, { 40, 39 });
cr::box bbox2 = ruler.bufferBBox(bbox, 1);
ASSERT_EQ(bbox2, cr::box({ 29.989319515875376, 37.99098271225711 }, { 40.01068048412462, 39.00901728774289 }));
}
TEST_F(CheapRulerTest, insideBBox) {
cr::box bbox({ 30, 38 }, { 40, 39 });
ASSERT_TRUE(ruler.insideBBox({ 35, 38.5 }, bbox));
ASSERT_FALSE(ruler.insideBBox({ 45, 45 }, bbox));
}
TEST_F(CheapRulerTest, fromTile) {
auto ruler1 = cr::CheapRuler(50.5);
auto ruler2 = cr::CheapRuler::fromTile(11041, 15);
cr::point p1(30.5, 50.5);
cr::point p2(30.51, 50.51);
assertErr(ruler1.distance(p1, p2), ruler2.distance(p1, p2), 2e-5);
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
mason_packages
test

View File

@ -0,0 +1,3 @@
[submodule ".mason"]
path = .mason
url = https://github.com/mapbox/mason.git

View File

@ -0,0 +1,50 @@
language: generic
sudo: false
matrix:
include:
- os: linux
env: CXX=g++-4.9
addons:
apt:
sources: [ 'ubuntu-toolchain-r-test' ]
packages: [ 'g++-4.9' ]
- os: linux
env: CXX=g++-5
addons:
apt:
sources: [ 'ubuntu-toolchain-r-test' ]
packages: [ 'g++-5' ]
- os: linux
env: CXX=g++-6
addons:
apt:
sources: [ 'ubuntu-toolchain-r-test' ]
packages: [ 'g++-6' ]
- os: linux
env: CXX=clang++-3.8
addons:
apt:
sources: [ 'ubuntu-toolchain-r-test' ]
packages: [ 'libstdc++-4.9-dev' ]
before_script:
- git submodule update --init
- .mason/mason install clang++ 3.8.1
- export PATH=$(.mason/mason prefix clang++ 3.8.1)/bin:$PATH
- os: linux
env: CXX=clang++-3.9
addons:
apt:
sources: [ 'ubuntu-toolchain-r-test' ]
packages: [ 'libstdc++-4.9-dev' ]
before_script:
- git submodule update --init
- .mason/mason install clang++ 3.9.1
- export PATH=$(.mason/mason prefix clang++ 3.9.1)/bin:$PATH
- os: osx
osx_image: xcode7.3
cache: apt
script:
- make test

13
third_party/geometry.hpp-0.9.2/LICENSE vendored Normal file
View File

@ -0,0 +1,13 @@
Copyright (c) 2016, Mapbox
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

19
third_party/geometry.hpp-0.9.2/Makefile vendored Normal file
View File

@ -0,0 +1,19 @@
CXXFLAGS += -I include -std=c++14 -DDEBUG -O0 -Wall -Wextra -Werror
MASON ?= .mason/mason
VARIANT = 1.1.4
default: test
$(MASON):
git submodule update --init
mason_packages/headers/variant/$(VARIANT):
$(MASON) install variant $(VARIANT)
test: tests/* include/mapbox/geometry/* mason_packages/headers/variant/$(VARIANT) Makefile
$(CXX) tests/*.cpp $(CXXFLAGS) `$(MASON) cflags variant $(VARIANT)` -o test
./test
clean:
rm -f test

View File

@ -0,0 +1,83 @@
# geometry.hpp
Provides header-only, generic C++ interfaces for geometry types, geometry collections, and features.
- `mapbox::geometry::point`
- `mapbox::geometry::multi_point`
- `mapbox::geometry::line_string`
- `mapbox::geometry::multi_line_string`
- `mapbox::geometry::polygon`
- `mapbox::geometry::multi_polygon`
- `mapbox::geometry::geometry_collection`
- `mapbox::geometry::feature` (experimental)
### Design
These types are designed to be easy to parse and serialize to [GeoJSON](http://geojson.org/).
They should also be a robust and high performance container for data processing and conversion.
### Goals
- Header-only
- Fast compile
- c++11/c++14 compatibility
- No external dependencies for usage of core types (point, line_string, etc)
- Minimal dependencies for usage of enclosing `geometry` type (`mapbox::variant`)
- Easily [adaptable to `boost::geometry`](http://www.boost.org/doc/libs/1_56_0/libs/geometry/doc/html/geometry/examples/example__adapting_a_legacy_geometry_object_model.html)
### Usage
Using a single type directly (requires no external dependencies):
```cpp
#include <mapbox/geometry/point.hpp>
#include <iostream>
using mapbox::geometry::point;
int main() {
point<double> pt(1.0,0.0);
std::clog << "x: " << pt.x << " y: " << pt.y << "\n";
}
```
Creating a geometry collection (depends on https://github.com/mapbox/variant):
```cpp
#include <mapbox/geometry/geometry.hpp>
#include <mapbox/variant.hpp>
#include <iostream>
using mapbox::geometry::geometry_collection;
using mapbox::geometry::geometry;
using mapbox::geometry::point;
using point_type = point<double>;
struct printer
{
printer() {}
void operator()(point_type const& pt) const
{
std::clog << "x: " << pt.x << " y: " << pt.y << "\n";
}
template <typename T>
void operator()(T const& g) const
{
std::clog << "encountered non-point geometry\n";
}
};
int main() {
geometry_collection<double> gc;
gc.emplace_back(point_type(1.0,0.0));
geometry<double> const& geom = gc.at(0);
printer visitor;
mapbox::util::apply_visitor(visitor,geom);
}
```

View File

@ -0,0 +1,13 @@
#pragma once
#include <mapbox/geometry/point.hpp>
#include <mapbox/geometry/line_string.hpp>
#include <mapbox/geometry/polygon.hpp>
#include <mapbox/geometry/multi_point.hpp>
#include <mapbox/geometry/multi_line_string.hpp>
#include <mapbox/geometry/multi_polygon.hpp>
#include <mapbox/geometry/geometry.hpp>
#include <mapbox/geometry/feature.hpp>
#include <mapbox/geometry/point_arithmetic.hpp>
#include <mapbox/geometry/for_each_point.hpp>
#include <mapbox/geometry/envelope.hpp>

View File

@ -0,0 +1,34 @@
#pragma once
#include <mapbox/geometry/point.hpp>
namespace mapbox {
namespace geometry {
template <typename T>
struct box
{
using point_type = point<T>;
constexpr box(point_type const& min_, point_type const& max_)
: min(min_), max(max_)
{}
point_type min;
point_type max;
};
template <typename T>
constexpr bool operator==(box<T> const& lhs, box<T> const& rhs)
{
return lhs.min == rhs.min && lhs.max == rhs.max;
}
template <typename T>
constexpr bool operator!=(box<T> const& lhs, box<T> const& rhs)
{
return lhs.min != rhs.min || lhs.max != rhs.max;
}
} // namespace geometry
} // namespace mapbox

View File

@ -0,0 +1,33 @@
#pragma once
#include <mapbox/geometry/box.hpp>
#include <mapbox/geometry/for_each_point.hpp>
#include <limits>
namespace mapbox {
namespace geometry {
template <typename G, typename T = typename G::coordinate_type>
box<T> envelope(G const& geometry)
{
using limits = std::numeric_limits<T>;
T min_t = limits::has_infinity ? -limits::infinity() : limits::min();
T max_t = limits::has_infinity ? limits::infinity() : limits::max();
point<T> min(max_t, max_t);
point<T> max(min_t, min_t);
for_each_point(geometry, [&] (point<T> const& point) {
if (min.x > point.x) min.x = point.x;
if (min.y > point.y) min.y = point.y;
if (max.x < point.x) max.x = point.x;
if (max.y < point.y) max.y = point.y;
});
return box<T>(min, max);
}
} // namespace geometry
} // namespace mapbox

View File

@ -0,0 +1,91 @@
#pragma once
#include <mapbox/geometry/geometry.hpp>
#include <mapbox/variant.hpp>
#include <cstdint>
#include <string>
#include <vector>
#include <unordered_map>
#include <experimental/optional>
namespace mapbox {
namespace geometry {
struct value;
struct null_value_t
{
constexpr null_value_t() {}
constexpr null_value_t(std::nullptr_t) {}
};
constexpr bool operator==(const null_value_t&, const null_value_t&) { return true; }
constexpr bool operator!=(const null_value_t&, const null_value_t&) { return false; }
constexpr bool operator<(const null_value_t&, const null_value_t&) { return false; }
constexpr null_value_t null_value = null_value_t();
// Multiple numeric types (uint64_t, int64_t, double) are present in order to support
// the widest possible range of JSON numbers, which do not have a maximum range.
// Implementations that produce `value`s should use that order for type preference,
// using uint64_t for positive integers, int64_t for negative integers, and double
// for non-integers and integers outside the range of 64 bits.
using value_base = mapbox::util::variant<null_value_t, bool, uint64_t, int64_t, double, std::string,
mapbox::util::recursive_wrapper<std::vector<value>>,
mapbox::util::recursive_wrapper<std::unordered_map<std::string, value>>>;
struct value : value_base
{
using value_base::value_base;
};
using property_map = std::unordered_map<std::string, value>;
// The same considerations and requirement for numeric types apply as for `value_base`.
using identifier = mapbox::util::variant<uint64_t, int64_t, double, std::string>;
template <class T>
struct feature
{
using coordinate_type = T;
using geometry_type = mapbox::geometry::geometry<T>; // Fully qualified to avoid GCC -fpermissive error.
geometry_type geometry;
property_map properties {};
std::experimental::optional<identifier> id {};
// GCC 4.9 does not support C++14 aggregates with non-static data member
// initializers.
feature(geometry_type geometry_,
property_map properties_ = property_map {},
std::experimental::optional<identifier> id_ = std::experimental::optional<identifier> {})
: geometry(std::move(geometry_)),
properties(std::move(properties_)),
id(std::move(id_)) {}
};
template <class T>
constexpr bool operator==(feature<T> const& lhs, feature<T> const& rhs)
{
return lhs.id == rhs.id && lhs.geometry == rhs.geometry && lhs.properties == rhs.properties;
}
template <class T>
constexpr bool operator!=(feature<T> const& lhs, feature<T> const& rhs)
{
return !(lhs == rhs);
}
template <class T, template <typename...> class Cont = std::vector>
struct feature_collection : Cont<feature<T>>
{
using coordinate_type = T;
using feature_type = feature<T>;
using container_type = Cont<feature_type>;
using container_type::container_type;
};
} // namespace geometry
} // namespace mapbox

View File

@ -0,0 +1,45 @@
#pragma once
#include <mapbox/geometry/geometry.hpp>
namespace mapbox {
namespace geometry {
template <typename Point, typename F>
auto for_each_point(Point&& point, F&& f)
-> decltype(point.x, point.y, void())
{
f(std::forward<Point>(point));
}
template <typename Container, typename F>
auto for_each_point(Container&& container, F&& f)
-> decltype(container.begin(), container.end(), void());
template <typename...Types, typename F>
void for_each_point(mapbox::util::variant<Types...> const& geom, F&& f)
{
mapbox::util::variant<Types...>::visit(geom, [&] (auto const& g) {
for_each_point(g, f);
});
}
template <typename...Types, typename F>
void for_each_point(mapbox::util::variant<Types...> & geom, F&& f)
{
mapbox::util::variant<Types...>::visit(geom, [&] (auto & g) {
for_each_point(g, f);
});
}
template <typename Container, typename F>
auto for_each_point(Container&& container, F&& f)
-> decltype(container.begin(), container.end(), void())
{
for (auto& e: container) {
for_each_point(e, f);
}
}
} // namespace geometry
} // namespace mapbox

View File

@ -0,0 +1,58 @@
#pragma once
#include <mapbox/geometry/point.hpp>
#include <mapbox/geometry/line_string.hpp>
#include <mapbox/geometry/polygon.hpp>
#include <mapbox/geometry/multi_point.hpp>
#include <mapbox/geometry/multi_line_string.hpp>
#include <mapbox/geometry/multi_polygon.hpp>
#include <mapbox/variant.hpp>
// stl
#include <vector>
namespace mapbox {
namespace geometry {
template <typename T, template <typename...> class Cont = std::vector>
struct geometry_collection;
template <typename T>
using geometry_base = mapbox::util::variant<point<T>,
line_string<T>,
polygon<T>,
multi_point<T>,
multi_line_string<T>,
multi_polygon<T>,
geometry_collection<T>>;
template <typename T>
struct geometry : geometry_base<T>
{
using coordinate_type = T;
using geometry_base<T>::geometry_base;
/*
* The default constructor would create a point geometry with default-constructed coordinates;
* i.e. (0, 0). Since this is not particularly useful, and could hide bugs, it is disabled.
*/
geometry() = delete;
};
template <typename T, template <typename...> class Cont>
struct geometry_collection : Cont<geometry<T>>
{
using coordinate_type = T;
using geometry_type = geometry<T>;
using container_type = Cont<geometry_type>;
geometry_collection() = default;
geometry_collection(geometry_collection const&) = default;
geometry_collection(geometry_collection &&) = default;
geometry_collection(std::initializer_list<geometry_type> && args)
: container_type(std::forward<std::initializer_list<geometry_type>>(args)) {};
};
} // namespace geometry
} // namespace mapbox

View File

@ -0,0 +1,21 @@
#pragma once
// mapbox
#include <mapbox/geometry/point.hpp>
// stl
#include <vector>
namespace mapbox {
namespace geometry {
template <typename T, template <typename...> class Cont = std::vector>
struct line_string : Cont<point<T> >
{
using coordinate_type = T;
using point_type = point<T>;
using container_type = Cont<point_type>;
using container_type::container_type;
};
} // namespace geometry
} // namespace mapbox

View File

@ -0,0 +1,21 @@
#pragma once
// mapbox
#include <mapbox/geometry/line_string.hpp>
// stl
#include <vector>
namespace mapbox {
namespace geometry {
template <typename T, template <typename...> class Cont = std::vector>
struct multi_line_string : Cont<line_string<T>>
{
using coordinate_type = T;
using line_string_type = line_string<T>;
using container_type = Cont<line_string_type>;
using container_type::container_type;
};
} // namespace geometry
} // namespace mapbox

View File

@ -0,0 +1,21 @@
#pragma once
// mapbox
#include <mapbox/geometry/point.hpp>
// stl
#include <vector>
namespace mapbox {
namespace geometry {
template <typename T, template <typename...> class Cont = std::vector>
struct multi_point : Cont<point<T>>
{
using coordinate_type = T;
using point_type = point<T>;
using container_type = Cont<point_type>;
using container_type::container_type;
};
} // namespace geometry
} // namespace mapbox

View File

@ -0,0 +1,21 @@
#pragma once
// mapbox
#include <mapbox/geometry/polygon.hpp>
// stl
#include <vector>
namespace mapbox {
namespace geometry {
template <typename T, template <typename...> class Cont = std::vector>
struct multi_polygon : Cont<polygon<T>>
{
using coordinate_type = T;
using polygon_type = polygon<T>;
using container_type = Cont<polygon_type>;
using container_type::container_type;
};
} // namespace geometry
} // namespace mapbox

View File

@ -0,0 +1,35 @@
#pragma once
namespace mapbox {
namespace geometry {
template <typename T>
struct point
{
using coordinate_type = T;
constexpr point()
: x(), y()
{}
constexpr point(T x_, T y_)
: x(x_), y(y_)
{}
T x;
T y;
};
template <typename T>
constexpr bool operator==(point<T> const& lhs, point<T> const& rhs)
{
return lhs.x == rhs.x && lhs.y == rhs.y;
}
template <typename T>
constexpr bool operator!=(point<T> const& lhs, point<T> const& rhs)
{
return !(lhs == rhs);
}
} // namespace geometry
} // namespace mapbox

View File

@ -0,0 +1,119 @@
#pragma once
namespace mapbox {
namespace geometry {
template <typename T>
point<T> operator+(point<T> const& lhs, point<T> const& rhs)
{
return point<T>(lhs.x + rhs.x, lhs.y + rhs.y);
}
template <typename T>
point<T> operator+(point<T> const& lhs, T const& rhs)
{
return point<T>(lhs.x + rhs, lhs.y + rhs);
}
template <typename T>
point<T> operator-(point<T> const& lhs, point<T> const& rhs)
{
return point<T>(lhs.x - rhs.x, lhs.y - rhs.y);
}
template <typename T>
point<T> operator-(point<T> const& lhs, T const& rhs)
{
return point<T>(lhs.x - rhs, lhs.y - rhs);
}
template <typename T>
point<T> operator*(point<T> const& lhs, point<T> const& rhs)
{
return point<T>(lhs.x * rhs.x, lhs.y * rhs.y);
}
template <typename T>
point<T> operator*(point<T> const& lhs, T const& rhs)
{
return point<T>(lhs.x * rhs, lhs.y * rhs);
}
template <typename T>
point<T> operator/(point<T> const& lhs, point<T> const& rhs)
{
return point<T>(lhs.x / rhs.x, lhs.y / rhs.y);
}
template <typename T>
point<T> operator/(point<T> const& lhs, T const& rhs)
{
return point<T>(lhs.x / rhs, lhs.y / rhs);
}
template <typename T>
point<T>& operator+=(point<T>& lhs, point<T> const& rhs)
{
lhs.x += rhs.x;
lhs.y += rhs.y;
return lhs;
}
template <typename T>
point<T>& operator+=(point<T>& lhs, T const& rhs)
{
lhs.x += rhs;
lhs.y += rhs;
return lhs;
}
template <typename T>
point<T>& operator-=(point<T>& lhs, point<T> const& rhs)
{
lhs.x -= rhs.x;
lhs.y -= rhs.y;
return lhs;
}
template <typename T>
point<T>& operator-=(point<T>& lhs, T const& rhs)
{
lhs.x -= rhs;
lhs.y -= rhs;
return lhs;
}
template <typename T>
point<T>& operator*=(point<T>& lhs, point<T> const& rhs)
{
lhs.x *= rhs.x;
lhs.y *= rhs.y;
return lhs;
}
template <typename T>
point<T>& operator*=(point<T>& lhs, T const& rhs)
{
lhs.x *= rhs;
lhs.y *= rhs;
return lhs;
}
template <typename T>
point<T>& operator/=(point<T>& lhs, point<T> const& rhs)
{
lhs.x /= rhs.x;
lhs.y /= rhs.y;
return lhs;
}
template <typename T>
point<T>& operator/=(point<T>& lhs, T const& rhs)
{
lhs.x /= rhs;
lhs.y /= rhs;
return lhs;
}
} // namespace geometry
} // namespace mapbox

View File

@ -0,0 +1,31 @@
#pragma once
// mapbox
#include <mapbox/geometry/point.hpp>
// stl
#include <vector>
namespace mapbox {
namespace geometry {
template <typename T, template <typename...> class Cont = std::vector>
struct linear_ring : Cont<point<T>>
{
using coordinate_type = T;
using point_type = point<T>;
using container_type = Cont<point_type>;
using container_type::container_type;
};
template <typename T, template <typename...> class Cont = std::vector>
struct polygon : Cont<linear_ring<T>>
{
using coordinate_type = T;
using linear_ring_type = linear_ring<T>;
using container_type = Cont<linear_ring_type>;
using container_type::container_type;
};
} // namespace geometry
} // namespace mapbox

View File

@ -0,0 +1,5 @@
#include <mapbox/geometry.hpp>
void test() {
mapbox::geometry::geometry_collection<double> gc;
}

View File

@ -0,0 +1,260 @@
#include <mapbox/geometry.hpp>
#include <cassert>
using namespace mapbox::geometry;
static void testPoint() {
point<double> p1;
assert(int(p1.x) == 0);
assert(int(p1.y) == 0);
point<uint32_t> p2(2, 3);
point<uint32_t> p3(4, 6);
assert((p2 + p3) == point<uint32_t>(6, 9));
assert((p2 + 1u) == point<uint32_t>(3, 4));
assert((p3 - p2) == point<uint32_t>(2, 3));
assert((p3 - 1u) == point<uint32_t>(3, 5));
assert((p3 * p2) == point<uint32_t>(8, 18));
assert((p2 * 2u) == point<uint32_t>(4, 6));
assert((p3 / p2) == point<uint32_t>(2, 2));
assert((p3 / 2u) == point<uint32_t>(2, 3));
{ point<uint32_t> p(2, 3); assert((p += p3) == point<uint32_t>(6, 9)); }
{ point<uint32_t> p(2, 3); assert((p += 1u) == point<uint32_t>(3, 4)); }
{ point<uint32_t> p(4, 6); assert((p -= p2) == point<uint32_t>(2, 3)); }
{ point<uint32_t> p(4, 6); assert((p -= 1u) == point<uint32_t>(3, 5)); }
{ point<uint32_t> p(4, 6); assert((p *= p2) == point<uint32_t>(8, 18)); }
{ point<uint32_t> p(2, 3); assert((p *= 2u) == point<uint32_t>(4, 6)); }
{ point<uint32_t> p(4, 6); assert((p /= p2) == point<uint32_t>(2, 2)); }
{ point<uint32_t> p(4, 6); assert((p /= 2u) == point<uint32_t>(2, 3)); }
}
static void testMultiPoint() {
multi_point<double> mp1;
assert(mp1.size() == 0);
multi_point<double> mp2(10);
assert(mp2.size() == 10);
assert(mp1 == mp1);
assert(!(mp1 != mp1));
assert(mp1 != mp2);
}
static void testLineString() {
line_string<double> ls1;
assert(ls1.size() == 0);
line_string<double> ls2(10);
assert(ls2.size() == 10);
assert(ls1 == ls1);
assert(!(ls1 != ls1));
assert(ls1 != ls2);
}
static void testMultiLineString() {
multi_line_string<double> mls1;
assert(mls1.size() == 0);
multi_line_string<double> mls2(10);
assert(mls2.size() == 10);
assert(mls1 == mls1);
assert(!(mls1 != mls1));
assert(mls1 != mls2);
}
static void testPolygon() {
polygon<double> pg1;
assert(pg1.size() == 0);
polygon<double> pg2({{{0, 1}}});
assert(pg2.size() == 1);
assert(pg2[0].size() == 1);
assert(pg2[0][0] == point<double>(0, 1));
assert(pg1 == pg1);
assert(!(pg1 != pg1));
assert(pg1 != pg2);
}
static void testMultiPolygon() {
multi_polygon<double> mpg1;
assert(mpg1.size() == 0);
multi_polygon<double> mpg2(10);
assert(mpg2.size() == 10);
assert(mpg1 == mpg1);
assert(!(mpg1 != mpg1));
assert(mpg1 != mpg2);
}
static void testGeometry() {
geometry<double> pg { point<double>() };
assert(pg.is<point<double>>());
geometry<double> lsg { line_string<double>() };
assert(lsg.is<line_string<double>>());
geometry<double> pgg { polygon<double>() };
assert(pgg.is<polygon<double>>());
geometry<double> mpg { multi_point<double>() };
assert(mpg.is<multi_point<double>>());
geometry<double> mlsg { multi_line_string<double>() };
assert(mlsg.is<multi_line_string<double>>());
geometry<double> mpgg { multi_polygon<double>() };
assert(mpgg.is<multi_polygon<double>>());
geometry<double> gcg { geometry_collection<double>() };
assert(gcg.is<geometry_collection<double>>());
assert(pg == pg);
assert(!(pg != pg));
assert(pg != lsg);
}
static void testGeometryCollection() {
geometry_collection<double> gc1;
assert(gc1.size() == 0);
assert(gc1 == gc1);
assert(!(gc1 != gc1));
}
static void testFeature() {
feature<double> pf { point<double>() };
assert(pf.geometry.is<point<double>>());
assert(pf.properties.size() == 0);
auto &p = pf.properties;
p["bool"] = true;
p["string"] = std::string("foo");
p["double"] = 2.5;
p["uint"] = uint64_t(10);
p["int"] = int64_t(-10);
p["null"] = null_value;
assert(p["bool"].is<bool>());
assert(p["bool"] == true);
assert(p["string"].is<std::string>());
assert(p["string"] == std::string("foo"));
assert(p["double"].is<double>());
assert(p["double"] == 2.5);
assert(p["uint"].is<uint64_t>());
assert(p["uint"] == uint64_t(10));
assert(p["int"].is<int64_t>());
assert(p["int"] == int64_t(-10));
assert(p["null"].is<null_value_t>());
assert(p["null"] == null_value);
p["null"] = null_value_t{};
assert(p["null"].is<null_value_t>());
assert(p["null"] == null_value);
assert(p == p);
assert(!(p != p));
assert(pf == pf);
assert(!(pf != pf));
assert(p.size() == 6);
feature<double> id1 { point<double>() };
id1.id = { uint64_t(1) };
feature<double> id2 { point<double>() };
id1.id = { uint64_t(2) };
assert(id1 == id1);
assert(id1 != id2);
}
static void testFeatureCollection() {
feature_collection<double> fc1;
assert(fc1.size() == 0);
assert(fc1 == fc1);
assert(!(fc1 != fc1));
}
struct point_counter {
std::size_t count = 0;
template <class Point>
void operator()(Point const&) { count++; };
};
static void testForEachPoint() {
auto count_points = [] (auto const& g) {
point_counter counter;
for_each_point(g, counter);
return counter.count;
};
assert(count_points(point<double>()) == 1);
assert(count_points(line_string<double>({{0, 1}, {2, 3}})) == 2);
assert(count_points(geometry<double>(polygon<double>({{{0, 1}, {2, 3}}}))) == 2);
auto point_negator = [] (point<double>& p) { p *= -1.0; };
point<double> p(1, 2);
for_each_point(p, point_negator);
assert(p == point<double>(-1, -2));
line_string<double> ls({{0, 1}, {2, 3}});
for_each_point(ls, point_negator);
assert(ls == line_string<double>({{0, -1}, {-2, -3}}));
geometry<double> g(polygon<double>({{{0, 1}, {2, 3}}}));
for_each_point(g, point_negator);
assert(g == geometry<double>(polygon<double>({{{0, -1}, {-2, -3}}})));
// Custom geometry type
using my_geometry = mapbox::util::variant<point<double>>;
assert(count_points(my_geometry(point<double>())) == 1);
// Custom point type
struct my_point {
int16_t x;
int16_t y;
};
assert(count_points(std::vector<my_point>({my_point{0, 1}})) == 1);
assert(count_points(mapbox::util::variant<my_point>(my_point{0, 1})) == 1);
}
static void testEnvelope() {
assert(envelope(point<double>(0, 0)) == box<double>({0, 0}, {0, 0}));
assert(envelope(line_string<double>({{0, 1}, {2, 3}})) == box<double>({0, 1}, {2, 3}));
assert(envelope(polygon<double>({{{0, 1}, {2, 3}}})) == box<double>({0, 1}, {2, 3}));
assert(envelope(multi_point<double>({{0, 0}})) == box<double>({0, 0}, {0, 0}));
assert(envelope(multi_line_string<double>({{{0, 1}, {2, 3}}})) == box<double>({0, 1}, {2, 3}));
assert(envelope(multi_polygon<double>({{{{0, 1}, {2, 3}}}})) == box<double>({0, 1}, {2, 3}));
assert(envelope(geometry<int>(point<int>(0, 0))) == box<int>({0, 0}, {0, 0}));
assert(envelope(geometry_collection<int>({point<int>(0, 0)})) == box<int>({0, 0}, {0, 0}));
}
int main() {
testPoint();
testMultiPoint();
testLineString();
testMultiLineString();
testPolygon();
testMultiPolygon();
testGeometry();
testGeometryCollection();
testFeature();
testFeatureCollection();
testForEachPoint();
testEnvelope();
return 0;
}