Fix distance calculation consistency. (#6315)
Consolidate great circle distance calculations to use cheap ruler library.
This commit is contained in:
committed by
GitHub
parent
8f0cd5cf7b
commit
aadc088084
@@ -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
|
||||
@@ -0,0 +1,2 @@
|
||||
build/
|
||||
mason_packages/
|
||||
+25
@@ -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
|
||||
@@ -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)
|
||||
+15
@@ -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.
|
||||
+199
@@ -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.
|
||||
|
||||
[](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
|
||||
```
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -0,0 +1,390 @@
|
||||
#pragma once
|
||||
|
||||
#include <mapbox/geometry/box.hpp>
|
||||
#include <mapbox/geometry/multi_line_string.hpp>
|
||||
#include <mapbox/geometry/polygon.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#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 {
|
||||
|
||||
// Values that define WGS84 ellipsoid model of the Earth
|
||||
static constexpr double RE = 6378.137; // equatorial radius
|
||||
static constexpr double FE = 1.0 / 298.257223563; // flattening
|
||||
|
||||
static constexpr double E2 = FE * (2 - FE);
|
||||
static constexpr double RAD = M_PI / 180.0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Curvature formulas from https://en.wikipedia.org/wiki/Earth_radius#Meridional
|
||||
double mul = RAD * RE * m;
|
||||
double coslat = std::cos(latitude * RAD);
|
||||
double w2 = 1 / (1 - E2 * (1 - coslat * coslat));
|
||||
double w = std::sqrt(w2);
|
||||
|
||||
// multipliers for converting longitude and latitude degrees into distance
|
||||
kx = mul * w * coslat; // based on normal radius of curvature
|
||||
ky = mul * w * w2 * (1 - E2); // based on meridonal radius of curvature
|
||||
}
|
||||
|
||||
static CheapRuler fromTile(uint32_t y, uint32_t z) {
|
||||
assert(z < 32);
|
||||
double n = M_PI * (1. - 2. * (y + 0.5) / double(uint32_t(1) << z));
|
||||
double latitude = std::atan(std::sinh(n)) / RAD;
|
||||
|
||||
return CheapRuler(latitude);
|
||||
}
|
||||
|
||||
double squareDistance(point a, point b) const {
|
||||
auto dx = longDiff(a.x, b.x) * kx;
|
||||
auto dy = (a.y - b.y) * ky;
|
||||
return dx * dx + dy * dy;
|
||||
}
|
||||
|
||||
//
|
||||
// Given two points of the form [x = longitude, y = latitude], returns the distance.
|
||||
//
|
||||
double distance(point a, point b) const {
|
||||
return std::sqrt(squareDistance(a, b));
|
||||
}
|
||||
|
||||
//
|
||||
// Returns the bearing between two points in angles.
|
||||
//
|
||||
double bearing(point a, point b) const {
|
||||
auto dx = longDiff(b.x, a.x) * kx;
|
||||
auto dy = (b.y - a.y) * ky;
|
||||
|
||||
return std::atan2(dx, dy) / RAD;
|
||||
}
|
||||
|
||||
//
|
||||
// Returns a new point given distance and bearing from the starting point.
|
||||
//
|
||||
point destination(point origin, double dist, double bearing_) const {
|
||||
auto a = bearing_ * RAD;
|
||||
|
||||
return offset(origin, std::sin(a) * dist, std::cos(a) * dist);
|
||||
}
|
||||
|
||||
//
|
||||
// Returns a new point given easting and northing offsets from the starting point.
|
||||
//
|
||||
point offset(point origin, double dx, double dy) const {
|
||||
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 (size_t i = 1; i < points.size(); ++i) {
|
||||
total += distance(points[i - 1], points[i]);
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
//
|
||||
// Given a polygon (an array of rings, where each ring is an array of points),
|
||||
// returns the area.
|
||||
//
|
||||
double area(polygon poly) const {
|
||||
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 += longDiff(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) const {
|
||||
double sum = 0.;
|
||||
|
||||
if (line.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
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 the distance from a point `p` to a line segment `a` to `b`.
|
||||
//
|
||||
double pointToSegmentDistance(const point& p, const point& a, const point& b) const {
|
||||
auto t = 0.0;
|
||||
auto x = a.x;
|
||||
auto y = a.y;
|
||||
auto dx = longDiff(b.x, x) * kx;
|
||||
auto dy = (b.y - y) * ky;
|
||||
|
||||
if (dx != 0.0 || dy != 0.0) {
|
||||
t = (longDiff(p.x, x) * kx * dx + (p.y - y) * ky * dy) / (dx * dx + dy * dy);
|
||||
if (t > 1.0) {
|
||||
x = b.x;
|
||||
y = b.y;
|
||||
} else if (t > 0.0) {
|
||||
x += (dx / kx) * t;
|
||||
y += (dy / ky) * t;
|
||||
}
|
||||
}
|
||||
return distance(p, { x, y });
|
||||
}
|
||||
|
||||
//
|
||||
// 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) const {
|
||||
double minDist = std::numeric_limits<double>::infinity();
|
||||
double minX = 0., minY = 0., minI = 0., minT = 0.;
|
||||
|
||||
if (line.empty()) {
|
||||
return std::make_tuple(point(), 0., 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 = longDiff(line[i + 1].x, x) * kx;
|
||||
auto dy = (line[i + 1].y - y) * ky;
|
||||
|
||||
if (dx != 0. || dy != 0.) {
|
||||
t = (longDiff(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;
|
||||
}
|
||||
}
|
||||
|
||||
auto sqDist = squareDistance(p, {x, y});
|
||||
|
||||
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) const {
|
||||
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) const {
|
||||
double sum = 0.;
|
||||
line_string slice;
|
||||
|
||||
for (size_t i = 1; i < line.size(); ++i) {
|
||||
auto p0 = line[i - 1];
|
||||
auto p1 = line[i];
|
||||
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) const {
|
||||
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) const {
|
||||
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.
|
||||
//
|
||||
static bool insideBBox(point p, box bbox) {
|
||||
return p.y >= bbox.min.y &&
|
||||
p.y <= bbox.max.y &&
|
||||
longDiff(p.x, bbox.min.x) >= 0 &&
|
||||
longDiff(p.x, bbox.max.x) <= 0;
|
||||
}
|
||||
|
||||
static point interpolate(point a, point b, double t) {
|
||||
double dx = longDiff(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;
|
||||
static double longDiff(double a, double b) {
|
||||
return std::remainder(a - b, 360);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cheap_ruler
|
||||
} // namespace mapbox
|
||||
@@ -0,0 +1,276 @@
|
||||
#include <mapbox/cheap_ruler.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
#include <random>
|
||||
|
||||
#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) {
|
||||
{
|
||||
cr::line_string emptyLine {};
|
||||
auto expected = 0.0;
|
||||
auto actual = ruler.lineDistance(emptyLine);
|
||||
assertErr(expected, actual, 0.0);
|
||||
}
|
||||
|
||||
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) {
|
||||
{
|
||||
cr::point emptyPoint {};
|
||||
cr::line_string emptyLine {};
|
||||
auto expected = emptyPoint;
|
||||
auto actual = ruler.along(emptyLine, 0.0);
|
||||
|
||||
assertErr(expected.x, actual.x, 0.0);
|
||||
assertErr(expected.y, actual.y, 0.0);
|
||||
}
|
||||
|
||||
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 });
|
||||
|
||||
assertErr(std::get<0>(result).x, -77.03052689033436, 1e-6);
|
||||
assertErr(std::get<0>(result).y, 38.880457324462576, 1e-6);
|
||||
ASSERT_EQ(std::get<1>(result), 0u); // index
|
||||
assertErr(std::get<2>(result), 0.5544221677861756, 1e-6); // 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, pointToSegmentDistance) {
|
||||
cr::point p{ -77.034076, 38.882017 };
|
||||
cr::point p0{ -77.031669, 38.878605 };
|
||||
cr::point p1{ -77.029609, 38.881946 };
|
||||
const auto distance = ruler.pointToSegmentDistance(p, p0, p1);
|
||||
assertErr(0.37461484020420416, distance, 1e-6);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
/// @todo Should update turf_lineSlice and revert maxError back.
|
||||
assertErr(expected, actual, 1e-4);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CheapRulerTest, lineSliceAlong) {
|
||||
{
|
||||
cr::line_string emptyLine {};
|
||||
auto expected = ruler.lineDistance(emptyLine);
|
||||
auto actual = ruler.lineDistance(ruler.lineSliceAlong(0.0, 0.0, emptyLine));
|
||||
assertErr(expected, actual, 0.0);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
/// @todo Should update turf_lineSlice and revert maxError back.
|
||||
assertErr(expected, actual, 1e-4);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
assertErr(0.018676476689649835, actual, 1e-6);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
assertErr(bbox2.min.x, 29.989319515875376, 1e-6);
|
||||
assertErr(bbox2.min.y, 37.99098271225711, 1e-6);
|
||||
assertErr(bbox2.max.x, 40.01068048412462, 1e-6);
|
||||
assertErr(bbox2.max.y, 39.00901728774289, 1e-6);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
TEST_F(CheapRulerTest, longitudeWrap) {
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
std::bernoulli_distribution d(0.5); // true with prob 0.5
|
||||
|
||||
auto r = cr::CheapRuler(50.5);
|
||||
cr::polygon poly(1);
|
||||
auto& ring = poly[0];
|
||||
cr::line_string line;
|
||||
cr::point origin(0, 50.5); // Greenwich
|
||||
auto rad = 1000.0;
|
||||
// construct a regular dodecagon
|
||||
for (int i = -180; i <= 180; i += 30) {
|
||||
auto p = r.destination(origin, rad, i);
|
||||
// shift randomly east/west to the international date line
|
||||
p.x += d(gen) ? 180 : -180;
|
||||
ring.push_back(p);
|
||||
line.push_back(p);
|
||||
}
|
||||
auto p = r.lineDistance(line);
|
||||
auto a = r.area(poly);
|
||||
// cheap_ruler does planar calculations, so the perimeter and area of a
|
||||
// planar regular dodecagon with circumradius rad are used in these checks.
|
||||
// For the record, the results for rad = 1000 km are:
|
||||
// perimeter area
|
||||
// planar 6211.657082 3000000
|
||||
// WGS84 6187.959236 2996317.6328
|
||||
// error 0.38% 0.12%
|
||||
assertErr(12 * rad / sqrt(2 + sqrt(3.0)), p, 1e-12);
|
||||
assertErr(3 * rad * rad, a, 1e-12);
|
||||
for (int j = 1; j < (int)line.size(); ++j) {
|
||||
auto azi = r.bearing(line[j-1], line[j]);
|
||||
// offset expect and actual by 1 to make err criterion absolute
|
||||
assertErr(1, std::remainder(270 - 15 + 30*j - azi, 360) + 1, 1e-12);
|
||||
}
|
||||
}
|
||||
|
||||
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
Reference in New Issue
Block a user