diff --git a/.gitignore b/.gitignore index 0f855a8..a8b8d55 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *.o *.so .cache +build compile_commands.json \ No newline at end of file diff --git a/mgist-postgis/CMakeLists.txt b/mgist-postgis/CMakeLists.txt new file mode 100755 index 0000000..583e427 --- /dev/null +++ b/mgist-postgis/CMakeLists.txt @@ -0,0 +1,40 @@ +#------------------------------------- +# MGiST-Postgis Main CMake file +#------------------------------------- + +cmake_minimum_required(VERSION 3.10) + +# Disallow in-source builds +if(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) + message(FATAL_ERROR "In-source builds not allowed. + Please make a new directory (called a build directory) and run CMake from there. + You may need to remove 'CMakeCache.txt' and 'CMakeFiles/'.") +endif() + +# Specify search path for CMake modules to be loaded +list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") + +project(MGiST-Postgis VERSION 1.0.0) +message(STATUS "Building MGiST-Postgis version ${PROJECT_VERSION}") + +# Find PostgreSQL +find_package(POSTGRESQL REQUIRED) +find_package(POSTGIS REQUIRED) +find_package(GEOS REQUIRED) +include_directories(SYSTEM ${POSTGRESQL_INCLUDE_DIR}) + +include_directories("${CMAKE_SOURCE_DIR}/ryu") +include_directories("${CMAKE_SOURCE_DIR}/liblwgeom") +add_subdirectory("${CMAKE_SOURCE_DIR}/liblwgeom" "liblwgeom") +add_subdirectory("${CMAKE_SOURCE_DIR}/ryu" "ryu") + +set(PROJECT_OBJECTS ${PROJECT_OBJECTS} "$") +set(PROJECT_OBJECTS ${PROJECT_OBJECTS} "$") +set(PROJECT_OBJECTS ${PROJECT_OBJECTS} "mgist_postgis.c") + +add_library(mgist_postgis MODULE ${PROJECT_OBJECTS}) + +install( + FILES "${CMAKE_SOURCE_DIR}/mgist_postgis.control" "${CMAKE_SOURCE_DIR}/mgist_postgis--1.0.sql" + DESTINATION "${POSTGRESQL_SHARE_DIR}/extension") +install(TARGETS mgist_postgis DESTINATION "${POSTGRESQL_DYNLIB_DIR}") \ No newline at end of file diff --git a/mgist-postgis/README.md b/mgist-postgis/README.md new file mode 100644 index 0000000..51149b5 --- /dev/null +++ b/mgist-postgis/README.md @@ -0,0 +1,38 @@ +Multi-Entry GiST Indexing for PostGIS +======================================== + +This directory contains an implementation of Multi-Entry GiST indexes for the PostGIS geometry type. + +Contrary to the traditional GiST index for PostGIS, MGiST will index collection types with one bounding box per element in the collection. This index will thus mainly benefit datasets containing collections such as multi-points or multi-polygons that are spread out over a large area. Tuples containing single geometries will be indexed using a single bounding box as usual. + +The MGiST index for geometries currently provides speedups for overlaps `&&` and distance `<->` operators. +However, the PostGIS support functions are not yet implemented for MGiST indexes, so the index will not be used for queries using the `ST_Intersects` or `ST_Contains` functions. To provide speedup for these functions, you will need to add an explicit overlaps test to the query. + +Dependencies +------------ +- [PostgreSQL 16](https://www.postgresql.org/) +- [PostGIS 3.4](https://postgis.net/) +- [mgist](../mgist) + +You should also set the following in postgresql.conf depending on the version of PostGIS you have installed (below we use PostGIS 3): + +``` +shared_preload_libraries = 'postgis-3' +``` + +Installation +------------ +Compiling and installing the extension +``` +make +sudo make install +``` + +Using the extension to create a Multi-Entry R-Tree on the geometry column `trip` from the table `trips(id, trip)` +```sql +CREATE EXTENSION mgist_mobilitydb CASCADE; +CREATE INDEX trips_mgist_trip on trips using mgist(trip); +``` + +Contact: + Maxime Schoemans \ No newline at end of file diff --git a/mgist-postgis/cmake/FindGEOS.cmake b/mgist-postgis/cmake/FindGEOS.cmake new file mode 100644 index 0000000..01dd7c2 --- /dev/null +++ b/mgist-postgis/cmake/FindGEOS.cmake @@ -0,0 +1,218 @@ +# Find GEOS +# ~~~~~~~~~ +# Taken from QGIS and made minimal modifications for MobilityDB, +# e.g., to allow 'dev' suffix in GEOS version +# Copyright (c) 2021, Esteban Zimanyi +# +# Copyright (c) 2008, Mateusz Loskot +# (based on FindGDAL.cmake by Magnus Homann) +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# +# CMake module to search for GEOS library +# +# If it's found it sets GEOS_FOUND to TRUE +# and following variables are set: +# GEOS_INCLUDE_DIR +# GEOS_LIBRARY +# + +IF(APPLE) + FUNCTION (GET_VERSION_PLIST PLISTFILE OUTVAR) + SET (PVERSION "") + IF (EXISTS ${PLISTFILE}) + FILE (READ "${PLISTFILE}" info_plist) + STRING (REGEX REPLACE "\n" "" info_plist "${info_plist}") + STRING (REGEX MATCH "CFBundleShortVersionString[ \t]*([0-9\\.]*)" PLISTVERSION "${info_plist}") + STRING (REGEX REPLACE "CFBundleShortVersionString[ \t]*([0-9\\.]*)" "\\1" PVERSION "${PLISTVERSION}") + ENDIF (EXISTS ${PLISTFILE}) + SET (${OUTVAR} ${PVERSION} PARENT_SCOPE) + ENDFUNCTION (GET_VERSION_PLIST) +ENDIF(APPLE) + +IF(WIN32) + + IF (MINGW) + FIND_PATH(GEOS_INCLUDE_DIR geos_c.h "$ENV{LIB_DIR}/include" /usr/local/include /usr/include c:/msys/local/include) + FIND_LIBRARY(GEOS_LIBRARY NAMES geos_c PATHS "$ENV{LIB_DIR}/lib" /usr/local/lib /usr/lib c:/msys/local/lib) + ENDIF (MINGW) + + IF (MSVC) + FIND_PATH(GEOS_INCLUDE_DIR geos_c.h $ENV{LIB_DIR}/include $ENV{INCLUDE}) + FIND_LIBRARY(GEOS_LIBRARY NAMES geos_c_i geos_c PATHS + "$ENV{LIB_DIR}/lib" + $ENV{LIB} + ) + ENDIF (MSVC) + +ELSEIF(APPLE AND QGIS_MAC_DEPS_DIR) + + FIND_PATH(GEOS_INCLUDE_DIR geos_c.h "$ENV{LIB_DIR}/include" ) + FIND_LIBRARY(GEOS_LIBRARY NAMES geos_c PATHS "$ENV{LIB_DIR}/lib" ) + +ELSE(WIN32) + + IF(UNIX) + # try to use framework on mac + # want clean framework path, not unix compatibility path + IF (APPLE) + IF (CMAKE_FIND_FRAMEWORK MATCHES "FIRST" + OR CMAKE_FRAMEWORK_PATH MATCHES "ONLY" + OR NOT CMAKE_FIND_FRAMEWORK) + SET (CMAKE_FIND_FRAMEWORK_save ${CMAKE_FIND_FRAMEWORK} CACHE STRING "" FORCE) + SET (CMAKE_FIND_FRAMEWORK "ONLY" CACHE STRING "" FORCE) + FIND_LIBRARY(GEOS_LIBRARY GEOS) + IF (GEOS_LIBRARY) + # they're all the same in a framework + SET (GEOS_INCLUDE_DIR ${GEOS_LIBRARY}/Headers CACHE PATH "Path to a file.") + # set GEOS_CONFIG to make later test happy, not used here, may not exist + SET (GEOS_CONFIG ${GEOS_LIBRARY}/unix/bin/geos-config CACHE FILEPATH "Path to a program.") + # version in info.plist + GET_VERSION_PLIST (${GEOS_LIBRARY}/Resources/Info.plist GEOS_VERSION) + IF (NOT GEOS_VERSION) + MESSAGE (FATAL_ERROR "Could not determine GEOS version from framework.") + ENDIF (NOT GEOS_VERSION) + STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)(dev)?" "\\1" GEOS_VERSION_MAJOR "${GEOS_VERSION}") + STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)(dev)?" "\\2" GEOS_VERSION_MINOR "${GEOS_VERSION}") + STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)(dev)?" "\\3" GEOS_VERSION_MICRO "${GEOS_VERSION}") + IF (GEOS_VERSION_MAJOR LESS 3) + MESSAGE (FATAL_ERROR "GEOS version is too old (${GEOS_VERSION}). Use 3.0.0 or higher.") + ENDIF (GEOS_VERSION_MAJOR LESS 3) + ENDIF (GEOS_LIBRARY) + SET (CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK_save} CACHE STRING "" FORCE) + ENDIF () + ENDIF (APPLE) + + IF(CYGWIN) + FIND_LIBRARY(GEOS_LIBRARY NAMES geos_c PATHS /usr/lib /usr/local/lib) + ENDIF(CYGWIN) + + IF (NOT GEOS_INCLUDE_DIR OR NOT GEOS_LIBRARY OR NOT GEOS_CONFIG) + # didn't find OS X framework, and was not set by user + SET(GEOS_CONFIG_PREFER_PATH "$ENV{GEOS_HOME}/bin" CACHE STRING "preferred path to GEOS (geos-config)") + FIND_PROGRAM(GEOS_CONFIG geos-config + ${GEOS_CONFIG_PREFER_PATH} + $ENV{LIB_DIR}/bin + /usr/local/bin/ + /usr/bin/ + ) + #MESSAGE("DBG GEOS_CONFIG ${GEOS_CONFIG}") + + IF (GEOS_CONFIG) + execute_process( + COMMAND ${GEOS_CONFIG} --version + OUTPUT_VARIABLE GEOS_VERSION) + # Remove trailing newline + STRING(REGEX REPLACE "\n$" "" GEOS_VERSION "${GEOS_VERSION}") + STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)(dev)?" "\\1" GEOS_VERSION_MAJOR "${GEOS_VERSION}") + STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)(dev)?" "\\2" GEOS_VERSION_MINOR "${GEOS_VERSION}") + STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)(dev)?" "\\3" GEOS_VERSION_MICRO "${GEOS_VERSION}") + + IF (GEOS_VERSION_MAJOR LESS 3 OR (GEOS_VERSION_MAJOR EQUAL 3 AND GEOS_VERSION_MINOR LESS 3) ) + MESSAGE (FATAL_ERROR "GEOS version is too old (${GEOS_VERSION}). Use 3.3.0 or higher.") + ENDIF (GEOS_VERSION_MAJOR LESS 3 OR (GEOS_VERSION_MAJOR EQUAL 3 AND GEOS_VERSION_MINOR LESS 3) ) + + # set INCLUDE_DIR to prefix+include + execute_process( + COMMAND ${GEOS_CONFIG} --prefix + OUTPUT_VARIABLE GEOS_PREFIX) + # Remove trailing newline + STRING(REGEX REPLACE "\n$" "" GEOS_PREFIX "${GEOS_PREFIX}") + FIND_PATH(GEOS_INCLUDE_DIR + geos_c.h + ${GEOS_PREFIX}/include + /usr/local/include + /usr/include + ) + + ## extract link dirs for rpath + execute_process( + COMMAND ${GEOS_CONFIG} --libs + OUTPUT_VARIABLE GEOS_CONFIG_LIBS) + # Remove trailing newline + STRING(REGEX REPLACE "\n$" "" GEOS_CONFIG_LIBS "${GEOS_CONFIG_LIBS}") + + ## split off the link dirs (for rpath) + ## use regular expression to match wildcard equivalent "-L*" + ## with is a space or a semicolon + STRING(REGEX MATCHALL "[-][L]([^ ;])+" + GEOS_LINK_DIRECTORIES_WITH_PREFIX + "${GEOS_CONFIG_LIBS}" ) + #MESSAGE("DBG GEOS_LINK_DIRECTORIES_WITH_PREFIX=${GEOS_LINK_DIRECTORIES_WITH_PREFIX}") + + ## remove prefix -L because we need the pure directory for LINK_DIRECTORIES + + IF (GEOS_LINK_DIRECTORIES_WITH_PREFIX) + STRING(REGEX REPLACE "[-][L]" "" GEOS_LINK_DIRECTORIES ${GEOS_LINK_DIRECTORIES_WITH_PREFIX} ) + ENDIF (GEOS_LINK_DIRECTORIES_WITH_PREFIX) + + ### XXX - mloskot: geos-config --libs does not return -lgeos_c, so set it manually + ## split off the name + ## use regular expression to match wildcard equivalent "-l*" + ## with is a space or a semicolon + #STRING(REGEX MATCHALL "[-][l]([^ ;])+" + # GEOS_LIB_NAME_WITH_PREFIX + # "${GEOS_CONFIG_LIBS}" ) + #MESSAGE("DBG GEOS_CONFIG_LIBS=${GEOS_CONFIG_LIBS}") + #MESSAGE("DBG GEOS_LIB_NAME_WITH_PREFIX=${GEOS_LIB_NAME_WITH_PREFIX}") + SET(GEOS_LIB_NAME_WITH_PREFIX -lgeos_c CACHE STRING INTERNAL) + + ## remove prefix -l because we need the pure name + + IF (GEOS_LIB_NAME_WITH_PREFIX) + STRING(REGEX REPLACE "[-][l]" "" GEOS_LIB_NAME ${GEOS_LIB_NAME_WITH_PREFIX} ) + ENDIF (GEOS_LIB_NAME_WITH_PREFIX) + #MESSAGE("DBG GEOS_LIB_NAME=${GEOS_LIB_NAME}") + + IF (APPLE) + IF (NOT GEOS_LIBRARY) + # work around empty GEOS_LIBRARY left by framework check + # while still preserving user setting if given + # ***FIXME*** need to improve framework check so below not needed + SET(GEOS_LIBRARY ${GEOS_LINK_DIRECTORIES}/lib${GEOS_LIB_NAME}.dylib CACHE STRING INTERNAL FORCE) + ENDIF (NOT GEOS_LIBRARY) + ELSE (APPLE) + FIND_LIBRARY(GEOS_LIBRARY NAMES ${GEOS_LIB_NAME} PATHS ${GEOS_LIB_DIRECTORIES}/lib) + ENDIF (APPLE) + #MESSAGE("DBG GEOS_LIBRARY=${GEOS_LIBRARY}") + + ELSE(GEOS_CONFIG) + MESSAGE("FindGEOS.cmake: geos-config not found. Please set it manually. GEOS_CONFIG=${GEOS_CONFIG}") + ENDIF(GEOS_CONFIG) + ENDIF(NOT GEOS_INCLUDE_DIR OR NOT GEOS_LIBRARY OR NOT GEOS_CONFIG) + ENDIF(UNIX) +ENDIF(WIN32) + +IF(GEOS_INCLUDE_DIR AND NOT GEOS_VERSION) + FILE(READ ${GEOS_INCLUDE_DIR}/geos_c.h VERSIONFILE) + STRING(REGEX MATCH "#define GEOS_VERSION \"[0-9]+\\.[0-9]+\\.[0-9]+" GEOS_VERSION ${VERSIONFILE}) + STRING(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" GEOS_VERSION ${GEOS_VERSION}) + STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)(dev)?" "\\1" GEOS_VERSION_MAJOR "${GEOS_VERSION}") + STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)(dev)?" "\\2" GEOS_VERSION_MINOR "${GEOS_VERSION}") + STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)(dev)?" "\\3" GEOS_VERSION_MICRO "${GEOS_VERSION}") +ENDIF(GEOS_INCLUDE_DIR AND NOT GEOS_VERSION) + +IF (GEOS_INCLUDE_DIR AND GEOS_LIBRARY) + SET(GEOS_FOUND TRUE) +ENDIF (GEOS_INCLUDE_DIR AND GEOS_LIBRARY) + +IF (GEOS_FOUND) + + IF (NOT GEOS_FIND_QUIETLY) + MESSAGE(STATUS "Found GEOS: ${GEOS_LIBRARY} (${GEOS_VERSION})") + ENDIF (NOT GEOS_FIND_QUIETLY) + +ELSE (GEOS_FOUND) + + MESSAGE(GEOS_INCLUDE_DIR=${GEOS_INCLUDE_DIR}) + MESSAGE(GEOS_LIBRARY=${GEOS_LIBRARY}) + MESSAGE(FATAL_ERROR "Could not find GEOS") + +ENDIF (GEOS_FOUND) + +message(STATUS "GEOS_INCLUDE_DIR: ${GEOS_INCLUDE_DIR}") +message(STATUS "GEOS_LIBRARY: ${GEOS_LIBRARY}") +message(STATUS "GEOS_VERSION: ${GEOS_VERSION}") +message(STATUS "GEOS_VERSION_MAJOR: ${GEOS_VERSION_MAJOR}") +message(STATUS "GEOS_VERSION_MINOR: ${GEOS_VERSION_MINOR}") +message(STATUS "GEOS_VERSION_MICRO: ${GEOS_VERSION_MICRO}") diff --git a/mgist-postgis/cmake/FindPOSTGIS.cmake b/mgist-postgis/cmake/FindPOSTGIS.cmake new file mode 100644 index 0000000..760b7b2 --- /dev/null +++ b/mgist-postgis/cmake/FindPOSTGIS.cmake @@ -0,0 +1,76 @@ +# (c) 2015 pgRouting colaborators +# +# Finds the most recent postGIS for a particular postgreSQL +# We need this for the tests +# +# Usage: +# find_package(POSTGIS) +# +# The following variables are set if PostGIS is found: +# POSTGIS_FOUND - Set to true when PostGIS is found. +# POSTGIS_LIBRARY - if we ever need to link it +# POSTGIS_CONTROL - if we ever need to see the contents +# POSTGIS_VERSION - The full numbers +# POSTGIS_VERSION_STR - The th PostGIS prefix + +if(POSTGIS_FOUND) + return() +endif() + +if(NOT POSTGRESQL_FOUND) + find_package(POSTGRESQL REQUIRED) +endif() + +# Find PostGIS library + +# If specific version of PostGIS requested, choose that one, otherwise get all versions +if(POSTGIS_REQUIRED_VERSION) + message(STATUS "Selecting requested PostGIS version: Selecting postgis-${POSTGIS_REQUIRED_VERSION}") + file(GLOB POSTGIS_LIBRARY "${POSTGRESQL_DYNLIB_DIR}/postgis-${POSTGIS_REQUIRED_VERSION}.*") +else() + file(GLOB POSTGIS_LIBRARY "${POSTGRESQL_DYNLIB_DIR}/postgis-*.*") +endif() + +if(POSTGIS_LIBRARY STREQUAL "") + message(FATAL_ERROR "No PostGIS library have been found in ${POSTGRESQL_DYNLIB_DIR}") +else() + # If several versions of PostGIS found, choose the first one + list(LENGTH POSTGIS_LIBRARY NO_POSTGIS_LIBRARIES) + if(NO_POSTGIS_LIBRARIES GREATER 1) + list(GET POSTGIS_LIBRARY 0 POSTGIS_LIBRARY) + message(STATUS "Several PostGIS versions found: Selecting ${POSTGIS_LIBRARY}") + endif() +endif() + +message(STATUS "Looking for postgis.control file in ${POSTGRESQL_SHARE_DIR}/extension") +find_file(POSTGIS_CONTROL postgis.control + PATHS "${POSTGRESQL_SHARE_DIR}/extension") + +if(POSTGIS_CONTROL) + file(READ ${POSTGIS_CONTROL} control_contents) + string(REGEX MATCH "([0-9]+)\\.([0-9]+)\\.([0-9]+)" POSTGIS_VERSION ${control_contents}) + set(POSTGIS_VERSION_STR "PostGIS ${POSTGIS_VERSION}") + string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\1" POSTGIS_VERSION_MAJOR ${POSTGIS_VERSION}) + string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\2" POSTGIS_VERSION_MINOR ${POSTGIS_VERSION}) + string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" "\\3" POSTGIS_VERSION_MICRO ${POSTGIS_VERSION}) + math(EXPR POSTGIS_VERSION_NUMBER "${POSTGIS_VERSION_MAJOR} * 10000 + ${POSTGIS_VERSION_MINOR} * 100 + ${POSTGIS_VERSION_MICRO}") +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(POSTGIS + FOUND_VAR POSTGIS_FOUND + REQUIRED_VARS POSTGIS_LIBRARY POSTGIS_CONTROL + VERSION_VAR POSTGIS_VERSION + FAIL_MESSAGE "Could NOT find PostGIS") + +if(POSTGIS_FOUND) + mark_as_advanced(POSTGIS_LIBRARY POSTGIS_CONTROL POSTGIS_VERSION POSTGIS_VERSION_STR) +endif() + +message(STATUS "POSTGRESQL_SHARE_DIR: ${POSTGRESQL_SHARE_DIR}") +message(STATUS "POSTGRESQL_DYNLIB_DIR: ${POSTGRESQL_DYNLIB_DIR}") +message(STATUS "POSTGIS_LIBRARY: ${POSTGIS_LIBRARY}") +message(STATUS "POSTGIS_CONTROL: ${POSTGIS_CONTROL}") +message(STATUS "POSTGIS_VERSION: ${POSTGIS_VERSION}") +message(STATUS "POSTGIS_VERSION_STR: ${POSTGIS_VERSION_STR}") +message(STATUS "POSTGIS_VERSION_NUMBER: ${POSTGIS_VERSION_NUMBER}") diff --git a/mgist-postgis/cmake/FindPOSTGRESQL.cmake b/mgist-postgis/cmake/FindPOSTGRESQL.cmake new file mode 100644 index 0000000..bdd0cd6 --- /dev/null +++ b/mgist-postgis/cmake/FindPOSTGRESQL.cmake @@ -0,0 +1,126 @@ +# - Find PostgreSQL +# Find the PostgreSQL includes and client library +# This module defines +# POSTGRESQL_INCLUDE_DIR, where to find postgres.h +# POSTGRESQL_BINARY_DIR, location of postgres binaries +# POSTGRESQL_LIBRARIES, the libraries needed to use POSTGRESQL. +# POSTGRESQL_FOUND, If false, do not try to use PostgreSQL. +# POSTGRESQL_VERSION_STRING +# +# Copyright (c) 2021, Vicky Vergara +# Copyright (c) 2006, Jaroslaw Staniek, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +SET(POSTGRESQL_BIN "" CACHE STRING "non-standard path to the postgresql program executables") + +if(NOT "${POSTGRESQL_BIN}" STREQUAL "") + # Checking POSTGRESQL_PG_CONFIG + find_program(POSTGRESQL_PG_CONFIG NAMES pg_config + PATHS + ${POSTGRESQL_BIN} + NO_DEFAULT_PATH + ) +else() + # Checking POSTGRESQL_PG_CONFIG + # First we search for installation via source compilation + # If not found, we search for normal installation (via package repositories) + find_program(POSTGRESQL_PG_CONFIG NAMES pg_config + PATHS + /usr/local/pgsql/bin/ + NO_DEFAULT_PATH + ) + if(NOT POSTGRESQL_PG_CONFIG) + unset(POSTGRESQL_PG_CONFIG CACHE) + find_program(POSTGRESQL_PG_CONFIG NAMES pg_config + PATHS + /usr/lib/postgresql/*/bin/ + ) + endif() +endif() + +if(POSTGRESQL_PG_CONFIG) + execute_process( + COMMAND ${POSTGRESQL_PG_CONFIG} --bindir + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE POSTGRESQL_BIN_DIR) +else() + message(FATAL_ERROR "Could not find pg_config") +endif() + +set(POSTGRESQL_PG_CONFIG "${POSTGRESQL_BIN_DIR}/pg_config") + +execute_process( + COMMAND ${POSTGRESQL_PG_CONFIG} --version + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE POSTGRESQL_VERSION_STRING) + +string(SUBSTRING "${POSTGRESQL_VERSION_STRING}" 11 -1 POSTGRESQL_VERSION) + +# for XbetaY XalphaY XrcY -> X.Y +string(REGEX REPLACE "^([0-9]+)[beta|alpha|rc|devel].*" "\\1.0" POSTGRESQL_VERSION ${POSTGRESQL_VERSION}) + +#for X.Y.Z -> XY Y<10 +string(REGEX REPLACE "^([0-9]+)\\.([0-9]+).*" "\\1.\\2" POSTGRESQL_VERSION ${POSTGRESQL_VERSION}) +string(REGEX REPLACE "^([0-9]+)\\.([0-9]+).*" "\\1" POSTGRESQL_VERSION_MAJOR ${POSTGRESQL_VERSION}) +string(REGEX REPLACE "^([0-9]+)\\.([0-9]+).*" "\\2" POSTGRESQL_VERSION_MINOR ${POSTGRESQL_VERSION}) + +message(STATUS "POSTGRESQL_VERSION_MAJOR=${POSTGRESQL_VERSION_MAJOR}") +message(STATUS "POSTGRESQL_VERSION_MINOR=${POSTGRESQL_VERSION_MINOR}") +math(EXPR POSTGRESQL_VERSION_NUMBER "${POSTGRESQL_VERSION_MAJOR} * 10000 + ${POSTGRESQL_VERSION_MINOR} * 100") + +execute_process( + COMMAND ${POSTGRESQL_PG_CONFIG} --includedir-server + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE POSTGRESQL_INCLUDE_DIR) + +#[[ +#instead of path and our own guesses +find_path(POSTGRESQL_INCLUDE_DIR postgres.h + HINTS + ${TEMP_POSTGRESQL_INCLUDE_DIR} + PATHS + /usr/include/server + /usr/include/pgsql/server + /usr/local/include/pgsql/server + /usr/include/postgresql/server + /usr/include/postgresql/*/server + /usr/local/include/postgresql/server + /usr/local/include/postgresql/*/server + $ENV{ProgramFiles}/PostgreSQL/*/include/server + $ENV{SystemDrive}/PostgreSQL/*/include/server + ) +]] + +execute_process( + COMMAND ${POSTGRESQL_PG_CONFIG} --libdir + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE POSTGRESQL_LIBRARIES) +execute_process( + COMMAND ${POSTGRESQL_PG_CONFIG} --sharedir + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE POSTGRESQL_SHARE_DIR) +execute_process( + COMMAND ${POSTGRESQL_PG_CONFIG} --pkglibdir + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE POSTGRESQL_DYNLIB_DIR) + +message(STATUS "POSTGRESQL_BIN_DIR: ${POSTGRESQL_BIN_DIR}") +message(STATUS "POSTGRESQL_INCLUDE_DIR: ${POSTGRESQL_INCLUDE_DIR}") +message(STATUS "POSTGRESQL_LIBRARIES: ${POSTGRESQL_LIBRARIES}") +message(STATUS "POSTGRESQL_SHARE_DIR: ${POSTGRESQL_SHARE_DIR}") +message(STATUS "POSTGRESQL_DYNLIB_DIR: ${POSTGRESQL_DYNLIB_DIR}") +message(STATUS "POSTGRESQL_VERSION: ${POSTGRESQL_VERSION}") +message(STATUS "POSTGRESQL_VERSION_NUMBER: ${POSTGRESQL_VERSION_NUMBER}") +message(STATUS "POSTGRESQL_VERSION_STRING: ${POSTGRESQL_VERSION_STRING}") + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(POSTGRESQL + FOUND_VAR POSTGRESQL_FOUND + REQUIRED_VARS POSTGRESQL_BIN_DIR POSTGRESQL_INCLUDE_DIR POSTGRESQL_LIBRARIES POSTGRESQL_SHARE_DIR POSTGRESQL_DYNLIB_DIR + VERSION_VAR POSTGRESQL_VERSION) + +if (POSTGRESQL_FOUND) + mark_as_advanced(POSTGRESQL_BIN_DIR POSTGRESQL_INCLUDE_DIR POSTGRESQL_LIBRARIES POSTGRESQL_SHARE_DIR POSTGRESQL_DYNLIB_DIR) +endif() diff --git a/mgist-postgis/liblwgeom/CMakeLists.txt b/mgist-postgis/liblwgeom/CMakeLists.txt new file mode 100644 index 0000000..66e2f81 --- /dev/null +++ b/mgist-postgis/liblwgeom/CMakeLists.txt @@ -0,0 +1,80 @@ +add_library(liblwgeom OBJECT + bytebuffer.c + effectivearea.c + gbox.c + gserialized.c + gserialized1.c + gserialized2.c + lookup3.c + lwalgorithm.c + lwboundingcircle.c + lwchaikins.c + lwcircstring.c + lwcollection.c + lwcompound.c + lwcurvepoly.c + lwgeodetic.c + lwgeodetic_measures.c + lwgeodetic_tree.c + lwgeom.c + lwgeom_api.c + lwgeom_debug.c + lwgeom_geos.c + lwgeom_geos_clean.c + lwgeom_geos_cluster.c + lwgeom_geos_node.c + lwgeom_geos_split.c + lwgeom_median.c + lwgeom_topo.c + lwgeom_transform.c + lwgeom_wrapx.c + lwhomogenize.c + lwin_encoded_polyline.c + lwin_geojson.c + lwin_twkb.c + lwin_wkb.c + lwin_wkt.c + lwin_wkt_lex.c + lwin_wkt_parse.c + lwiterator.c + lwkmeans.c + lwline.c + lwlinearreferencing.c + lwmcurve.c + lwmline.c + lwmpoint.c + lwmpoly.c + lwmsurface.c + lwmval.c + lwout_encoded_polyline.c + lwout_geojson.c + lwout_gml.c + lwout_kml.c + lwout_svg.c + lwout_twkb.c + lwout_wkb.c + lwout_wkt.c + lwout_x3d.c + lwpoint.c + lwpoly.c + lwprint.c + lwpsurface.c + lwrandom.c + lwspheroid.c + lwstroke.c + lwtin.c + lwtree.c + lwtriangle.c + lwunionfind.c + lwutil.c + measures.c + measures3d.c + optionlist.c + ptarray.c + stringbuffer.c + stringlist.c + varint.c + ) + +set_property(TARGET liblwgeom PROPERTY C_VISIBILITY_PRESET hidden) +set_property(TARGET liblwgeom PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/mgist-postgis/liblwgeom/Makefile.in b/mgist-postgis/liblwgeom/Makefile.in new file mode 100644 index 0000000..3094837 --- /dev/null +++ b/mgist-postgis/liblwgeom/Makefile.in @@ -0,0 +1,240 @@ +#/********************************************************************** +# * +# * PostGIS - Spatial Types for PostgreSQL +# * http://postgis.net +# * +# * PostGIS is free software: you can redistribute it and/or modify +# * it under the terms of the GNU General Public License as published by +# * the Free Software Foundation, either version 2 of the License, or +# * (at your option) any later version. +# * +# * PostGIS is distributed in the hope that it will be useful, +# * but WITHOUT ANY WARRANTY; without even the implied warranty of +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# * GNU General Public License for more details. +# * +# * You should have received a copy of the GNU General Public License +# * along with PostGIS. If not, see . +# * +# ********************************************************************** +# * +# * Copyright 2022 Sandro Santilli +# * Copyright 2008 Mark Cave-Ayland +# * +# **********************************************************************/ + +srcdir = @srcdir@ +builddir = @builddir@ +top_builddir = @top_builddir@ +prefix = @prefix@ +libdir = @libdir@ +includedir = @includedir@ +exec_prefix = @exec_prefix@ + +CC = @CC@ +CPPFLAGS = $(RYU_INCLUDE) @CPPFLAGS@ -I$(builddir) -I$(srcdir) +CFLAGS = @CFLAGS@ @PICFLAGS@ +LDFLAGS = @LDFLAGS@ @GEOS_LDFLAGS@ @PROJ_LDFLAGS@ @JSON_LDFLAGS@ +SHELL = @SHELL@ +INSTALL = @INSTALL@ +LIBTOOL = @LIBTOOL@ + +VPATH = $(srcdir):$(builddir) +RYU_INCLUDE = -I$(srcdir)/../deps/ryu/.. + +SOVER = @POSTGIS_MAJOR_VERSION@.@POSTGIS_MINOR_VERSION@ + +IFACE_CURRENT = @LIBLWGEOM_CURRENT@ +IFACE_AGE = @LIBLWGEOM_AGE@ +IFACE_REV = @LIBLWGEOM_REV@ + +VERSION_INFO = $(IFACE_CURRENT):$(IFACE_REV):$(IFACE_AGE) + + +YACC=@YACC@ +LEX=@LEX@ + +# Standalone LWGEOM objects +SA_OBJS = \ + stringbuffer.o \ + optionlist.o \ + stringlist.o \ + bytebuffer.o \ + measures.o \ + measures3d.o \ + ptarray.o \ + lookup3.o \ + lwgeom_api.o \ + lwgeom.o \ + lwpoint.o \ + lwline.o \ + lwpoly.o \ + lwtriangle.o \ + lwmpoint.o \ + lwmline.o \ + lwmpoly.o \ + lwboundingcircle.o \ + lwcollection.o \ + lwcircstring.o \ + lwcompound.o \ + lwcurvepoly.o \ + lwmcurve.o \ + lwmsurface.o \ + lwpsurface.o \ + lwtin.o \ + lwout_wkb.o \ + lwin_geojson.o \ + lwin_wkb.o \ + lwin_twkb.o \ + lwiterator.o \ + lwgeom_median.o \ + lwout_wkt.o \ + lwout_twkb.o \ + lwin_wkt_parse.o \ + lwin_wkt_lex.o \ + lwin_wkt.o \ + lwin_encoded_polyline.o \ + lwutil.o \ + lwhomogenize.o \ + lwalgorithm.o \ + lwstroke.o \ + lwlinearreferencing.o \ + lwprint.o \ + gbox.o \ + gserialized.o \ + gserialized1.o \ + gserialized2.o \ + lwgeodetic.o \ + lwgeodetic_measures.o \ + lwgeodetic_tree.o \ + lwrandom.o \ + lwtree.o \ + lwout_gml.o \ + lwout_kml.o \ + lwout_geojson.o \ + lwout_svg.o \ + lwout_x3d.o \ + lwout_encoded_polyline.o \ + lwgeom_debug.o \ + lwgeom_geos.o \ + lwgeom_geos_clean.o \ + lwgeom_geos_cluster.o \ + lwgeom_geos_node.o \ + lwgeom_geos_split.o \ + lwgeom_topo.o \ + lwgeom_transform.o \ + lwgeom_wrapx.o \ + lwunionfind.o \ + effectivearea.o \ + lwchaikins.o \ + lwmval.o \ + lwkmeans.o \ + varint.o + +NM_OBJS = \ + lwspheroid.o + +ifeq (@SFCGAL@,sfcgal) +CFLAGS += @SFCGAL_CPPFLAGS@ +LDFLAGS += @SFCGAL_LDFLAGS@ +SA_OBJS += lwgeom_sfcgal.o +endif + +LDFLAGS += -no-undefined + +LT_SA_OBJS = $(SA_OBJS:.o=.lo) +LT_NM_OBJS = $(NM_OBJS:.o=.lo) +LT_OBJS = $(LT_SA_OBJS) $(LT_NM_OBJS) + +SA_HEADERS = \ + bytebuffer.h \ + effectivearea.h \ + liblwgeom.h \ + liblwgeom_internal.h \ + lwgeodetic.h \ + lwgeodetic_tree.h \ + liblwgeom_topo.h \ + liblwgeom_topo_internal.h \ + lwgeom_log.h \ + lwgeom_geos.h \ + lwgeom_log.h \ + lwgeom_sfcgal.h \ + lwinline.h \ + lwin_wkt.h \ + lwin_wkt_parse.h \ + lwout_twkb.h \ + lwrandom.h \ + lwtree.h \ + measures3d.h \ + measures.h \ + stringbuffer.h \ + varint.h + +all: liblwgeom.la + +install: + +uninstall: + +# Make all objects depend upon postgis_config.h and postgis_revision.h +$(LT_OBJS): ../postgis_config.h ../postgis_revision.h $(SA_HEADERS) + +../postgis_revision.h: + $(MAKE) -C .. postgis_revision.h + + +RYU_LIBPATH = ../deps/ryu/@RYU_LIB@ +$(RYU_LIBPATH): ../deps/ryu/d2s.c + $(MAKE) -C ../deps/ryu @RYU_LIB@ + +liblwgeom.la: $(LT_OBJS) $(RYU_LIBPATH) + $(LIBTOOL) --tag=CC --mode=link $(CC) -rpath $(libdir) $(LT_OBJS) $(RYU_LIBPATH)\ + -release $(SOVER) -version-info $(VERSION_INFO) $(LDFLAGS) -static -o $@ + +maintainer-clean: clean + rm -f lwin_wkt_lex.c + rm -f lwin_wkt_parse.h + rm -f lwin_wkt_parse.c + +clean: + $(MAKE) -C cunit clean + $(MAKE) -C ../deps/ryu clean + rm -f $(LT_OBJS) $(SA_OBJS) $(NM_OBJS) + rm -f liblwgeom.la + rm -rf .libs + +distclean: clean + $(MAKE) -C cunit distclean + rm -f liblwgeom.h Makefile + +check: check-unit + +check-regress: + +.PHONY: check-unit +check-unit: liblwgeom.la + $(MAKE) -C cunit check + +# Command to build each of the .lo files +$(LT_SA_OBJS): %.lo: %.c + $(LIBTOOL) --mode=compile $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< + +$(LT_NM_OBJS): %.lo: %.c + $(LIBTOOL) --mode=compile $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< + +lwin_wkt_parse.c lwin_wkt_parse.h: lwin_wkt_parse.y + @test -n "$(YACC)" || { \ + echo "No yacc found, cannot build parser" >&2; \ + false; \ + } + $(YACC) -p wkt_yy -o'lwin_wkt_parse.c' -d $< + +lwin_wkt_lex.c: lwin_wkt_lex.l + @test -n "$(LEX)" || { \ + echo "No lex found, cannot build parser" >&2; \ + false; \ + } + $(LEX) -i $< + +liblwgeom.h: liblwgeom.h.in + cd $(top_builddir) && ./config.status diff --git a/mgist-postgis/liblwgeom/README.topo b/mgist-postgis/liblwgeom/README.topo new file mode 100644 index 0000000..6ea051e --- /dev/null +++ b/mgist-postgis/liblwgeom/README.topo @@ -0,0 +1,26 @@ +About topology support in liblwgeom +----------------------------------- + Author: Sandro Santilli + Last modified: Jun 13, 2015 + +The topology support in liblwgeom exposes an API to create and manage +"standard" topologies that use provided callbacks to take care of actual +data storage. + +The topology standard is based on what was provided by PostGIS at its +version 2.0.0, which in turn is based on ISO SQL/MM (ISO 13249) with +the addition of the "TopoGeometry" concept. + +The public header for topology support is `liblwgeom_topo.h`. +The caller has to setup a backend interface (LWT_BE_IFACE) implementing +all the required callbacks and will then be able to use the provided +editing functions. + +The contract for each callback is fully specified in the header. +The callbacks are as simple as possible while still allowing for +backend-specific optimizations. + +The backend interface is an opaque object and callbacks are registered +into it using free functions. This is to allow for modifying the required +set of callbacks between versions of the library without breaking backward +compatibility. diff --git a/mgist-postgis/liblwgeom/bytebuffer.c b/mgist-postgis/liblwgeom/bytebuffer.c new file mode 100644 index 0000000..fc1fa5c --- /dev/null +++ b/mgist-postgis/liblwgeom/bytebuffer.c @@ -0,0 +1,416 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2015 Nicklas Avén + * + **********************************************************************/ + + + +#include "liblwgeom_internal.h" +#include "bytebuffer.h" + + +/** +* Allocate just the internal buffer of an existing bytebuffer_t +* struct. Useful for allocating short-lived bytebuffers off the stack. +*/ +void +bytebuffer_init_with_size(bytebuffer_t *s, size_t size) +{ + if ( size < BYTEBUFFER_STATICSIZE ) + { + s->capacity = BYTEBUFFER_STATICSIZE; + s->buf_start = s->buf_static; + } + else + { + s->buf_start = lwalloc(size); + s->capacity = size; + } + s->readcursor = s->writecursor = s->buf_start; + memset(s->buf_start, 0, s->capacity); +} + +/** +* Free the bytebuffer_t and all memory managed within it. +*/ +void +bytebuffer_destroy_buffer(bytebuffer_t *s) +{ + if ( s->buf_start != s->buf_static ) + { + lwfree(s->buf_start); + s->buf_start = NULL; + } + + return; +} + +/** +* If necessary, expand the bytebuffer_t internal buffer to accomodate the +* specified additional size. +*/ +static inline void +bytebuffer_makeroom(bytebuffer_t *s, size_t size_to_add) +{ + LWDEBUGF(2,"Entered bytebuffer_makeroom with space need of %d", size_to_add); + size_t current_write_size = (s->writecursor - s->buf_start); + size_t capacity = s->capacity; + size_t required_size = current_write_size + size_to_add; + + LWDEBUGF(2,"capacity = %d and required size = %d",capacity ,required_size); + while (capacity < required_size) + capacity *= 2; + + if ( capacity > s->capacity ) + { + size_t current_read_size = (s->readcursor - s->buf_start); + LWDEBUGF(4,"We need to realloc more memory. New capacity is %d", capacity); + if ( s->buf_start == s->buf_static ) + { + s->buf_start = lwalloc(capacity); + memcpy(s->buf_start, s->buf_static, s->capacity); + } + else + { + s->buf_start = lwrealloc(s->buf_start, capacity); + } + s->capacity = capacity; + s->writecursor = s->buf_start + current_write_size; + s->readcursor = s->buf_start + current_read_size; + } + return; +} + +/** Returns a copy of the internal buffer */ +lwvarlena_t * +bytebuffer_get_buffer_varlena(const bytebuffer_t *s) +{ + size_t bufsz = bytebuffer_getlength(s); + lwvarlena_t *v = lwalloc(bufsz + LWVARHDRSZ); + memcpy(v->data, s->buf_start, bufsz); + LWSIZE_SET(v->size, bufsz + LWVARHDRSZ); + return v; +} + +/** Returns a read-only reference to the internal buffer */ +const uint8_t* +bytebuffer_get_buffer(const bytebuffer_t *s, size_t *buffer_length) +{ + if ( buffer_length ) + *buffer_length = bytebuffer_getlength(s); + return s->buf_start; +} + +/** +* Writes a uint8_t value to the buffer +*/ +void +bytebuffer_append_byte(bytebuffer_t *s, const uint8_t val) +{ + LWDEBUGF(2,"Entered bytebuffer_append_byte with value %d", val); + bytebuffer_makeroom(s, 1); + *(s->writecursor)=val; + s->writecursor += 1; + return; +} + +/** +* Writes a uint8_t value to the buffer +*/ +void +bytebuffer_append_bytebuffer(bytebuffer_t *write_to,bytebuffer_t *write_from ) +{ + LWDEBUG(2,"bytebuffer_append_bytebuffer"); + size_t size = bytebuffer_getlength(write_from); + bytebuffer_makeroom(write_to, size); + memcpy(write_to->writecursor, write_from->buf_start, size); + write_to->writecursor += size; + return; +} + +/** +* Writes a signed varInt to the buffer +*/ +void +bytebuffer_append_varint(bytebuffer_t *b, const int64_t val) +{ + bytebuffer_makeroom(b, 16); + b->writecursor += varint_s64_encode_buf(val, b->writecursor); + return; +} + +/** +* Writes a unsigned varInt to the buffer +*/ +void +bytebuffer_append_uvarint(bytebuffer_t *b, const uint64_t val) +{ + bytebuffer_makeroom(b, 16); + b->writecursor += varint_u64_encode_buf(val, b->writecursor); + return; +} + +/** + * Returns the length of the current buffer + */ +size_t +bytebuffer_getlength(const bytebuffer_t *s) +{ + return (size_t)(s->writecursor - s->buf_start); +} + +/* Unused functions */ +#if 0 + +/** Returns a copy of the internal buffer */ +uint8_t* +bytebuffer_get_buffer_copy(const bytebuffer_t *s, size_t *buffer_length) +{ + size_t bufsz = bytebuffer_getlength(s); + uint8_t *buf = lwalloc(bufsz); + memcpy(buf, s->buf_start, bufsz); + if ( buffer_length ) + *buffer_length = bufsz; + return buf; +} + +/** +* Allocate a new bytebuffer_t. Use bytebuffer_destroy to free. +*/ +bytebuffer_t* +bytebuffer_create(void) +{ + LWDEBUG(2,"Entered bytebuffer_create"); + return bytebuffer_create_with_size(BYTEBUFFER_STARTSIZE); +} + + /** +* Allocate a new bytebuffer_t. Use bytebuffer_destroy to free. +*/ +bytebuffer_t* +bytebuffer_create_with_size(size_t size) +{ + LWDEBUGF(2,"Entered bytebuffer_create_with_size %d", size); + bytebuffer_t *s; + + s = lwalloc(sizeof(bytebuffer_t)); + if ( size < BYTEBUFFER_STATICSIZE ) + { + s->capacity = BYTEBUFFER_STATICSIZE; + s->buf_start = s->buf_static; + } + else + { + s->buf_start = lwalloc(size); + s->capacity = size; + } + s->readcursor = s->writecursor = s->buf_start; + memset(s->buf_start,0,s->capacity); + LWDEBUGF(4,"We create a buffer on %p of %d bytes", s->buf_start, s->capacity); + return s; +} + +/** +* Free the bytebuffer_t and all memory managed within it. +*/ +void +bytebuffer_destroy(bytebuffer_t *s) +{ + bytebuffer_destroy_buffer(s); + if ( s ) + lwfree(s); + + return; +} + +/** +* Set the read cursor to the beginning +*/ +void +bytebuffer_reset_reading(bytebuffer_t *s) +{ + s->readcursor = s->buf_start; +} + + /** +* Reset the bytebuffer_t. Useful for starting a fresh string +* without the expense of freeing and re-allocating a new +* bytebuffer_t. +*/ +void +bytebuffer_clear(bytebuffer_t *s) +{ + s->readcursor = s->writecursor = s->buf_start; +} + +/** +* Writes a uint8_t value to the buffer +*/ +void +bytebuffer_append_bulk(bytebuffer_t *s, void * start, size_t size) +{ + LWDEBUGF(2,"bytebuffer_append_bulk with size %d",size); + bytebuffer_makeroom(s, size); + memcpy(s->writecursor, start, size); + s->writecursor += size; + return; +} + +/* +* Writes Integer to the buffer +*/ +void +bytebuffer_append_int(bytebuffer_t *buf, const int val, int swap) +{ + LWDEBUGF(2,"Entered bytebuffer_append_int with value %d, swap = %d", val, swap); + + LWDEBUGF(4,"buf_start = %p and write_cursor=%p", buf->buf_start,buf->writecursor); + char *iptr = (char*)(&val); + int i = 0; + + if ( sizeof(int) != WKB_INT_SIZE ) + { + lwerror("Machine int size is not %d bytes!", WKB_INT_SIZE); + } + + bytebuffer_makeroom(buf, WKB_INT_SIZE); + /* Machine/request arch mismatch, so flip byte order */ + if ( swap) + { + LWDEBUG(4,"Ok, let's do the swaping thing"); + for ( i = 0; i < WKB_INT_SIZE; i++ ) + { + *(buf->writecursor) = iptr[WKB_INT_SIZE - 1 - i]; + buf->writecursor += 1; + } + } + /* If machine arch and requested arch match, don't flip byte order */ + else + { + LWDEBUG(4,"Ok, let's do the memcopying thing"); + memcpy(buf->writecursor, iptr, WKB_INT_SIZE); + buf->writecursor += WKB_INT_SIZE; + } + + LWDEBUGF(4,"buf_start = %p and write_cursor=%p", buf->buf_start,buf->writecursor); + return; + + } + + + + + + /** +* Writes a float64 to the buffer +*/ +void +bytebuffer_append_double(bytebuffer_t *buf, const double val, int swap) +{ + LWDEBUGF(2,"Entered bytebuffer_append_double with value %lf swap = %d", val, swap); + + LWDEBUGF(4,"buf_start = %p and write_cursor=%p", buf->buf_start,buf->writecursor); + char *dptr = (char*)(&val); + int i = 0; + + if ( sizeof(double) != WKB_DOUBLE_SIZE ) + { + lwerror("Machine double size is not %d bytes!", WKB_DOUBLE_SIZE); + } + + bytebuffer_makeroom(buf, WKB_DOUBLE_SIZE); + + /* Machine/request arch mismatch, so flip byte order */ + if ( swap ) + { + LWDEBUG(4,"Ok, let's do the swapping thing"); + for ( i = 0; i < WKB_DOUBLE_SIZE; i++ ) + { + *(buf->writecursor) = dptr[WKB_DOUBLE_SIZE - 1 - i]; + buf->writecursor += 1; + } + } + /* If machine arch and requested arch match, don't flip byte order */ + else + { + LWDEBUG(4,"Ok, let's do the memcopying thing"); + memcpy(buf->writecursor, dptr, WKB_DOUBLE_SIZE); + buf->writecursor += WKB_DOUBLE_SIZE; + } + + LWDEBUG(4,"Return from bytebuffer_append_double"); + return; + + } + + /** +* Reads a signed varInt from the buffer +*/ +int64_t +bytebuffer_read_varint(bytebuffer_t *b) +{ + size_t size; + int64_t val = varint_s64_decode(b->readcursor, b->buf_start + b->capacity, &size); + b->readcursor += size; + return val; +} + + /** +* Reads a unsigned varInt from the buffer +*/ +uint64_t +bytebuffer_read_uvarint(bytebuffer_t *b) +{ + size_t size; + uint64_t val = varint_u64_decode(b->readcursor, b->buf_start + b->capacity, &size); + b->readcursor += size; + return val; +} + + + /** +* Returns a new bytebuffer were both ingoing bytebuffers is merged. +* Caller is responsible for freeing both incoming bytebuffers and resulting bytebuffer +*/ +bytebuffer_t* +bytebuffer_merge(bytebuffer_t **buff_array, int nbuffers) +{ + size_t total_size = 0, current_size, acc_size = 0; + int i; + for ( i = 0; i < nbuffers; i++ ) + { + total_size += bytebuffer_getlength(buff_array[i]); + } + + bytebuffer_t *res = bytebuffer_create_with_size(total_size); + for ( i = 0; i < nbuffers; i++) + { + current_size = bytebuffer_getlength(buff_array[i]); + memcpy(res->buf_start+acc_size, buff_array[i]->buf_start, current_size); + acc_size += current_size; + } + res->writecursor = res->buf_start + total_size; + res->readcursor = res->buf_start; + return res; +} + +#endif diff --git a/mgist-postgis/liblwgeom/bytebuffer.h b/mgist-postgis/liblwgeom/bytebuffer.h new file mode 100644 index 0000000..bf00f8f --- /dev/null +++ b/mgist-postgis/liblwgeom/bytebuffer.h @@ -0,0 +1,77 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2015 Nicklas Avén + * + **********************************************************************/ + + +#ifndef _BYTEBUFFER_H +#define _BYTEBUFFER_H 1 + +#include +#include +#include +#include "varint.h" + +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + +#define BYTEBUFFER_STARTSIZE 512 +#define BYTEBUFFER_STATICSIZE 1024 + +typedef struct +{ + size_t capacity; + uint8_t *buf_start; + uint8_t *writecursor; + uint8_t *readcursor; + uint8_t buf_static[BYTEBUFFER_STATICSIZE]; +} +bytebuffer_t; + +void bytebuffer_init_with_size(bytebuffer_t *b, size_t size); +void bytebuffer_destroy_buffer(bytebuffer_t *s); +void bytebuffer_append_byte(bytebuffer_t *s, const uint8_t val); +void bytebuffer_append_bytebuffer(bytebuffer_t *write_to, bytebuffer_t *write_from); +void bytebuffer_append_varint(bytebuffer_t *s, const int64_t val); +void bytebuffer_append_uvarint(bytebuffer_t *s, const uint64_t val); +size_t bytebuffer_getlength(const bytebuffer_t *s); +lwvarlena_t *bytebuffer_get_buffer_varlena(const bytebuffer_t *s); +const uint8_t* bytebuffer_get_buffer(const bytebuffer_t *s, size_t *buffer_length); + +/* Unused functions */ +#if 0 +void bytebuffer_destroy(bytebuffer_t *s); +bytebuffer_t *bytebuffer_create_with_size(size_t size); +bytebuffer_t *bytebuffer_create(void); +void bytebuffer_clear(bytebuffer_t *s); +uint8_t* bytebuffer_get_buffer_copy(const bytebuffer_t *s, size_t *buffer_length); +uint64_t bytebuffer_read_uvarint(bytebuffer_t *s); +int64_t bytebuffer_read_varint(bytebuffer_t *s); +bytebuffer_t* bytebuffer_merge(bytebuffer_t **buff_array, int nbuffers); +void bytebuffer_reset_reading(bytebuffer_t *s); +void bytebuffer_append_bytebuffer(bytebuffer_t *write_to,bytebuffer_t *write_from); +void bytebuffer_append_bulk(bytebuffer_t *s, void * start, size_t size); +void bytebuffer_append_int(bytebuffer_t *buf, const int val, int swap); +void bytebuffer_append_double(bytebuffer_t *buf, const double val, int swap); +#endif + +#endif /* _BYTEBUFFER_H */ diff --git a/mgist-postgis/liblwgeom/cunit/Makefile.in b/mgist-postgis/liblwgeom/cunit/Makefile.in new file mode 100644 index 0000000..83fc75b --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/Makefile.in @@ -0,0 +1,129 @@ +# ********************************************************************** +# * +# * PostGIS - Spatial Types for PostgreSQL +# * http://postgis.net +# * +# * Copyright 2022 Sandro Santilli +# * Copyright 2008 Paul Ramsey, Mark Cave-Ayland +# * +# * This is free software; you can redistribute and/or modify it under +# * the terms of the GNU General Public Licence. See the COPYING file. +# * +# ********************************************************************** + +top_builddir = @top_builddir@ +builddir = @builddir@ +srcdir = @srcdir@ + +CC=@CC@ +SHELL = @SHELL@ +LIBTOOL = @LIBTOOL@ + +CUNIT_LDFLAGS=@CUNIT_LDFLAGS@ +CUNIT_CPPFLAGS = -I$(srcdir)/.. -I$(builddir)/.. @CUNIT_CPPFLAGS@ @CPPFLAGS@ +CFLAGS=$(CUNIT_CPPFLAGS) @CFLAGS@ +LDFLAGS = @GEOS_LDFLAGS@ @PROJ_LDFLAGS@ $(CUNIT_LDFLAGS) + +VPATH = $(srcdir) + +ifeq ($(CUNIT_WITH_VALGRIND), YES) + LIBTOOL_VALGRIND := valgrind $(CUNIT_VALGRIND_FLAGS) +endif + +# ADD YOUR NEW TEST FILE HERE (1/1) +OBJS= \ + cu_algorithm.o \ + cu_boundary.o \ + cu_buildarea.o \ + cu_bytebuffer.o \ + cu_clean.o \ + cu_print.o \ + cu_minimum_bounding_circle.o \ + cu_misc.o \ + cu_ptarray.o \ + cu_geodetic.o \ + cu_geos.o \ + cu_geos_cluster.o \ + cu_tree.o \ + cu_measures.o \ + cu_effectivearea.o \ + cu_chaikin.o \ + cu_filterm.o \ + cu_node.o \ + cu_clip_by_rect.o \ + cu_gserialized1.o \ + cu_gserialized2.o \ + cu_lwstroke.o \ + cu_split.o \ + cu_stringbuffer.o \ + cu_triangulate.o \ + cu_homogenize.o \ + cu_force_dims.o \ + cu_force_sfs.o \ + cu_out_twkb.o \ + cu_out_wkt.o \ + cu_out_wkb.o \ + cu_out_gml.o \ + cu_out_kml.o \ + cu_out_geojson.o \ + cu_out_svg.o \ + cu_out_encoded_polyline.o \ + cu_surface.o \ + cu_out_x3d.o \ + cu_in_geojson.o \ + cu_in_twkb.o \ + cu_in_wkb.o \ + cu_in_wkt.o \ + cu_in_encoded_polyline.o \ + cu_iterator.o \ + cu_varint.o \ + cu_unionfind.o \ + cu_wrapx.o \ + cu_tester.o + +ifeq (@SFCGAL@,sfcgal) +CFLAGS += @SFCGAL_CPPFLAGS@ +LDFLAGS += @SFCGAL_LDFLAGS@ +OBJS += cu_sfcgal.o +endif + +# If we couldn't find the cunit library then display a helpful message +ifeq ($(CUNIT_LDFLAGS),) +all: requirements_not_met_cunit +check: requirements_not_met_cunit +else + +# Build the unit tester +all: cu_tester + +# Build and run the unit tester +.PHONY: check +check: cu_tester + $(LIBTOOL) --mode=execute $(LIBTOOL_VALGRIND) ./cu_tester + +endif + +# Build the main unit test executable +cu_tester: ../liblwgeom.la $(OBJS) cu_tester.h + $(LIBTOOL) --mode=link $(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) -static ../liblwgeom.la + +# Command to build each of the .o files +$(OBJS): %.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +# Clean target +clean: + rm -f $(OBJS) + rm -f cu_tester + +distclean: clean + rm -f Makefile + +# Requirements message +requirements_not_met_cunit: + @echo + @echo "WARNING:" + @echo + @echo "configure was unable to find CUnit which is required for unit testing." + @echo "In order to enable unit testing, you must install CUnit and then re-run configure." + @echo diff --git a/mgist-postgis/liblwgeom/cunit/README b/mgist-postgis/liblwgeom/cunit/README new file mode 100644 index 0000000..dc6bbca --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/README @@ -0,0 +1,191 @@ +TABLE OF CONTENTS + 1. HOW TO RUN LIBLWGEOM UNIT TESTS + 2. HOW TO ADD A SINGLE TEST + 3. HOW TO ADD AN ENTIRE TEST SUITE + 4. ABOUT TEST OUTPUT + 5. HOW TO ASSERT A FAILURE + + +1. HOW TO RUN LIBLWGEOM UNIT TESTS + +NOTE: We use the CUnit test framework, so you will need to have + this installed before you will be able to build and run the + unit tests. + +If you have already built the rest of the code, then from the +postgis/liblwgeom/cunit directory, run: + +make +./cu_tester + +This will run all the tests. To run just one suite: + +./cu_tester + +To run just one test: + +./cu_tester + +To run selected suites or tests (will be run in the order you specify): + +./cu_tester + +Unit tests for the entire system (including both these unit tests and others +that require postgresql to be running) can be done by running the following +command from the top of the directory tree (postgis directory): + +make check + + +2. HOW TO ADD A SINGLE TEST + +To add a test to an existing suite, follow these steps: + +2.1 Create the test: + +Open the cu_.c file, and add your +new test function. Test functions must have the following signature: + +static void (void) + + must be unique among all tests. A useful naming convention is: + +static void test__(void) + +Although not all existing tests follow that convention. + +For information on the various ASSERT macros you can use, see the CUnit +documentation: + +http://cunit.sourceforge.net/doc/writing_tests.html + +2.2 Add the test to the suite: + +At the bottom of the cu_.c file, below all the test functions, you +will find a block that looks like this (this example is from cu_print.c): + +/* +** Used by the test harness to register the tests in this file. +*/ +void print_suite_setup(void); +void print_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("Print", init_print_suite, clean_print_suite); + PG_ADD_TEST(test_lwprint_default_format); + PG_ADD_TEST(test_lwprint_format_orders); + PG_ADD_TEST(test_lwprint_optional_format); +}; + +Add a new line for your test: + + PG_ADD_TEST(); + +The tests will be run in the order they appear in the list. +CU_TEST_INFO_NULL must always be the last entry. + +2.3 Add any necessary init / cleanup code. + +If your test needs global data created or any other kind of init done +before it runs, or cleanup done after it runs, add the appropriate code +to the init_ or clean_ functions. If the test +suite does not seem to have these functions (they are optional), see +below (3.3) for how to create them. + +Save your changes, run make, and run ./cu_tester, and your test +should be executed. + + + +3. HOW TO ADD AN ENTIRE TEST SUITE + +Do the following steps to create a whole new test suite (new .c file). + +3.1 Create the file. + +Create the new file (remember to add to repository as well). +The naming convention is cu_.c. + +Make sure to import: + +#include "CUnit/Basic.h" +#include "cu_tester.h" + +Now add the file to Makefile.in. Look for "ADD YOUR NEW TEST FILE HERE". +Remember that you'll have to re-run "configure" (from the top directory) +after modifying a .in file. + +3.2 Write the tests. + +Write the test functions as described in section 2. Then at the bottom +of the file, construct the array of tests (example taken from cu_print.c): + +/* +** Used by the test harness to register the tests in this file. +*/ +void print_suite_setup(void); +void print_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("Print", init_print_suite, clean_print_suite); + PG_ADD_TEST(test_lwprint_default_format); + PG_ADD_TEST(test_lwprint_format_orders); + PG_ADD_TEST(test_lwprint_optional_format); +}; + +The naming convention is generally _suite_setup. + +3.3 Construct the init / clean functions and the suite struct. + +Test suites can have initialization and cleanup functions to setup and +dispose of any common or global data. They must have the following +signature: + +static int (void) + +The naming convention is generally: + +static int init_(void) +static int clean_(void) + +3.4 Add your suite to cu_tester.c. + +Edit cu_tester.c. Search for "ADD YOUR SUITE HERE" and add new lines in +the appropriate places, using the _suite_setup name you used in the last step. + +Now run make (remember to run configure first), then ./cu_tester and your +new suite should run. + + + +4. ABOUT TEST OUTPUT + +CUnit does not print much about asserts that fail, just the line number +within the appropriate file. If you need any more detailed output, it +is up to you to printf it. If you discover that all the test suites +are full of individual hacks to do this, please consolidate them into +cu_tester.h / .c, and/or enter a trac issue suggesting an improvement. + + +5. HOW TO ASSERT A FAILURE + +Often you may want to assert that lwerror was called, possibly verifying +that a specific error message was generated. There is now a way to do +this. The global char array "cu_error_msg" will always contain the most +recent error from an lwerror call. You can check it in your test function +(either asserting its length is greater than zero, or looking for a +specific string). Then call cu_error_msg_reset() to clear it when you're +done. It is a good idea to call cu_error_msg_reset prior to your test, +in case a previous test has generated an error that was not cleared. + +Example: + +cu_error_msg_reset(); + +if (strlen(cu_error_msg) > 0) +{ + printf("\nError: generated an error: %s\n", cu_error_msg); + CU_FAIL(); + /* be nice and clean it up for the next test. */ + cu_error_msg_reset(); +} + + diff --git a/mgist-postgis/liblwgeom/cunit/cu_algorithm.c b/mgist-postgis/liblwgeom/cunit/cu_algorithm.c new file mode 100644 index 0000000..44e2187 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_algorithm.c @@ -0,0 +1,1853 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2008 Paul Ramsey + * Copyright 2018 Darafei Praliaskouski, me@komzpa.net + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +/* +** Global variables used by tests below +*/ + +/* Two-point objects */ +POINTARRAY *pa21 = NULL; +POINTARRAY *pa22 = NULL; +LWLINE *l21 = NULL; +LWLINE *l22 = NULL; +/* Parsing support */ +LWGEOM_PARSER_RESULT parse_result; + + +/* +** The suite initialization function. +** Create any re-used objects. +*/ +static int init_cg_suite(void) +{ + pa21 = ptarray_construct(0, 0, 2); + pa22 = ptarray_construct(0, 0, 2); + l21 = lwline_construct(SRID_UNKNOWN, NULL, pa21); + l22 = lwline_construct(SRID_UNKNOWN, NULL, pa22); + return 0; + +} + +/* +** The suite cleanup function. +** Frees any global objects. +*/ +static int clean_cg_suite(void) +{ + if ( l21 ) lwline_free(l21); + if ( l22 ) lwline_free(l22); + return 0; +} + +/* +** Test left/right side. +*/ +static void test_lw_segment_side(void) +{ + int rv = 0; + POINT2D p1, p2, q; + + /* Vertical line at x=0 */ + p1.x = 0.0; + p1.y = 0.0; + p2.x = 0.0; + p2.y = 1.0; + + /* On the left */ + q.x = -2.0; + q.y = 1.5; + rv = lw_segment_side(&p1, &p2, &q); + //printf("left %g\n",rv); + CU_ASSERT(rv < 0); + + /* On the right */ + q.x = 2.0; + rv = lw_segment_side(&p1, &p2, &q); + //printf("right %g\n",rv); + CU_ASSERT(rv > 0); + + /* On the line */ + q.x = 0.0; + rv = lw_segment_side(&p1, &p2, &q); + //printf("on line %g\n",rv); + CU_ASSERT_EQUAL(rv, 0); + +} + +static void test_lw_arc_center(void) +{ +/* double lw_arc_center(const POINT2D *p1, const POINT2D *p2, const POINT2D *p3, POINT2D *result); */ + POINT2D c1; + double d1; + POINT2D p1, p2, p3; + + p1.x = 2047538.600; + p1.y = 7268770.435; + p2.x = 2047538.598; + p2.y = 7268770.435; + p3.x = 2047538.596; + p3.y = 7268770.436; + + d1 = lw_arc_center(&p1, &p2, &p3, &c1); + + CU_ASSERT_DOUBLE_EQUAL(d1, 0.0046097720751, 0.0001); + CU_ASSERT_DOUBLE_EQUAL(c1.x, 2047538.599, 0.001); + CU_ASSERT_DOUBLE_EQUAL(c1.y, 7268770.4395, 0.001); + + // printf("lw_arc_center: (%12.12g, %12.12g) %12.12g\n", c1.x, c1.y, d1); +} + +/* +** Test crossings side. +*/ +static void test_lw_segment_intersects(void) +{ + +#define setpoint(p, x1, y1) {(p).x = (x1); (p).y = (y1);} + + POINT2D p1, p2, q1, q2; + + /* P: Vertical line at x=0 */ + setpoint(p1, 0.0, 0.0); + p1.x = 0.0; + p1.y = 0.0; + p2.x = 0.0; + p2.y = 1.0; + + /* Q: Horizontal line crossing left to right */ + q1.x = -0.5; + q1.y = 0.5; + q2.x = 0.5; + q2.y = 0.5; + CU_ASSERT( lw_segment_intersects(&p1, &p2, &q1, &q2) == SEG_CROSS_RIGHT ); + + /* Q: Horizontal line crossing right to left */ + q1.x = 0.5; + q1.y = 0.5; + q2.x = -0.5; + q2.y = 0.5; + CU_ASSERT( lw_segment_intersects(&p1, &p2, &q1, &q2) == SEG_CROSS_LEFT ); + + /* Q: Horizontal line not crossing right to left */ + q1.x = 0.5; + q1.y = 1.5; + q2.x = -0.5; + q2.y = 1.5; + CU_ASSERT( lw_segment_intersects(&p1, &p2, &q1, &q2) == SEG_NO_INTERSECTION ); + + /* Q: Horizontal line crossing at second vertex right to left */ + q1.x = 0.5; + q1.y = 1.0; + q2.x = -0.5; + q2.y = 1.0; + CU_ASSERT( lw_segment_intersects(&p1, &p2, &q1, &q2) == SEG_NO_INTERSECTION ); + + /* Q: Horizontal line crossing at first vertex right to left */ + q1.x = 0.5; + q1.y = 0.0; + q2.x = -0.5; + q2.y = 0.0; + CU_ASSERT( lw_segment_intersects(&p1, &p2, &q1, &q2) == SEG_CROSS_LEFT ); + + /* Q: Diagonal line with large range crossing at first vertex right to left */ + q1.x = 0.5; + q1.y = 10.0; + q2.x = -0.5; + q2.y = -10.0; + CU_ASSERT( lw_segment_intersects(&p1, &p2, &q1, &q2) == SEG_CROSS_LEFT ); + + /* Q: Diagonal line with large range crossing at second vertex right to left */ + q1.x = 0.5; + q1.y = 11.0; + q2.x = -0.5; + q2.y = -9.0; + CU_ASSERT( lw_segment_intersects(&p1, &p2, &q1, &q2) == SEG_NO_INTERSECTION ); + + /* Q: Horizontal touching from left at second vertex*/ + q1.x = -0.5; + q1.y = 0.5; + q2.x = 0.0; + q2.y = 0.5; + CU_ASSERT( lw_segment_intersects(&p1, &p2, &q1, &q2) == SEG_NO_INTERSECTION ); + + /* Q: Horizontal touching from right at first vertex */ + q1.x = 0.0; + q1.y = 0.5; + q2.x = 0.5; + q2.y = 0.5; + CU_ASSERT( lw_segment_intersects(&p1, &p2, &q1, &q2) == SEG_CROSS_RIGHT ); + + /* Q: Horizontal touching from left and far below on second vertex */ + q1.x = -0.5; + q1.y = -10.5; + q2.x = 0.0; + q2.y = 0.5; + CU_ASSERT( lw_segment_intersects(&p1, &p2, &q1, &q2) == SEG_NO_INTERSECTION ); + + /* Q: Horizontal touching from right and far above on second vertex */ + q1.x = 0.5; + q1.y = 10.5; + q2.x = 0.0; + q2.y = 0.5; + CU_ASSERT( lw_segment_intersects(&p1, &p2, &q1, &q2) == SEG_NO_INTERSECTION ); + + /* Q: Co-linear from top */ + q1.x = 0.0; + q1.y = 10.0; + q2.x = 0.0; + q2.y = 0.5; + CU_ASSERT( lw_segment_intersects(&p1, &p2, &q1, &q2) == SEG_COLINEAR ); + + /* Q: Co-linear from bottom */ + q1.x = 0.0; + q1.y = -10.0; + q2.x = 0.0; + q2.y = 0.5; + CU_ASSERT( lw_segment_intersects(&p1, &p2, &q1, &q2) == SEG_COLINEAR ); + + /* Q: Co-linear contained */ + q1.x = 0.0; + q1.y = 0.4; + q2.x = 0.0; + q2.y = 0.5; + CU_ASSERT( lw_segment_intersects(&p1, &p2, &q1, &q2) == SEG_COLINEAR ); + + /* Q: Horizontal touching at end point from left */ + q1.x = -0.5; + q1.y = 1.0; + q2.x = 0.0; + q2.y = 1.0; + CU_ASSERT( lw_segment_intersects(&p1, &p2, &q1, &q2) == SEG_NO_INTERSECTION ); + + /* Q: Horizontal touching at end point from right */ + q1.x = 0.0; + q1.y = 1.0; + q2.x = 0.0; + q2.y = 0.5; + CU_ASSERT( lw_segment_intersects(&p1, &p2, &q1, &q2) == SEG_COLINEAR ); + + /* Q: Horizontal touching at start point from left */ + q1.x = 0.0; + q1.y = 0.0; + q2.x = -0.5; + q2.y = 0.0; + CU_ASSERT( lw_segment_intersects(&p1, &p2, &q1, &q2) == SEG_CROSS_LEFT ); + + /* Q: Horizontal touching at start point from right */ + q1.x = 0.0; + q1.y = 0.0; + q2.x = 0.5; + q2.y = 0.0; + CU_ASSERT( lw_segment_intersects(&p1, &p2, &q1, &q2) == SEG_CROSS_RIGHT ); + +} + +static void test_lwline_crossing_short_lines(void) +{ + + POINT4D p; + + /* + ** Simple test, two two-point lines + */ + + /* Vertical line from 0,0 to 1,1 */ + p.x = 0.0; + p.y = 0.0; + ptarray_set_point4d(pa21, 0, &p); + p.y = 1.0; + ptarray_set_point4d(pa21, 1, &p); + + /* Horizontal, crossing mid-segment */ + p.x = -0.5; + p.y = 0.5; + ptarray_set_point4d(pa22, 0, &p); + p.x = 0.5; + ptarray_set_point4d(pa22, 1, &p); + + CU_ASSERT( lwline_crossing_direction(l21, l22) == LINE_CROSS_RIGHT ); + + /* Horizontal, crossing at top end vertex (end crossings don't count) */ + p.x = -0.5; + p.y = 1.0; + ptarray_set_point4d(pa22, 0, &p); + p.x = 0.5; + ptarray_set_point4d(pa22, 1, &p); + + CU_ASSERT( lwline_crossing_direction(l21, l22) == LINE_NO_CROSS ); + + /* Horizontal, crossing at bottom end vertex */ + p.x = -0.5; + p.y = 0.0; + ptarray_set_point4d(pa22, 0, &p); + p.x = 0.5; + ptarray_set_point4d(pa22, 1, &p); + + CU_ASSERT( lwline_crossing_direction(l21, l22) == LINE_CROSS_RIGHT ); + + /* Horizontal, no crossing */ + p.x = -0.5; + p.y = 2.0; + ptarray_set_point4d(pa22, 0, &p); + p.x = 0.5; + ptarray_set_point4d(pa22, 1, &p); + + CU_ASSERT( lwline_crossing_direction(l21, l22) == LINE_NO_CROSS ); + + /* Vertical, no crossing */ + p.x = -0.5; + p.y = 0.0; + ptarray_set_point4d(pa22, 0, &p); + p.y = 1.0; + ptarray_set_point4d(pa22, 1, &p); + + CU_ASSERT( lwline_crossing_direction(l21, l22) == LINE_NO_CROSS ); + +} + +static void test_lwline_crossing_long_lines(void) +{ + LWLINE *l51; + LWLINE *l52; + /* + ** More complex test, longer lines and multiple crossings + */ + /* Vertical line with vertices at y integers */ + l51 = (LWLINE*)lwgeom_from_wkt("LINESTRING(0 0, 0 1, 0 2, 0 3, 0 4)", LW_PARSER_CHECK_NONE); + + /* Two crossings at segment midpoints */ + l52 = (LWLINE*)lwgeom_from_wkt("LINESTRING(1 1, -1 1.5, 1 3, 1 4, 1 5)", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwline_crossing_direction(l51, l52) == LINE_MULTICROSS_END_SAME_FIRST_LEFT ); + lwline_free(l52); + + /* One crossing at interior vertex */ + l52 = (LWLINE*)lwgeom_from_wkt("LINESTRING(1 1, 0 1, -1 1, -1 2, -1 3)", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwline_crossing_direction(l51, l52) == LINE_CROSS_LEFT ); + lwline_free(l52); + + /* Two crossings at interior vertices */ + l52 = (LWLINE*)lwgeom_from_wkt("LINESTRING(1 1, 0 1, -1 1, 0 3, 1 3)", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwline_crossing_direction(l51, l52) == LINE_MULTICROSS_END_SAME_FIRST_LEFT ); + lwline_free(l52); + + /* Two crossings, one at the first vertex on at interior vertex */ + l52 = (LWLINE*)lwgeom_from_wkt("LINESTRING(1 0, 0 0, -1 1, 0 3, 1 3)", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwline_crossing_direction(l51, l52) == LINE_MULTICROSS_END_SAME_FIRST_LEFT ); + lwline_free(l52); + + /* Two crossings, one at the first vertex on the next interior vertex */ + l52 = (LWLINE*)lwgeom_from_wkt("LINESTRING(1 0, 0 0, -1 1, 0 1, 1 2)", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwline_crossing_direction(l51, l52) == LINE_MULTICROSS_END_SAME_FIRST_LEFT ); + lwline_free(l52); + + /* Three crossings, two at midpoints, one at vertex */ + l52 = (LWLINE*)lwgeom_from_wkt("LINESTRING(0.5 1, -1 0.5, 1 2, -1 2, -1 3)", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwline_crossing_direction(l51, l52) == LINE_MULTICROSS_END_LEFT ); + lwline_free(l52); + + /* One mid-point co-linear crossing */ + l52 = (LWLINE*)lwgeom_from_wkt("LINESTRING(1 1, 0 1.5, 0 2.5, -1 3, -1 4)", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwline_crossing_direction(l51, l52) == LINE_CROSS_LEFT ); + lwline_free(l52); + + /* One on-vertices co-linear crossing */ + l52 = (LWLINE*)lwgeom_from_wkt("LINESTRING(1 1, 0 1, 0 2, -1 4, -1 4)", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwline_crossing_direction(l51, l52) == LINE_CROSS_LEFT ); + lwline_free(l52); + + /* No crossing, but end on a co-linearity. */ + l52 = (LWLINE*)lwgeom_from_wkt("LINESTRING(1 1, 1 2, 1 3, 0 3, 0 4)", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwline_crossing_direction(l51, l52) == LINE_NO_CROSS ); + lwline_free(l52); + + lwline_free(l51); + +} + + +static void test_lwline_crossing_bugs(void) +{ + LWLINE *l1; + LWLINE *l2; + + l1 = (LWLINE*)lwgeom_from_wkt("LINESTRING(2.99 90.16,71 74,20 140,171 154)", LW_PARSER_CHECK_NONE); + l2 = (LWLINE*)lwgeom_from_wkt("LINESTRING(25 169,89 114,40 70,86 43)", LW_PARSER_CHECK_NONE); + + CU_ASSERT( lwline_crossing_direction(l1, l2) == LINE_MULTICROSS_END_RIGHT ); + lwline_free(l1); + lwline_free(l2); + +} + +static void test_lwpoint_set_ordinate(void) +{ + POINT4D p; + + p.x = 0.0; + p.y = 0.0; + p.z = 0.0; + p.m = 0.0; + + lwpoint_set_ordinate(&p, 'X', 1.5); + CU_ASSERT_EQUAL( p.x, 1.5 ); + + lwpoint_set_ordinate(&p, 'M', 2.5); + CU_ASSERT_EQUAL( p.m, 2.5 ); + + lwpoint_set_ordinate(&p, 'Z', 3.5); + CU_ASSERT_EQUAL( p.z, 3.5 ); + +} + +static void test_lwpoint_get_ordinate(void) +{ + POINT4D p; + + p.x = 10.0; + p.y = 20.0; + p.z = 30.0; + p.m = 40.0; + + CU_ASSERT_EQUAL( lwpoint_get_ordinate(&p, 'X'), 10.0 ); + CU_ASSERT_EQUAL( lwpoint_get_ordinate(&p, 'Y'), 20.0 ); + CU_ASSERT_EQUAL( lwpoint_get_ordinate(&p, 'Z'), 30.0 ); + CU_ASSERT_EQUAL( lwpoint_get_ordinate(&p, 'M'), 40.0 ); + +} + +static void +test_point_interpolate(void) +{ + POINT4D p, q, r = {0, 0, 0, 0}; + int rv = 0; + + p.x = 10.0; + p.y = 20.0; + p.z = 30.0; + p.m = 40.0; + + q.x = 20.0; + q.y = 30.0; + q.z = 40.0; + q.m = 50.0; + + rv = point_interpolate(&p, &q, &r, 1, 1, 'Z', 35.0); + CU_ASSERT_EQUAL(rv, LW_SUCCESS); + CU_ASSERT_EQUAL(r.x, 15.0); + + rv = point_interpolate(&p, &q, &r, 1, 1, 'M', 41.0); + CU_ASSERT_EQUAL(rv, LW_SUCCESS); + CU_ASSERT_EQUAL(r.y, 21.0); + + rv = point_interpolate(&p, &q, &r, 1, 1, 'M', 50.0); + CU_ASSERT_EQUAL(rv, LW_SUCCESS); + CU_ASSERT_EQUAL(r.y, 30.0); + + rv = point_interpolate(&p, &q, &r, 1, 1, 'M', 40.0); + CU_ASSERT_EQUAL(rv, LW_SUCCESS); + CU_ASSERT_EQUAL(r.y, 20.0); +} + +static void test_lwline_interpolate_points(void) +{ + LWLINE* line = lwgeom_as_lwline(lwgeom_from_wkt("LINESTRING ZM (0 0 3 1, 1 1 2 2, 10 10 4 3)", LW_PARSER_CHECK_NONE)); + LWLINE* line2d = lwgeom_as_lwline(lwgeom_from_wkt("LINESTRING (1 1, 3 7, 9 12)", LW_PARSER_CHECK_NONE)); + LWLINE* empty_line = lwgeom_as_lwline(lwgeom_from_wkt("LINESTRING EMPTY", LW_PARSER_CHECK_NONE)); + + POINTARRAY* rpa; + POINT4D pta; + POINT4D ptb; + double eps = 1e-10; + + /* Empty line gives empty point */ + rpa = lwline_interpolate_points(empty_line, 0.5, LW_TRUE); + ASSERT_INT_EQUAL(rpa->npoints, 0); + ptarray_free(rpa); + + /* Get first endpoint when fraction = 0 */ + rpa = lwline_interpolate_points(line, 0, LW_TRUE); + ASSERT_INT_EQUAL(rpa->npoints, 1); + pta = getPoint4d(line->points, 0); + ptb = getPoint4d(rpa, 0); + ASSERT_POINT4D_EQUAL(pta, ptb, eps); + ptarray_free(rpa); + + /* Get last endpoint when fraction = 0 */ + rpa = lwline_interpolate_points(line, 1, LW_TRUE); + ASSERT_INT_EQUAL(rpa->npoints, 1); + pta = getPoint4d(line->points, line->points->npoints - 1); + ptb = getPoint4d(rpa, 0); + ASSERT_POINT4D_EQUAL(pta, ptb, eps); + ptarray_free(rpa); + + /* Interpolate a single point */ + /* First vertex is at 10% */ + rpa = lwline_interpolate_points(line, 0.1, LW_FALSE); + ASSERT_INT_EQUAL(rpa->npoints, 1); + pta = getPoint4d(line->points, 1); + ptb = getPoint4d(rpa, 0); + ASSERT_POINT4D_EQUAL(pta, ptb, eps); + ptarray_free(rpa); + + /* 5% is halfway to first vertex */ + rpa = lwline_interpolate_points(line, 0.05, LW_FALSE); + ASSERT_INT_EQUAL(rpa->npoints, 1); + pta.x = 0.5; + pta.y = 0.5; + pta.m = 1.5; + pta.z = 2.5; + ptb = getPoint4d(rpa, 0); + ASSERT_POINT4D_EQUAL(pta, ptb, eps); + ptarray_free(rpa); + + /* Now repeat points */ + rpa = lwline_interpolate_points(line, 0.4, LW_TRUE); + ASSERT_INT_EQUAL(rpa->npoints, 2); + pta.x = 4; + pta.y = 4; + ptb = getPoint4d(rpa, 0); + ASSERT_POINT2D_EQUAL(pta, ptb, eps); + + pta.x = 8; + pta.y = 8; + ptb = getPoint4d(rpa, 1); + ASSERT_POINT2D_EQUAL(pta, ptb, eps); + ptarray_free(rpa); + + /* Make sure it works on 2D lines */ + rpa = lwline_interpolate_points(line2d, 0.4, LW_TRUE); + ASSERT_INT_EQUAL(rpa->npoints, 2); + CU_ASSERT_FALSE(ptarray_has_z(rpa)); + CU_ASSERT_FALSE(ptarray_has_m(rpa)); + ptarray_free(rpa); + + lwgeom_free(lwline_as_lwgeom(line)); + lwgeom_free(lwline_as_lwgeom(line2d)); + lwgeom_free(lwline_as_lwgeom(empty_line)); +} + +static void +test_lwline_interpolate_point_3d(void) +{ + LWLINE *line; + POINT4D point; + LWPOINT *pt; + + /* Empty line -> Empty point*/ + line = lwline_construct_empty(4326, LW_TRUE, LW_FALSE); + pt = lwline_interpolate_point_3d(line, 0.5); + CU_ASSERT(lwpoint_is_empty(pt)); + CU_ASSERT(lwgeom_has_z(lwpoint_as_lwgeom(pt))); + CU_ASSERT_FALSE(lwgeom_has_m(lwpoint_as_lwgeom(pt))); + lwpoint_free(pt); + lwline_free(line); + + line = lwgeom_as_lwline(lwgeom_from_wkt("LINESTRING Z (0 0 0, 1 1 1, 2 2 2)", LW_PARSER_CHECK_NONE)); + + /* distance = 0 -> first point */ + pt = lwline_interpolate_point_3d(line, 0); + lwpoint_getPoint4d_p(pt, &point); + CU_ASSERT_DOUBLE_EQUAL(point.x, 0, 0.0001); + CU_ASSERT_DOUBLE_EQUAL(point.y, 0, 0.001); + CU_ASSERT_DOUBLE_EQUAL(point.z, 0, 0.001); + lwpoint_free(pt); + + /* distance = 1 -> last point */ + pt = lwline_interpolate_point_3d(line, 1); + lwpoint_getPoint4d_p(pt, &point); + CU_ASSERT_DOUBLE_EQUAL(point.x, 2, 0.0001); + CU_ASSERT_DOUBLE_EQUAL(point.y, 2, 0.001); + CU_ASSERT_DOUBLE_EQUAL(point.z, 2, 0.001); + lwpoint_free(pt); + + /* simple where distance 50% -> second point */ + pt = lwline_interpolate_point_3d(line, 0.5); + lwpoint_getPoint4d_p(pt, &point); + CU_ASSERT_DOUBLE_EQUAL(point.x, 1, 0.0001); + CU_ASSERT_DOUBLE_EQUAL(point.y, 1, 0.001); + CU_ASSERT_DOUBLE_EQUAL(point.z, 1, 0.001); + lwpoint_free(pt); + + /* simple where distance 80% -> between second and last point */ + pt = lwline_interpolate_point_3d(line, 0.8); + lwpoint_getPoint4d_p(pt, &point); + CU_ASSERT_DOUBLE_EQUAL(point.x, 1.6, 0.0001); + CU_ASSERT_DOUBLE_EQUAL(point.y, 1.6, 0.001); + CU_ASSERT_DOUBLE_EQUAL(point.z, 1.6, 0.001); + lwpoint_free(pt); + + lwline_free(line); +} + +static void test_lwline_clip(void) +{ + LWCOLLECTION *c; + LWGEOM *line = NULL; + LWGEOM *l51 = NULL; + char *ewkt; + + /* Vertical line with vertices at y integers */ + l51 = lwgeom_from_wkt("LINESTRING(0 0, 0 1, 0 2, 0 3, 0 4)", LW_PARSER_CHECK_NONE); + + /* Clip in the middle, mid-range. */ + c = lwgeom_clip_to_ordinate_range(l51, 'Y', 1.5, 2.5, 0); + ewkt = lwgeom_to_ewkt((LWGEOM*)c); + //printf("c = %s\n", ewkt); + ASSERT_STRING_EQUAL(ewkt, "MULTILINESTRING((0 1.5,0 2,0 2.5))"); + lwfree(ewkt); + lwcollection_free(c); + + /* Clip off the top. */ + c = lwgeom_clip_to_ordinate_range(l51, 'Y', 3.5, 5.5, 0); + ewkt = lwgeom_to_ewkt((LWGEOM*)c); + //printf("c = %s\n", ewkt); + ASSERT_STRING_EQUAL(ewkt, "MULTILINESTRING((0 3.5,0 4))"); + lwfree(ewkt); + lwcollection_free(c); + + /* Clip off the bottom. */ + c = lwgeom_clip_to_ordinate_range(l51, 'Y', -1.5, 2.5, 0); + ewkt = lwgeom_to_ewkt((LWGEOM*)c); + //printf("c = %s\n", ewkt); + ASSERT_STRING_EQUAL(ewkt, "MULTILINESTRING((0 0,0 1,0 2,0 2.5))"); + lwfree(ewkt); + lwcollection_free(c); + + /* Range holds entire object. */ + c = lwgeom_clip_to_ordinate_range(l51, 'Y', -1.5, 5.5, 0); + ewkt = lwgeom_to_ewkt((LWGEOM*)c); + //printf("c = %s\n", ewkt); + ASSERT_STRING_EQUAL(ewkt, "MULTILINESTRING((0 0,0 1,0 2,0 3,0 4))"); + lwfree(ewkt); + lwcollection_free(c); + + /* Clip on vertices. */ + c = lwgeom_clip_to_ordinate_range(l51, 'Y', 1.0, 2.0, 0); + ewkt = lwgeom_to_ewkt((LWGEOM*)c); + //printf("c = %s\n", ewkt); + ASSERT_STRING_EQUAL(ewkt, "MULTILINESTRING((0 1,0 2))"); + lwfree(ewkt); + lwcollection_free(c); + + /* Clip on vertices off the bottom. */ + c = lwgeom_clip_to_ordinate_range(l51, 'Y', -1.0, 2.0, 0); + ewkt = lwgeom_to_ewkt((LWGEOM*)c); + //printf("c = %s\n", ewkt); + ASSERT_STRING_EQUAL(ewkt, "MULTILINESTRING((0 0,0 1,0 2))"); + lwfree(ewkt); + lwcollection_free(c); + + /* Clip on top. */ + c = lwgeom_clip_to_ordinate_range(l51, 'Y', -1.0, 0.0, 0); + ewkt = lwgeom_to_ewkt((LWGEOM*)c); + //printf("c = %s\n", ewkt); + ASSERT_STRING_EQUAL(ewkt, "GEOMETRYCOLLECTION(POINT(0 0))"); + lwfree(ewkt); + lwcollection_free(c); + + /* ST_LocateBetweenElevations(ST_GeomFromEWKT('LINESTRING(1 2 3, 4 5 6, 6 6 6, 1 1 1)'), 1, 2)) */ + line = lwgeom_from_wkt("LINESTRING(1 2 3, 4 5 6, 6 6 6, 1 1 1)", LW_PARSER_CHECK_NONE); + c = lwgeom_clip_to_ordinate_range(line, 'Z', 1.0, 2.0, 0); + ewkt = lwgeom_to_ewkt((LWGEOM*)c); + ASSERT_STRING_EQUAL(ewkt, "MULTILINESTRING((2 2 2,1 1 1))"); + lwfree(ewkt); + lwcollection_free(c); + lwgeom_free(line); + + /* ST_LocateBetweenElevations('LINESTRING(1 2 3, 4 5 6, 6 6 6, 1 1 1)', 1, 2)) */ + line = lwgeom_from_wkt("LINESTRING(1 2 3, 4 5 6, 6 6 6, 1 1 1)", LW_PARSER_CHECK_NONE); + c = lwgeom_clip_to_ordinate_range(line, 'Z', 1.0, 2.0, 0); + ewkt = lwgeom_to_ewkt((LWGEOM*)c); + //printf("a = %s\n", ewkt); + ASSERT_STRING_EQUAL(ewkt, "MULTILINESTRING((2 2 2,1 1 1))"); + lwfree(ewkt); + lwcollection_free(c); + lwgeom_free(line); + + /* ST_LocateBetweenElevations('LINESTRING(1 2 3, 4 5 6, 6 6 6, 1 1 1)', 1, 1)) */ + line = lwgeom_from_wkt("LINESTRING(1 2 3, 4 5 6, 6 6 6, 1 1 1)", LW_PARSER_CHECK_NONE); + c = lwgeom_clip_to_ordinate_range(line, 'Z', 1.0, 1.0, 0); + ewkt = lwgeom_to_ewkt((LWGEOM*)c); + //printf("b = %s\n", ewkt); + ASSERT_STRING_EQUAL(ewkt, "GEOMETRYCOLLECTION(POINT(1 1 1))"); + lwfree(ewkt); + lwcollection_free(c); + lwgeom_free(line); + + /* ST_LocateBetweenElevations('LINESTRING(1 1 1, 1 2 2)', 1,1) */ + line = lwgeom_from_wkt("LINESTRING(1 1 1, 1 2 2)", LW_PARSER_CHECK_NONE); + c = lwgeom_clip_to_ordinate_range(line, 'Z', 1.0, 1.0, 0); + ewkt = lwgeom_to_ewkt((LWGEOM*)c); + //printf("c = %s\n", ewkt); + ASSERT_STRING_EQUAL(ewkt, "GEOMETRYCOLLECTION(POINT(1 1 1))"); + lwfree(ewkt); + lwcollection_free(c); + lwgeom_free(line); + + lwgeom_free(l51); +} + +static void +test_lwpoly_clip(void) +{ + LWCOLLECTION *c; + LWGEOM *g = NULL; + char *ewkt; + + g = lwgeom_from_wkt( + "POLYGON ((0.51 -0.25, 1.27 -0.14, 1.27 0.25, 0.6 0.3, 0.7 0.7, 1.2 0.7, 0.8 0.5, 1.3 0.4, 1.2 1.2, 0.5 1.2, 0.5 -0.1, 0.3 -0.1, 0.3 1.3, -0.18 1.25, -0.17 -0.25, 0.51 -0.25))", + LW_PARSER_CHECK_NONE); + c = lwgeom_clip_to_ordinate_range(g, 'X', 0.0, 1.0, 0); + + ewkt = lwgeom_to_ewkt((LWGEOM *)c); + // printf("c = %s\n", ewkt); + ASSERT_STRING_EQUAL( + ewkt, + "MULTIPOLYGON(((0.51 -0.25,1 -0.179078947368,1 0.270149253731,0.6 0.3,0.7 0.7,1 0.7,1 0.6,0.8 0.5,1 0.46,1 1.2,0.5 1.2,0.5 -0.1,0.3 -0.1,0.3 1.3,0 1.26875,0 -0.25,0.51 -0.25)))"); + lwfree(ewkt); + lwcollection_free(c); + lwgeom_free(g); + + g = lwgeom_from_wkt( + "MULTIPOLYGON(((0.51 -0.25,1 -0.179078947368,1 0.270149253731,0.6 0.3,0.7 0.7,1 0.7,1 0.6,0.8 0.5,1 0.46,1 1.2,0.5 1.2,0.5 -0.1,0.3 -0.1,0.3 1.3,0 1.26875,0 -0.25,0.51 -0.25)))", + LW_PARSER_CHECK_NONE); + c = lwgeom_clip_to_ordinate_range(g, 'Y', 0.0, 1.0, 0); + + ewkt = lwgeom_to_ewkt((LWGEOM *)c); + //printf("c = %s\n", ewkt); + ASSERT_STRING_EQUAL( + ewkt, + "MULTIPOLYGON(((1 0,1 0.270149253731,0.6 0.3,0.7 0.7,1 0.7,1 0.6,0.8 0.5,1 0.46,1 1,0.5 1,0.5 0,0.3 0,0.3 1,0 1,0 0,1 0)))"); + lwfree(ewkt); + lwcollection_free(c); + lwgeom_free(g); +} + +static void +test_lwtriangle_clip(void) +{ + LWCOLLECTION *c; + LWGEOM *g = NULL; + char *ewkt; + + g = lwgeom_from_wkt("TRIANGLE((0 0 0, 1 1 1, 3 2 2, 0 0 0))", LW_PARSER_CHECK_NONE); + c = lwgeom_clip_to_ordinate_range(g, 'Z', -10.0, 4.0, 0); + + ewkt = lwgeom_to_ewkt((LWGEOM *)c); + // printf("c = %s\n", ewkt); + ASSERT_STRING_EQUAL(ewkt, "TIN(((0 0 0,1 1 1,3 2 2,0 0 0)))"); + lwfree(ewkt); + lwcollection_free(c); + lwgeom_free(g); + + g = lwgeom_from_wkt("TRIANGLE((0 0 0, 1 1 1, 3 2 2, 0 0 0))", LW_PARSER_CHECK_NONE); + c = lwgeom_clip_to_ordinate_range(g, 'Z', 0.0, 1.0, 0); + + ewkt = lwgeom_to_ewkt((LWGEOM *)c); + // printf("c = %s\n", ewkt); + ASSERT_STRING_EQUAL(ewkt, "TIN(((0 0 0,1 1 1,1.5 1 1,0 0 0)))"); + lwfree(ewkt); + lwcollection_free(c); + lwgeom_free(g); + + g = lwgeom_from_wkt("TRIANGLE((0 0 0, 1 1 1, 3 2 3, 0 0 0))", LW_PARSER_CHECK_NONE); + c = lwgeom_clip_to_ordinate_range(g, 'Z', 1.0, 2.0, 0); + + ewkt = lwgeom_to_ewkt((LWGEOM *)c); + // printf("c = %s\n", ewkt); + ASSERT_STRING_EQUAL( + ewkt, + "TIN(((1 1 1,2 1.5 2,2 1.333333333333 2,1 1 1)),((1 1 1,2 1.333333333333 2,1 0.666666666667 1,1 1 1)))"); + lwfree(ewkt); + lwcollection_free(c); + lwgeom_free(g); + + g = lwgeom_from_wkt( + "TIN Z (((0 0 2,0 1 2,-1 2 2,0 0 2)),((0 1 2,0 0 2,0 1 4,0 1 2)),((-1 2 2,0 1 2,1 1 2,-1 2 2)),((0 0 2,-1 2 2,-1 -1 2,0 0 2)),((0 1 4,0 0 2,0 0 4,0 1 4)),((0 1 2,0 1 4,1 1 4,0 1 2)),((1 1 2,0 1 2,1 1 4,1 1 2)),((-1 2 2,1 1 2,2 2 2,-1 2 2)),((-1 -1 2,-1 2 2,-1 -1 -1,-1 -1 2)),((0 0 2,-1 -1 2,1 0 2,0 0 2)),((0 0 4,0 0 2,1 0 2,0 0 4)),((0 1 4,0 0 4,1 0 4,0 1 4)),((1 1 4,0 1 4,1 0 4,1 1 4)),((1 1 2,1 1 4,1 0 4,1 1 2)),((2 2 2,1 1 2,2 -1 2,2 2 2)),((-1 2 2,2 2 2,-1 2 -1,-1 2 2)),((-1 -1 -1,-1 2 2,-1 2 -1,-1 -1 -1)),((-1 -1 2,-1 -1 -1,2 -1 -1,-1 -1 2)),((1 0 2,-1 -1 2,2 -1 2,1 0 2)),((0 0 4,1 0 2,1 0 4,0 0 4)),((1 1 2,1 0 4,1 0 2,1 1 2)),((2 -1 2,1 1 2,1 0 2,2 -1 2)),((2 2 2,2 -1 2,2 2 -1,2 2 2)),((-1 2 -1,2 2 2,2 2 -1,-1 2 -1)),((-1 -1 -1,-1 2 -1,2 2 -1,-1 -1 -1)),((2 -1 -1,-1 -1 -1,2 2 -1,2 -1 -1)),((-1 -1 2,2 -1 -1,2 -1 2,-1 -1 2)),((2 2 -1,2 -1 2,2 -1 -1,2 2 -1)))", + LW_PARSER_CHECK_NONE); + c = lwgeom_clip_to_ordinate_range(g, 'Z', 0.0, DBL_MAX, 0); + + /* Adjust for Rasberry Pi 64-bit (Debian Buster) */ + gridspec grid = {0}; + grid.xsize = grid.ysize = grid.zsize = grid.msize = 1; + lwgeom_grid_in_place((LWGEOM *)c, &grid); + + ewkt = lwgeom_to_ewkt((LWGEOM *)c); + // printf("c = %s\n", ewkt); + ASSERT_STRING_EQUAL( + ewkt, + "TIN(((0 0 2,0 1 2,-1 2 2,0 0 2)),((0 1 2,0 0 2,0 1 4,0 1 2)),((-1 2 2,0 1 2,1 1 2,-1 2 2)),((0 0 2,-1 2 2,-1 -1 2,0 0 2)),((0 1 4,0 0 2,0 0 4,0 1 4)),((0 1 2,0 1 4,1 1 4,0 1 2)),((1 1 2,0 1 2,1 1 4,1 1 2)),((-1 2 2,1 1 2,2 2 2,-1 2 2)),((-1 -1 2,-1 2 2,-1 0 0,-1 -1 2)),((-1 -1 2,-1 0 0,-1 -1 0,-1 -1 2)),((0 0 2,-1 -1 2,1 0 2,0 0 2)),((0 0 4,0 0 2,1 0 2,0 0 4)),((0 1 4,0 0 4,1 0 4,0 1 4)),((1 1 4,0 1 4,1 0 4,1 1 4)),((1 1 2,1 1 4,1 0 4,1 1 2)),((2 2 2,1 1 2,2 -1 2,2 2 2)),((-1 2 2,2 2 2,0 2 0,-1 2 2)),((-1 2 2,0 2 0,-1 2 0,-1 2 2)),((-1 0 0,-1 2 2,-1 2 0,-1 0 0)),((-1 -1 2,-1 -1 0,1 -1 0,-1 -1 2)),((1 0 2,-1 -1 2,2 -1 2,1 0 2)),((0 0 4,1 0 2,1 0 4,0 0 4)),((1 1 2,1 0 4,1 0 2,1 1 2)),((2 -1 2,1 1 2,1 0 2,2 -1 2)),((2 2 2,2 -1 2,2 1 0,2 2 2)),((2 2 2,2 1 0,2 2 0,2 2 2)),((0 2 0,2 2 2,2 2 0,0 2 0)),((-1 -1 2,1 -1 0,2 -1 0,-1 -1 2)),((-1 -1 2,2 -1 0,2 -1 2,-1 -1 2)),((2 1 0,2 -1 2,2 -1 0,2 1 0)))"); + lwfree(ewkt); + lwcollection_free(c); + lwgeom_free(g); +} + +static void test_lwmline_clip(void) +{ + LWCOLLECTION *c; + char *ewkt; + LWGEOM *mline = NULL; + LWGEOM *line = NULL; + + /* + ** Set up the input line. Trivial one-member case. + */ + mline = lwgeom_from_wkt("MULTILINESTRING((0 0,0 1,0 2,0 3,0 4))", LW_PARSER_CHECK_NONE); + + /* Clip in the middle, mid-range. */ + c = lwgeom_clip_to_ordinate_range(mline, 'Y', 1.5, 2.5, 0); + ewkt = lwgeom_to_ewkt((LWGEOM *)c); + ASSERT_STRING_EQUAL(ewkt, "MULTILINESTRING((0 1.5,0 2,0 2.5))"); + lwfree(ewkt); + lwcollection_free(c); + + lwgeom_free(mline); + + /* + ** Set up the input line. Two-member case. + */ + mline = lwgeom_from_wkt("MULTILINESTRING((1 0,1 1,1 2,1 3,1 4), (0 0,0 1,0 2,0 3,0 4))", LW_PARSER_CHECK_NONE); + + /* Clip off the top. */ + c = lwgeom_clip_to_ordinate_range(mline, 'Y', 3.5, 5.5, 0); + ewkt = lwgeom_to_ewkt((LWGEOM *)c); + ASSERT_STRING_EQUAL(ewkt, "MULTILINESTRING((1 3.5,1 4),(0 3.5,0 4))"); + lwfree(ewkt); + lwcollection_free(c); + + lwgeom_free(mline); + + /* + ** Set up staggered input line to create multi-type output. + */ + mline = + lwgeom_from_wkt("MULTILINESTRING((1 0,1 -1,1 -2,1 -3,1 -4), (0 0,0 1,0 2,0 3,0 4))", LW_PARSER_CHECK_NONE); + + /* Clip from 0 upwards.. */ + c = lwgeom_clip_to_ordinate_range(mline, 'Y', 0.0, 2.5, 0); + ewkt = lwgeom_to_ewkt((LWGEOM *)c); + ASSERT_STRING_EQUAL(ewkt, "GEOMETRYCOLLECTION(POINT(1 0),LINESTRING(0 0,0 1,0 2,0 2.5))"); + lwfree(ewkt); + lwcollection_free(c); + + lwgeom_free(mline); + + /* + ** Set up input line from MAC + */ + line = lwgeom_from_wkt("LINESTRING(0 0 0 0,1 1 1 1,2 2 2 2,3 3 3 3,4 4 4 4,3 3 3 5,2 2 2 6,1 1 1 7,0 0 0 8)", + LW_PARSER_CHECK_NONE); + + /* Clip from 3 to 3.5 */ + c = lwgeom_clip_to_ordinate_range(line, 'Z', 3.0, 3.5, 0); + ewkt = lwgeom_to_ewkt((LWGEOM *)c); + ASSERT_STRING_EQUAL(ewkt, "MULTILINESTRING((3 3 3 3,3.5 3.5 3.5 3.5),(3.5 3.5 3.5 4.5,3 3 3 5))"); + lwfree(ewkt); + lwcollection_free(c); + + /* Clip from 2 to 3.5 */ + c = lwgeom_clip_to_ordinate_range(line, 'Z', 2.0, 3.5, 0); + ewkt = lwgeom_to_ewkt((LWGEOM *)c); + ASSERT_STRING_EQUAL(ewkt, + "MULTILINESTRING((2 2 2 2,3 3 3 3,3.5 3.5 3.5 3.5),(3.5 3.5 3.5 4.5,3 3 3 5,2 2 2 6))"); + lwfree(ewkt); + lwcollection_free(c); + + /* Clip from 3 to 4 */ + c = lwgeom_clip_to_ordinate_range(line, 'Z', 3.0, 4.0, 0); + ewkt = lwgeom_to_ewkt((LWGEOM *)c); + ASSERT_STRING_EQUAL(ewkt, "MULTILINESTRING((3 3 3 3,4 4 4 4,3 3 3 5))"); + lwfree(ewkt); + lwcollection_free(c); + + /* Clip from 2 to 3 */ + c = lwgeom_clip_to_ordinate_range(line, 'Z', 2.0, 3.0, 0); + ewkt = lwgeom_to_ewkt((LWGEOM *)c); + ASSERT_STRING_EQUAL(ewkt, "MULTILINESTRING((2 2 2 2,3 3 3 3),(3 3 3 5,2 2 2 6))"); + lwfree(ewkt); + lwcollection_free(c); + + lwgeom_free(line); +} + +static void test_lwline_clip_big(void) +{ + POINTARRAY *pa = ptarray_construct(1, 0, 3); + LWGEOM *line = (LWGEOM *)lwline_construct(SRID_UNKNOWN, NULL, pa); + LWCOLLECTION *c; + char *ewkt; + POINT4D p; + + p.x = 0.0; + p.y = 0.0; + p.z = 0.0; + ptarray_set_point4d(pa, 0, &p); + + p.x = 1.0; + p.y = 1.0; + p.z = 1.0; + ptarray_set_point4d(pa, 1, &p); + + p.x = 2.0; + p.y = 2.0; + p.z = 2.0; + ptarray_set_point4d(pa, 2, &p); + + c = lwgeom_clip_to_ordinate_range(line, 'Z', 0.5, 1.5, 0); + ewkt = lwgeom_to_ewkt((LWGEOM*)c); + //printf("c = %s\n", ewkt); + ASSERT_STRING_EQUAL(ewkt, "MULTILINESTRING((0.5 0.5 0.5,1 1 1,1.5 1.5 1.5))"); + + lwfree(ewkt); + lwcollection_free(c); + lwgeom_free(line); +} + +static void test_geohash_precision(void) +{ + GBOX bbox; + GBOX bounds; + int precision = 0; + gbox_init(&bbox); + gbox_init(&bounds); + + bbox.xmin = 23.0; + bbox.xmax = 23.0; + bbox.ymin = 25.2; + bbox.ymax = 25.2; + precision = lwgeom_geohash_precision(bbox, &bounds); + //printf("\nprecision %d\n",precision); + CU_ASSERT_EQUAL(precision, 20); + + bbox.xmin = 23.0; + bbox.ymin = 23.0; + bbox.xmax = 23.1; + bbox.ymax = 23.1; + precision = lwgeom_geohash_precision(bbox, &bounds); + //printf("precision %d\n",precision); + CU_ASSERT_EQUAL(precision, 3); + + bbox.xmin = 23.0; + bbox.ymin = 23.0; + bbox.xmax = 23.0001; + bbox.ymax = 23.0001; + precision = lwgeom_geohash_precision(bbox, &bounds); + //printf("precision %d\n",precision); + CU_ASSERT_EQUAL(precision, 7); + +} + +static void test_geohash_point(void) +{ + lwvarlena_t *geohash; + + geohash = geohash_point(0, 0, 16); + //printf("\ngeohash %s\n",geohash); + ASSERT_VARLENA_EQUAL(geohash, "s000000000000000"); + lwfree(geohash); + + geohash = geohash_point(90, 0, 16); + //printf("\ngeohash %s\n",geohash); + ASSERT_VARLENA_EQUAL(geohash, "w000000000000000"); + lwfree(geohash); + + geohash = geohash_point(20.012345, -20.012345, 15); + //printf("\ngeohash %s\n",geohash); + ASSERT_VARLENA_EQUAL(geohash, "kkqnpkue9ktbpe5"); + lwfree(geohash); + +} + +static void test_geohash(void) +{ + LWPOINT *lwpoint = NULL; + LWLINE *lwline = NULL; + LWMLINE *lwmline = NULL; + lwvarlena_t *geohash = NULL; + + lwpoint = (LWPOINT*)lwgeom_from_wkt("POINT(23.0 25.2)", LW_PARSER_CHECK_NONE); + geohash = lwgeom_geohash((LWGEOM*)lwpoint,0); + //printf("\ngeohash %s\n",geohash); + ASSERT_VARLENA_EQUAL(geohash, "ss2r77s0du7p2ewb8hmx"); + lwpoint_free(lwpoint); + lwfree(geohash); + + lwpoint = (LWPOINT*)lwgeom_from_wkt("POINT(23.0 25.2 2.0)", LW_PARSER_CHECK_NONE); + geohash = lwgeom_geohash((LWGEOM*)lwpoint,0); + //printf("geohash %s\n",geohash); + ASSERT_VARLENA_EQUAL(geohash, "ss2r77s0du7p2ewb8hmx"); + lwpoint_free(lwpoint); + lwfree(geohash); + + lwline = (LWLINE*)lwgeom_from_wkt("LINESTRING(23.0 23.0,23.1 23.1)", LW_PARSER_CHECK_NONE); + geohash = lwgeom_geohash((LWGEOM*)lwline,0); + //printf("geohash %s\n",geohash); + ASSERT_VARLENA_EQUAL(geohash, "ss0"); + lwline_free(lwline); + lwfree(geohash); + + lwline = (LWLINE*)lwgeom_from_wkt("LINESTRING(23.0 23.0,23.001 23.001)", LW_PARSER_CHECK_NONE); + geohash = lwgeom_geohash((LWGEOM*)lwline,0); + //printf("geohash %s\n",geohash); + ASSERT_VARLENA_EQUAL(geohash, "ss06g7h"); + lwline_free(lwline); + lwfree(geohash); + + lwmline = (LWMLINE*)lwgeom_from_wkt("MULTILINESTRING((23.0 23.0,23.1 23.1),(23.0 23.0,23.1 23.1))", LW_PARSER_CHECK_NONE); + geohash = lwgeom_geohash((LWGEOM*)lwmline,0); + //printf("geohash %s\n",geohash); + ASSERT_VARLENA_EQUAL(geohash, "ss0"); + lwmline_free(lwmline); + lwfree(geohash); +} + +static void test_isclosed(void) +{ + LWGEOM *geom; + + /* LINESTRING */ + + /* Not Closed on 2D */ + geom = lwgeom_from_wkt("LINESTRING(1 2,3 4)", LW_PARSER_CHECK_NONE); + CU_ASSERT(!lwline_is_closed((LWLINE *) geom)); + lwgeom_free(geom); + + /* Closed on 2D */ + geom = lwgeom_from_wkt("LINESTRING(1 2,3 4,1 2)", LW_PARSER_CHECK_NONE); + CU_ASSERT(lwline_is_closed((LWLINE *) geom)); + lwgeom_free(geom); + + /* Not closed on 3D */ + geom = lwgeom_from_wkt("LINESTRING(1 2 3,4 5 6)", LW_PARSER_CHECK_NONE); + CU_ASSERT(!lwline_is_closed((LWLINE *) geom)); + lwgeom_free(geom); + + /* Closed on 3D */ + geom = lwgeom_from_wkt("LINESTRING(1 2 3,4 5 6,1 2 3)", LW_PARSER_CHECK_NONE); + CU_ASSERT(lwline_is_closed((LWLINE *) geom)); + lwgeom_free(geom); + + /* Closed on 4D, even if M is not the same */ + geom = lwgeom_from_wkt("LINESTRING(1 2 3 4,5 6 7 8,1 2 3 0)", LW_PARSER_CHECK_NONE); + CU_ASSERT(lwline_is_closed((LWLINE *) geom)); + lwgeom_free(geom); + + + /* CIRCULARSTRING */ + + /* Not Closed on 2D */ + geom = lwgeom_from_wkt("CIRCULARSTRING(1 2,3 4,5 6)", LW_PARSER_CHECK_NONE); + CU_ASSERT(!lwcircstring_is_closed((LWCIRCSTRING *) geom)); + lwgeom_free(geom); + + /* Closed on 2D */ + geom = lwgeom_from_wkt("CIRCULARSTRING(1 2,3 4,1 2)", LW_PARSER_CHECK_NONE); + CU_ASSERT(lwcircstring_is_closed((LWCIRCSTRING *) geom)); + lwgeom_free(geom); + + /* Not closed on 3D */ + geom = lwgeom_from_wkt("CIRCULARSTRING(1 2 3,4 5 6,7 8 9)", LW_PARSER_CHECK_NONE); + CU_ASSERT(!lwcircstring_is_closed((LWCIRCSTRING *) geom)); + lwgeom_free(geom); + + /* Closed on 3D */ + geom = lwgeom_from_wkt("CIRCULARSTRING(1 2 3,4 5 6,1 2 3)", LW_PARSER_CHECK_NONE); + CU_ASSERT(lwcircstring_is_closed((LWCIRCSTRING *) geom)); + lwgeom_free(geom); + + /* Closed on 4D, even if M is not the same */ + geom = lwgeom_from_wkt("CIRCULARSTRING(1 2 3 4,5 6 7 8,1 2 3 0)", LW_PARSER_CHECK_NONE); + CU_ASSERT(lwcircstring_is_closed((LWCIRCSTRING *) geom)); + lwgeom_free(geom); + + + /* COMPOUNDCURVE */ + + /* Not Closed on 2D */ + geom = lwgeom_from_wkt("COMPOUNDCURVE(CIRCULARSTRING(1 2,3 4,1 2),(1 2,7 8,5 6))", LW_PARSER_CHECK_NONE); + CU_ASSERT(!lwcompound_is_closed((LWCOMPOUND *) geom)); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("COMPOUNDCURVE((1 2,3 4,1 2),CIRCULARSTRING(1 2,7 8,5 6))", LW_PARSER_CHECK_NONE); + CU_ASSERT(!lwcompound_is_closed((LWCOMPOUND *) geom)); + lwgeom_free(geom); + + /* Closed on 2D */ + geom = lwgeom_from_wkt("COMPOUNDCURVE(CIRCULARSTRING(1 2,3 4,5 6), (5 6,7 8,1 2))", LW_PARSER_CHECK_NONE); + CU_ASSERT(lwcompound_is_closed((LWCOMPOUND *) geom)); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("COMPOUNDCURVE((1 2,3 4,5 6),CIRCULARSTRING(5 6,7 8,1 2))", LW_PARSER_CHECK_NONE); + CU_ASSERT(lwcompound_is_closed((LWCOMPOUND *) geom)); + lwgeom_free(geom); + + /* Not Closed on 3D */ + geom = lwgeom_from_wkt("COMPOUNDCURVE(CIRCULARSTRING(1 2 3,4 5 6,1 2 3),(1 2 3,7 8 9,10 11 12))", LW_PARSER_CHECK_NONE); + CU_ASSERT(!lwcompound_is_closed((LWCOMPOUND *) geom)); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("COMPOUNDCURVE((1 2 3,4 5 6,1 2 3),CIRCULARSTRING(1 2 3,7 8 9,10 11 12))", LW_PARSER_CHECK_NONE); + CU_ASSERT(!lwcompound_is_closed((LWCOMPOUND *) geom)); + lwgeom_free(geom); + + /* Closed on 3D */ + geom = lwgeom_from_wkt("COMPOUNDCURVE(CIRCULARSTRING(1 2 3,4 5 6,7 8 9),(7 8 9,10 11 12,1 2 3))", LW_PARSER_CHECK_NONE); + CU_ASSERT(lwcompound_is_closed((LWCOMPOUND *) geom)); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("COMPOUNDCURVE((1 2 3,4 5 6,7 8 9),CIRCULARSTRING(7 8 9,10 11 12,1 2 3))", LW_PARSER_CHECK_NONE); + CU_ASSERT(lwcompound_is_closed((LWCOMPOUND *) geom)); + lwgeom_free(geom); + + /* Closed on 4D, even if M is not the same */ + geom = lwgeom_from_wkt("COMPOUNDCURVE((1 2 3 4,5 6 7 8,9 10 11 12),CIRCULARSTRING(9 10 11 12,13 14 15 16,1 2 3 0))", LW_PARSER_CHECK_NONE); + CU_ASSERT(lwcompound_is_closed((LWCOMPOUND *) geom)); + lwgeom_free(geom); +} + + +static void test_geohash_point_as_int(void) +{ + unsigned int gh; + POINT2D p; + unsigned long long rs; + + p.x = 50; p.y = 35; + gh = geohash_point_as_int(&p); + rs = 3440103613; + CU_ASSERT_EQUAL(gh, rs); + p.x = 140; p.y = 45; + gh = geohash_point_as_int(&p); + rs = 3982480893; + CU_ASSERT_EQUAL(gh, rs); + p.x = 140; p.y = 55; + gh = geohash_point_as_int(&p); + rs = 4166944232; + CU_ASSERT_EQUAL(gh, rs); +} + +static void +test_geohash_bbox(void) +{ + double lat[2], lon[2]; + + /* SELECT ST_GeoHash(ST_SetSRID(ST_MakePoint(-126,48),4326)) */ + decode_geohash_bbox("c0w3hf1s70w3hf1s70w3", lat, lon, 100); + CU_ASSERT_DOUBLE_EQUAL(lat[0], 48, 1e-11); + CU_ASSERT_DOUBLE_EQUAL(lat[1], 48, 1e-11); + CU_ASSERT_DOUBLE_EQUAL(lon[0], -126, 1e-11); + CU_ASSERT_DOUBLE_EQUAL(lon[1], -126, 1e-11); + + cu_error_msg_reset(); + decode_geohash_bbox("@@@@@@", lat, lon, 100); + ASSERT_STRING_EQUAL(cu_error_msg, "decode_geohash_bbox: Invalid character '@'"); +} + +static void test_lwgeom_remove_repeated_points(void) +{ + LWGEOM *g, *gn; + char *ewkt; + char *ewkt_exp; + int modified = LW_FALSE; + + g = lwgeom_from_wkt("LINESTRING(0 0,1 1,1 1,1 1,1.707 1.707)", LW_PARSER_CHECK_NONE); + modified = lwgeom_remove_repeated_points_in_place(g, 0.001); + ASSERT_INT_EQUAL(modified, LW_TRUE); + ewkt = lwgeom_to_ewkt(g); + ASSERT_STRING_EQUAL(ewkt, "LINESTRING(0 0,1 1,1.707 1.707)"); + lwgeom_free(g); + lwfree(ewkt); + + g = lwgeom_from_wkt("MULTIPOINT(0 0, 10 0, 10 10, 10 10, 0 10, 0 10, 0 10, 0 0, 0 0, 0 0, 5 5, 0 0, 5 5)", LW_PARSER_CHECK_NONE); + modified = lwgeom_remove_repeated_points_in_place(g, 1); + ASSERT_INT_EQUAL(modified, LW_TRUE); + gn = lwgeom_normalize(g); + ewkt = lwgeom_to_ewkt(gn); + ASSERT_STRING_EQUAL(ewkt, "MULTIPOINT(10 10,10 0,5 5,0 10,0 0)"); + lwgeom_free(g); + lwgeom_free(gn); + lwfree(ewkt); + + g = lwgeom_from_wkt("LINESTRING(1612830.15445 4841287.12672,1612830.15824 4841287.12674,1612829.98813 4841274.56198)", LW_PARSER_CHECK_NONE); + modified = lwgeom_remove_repeated_points_in_place(g, 0.01); + ASSERT_INT_EQUAL(modified, LW_TRUE); + ewkt = lwgeom_to_ewkt(g); + ASSERT_STRING_EQUAL(ewkt, "LINESTRING(1612830.15445 4841287.12672,1612829.98813 4841274.56198)"); + lwgeom_free(g); + lwfree(ewkt); + + /* remove points */ + g = lwgeom_from_wkt("MULTIPOINT(0 0,10 0,10 10,10 10,0 10,0 10,0 10,0 0,0 0,0 0,5 5,5 5,5 8,8 8,8 8,8 8,8 5,8 5,5 5,5 5,5 5,5 5,5 5,50 50,50 50,50 50,50 60,50 60,50 60,60 60,60 50,60 50,50 50,55 55,55 58,58 58,58 55,58 55,55 55)", LW_PARSER_CHECK_NONE); + modified = lwgeom_remove_repeated_points_in_place(g, 1); + ASSERT_INT_EQUAL(modified, LW_TRUE); + gn = lwgeom_normalize(g); + ewkt = lwgeom_to_ewkt(gn); + lwgeom_free(g); + lwgeom_free(gn); + /* build expected and normalize */ + g = lwgeom_from_wkt("MULTIPOINT(0 0,0 10,5 5,5 8,8 5,8 8,10 0,10 10,50 50,50 60,55 55,55 58,58 55,58 58,60 50,60 60)", LW_PARSER_CHECK_NONE); + gn = lwgeom_normalize(g); + ewkt_exp = lwgeom_to_ewkt(gn); + ASSERT_STRING_EQUAL(ewkt, ewkt_exp); + lwgeom_free(g); + lwgeom_free(gn); + lwfree(ewkt); + lwfree(ewkt_exp); + + g = lwgeom_from_wkt("POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))", LW_PARSER_CHECK_NONE); + modified = lwgeom_remove_repeated_points_in_place(g, 10000); + ASSERT_INT_EQUAL(modified, LW_TRUE); + ewkt = lwgeom_to_ewkt(g); + ASSERT_STRING_EQUAL(ewkt, "POLYGON((0 0,1 1,1 0,0 0))"); + lwgeom_free(g); + lwfree(ewkt); + + // Test the return value (modified or not) + g = lwgeom_from_wkt("POINT(0 0)", LW_PARSER_CHECK_NONE); + modified = lwgeom_remove_repeated_points_in_place(g, 10000); + ASSERT_INT_EQUAL(modified, LW_FALSE); + lwgeom_free(g); + + g = lwgeom_from_wkt("TRIANGLE((0 0, 5 0, 3 3, 0 0))", LW_PARSER_CHECK_NONE); + modified = lwgeom_remove_repeated_points_in_place(g, 10000); + ASSERT_INT_EQUAL(modified, LW_FALSE); + lwgeom_free(g); + + g = lwgeom_from_wkt("POLYGON((0 0,0 1,1 1,1 0,0 0))", LW_PARSER_CHECK_NONE); + modified = lwgeom_remove_repeated_points_in_place(g, 0.1); + ASSERT_INT_EQUAL(modified, LW_FALSE); + ewkt = lwgeom_to_ewkt(g); + ASSERT_STRING_EQUAL(ewkt, "POLYGON((0 0,0 1,1 1,1 0,0 0))"); + lwgeom_free(g); + lwfree(ewkt); + + g = lwgeom_from_wkt("POLYGON((0 0,0 1,1 1,1 0,0 0), (0.4 0.4, 0.4 0.4, 0.4 0.5, 0.5 0.5, 0.5 0.4, 0.4 0.4))", + LW_PARSER_CHECK_NONE); + modified = lwgeom_remove_repeated_points_in_place(g, 0.1); + ASSERT_INT_EQUAL(modified, LW_TRUE); + ewkt = lwgeom_to_ewkt(g); + ASSERT_STRING_EQUAL(ewkt, "POLYGON((0 0,0 1,1 1,1 0,0 0),(0.4 0.4,0.5 0.5,0.5 0.4,0.4 0.4))"); + lwgeom_free(g); + lwfree(ewkt); + + g = lwgeom_from_wkt("GEOMETRYCOLLECTION(POINT(2 0),POLYGON((0 0,1 0,1 1,0 1,0 0)))", LW_PARSER_CHECK_NONE); + modified = lwgeom_remove_repeated_points_in_place(g, 0.1); + ASSERT_INT_EQUAL(modified, LW_FALSE); + ewkt = lwgeom_to_ewkt(g); + ASSERT_STRING_EQUAL(ewkt, "GEOMETRYCOLLECTION(POINT(2 0),POLYGON((0 0,1 0,1 1,0 1,0 0)))"); + lwgeom_free(g); + lwfree(ewkt); + + g = lwgeom_from_wkt("GEOMETRYCOLLECTION(POINT(2 0),POLYGON((0 0, 0 1, 1 1, 1 0, 0 0)))", LW_PARSER_CHECK_NONE); + modified = lwgeom_remove_repeated_points_in_place(g, 10000); + ASSERT_INT_EQUAL(modified, LW_TRUE); + ewkt = lwgeom_to_ewkt(g); + ASSERT_STRING_EQUAL(ewkt, "GEOMETRYCOLLECTION(POINT(2 0),POLYGON((0 0,1 1,1 0,0 0)))"); + lwgeom_free(g); + lwfree(ewkt); + + g = lwgeom_from_wkt("GEOMETRYCOLLECTION(POLYGON((0 0, 0 1, 1 1, 1 0, 0 0)),POINT(2 0))", LW_PARSER_CHECK_NONE); + modified = lwgeom_remove_repeated_points_in_place(g, 10000); + ASSERT_INT_EQUAL(modified, LW_TRUE); + ewkt = lwgeom_to_ewkt(g); + ASSERT_STRING_EQUAL(ewkt, "GEOMETRYCOLLECTION(POLYGON((0 0,1 1,1 0,0 0)),POINT(2 0))"); + lwgeom_free(g); + lwfree(ewkt); +} + +static void test_lwgeom_simplify(void) +{ + LWGEOM *l; + LWGEOM *g; + char *ewkt; + + /* Simplify but only so far... */ + g = lwgeom_from_wkt("LINESTRING(0 0, 1 0, 1 1, 0 1, 0 0)", LW_PARSER_CHECK_NONE); + l = lwgeom_simplify(g, 10, LW_TRUE); + ewkt = lwgeom_to_ewkt(l); + ASSERT_STRING_EQUAL(ewkt, "LINESTRING(0 0,0 0)"); + lwgeom_free(g); + lwgeom_free(l); + lwfree(ewkt); + + /* Simplify but only so far... */ + g = lwgeom_from_wkt("POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))", LW_PARSER_CHECK_NONE); + l = lwgeom_simplify(g, 10, LW_TRUE); + ewkt = lwgeom_to_ewkt(l); + ASSERT_STRING_EQUAL(ewkt, "POLYGON((0 0,1 0,1 1,0 0))"); + lwgeom_free(g); + lwgeom_free(l); + lwfree(ewkt); + + /* Simplify and collapse */ + g = lwgeom_from_wkt("LINESTRING(0 0, 1 0, 1 1, 0 1, 0 0)", LW_PARSER_CHECK_NONE); + l = lwgeom_simplify(g, 10, LW_FALSE); + CU_ASSERT_EQUAL(l, NULL); + lwgeom_free(g); + lwgeom_free(l); + + /* Simplify and collapse */ + g = lwgeom_from_wkt("POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))", LW_PARSER_CHECK_NONE); + l = lwgeom_simplify(g, 10, LW_FALSE); + CU_ASSERT_EQUAL(l, NULL); + lwgeom_free(g); + lwgeom_free(l); + + /* Not simplifiable */ + g = lwgeom_from_wkt("LINESTRING(0 0, 50 1.00001, 100 0)", LW_PARSER_CHECK_NONE); + l = lwgeom_simplify(g, 1.0, LW_FALSE); + ewkt = lwgeom_to_ewkt(l); + ASSERT_STRING_EQUAL(ewkt, "LINESTRING(0 0,50 1.00001,100 0)"); + lwgeom_free(g); + lwgeom_free(l); + lwfree(ewkt); + + /* Simplifiable */ + g = lwgeom_from_wkt("LINESTRING(0 0,50 0.99999,100 0)", LW_PARSER_CHECK_NONE); + l = lwgeom_simplify(g, 1.0, LW_FALSE); + ewkt = lwgeom_to_ewkt(l); + ASSERT_STRING_EQUAL(ewkt, "LINESTRING(0 0,100 0)"); + lwgeom_free(g); + lwgeom_free(l); + lwfree(ewkt); + + /* POLYGON with multiple inner rings*/ + g = lwgeom_from_wkt( + "POLYGON(" + "(0 0, 100 0, 100 100, 0 100, 0 0)," + "(1 1, 1 5, 5 5, 5 1, 1 1)," + "(20 20, 20 40, 40 40, 40 20, 20 20)" + ")", + LW_PARSER_CHECK_NONE); + l = lwgeom_simplify(g, 10, LW_FALSE); + ewkt = lwgeom_to_ewkt(l); + ASSERT_STRING_EQUAL(ewkt, "POLYGON((0 0,100 0,100 100,0 100,0 0),(20 20,20 40,40 40,40 20,20 20))"); + lwgeom_free(g); + lwgeom_free(l); + lwfree(ewkt); + + /* Reorder inner rings: Same result */ + g = lwgeom_from_wkt( + "POLYGON(" + "(0 0, 100 0, 100 100, 0 100, 0 0)," + "(20 20, 20 40, 40 40, 40 20, 20 20)," + "(1 1, 1 5, 5 5, 5 1, 1 1)" + ")", + LW_PARSER_CHECK_NONE); + l = lwgeom_simplify(g, 10, LW_FALSE); + ewkt = lwgeom_to_ewkt(l); + ASSERT_STRING_EQUAL(ewkt, "POLYGON((0 0,100 0,100 100,0 100,0 0),(20 20,20 40,40 40,40 20,20 20))"); + lwgeom_free(g); + lwgeom_free(l); + lwfree(ewkt); + + g = lwgeom_from_wkt( + "POLYGON(" + "(0 0, 100 0, 100 100, 0 100, 0 0)," + "(20 20, 20 40, 40 40, 40 20, 20 20)," + "(1 1, 1 5, 5 5, 5 1, 1 1)" + ")", + LW_PARSER_CHECK_NONE); + l = lwgeom_simplify(g, 100, LW_FALSE); + CU_ASSERT_EQUAL(l, NULL); + lwgeom_free(g); + lwgeom_free(l); +} + + +static void do_median_dims_check(char* wkt, int expected_dims) +{ + LWGEOM* g = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_NONE); + LWPOINT* result = lwgeom_median(g, 1e-8, 100, LW_FALSE); + + CU_ASSERT_EQUAL(expected_dims, lwgeom_ndims((LWGEOM*) result)); + + lwgeom_free(g); + lwpoint_free(result); +} + +static void test_median_handles_3d_correctly(void) +{ + do_median_dims_check("MULTIPOINT ((1 3), (4 7), (2 9), (0 4), (2 2))", 2); + do_median_dims_check("MULTIPOINT Z ((1 3 4), (4 7 8), (2 9 1), (0 4 4), (2 2 3))", 3); + do_median_dims_check("MULTIPOINT M ((1 3 4), (4 7 8), (2 9 1), (0 4 4), (2 2 3))", 2); + do_median_dims_check("MULTIPOINT ZM ((1 3 4 5), (4 7 8 6), (2 9 1 7), (0 4 4 8), (2 2 3 9))", 3); +} + +static double +test_weighted_distance(const POINT4D* curr, const POINT4D* points, uint32_t npoints) +{ + double distance = 0.0; + uint32_t i; + for (i = 0; i < npoints; i++) + { + distance += distance3d_pt_pt((POINT3D*)curr, (POINT3D*)&points[i]) * points[i].m; + } + + return distance; +} + +static void do_median_test(char* input, char* expected, int fail_if_not_converged, int iter_count) +{ + cu_error_msg_reset(); + LWGEOM* g = lwgeom_from_wkt(input, LW_PARSER_CHECK_NONE); + LWPOINT* expected_result = NULL; + POINT4D actual_pt; + POINT4D expected_pt; + const double tolerance = FP_TOLERANCE / 10.0; + + LWPOINT* result = lwgeom_median(g, tolerance, iter_count, fail_if_not_converged); + int passed = LW_FALSE; + + if (expected != NULL) + { + expected_result = lwgeom_as_lwpoint(lwgeom_from_wkt(expected, LW_PARSER_CHECK_NONE)); + lwpoint_getPoint4d_p(expected_result, &expected_pt); + } + if (result != NULL) + { + lwpoint_getPoint4d_p(result, &actual_pt); + } + + if (result != NULL && expected != NULL) /* got something, expecting something */ + { + passed = LW_TRUE; + passed = passed && lwgeom_is_empty((LWGEOM*) expected_result) == lwgeom_is_empty((LWGEOM*) result); + passed = passed && (lwgeom_has_z((LWGEOM*) expected_result) == lwgeom_has_z((LWGEOM*) result)); + + if (passed && !lwgeom_is_empty((LWGEOM*) result)) + { + if (g->type == POINTTYPE) + { + passed &= fabs(actual_pt.x - expected_pt.x) < tolerance; + passed &= fabs(actual_pt.y - expected_pt.y) < tolerance; + passed &= (!lwgeom_has_z((LWGEOM*) expected_result) || fabs(actual_pt.z - expected_pt.z) < tolerance); + passed &= (!lwgeom_has_m((LWGEOM*) expected_result) || fabs(actual_pt.m - expected_pt.m) < tolerance); + } + else + { + /* Check that the difference between the obtained geometric + median and the expected point is within tolerance */ + uint32_t npoints = 1; + int input_empty = LW_TRUE; + POINT4D* points = lwmpoint_extract_points_4d(lwgeom_as_lwmpoint(g), &npoints, &input_empty); + double distance_expected = test_weighted_distance(&expected_pt, points, npoints); + double distance_result = test_weighted_distance(&actual_pt, points, npoints); + + passed = distance_result <= (1.0 + tolerance) * distance_expected; + if (!passed) + { + printf("Diff: Got %.10f Expected %.10f\n", distance_result, distance_expected); + } + lwfree(points); + } + } + + if (!passed) + { + printf("median_test input %s (parsed %s) expected %s got %s\n", + input, lwgeom_to_ewkt(g), + lwgeom_to_ewkt((LWGEOM*) expected_result), + lwgeom_to_ewkt((LWGEOM*) result)); + } + + } + else if (result == NULL && expected == NULL) /* got nothing, expecting nothing */ + { + passed = LW_TRUE; + } + else if (result != NULL && expected == NULL) /* got something, expecting nothing */ + { + passed = LW_FALSE; + printf("median_test input %s (parsed %s) expected NULL got %s\n", input, lwgeom_to_ewkt(g), lwgeom_to_ewkt((LWGEOM*) result)); + } + else if (result == NULL && expected != NULL) /* got nothing, expecting something */ + { + passed = LW_FALSE; + printf("%s", cu_error_msg); + printf("median_test input %s (parsed %s) expected %s got NULL\n", input, lwgeom_to_ewkt(g), lwgeom_to_ewkt((LWGEOM*) expected_result)); + } + + CU_ASSERT_TRUE(passed); + + lwgeom_free(g); + lwpoint_free(expected_result); + lwpoint_free(result); +} + +static void test_median_robustness(void) +{ + /* A simple implementation of Weiszfeld's algorithm will fail if the median is equal + * to any one of the inputs, during any iteration of the algorithm. + * + * Because the algorithm uses the centroid as a starting point, this situation will + * occur in the test case below. + */ + do_median_test("MULTIPOINT ((0 -1), (0 0), (0 1))", "POINT (0 0)", LW_TRUE, 1000); + + /* Same as above but 3D, and shifter */ + do_median_test("MULTIPOINT ((1 -1 3), (1 0 2), (2 1 1))", "POINT (1 0 2)", LW_TRUE, 1000); + + /* Starting point is duplicated */ + do_median_test("MULTIPOINT ((0 -1), (0 0), (0 0), (0 1))", "POINT (0 0)", LW_TRUE, 1000); + + /* Cube */ + do_median_test("MULTIPOINT ((10 10 10), (10 20 10), (20 10 10), (20 20 10), (10 10 20), (10 20 20), (20 10 20), (20 20 20))", + "POINT (15 15 15)", LW_TRUE, 1000); + + /* Some edge cases */ + do_median_test("POINT (7 6)", "POINT (7 6)", LW_TRUE, 1000); + do_median_test("POINT (7 6 2)", "POINT (7 6 2)", LW_TRUE, 1000); + do_median_test("MULTIPOINT ((7 6 2), EMPTY)", "POINT (7 6 2)", LW_TRUE, 1000); + + /* Empty input */ + do_median_test("MULTIPOINT EMPTY", "POINT EMPTY", LW_FALSE, 1000); + do_median_test("MULTIPOINT (EMPTY)", "POINT EMPTY", LW_FALSE, 1000); + do_median_test("MULTIPOINT EMPTY", "POINT EMPTY", LW_TRUE, 1000); + do_median_test("MULTIPOINT (EMPTY)", "POINT EMPTY", LW_TRUE, 1000); + do_median_test("MULTIPOINT ZM (1 -1 3 1, 1 0 2 7, 2 1 1 1, EMPTY)", "POINT (1 0 2)", LW_TRUE, 1000); + + /* Weighted input */ + do_median_test("MULTIPOINT ZM (1 -1 3 1, 1 0 2 7, 2 1 1 1)", "POINT (1 0 2)", LW_TRUE, 1000); + do_median_test("MULTIPOINT ZM (-1 1 -3 1, -1 0 -2 7, -2 -1 -1 1)", "POINT (-1 0 -2)", LW_TRUE, 1000); + do_median_test("MULTIPOINT ZM (-1 1 -3 1, -1 0 -2 7, -2 -1 -1 0.5, -2 -1 -1 0.5)", "POINT (-1 0 -2)", LW_TRUE, 1000); + + /* Point that is replaced by two half-weighted */ + do_median_test("MULTIPOINT ZM ((0 -1 0 1), (0 0 0 1), (0 1 0 0.5), (0 1 0 0.5))", "POINT (0 0 0)", LW_TRUE, 1000); + /* Point is doubled and then erased by negative weight */ + do_median_test("MULTIPOINT ZM ((1 -1 3 1), (1 0 2 7), (2 1 1 2), (2 1 1 -1))", NULL, LW_TRUE, 1000); + do_median_test("MULTIPOINT ZM ((1 -1 3 1), (1 0 2 7), (2 1 1 2), (2 1 1 -1))", NULL, LW_FALSE, 1000); + /* Weightless input won't converge */ + do_median_test("MULTIPOINT ZM ((0 -1 0 0), (0 0 0 0), (0 0 0 0), (0 1 0 0))", NULL, LW_FALSE, 1000); + do_median_test("MULTIPOINT ZM ((0 -1 0 0), (0 0 0 0), (0 0 0 0), (0 1 0 0))", NULL, LW_TRUE, 1000); + /* Negative weight won't converge */ + do_median_test("MULTIPOINT ZM ((0 -1 0 -1), (0 0 0 -1), (0 1 0 -1))", NULL, LW_FALSE, 1000); + do_median_test("MULTIPOINT ZM ((0 -1 0 -1), (0 0 0 -1), (0 1 0 -1))", NULL, LW_TRUE, 1000); + + /* Bind convergence too tightly */ + do_median_test("MULTIPOINT ((0 0), (1 1), (0 1), (2 2))", "POINT(0.75 1.0)", LW_FALSE, 0); + do_median_test("MULTIPOINT ((0 0), (1 1), (0 1), (2 2))", NULL, LW_TRUE, 1); + /* Unsupported geometry type */ + do_median_test("POLYGON((1 0,0 1,1 2,2 1,1 0))", NULL, LW_TRUE, 1000); + do_median_test("POLYGON((1 0,0 1,1 2,2 1,1 0))", NULL, LW_FALSE, 1000); + + /* Median point is included */ + do_median_test("MULTIPOINT ZM (" + "(1480 0 200 100)," + "(620 0 200 100)," + "(1000 0 -200 100)," + "(1000 0 -590 100)," + "(1025 0 65 100)," + "(1025 0 -65 100)" + ")", + "POINT (1025 0 -65)", LW_TRUE, 10000); + +#if 0 + /* Leads to invalid result (0 0 0) with 80bit (fmulp + faddp) precision. ok with 64 bit float ops */ + do_median_test("MULTIPOINT ZM (" + "(0 0 20000 0.5)," + "(0 0 59000 0.5)," + "(0 -3000 -3472.22222222222262644208967685699462890625 1)," + "(0 3000 3472.22222222222262644208967685699462890625 1)," + "(0 0 -1644.736842105263121993630193173885345458984375 1)," + "(0 0 1644.736842105263121993630193173885345458984375 1)," + "(0 48000 -20000 1.3)," + "(0 -48000 -20000 1.3)" + ")", + "POINT (0 0 0)", LW_TRUE, 10000); +#endif + +#if 0 + /* Leads to invalid result (0 0 0) with 64bit (vfmadd231sd) precision. Ok with 80 bit float ops */ + do_median_test("MULTIPOINT ZM (" + "(0 0 20000 0.5)," + "(0 0 59000 0.5)," + "(0 -3000 -3472.22222222222262644208967685699462890625 1)," + "(0 3000 3472.22222222222262644208967685699462890625 1)," + "(0 -0.00000000000028047739569477638384522295466033823196 -1644.736842105263121993630193173885345458984375 1)," + "(0 0.00000000000028047739569477638384522295466033823196 1644.736842105263121993630193173885345458984375 1)," + "(0 48000 -20000 1.3)," + "(0 -48000 -20000 1.3)" + ")", + "POINT (0 0 0)", LW_TRUE, 10000); +#endif +} + +static void test_point_density(void) +{ + LWGEOM *geom; + LWMPOINT *mpt; + LWMPOINT *mpt2; + LWPOINT *pt; + LWPOINT *pt2; + int eq, i; + // char *ewkt; + + /* POLYGON */ + geom = lwgeom_from_wkt("POLYGON((0 0,1 0,1 1,0 1,0 0))", LW_PARSER_CHECK_NONE); + mpt = lwgeom_to_points(geom, 100, 0); /* Set a zero seed to base it on Unix time and process ID */ + CU_ASSERT_EQUAL(mpt->ngeoms,100); + + /* Run a second time with a zero seed to get a different multipoint sequence */ + mpt2 = lwgeom_to_points(geom, 100, 0); + eq = 0; + for (i = 0; i < 100; i++) + { + pt = (LWPOINT*)mpt->geoms[i]; + pt2 = (LWPOINT*)mpt2->geoms[i]; + if (lwpoint_get_x(pt) == lwpoint_get_x(pt2) && lwpoint_get_y(pt) == lwpoint_get_y(pt2)) + eq++; + } + CU_ASSERT_EQUAL(eq, 0); + lwmpoint_free(mpt); + lwmpoint_free(mpt2); + pt = NULL; + pt2 = NULL; + + /* Set seed to get a deterministic sequence */ + mpt = lwgeom_to_points(geom, 1000, 12345); + + /* Check to find a different multipoint sequence with different seed */ + mpt2 = lwgeom_to_points(geom, 1000, 54321); + eq = 0; + for (i = 0; i < 1000; i++) + { + pt = (LWPOINT*)mpt->geoms[i]; + pt2 = (LWPOINT*)mpt2->geoms[i]; + if (lwpoint_get_x(pt) == lwpoint_get_x(pt2) && lwpoint_get_y(pt) == lwpoint_get_y(pt2)) + eq++; + } + CU_ASSERT_EQUAL(eq, 0); + lwmpoint_free(mpt2); + pt = NULL; + pt2 = NULL; + + /* Check to find an identical multipoint sequence with same seed */ + mpt2 = lwgeom_to_points(geom, 1000, 12345); + eq = 0; + for (i = 0; i < 1000; i++) + { + pt = (LWPOINT*)mpt->geoms[i]; + pt2 = (LWPOINT*)mpt2->geoms[i]; + if (lwpoint_get_x(pt) == lwpoint_get_x(pt2) && lwpoint_get_y(pt) == lwpoint_get_y(pt2)) + eq++; + } + CU_ASSERT_EQUAL(eq, 1000); + lwmpoint_free(mpt2); + pt = NULL; + pt2 = NULL; + + + /* Check if the 1000th point is the expected value. + * Note that if the RNG is not portable, this test may fail. */ + pt = (LWPOINT*)mpt->geoms[999]; + // ewkt = lwgeom_to_ewkt((LWGEOM*)pt); + // printf("%s\n", ewkt); + // lwfree(ewkt); + CU_ASSERT_DOUBLE_EQUAL(lwpoint_get_x(pt), 0.801167838758, 1e-11); + CU_ASSERT_DOUBLE_EQUAL(lwpoint_get_y(pt), 0.345281131175, 1e-11); + lwmpoint_free(mpt); + pt = NULL; + + mpt = lwgeom_to_points(geom, 0, 0); + CU_ASSERT_EQUAL(mpt, NULL); + lwmpoint_free(mpt); + + lwgeom_free(geom); + + /* MULTIPOLYGON */ + geom = lwgeom_from_wkt("MULTIPOLYGON(((10 0,0 10,10 20,20 10,10 0)),((0 0,5 0,5 5,0 5,0 0)))", LW_PARSER_CHECK_NONE); + + mpt = lwgeom_to_points(geom, 1000, 0); + CU_ASSERT_EQUAL(mpt->ngeoms,1000); + lwmpoint_free(mpt); + + mpt = lwgeom_to_points(geom, 1, 0); + CU_ASSERT_EQUAL(mpt->ngeoms,1); + lwmpoint_free(mpt); + + lwgeom_free(geom); +} + +static void test_lwpoly_construct_circle(void) +{ + LWPOLY* p; + const GBOX* g; + const int32_t srid = 4326; + const uint32_t segments_per_quad = 17; + const int x = 10; + const int y = 20; + const int r = 5; + + /* With normal arguments you should get something circle-y */ + p = lwpoly_construct_circle(srid, x, y, r, segments_per_quad, LW_TRUE); + + ASSERT_INT_EQUAL(lwgeom_count_vertices(lwpoly_as_lwgeom(p)), segments_per_quad * 4 + 1); + ASSERT_INT_EQUAL(lwgeom_get_srid(lwpoly_as_lwgeom(p)), srid) + + g = lwgeom_get_bbox(lwpoly_as_lwgeom(p)); + CU_ASSERT_DOUBLE_EQUAL(g->xmin, x-r, 0.1); + CU_ASSERT_DOUBLE_EQUAL(g->xmax, x+r, 0.1); + CU_ASSERT_DOUBLE_EQUAL(g->ymin, y-r, 0.1); + CU_ASSERT_DOUBLE_EQUAL(g->ymax, y+r, 0.1); + + CU_ASSERT_DOUBLE_EQUAL(lwgeom_area(lwpoly_as_lwgeom(p)), M_PI*5*5, 0.1); + + lwpoly_free(p); + + /* No segments? No circle. */ + p = lwpoly_construct_circle(srid, x, y, r, 0, LW_TRUE); + CU_ASSERT_TRUE(p == NULL); + + /* Negative radius? No circle. */ + p = lwpoly_construct_circle(srid, x, y, -1e-3, segments_per_quad, LW_TRUE); + CU_ASSERT_TRUE(p == NULL); + + /* Zero radius? Invalid circle */ + p = lwpoly_construct_circle(srid, x, y, 0, segments_per_quad, LW_TRUE); + CU_ASSERT_TRUE(p != NULL); + lwpoly_free(p); +} + +static void test_kmeans(void) +{ + static int cluster_size = 100; + static int num_clusters = 4; + static double spread = 1.5; + int N = cluster_size * num_clusters; + LWGEOM **geoms; + int i, j, k=0; + int *r; + + geoms = lwalloc(sizeof(LWGEOM*) * N); + + for (j = 0; j < num_clusters; j++) { + for (i = 0; i < cluster_size; i++) + { + double u1 = 1.0 * rand() / RAND_MAX; + double u2 = 1.0 * rand() / RAND_MAX; + double z1 = spread * j + sqrt(-2*log2(u1))*cos(2*M_PI*u2); + double z2 = spread * j + sqrt(-2*log2(u1))*sin(2*M_PI*u2); + + LWPOINT *lwp = lwpoint_make2d(SRID_UNKNOWN, z1, z2); + geoms[k++] = lwpoint_as_lwgeom(lwp); + } + } + + r = lwgeom_cluster_kmeans((const LWGEOM **)geoms, N, num_clusters, 0.0); + + // for (i = 0; i < k; i++) + // { + // printf("[%d] %s\n", r[i], lwgeom_to_ewkt(geoms[i])); + // } + + /* Clean up */ + lwfree(r); + for (i = 0; i < k; i++) + lwgeom_free(geoms[i]); + lwfree(geoms); + + return; +} + +static void test_trim_bits(void) +{ + POINTARRAY *pta = ptarray_construct_empty(LW_TRUE, LW_TRUE, 2); + POINT4D pt; + LWLINE *line; + int precision; + uint32_t i; + + pt.x = 1.2345678901234; + pt.y = 2.3456789012345; + pt.z = 3.4567890123456; + pt.m = 4.5678901234567; + + ptarray_insert_point(pta, &pt, 0); + + pt.x *= 3; + pt.y *= 3; + pt.y *= 3; + pt.z *= 3; + + ptarray_insert_point(pta, &pt, 1); + + line = lwline_construct(0, NULL, pta); + + for (precision = -15; precision <= 15; precision++) + { + LWLINE *line2 = (LWLINE*) lwgeom_clone_deep((LWGEOM*) line); + lwgeom_trim_bits_in_place((LWGEOM*) line2, precision, precision, precision, precision); + + for (i = 0; i < line->points->npoints; i++) + { + POINT4D pt1 = getPoint4d(line->points, i); + POINT4D pt2 = getPoint4d(line2->points, i); + + CU_ASSERT_DOUBLE_EQUAL(pt1.x, pt2.x, pow(10, -1*precision)); + CU_ASSERT_DOUBLE_EQUAL(pt1.y, pt2.y, pow(10, -1*precision)); + CU_ASSERT_DOUBLE_EQUAL(pt1.z, pt2.z, pow(10, -1*precision)); + CU_ASSERT_DOUBLE_EQUAL(pt1.m, pt2.m, pow(10, -1*precision)); + } + + lwline_free(line2); + } + + lwline_free(line); +} + +/* +** Used by test harness to register the tests in this file. +*/ +void algorithms_suite_setup(void); +void algorithms_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("algorithm", init_cg_suite, clean_cg_suite); + PG_ADD_TEST(suite,test_lw_segment_side); + PG_ADD_TEST(suite,test_lw_segment_intersects); + PG_ADD_TEST(suite,test_lwline_crossing_short_lines); + PG_ADD_TEST(suite,test_lwline_crossing_long_lines); + PG_ADD_TEST(suite,test_lwline_crossing_bugs); + PG_ADD_TEST(suite,test_lwpoint_set_ordinate); + PG_ADD_TEST(suite,test_lwpoint_get_ordinate); + PG_ADD_TEST(suite,test_point_interpolate); + PG_ADD_TEST(suite,test_lwline_interpolate_points); + PG_ADD_TEST(suite, test_lwline_interpolate_point_3d); + PG_ADD_TEST(suite,test_lwline_clip); + PG_ADD_TEST(suite, test_lwpoly_clip); + PG_ADD_TEST(suite, test_lwtriangle_clip); + PG_ADD_TEST(suite,test_lwline_clip_big); + PG_ADD_TEST(suite,test_lwmline_clip); + PG_ADD_TEST(suite,test_geohash_point); + PG_ADD_TEST(suite,test_geohash_precision); + PG_ADD_TEST(suite,test_geohash); + PG_ADD_TEST(suite,test_geohash_point_as_int); + PG_ADD_TEST(suite, test_geohash_bbox); + PG_ADD_TEST(suite,test_isclosed); + PG_ADD_TEST(suite,test_lwgeom_simplify); + PG_ADD_TEST(suite,test_lw_arc_center); + PG_ADD_TEST(suite,test_point_density); + PG_ADD_TEST(suite,test_kmeans); + PG_ADD_TEST(suite,test_median_handles_3d_correctly); + PG_ADD_TEST(suite,test_median_robustness); + PG_ADD_TEST(suite,test_lwpoly_construct_circle); + PG_ADD_TEST(suite,test_trim_bits); + PG_ADD_TEST(suite,test_lwgeom_remove_repeated_points); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_boundary.c b/mgist-postgis/liblwgeom/cunit/cu_boundary.c new file mode 100644 index 0000000..3088f91 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_boundary.c @@ -0,0 +1,216 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright (C) 2021 Sandro Santilli + * Copyright (C) 2021 Aliaksandr Kalinik + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include "CUnit/Basic.h" +#include "cu_tester.h" + +#include "liblwgeom.h" +#include "liblwgeom_internal.h" + +#define BOUNDARY_TEST(wkt_in, wkt_exp) \ + do \ + { \ + LWGEOM *gin, *gout, *gexp; \ + cu_error_msg_reset(); \ + gin = lwgeom_from_wkt(wkt_in, LW_PARSER_CHECK_NONE); \ + CU_ASSERT_PTR_NOT_NULL_FATAL(gin); \ + gexp = lwgeom_from_wkt(wkt_exp, LW_PARSER_CHECK_NONE); \ + CU_ASSERT_PTR_NOT_NULL_FATAL(gexp); \ + gout = lwgeom_boundary(gin); \ + CU_ASSERT_PTR_NOT_NULL_FATAL(gout); \ + ASSERT_NORMALIZED_GEOM_SAME(gout, gexp); \ + lwgeom_free(gout); \ + lwgeom_free(gexp); \ + lwgeom_free(gin); \ + } while (0) + +static void +boundary_point(void) +{ + BOUNDARY_TEST( + "POINT EMPTY", + "POINT EMPTY" + ); + + BOUNDARY_TEST( + "POINT(0 0)", + "POINT EMPTY" + ); + + BOUNDARY_TEST( + "MULTIPOINT EMPTY", + "MULTIPOINT EMPTY" /* debatable return type */ + ); + + BOUNDARY_TEST( + "MULTIPOINT(0 0,10 0)", + "MULTIPOINT EMPTY" /* debatable return type */ + ); +} + +static void +boundary_line(void) +{ + BOUNDARY_TEST( + "LINESTRING EMPTY", + "MULTIPOINT EMPTY" + ); + + BOUNDARY_TEST( + "CIRCULARSTRING EMPTY", + "MULTIPOINT EMPTY" + ); + + BOUNDARY_TEST( + "LINESTRING(0 0, 5 5, 10 0, 0 0)", + "MULTIPOINT EMPTY" + ); + + BOUNDARY_TEST( + "CIRCULARSTRING(0 0, 4 0, 4 4, 0 4, 0 0)", + "MULTIPOINT EMPTY" + ); + + BOUNDARY_TEST( + "LINESTRING(0 0, 5 0, 10 0)", + "MULTIPOINT(0 0, 10 0)" + ); + + BOUNDARY_TEST( + "MULTILINESTRING((0 0, 5 0, 10 0),(20 5, 20 6, 20 10))", + "MULTIPOINT(0 0, 10 0, 20 5, 20 10)" + ); + + BOUNDARY_TEST( + "MULTICURVE((1 1 1,0 0 0.5, -1 1 1),(1 1 0.5,0 0 0.5, -1 1 0.5, 1 1 0.5))", + "MULTIPOINT(1 1 1,-1 1 1)" + ); + +} + +static void +boundary_polygon(void) +{ + BOUNDARY_TEST( + "POLYGON EMPTY", + "MULTILINESTRING EMPTY" + ); + + BOUNDARY_TEST( + "POLYGON((0 0, 5 5, 10 0, 0 0))", + "LINESTRING(0 0, 5 5, 10 0, 0 0)" + ); + + BOUNDARY_TEST( + "POLYGON((0 0, 5 5, 10 0, 0 0),(3 1,7 1,5 2,3 1))", + "MULTILINESTRING((0 0, 5 5, 10 0, 0 0),(3 1,7 1,5 2,3 1))" + ); + + /* See https://trac.osgeo.org/postgis/ticket/4961 */ + BOUNDARY_TEST( + "MULTIPOLYGON(((0 0, 5 5, 10 0, 0 0),(3 1,7 1,5 2,3 1)),((20 0,20 5,25 5,20 0)))", + "MULTILINESTRING((0 0, 5 5, 10 0, 0 0),(3 1,7 1,5 2,3 1),(20 0,20 5,25 5,20 0))" + ); + + BOUNDARY_TEST( + "CURVEPOLYGON(CIRCULARSTRING(0 0, 4 0, 4 4, 0 4, 0 0))", + "MULTICURVE(CIRCULARSTRING(0 0, 4 0, 4 4, 0 4, 0 0))" + ); + + BOUNDARY_TEST( + "CURVEPOLYGON(CIRCULARSTRING(0 0, 4 0, 4 4, 0 4, 0 0),(1 1, 3 3, 3 1, 1 1))", + "MULTICURVE(CIRCULARSTRING(0 0, 4 0, 4 4, 0 4, 0 0),(1 1, 3 3, 3 1, 1 1))" + ); + +} + +static void +boundary_triangle(void) +{ + BOUNDARY_TEST( + "TRIANGLE EMPTY", + "LINESTRING EMPTY" + ); + + BOUNDARY_TEST( + "TRIANGLE((1 1, 0 0, -1 1, 1 1))", + "LINESTRING(1 1, 0 0, -1 1, 1 1)" + ); +} + +static void +boundary_tin(void) +{ + BOUNDARY_TEST( + "TIN EMPTY", + "GEOMETRYCOLLECTION EMPTY" + ); + + BOUNDARY_TEST( + "TIN(((0 0,0 -1,-1 1,0 0)))", + "LINESTRING(0 0,0 -1,-1 1,0 0)" + ); + + BOUNDARY_TEST( + "TIN(((0 0,0 -1,-1 1,0 0)),((0 0,1 0,0 -1,0 0)))", + "MULTILINESTRING((0 0,0 -1,-1 1,0 0),(0 0,1 0,0 -1,0 0))" + ); +} + +static void +boundary_collection(void) +{ + BOUNDARY_TEST( + "GEOMETRYCOLLECTION EMPTY", + "GEOMETRYCOLLECTION EMPTY" + ); + + /* See https://trac.osgeo.org/postgis/ticket/4956 */ + BOUNDARY_TEST( + "GEOMETRYCOLLECTION(POLYGON((0 0,0 10,10 10,10 0,0 0),(2 2,4 2,4 4,2 4,2 2)),POLYGON((2 2,2 4,4 4,4 2,2 2)))", + "MULTILINESTRING((0 0,0 10,10 10,10 0,0 0),(2 2,4 2,4 4,2 4,2 2),(2 2,2 4,4 4,4 2,2 2))" + ); + + BOUNDARY_TEST( + "GEOMETRYCOLLECTION(TIN(((0 0,0 -1,-1 1,0 0)),((0 0,1 0,0 -1,0 0))),TIN(((10 10,10 20,20 20,10 10))))", + "MULTILINESTRING((0 0,0 -1,-1 1,0 0),(0 0,1 0,0 -1,0 0),(10 10,10 20,20 20,10 10))" + ); + + BOUNDARY_TEST( + "GEOMETRYCOLLECTION(TRIANGLE((1 1, 0 0, -1 1, 1 1)))", + "LINESTRING(1 1, 0 0, -1 1, 1 1)" + ); + + BOUNDARY_TEST( + "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(TRIANGLE((0 0,0 -1,-1 1,0 0)),TRIANGLE((0 0,1 0,0 -1,0 0))),MULTILINESTRING((0 0, 5 0, 10 0),(20 5, 20 6, 20 10)))", + "GEOMETRYCOLLECTION(MULTILINESTRING((0 0,0 -1,1 0,0 0),(0 0,-1 1,0 -1,0 0)),MULTIPOINT(20 10,20 5,10 0,0 0))" + ); + + BOUNDARY_TEST( + "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(0 0)),POINT(1 1),MULTIPOINT(2 2,3 3))", + "GEOMETRYCOLLECTION EMPTY" + ); +} + +void boundary_suite_setup(void); +void +boundary_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("boundary", NULL, NULL); + PG_ADD_TEST(suite, boundary_point); + PG_ADD_TEST(suite, boundary_line); + PG_ADD_TEST(suite, boundary_polygon); + PG_ADD_TEST(suite, boundary_triangle); + PG_ADD_TEST(suite, boundary_tin); + PG_ADD_TEST(suite, boundary_collection); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_buildarea.c b/mgist-postgis/liblwgeom/cunit/cu_buildarea.c new file mode 100644 index 0000000..d606697 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_buildarea.c @@ -0,0 +1,338 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright (C) 2012 Sandro Santilli + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include "CUnit/Basic.h" +#include "cu_tester.h" + +#include "liblwgeom.h" +#include "liblwgeom_internal.h" + +/* + * TODO: change lwgeom_same to lwgeom_equals + * (requires porting predicates to liblwgeom) + */ +#define check_geom_equal(gobt, gexp) do { \ + char *obt, *exp; \ + LWGEOM *ngobt, *ngexp; \ + ngobt = lwgeom_normalize(gobt); \ + ngexp = lwgeom_normalize(gexp); \ + if ( ! lwgeom_same((ngobt), (ngexp)) ) { \ + obt = lwgeom_to_wkt((ngobt), WKT_ISO, 8, NULL); \ + exp = lwgeom_to_wkt((ngexp), WKT_ISO, 8, NULL); \ + printf(" Failure at %s:%d\n", __FILE__, __LINE__); \ + printf(" Exp: %s\n", exp); \ + printf(" Obt: %s\n", obt); \ + free(obt); free(exp); \ + lwgeom_free(ngobt); lwgeom_free(ngexp); \ + CU_ASSERT(0); \ + } else { \ + lwgeom_free(ngobt); lwgeom_free(ngexp); \ + CU_ASSERT(1); \ + } \ +} while (0) + +/* + +-----+ + | | + +-----+-----+ + | | + +-----+ +*/ +static void buildarea1(void) +{ + LWGEOM *gin, *gout, *gexp; + + cu_error_msg_reset(); + + gin = lwgeom_from_wkt( +"MULTILINESTRING((0 0, 10 0, 10 10, 0 10, 0 0),(10 10, 20 10, 20 20, 10 20, 10 10))", + LW_PARSER_CHECK_NONE); + CU_ASSERT( gin != NULL ); + + gexp = lwgeom_from_wkt( +"MULTIPOLYGON(((0 0,0 10,10 10,10 0,0 0)),((10 10,10 20,20 20,20 10,10 10)))", + LW_PARSER_CHECK_NONE); + CU_ASSERT( gexp != NULL ); + + gout = lwgeom_buildarea(gin); + CU_ASSERT(gout != NULL); + + check_geom_equal(gout, gexp); + + lwgeom_free(gout); + lwgeom_free(gexp); + lwgeom_free(gin); +} + +/* + +-----+-----+ + | | | + +-----+-----+ +*/ +static void buildarea2(void) +{ + LWGEOM *gin, *gout, *gexp; + + /* because i don't trust that much prior tests... ;) */ + cu_error_msg_reset(); + + gin = lwgeom_from_wkt( +"MULTILINESTRING((0 0, 10 0, 10 10, 0 10, 0 0),(10 10, 20 10, 20 0, 10 0, 10 10))", + LW_PARSER_CHECK_NONE); + CU_ASSERT(gin != NULL); + + gexp = lwgeom_from_wkt( +"POLYGON((0 0,0 10,10 10,20 10,20 0,10 0,0 0))", + LW_PARSER_CHECK_NONE); + CU_ASSERT(gexp != NULL); + + gout = lwgeom_buildarea(gin); + CU_ASSERT(gout != NULL); + + check_geom_equal(gout, gexp); + + lwgeom_free(gout); + lwgeom_free(gexp); + lwgeom_free(gin); + +} + +/* + +-----------+ + | +-----+ | + | | | | + | +-----+ | + +-----------+ +*/ +static void buildarea3(void) +{ + LWGEOM *gin, *gout, *gexp; + + cu_error_msg_reset(); + + gin = lwgeom_from_wkt( +"MULTILINESTRING((0 0, 20 0, 20 20, 0 20, 0 0),(2 2, 18 2, 18 18, 2 18, 2 2))", + LW_PARSER_CHECK_NONE); + CU_ASSERT(gin != NULL); + + gexp = lwgeom_from_wkt( +"POLYGON((0 0,0 20,20 20,20 0,0 0),(2 2,18 2,18 18,2 18,2 2))", + LW_PARSER_CHECK_NONE); + CU_ASSERT(gexp != NULL); + + gout = lwgeom_buildarea(gin); + CU_ASSERT(gout != NULL); + + check_geom_equal(gout, gexp); + + lwgeom_free(gout); + lwgeom_free(gexp); + lwgeom_free(gin); + +} + +/* + +-----------+ + | +-----+ | + | | +-+ | | + | | | | | | + | | +-+ | | + | +-----+ | + +-----------+ +*/ +static void buildarea4(void) +{ + LWGEOM *gin, *gout, *gexp; + + cu_error_msg_reset(); + + gin = lwgeom_from_wkt( +"MULTILINESTRING((0 0, 20 0, 20 20, 0 20, 0 0),(2 2, 18 2, 18 18, 2 18, 2 2),(8 8, 8 12, 12 12, 12 8, 8 8))", + LW_PARSER_CHECK_NONE); + CU_ASSERT(gin != NULL); + + gexp = lwgeom_from_wkt( +"MULTIPOLYGON(((0 0,0 20,20 20,20 0,0 0),(2 2,18 2,18 18,2 18,2 2)),((8 8,8 12,12 12,12 8,8 8)))", + LW_PARSER_CHECK_NONE); + CU_ASSERT(gexp != NULL); + + gout = lwgeom_buildarea(gin); + CU_ASSERT(gout != NULL); + + check_geom_equal(gout, gexp); + + lwgeom_free(gout); + lwgeom_free(gexp); + lwgeom_free(gin); + +} + +/* + +-----------+ + | +-----+ | This time the innermost ring has + | | +-+ | | more points than the other (outer) two. + | | | | | | + | | +-+ | | + | +-----+ | + +-----------+ +*/ +static void buildarea4b(void) +{ + LWGEOM *gin, *gout, *gexp; + + cu_error_msg_reset(); + + gin = lwgeom_from_wkt( +"MULTILINESTRING((0 0, 20 0, 20 20, 0 20, 0 0),(2 2, 18 2, 18 18, 2 18, 2 2), (8 8, 8 9, 8 10, 8 11, 8 12, 9 12, 10 12, 11 12, 12 12, 12 11, 12 10, 12 9, 12 8, 11 8, 10 8, 9 8, 8 8))", + LW_PARSER_CHECK_NONE); + CU_ASSERT(gin != NULL); + + gexp = lwgeom_from_wkt( +"MULTIPOLYGON(((0 0,0 20,20 20,20 0,0 0),(2 2,18 2,18 18,2 18,2 2)),((8 8,8 9,8 10,8 11,8 12,9 12,10 12,11 12,12 12,12 11,12 10,12 9,12 8,11 8,10 8,9 8,8 8)))", + LW_PARSER_CHECK_NONE); + CU_ASSERT(gexp != NULL); + + gout = lwgeom_buildarea(gin); + CU_ASSERT(gout != NULL); + + check_geom_equal(gout, gexp); + + lwgeom_free(gout); + lwgeom_free(gexp); + lwgeom_free(gin); + +} + +/* + +---------------+ + | +---------+ | + | | +--+--+ | | + | | | | | | | + | | +--+--+ | | + | +---------+ | + +---------------+ +*/ +static void buildarea5(void) +{ + LWGEOM *gin, *gout, *gexp; + + cu_error_msg_reset(); + + gin = lwgeom_from_wkt( +"MULTILINESTRING((0 0, 20 0, 20 20, 0 20, 0 0),(2 2, 18 2, 18 18, 2 18, 2 2),(8 8, 8 12, 12 12, 12 8, 8 8),(10 8, 10 12))", + LW_PARSER_CHECK_NONE); + CU_ASSERT(gin != NULL); + + gexp = lwgeom_from_wkt( +"MULTIPOLYGON(((0 0,0 20,20 20,20 0,0 0),(2 2,18 2,18 18,2 18,2 2)),((8 8,8 12,12 12,12 8,8 8)))", + LW_PARSER_CHECK_NONE); + CU_ASSERT(gexp != NULL); + + gout = lwgeom_buildarea(gin); + CU_ASSERT(gout != NULL); + + check_geom_equal(gout, gexp); + + lwgeom_free(gout); + lwgeom_free(gexp); + lwgeom_free(gin); + +} + +/* + +---------------+ + | +----+----+ | + | | | | | + | | | | | + | | | | | + | +----+----+ | + +---------------+ +*/ +static void buildarea6(void) +{ + LWGEOM *gin, *gout, *gexp; + + cu_error_msg_reset(); + + gin = lwgeom_from_wkt( +"MULTILINESTRING((0 0, 20 0, 20 20, 0 20, 0 0),(2 2, 18 2, 18 18, 2 18, 2 2),(10 2, 10 18))", + LW_PARSER_CHECK_NONE); + CU_ASSERT(gin != NULL); + + gexp = lwgeom_from_wkt( +"POLYGON((0 0,0 20,20 20,20 0,0 0),(2 2,18 2,18 18,2 18,2 2))", + LW_PARSER_CHECK_NONE); + CU_ASSERT(gexp != NULL); + + gout = lwgeom_buildarea(gin); + CU_ASSERT(gout != NULL); + + check_geom_equal(gout, gexp); + + lwgeom_free(gout); + lwgeom_free(gexp); + lwgeom_free(gin); + +} + +/* + +--------------------+ +-------+ + | +-----+ +----+ | | +---+ | + | | +-+ | | | | | | | | + | | | | | +----+ | | +---+ | + | | +-+ | | | | | | + | | | | | | | | | | + | | +-+ | | | | | | + | +-----+ +----+ | | | + +--------------------+ +-------+ +*/ +static void buildarea7(void) +{ + LWGEOM *gin, *gout, *gexp; + + cu_error_msg_reset(); + + gin = lwgeom_from_wkt( +"MULTILINESTRING( (0 0, 70 0, 70 70, 0 70, 0 0), (10 10, 10 60, 40 60, 40 10, 10 10), (20 20, 20 30, 30 30, 30 20, 20 20), (20 30, 30 30, 30 50, 20 50, 20 30), (50 20, 60 20, 60 40, 50 40, 50 20), (50 40, 60 40, 60 60, 50 60, 50 40), (80 0, 110 0, 110 70, 80 70, 80 0), (90 60, 100 60, 100 50, 90 50, 90 60))", + LW_PARSER_CHECK_NONE); + CU_ASSERT(gin != NULL); + + gexp = lwgeom_from_wkt( +"MULTIPOLYGON(((80 0,80 70,110 70,110 0,80 0),(90 60,90 50,100 50,100 60,90 60)),((20 20,20 30,20 50,30 50,30 30,30 20,20 20)),((0 0,0 70,70 70,70 0,0 0),(10 10,40 10,40 60,10 60,10 10),(50 20,60 20,60 40,60 60,50 60,50 40,50 20)))", + LW_PARSER_CHECK_NONE); + CU_ASSERT(gexp != NULL); + + gout = lwgeom_buildarea(gin); + CU_ASSERT(gout != NULL); + + check_geom_equal(gout, gexp); + + lwgeom_free(gout); + lwgeom_free(gexp); + lwgeom_free(gin); + +} + + +void buildarea_suite_setup(void); +void buildarea_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("buildarea", NULL, NULL); + PG_ADD_TEST(suite,buildarea1); + PG_ADD_TEST(suite,buildarea2); + PG_ADD_TEST(suite,buildarea3); + PG_ADD_TEST(suite,buildarea4); + PG_ADD_TEST(suite,buildarea4b); + PG_ADD_TEST(suite,buildarea5); + PG_ADD_TEST(suite,buildarea6); + PG_ADD_TEST(suite,buildarea7); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_bytebuffer.c b/mgist-postgis/liblwgeom/cunit/cu_bytebuffer.c new file mode 100644 index 0000000..64dc80a --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_bytebuffer.c @@ -0,0 +1,58 @@ +/********************************************************************** + * $Id$ + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright 2012 Sandro Santilli + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "bytebuffer.h" +#include "cu_tester.h" + +#if 0 + +static void test_bytebuffer_append(void) +{ + bytebuffer_t *bb1; + int64_t res; + bb1 = bytebuffer_create_with_size(2); + + bytebuffer_append_varint(bb1,(int64_t) -12345); + + + bytebuffer_reset_reading(bb1); + + res= bytebuffer_read_varint(bb1); + + CU_ASSERT_EQUAL(res, -12345); + + bytebuffer_destroy(bb1); +} + + +/* TODO: add more... */ + + + +/* +** Used by the test harness to register the tests in this file. +*/ +void bytebuffer_suite_setup(void); +void bytebuffer_suite_setup(void) +{ + PG_TEST(test_bytebuffer_append), + CU_TEST_INFO_NULL +}; +CU_SuiteInfo bytebuffer_suite = {"bytebuffer", NULL, NULL, bytebuffer_tests }; + +#endif diff --git a/mgist-postgis/liblwgeom/cunit/cu_chaikin.c b/mgist-postgis/liblwgeom/cunit/cu_chaikin.c new file mode 100644 index 0000000..60b398b --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_chaikin.c @@ -0,0 +1,66 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright 2018 Nicklas Avén + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +static void do_test_chaikin(char *geom_txt,char *expected, int n_iterations, int preserve_end_points) +{ + LWGEOM *geom_in, *geom_out; + char *out_txt; + geom_in = lwgeom_from_wkt(geom_txt, LW_PARSER_CHECK_NONE); + geom_out = lwgeom_chaikin(geom_in, n_iterations, preserve_end_points); + out_txt = lwgeom_to_wkt(geom_out, WKT_EXTENDED, 3, NULL); + if(strcmp(expected, out_txt)) + printf("%s is not equal to %s\n", expected, out_txt); + CU_ASSERT_STRING_EQUAL(expected, out_txt) + lwfree(out_txt); + lwgeom_free(geom_in); + lwgeom_free(geom_out); + return; +} + +static void do_test_chaikin_lines(void) +{ + /*Simpliest test*/ + do_test_chaikin("LINESTRING(0 0,8 8,16 0)","LINESTRING(0 0,6 6,10 6,16 0)", 1, 1); + /*2 iterations*/ + do_test_chaikin("LINESTRING(0 0,8 8,16 0)","LINESTRING(0 0,4.5 4.5,7 6,9 6,11.5 4.5,16 0)", 2, 1); + /*check so it really ignores preserve_end_points set to off for linestrings*/ + do_test_chaikin("LINESTRING(0 0,8 8,16 0)","LINESTRING(0 0,4.5 4.5,7 6,9 6,11.5 4.5,16 0)", 2, 0); + return; +} + +static void do_test_chaikin_polygons(void) +{ + /*Simpliest test*/ + do_test_chaikin("POLYGON((0 0,8 8,16 0,0 0))","POLYGON((0 0,6 6,10 6,14 2,12 0,0 0))", 1, 1); + /*2 iterations*/ + do_test_chaikin("POLYGON((0 0,8 8,16 0,0 0))","POLYGON((0 0,4.5 4.5,7 6,9 6,11 5,13 3,13.5 1.5,12.5 0.5,9 0,0 0))", 2, 1); + /*2 iterations without preserving end points*/ + do_test_chaikin("POLYGON((0 0,8 8,16 0,0 0))","POLYGON((3 3,5 5,7 6,9 6,11 5,13 3,13.5 1.5,12.5 0.5,10 0,6 0,3.5 0.5,2.5 1.5,3 3))", 2, 0); + return; +} + + +void chaikin_suite_setup(void); +void chaikin_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("chaikin",NULL,NULL); + PG_ADD_TEST(suite, do_test_chaikin_lines); + PG_ADD_TEST(suite, do_test_chaikin_polygons); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_clean.c b/mgist-postgis/liblwgeom/cunit/cu_clean.c new file mode 100644 index 0000000..3155265 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_clean.c @@ -0,0 +1,181 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright (C) 2012 Sandro Santilli + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include "CUnit/Basic.h" +#include "cu_tester.h" + +#include "liblwgeom.h" +#include "liblwgeom_internal.h" +/* + * TODO: change lwgeom_same to lwgeom_equals + * (requires porting predicates to liblwgeom) + */ +#define check_geom_equal(gobt, gexp) do { \ + char *obt, *exp; \ + LWGEOM *ngobt, *ngexp; \ + ngobt = lwgeom_normalize(gobt); \ + ngexp = lwgeom_normalize(gexp); \ + if ( ! lwgeom_same((ngobt), (ngexp)) ) { \ + obt = lwgeom_to_wkt((ngobt), WKT_ISO, 8, NULL); \ + exp = lwgeom_to_wkt((ngexp), WKT_ISO, 8, NULL); \ + printf(" Failure at %s:%d\n", __FILE__, __LINE__); \ + printf(" Exp: %s\n", exp); \ + printf(" Obt: %s\n", obt); \ + free(obt); free(exp); \ + lwgeom_free(ngobt); lwgeom_free(ngexp); \ + CU_ASSERT(0); \ + } else { \ + lwgeom_free(ngobt); lwgeom_free(ngexp); \ + CU_ASSERT(1); \ + } \ +} while (0) + +static void test_lwgeom_make_valid(void) +{ + LWGEOM *gin, *gout, *gexp; + char *ewkt; + + /* Because i don't trust that much prior tests... ;) */ + cu_error_msg_reset(); + + gin = lwgeom_from_wkt( +"MULTIPOLYGON(((1725063 4819121, 1725104 4819067, 1725060 4819087, 1725064.14183882 4819094.70208557,1725064.13656044 4819094.70235069,1725064.14210359 4819094.70227252,1725064.14210362 4819094.70227252,1725064.13656043 4819094.70235069,1725055. 4819094, 1725055 4819094, 1725055 4819094, 1725063 4819121)))", + LW_PARSER_CHECK_NONE); + CU_ASSERT(gin != NULL); + + gout = lwgeom_make_valid(gin); + + /* We're really only interested in avoiding a crash in here. + * See http://trac.osgeo.org/postgis/ticket/1738 + * TODO: enhance the test if we find a workaround + * to the excepion: + * See http://trac.osgeo.org/postgis/ticket/1735 + */ + + lwgeom_free(gout); + lwgeom_free(gin); + + /* Test for http://trac.osgeo.org/postgis/ticket/2307 */ + + gin = lwgeom_from_hexwkb("0106000020E6100000010000000103000000010000000A0000004B7DA956B99844C0DB0790FE8B4D1DC010BA74A9AF9444C049AFFC5B8C4D1DC03FC6CC690D9844C0DD67E5628C4D1DC07117B56B0D9844C0C80ABA67C45E1DC0839166ABAF9444C0387D4568C45E1DC010BA74A9AF9444C049AFFC5B8C4D1DC040C3CD74169444C0362EC0608C4D1DC07C1A3B77169444C0DC3ADB40B2641DC03AAE5F68B99844C0242948DEB1641DC04B7DA956B99844C0DB0790FE8B4D1DC0", LW_PARSER_CHECK_NONE); + CU_ASSERT(gin != NULL); + + gout = lwgeom_make_valid(gin); + CU_ASSERT(gout != NULL); + lwgeom_free(gin); + + /* We're really only interested in avoiding memory problems. + * Convertion to ewkt ensures full scan of coordinates thus + * triggering the error, if any + */ + ewkt = lwgeom_to_ewkt(gout); + lwgeom_free(gout); + lwfree(ewkt); + + + /* Test collection */ + + gin = lwgeom_from_wkt( +"GEOMETRYCOLLECTION(LINESTRING(0 0, 0 0), POLYGON((0 0, 10 10, 10 0, 0 10, 0 0)), LINESTRING(10 0, 10 10))", + LW_PARSER_CHECK_NONE); + CU_ASSERT(gin != NULL); + + gout = lwgeom_make_valid(gin); + CU_ASSERT(gout != NULL); + + ewkt = lwgeom_to_ewkt(gout); + /* printf("c = %s\n", ewkt); */ + /* + TODO: This doesn't work on windows returns in different order. + strk figure out why. For now will replace with normalized version + */ +/* CU_ASSERT_STRING_EQUAL(ewkt, +"GEOMETRYCOLLECTION(POINT(0 0),MULTIPOLYGON(((5 5,0 0,0 10,5 5)),((5 5,10 10,10 0,5 5))),LINESTRING(10 0,10 10))");*/ + gexp = lwgeom_from_wkt( +"GEOMETRYCOLLECTION(MULTIPOLYGON(((5 5,10 10,10 0,5 5)),((0 0,0 10,5 5,0 0))),LINESTRING(10 0,10 10),POINT(0 0))", + LW_PARSER_CHECK_NONE); + check_geom_equal(gout, gexp); + lwfree(ewkt); + + lwgeom_free(gout); + lwgeom_free(gin); + lwgeom_free(gexp); + + /* Test multipoint */ + + gin = lwgeom_from_wkt( +"MULTIPOINT(0 0,1 1,2 2)", + LW_PARSER_CHECK_NONE); + CU_ASSERT(gin != NULL); + + gout = lwgeom_make_valid(gin); + CU_ASSERT(gout != NULL); + + ewkt = lwgeom_to_ewkt(gout); + /* printf("c = %s\n", ewkt); */ + CU_ASSERT_STRING_EQUAL(ewkt, +"MULTIPOINT(0 0,1 1,2 2)"); + lwfree(ewkt); + + lwgeom_free(gout); + lwgeom_free(gin); + + /* Test unclosed polygon */ + + gin = lwgeom_from_hexwkb( +"0103000000010000000400000000000000000024400000000000003640000000000000244000000000000040400000000000003440000000000000404000000000000034400000000000003640", + LW_PARSER_CHECK_NONE); + CU_ASSERT(gin != NULL); + + gout = lwgeom_make_valid(gin); + CU_ASSERT(gout != NULL); + + ewkt = lwgeom_to_ewkt(gout); + /* printf("c = %s\n", ewkt); */ + CU_ASSERT_STRING_EQUAL(ewkt, +"POLYGON((10 22,10 32,20 32,20 22,10 22))"); + lwfree(ewkt); + + lwgeom_free(gout); + lwgeom_free(gin); + + /* Test collection with empty component */ + + gin = lwgeom_from_hexwkb( "0106000020110F000000000000", + LW_PARSER_CHECK_NONE); + CU_ASSERT(gin != NULL); + + gout = lwgeom_make_valid(gin); + CU_ASSERT(gout != NULL); + + ewkt = lwgeom_to_ewkt(gout); + /* printf("c = %s\n", ewkt); */ + ASSERT_STRING_EQUAL(ewkt, "SRID=3857;MULTIPOLYGON EMPTY"); + lwfree(ewkt); + + lwgeom_free(gout); + lwgeom_free(gin); + +} + +/* TODO: add more tests ! */ + + +/* +** Used by test harness to register the tests in this file. +*/ +void clean_suite_setup(void); +void clean_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("geometry_clean", NULL, NULL); + PG_ADD_TEST(suite, test_lwgeom_make_valid); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_clip_by_rect.c b/mgist-postgis/liblwgeom/cunit/cu_clip_by_rect.c new file mode 100644 index 0000000..c4cf8b1 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_clip_by_rect.c @@ -0,0 +1,84 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright (C) 2014 Sandro Santilli + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include "CUnit/Basic.h" +#include "cu_tester.h" + +#include "liblwgeom.h" +#include "liblwgeom_internal.h" + +static void test_lwgeom_clip_by_rect(void) +{ + LWGEOM *in, *out; + const char *wkt; + char *tmp; + + /* Because i don't trust that much prior tests... ;) */ + cu_error_msg_reset(); + + wkt = "LINESTRING(0 0, 5 5, 10 0)"; + in = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_NONE); + out = lwgeom_clip_by_rect(in, 5, 0, 10, 10); + tmp = lwgeom_to_ewkt(out); + /* printf("%s\n", tmp); */ + CU_ASSERT_STRING_EQUAL("LINESTRING(5 5,10 0)", tmp) + lwfree(tmp); lwgeom_free(out); lwgeom_free(in); + + wkt = "LINESTRING EMPTY"; + in = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_NONE); + out = lwgeom_clip_by_rect(in, 5, 0, 10, 10); + tmp = lwgeom_to_ewkt(out); + /* printf("%s\n", tmp); */ + CU_ASSERT_STRING_EQUAL(wkt, tmp) + lwfree(tmp); lwgeom_free(out); lwgeom_free(in); + + wkt = "MULTIPOINT EMPTY"; + in = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_NONE); + out = lwgeom_clip_by_rect(in, 5, 0, 10, 10); + tmp = lwgeom_to_ewkt(out); + /* printf("%s\n", tmp); */ + CU_ASSERT_STRING_EQUAL(wkt, tmp) + lwfree(tmp); lwgeom_free(out); lwgeom_free(in); + + wkt = "MULTIPOINT(0 0, 6 6, 7 5)"; + in = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_NONE); + out = lwgeom_clip_by_rect(in, 5, 0, 10, 10); + tmp = lwgeom_to_ewkt(out); + /* printf("%s\n", tmp); */ + CU_ASSERT_STRING_EQUAL("MULTIPOINT(6 6,7 5)", tmp) + lwfree(tmp); lwgeom_free(out); lwgeom_free(in); + + /* Disjoint polygon */ + wkt = "POLYGON((311017 4773762,311016 4773749,311006 4773744,310990 4773748,310980 4773758,310985 4773771,311003 4773776,311017 4773762))"; + in = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_NONE); + out = lwgeom_clip_by_rect(in, -80, -80, 80, 80); + //tmp = lwgeom_to_ewkt(out); printf("%s\n", tmp); lwfree(tmp); + CU_ASSERT(lwgeom_is_empty(out)); + lwgeom_free(out); lwgeom_free(in); + + /* Returns NULL with an invalid polygon (line) */ + wkt = "POLYGON((1410 2055, 1410 2056, 1410 2057, 1410 2055))"; + in = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_NONE); + out = lwgeom_clip_by_rect(in, -8.000000, -8.000000, 2056.000000, 2056.000000); + CU_ASSERT_PTR_NULL(out); + lwgeom_free(in); +} + +/* +** Used by test harness to register the tests in this file. +*/ +void clip_by_rect_suite_setup(void); +void clip_by_rect_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("clip_by_rectangle", NULL, NULL); + PG_ADD_TEST(suite, test_lwgeom_clip_by_rect); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_effectivearea.c b/mgist-postgis/liblwgeom/cunit/cu_effectivearea.c new file mode 100644 index 0000000..c433e43 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_effectivearea.c @@ -0,0 +1,79 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright 2012 Sandro Santilli + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "effectivearea.h" +#include "cu_tester.h" + + +static void do_test_lwgeom_effectivearea(POINTARRAY *pa,double *the_areas,int avoid_collaps) +{ + + uint32_t i; + EFFECTIVE_AREAS *ea; + + ea=initiate_effectivearea(pa); + ptarray_calc_areas(ea,avoid_collaps,1,0); + + for (i=0;inpoints;i++) + { + CU_ASSERT_EQUAL(ea->res_arealist[i],the_areas[i]); + } + + destroy_effectivearea(ea); + + +} + +static void do_test_lwgeom_effectivearea_lines(void) +{ + LWLINE *the_geom; + int avoid_collaps=2; + /*Line 1*/ + the_geom = (LWLINE*)lwgeom_from_wkt("LINESTRING(1 1, 0 1, 0 2, -1 4, -1 4)", LW_PARSER_CHECK_NONE); + double the_areas1[]={FLT_MAX,0.5,0.5,0,FLT_MAX}; + do_test_lwgeom_effectivearea(the_geom->points,the_areas1,avoid_collaps); + lwline_free(the_geom); + /*Line 2*/ + the_geom = (LWLINE*)lwgeom_from_wkt("LINESTRING(10 10,12 8, 15 7, 18 7, 20 20, 15 21, 18 22, 10 30, 40 100)", LW_PARSER_CHECK_NONE); + double the_areas2[]={FLT_MAX,5,1.5,55,100,4,4,300,FLT_MAX}; + do_test_lwgeom_effectivearea(the_geom->points,the_areas2,avoid_collaps); + lwline_free(the_geom); +} + + + +static void do_test_lwgeom_effectivearea_polys(void) +{ + LWPOLY *the_geom; + int avoid_collaps=4; + + /*POLYGON 1*/ + the_geom = (LWPOLY*)lwgeom_from_wkt("POLYGON((10 10,12 8, 15 7, 18 7, 20 20, 15 21, 18 22, 10 30,1 99, 0 100, 10 10))", LW_PARSER_CHECK_NONE); + double the_areas1[]={FLT_MAX,5,1.5,55,100,4,4,FLT_MAX,30,FLT_MAX,FLT_MAX}; + do_test_lwgeom_effectivearea(the_geom->rings[0],the_areas1,avoid_collaps); + lwpoly_free(the_geom); +} + + +void effectivearea_suite_setup(void); +void effectivearea_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("effectivearea",NULL,NULL); + PG_ADD_TEST(suite, do_test_lwgeom_effectivearea_lines); + PG_ADD_TEST(suite, do_test_lwgeom_effectivearea_polys); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_filterm.c b/mgist-postgis/liblwgeom/cunit/cu_filterm.c new file mode 100644 index 0000000..d516550 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_filterm.c @@ -0,0 +1,64 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright 2018 Nicklas Avén + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +static void do_test_filterm(char *geom_txt,char *expected, double min, double max) +{ + LWGEOM *geom_in, *geom_out; + char *out_txt; + geom_in = lwgeom_from_wkt(geom_txt, LW_PARSER_CHECK_NONE); + geom_out = lwgeom_filter_m(geom_in,min, max, 1); + out_txt = lwgeom_to_wkt(geom_out, WKT_EXTENDED, 3, NULL); + if(strcmp(expected, out_txt)) + printf("%s is not equal to %s\n", expected, out_txt); + CU_ASSERT_STRING_EQUAL(expected, out_txt) + lwfree(out_txt); + lwgeom_free(geom_in); + lwgeom_free(geom_out); + return; +} + +static void do_test_filterm_single_geometries(void) +{ + /*Point*/ + do_test_filterm("POINT(0 0 0 0)","POINT(0 0 0 0)", 0, 11); + do_test_filterm("POINT(0 0 0 0)","POINT EMPTY", 1, 11); + /*Line*/ + do_test_filterm("LINESTRING(0 0 0 0,1 1 0 2,2 2 0 5,3 1 0 3)","LINESTRING(1 1 0 2,3 1 0 3)",2,3); + do_test_filterm("LINESTRING(0 0 0 0,1 1 0 2,2 2 0 5,3 1 0 3)","LINESTRING EMPTY",10, 11); + /*Polygon*/ + do_test_filterm("POLYGON((0 0 0 0,1 1 0 2,5 5 0 5,3 1 0 3,0 0 0 0))","POLYGON((0 0 0 0,1 1 0 2,3 1 0 3,0 0 0 0))",0,4); + do_test_filterm("POLYGON((0 0 0 0,1 1 0 2,5 5 0 5,3 1 0 3,0 0 0 0))","POLYGON EMPTY",10, 11); + return; +} + +static void do_test_filterm_collections(void) +{ + do_test_filterm("GEOMETRYCOLLECTION(POINT(1 1 1 1), LINESTRING(1 1 1 1, 1 2 1 4, 2 2 1 3), POLYGON((0 0 0 4,1 1 0 2,5 5 0 5,3 1 0 3,0 0 0 4)))","GEOMETRYCOLLECTION(POINT(1 1 1 1),LINESTRING(1 1 1 1,1 2 1 4,2 2 1 3),POLYGON((0 0 0 4,1 1 0 2,3 1 0 3,0 0 0 4)))",0,4); + do_test_filterm("GEOMETRYCOLLECTION(POINT(1 1 1 1), LINESTRING(1 1 1 1, 1 2 1 4, 2 2 1 3), POLYGON((0 0 0 4,1 1 0 2,5 5 0 5,3 1 0 3,0 0 0 4)))","GEOMETRYCOLLECTION(LINESTRING(1 2 1 4,2 2 1 3),POLYGON((0 0 0 4,1 1 0 2,3 1 0 3,0 0 0 4)))",2,4); + return; +} + +void filterm_suite_setup(void); +void filterm_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("filterm",NULL,NULL); + PG_ADD_TEST(suite, do_test_filterm_single_geometries); + PG_ADD_TEST(suite, do_test_filterm_collections); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_force_dims.c b/mgist-postgis/liblwgeom/cunit/cu_force_dims.c new file mode 100644 index 0000000..68a6862 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_force_dims.c @@ -0,0 +1,118 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright (C) 2008 Paul Ramsey + * Copyright (C) 2020 Kristian Thy + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +static void +test_force_2d(void) +{ + LWGEOM *geom; + LWGEOM *geom2d; + char *wkt_out; + + geom = lwgeom_from_wkt("CIRCULARSTRINGM(-5 0 4,0 5 3,5 0 2,10 -5 1,15 0 0)", LW_PARSER_CHECK_NONE); + geom2d = lwgeom_force_2d(geom); + wkt_out = lwgeom_to_ewkt(geom2d); + CU_ASSERT_STRING_EQUAL("CIRCULARSTRING(-5 0,0 5,5 0,10 -5,15 0)", wkt_out); + lwgeom_free(geom); + lwgeom_free(geom2d); + lwfree(wkt_out); + + geom = lwgeom_from_wkt( + "GEOMETRYCOLLECTION(POINT(0 0 0),LINESTRING(1 1 1,2 2 2),POLYGON((0 0 1,0 1 1,1 1 1,1 0 1,0 0 1)),CURVEPOLYGON(CIRCULARSTRING(0 0 0,1 1 1,2 2 2,1 1 1,0 0 0)))", + LW_PARSER_CHECK_NONE); + geom2d = lwgeom_force_2d(geom); + wkt_out = lwgeom_to_ewkt(geom2d); + CU_ASSERT_STRING_EQUAL( + "GEOMETRYCOLLECTION(POINT(0 0),LINESTRING(1 1,2 2),POLYGON((0 0,0 1,1 1,1 0,0 0)),CURVEPOLYGON(CIRCULARSTRING(0 0,1 1,2 2,1 1,0 0)))", + wkt_out); + lwgeom_free(geom); + lwgeom_free(geom2d); + lwfree(wkt_out); +} + +static void +test_force_3dm(void) +{ + LWGEOM *geom; + LWGEOM *geom3dm; + char *wkt_out; + + geom = lwgeom_from_wkt("CIRCULARSTRING(-5 0 4,0 5 3,5 0 2,10 -5 1,15 0 0)", LW_PARSER_CHECK_NONE); + geom3dm = lwgeom_force_3dm(geom, 1); + wkt_out = lwgeom_to_ewkt(geom3dm); + CU_ASSERT_STRING_EQUAL("CIRCULARSTRINGM(-5 0 1,0 5 1,5 0 1,10 -5 1,15 0 1)", wkt_out); + lwgeom_free(geom); + lwgeom_free(geom3dm); + lwfree(wkt_out); +} + +static void +test_force_3dz(void) +{ + LWGEOM *geom; + LWGEOM *geom3dz; + char *wkt_out; + + geom = lwgeom_from_wkt("CIRCULARSTRING(-5 0,0 5,5 0,10 -5,15 0)", LW_PARSER_CHECK_NONE); + geom3dz = lwgeom_force_3dz(geom, -99); + wkt_out = lwgeom_to_ewkt(geom3dz); + CU_ASSERT_STRING_EQUAL("CIRCULARSTRING(-5 0 -99,0 5 -99,5 0 -99,10 -5 -99,15 0 -99)", wkt_out); + lwgeom_free(geom); + lwgeom_free(geom3dz); + lwfree(wkt_out); + + geom = lwgeom_from_wkt("CIRCULARSTRING(-5 0,0 5,5 0,10 -5,15 0)", LW_PARSER_CHECK_NONE); + geom3dz = lwgeom_force_3dz(geom, 0.0); + wkt_out = lwgeom_to_ewkt(geom3dz); + CU_ASSERT_STRING_EQUAL("CIRCULARSTRING(-5 0 0,0 5 0,5 0 0,10 -5 0,15 0 0)", wkt_out); + lwgeom_free(geom); + lwgeom_free(geom3dz); + lwfree(wkt_out); +} + +static void +test_force_4d(void) +{ + LWGEOM *geom; + LWGEOM *geom4d; + char *wkt_out; + + geom = lwgeom_from_wkt("POINT(1 2)", LW_PARSER_CHECK_NONE); + geom4d = lwgeom_force_4d(geom, 3, 4); + wkt_out = lwgeom_to_ewkt(geom4d); + CU_ASSERT_STRING_EQUAL("POINT(1 2 3 4)", wkt_out); + lwgeom_free(geom); + lwgeom_free(geom4d); + lwfree(wkt_out); +} + +/* +** Used by the test harness to register the tests in this file. +*/ +void force_dims_suite_setup(void); +void +force_dims_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("force_dims", NULL, NULL); + PG_ADD_TEST(suite, test_force_2d); + PG_ADD_TEST(suite, test_force_3dm); + PG_ADD_TEST(suite, test_force_3dz); + PG_ADD_TEST(suite, test_force_4d); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_force_sfs.c b/mgist-postgis/liblwgeom/cunit/cu_force_sfs.c new file mode 100644 index 0000000..73026e9 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_force_sfs.c @@ -0,0 +1,173 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2013 Olivier Courtin + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +static void do_geom_test(char * in, char * out) +{ + LWGEOM *g, *h; + char *tmp; + + g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + h = lwgeom_force_sfs(g, 110); + tmp = lwgeom_to_ewkt(h); + if (strcmp(tmp, out)) + fprintf(stderr, "\nIn: %s\nOut: %s\nExp: %s\n", + in, tmp, out); + CU_ASSERT_STRING_EQUAL(tmp, out); + lwfree(tmp); + lwgeom_free(h); +} + + +static void do_type_test(char * in, int type) +{ + LWGEOM *g, *h; + + g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + h = lwgeom_force_sfs(g, 110); + if(h->type != type) + fprintf(stderr, "\nIn: %s\nOut: %s\nExp: %s\n", + in, lwtype_name(h->type), lwtype_name(type)); + CU_ASSERT_EQUAL(h->type, type); + lwgeom_free(h); + lwgeom_free(g); +} + + +static void test_sqlmm(void) +{ + do_type_test("CIRCULARSTRING(-1 0,0 1,0 -1)", + LINETYPE); + + do_type_test("COMPOUNDCURVE(CIRCULARSTRING(-1 0,0 1,0 -1),(0 -1,-1 -1))", + LINETYPE); + + do_type_test("COMPOUNDCURVE((-3 -3,-1 0),CIRCULARSTRING(-1 0,0 1,0 -1),(0 -1,0 -1.5,0 -2),CIRCULARSTRING(0 -2,-1 -3,1 -3),(1 -3,5 5))", + LINETYPE); + + do_type_test("COMPOUNDCURVE(CIRCULARSTRING(-1 0,0 1,0 -1),CIRCULARSTRING(0 -1,-1 -2,1 -2))", + LINETYPE); + + do_type_test("CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING (0 0 2,1 1 2,1 0 2),(1 0 2,0 1 2),(0 1 2, 0 0 2)))", + POLYGONTYPE); + + do_type_test("CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (0 0 2 5,1 1 2 6,1 0 2 5), (1 0 2 3,0 1 2 2), (0 1 2 2,30 1 2 2), CIRCULARSTRING (30 1 2 2,12 1 2 6,1 10 2 5, 1 10 3 5, 0 0 2 5)))", + POLYGONTYPE); + + do_type_test("MULTISURFACE (CURVEPOLYGON (CIRCULARSTRING (-2 0, -1 -1, 0 0, 1 -1, 2 0, 0 2, -2 0), (-1 0, 0 0.5, 1 0, 0 1, -1 0)), ((7 8, 10 10, 6 14, 4 11, 7 8)))", + MULTIPOLYGONTYPE); + +} + +static void test_sfs_12(void) +{ + do_geom_test("TRIANGLE((1 2,3 4,5 6,1 2))", + "POLYGON((1 2,3 4,5 6,1 2))"); + + do_geom_test("GEOMETRYCOLLECTION(TRIANGLE((1 2,3 4,5 6,1 2)))", + "GEOMETRYCOLLECTION(POLYGON((1 2,3 4,5 6,1 2)))"); + + do_geom_test("GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(TRIANGLE((1 2,3 4,5 6,1 2))))", + "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POLYGON((1 2,3 4,5 6,1 2))))"); + + + do_geom_test("TIN(((1 2,3 4,5 6,1 2)),((7 8,9 10,11 12,7 8)))", + "GEOMETRYCOLLECTION(POLYGON((1 2,3 4,5 6,1 2)),POLYGON((7 8,9 10,11 12,7 8)))"); + + do_geom_test("GEOMETRYCOLLECTION(TIN(((1 2,3 4,5 6,1 2)),((7 8,9 10,11 12,7 8))))", + "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POLYGON((1 2,3 4,5 6,1 2)),POLYGON((7 8,9 10,11 12,7 8))))"); + + do_geom_test("GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(TIN(((1 2,3 4,5 6,1 2)),((7 8,9 10,11 12,7 8)))))", + "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POLYGON((1 2,3 4,5 6,1 2)),POLYGON((7 8,9 10,11 12,7 8)))))"); + + + do_geom_test("POLYHEDRALSURFACE(((1 2,3 4,5 6,1 2)),((7 8,9 10,11 12,7 8)))", + "GEOMETRYCOLLECTION(POLYGON((1 2,3 4,5 6,1 2)),POLYGON((7 8,9 10,11 12,7 8)))"); + + do_geom_test("GEOMETRYCOLLECTION(POLYHEDRALSURFACE(((1 2,3 4,5 6,1 2)),((7 8,9 10,11 12,7 8))))", + "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POLYGON((1 2,3 4,5 6,1 2)),POLYGON((7 8,9 10,11 12,7 8))))"); + + do_geom_test("GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POLYGON((1 2,3 4,5 6,1 2)),POLYGON((7 8,9 10,11 12,7 8)))))", + "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POLYGON((1 2,3 4,5 6,1 2)),POLYGON((7 8,9 10,11 12,7 8)))))"); + +} + +static void test_sfs_11(void) +{ + do_geom_test("POINT(1 2)", + "POINT(1 2)"); + + do_geom_test("LINESTRING(1 2,3 4)", + "LINESTRING(1 2,3 4)"); + + do_geom_test("POLYGON((1 2,3 4,5 6,1 2))", + "POLYGON((1 2,3 4,5 6,1 2))"); + + do_geom_test("POLYGON((1 2,3 4,5 6,1 2),(7 8,9 10,11 12,7 8))", + "POLYGON((1 2,3 4,5 6,1 2),(7 8,9 10,11 12,7 8))"); + + do_geom_test("MULTIPOINT(1 2,3 4)", + "MULTIPOINT(1 2,3 4)"); + + do_geom_test("MULTILINESTRING((1 2,3 4),(5 6,7 8))", + "MULTILINESTRING((1 2,3 4),(5 6,7 8))"); + + do_geom_test("MULTIPOLYGON(((1 2,3 4,5 6,1 2)),((7 8,9 10,11 12,7 8)))", + "MULTIPOLYGON(((1 2,3 4,5 6,1 2)),((7 8,9 10,11 12,7 8)))"); + + do_geom_test("MULTIPOLYGON(((1 2,3 4,5 6,1 2),(7 8,9 10,11 12,7 8)),((13 14,15 16,17 18,13 14)))", + "MULTIPOLYGON(((1 2,3 4,5 6,1 2),(7 8,9 10,11 12,7 8)),((13 14,15 16,17 18,13 14)))"); + + do_geom_test("GEOMETRYCOLLECTION(POINT(1 2),LINESTRING(3 4,5 6))", + "GEOMETRYCOLLECTION(POINT(1 2),LINESTRING(3 4,5 6))"); + + do_geom_test("GEOMETRYCOLLECTION EMPTY", + "GEOMETRYCOLLECTION EMPTY"); + + /* SRID */ + do_geom_test("SRID=4326;GEOMETRYCOLLECTION EMPTY", + "SRID=4326;GEOMETRYCOLLECTION EMPTY"); + + do_geom_test("SRID=4326;POINT(1 2)", + "SRID=4326;POINT(1 2)"); + + + /* 3D and 4D */ + /* SFS 1.2 is only 2D but we choose here to keep 3D and 4D, + and let the user use force_2d if he want/need it */ + do_geom_test("POINT(1 2 3)", + "POINT(1 2 3)"); + + do_geom_test("POINTM(1 2 3)", + "POINTM(1 2 3)"); + + do_geom_test("POINT(1 2 3 4)", + "POINT(1 2 3 4)"); +} + +/* +** Used by test harness to register the tests in this file. +*/ +void force_sfs_suite_setup(void); +void force_sfs_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("force_sfs", NULL, NULL); + PG_ADD_TEST(suite, test_sfs_11); + PG_ADD_TEST(suite, test_sfs_12); + PG_ADD_TEST(suite, test_sqlmm); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_geodetic.c b/mgist-postgis/liblwgeom/cunit/cu_geodetic.c new file mode 100644 index 0000000..1250647 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_geodetic.c @@ -0,0 +1,1647 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2009 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + * Note: Geodesic measurements have been independently verified using + * GeographicLib v 1.37 with MPFR C++ using utilities GeodSolve and + * Planimeter, with -E "exact" flag with the following env vars: + * export GEOGRAPHICLIB_DIGITS=1000 + * export WGS84_ELIPSOID="6378137 298.257223563" + * export WGS84_SPHERE="6371008.7714150598325213222 0" + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "lwgeodetic.h" +#include "cu_tester.h" + +#define RANDOM_TEST 0 + +/** +* Convert an edge from degrees to radians. +*/ +static void edge_deg2rad(GEOGRAPHIC_EDGE *e) +{ + (e->start).lat = deg2rad((e->start).lat); + (e->end).lat = deg2rad((e->end).lat); + (e->start).lon = deg2rad((e->start).lon); + (e->end).lon = deg2rad((e->end).lon); +} + +/** +* Convert an edge from radians to degrees. +static void edge_rad2deg(GEOGRAPHIC_EDGE *e) +{ + (e->start).lat = rad2deg((e->start).lat); + (e->end).lat = rad2deg((e->end).lat); + (e->start).lon = rad2deg((e->start).lon); + (e->end).lon = rad2deg((e->end).lon); +} +*/ + +/** +* Convert a point from degrees to radians. +*/ +static void point_deg2rad(GEOGRAPHIC_POINT *p) +{ + p->lat = latitude_radians_normalize(deg2rad(p->lat)); + p->lon = longitude_radians_normalize(deg2rad(p->lon)); +} + +/** +* Convert a point from radians to degrees. +*/ +static void point_rad2deg(GEOGRAPHIC_POINT *p) +{ + p->lat = rad2deg(p->lat); + p->lon = rad2deg(p->lon); +} + +static void test_sphere_direction(void) +{ + GEOGRAPHIC_POINT s, e; + double dir, dist; + + geographic_point_init(0, 0, &s); + geographic_point_init(1, 0, &e); + dist = sphere_distance(&s, &e); + dir = sphere_direction(&s, &e, dist); + /* GeodSolve -i -E -p 16 -e 1 0 --input-string "0 0 0 1" */ + CU_ASSERT_DOUBLE_EQUAL(dir, M_PI_2, 1e-14); + CU_ASSERT_DOUBLE_EQUAL(dist, 0.0174532925199433, 1e-14); + + geographic_point_init(0, 0, &s); + geographic_point_init(0, 1, &e); + dist = sphere_distance(&s, &e); + dir = sphere_direction(&s, &e, dist); + /* GeodSolve -i -E -p 16 -e 1 0 --input-string "0 0 1 0" */ + CU_ASSERT_DOUBLE_EQUAL(dir, 0.0, 1e-14); + CU_ASSERT_DOUBLE_EQUAL(dist, 0.0174532925199433, 1e-14); + +} + +static void test_sphere_project(void) +{ + GEOGRAPHIC_POINT s, e; + double dir1, dist1, dir2, dist2; + + dir1 = M_PI_2; + dist1 = 0.1; + + geographic_point_init(0, 0, &s); + sphere_project(&s, dist1, dir1, &e); + + CU_ASSERT_DOUBLE_EQUAL(e.lon, 0.1, 1e-14); + CU_ASSERT_DOUBLE_EQUAL(e.lat, 0.0, 1e-14); + + /* Direct and inverse solutions agree */ + dist2 = sphere_distance(&s, &e); + dir2 = sphere_direction(&s, &e, dist1); + + CU_ASSERT_DOUBLE_EQUAL(dist1, dist2, 1e-14); + CU_ASSERT_DOUBLE_EQUAL(dir1, dir2, 1e-14); + + dist1 = sphere_distance(&e, &s); + dir1 = sphere_direction(&e, &s, dist1); + sphere_project(&e, dist1, dir1, &s); + + CU_ASSERT_DOUBLE_EQUAL(s.lon, 0.0, 1e-14); + CU_ASSERT_DOUBLE_EQUAL(s.lat, 0.0, 1e-14); + + geographic_point_init(0, 0.2, &e); + geographic_point_init(0, 0.4, &s); + dist1 = sphere_distance(&s, &e); + dir1 = sphere_direction(&e, &s, dist1); + /* GeodSolve -i -E -p 16 -e 1 0 --input-string "0.2 0 0.4 0" */ + CU_ASSERT_DOUBLE_EQUAL(dir1, 0.0, 1e-14); + CU_ASSERT_DOUBLE_EQUAL(dist1, 0.0034906585039887, 1e-14); + + geographic_point_init(0, 1, &s); /* same start point for remainder of tests */ + geographic_point_init(0, 2, &e); + dist2 = sphere_distance(&s, &e); + dir2 = sphere_direction(&s, &e, dist2); + /* GeodSolve -i -E -p 16 -e 1 0 --input-string "1 0 2 0" */ + CU_ASSERT_DOUBLE_EQUAL(dir2, 0.0, 1e-14); + CU_ASSERT_DOUBLE_EQUAL(dist2, 0.0174532925199433, 1e-14); + + geographic_point_init(1, 1, &e); + dist2 = sphere_distance(&s, &e); + dir2 = sphere_direction(&s, &e, dist2); + /* GeodSolve -i -E -p 16 -e 1 0 --input-string "1 0 1 1" */ + CU_ASSERT_DOUBLE_EQUAL(dir2, 89.991273575329292895136 * M_PI / 180.0, 1e-14); + CU_ASSERT_DOUBLE_EQUAL(dist2, 0.0174506342314906, 1e-14); + + geographic_point_init(0, 0, &e); + dist2 = sphere_distance(&s, &e); + dir2 = sphere_direction(&s, &e, dist2); + /* GeodSolve -i -E -p 16 -e 1 0 --input-string "1 0 0 0" */ + CU_ASSERT_DOUBLE_EQUAL(dir2, M_PI, 1e-14); + CU_ASSERT_DOUBLE_EQUAL(dist2, 0.0174532925199433, 1e-14); + + geographic_point_init(-1, 1, &e); + dist2 = sphere_distance(&s, &e); + dir2 = sphere_direction(&s, &e, dist2); + /* GeodSolve -i -E -p 16 -e 1 0 --input-string "1 0 1 -1" */ + CU_ASSERT_DOUBLE_EQUAL(dir2, -89.991273575329292895136 * M_PI / 180.0, 1e-14); + CU_ASSERT_DOUBLE_EQUAL(dist2, 0.0174506342314906, 1e-14); + + geographic_point_init(1, 2, &e); + dist2 = sphere_distance(&s, &e); + dir2 = sphere_direction(&s, &e, dist2); + /* GeodSolve -i -E -p 16 -e 1 0 --input-string "1 0 2 1" */ + CU_ASSERT_DOUBLE_EQUAL(dir2, 44.978182941465044354783 * M_PI / 180.0, 1e-14); + CU_ASSERT_DOUBLE_EQUAL(dist2, 0.0246782972905467, 1e-14); + + geographic_point_init(-1, 0, &e); + dist2 = sphere_distance(&s, &e); + dir2 = sphere_direction(&s, &e, dist2); + /* GeodSolve -i -E -p 16 -e 1 0 --input-string "1 0 0 -1" */ + CU_ASSERT_DOUBLE_EQUAL(dir2, -134.995636455344851488216 * M_PI / 180.0, 1e-14); + CU_ASSERT_DOUBLE_EQUAL(dist2, 0.0246820563917664, 1e-14); +} + +#if 0 +/** +* Tests the relative numerical stability of the "robust" and +* naive cross product calculation methods. +*/ +static void cross_product_stability(void) +{ + POINT2D p1, p2; + int i; + GEOGRAPHIC_POINT g1, g2; + POINT3D A1, A2; + POINT3D Nr, Nc; + POINT3D Or, Oc; + + p1.x = 10.0; + p1.y = 45.0; + p2.x = 10.0; + p2.y = 50.0; + + geographic_point_init(p1.x, p1.y, &g1); + ll2cart(&p1, &A1); + + for ( i = 0; i < 40; i++ ) + { + geographic_point_init(p2.x, p2.y, &g2); + ll2cart(&p2, &A2); + + /* Skea */ + robust_cross_product(&g1, &g2, &Nr); + normalize(&Nr); + + /* Ramsey */ + unit_normal(&A1, &A2, &Nc); + + if ( i > 0 ) + { + printf("\n- %d -------------------- %.24g ------------------------\n", i, p2.y); + printf("Skea: %.24g,%.24g,%.24g\n", Nr.x, Nr.y, Nr.z); + printf("Skea Diff: %.24g,%.24g,%.24g\n", Or.x-Nr.x, Or.y-Nr.y, Or.z-Nr.z); + printf("Ramsey: %.24g,%.24g,%.24g\n", Nc.x, Nc.y, Nc.z); + printf("Ramsey Diff: %.24g,%.24g,%.24g\n", Oc.x-Nc.x, Oc.y-Nc.y, Oc.z-Nc.z); + printf("Diff: %.24g,%.24g,%.24g\n", Nr.x-Nc.x, Nr.y-Nc.y, Nr.z-Nc.z); + } + + Or = Nr; + Oc = Nc; + + p2.y += (p1.y - p2.y)/2.0; + } +} +#endif + +static void test_gbox_from_spherical_coordinates(void) +{ +#if RANDOM_TEST + const double gtolerance = 0.000001; + const int loops = RANDOM_TEST; + int i; + double ll[64]; + GBOX gbox; + GBOX gbox_slow; + int rndlat; + int rndlon; + + POINTARRAY *pa; + LWGEOM *lwline; + + ll[0] = -3.083333333333333333333333333333333; + ll[1] = 9.83333333333333333333333333333333; + ll[2] = 15.5; + ll[3] = -5.25; + + pa = ptarray_construct_reference_data(0, 0, 2, (uint8_t*)ll); + + lwline = lwline_as_lwgeom(lwline_construct(SRID_UNKNOWN, 0, pa)); + FLAGS_SET_GEODETIC(lwline->flags, 1); + + srandomdev(); + + for ( i = 0; i < loops; i++ ) + { + rndlat = (int)(90.0 - 180.0 * (double)random() / pow(2.0, 31.0)); + rndlon = (int)(180.0 - 360.0 * (double)random() / pow(2.0, 31.0)); + ll[0] = (double)rndlon; + ll[1] = (double)rndlat; + + rndlat = (int)(90.0 - 180.0 * (double)random() / pow(2.0, 31.0)); + rndlon = (int)(180.0 - 360.0 * (double)random() / pow(2.0, 31.0)); + ll[2] = (double)rndlon; + ll[3] = (double)rndlat; + + gbox_geocentric_slow = LW_FALSE; + lwgeom_calculate_gbox_geodetic(lwline, &gbox); + gbox_geocentric_slow = LW_TRUE; + lwgeom_calculate_gbox_geodetic(lwline, &gbox_slow); + gbox_geocentric_slow = LW_FALSE; + + if ( + ( fabs( gbox.xmin - gbox_slow.xmin ) > gtolerance ) || + ( fabs( gbox.xmax - gbox_slow.xmax ) > gtolerance ) || + ( fabs( gbox.ymin - gbox_slow.ymin ) > gtolerance ) || + ( fabs( gbox.ymax - gbox_slow.ymax ) > gtolerance ) || + ( fabs( gbox.zmin - gbox_slow.zmin ) > gtolerance ) || + ( fabs( gbox.zmax - gbox_slow.zmax ) > gtolerance ) ) + { + printf("\n-------\n"); + printf("If you are seeing this, cut and paste it, it is a randomly generated test case!\n"); + printf("LOOP: %d\n", i); + printf("SEGMENT (Lon Lat): (%.9g %.9g) (%.9g %.9g)\n", ll[0], ll[1], ll[2], ll[3]); + printf("CALC: %s\n", gbox_to_string(&gbox)); + printf("SLOW: %s\n", gbox_to_string(&gbox_slow)); + printf("-------\n\n"); + CU_FAIL_FATAL(Slow (GOOD) and fast (CALC) box calculations returned different values!!); + } + + } + + lwgeom_free(lwline); +#endif /* RANDOM_TEST */ +} + +#include "cu_geodetic_data.h" + +static void test_gserialized_get_gbox_geocentric(void) +{ + LWGEOM *lwg; + GBOX gbox, gbox_slow; + int i; + + for ( i = 0; i < gbox_data_length; i++ ) + { +#if 0 +// if ( i != 0 ) continue; /* skip our bad case */ + printf("\n\n------------\n"); + printf("%s\n", gbox_data[i]); +#endif + lwg = lwgeom_from_wkt(gbox_data[i], LW_PARSER_CHECK_NONE); + FLAGS_SET_GEODETIC(lwg->flags, 1); + gbox_geocentric_slow = LW_FALSE; + lwgeom_calculate_gbox(lwg, &gbox); + gbox_geocentric_slow = LW_TRUE; + lwgeom_calculate_gbox(lwg, &gbox_slow); + gbox_geocentric_slow = LW_FALSE; + lwgeom_free(lwg); +#if 0 + printf("\nCALC: %s\n", gbox_to_string(&gbox)); + printf("GOOD: %s\n", gbox_to_string(&gbox_slow)); + printf("line %d: diff %.9g\n", i, fabs(gbox.xmin - gbox_slow.xmin)+fabs(gbox.ymin - gbox_slow.ymin)+fabs(gbox.zmin - gbox_slow.zmin)); + printf("------------\n"); +#endif + CU_ASSERT_DOUBLE_EQUAL(gbox.xmin, gbox_slow.xmin, 0.00000001); + CU_ASSERT_DOUBLE_EQUAL(gbox.ymin, gbox_slow.ymin, 0.00000001); + CU_ASSERT_DOUBLE_EQUAL(gbox.zmin, gbox_slow.zmin, 0.00000001); + CU_ASSERT_DOUBLE_EQUAL(gbox.xmax, gbox_slow.xmax, 0.00000001); + CU_ASSERT_DOUBLE_EQUAL(gbox.ymax, gbox_slow.ymax, 0.00000001); + CU_ASSERT_DOUBLE_EQUAL(gbox.zmax, gbox_slow.zmax, 0.00000001); + } + +} + +/* +* Build LWGEOM on top of *aligned* structure so we can use the read-only +* point access methods on them. +static LWGEOM* lwgeom_over_gserialized(char *wkt) +{ + LWGEOM *lwg; + GSERIALIZED *g; + + lwg = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_NONE); + g = gserialized_from_lwgeom(lwg, 0); + lwgeom_free(lwg); + return lwgeom_from_gserialized(g); +} +*/ + +static void edge_set(double lon1, double lat1, double lon2, double lat2, GEOGRAPHIC_EDGE *e) +{ + e->start.lon = lon1; + e->start.lat = lat1; + e->end.lon = lon2; + e->end.lat = lat2; + edge_deg2rad(e); +} + +static void point_set(double lon, double lat, GEOGRAPHIC_POINT *p) +{ + p->lon = lon; + p->lat = lat; + point_deg2rad(p); +} + +static void test_clairaut(void) +{ + + GEOGRAPHIC_POINT gs, ge; + POINT3D vs, ve; + GEOGRAPHIC_POINT g_out_top, g_out_bottom, v_out_top, v_out_bottom; + + point_set(-45.0, 60.0, &gs); + point_set(135.0, 60.0, &ge); + + geog2cart(&gs, &vs); + geog2cart(&ge, &ve); + + clairaut_cartesian(&vs, &ve, &v_out_top, &v_out_bottom); + clairaut_geographic(&gs, &ge, &g_out_top, &g_out_bottom); + + CU_ASSERT_DOUBLE_EQUAL(v_out_top.lat, g_out_top.lat, 0.000001); + CU_ASSERT_DOUBLE_EQUAL(v_out_top.lon, g_out_top.lon, 0.000001); + CU_ASSERT_DOUBLE_EQUAL(v_out_bottom.lat, g_out_bottom.lat, 0.000001); + CU_ASSERT_DOUBLE_EQUAL(v_out_bottom.lon, g_out_bottom.lon, 0.000001); + + gs.lat = 1.3021240033804449; + ge.lat = 1.3021240033804449; + gs.lon = -1.3387392931438733; + ge.lon = 1.80285336044592; + + geog2cart(&gs, &vs); + geog2cart(&ge, &ve); + + clairaut_cartesian(&vs, &ve, &v_out_top, &v_out_bottom); + clairaut_geographic(&gs, &ge, &g_out_top, &g_out_bottom); + + CU_ASSERT_DOUBLE_EQUAL(v_out_top.lat, g_out_top.lat, 0.000001); + CU_ASSERT_DOUBLE_EQUAL(v_out_top.lon, g_out_top.lon, 0.000001); + CU_ASSERT_DOUBLE_EQUAL(v_out_bottom.lat, g_out_bottom.lat, 0.000001); + CU_ASSERT_DOUBLE_EQUAL(v_out_bottom.lon, g_out_bottom.lon, 0.000001); +} + +static void test_edge_intersection(void) +{ + GEOGRAPHIC_EDGE e1, e2; + GEOGRAPHIC_POINT g; + int rv; + + /* Covers case, end-to-end intersection */ + edge_set(50, -10.999999999999998224, -10.0, 50.0, &e1); + edge_set(-10.0, 50.0, -10.272779983831613393, -16.937003313332997578, &e2); + rv = edge_intersection(&e1, &e2, &g); + CU_ASSERT_EQUAL(rv, LW_TRUE); + + /* Medford case, very short segment vs very long one */ + e1.start.lat = 0.74123572595649878103; + e1.start.lon = -2.1496353191142714145; + e1.end.lat = 0.74123631950116664058; + e1.end.lon = -2.1496353248304860273; + e2.start.lat = 0.73856343764436815924; + e2.start.lon = -2.1461493501950630325; + e2.end.lat = 0.70971354024834598651; + e2.end.lon = 2.1082194552519770703; + rv = edge_intersection(&e1, &e2, &g); + CU_ASSERT_EQUAL(rv, LW_FALSE); + + /* Again, this time with a less exact input edge. */ + edge_set(-123.165031277506, 42.4696787216231, -123.165031605021, 42.4697127292275, &e1); + rv = edge_intersection(&e1, &e2, &g); + CU_ASSERT_EQUAL(rv, LW_FALSE); + + /* Second Medford case, very short segment vs very long one + e1.start.lat = 0.73826546728290887156; + e1.start.lon = -2.14426380171833042; + e1.end.lat = 0.73826545883786642843; + e1.end.lon = -2.1442638997530165668; + e2.start.lat = 0.73775469118192538165; + e2.start.lon = -2.1436035534281718817; + e2.end.lat = 0.71021099548296817705; + e2.end.lon = 2.1065275171200439353; + rv = edge_intersection(e1, e2, &g); + CU_ASSERT_EQUAL(rv, LW_FALSE); + */ + + /* Intersection at (0 0) */ + edge_set(-1.0, 0.0, 1.0, 0.0, &e1); + edge_set(0.0, -1.0, 0.0, 1.0, &e2); + rv = edge_intersection(&e1, &e2, &g); + point_rad2deg(&g); + CU_ASSERT_DOUBLE_EQUAL(g.lat, 0.0, 0.00001); + CU_ASSERT_DOUBLE_EQUAL(g.lon, 0.0, 0.00001); + CU_ASSERT_EQUAL(rv, LW_TRUE); + + /* No intersection at (0 0) */ + edge_set(-1.0, 0.0, 1.0, 0.0, &e1); + edge_set(0.0, -1.0, 0.0, -2.0, &e2); + rv = edge_intersection(&e1, &e2, &g); + CU_ASSERT_EQUAL(rv, LW_FALSE); + + /* End touches middle of segment at (0 0) */ + edge_set(-1.0, 0.0, 1.0, 0.0, &e1); + edge_set(0.0, -1.0, 0.0, 0.0, &e2); + rv = edge_intersection(&e1, &e2, &g); + point_rad2deg(&g); +#if 0 + printf("\n"); + printf("LINESTRING(%.15g %.15g, %.15g %.15g)\n", e1.start.lon, e1.start.lat, e1.end.lon, e1.end.lat); + printf("LINESTRING(%.15g %.15g, %.15g %.15g)\n", e2.start.lon, e2.start.lat, e2.end.lon, e2.end.lat); + printf("g = (%.15g %.15g)\n", g.lon, g.lat); + printf("rv = %d\n", rv); +#endif + CU_ASSERT_DOUBLE_EQUAL(g.lon, 0.0, 0.00001); + CU_ASSERT_EQUAL(rv, LW_TRUE); + + /* End touches end of segment at (0 0) */ + edge_set(0.0, 0.0, 1.0, 0.0, &e1); + edge_set(0.0, -1.0, 0.0, 0.0, &e2); + rv = edge_intersection(&e1, &e2, &g); + point_rad2deg(&g); +#if 0 + printf("\n"); + printf("LINESTRING(%.15g %.15g, %.15g %.15g)\n", e1.start.lon, e1.start.lat, e1.end.lon, e1.end.lat); + printf("LINESTRING(%.15g %.15g, %.15g %.15g)\n", e2.start.lon, e2.start.lat, e2.end.lon, e2.end.lat); + printf("g = (%.15g %.15g)\n", g.lon, g.lat); + printf("rv = %d\n", rv); +#endif + CU_ASSERT_DOUBLE_EQUAL(g.lat, 0.0, 0.00001); + CU_ASSERT_DOUBLE_EQUAL(g.lon, 0.0, 0.00001); + CU_ASSERT_EQUAL(rv, LW_TRUE); + + /* Intersection at (180 0) */ + edge_set(-179.0, -1.0, 179.0, 1.0, &e1); + edge_set(-179.0, 1.0, 179.0, -1.0, &e2); + rv = edge_intersection(&e1, &e2, &g); + point_rad2deg(&g); + CU_ASSERT_DOUBLE_EQUAL(g.lat, 0.0, 0.00001); + CU_ASSERT_DOUBLE_EQUAL(fabs(g.lon), 180.0, 0.00001); + CU_ASSERT_EQUAL(rv, LW_TRUE); + + /* Intersection at (180 0) */ + edge_set(-170.0, 0.0, 170.0, 0.0, &e1); + edge_set(180.0, -10.0, 180.0, 10.0, &e2); + rv = edge_intersection(&e1, &e2, &g); + point_rad2deg(&g); + CU_ASSERT_DOUBLE_EQUAL(g.lat, 0.0, 0.00001); + CU_ASSERT_DOUBLE_EQUAL(fabs(g.lon), 180.0, 0.00001); + CU_ASSERT_EQUAL(rv, LW_TRUE); + + /* Intersection at north pole */ + edge_set(-180.0, 80.0, 0.0, 80.0, &e1); + edge_set(90.0, 80.0, -90.0, 80.0, &e2); + rv = edge_intersection(&e1, &e2, &g); + point_rad2deg(&g); + CU_ASSERT_DOUBLE_EQUAL(g.lat, 90.0, 0.00001); + CU_ASSERT_EQUAL(rv, LW_TRUE); + + /* Equal edges return true */ + edge_set(45.0, 10.0, 50.0, 20.0, &e1); + edge_set(45.0, 10.0, 50.0, 20.0, &e2); + rv = edge_intersection(&e1, &e2, &g); + point_rad2deg(&g); + CU_ASSERT_EQUAL(rv, LW_TRUE); + + /* Parallel edges (same great circle, different end points) return true */ + edge_set(40.0, 0.0, 70.0, 0.0, &e1); + edge_set(60.0, 0.0, 50.0, 0.0, &e2); + rv = edge_intersection(&e1, &e2, &g); + point_rad2deg(&g); + CU_ASSERT_EQUAL(rv, 2); /* Hack, returning 2 as the 'co-linear' value */ + + /* End touches arc at north pole */ + edge_set(-180.0, 80.0, 0.0, 80.0, &e1); + edge_set(90.0, 80.0, -90.0, 90.0, &e2); + rv = edge_intersection(&e1, &e2, &g); + point_rad2deg(&g); +#if 0 + printf("\n"); + printf("LINESTRING(%.15g %.15g, %.15g %.15g)\n", e1.start.lon, e1.start.lat, e1.end.lon, e1.end.lat); + printf("LINESTRING(%.15g %.15g, %.15g %.15g)\n", e2.start.lon, e2.start.lat, e2.end.lon, e2.end.lat); + printf("g = (%.15g %.15g)\n", g.lon, g.lat); + printf("rv = %d\n", rv); +#endif + CU_ASSERT_DOUBLE_EQUAL(g.lat, 90.0, 0.00001); + CU_ASSERT_EQUAL(rv, LW_TRUE); + + /* End touches end at north pole */ + edge_set(-180.0, 80.0, 0.0, 90.0, &e1); + edge_set(90.0, 80.0, -90.0, 90.0, &e2); + rv = edge_intersection(&e1, &e2, &g); + point_rad2deg(&g); +#if 0 + printf("\n"); + printf("LINESTRING(%.15g %.15g, %.15g %.15g)\n", e1.start.lon, e1.start.lat, e1.end.lon, e1.end.lat); + printf("LINESTRING(%.15g %.15g, %.15g %.15g)\n", e2.start.lon, e2.start.lat, e2.end.lon, e2.end.lat); + printf("g = (%.15g %.15g)\n", g.lon, g.lat); + printf("rv = %d\n", rv); +#endif + CU_ASSERT_DOUBLE_EQUAL(g.lat, 90.0, 0.00001); + CU_ASSERT_EQUAL(rv, LW_TRUE); + +} + +static void line2pts(const char *wkt, POINT3D *A1, POINT3D *A2) +{ + LWLINE *l = (LWLINE*)lwgeom_from_wkt(wkt, LW_PARSER_CHECK_NONE); + POINTARRAY *pa; + POINT2D p1, p2; + GEOGRAPHIC_POINT g1, g2; + if ( ! l ) + { + printf("BAD WKT FOUND in test_edge_intersects:\n %s\n\n", wkt); + exit(0); + } + pa = l->points; + getPoint2d_p(pa, 0, &p1); + getPoint2d_p(pa, 1, &p2); + geographic_point_init(p1.x, p1.y, &g1); + geographic_point_init(p2.x, p2.y, &g2); + geog2cart(&g1, A1); + geog2cart(&g2, A2); + lwline_free(l); + return; +} + +static void test_edge_intersects(void) +{ + POINT3D A1, A2, B1, B2; + GEOGRAPHIC_POINT g; + uint32_t rv; + + /* 5m close case */ + line2pts("LINESTRING(58.5112113206308 0, 58.511211320077201 0.00090193752520337797)", &A1, &A2); + line2pts("LINESTRING(58.511166525601702 0.00027058124084120699, 58.511166525562899 0.00036077498778824899)", &B1, &B2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv == 0); + + /* Covers case, end-to-end intersection */ + line2pts("LINESTRING(50 -10.999999999999998224, -10.0 50.0)", &A1, &A2); + line2pts("LINESTRING(-10.0 50.0, -10.272779983831613393 -16.937003313332997578)", &B1, &B2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv & PIR_INTERSECTS); + + /* Medford case, very short segment vs very long one */ + g.lat = 0.74123572595649878103; + g.lon = -2.1496353191142714145; + geog2cart(&g, &A1); + g.lat = 0.74123631950116664058; + g.lon = -2.1496353248304860273; + geog2cart(&g, &A2); + g.lat = 0.73856343764436815924; + g.lon = -2.1461493501950630325; + geog2cart(&g, &B1); + g.lat = 0.70971354024834598651; + g.lon = 2.1082194552519770703; + geog2cart(&g, &B2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv == 0); + + /* Second Medford case, very short segment vs very long one */ + g.lat = 0.73826546728290887156; + g.lon = -2.14426380171833042; + geog2cart(&g, &A1); + g.lat = 0.73826545883786642843; + g.lon = -2.1442638997530165668; + geog2cart(&g, &A2); + g.lat = 0.73775469118192538165; + g.lon = -2.1436035534281718817; + geog2cart(&g, &B1); + g.lat = 0.71021099548296817705; + g.lon = 2.1065275171200439353; + geog2cart(&g, &B2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv == PIR_INTERSECTS); + + /* Again, this time with a less exact input edge. */ + line2pts("LINESTRING(-123.165031277506 42.4696787216231, -123.165031605021 42.4697127292275)", &A1, &A2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv == 0); + + /* Intersection at (0 0) */ + line2pts("LINESTRING(-1.0 0.0, 1.0 0.0)", &A1, &A2); + line2pts("LINESTRING(0.0 -1.0, 0.0 1.0)", &B1, &B2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv == PIR_INTERSECTS); + + /* No intersection at (0 0) */ + line2pts("LINESTRING(-1.0 0.0, 1.0 0.0)", &A1, &A2); + line2pts("LINESTRING(0.0 -1.0, 0.0 -2.0)", &B1, &B2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv == 0); + + /* End touches middle of segment at (0 0) */ + line2pts("LINESTRING(-1.0 0.0, 1.0 0.0)", &A1, &A2); + line2pts("LINESTRING(0.0 -1.0, 0.0 0.0)", &B1, &B2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv == (PIR_INTERSECTS|PIR_B_TOUCH_RIGHT) ); + + /* End touches end of segment at (0 0) */ + line2pts("LINESTRING(0.0 0.0, 1.0 0.0)", &A1, &A2); + line2pts("LINESTRING(0.0 -1.0, 0.0 0.0)", &B1, &B2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv == (PIR_INTERSECTS|PIR_B_TOUCH_RIGHT|PIR_A_TOUCH_RIGHT) ); + + /* Intersection at (180 0) */ + line2pts("LINESTRING(-179.0 -1.0, 179.0 1.0)", &A1, &A2); + line2pts("LINESTRING(-179.0 1.0, 179.0 -1.0)", &B1, &B2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv == PIR_INTERSECTS); + + /* Intersection at (180 0) */ + line2pts("LINESTRING(-170.0 0.0, 170.0 0.0)", &A1, &A2); + line2pts("LINESTRING(180.0 -10.0, 180.0 10.0)", &B1, &B2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv == PIR_INTERSECTS); + + /* Intersection at north pole */ + line2pts("LINESTRING(-180.0 80.0, 0.0 80.0)", &A1, &A2); + line2pts("LINESTRING(90.0 80.0, -90.0 80.0)", &B1, &B2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv == PIR_INTERSECTS); + + /* Equal edges return true */ + line2pts("LINESTRING(45.0 10.0, 50.0 20.0)", &A1, &A2); + line2pts("LINESTRING(45.0 10.0, 50.0 20.0)", &B1, &B2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv & PIR_INTERSECTS); + + /* Parallel edges (same great circle, different end points) return true */ + line2pts("LINESTRING(40.0 0.0, 70.0 0.0)", &A1, &A2); + line2pts("LINESTRING(60.0 0.0, 50.0 0.0)", &B1, &B2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv == (PIR_INTERSECTS|PIR_COLINEAR) ); + + /* End touches arc at north pole */ + line2pts("LINESTRING(-180.0 80.0, 0.0 80.0)", &A1, &A2); + line2pts("LINESTRING(90.0 80.0, -90.0 90.0)", &B1, &B2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv == (PIR_INTERSECTS|PIR_B_TOUCH_LEFT) ); + + /* End touches end at north pole */ + line2pts("LINESTRING(-180.0 80.0, 0.0 90.0)", &A1, &A2); + line2pts("LINESTRING(90.0 80.0, -90.0 90.0)", &B1, &B2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv == (PIR_INTERSECTS|PIR_B_TOUCH_LEFT|PIR_A_TOUCH_RIGHT) ); + + /* Antipodal straddles. Great circles cross but at opposite */ + /* sides of the globe */ + /* #2534 */ + /* http://www.gcmap.com/mapui?P=60N+90E-20S+90E%0D%0A0N+0E-90.04868865037885W+57.44011727050777S%0D%0A&MS=wls&DU=mi */ + line2pts("LINESTRING(90.0 60.0, 90.0 -20.0)", &A1, &A2); + line2pts("LINESTRING(0.0 0.0, -90.04868865037885 -57.44011727050777)", &B1, &B2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv == 0); + + line2pts("LINESTRING(-5 0, 5 0)", &A1, &A2); + line2pts("LINESTRING(179 -5, 179 5)", &B1, &B2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv == 0); + + line2pts("LINESTRING(175 -85, 175 85)", &A1, &A2); + line2pts("LINESTRING(65 0, -105 0)", &B1, &B2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv == 0); + + line2pts("LINESTRING(175 -85, 175 85)", &A1, &A2); + line2pts("LINESTRING(45 0, -125 0)", &B1, &B2); + rv = edge_intersects(&A1, &A2, &B1, &B2); + CU_ASSERT(rv == 0); + +} + +static void test_edge_distance_to_point(void) +{ + GEOGRAPHIC_EDGE e; + GEOGRAPHIC_POINT g; + GEOGRAPHIC_POINT closest; + double d; + + /* closest point at origin, one degree away */ + edge_set(-50.0, 0.0, 50.0, 0.0, &e); + point_set(0.0, 1.0, &g); + d = edge_distance_to_point(&e, &g, 0); + CU_ASSERT_DOUBLE_EQUAL(d, M_PI / 180.0, 0.00001); + + /* closest point at origin, one degree away */ + edge_set(-50.0, 0.0, 50.0, 0.0, &e); + point_set(0.0, 2.0, &g); + d = edge_distance_to_point(&e, &g, &closest); +#if 0 + printf("LINESTRING(%.8g %.8g, %.8g %.8g)\n", e.start.lon, e.start.lat, e.end.lon, e.end.lat); + printf("POINT(%.9g %.9g)\n", g.lon, g.lat); + printf("\nDISTANCE == %.8g\n", d); +#endif + CU_ASSERT_DOUBLE_EQUAL(d, M_PI / 90.0, 0.00001); + CU_ASSERT_DOUBLE_EQUAL(closest.lat, 0.0, 0.00001); + CU_ASSERT_DOUBLE_EQUAL(closest.lon, 0.0, 0.00001); + + /* Ticket #2351 */ + edge_set(149.386990599235, -26.3567415843982, 149.386990599247, -26.3567415843965, &e); + point_set(149.386990599235, -26.3567415843982, &g); + d = edge_distance_to_point(&e, &g, &closest); + CU_ASSERT_DOUBLE_EQUAL(d, 0.0, 0.00001); + // printf("CLOSE POINT(%g %g)\n", closest.lon, closest.lat); + // printf(" ORIG POINT(%g %g)\n", g.lon, g.lat); + CU_ASSERT_DOUBLE_EQUAL(g.lat, closest.lat, 0.00001); + CU_ASSERT_DOUBLE_EQUAL(g.lon, closest.lon, 0.00001); +} + +static void test_edge_distance_to_edge(void) +{ + GEOGRAPHIC_EDGE e1, e2; + GEOGRAPHIC_POINT c1, c2; + double d; + + /* closest point at origin, one degree away */ + edge_set(-50.0, 0.0, 50.0, 0.0, &e1); + edge_set(-5.0, 20.0, 0.0, 1.0, &e2); + d = edge_distance_to_edge(&e1, &e2, &c1, &c2); +#if 0 + printf("LINESTRING(%.8g %.8g, %.8g %.8g)\n", e1.start.lon, e1.start.lat, e1.end.lon, e1.end.lat); + printf("LINESTRING(%.8g %.8g, %.8g %.8g)\n", e2.start.lon, e2.start.lat, e2.end.lon, e2.end.lat); + printf("\nDISTANCE == %.8g\n", d); +#endif + CU_ASSERT_DOUBLE_EQUAL(d, M_PI / 180.0, 0.00001); + CU_ASSERT_DOUBLE_EQUAL(c1.lat, 0.0, 0.00001); + CU_ASSERT_DOUBLE_EQUAL(c2.lat, M_PI / 180.0, 0.00001); + CU_ASSERT_DOUBLE_EQUAL(c1.lon, 0.0, 0.00001); + CU_ASSERT_DOUBLE_EQUAL(c2.lon, 0.0, 0.00001); +} + + + +/* +* Build LWGEOM on top of *aligned* structure so we can use the read-only +* point access methods on them. +*/ +static LWGEOM* lwgeom_over_gserialized(char *wkt, GSERIALIZED **g) +{ + LWGEOM *lwg; + + lwg = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_NONE); + FLAGS_SET_GEODETIC(lwg->flags, 1); + *g = gserialized_from_lwgeom(lwg, 0); + lwgeom_free(lwg); + return lwgeom_from_gserialized(*g); +} + +static void test_lwgeom_check_geodetic(void) +{ + LWGEOM *geom; + int i = 0; + + char ewkt[][512] = + { + "POINT(0 0.2)", + "LINESTRING(-1 -1,-1 2.5,2 2,2 -1)", + "SRID=1;MULTILINESTRING((-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1))", + "POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0))", + "SRID=4326;MULTIPOLYGON(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)),((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)))", + "SRID=4326;GEOMETRYCOLLECTION(POINT(0 1),POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0)),MULTIPOLYGON(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5))))", + "POINT(0 220.2)", + "LINESTRING(-1 -1,-1231 2.5,2 2,2 -1)", + "SRID=1;MULTILINESTRING((-1 -131,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1))", + "POLYGON((-1 -1,-1 2.5,2 2,2 -133,-1 -1),(0 0,0 1,1 1,1 0,0 0))", + "SRID=4326;MULTIPOLYGON(((-1 -1,-1 2.5,211 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)),((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)))", + "SRID=4326;GEOMETRYCOLLECTION(POINT(0 1),POLYGON((-1 -1,-1111 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0)),MULTIPOLYGON(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5))))", + }; + + for ( i = 0; i < 6; i++ ) + { + GSERIALIZED *g; + geom = lwgeom_over_gserialized(ewkt[i], &g); + CU_ASSERT_EQUAL(lwgeom_check_geodetic(geom), LW_TRUE); + lwgeom_free(geom); + lwfree(g); + } + + for ( i = 6; i < 12; i++ ) + { + GSERIALIZED *g; + //char *out_ewkt; + geom = lwgeom_over_gserialized(ewkt[i], &g); + CU_ASSERT_EQUAL(lwgeom_check_geodetic(geom), LW_FALSE); + //out_ewkt = lwgeom_to_ewkt(geom); + //printf("%s\n", out_ewkt); + lwgeom_free(geom); + lwfree(g); + } + +} + +/* +static void test_gbox_calculation(void) +{ + + LWGEOM *geom; + int i = 0; + GBOX *gbox = gbox_new(lwflags(0,0,0)); + BOX3D *box3d; + + char ewkt[][512] = + { + "POINT(0 0.2)", + "LINESTRING(-1 -1,-1 2.5,2 2,2 -1)", + "SRID=1;MULTILINESTRING((-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1))", + "POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0))", + "SRID=4326;MULTIPOLYGON(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)),((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)))", + "SRID=4326;GEOMETRYCOLLECTION(POINT(0 1),POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0)),MULTIPOLYGON(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5))))", + "POINT(0 220.2)", + "LINESTRING(-1 -1,-1231 2.5,2 2,2 -1)", + "SRID=1;MULTILINESTRING((-1 -131,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1))", + "POLYGON((-1 -1,-1 2.5,2 2,2 -133,-1 -1),(0 0,0 1,1 1,1 0,0 0))", + "SRID=4326;MULTIPOLYGON(((-1 -1,-1 2.5,211 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)),((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)))", + "SRID=4326;GEOMETRYCOLLECTION(POINT(0 1),POLYGON((-1 -1,-1111 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0)),MULTIPOLYGON(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5))))", + }; + + for ( i = 0; i < 6; i++ ) + { + GSERIALIZED *g; + geom = lwgeom_over_gserialized(ewkt[i], &g); + lwgeom_calculate_gbox_cartesian(geom, gbox); + box3d = lwgeom_compute_box3d(geom); + //printf("%g %g\n", gbox->xmin, box3d->xmin); + CU_ASSERT_EQUAL(gbox->xmin, box3d->xmin); + CU_ASSERT_EQUAL(gbox->xmax, box3d->xmax); + CU_ASSERT_EQUAL(gbox->ymin, box3d->ymin); + CU_ASSERT_EQUAL(gbox->ymax, box3d->ymax); + lwgeom_free(geom); + lwfree(box3d); + lwfree(g); + } + lwfree(gbox); +} +*/ + +static void test_gserialized_from_lwgeom(void) +{ + LWGEOM *geom; + GSERIALIZED *g; + uint32_t type; + double *inspect; /* To poke right into the blob. */ + + geom = lwgeom_from_wkt("POINT(0 0.2)", LW_PARSER_CHECK_NONE); + FLAGS_SET_GEODETIC(geom->flags, 1); + g = gserialized_from_lwgeom(geom, 0); + type = gserialized_get_type(g); + CU_ASSERT_EQUAL( type, POINTTYPE ); + inspect = (double*)g; + CU_ASSERT_EQUAL(inspect[3], 0.2); + lwgeom_free(geom); + lwfree(g); + + geom = lwgeom_from_wkt("POLYGON((-1 -1, -1 2.5, 2 2, 2 -1, -1 -1), (0 0, 0 1, 1 1, 1 0, 0 0))", LW_PARSER_CHECK_NONE); + FLAGS_SET_GEODETIC(geom->flags, 1); + g = gserialized_from_lwgeom(geom, 0); + type = gserialized_get_type(g); + CU_ASSERT_EQUAL( type, POLYGONTYPE ); + inspect = (double*)g; + CU_ASSERT_EQUAL(inspect[9], 2.5); + lwgeom_free(geom); + lwfree(g); + + geom = lwgeom_from_wkt("MULTILINESTRING((0 0, 1 1),(0 0.1, 1 1))", LW_PARSER_CHECK_NONE); + FLAGS_SET_GEODETIC(geom->flags, 1); + g = gserialized_from_lwgeom(geom, 0); + type = gserialized_get_type(g); + CU_ASSERT_EQUAL( type, MULTILINETYPE ); + inspect = (double*)g; + CU_ASSERT_EQUAL(inspect[12], 0.1); + lwgeom_free(geom); + lwfree(g); + +} + +static void test_ptarray_contains_point_sphere(void) +{ + LWGEOM *lwg; + LWPOLY *poly; + POINT2D pt_to_test; + POINT2D pt_outside; + int result; + + /* Small polygon and huge distance between outside point and close-but-not-quite-inside point. Should return LW_FALSE. Pretty degenerate case. */ + lwg = lwgeom_from_hexwkb("0103000020E61000000100000025000000ACAD6F91DDB65EC03F84A86D57264540CCABC279DDB65EC0FCE6926B57264540B6DEAA62DDB65EC0A79F6B63572645402E0BE84CDDB65EC065677155572645405D0B1D39DDB65EC0316310425726454082B5DB27DDB65EC060A4E12957264540798BB619DDB65EC0C393A10D57264540D4BC160FDDB65EC0BD0320EE56264540D7AC4E08DDB65EC096C862CC56264540AFD29205DDB65EC02A1F68A956264540363AFA06DDB65EC0722E418656264540B63A780CDDB65EC06E9B0064562645409614E215DDB65EC0E09DA84356264540FF71EF22DDB65EC0B48145265626454036033F33DDB65EC081B8A60C5626454066FB4546DDB65EC08A47A6F7552645409061785BDDB65EC0F05AE0E755264540D4B63772DDB65EC05C86CEDD55264540D2E4C689DDB65EC09B6EBFD95526454082E573A1DDB65EC0C90BD5DB552645401ABE85B8DDB65EC06692FCE35526454039844ECEDDB65EC04D8AF6F155264540928319E2DDB65EC0AD8D570556264540D31055F3DDB65EC02D618F1D56264540343B7A01DEB65EC0EB70CF3956264540920A1A0CDEB65EC03B00515956264540911BE212DEB65EC0E43A0E7B56264540E3F69D15DEB65EC017E4089E562645408D903614DEB65EC0F0D42FC1562645402191B80EDEB65EC0586870E35626454012B84E05DEB65EC09166C80357264540215B41F8DDB65EC08F832B21572645408392F7E7DDB65EC01138C13A57264540F999F0D4DDB65EC0E4A9C14F57264540AC3FB8BFDDB65EC0EED6875F57264540D3DCFEA8DDB65EC04F6C996957264540ACAD6F91DDB65EC03F84A86D57264540", LW_PARSER_CHECK_NONE); + poly = (LWPOLY*)lwg; + pt_to_test.x = -122.819436560680316; + pt_to_test.y = 42.2702301207017328; + pt_outside.x = 120.695136159150778; + pt_outside.y = 40.6920926049588516; + result = ptarray_contains_point_sphere(poly->rings[0], &pt_outside, &pt_to_test); + CU_ASSERT_EQUAL(result, LW_FALSE); + lwgeom_free(lwg); + + /* Point on ring between vertexes case */ + lwg = lwgeom_from_wkt("POLYGON((1.0 1.0, 1.0 1.1, 1.1 1.1, 1.1 1.0, 1.0 1.0))", LW_PARSER_CHECK_NONE); + poly = (LWPOLY*)lwg; + pt_to_test.x = 1.1; + pt_to_test.y = 1.05; + pt_outside.x = 1.2; + pt_outside.y = 1.05; + result = ptarray_contains_point_sphere(poly->rings[0], &pt_outside, &pt_to_test); + CU_ASSERT_EQUAL(result, LW_TRUE); + lwgeom_free(lwg); + + /* Simple containment case */ + lwg = lwgeom_from_wkt("POLYGON((1.0 1.0, 1.0 1.1, 1.1 1.1, 1.1 1.0, 1.0 1.0))", LW_PARSER_CHECK_NONE); + poly = (LWPOLY*)lwg; + pt_to_test.x = 1.05; + pt_to_test.y = 1.05; + pt_outside.x = 1.2; + pt_outside.y = 1.15; + result = ptarray_contains_point_sphere(poly->rings[0], &pt_outside, &pt_to_test); + CU_ASSERT_EQUAL(result, LW_TRUE); + lwgeom_free(lwg); + + /* Less Simple containment case. */ + /* Interior point quite close to boundary and stab line going through bottom edge vertex */ + /* This breaks the "extend-it" trick of handling vertex crossings */ + /* It should also break the "lowest end" trick. */ + lwg = lwgeom_from_wkt("POLYGON((1.0 1.0, 1.0 1.1, 1.1 1.1, 1.1 1.0, 1.05 0.95, 1.0 1.0))", LW_PARSER_CHECK_NONE); + poly = (LWPOLY*)lwg; + pt_to_test.x = 1.05; + pt_to_test.y = 1.00; + pt_outside.x = 1.05; + pt_outside.y = 0.5; + result = ptarray_contains_point_sphere(poly->rings[0], &pt_outside, &pt_to_test); + CU_ASSERT_EQUAL(result, LW_TRUE); + lwgeom_free(lwg); + + /* Simple noncontainment case */ + lwg = lwgeom_from_wkt("POLYGON((1.0 1.0, 1.0 1.1, 1.1 1.1, 1.1 1.0, 1.0 1.0))", LW_PARSER_CHECK_NONE); + poly = (LWPOLY*)lwg; + pt_to_test.x = 1.05; + pt_to_test.y = 1.15; + pt_outside.x = 1.2; + pt_outside.y = 1.2; + result = ptarray_contains_point_sphere(poly->rings[0], &pt_outside, &pt_to_test); + CU_ASSERT_EQUAL(result, LW_FALSE); + lwgeom_free(lwg); + + /* Harder noncontainment case */ + lwg = lwgeom_from_wkt("POLYGON((1.0 1.0, 1.0 1.1, 1.1 1.1, 1.1 1.0, 1.0 1.0))", LW_PARSER_CHECK_NONE); + poly = (LWPOLY*)lwg; + pt_to_test.x = 1.05; + pt_to_test.y = 0.9; + pt_outside.x = 1.2; + pt_outside.y = 1.05; + result = ptarray_contains_point_sphere(poly->rings[0], &pt_outside, &pt_to_test); + CU_ASSERT_EQUAL(result, LW_FALSE); + lwgeom_free(lwg); + + /* Harder containment case */ + lwg = lwgeom_from_wkt("POLYGON((0 0, 0 2, 1 2, 0 3, 2 3, 0 4, 3 5, 0 6, 6 10, 6 1, 0 0))", LW_PARSER_CHECK_NONE); + poly = (LWPOLY*)lwg; + pt_to_test.x = 1.0; + pt_to_test.y = 1.0; + pt_outside.x = 1.0; + pt_outside.y = 10.0; + result = ptarray_contains_point_sphere(poly->rings[0], &pt_outside, &pt_to_test); + CU_ASSERT_EQUAL(result, LW_TRUE); + lwgeom_free(lwg); + + /* Point on ring at first vertex case */ + lwg = lwgeom_from_wkt("POLYGON((1.0 1.0, 1.0 1.1, 1.1 1.1, 1.1 1.0, 1.0 1.0))", LW_PARSER_CHECK_NONE); + poly = (LWPOLY*)lwg; + pt_to_test.x = 1.0; + pt_to_test.y = 1.0; + pt_outside.x = 1.2; + pt_outside.y = 1.05; + result = ptarray_contains_point_sphere(poly->rings[0], &pt_outside, &pt_to_test); + CU_ASSERT_EQUAL(result, LW_TRUE); + lwgeom_free(lwg); + + /* Point on ring at vertex case */ + lwg = lwgeom_from_wkt("POLYGON((1.0 1.0, 1.0 1.1, 1.1 1.1, 1.1 1.0, 1.0 1.0))", LW_PARSER_CHECK_NONE); + poly = (LWPOLY*)lwg; + pt_to_test.x = 1.0; + pt_to_test.y = 1.1; + pt_outside.x = 1.2; + pt_outside.y = 1.05; + result = ptarray_contains_point_sphere(poly->rings[0], &pt_outside, &pt_to_test); + CU_ASSERT_EQUAL(result, LW_TRUE); + lwgeom_free(lwg); + + /* Co-linear crossing case for point-in-polygon test, should return LW_TRUE */ + lwg = lwgeom_from_wkt("POLYGON((1.0 1.0, 1.0 1.1, 1.1 1.1, 1.1 1.2, 1.2 1.2, 1.2 1.0, 1.0 1.0))", LW_PARSER_CHECK_NONE); + poly = (LWPOLY*)lwg; + pt_to_test.x = 1.1; + pt_to_test.y = 1.05; + pt_outside.x = 1.1; + pt_outside.y = 1.3; + result = ptarray_contains_point_sphere(poly->rings[0], &pt_outside, &pt_to_test); + CU_ASSERT_EQUAL(result, LW_TRUE); + lwgeom_free(lwg); + + /* Co-linear grazing case for point-in-polygon test, should return LW_FALSE */ + lwg = lwgeom_from_wkt("POLYGON((1.0 1.0, 1.0 1.1, 1.1 1.1, 1.1 1.2, 1.2 1.2, 1.2 1.0, 1.0 1.0))", LW_PARSER_CHECK_NONE); + poly = (LWPOLY*)lwg; + pt_to_test.x = 1.0; + pt_to_test.y = 0.0; + pt_outside.x = 1.0; + pt_outside.y = 2.0; + result = ptarray_contains_point_sphere(poly->rings[0], &pt_outside, &pt_to_test); + CU_ASSERT_EQUAL(result, LW_FALSE); + lwgeom_free(lwg); + + /* Grazing case for point-in-polygon test, should return LW_FALSE */ + lwg = lwgeom_from_wkt("POLYGON((1.0 1.0, 1.0 2.0, 1.5 1.5, 1.0 1.0))", LW_PARSER_CHECK_NONE); + poly = (LWPOLY*)lwg; + pt_to_test.x = 1.5; + pt_to_test.y = 1.0; + pt_outside.x = 1.5; + pt_outside.y = 2.0; + result = ptarray_contains_point_sphere(poly->rings[0], &pt_outside, &pt_to_test); + CU_ASSERT_EQUAL(result, LW_FALSE); + lwgeom_free(lwg); + + /* Grazing case at first point for point-in-polygon test, should return LW_FALSE */ + lwg = lwgeom_from_wkt("POLYGON((1.0 1.0, 2.0 3.0, 2.0 0.0, 1.0 1.0))", LW_PARSER_CHECK_NONE); + poly = (LWPOLY*)lwg; + pt_to_test.x = 1.0; + pt_to_test.y = 0.0; + pt_outside.x = 1.0; + pt_outside.y = 2.0; + result = ptarray_contains_point_sphere(poly->rings[0], &pt_outside, &pt_to_test); + CU_ASSERT_EQUAL(result, LW_FALSE); + lwgeom_free(lwg); + + /* Outside multi-crossing case for point-in-polygon test, should return LW_FALSE */ + lwg = lwgeom_from_wkt("POLYGON((1.0 1.0, 1.0 1.1, 1.1 1.1, 1.1 1.2, 1.2 1.2, 1.2 1.0, 1.0 1.0))", LW_PARSER_CHECK_NONE); + poly = (LWPOLY*)lwg; + pt_to_test.x = 0.99; + pt_to_test.y = 0.99; + pt_outside.x = 1.21; + pt_outside.y = 1.21; + result = ptarray_contains_point_sphere(poly->rings[0], &pt_outside, &pt_to_test); + CU_ASSERT_EQUAL(result, LW_FALSE); + lwgeom_free(lwg); + + /* Inside multi-crossing case for point-in-polygon test, should return LW_TRUE */ + lwg = lwgeom_from_wkt("POLYGON((1.0 1.0, 1.0 1.1, 1.1 1.1, 1.1 1.2, 1.2 1.2, 1.2 1.0, 1.0 1.0))", LW_PARSER_CHECK_NONE); + poly = (LWPOLY*)lwg; + pt_to_test.x = 1.11; + pt_to_test.y = 1.11; + pt_outside.x = 1.21; + pt_outside.y = 1.21; + result = ptarray_contains_point_sphere(poly->rings[0], &pt_outside, &pt_to_test); + CU_ASSERT_EQUAL(result, LW_TRUE); + lwgeom_free(lwg); + + /* Point on vertex of ring */ + lwg = lwgeom_from_wkt("POLYGON((-9 50,51 -11,-10 50,-9 50))", LW_PARSER_CHECK_NONE); + poly = (LWPOLY*)lwg; + pt_to_test.x = -10.0; + pt_to_test.y = 50.0; + pt_outside.x = -10.2727799838316134; + pt_outside.y = -16.9370033133329976; + result = ptarray_contains_point_sphere(poly->rings[0], &pt_outside, &pt_to_test); + CU_ASSERT_EQUAL(result, LW_TRUE); + lwgeom_free(lwg); + +} + +static void test_lwpoly_covers_point2d(void) +{ + LWPOLY *poly; + LWGEOM *lwg; + POINT2D pt_to_test; + int result; + + lwg = lwgeom_from_wkt("POLYGON((-9 50,51 -11,-10 50,-9 50))", LW_PARSER_CHECK_NONE); + poly = (LWPOLY*)lwg; + pt_to_test.x = -10.0; + pt_to_test.y = 50.0; + result = lwpoly_covers_point2d(poly, &pt_to_test); + CU_ASSERT_EQUAL(result, LW_TRUE); + lwgeom_free(lwg); + + /* Great big ring */ + lwg = lwgeom_from_wkt("POLYGON((-40.0 52.0, -67.0 -29.0, 102.0 -6.0, -40.0 52.0))", LW_PARSER_CHECK_NONE); + poly = (LWPOLY*)lwg; + pt_to_test.x = 4.0; + pt_to_test.y = 11.0; + result = lwpoly_covers_point2d(poly, &pt_to_test); + CU_ASSERT_EQUAL(result, LW_TRUE); + lwgeom_free(lwg); + + /* Triangle over the antimeridian */ + lwg = lwgeom_from_wkt("POLYGON((140 52, 152.0 -6.0, -120.0 -29.0, 140 52))", LW_PARSER_CHECK_NONE); + poly = (LWPOLY*)lwg; + pt_to_test.x = -172.0; + pt_to_test.y = -13.0; + result = lwpoly_covers_point2d(poly, &pt_to_test); + CU_ASSERT_EQUAL(result, LW_TRUE); + lwgeom_free(lwg); + +} + +static void test_ptarray_contains_point_sphere_iowa(void) +{ + LWGEOM *lwg = lwgeom_from_wkt(iowa_data, LW_PARSER_CHECK_NONE); + LWPOLY *poly = (LWPOLY*)lwg; + POINTARRAY *pa = poly->rings[0]; + POINT2D pt_outside, pt_to_test; + int rv; + + pt_to_test.x = -95.900000000000006; + pt_to_test.y = 42.899999999999999; + pt_outside.x = -96.381873780830645; + pt_outside.y = 40.185394449416371; + + rv = ptarray_contains_point_sphere(pa, &pt_outside, &pt_to_test); + CU_ASSERT_EQUAL(rv, LW_TRUE); + + lwgeom_free(lwg); +} + + +static void test_lwgeom_distance_sphere(void) +{ + LWGEOM *lwg1, *lwg2; + double d; + SPHEROID s; + + /* Init and force spherical */ + spheroid_init(&s, 6378137.0, 6356752.314245179498); + s.a = s.b = s.radius; + + /* https://trac.osgeo.org/postgis/ticket/4835 */ + lwg1 = lwgeom_from_wkt("POINT(45 90)", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("LINESTRING(15.55 78.216667, -164.58 68.875)", LW_PARSER_CHECK_NONE); + d = lwgeom_distance_spheroid(lwg1, lwg2, &s, 0.0); + // printf("%12.8g\n", d); + CU_ASSERT_DOUBLE_EQUAL(d, 1958.2179, 0.1); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + + /* https://trac.osgeo.org/postgis/ticket/4835 */ + lwg1 = lwgeom_from_wkt("POINT(0 90)", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("LINESTRING(-166.11 68.875,15.55 78.216667)", LW_PARSER_CHECK_NONE); + d = lwgeom_distance_spheroid(lwg1, lwg2, &s, 0.0); + // printf("%12.8g\n", d); + CU_ASSERT_DOUBLE_EQUAL(d, 25003.707, 0.1); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + + /* Line/line distance, 1 degree apart */ + lwg1 = lwgeom_from_wkt("LINESTRING(-30 10, -20 5, -10 3, 0 1)", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("LINESTRING(-10 -5, -5 0, 5 0, 10 -5)", LW_PARSER_CHECK_NONE); + d = lwgeom_distance_spheroid(lwg1, lwg2, &s, 0.0); + CU_ASSERT_DOUBLE_EQUAL(d, s.radius * M_PI / 180.0, 0.00001); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + + /* Line/line distance, crossing, 0.0 apart */ + lwg1 = lwgeom_from_wkt("LINESTRING(-30 10, -20 5, -10 3, 0 1)", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("LINESTRING(-10 -5, -5 20, 5 0, 10 -5)", LW_PARSER_CHECK_NONE); + d = lwgeom_distance_spheroid(lwg1, lwg2, &s, 0.0); + CU_ASSERT_DOUBLE_EQUAL(d, 0.0, 0.00001); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + + /* Line/point distance, 1 degree apart */ + lwg1 = lwgeom_from_wkt("POINT(-4 1)", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("LINESTRING(-10 -5, -5 0, 5 0, 10 -5)", LW_PARSER_CHECK_NONE); + d = lwgeom_distance_spheroid(lwg1, lwg2, &s, 0.0); + CU_ASSERT_DOUBLE_EQUAL(d, s.radius * M_PI / 180.0, 0.00001); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + + lwg1 = lwgeom_from_wkt("POINT(-4 1)", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("POINT(-4 -1)", LW_PARSER_CHECK_NONE); + d = lwgeom_distance_spheroid(lwg1, lwg2, &s, 0.0); + CU_ASSERT_DOUBLE_EQUAL(d, s.radius * M_PI / 90.0, 0.00001); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + + /* Poly/point distance, point inside polygon, 0.0 apart */ + lwg1 = lwgeom_from_wkt("POLYGON((-4 1, -3 5, 1 2, 1.5 -5, -4 1))", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("POINT(-1 -1)", LW_PARSER_CHECK_NONE); + d = lwgeom_distance_spheroid(lwg1, lwg2, &s, 0.0); + CU_ASSERT_DOUBLE_EQUAL(d, 0.0, 0.00001); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + + /* Poly/point distance, point inside polygon hole, 1 degree apart */ + lwg1 = lwgeom_from_wkt("POLYGON((-4 -4, -4 4, 4 4, 4 -4, -4 -4), (-2 -2, -2 2, 2 2, 2 -2, -2 -2))", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("POINT(-1 -1)", LW_PARSER_CHECK_NONE); + d = lwgeom_distance_spheroid(lwg1, lwg2, &s, 0.0); + CU_ASSERT_DOUBLE_EQUAL(d, 111178.142466, 0.1); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + + /* Poly/point distance, point on hole boundary, 0.0 apart */ + lwg1 = lwgeom_from_wkt("POLYGON((-4 -4, -4 4, 4 4, 4 -4, -4 -4), (-2 -2, -2 2, 2 2, 2 -2, -2 -2))", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("POINT(2 2)", LW_PARSER_CHECK_NONE); + d = lwgeom_distance_spheroid(lwg1, lwg2, &s, 0.0); + CU_ASSERT_DOUBLE_EQUAL(d, 0.0, 0.00001); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + + /* Medford test case #1 */ + lwg1 = lwgeom_from_hexwkb("0105000020E610000001000000010200000002000000EF7B8779C7BD5EC0FD20D94B852845400E539C62B9BD5EC0F0A5BE767C284540", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_hexwkb("0106000020E61000000100000001030000000100000007000000280EC3FB8CCA5EC0A5CDC747233C45402787C8F58CCA5EC0659EA2761E3C45400CED58DF8FCA5EC0C37FAE6E1E3C4540AE97B8E08FCA5EC00346F58B1F3C4540250359FD8ECA5EC05460628E1F3C45403738F4018FCA5EC05DC84042233C4540280EC3FB8CCA5EC0A5CDC747233C4540", LW_PARSER_CHECK_NONE); + d = lwgeom_distance_spheroid(lwg1, lwg2, &s, 0.0); + CU_ASSERT_DOUBLE_EQUAL(d, 23630.8003, 0.1); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + + /* Ticket #2351 */ + lwg1 = lwgeom_from_wkt("LINESTRING(149.386990599235 -26.3567415843982,149.386990599247 -26.3567415843965)", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("POINT(149.386990599235 -26.3567415843982)", LW_PARSER_CHECK_NONE); + d = lwgeom_distance_spheroid(lwg1, lwg2, &s, 0.0); + CU_ASSERT_DOUBLE_EQUAL(d, 0.0, 0.00001); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + + /* Ticket #2638, no "M" */ + lwg1 = lwgeom_from_wkt("LINESTRING (-41.0821 50.3036,50 -41)", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("POLYGON((0 0,10 0,10 10,0 10,0 0),(5 5,7 5,7 7,5 7,5 5))", LW_PARSER_CHECK_NONE); + d = lwgeom_distance_spheroid(lwg1, lwg2, &s, 0.0); + CU_ASSERT_DOUBLE_EQUAL(d, 0.0, 0.00001); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + + /* Ticket #2638, with "M" */ + lwg1 = lwgeom_from_wkt("LINESTRING M (-41.0821 50.3036 1,50 -41 1)", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("POLYGON M ((0 0 2,10 0 1,10 10 -2,0 10 -5,0 0 -5),(5 5 6,7 5 6,7 7 6,5 7 10,5 5 -2))", LW_PARSER_CHECK_NONE); + d = lwgeom_distance_spheroid(lwg1, lwg2, &s, 0.0); + CU_ASSERT_DOUBLE_EQUAL(d, 0.0, 0.00001); + lwgeom_free(lwg1); + lwgeom_free(lwg2); +} + +static void test_spheroid_distance(void) +{ + GEOGRAPHIC_POINT g1, g2; + double d; +#ifndef PROJ_GEODESIC + double epsilon; /* irregular */ +#else + const double epsilon = 1e-8; /* at least 10 nm precision */ +#endif + SPHEROID s; + + /* Init to WGS84 */ + spheroid_init(&s, 6378137.0, 6356752.314245179498); + + /* One vertical degree + $ GeodSolve -E -i -p 16 --input-string "0 0 1 0" */ + point_set(0.0, 0.0, &g1); + point_set(0.0, 1.0, &g2); + d = spheroid_distance(&g1, &g2, &s); +#ifndef PROJ_GEODESIC + epsilon = 1e-6; +#endif + CU_ASSERT_DOUBLE_EQUAL(d, 110574.3885577987957342, epsilon); + + /* Ten horizontal degrees + $ GeodSolve -E -i -p 16 --input-string "0 -10 0 0" */ + point_set(-10.0, 0.0, &g1); + point_set(0.0, 0.0, &g2); + d = spheroid_distance(&g1, &g2, &s); +#ifndef PROJ_GEODESIC + epsilon = 1e-3; +#endif + CU_ASSERT_DOUBLE_EQUAL(d, 1113194.9079327357264771, epsilon); + + /* One horizonal degree + $ GeodSolve -E -i -p 16 --input-string "0 -1 0 0" */ + point_set(-1.0, 0.0, &g1); + point_set(0.0, 0.0, &g2); + d = spheroid_distance(&g1, &g2, &s); +#ifndef PROJ_GEODESIC + epsilon = 1e-4; +#endif + CU_ASSERT_DOUBLE_EQUAL(d, 111319.4907932735726477, epsilon); + + /* Around world w/ slight bend + $ GeodSolve -E -i -p 16 --input-string "0 -180 1 0" */ + point_set(-180.0, 0.0, &g1); + point_set(0.0, 1.0, &g2); + d = spheroid_distance(&g1, &g2, &s); +#ifndef PROJ_GEODESIC + epsilon = 1e-5; +#endif + CU_ASSERT_DOUBLE_EQUAL(d, 19893357.0700676468277450, epsilon); + + /* Up to pole + $ GeodSolve -E -i -p 16 --input-string "0 -180 90 0" */ + point_set(-180.0, 0.0, &g1); + point_set(0.0, 90.0, &g2); + d = spheroid_distance(&g1, &g2, &s); +#ifndef PROJ_GEODESIC + epsilon = 1e-6; +#endif + CU_ASSERT_DOUBLE_EQUAL(d, 10001965.7293127228117396, epsilon); + +} + +static void test_spheroid_area(void) +{ + LWGEOM *lwg; + GBOX gbox; + double a1, a2; + SPHEROID s; + + /* Init to WGS84 */ + spheroid_init(&s, WGS84_MAJOR_AXIS, WGS84_MINOR_AXIS); + + gbox.flags = lwflags(0, 0, 1); + + /* Medford lot test polygon */ + lwg = lwgeom_from_wkt("POLYGON((-122.848227067007 42.5007249610493,-122.848309475585 42.5007179884263,-122.848327688675 42.500835880696,-122.848245279942 42.5008428533324,-122.848227067007 42.5007249610493))", LW_PARSER_CHECK_NONE); + lwgeom_calculate_gbox_geodetic(lwg, &gbox); + /* sphere: Planimeter -E -p 20 -e $WGS84_SPHERE -r --input-string \ + "42.5007249610493 -122.848227067007;42.5007179884263 -122.848309475585;"\ + "42.500835880696 -122.848327688675;42.5008428533324 -122.848245279942" */ + a1 = lwgeom_area_sphere(lwg, &s); + CU_ASSERT_DOUBLE_EQUAL(a1, 89.721147136698008, 0.1); + /* spheroid: Planimeter -E -p 20 -r --input-string \ + "42.5007249610493 -122.848227067007;42.5007179884263 -122.848309475585;"\ + "42.500835880696 -122.848327688675;42.5008428533324 -122.848245279942" */ + a2 = lwgeom_area_spheroid(lwg, &s); + CU_ASSERT_DOUBLE_EQUAL(a2, 89.868413479309585, 0.1); + lwgeom_free(lwg); + + /* Big-ass polygon */ + lwg = lwgeom_from_wkt("POLYGON((-2 3, -2 4, -1 4, -1 3, -2 3))", LW_PARSER_CHECK_NONE); + lwgeom_calculate_gbox_geodetic(lwg, &gbox); + /* sphere: Planimeter -E -p 20 -e $WGS84_SPHERE -r --input-string "3 -2;4 -2;4 -1;3 -1" */ + a1 = lwgeom_area_sphere(lwg, &s); + CU_ASSERT_DOUBLE_EQUAL(a1, 12341436880.106982993974659, 0.1); + /* spheroid: Planimeter -E -p 20 -r --input-string "3 -2;4 -2;4 -1;3 -1" */ +#ifdef PROJ_GEODESIC + a2 = lwgeom_area_spheroid(lwg, &s); + CU_ASSERT_DOUBLE_EQUAL(a2, 12286884908.946891319597874, 0.1); +#endif + lwgeom_free(lwg); + + /* One-degree square */ + lwg = lwgeom_from_wkt("POLYGON((8.5 2,8.5 1,9.5 1,9.5 2,8.5 2))", LW_PARSER_CHECK_NONE); + lwgeom_calculate_gbox_geodetic(lwg, &gbox); + /* sphere: Planimeter -E -p 20 -e $WGS84_SPHERE --input-string "2 8.5;1 8.5;1 9.5;2 9.5" */ + a1 = lwgeom_area_sphere(lwg, &s); + CU_ASSERT_DOUBLE_EQUAL(a1, 12360265021.368023059138681, 0.1); + /* spheroid: Planimeter -E -p 20 --input-string "2 8.5;1 8.5;1 9.5;2 9.5" */ +#ifdef PROJ_GEODESIC + a2 = lwgeom_area_spheroid(lwg, &s); + CU_ASSERT_DOUBLE_EQUAL(a2, 12305128751.042900673161556, 0.1); +#endif + lwgeom_free(lwg); + + /* One-degree square *near* the antimeridian */ + lwg = lwgeom_from_wkt("POLYGON((179.5 2,179.5 1,178.5 1,178.5 2,179.5 2))", LW_PARSER_CHECK_NONE); + lwgeom_calculate_gbox_geodetic(lwg, &gbox); + /* sphere: Planimeter -E -p 20 -e $WGS84_SPHERE -r --input-string "2 179.5;1 179.5;1 178.5;2 178.5" */ + a1 = lwgeom_area_sphere(lwg, &s); + CU_ASSERT_DOUBLE_EQUAL(a1, 12360265021.368023059138681, 0.1); + /* spheroid: Planimeter -E -p 20 -r --input-string "2 179.5;1 179.5;1 178.5;2 178.5" */ +#ifdef PROJ_GEODESIC + a2 = lwgeom_area_spheroid(lwg, &s); + CU_ASSERT_DOUBLE_EQUAL(a2, 12305128751.042900673161556, 0.1); +#endif + lwgeom_free(lwg); + + /* One-degree square *across* the antimeridian */ + lwg = lwgeom_from_wkt("POLYGON((179.5 2,179.5 1,-179.5 1,-179.5 2,179.5 2))", LW_PARSER_CHECK_NONE); + lwgeom_calculate_gbox_geodetic(lwg, &gbox); + /* sphere: Planimeter -E -p 20 -e $WGS84_SPHERE --input-string "2 179.5;1 179.5;1 -179.5;2 -179.5" */ + a1 = lwgeom_area_sphere(lwg, &s); + CU_ASSERT_DOUBLE_EQUAL(a1, 12360265021.368023059138681, 0.1); + /* spheroid: Planimeter -E -p 20 --input-string "2 179.5;1 179.5;1 -179.5;2 -179.5" */ +#ifdef PROJ_GEODESIC + a2 = lwgeom_area_spheroid(lwg, &s); + CU_ASSERT_DOUBLE_EQUAL(a2, 12305128751.042900673161556, 0.1); +#endif + lwgeom_free(lwg); +} + +static void test_gbox_utils(void) +{ + LWGEOM *lwg; + GBOX gbox; + double a1, a2; + SPHEROID s; + POINT2D pt; + + /* Init to WGS84 */ + spheroid_init(&s, WGS84_MAJOR_AXIS, WGS84_MINOR_AXIS); + + gbox.flags = lwflags(0, 0, 1); + + /* One-degree square by equator */ + lwg = lwgeom_from_wkt("POLYGON((1 20,1 21,2 21,2 20,1 20))", LW_PARSER_CHECK_NONE); + lwgeom_calculate_gbox_geodetic(lwg, &gbox); + a1 = gbox_angular_width(&gbox); + a2 = gbox_angular_height(&gbox); + CU_ASSERT_DOUBLE_EQUAL(a1, 0.0177951, 0.0000001); + CU_ASSERT_DOUBLE_EQUAL(a2, 0.017764, 0.0000001); + lwgeom_free(lwg); + + /* One-degree square *across* antimeridian */ + lwg = lwgeom_from_wkt("POLYGON((179.5 2,179.5 1,-179.5 1,-179.5 2,179.5 2))", LW_PARSER_CHECK_NONE); + lwgeom_calculate_gbox_geodetic(lwg, &gbox); + a1 = gbox_angular_width(&gbox); + a2 = gbox_angular_height(&gbox); + //printf("a1=%g a2=%g\n", a1, a2); + CU_ASSERT_DOUBLE_EQUAL(a1, 0.0174613, 0.0000001); + CU_ASSERT_DOUBLE_EQUAL(a2, 0.0174553, 0.0000001); + lwgeom_free(lwg); + + /* One-degree square *across* antimeridian */ + lwg = lwgeom_from_wkt("POLYGON((178.5 2,178.5 1,-179.5 1,-179.5 2,178.5 2))", LW_PARSER_CHECK_NONE); + lwgeom_calculate_gbox_geodetic(lwg, &gbox); + gbox_centroid(&gbox, &pt); + //printf("POINT(%g %g)\n", pt.x, pt.y); + CU_ASSERT_DOUBLE_EQUAL(pt.x, 179.5, 0.0001); + CU_ASSERT_DOUBLE_EQUAL(pt.y, 1.50024, 0.0001); + lwgeom_free(lwg); + +} + +static void test_vector_angle(void) +{ + POINT3D p1, p2; + double angle; + + memset(&p1, 0, sizeof(POINT3D)); + memset(&p2, 0, sizeof(POINT3D)); + + p1.x = 1.0; + p2.y = 1.0; + angle = vector_angle(&p1, &p2); + CU_ASSERT_DOUBLE_EQUAL(angle, M_PI_2, 0.00001); + + p1.x = p2.y = 0.0; + p1.y = 1.0; + p2.x = 1.0; + angle = vector_angle(&p1, &p2); + CU_ASSERT_DOUBLE_EQUAL(angle, M_PI_2, 0.00001); + + p2.y = p2.x = 1.0; + normalize(&p2); + angle = vector_angle(&p1, &p2); + CU_ASSERT_DOUBLE_EQUAL(angle, M_PI_4, 0.00001); + + p2.x = p2.y = p2.z = 1.0; + normalize(&p2); + angle = vector_angle(&p1, &p2); + CU_ASSERT_DOUBLE_EQUAL(angle, 0.955317, 0.00001); + //printf ("angle = %g\n\n", angle); +} + +static void test_vector_rotate(void) +{ + POINT3D p1, p2, n; + double angle; + + memset(&p1, 0, sizeof(POINT3D)); + memset(&p2, 0, sizeof(POINT3D)); + memset(&n, 0, sizeof(POINT3D)); + + p1.x = 1.0; + p2.y = 1.0; + angle = M_PI_4; + vector_rotate(&p1, &p2, angle, &n); + //printf("%g %g %g\n\n", n.x, n.y, n.z); + CU_ASSERT_DOUBLE_EQUAL(n.x, 0.707107, 0.00001); + + angle = 2*M_PI/400000000; + vector_rotate(&p1, &p2, angle, &n); + //printf("%.21g %.21g %.21g\n\n", n.x, n.y, n.z); + CU_ASSERT_DOUBLE_EQUAL(n.x, 0.999999999999999888978, 0.0000000000000001); + CU_ASSERT_DOUBLE_EQUAL(n.y, 1.57079632679489654446e-08, 0.0000000000000001); + + angle = 0; + vector_rotate(&p1, &p2, angle, &n); + //printf("%.16g %.16g %.16g\n\n", n.x, n.y, n.z); + CU_ASSERT_DOUBLE_EQUAL(n.x, 1.0, 0.00000001); +} + +static void test_lwgeom_segmentize_sphere(void) +{ + LWGEOM *lwg1, *lwg2; + LWLINE *lwl; + double max = 100000.0 / WGS84_RADIUS; + //char *wkt; + + /* Simple case */ + lwg1 = lwgeom_from_wkt("LINESTRING(0 20, 5 20)", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_segmentize_sphere(lwg1, max); + lwl = (LWLINE*)lwg2; + // printf("%s\n", lwgeom_to_ewkt(lwg2)); + CU_ASSERT_EQUAL(lwl->points->npoints, 9); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + //lwfree(wkt); + + return; +} + +static void test_lwgeom_area_sphere(void) +{ + LWGEOM *lwg; + double area; + SPHEROID s; + + /* Init to WGS84 */ + spheroid_init(&s, WGS84_MAJOR_AXIS, WGS84_MINOR_AXIS); + + /* Simple case */ + lwg = lwgeom_from_wkt("POLYGON((1 1, 1 2, 2 2, 2 1, 1 1))", LW_PARSER_CHECK_NONE); + area = lwgeom_area_sphere(lwg, &s); + + CU_ASSERT_DOUBLE_EQUAL(area, 12360265021.3561, 1.0); + lwgeom_free(lwg); + + /* Robustness tests, from ticket #3393 */ + lwg = lwgeom_from_wkt("POLYGON((0 78.703946026663,0 0,179.999997913235 0,179.999997913235 -33.0888306884702,0 78.703946026663))", LW_PARSER_CHECK_NONE); + area = lwgeom_area_sphere(lwg, &s); + CU_ASSERT_DOUBLE_EQUAL(area, 127516467322130, 1.0); + lwgeom_free(lwg); + + lwg = lwgeom_from_wkt("POLYGON((0 78.703946026662,0 0,179.999997913235 0,179.999997913235 -33.0888306884702,0 78.703946026662))", LW_PARSER_CHECK_NONE); + area = lwgeom_area_sphere(lwg, &s); + CU_ASSERT_DOUBLE_EQUAL(area, 127516467322130, 1.0); + lwgeom_free(lwg); + + lwg = lwgeom_from_wkt("POLYGON((0 78.703946026664,0 0,179.999997913235 0,179.999997913235 -33.0888306884702,0 78.703946026664))", LW_PARSER_CHECK_NONE); + area = lwgeom_area_sphere(lwg, &s); + CU_ASSERT_DOUBLE_EQUAL(area, 127516467322130, 1.0); + lwgeom_free(lwg); + /* end #3393 */ +} + +static void test_gbox_to_string_truncated(void) +{ + GBOX g = { + .flags = 0, + .xmin = -DBL_MAX, + .xmax = -DBL_MAX, + .ymin = -DBL_MAX, + .ymax = -DBL_MAX, + .zmin = -DBL_MAX, + .zmax = -DBL_MAX, + .mmin = -DBL_MAX, + .mmax = -DBL_MAX, + }; + FLAGS_SET_Z(g.flags, 1); + FLAGS_SET_M(g.flags, 1); + char *c = gbox_to_string(&g); + + ASSERT_STRING_EQUAL(c, "GBOX((-1.7976931e+308,-1.7976931e+308,-1.7976931e+308,-1.7976931e+308),(-1.7976931e+308,-1.7976931e+308,-1.7976931e+308,-1.7976931e+308))"); + + lwfree(c); +} + +/* +** Used by test harness to register the tests in this file. +*/ +void geodetic_suite_setup(void); +void geodetic_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("geodetic", NULL, NULL); + PG_ADD_TEST(suite, test_sphere_direction); + PG_ADD_TEST(suite, test_sphere_project); + PG_ADD_TEST(suite, test_lwgeom_area_sphere); + PG_ADD_TEST(suite, test_gbox_from_spherical_coordinates); + PG_ADD_TEST(suite, test_gserialized_get_gbox_geocentric); + PG_ADD_TEST(suite, test_clairaut); + PG_ADD_TEST(suite, test_edge_intersection); + PG_ADD_TEST(suite, test_edge_intersects); + PG_ADD_TEST(suite, test_edge_distance_to_point); + PG_ADD_TEST(suite, test_edge_distance_to_edge); + PG_ADD_TEST(suite, test_lwgeom_distance_sphere); + PG_ADD_TEST(suite, test_lwgeom_check_geodetic); + PG_ADD_TEST(suite, test_gserialized_from_lwgeom); + PG_ADD_TEST(suite, test_spheroid_distance); + PG_ADD_TEST(suite, test_spheroid_area); + PG_ADD_TEST(suite, test_lwpoly_covers_point2d); + PG_ADD_TEST(suite, test_gbox_utils); + PG_ADD_TEST(suite, test_vector_angle); + PG_ADD_TEST(suite, test_vector_rotate); + PG_ADD_TEST(suite, test_lwgeom_segmentize_sphere); + PG_ADD_TEST(suite, test_ptarray_contains_point_sphere); + PG_ADD_TEST(suite, test_ptarray_contains_point_sphere_iowa); + PG_ADD_TEST(suite, test_gbox_to_string_truncated); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_geodetic_data.h b/mgist-postgis/liblwgeom/cunit/cu_geodetic_data.h new file mode 100644 index 0000000..94062bd --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_geodetic_data.h @@ -0,0 +1,79 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2008 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +int gbox_data_length = 55; +char gbox_data[][512] = +{ + "LINESTRING(-0 40,0 -30)", + "LINESTRING(-180 90,180 -80)", + "LINESTRING(-0 90,0 -89)", + "LINESTRING(0 90,80 -89)", + "LINESTRING(0 -5,0 -5)", + "LINESTRING(180 -35,180 45)", + "LINESTRING(158 -85,-57 86)", + "LINESTRING(-3.083333333333333333333333333333333 9.83333333333333333333333333333333,15.5 -5.25)", + "LINESTRING(-35.0 52.5,50.0 60.0)", + "LINESTRING(-122.5 56.25,-123.5 69.166666)", + "LINESTRING(-121.75 42.55,-122.35 43.25)", + "LINESTRING(-3.083333333333333333333333333333333 9.83333333333333333333333333333333,15.5 -5.25)", + "LINESTRING(86.85 9.85,105.5 -5.25)", + "LINESTRING(-120.0 62.55,-120.0 62.55)", + "LINESTRING(-135.0 40.0,45.0 -39.0)", + "LINESTRING(-120.0 62.55,60.0 73.25)", + "LINESTRING(-120.0 -62.55,60.0 -73.25)", + "LINESTRING(-120.0 20.0,-120.5 20.0)", + "LINESTRING(-120.0 45.0,-120.5 45.0)", + "LINESTRING(-120.0 75.0,-120.5 75.0)", + "LINESTRING(-120.0 -20.0,-120.5 -20.0)", + "LINESTRING(-120.0 -45.0,-120.5 -45.0)", + "LINESTRING(-120.0 -75.0,-120.5 -75.0)", + "LINESTRING(0.0 60.0,0.0 7.0)", + "LINESTRING(0.0 -60.0,0.0 -70.0)", + "LINESTRING(180.0 60.0,180.0 70.0)", + "LINESTRING(4.0 45.0,-4.0 45.0)", + "LINESTRING(-176.0 45.0,176.0 45.0)", + "LINESTRING(176.0 45.0,-176.0 45.0)", + "LINESTRING(-4.0 45.0,4.0 45.0)", + "LINESTRING(-45.0 60.0,135.0 72.0)", + "LINESTRING(-45.0 -60.0,135.0 -72.0)", + "LINESTRING(-15.0 3.5,15.0 -3.5)", + "LINESTRING(75.0 3.5,105.0 -3.5)", + "LINESTRING(-75.0 3.5,-105.0 -3.5)", + "LINESTRING(-153.11560 24.70504,-9.15580 24.18317)", + "LINESTRING(-178.0 45.0,165.0 45.0)", + "LINESTRING(10.0 45.0,110.0 45.0)", + "LINESTRING(10.0 -45.0,110.0 -45.0)", + "LINESTRING(-10.0 45.0,-110.0 45.0)", + "LINESTRING(160.0 25.0,-160.0 25.0)", + "LINESTRING(170.0 35.0,-160.0 35.0)", + "LINESTRING(-10.0 35.0,10.0 -35.0)", + "LINESTRING(-80.0 25.0,-60.0 -25.0)", + "LINESTRING(-80.0 25.0,-60.0 -45.0)", + "LINESTRING(-120.0 70.0,60.0 70.0)", + "LINESTRING(-120.0 -70.0,60.0 -70.0)", + "LINESTRING(-112.0 45.0,-112.00166666666666666666666666667 45.0)", + "LINESTRING(-120.0 -5.0,60.0 80.0)", + "LINESTRING(165.0 10.0,-172.0 -5.0)", + "LINESTRING(97.87714324162704 46.6879465040995,155.49353589912155 -68.93911854796522)", + "LINESTRING(-77.90029319006709 -20.61989357708765,-29.776541043747443 88.24497900223159)", + "LINESTRING(12.21419896647646 -2.2758177391540926,149.7713684095024 13.210117902931728)", + "LINESTRING(-49.891199414628915 66.72545480471234,-39.418865490450656 -89.97504625275525)", + "POLYGON((-40.0 52.0, 102.0 -6.0, -67.0 -29.0, -40.0 52.0))" +}; + + +/* Iowa Polygon for use in test case */ +char *iowa_data = "POLYGON((-94.015492 40.573914,-94.016088 40.5739,-94.016968 40.57388,-94.018058 40.573856,-94.02663 40.57371,-94.034211 40.573585,-94.038853 40.573525,-94.043491 40.573466,-94.046656 40.573426,-94.060375 40.573209,-94.069402 40.573067,-94.07214 40.573026,-94.079332 40.573147,-94.080122 40.57316,-94.080249 40.573147,-94.080315 40.57314,-94.080401 40.573143,-94.080409 40.573145,-94.080481 40.57314,-94.081532 40.57306,-94.081625 40.573059,-94.081866 40.573056,-94.083211 40.573037,-94.083542 40.573033,-94.083802 40.57303,-94.089019 40.572976,-94.089168 40.572956,-94.090421 40.572936,-94.090962 40.572941,-94.091084 40.572951,-94.091111 40.572956,-94.091169 40.572968,-94.095698 40.572951,-94.095709 40.572951,-94.10931 40.572902,-94.110406 40.572901,-94.118548 40.572903,-94.119918 40.572904,-94.119946 40.572904,-94.120008 40.572903,-94.120097 40.572901,-94.125259 40.57279,-94.128286 40.572768,-94.128303 40.572768,-94.130131 40.572754,-94.137859 40.572698,-94.146244 40.572638,-94.155089 40.572574,-94.163567 40.572508,-94.167509 40.572477,-94.167902 40.572474,-94.175618 40.572406,-94.199871 40.572168,-94.20377 40.572137,-94.213262 40.572061,-94.23224 40.571901,-94.234023 40.57189,-94.237434 40.571873,-94.24315 40.571845,-94.248117 40.571818,-94.248902 40.571814,-94.249265 40.571807,-94.24927 40.571807,-94.250172 40.571789,-94.250708 40.571779,-94.250798 40.571777,-94.25509 40.571693,-94.259163 40.571671,-94.263235 40.571642,-94.266531 40.57162,-94.269827 40.571597,-94.269901 40.571597,-94.270456 40.571531,-94.282994 40.571469,-94.283158 40.571476,-94.287344 40.571656,-94.28745 40.571661,-94.287529 40.571661,-94.287641 40.57166,-94.28809 40.571659,-94.288522 40.571657,-94.288608 40.571657,-94.288944 40.571634,-94.289214 40.571653,-94.289321 40.57166,-94.290732 40.571676,-94.29417 40.571656,-94.294288 40.571632,-94.294671 40.571428,-94.294813 40.571428,-94.294921 40.571428,-94.295172 40.571428,-94.304389 40.571421,-94.304554 40.571422,-94.305829 40.57156,-94.306066 40.571575,-94.306071 40.571575,-94.306569 40.571602,-94.307489 40.571594,-94.30811 40.571617,-94.30853 40.571648,-94.310423 40.57163,-94.311602 40.571647,-94.312491 40.571632,-94.313067 40.571601,-94.313952 40.571595,-94.314865 40.571608,-94.316439 40.571592,-94.31707 40.571589,-94.317923 40.571586,-94.319277 40.57157,-94.319883 40.571575,-94.320623 40.571565,-94.322007 40.571541,-94.324711 40.571542,-94.324765 40.571542,-94.325494 40.57154,-94.326363 40.571559,-94.327626 40.571552,-94.32847 40.571555,-94.334317 40.571548,-94.334461 40.571549,-94.335677 40.571554,-94.33598 40.571555,-94.33652 40.571538,-94.336763 40.57153,-94.33691 40.571528,-94.34602 40.571412,-94.346197 40.571411,-94.353988 40.571377,-94.355216 40.571373,-94.35739 40.571371,-94.358082 40.571371,-94.358236 40.571371,-94.358391 40.571371,-94.360623 40.571356,-94.372032 40.571252,-94.374625 40.57123,-94.374621 40.571316,-94.374651 40.571353,-94.374695 40.571379,-94.374793 40.571391,-94.375009 40.571396,-94.375868 40.571383,-94.376699 40.57138,-94.376782 40.571385,-94.377053 40.571368,-94.384005 40.571279,-94.38401 40.571279,-94.387546 40.571399,-94.389203 40.571453,-94.389206 40.571453,-94.38944 40.571425,-94.389871 40.571402,-94.391457 40.571364,-94.391917 40.571336,-94.392408 40.571326,-94.393203 40.571343,-94.393905 40.571339,-94.394146 40.571341,-94.395375 40.571353,-94.395829 40.571346,-94.396745 40.571331,-94.398112 40.571309,-94.39924 40.571309,-94.399755 40.571316,-94.400829 40.571312,-94.40179 40.57128,-94.402263 40.571295,-94.402582 40.571323,-94.402817 40.571334,-94.403314 40.571339,-94.403388 40.571318,-94.403432 40.571295,-94.403481 40.571312,-94.403549 40.57132,-94.403993 40.571309,-94.405541 40.571294,-94.405543 40.571294,-94.405815 40.571291,-94.40613 40.571286,-94.407294 40.571292,-94.40799 40.57129,-94.409646 40.571286,-94.409938 40.571282,-94.409963 40.571282,-94.412328 40.57125,-94.413798 40.571255,-94.414069 40.571269,-94.417054 40.571279,-94.418513 40.571298,-94.419029 40.571289,-94.419398 40.571273,-94.420664 40.571275,-94.420962 40.571285,-94.421524 40.571304,-94.421591 40.571305,-94.422342 40.571317,-94.423261 40.571306,-94.424308 40.571276,-94.425231 40.571264,-94.426098 40.571265,-94.426758 40.571288,-94.427368 40.571278,-94.427956 40.571256,-94.428728 40.571253,-94.429668 40.571236,-94.432654 40.571029,-94.435031 40.571018,-94.445015 40.570976,-94.456895 40.570934,-94.456954 40.570933,-94.457166 40.570931,-94.457441 40.570928,-94.457486 40.570928,-94.457866 40.570924,-94.458068 40.570938,-94.46047 40.571101,-94.460662 40.571098,-94.462174 40.571078,-94.466122 40.571064,-94.469396 40.571053,-94.469781 40.571049,-94.470406 40.571042,-94.470445 40.571045,-94.470541 40.571022,-94.471213 40.570825,-94.472549 40.570814,-94.473295 40.570808,-94.473459 40.570807,-94.474848 40.570795,-94.482828 40.570729,-94.482994 40.570728,-94.489109 40.570706,-94.489122 40.570857,-94.489131 40.57097,-94.489216 40.570989,-94.48932 40.57099,-94.489634 40.570982,-94.489869 40.570984,-94.490152 40.570969,-94.490223 40.570975,-94.490435 40.571005,-94.490746 40.570994,-94.490997 40.570986,-94.492014 40.570978,-94.49528 40.570977,-94.496069 40.570966,-94.49783 40.570957,-94.501191 40.570969,-94.50196 40.570958,-94.504516 40.570953,-94.504942 40.570948,-94.505838 40.570951,-94.509322 40.570931,-94.50943 40.57093,-94.510163 40.570926,-94.513507 40.570906,-94.51472 40.570895,-94.517605 40.570881,-94.517883 40.570876,-94.518352 40.570888,-94.519045 40.570893,-94.523188 40.570879,-94.524904 40.570868,-94.525489 40.570861,-94.525527 40.570866,-94.525761 40.570866,-94.525862 40.570866,-94.528929 40.57086,-94.52905 40.57086,-94.529166 40.57086,-94.530773 40.570856,-94.532379 40.570852,-94.532834 40.570851,-94.533289 40.57085,-94.533463 40.57085,-94.533925 40.570843,-94.536494 40.570818,-94.536699 40.570819,-94.537194 40.570822,-94.537486 40.57083,-94.537848 40.570836,-94.538315 40.570844,-94.538405 40.570844,-94.539193 40.570845,-94.53965 40.570846,-94.540087 40.570859,-94.540272 40.570854,-94.540766 40.570839,-94.541014 40.570828,-94.541648 40.570825,-94.541828 40.570819,-94.542154 40.570809,-94.544189 40.570816,-94.546014 40.570815,-94.547405 40.57082,-94.552281 40.57084,-94.566853 40.570881,-94.577568 40.570917,-94.577727 40.570918,-94.579041 40.570922,-94.585766 40.57094,-94.586048 40.570941,-94.594196 40.57096,-94.594293 40.571007,-94.594391 40.57103,-94.594951 40.571038,-94.596435 40.57106,-94.603368 40.571089,-94.604207 40.571096,-94.604633 40.571116,-94.604701 40.571113,-94.60497 40.571106,-94.606379 40.571133,-94.606615 40.571138,-94.607238 40.571145,-94.607836 40.57116,-94.608262 40.571152,-94.608944 40.571155,-94.609674 40.571153,-94.610334 40.571156,-94.610694 40.571164,-94.611289 40.571191,-94.61294 40.571187,-94.61377 40.571198,-94.61451 40.5712,-94.615343 40.571213,-94.616156 40.57122,-94.617231 40.571217,-94.61852 40.571224,-94.622442 40.571278,-94.622849 40.57128,-94.622963 40.571289,-94.62771 40.571317,-94.63203 40.571367,-94.63216 40.57137,-94.632954 40.571376,-94.635112 40.571385,-94.637042 40.571415,-94.638869 40.571432,-94.639967 40.571434,-94.640897 40.571429,-94.641472 40.571426,-94.642601 40.571437,-94.642899 40.57144,-94.643706 40.571468,-94.644536 40.571478,-94.645303 40.571476,-94.647342 40.571495,-94.649735 40.571535,-94.65097 40.571545,-94.651276 40.571547,-94.654257 40.571567,-94.654275 40.571568,-94.655334 40.571577,-94.657773 40.571616,-94.659154 40.57163,-94.659156 40.571631,-94.659526 40.571635,-94.65996 40.571629,-94.660619 40.57165,-94.661678 40.571652,-94.662644 40.571643,-94.66463 40.571658,-94.665838 40.571672,-94.667316 40.571711,-94.668784 40.571714,-94.668787 40.571715,-94.669073 40.571716,-94.670127 40.57174,-94.671078 40.571762,-94.671711 40.571769,-94.674848 40.571803,-94.677096 40.571849,-94.679507 40.57188,-94.682589 40.57193,-94.682595 40.57193,-94.682758 40.571933,-94.683078 40.571933,-94.683601 40.571934,-94.686827 40.571965,-94.687087 40.571968,-94.687292 40.57197,-94.688308 40.571995,-94.688331 40.571996,-94.688353 40.571996,-94.688398 40.571998,-94.688759 40.572006,-94.690936 40.572015,-94.692535 40.572046,-94.697806 40.572105,-94.699914 40.572132,-94.701815 40.572166,-94.703362 40.572189,-94.705568 40.57221,-94.706043 40.572218,-94.708066 40.57225,-94.708314 40.572254,-94.70927 40.57227,-94.710707 40.57227,-94.712971 40.572315,-94.71416 40.572318,-94.714512 40.572325,-94.71632 40.572363,-94.716323 40.572363,-94.716535 40.572343,-94.717318 40.572365,-94.717624 40.572366,-94.717629 40.572367,-94.717859 40.572367,-94.717863 40.572368,-94.719147 40.572375,-94.719977 40.572392,-94.720912 40.572399,-94.72212 40.572419,-94.723173 40.572431,-94.72432 40.572456,-94.725211 40.572476,-94.72751 40.572495,-94.728787 40.572514,-94.729918 40.572526,-94.731693 40.572537,-94.733256 40.572564,-94.733616 40.57257,-94.734522 40.572594,-94.736227 40.572618,-94.736881 40.572612,-94.736882 40.572612,-94.738155 40.572635,-94.739925 40.57265,-94.740662 40.572668,-94.742055 40.572685,-94.742677 40.572683,-94.743299 40.572691,-94.74424 40.57271,-94.744818 40.572717,-94.745519 40.572726,-94.747527 40.572752,-94.74844 40.572771,-94.748931 40.572772,-94.749568 40.572782,-94.750042 40.572795,-94.750371 40.572807,-94.751263 40.57284,-94.751841 40.572844,-94.75273 40.572858,-94.752732 40.572859,-94.753375 40.57287,-94.753746 40.572881,-94.754503 40.572861,-94.754644 40.572822,-94.754694 40.572792,-94.754738 40.572793,-94.756165 40.572827,-94.757436 40.572848,-94.75825 40.572862,-94.759063 40.57287,-94.760815 40.572897,-94.762927 40.57294,-94.765223 40.572992,-94.767007 40.57304,-94.767014 40.573039,-94.767014 40.573038,-94.767014 40.573037,-94.767014 40.573036,-94.767014 40.573035,-94.767014 40.573034,-94.767014 40.573033,-94.767014 40.573032,-94.767216 40.573032,-94.768193 40.573071,-94.768627 40.573075,-94.768628 40.573076,-94.769149 40.573082,-94.769487 40.57308,-94.769822 40.573078,-94.769826 40.573077,-94.769966 40.573077,-94.771211 40.573096,-94.77229 40.573096,-94.773895 40.573105,-94.774935 40.573115,-94.78122 40.573174,-94.78203 40.573181,-94.78446 40.573204,-94.78527 40.573212,-94.787684 40.573234,-94.794928 40.573302,-94.797343 40.573325,-94.797358 40.573325,-94.797403 40.573325,-94.797419 40.573326,-94.797423 40.573326,-94.797438 40.573326,-94.797443 40.573326,-94.797461 40.573326,-94.797515 40.573326,-94.797533 40.573327,-94.797631 40.573328,-94.797928 40.573333,-94.798027 40.573335,-94.79957 40.573358,-94.799887 40.573363,-94.801475 40.573387,-94.801484 40.573387,-94.806417 40.573488,-94.811008 40.573581,-94.811085 40.573583,-94.81112 40.573607,-94.811251 40.573629,-94.812439 40.573636,-94.814992 40.573679,-94.815863 40.573683,-94.815993 40.573684,-94.816003 40.573684,-94.818721 40.573724,-94.819756 40.573729,-94.81995 40.573756,-94.821813 40.573768,-94.822887 40.573785,-94.823381 40.573792,-94.823382 40.573792,-94.823453 40.573793,-94.824283 40.573804,-94.826773 40.573837,-94.827604 40.573849,-94.829913 40.57388,-94.836841 40.573973,-94.839151 40.574004,-94.839222 40.574001,-94.839437 40.573995,-94.83951 40.573994,-94.841214 40.574023,-94.846329 40.57411,-94.848034 40.57414,-94.848045 40.57414,-94.848078 40.57414,-94.84809 40.574141,-94.848099 40.574141,-94.848126 40.574141,-94.848135 40.574142,-94.853273 40.57422,-94.858557 40.5743,-94.858567 40.574301,-94.875248 40.574519,-94.878201 40.574563,-94.88578 40.574674,-94.886885 40.574689,-94.887084 40.574694,-94.888998 40.574702,-94.894773 40.574728,-94.896595 40.574737,-94.896699 40.574737,-94.896773 40.574737,-94.896774 40.57474,-94.896819 40.574761,-94.89971 40.5748,-94.90055 40.574815,-94.900572 40.574816,-94.900885 40.574822,-94.901101 40.574859,-94.901107 40.574863,-94.914781 40.575066,-94.914896 40.575068,-94.921114 40.57516,-94.921225 40.575161,-94.933987 40.575352,-94.936634 40.575392,-94.936911 40.575396,-94.948199 40.575567,-94.953271 40.575641,-94.95392 40.575651,-94.955058 40.575668,-94.955134 40.575669,-94.966279 40.575836,-94.966491 40.575839,-94.971223 40.575688,-94.972045 40.575687,-94.991333 40.575692,-94.99166 40.575692,-95.000265 40.575936,-95.000555 40.575939,-95.010169 40.576039,-95.021797 40.57618,-95.021836 40.576181,-95.022769 40.576189,-95.023063 40.576192,-95.030676 40.576263,-95.036265 40.576354,-95.036362 40.576355,-95.038232 40.576341,-95.039152 40.576348,-95.039613 40.576355,-95.040027 40.576362,-95.040328 40.576368,-95.048786 40.57652,-95.048797 40.57652,-95.059337 40.57671,-95.062875 40.576772,-95.062997 40.576774,-95.06872 40.576878,-95.073022 40.576916,-95.073404 40.576919,-95.076369 40.576872,-95.077591 40.576907,-95.077992 40.576889,-95.078377 40.576897,-95.079666 40.576912,-95.084503 40.577032,-95.084525 40.577033,-95.087726 40.577118,-95.097266 40.577161,-95.107098 40.577269,-95.107207 40.577252,-95.107421 40.57724,-95.107973 40.577251,-95.108608 40.577244,-95.109632 40.577244,-95.109949 40.577244,-95.11012 40.577254,-95.110243 40.577281,-95.11041 40.577337,-95.110468 40.577363,-95.112109 40.577318,-95.112135 40.577313,-95.112224 40.577308,-95.112549 40.577306,-95.11398 40.577339,-95.115481 40.577341,-95.116419 40.577361,-95.1207 40.577427,-95.120705 40.577425,-95.120705 40.577424,-95.120825 40.577426,-95.120898 40.577415,-95.120986 40.577417,-95.122047 40.577423,-95.124405 40.577436,-95.125295 40.577443,-95.131592 40.577503,-95.1316 40.577503,-95.135489 40.577561,-95.144886 40.577704,-95.145089 40.577707,-95.145115 40.577707,-95.15441 40.577861,-95.157467 40.577908,-95.157712 40.577912,-95.164008 40.578016,-95.166331 40.578057,-95.167655 40.578066,-95.17062 40.578128,-95.174887 40.578174,-95.176189 40.578175,-95.179176 40.578236,-95.180197 40.57823,-95.183136 40.578265,-95.183459 40.578269,-95.184035 40.578267,-95.18501 40.578265,-95.187398 40.578305,-95.188453 40.57831,-95.188976 40.57833,-95.189023 40.578331,-95.189116 40.578334,-95.189843 40.578336,-95.190467 40.578345,-95.191484 40.578342,-95.193247 40.578374,-95.194188 40.578381,-95.194695 40.578391,-95.195415 40.578404,-95.198186 40.578434,-95.19882 40.578446,-95.199422 40.578451,-95.199854 40.578442,-95.200396 40.57844,-95.200955 40.578467,-95.201447 40.57847,-95.202265 40.578488,-95.203948 40.578517,-95.203983 40.578518,-95.205344 40.578533,-95.206587 40.578556,-95.207125 40.578558,-95.208043 40.578561,-95.209379 40.578594,-95.210074 40.578593,-95.211407 40.578617,-95.21154 40.578619,-95.21159 40.578624,-95.211754 40.57864,-95.211831 40.57866,-95.213318 40.578689,-95.216016 40.578734,-95.217363 40.578758,-95.218692 40.578779,-95.220047 40.578802,-95.221335 40.578824,-95.222796 40.579079,-95.224561 40.57888,-95.224733 40.578883,-95.225308 40.578894,-95.230674 40.578964,-95.232787 40.57899,-95.23279 40.57899,-95.233055 40.578995,-95.249191 40.579206,-95.250258 40.579189,-95.259008 40.579149,-95.25901 40.57915,-95.276644 40.579316,-95.278096 40.579322,-95.278116 40.579322,-95.297183 40.57951,-95.302554 40.57956,-95.308312 40.579614,-95.316264 40.57969,-95.316288 40.57969,-95.335483 40.580018,-95.335485 40.580018,-95.336474 40.580019,-95.337369 40.58003,-95.338082 40.580046,-95.339985 40.58006,-95.340264 40.580064,-95.340607 40.580067,-95.340844 40.580071,-95.341006 40.580073,-95.341176 40.580075,-95.342572 40.580092,-95.343737 40.580106,-95.346438 40.580138,-95.347476 40.580153,-95.349234 40.580171,-95.350431 40.580186,-95.353323 40.580234,-95.354508 40.580245,-95.357022 40.580254,-95.360839 40.580175,-95.364446 40.580263,-95.364651 40.580268,-95.368262 40.580356,-95.370491 40.580414,-95.373923 40.580503,-95.373951 40.580503,-95.375311 40.580626,-95.389268 40.580759,-95.392922 40.580793,-95.392926 40.580793,-95.411932 40.580979,-95.411985 40.58098,-95.412011 40.58098,-95.413824 40.580998,-95.415406 40.581014,-95.429239 40.581148,-95.431075 40.581167,-95.431095 40.581167,-95.44058 40.581259,-95.449753 40.581349,-95.45019 40.581353,-95.450206 40.581353,-95.460657 40.581455,-95.460812 40.581457,-95.469213 40.581548,-95.469255 40.581548,-95.469634 40.581548,-95.470332 40.581558,-95.471381 40.581579,-95.472055 40.581586,-95.473033 40.581583,-95.474974 40.581625,-95.475882 40.581639,-95.476481 40.58167,-95.47695 40.581667,-95.477582 40.581679,-95.480115 40.581665,-95.481354 40.581682,-95.483869 40.581697,-95.483914 40.581698,-95.484021 40.581702,-95.484298 40.581708,-95.485319 40.581724,-95.486088 40.581731,-95.486539 40.581722,-95.488144 40.581733,-95.488196 40.581733,-95.488514 40.581735,-95.490631 40.581777,-95.491616 40.581776,-95.492009 40.581781,-95.493391 40.581803,-95.494819 40.581833,-95.496945 40.581857,-95.498021 40.581881,-95.499155 40.581899,-95.499169 40.5819,-95.500967 40.581927,-95.501912 40.581928,-95.503033 40.581947,-95.503859 40.581952,-95.507142 40.581993,-95.507477 40.582004,-95.509146 40.582035,-95.509764 40.582051,-95.511071 40.582073,-95.512743 40.582083,-95.514244 40.5821,-95.51487 40.582101,-95.517384 40.582146,-95.519145 40.582165,-95.520514 40.582189,-95.521393 40.582198,-95.522252 40.582215,-95.523654 40.582224,-95.523906 40.582233,-95.525098 40.582243,-95.525201 40.582244,-95.525703 40.582232,-95.525873 40.582235,-95.525873 40.582234,-95.526498 40.582234,-95.52653 40.582235,-95.527504 40.582257,-95.528431 40.58227,-95.528858 40.582271,-95.530009 40.582301,-95.530346 40.582305,-95.531491 40.582338,-95.532626 40.582356,-95.533024 40.582358,-95.533115 40.582358,-95.533437 40.582356,-95.534335 40.582371,-95.53528 40.582377,-95.536445 40.582398,-95.538891 40.582449,-95.539424 40.582455,-95.540881 40.582441,-95.541593 40.58245,-95.542548 40.582453,-95.543436 40.582468,-95.54449 40.582469,-95.544932 40.582487,-95.545306 40.5825,-95.545306 40.582517,-95.545737 40.582529,-95.546468 40.58253,-95.547057 40.582543,-95.548209 40.582546,-95.548258 40.582547,-95.548306 40.582547,-95.548915 40.582565,-95.549636 40.582562,-95.550197 40.582573,-95.550914 40.582582,-95.55293 40.582635,-95.554391 40.582649,-95.554826 40.582656,-95.554875 40.582657,-95.556719 40.582677,-95.557384 40.582695,-95.558314 40.582699,-95.559952 40.582724,-95.562275 40.582759,-95.563865 40.582789,-95.565264 40.582814,-95.565364 40.582816,-95.566571 40.582841,-95.570387 40.582889,-95.571762 40.582919,-95.57208 40.582919,-95.572717 40.582939,-95.573954 40.582946,-95.573967 40.582946,-95.58496 40.583113,-95.585048 40.583114,-95.591526 40.583203,-95.593194 40.583228,-95.593195 40.583228,-95.59325 40.583228,-95.593317 40.583231,-95.601397 40.58335,-95.601441 40.583349,-95.602355 40.583365,-95.606904 40.583433,-95.607046 40.583435,-95.610881 40.583491,-95.610928 40.583492,-95.625269 40.583792,-95.630619 40.583901,-95.631692 40.583954,-95.631732 40.583955,-95.632106 40.583974,-95.632177 40.583977,-95.637722 40.584123,-95.637817 40.584127,-95.638063 40.584133,-95.638208 40.584137,-95.638459 40.584142,-95.641662 40.584229,-95.641695 40.584229,-95.647973 40.584398,-95.648534 40.584396,-95.652198 40.584389,-95.652303 40.584389,-95.652775 40.584388,-95.656646 40.584381,-95.657169 40.58438,-95.658705 40.584378,-95.658764 40.584378,-95.658797 40.584377,-95.673127 40.58435,-95.673257 40.58435,-95.685421 40.584327,-95.68631 40.584328,-95.687253 40.58437,-95.687374 40.584375,-95.687432 40.584378,-95.68748 40.584312,-95.687573 40.584295,-95.687694 40.584273,-95.69046 40.584354,-95.694246 40.584425,-95.701831 40.584537,-95.703792 40.584556,-95.705476 40.584575,-95.709173 40.584627,-95.712851 40.584646,-95.716233 40.584686,-95.716291 40.584686,-95.717391 40.584715,-95.720576 40.584753,-95.725782 40.584835,-95.731026 40.584893,-95.734317 40.584919,-95.735283 40.584911,-95.735429 40.584905,-95.735514 40.5849,-95.735606 40.584896,-95.735828 40.584912,-95.737565 40.584927,-95.742848 40.584997,-95.743969 40.585022,-95.744892 40.585029,-95.744937 40.585029,-95.745452 40.585013,-95.745543 40.585001,-95.745769 40.584972,-95.746 40.584959,-95.746165 40.584963,-95.746384 40.585012,-95.746647 40.585039,-95.747247 40.58505,-95.748016 40.585057,-95.749457 40.585014,-95.749776 40.585014,-95.750079 40.585066,-95.750481 40.585073,-95.751872 40.585076,-95.752307 40.585092,-95.752826 40.585079,-95.754648 40.585088,-95.763855 40.585207,-95.764549 40.585208,-95.765645 40.585208,-95.76463 40.585883,-95.758895 40.588973,-95.75524 40.591469,-95.753148 40.59284,-95.751195 40.595253,-95.750274 40.596317,-95.750053 40.597052,-95.749344 40.598803,-95.748858 40.599965,-95.748572 40.601265,-95.748626 40.603355,-95.749028 40.605333,-95.749685 40.606842,-95.750274 40.6076,-95.751271 40.609057,-95.752793 40.6102,-95.753545 40.610764,-95.75534 40.612056,-95.755583 40.612231,-95.758045 40.613759,-95.761683 40.615353,-95.764412 40.61709,-95.766823 40.61878,-95.768926 40.621264,-95.769759 40.622884,-95.770083 40.624425,-95.769671 40.625001,-95.769783 40.625463,-95.769904 40.626044,-95.770082 40.627504,-95.770189 40.628512,-95.770236 40.629117,-95.770293 40.629811,-95.770339 40.631486,-95.770442 40.635285,-95.770559 40.63635,-95.770834 40.63756,-95.771069 40.638456,-95.771296 40.639151,-95.771325 40.639393,-95.77147 40.639788,-95.772832 40.642496,-95.774143 40.644472,-95.774268 40.64466,-95.774598 40.645157,-95.776251 40.647463,-95.778276 40.64961,-95.77975 40.651131,-95.780239 40.651635,-95.781909 40.653272,-95.783822 40.654974,-95.785957 40.656734,-95.786568 40.657253,-95.787474 40.657919,-95.789485 40.659388,-95.793061 40.661227,-95.795489 40.662384,-95.797729 40.663209,-95.798669 40.663536,-95.801472 40.664283,-95.804307 40.664886,-95.805313 40.66502,-95.805641 40.665061,-95.808724 40.66527,-95.811151 40.665418,-95.812762 40.665507,-95.81415 40.66557,-95.815707 40.665779,-95.816491 40.665879,-95.817338 40.66598,-95.818354 40.666106,-95.820047 40.666468,-95.821412 40.666838,-95.822226 40.667083,-95.822913 40.66724,-95.824393 40.667784,-95.824891 40.667969,-95.826199 40.668498,-95.828372 40.669453,-95.82997 40.670224,-95.83074 40.670571,-95.830834 40.670613,-95.830845 40.670618,-95.832397 40.671309,-95.834419 40.672444,-95.836056 40.67343,-95.836365 40.673614,-95.837137 40.674067,-95.837399 40.674205,-95.837788 40.674413,-95.839395 40.675272,-95.840966 40.676295,-95.842316 40.677171,-95.842801 40.677496,-95.843841 40.678589,-95.844481 40.679382,-95.844827 40.679867,-95.844986 40.68012,-95.845172 40.680417,-95.845443 40.681046,-95.845765 40.681806,-95.846034 40.682605,-95.846239 40.68346,-95.846465 40.684324,-95.846712 40.685171,-95.846858 40.686206,-95.846883 40.686381,-95.846949 40.687252,-95.847015 40.688244,-95.84705 40.689211,-95.847116 40.690179,-95.847241 40.691086,-95.847266 40.69126,-95.847428 40.692163,-95.847581 40.692954,-95.847756 40.693583,-95.847931 40.694197,-95.84868 40.695973,-95.849166 40.696893,-95.849828 40.698147,-95.850582 40.699383,-95.851336 40.700523,-95.852615 40.702262,-95.854456 40.704164,-95.856362 40.705769,-95.857901 40.70713,-95.857984 40.707182,-95.859378 40.708055,-95.859875 40.708275,-95.860773 40.708738,-95.861798 40.709242,-95.863034 40.709715,-95.865954 40.710832,-95.868196 40.71164,-95.86954 40.712137,-95.870481 40.71248,-95.871836 40.712832,-95.872937 40.713127,-95.87528 40.71412,-95.877015 40.714287,-95.880993 40.716428,-95.883178 40.717579,-95.885349 40.721093,-95.886573 40.724637,-95.886592 40.724691,-95.887154 40.726317,-95.888907 40.731855,-95.888842 40.733226,-95.888697 40.736292,-95.88669 40.742101,-95.883643 40.747831,-95.881921 40.750001,-95.881529 40.750611,-95.879027 40.753081,-95.876896 40.754913,-95.875281 40.756011,-95.874492 40.756714,-95.873335 40.757616,-95.872281 40.758349,-95.871173 40.758896,-95.869982 40.759645,-95.868618 40.760177,-95.867224 40.76079,-95.865765 40.761299,-95.864349 40.76188,-95.862868 40.76234,-95.861695 40.762871,-95.860202 40.763291,-95.858426 40.76389,-95.856668 40.764319,-95.855051 40.764893,-95.852776 40.765631,-95.851572 40.766179,-95.850144 40.766663,-95.849005 40.767331,-95.847801 40.767943,-95.84662 40.768619,-95.844964 40.769693,-95.84372 40.770467,-95.842824 40.771093,-95.84154 40.772158,-95.840173 40.773392,-95.838879 40.774545,-95.837923 40.775511,-95.837289 40.776111,-95.836903 40.776477,-95.836162 40.777709,-95.835232 40.779151,-95.835178 40.779313,-95.834881 40.78021,-95.834462 40.781811,-95.834156 40.783016,-95.83412 40.783783,-95.834104 40.78413,-95.834065 40.785592,-95.834214 40.786665,-95.834523 40.787778,-95.835207 40.789309,-95.835815 40.79063,-95.83683 40.792288,-95.838578 40.794976,-95.84007 40.797471,-95.841527 40.799821,-95.842317 40.801247,-95.842738 40.801866,-95.843225 40.802816,-95.843745 40.803783,-95.843961 40.805178,-95.844408 40.806274,-95.844704 40.807209,-95.844994 40.808508,-95.845097 40.809108,-95.84522 40.809831,-95.845342 40.811324,-95.845251 40.812818,-95.844968 40.814217,-95.844922 40.814646,-95.844852 40.815307,-95.844397 40.816553,-95.843921 40.817686,-95.842948 40.819217,-95.842081 40.820569,-95.841581 40.821533,-95.841157 40.82204,-95.840437 40.822903,-95.839894 40.823786,-95.839269 40.825057,-95.838601 40.826175,-95.838351 40.826963,-95.8382 40.827437,-95.837665 40.829216,-95.837303 40.831164,-95.837266 40.832755,-95.837146 40.833474,-95.837122 40.834257,-95.837186 40.835347,-95.83757 40.836524,-95.838224 40.838079,-95.838688 40.839804,-95.839201 40.841094,-95.840014 40.842599,-95.840506 40.843953,-95.841309 40.845604,-95.841893 40.846603,-95.842681 40.847713,-95.843523 40.848976,-95.84434 40.849949,-95.845074 40.851035,-95.846076 40.85237,-95.847084 40.854174,-95.8475 40.855407,-95.847972 40.856809,-95.848112 40.857295,-95.84849 40.858607,-95.848565 40.859665,-95.848571 40.860016,-95.848587 40.860888,-95.84859 40.861061,-95.848242 40.862315,-95.847785 40.864328,-95.847397 40.864871,-95.846938 40.865745,-95.845974 40.867034,-95.844913 40.868202,-95.844073 40.869248,-95.842521 40.870266,-95.840788 40.871236,-95.840483 40.871377,-95.838735 40.872191,-95.836996 40.872644,-95.835054 40.872985,-95.833484 40.87326,-95.832103 40.873412,-95.830735 40.873702,-95.827787 40.874444,-95.824989 40.875,-95.824588 40.875158,-95.82328 40.875694,-95.821193 40.876682,-95.81959 40.877439,-95.817897 40.878448,-95.815933 40.879846,-95.81402 40.881808,-95.812083 40.884239,-95.810709 40.886681,-95.809994 40.889149,-95.809474 40.891228,-95.809379 40.893279,-95.809775 40.895447,-95.810886 40.897907,-95.812757 40.900642,-95.813465 40.901693,-95.814302 40.902936,-95.81618 40.904791,-95.818709 40.906818,-95.822081 40.909079,-95.822951 40.909639,-95.824959 40.910933,-95.827905 40.913069,-95.830699 40.915004,-95.833041 40.917243,-95.834906 40.919574,-95.836438 40.921642,-95.837774 40.924712,-95.838417 40.927408,-95.838446 40.927531,-95.83913 40.930341,-95.839743 40.93278,-95.840139 40.93548,-95.840207 40.937021,-95.840253 40.937909,-95.840277 40.938715,-95.840275 40.939942,-95.839979 40.941309,-95.839364 40.944059,-95.838877 40.946105,-95.838446 40.948385,-95.837951 40.950618,-95.836558 40.953205,-95.835095 40.955471,-95.833499 40.957988,-95.833015 40.958724,-95.832004 40.960269,-95.830801 40.962105,-95.829829 40.963857,-95.829507 40.965652,-95.829354 40.96738,-95.828991 40.969248,-95.828665 40.970793,-95.828329 40.972378,-95.828545 40.973901,-95.829074 40.975688,-95.829792 40.977344,-95.830297 40.978332,-95.831118 40.979777,-95.832055 40.98114,-95.833537 40.98266,-95.835434 40.984184,-95.837681 40.985599,-95.838908 40.986484,-95.840144 40.987243,-95.841186 40.987883,-95.842754 40.988812,-95.844351 40.989524,-95.84732 40.990214,-95.849232 40.990688,-95.851413 40.991362,-95.852547 40.991738,-95.854453 40.992479,-95.856071 40.993206,-95.857305 40.99384,-95.858668 40.994521,-95.860116 40.995242,-95.860445 40.995425,-95.8612 40.995845,-95.86248 40.996607,-95.863492 40.99734,-95.864391 40.998284,-95.865096 40.999076,-95.86601 40.999999,-95.86615 41.000015,-95.866423 41.000383,-95.866695 41.000734,-95.866786 41.000899,-95.8669 41.001026,-95.866951 41.001085,-95.867303 41.001645,-95.867305 41.001649,-95.867318 41.001673,-95.867327 41.001689,-95.867662 41.002304,-95.868037 41.003214,-95.86838 41.003753,-95.8687 41.004395,-95.868915 41.005133,-95.869216 41.005988,-95.869495 41.00709,-95.869567 41.007745,-95.869623 41.008432,-95.869526 41.009162,-95.869502 41.009429,-95.869301 41.009868,-95.868924 41.010954,-95.868643 41.011969,-95.868387 41.012727,-95.868002 41.013566,-95.867521 41.01442,-95.866872 41.01537,-95.866287 41.016349,-95.865886 41.017418,-95.865349 41.018164,-95.8647 41.018922,-95.864011 41.019753,-95.863265 41.020607,-95.862329 41.021808,-95.861616 41.022714,-95.860959 41.02362,-95.860786 41.023999,-95.860744 41.024058,-95.860294 41.024691,-95.859918 41.025403,-95.859622 41.026423,-95.859398 41.027787,-95.85919 41.028931,-95.859142 41.030323,-95.859102 41.031599,-95.85919 41.032591,-95.859278 41.033379,-95.859494 41.034467,-95.859654 41.035695,-95.86007 41.037011,-95.860386 41.037699,-95.860462 41.037887,-95.860862 41.038547,-95.861782 41.039427,-95.862686 41.040151,-95.863686 41.040971,-95.865158 41.042215,-95.866454 41.043103,-95.867246 41.043671,-95.86823 41.044271,-95.86923 41.044847,-95.869807 41.045199,-95.869863 41.045251,-95.870903 41.045823,-95.871783 41.046383,-95.872375 41.046955,-95.872903 41.047471,-95.873399 41.048031,-95.874247 41.048879,-95.874711 41.049323,-95.874998 41.049972,-95.875072 41.05014,-95.875147 41.050308,-95.875187 41.050399,-95.875287 41.050599,-95.875498 41.050743,-95.877258 41.051946,-95.877569 41.052135,-95.879462 41.053277,-95.879487 41.053299,-95.879488 41.053313,-95.879497 41.053448,-95.879724 41.053549,-95.879927 41.053639,-95.88082 41.054036,-95.881586 41.054376,-95.881588 41.054378,-95.881173 41.055938,-95.881387 41.056471,-95.881809 41.057521,-95.881939 41.057909,-95.882101 41.058359,-95.882342 41.059419,-95.882394 41.060412,-95.882476 41.06136,-95.882225 41.063539,-95.881768 41.064561,-95.881478 41.065359,-95.881011 41.066303,-95.880234 41.067457,-95.879367 41.068532,-95.877441 41.070357,-95.874998 41.072266,-95.873967 41.073071,-95.873877 41.073136,-95.873762 41.073218,-95.873761 41.073219,-95.87376 41.07322,-95.873757 41.073222,-95.873754 41.073224,-95.873743 41.073232,-95.873733 41.073239,-95.87257 41.07407,-95.871594 41.07495,-95.871464 41.075022,-95.870323 41.075809,-95.866912 41.078855,-95.865835 41.080079,-95.864782 41.081611,-95.864551 41.08216,-95.863843 41.083456,-95.86332 41.084915,-95.862937 41.086178,-95.862427 41.089687,-95.862517 41.090583,-95.862514 41.091123,-95.862587 41.091876,-95.862783 41.09258,-95.863044 41.093195,-95.863097 41.09366,-95.863113 41.093693,-95.863114 41.093695,-95.863115 41.093697,-95.863121 41.093711,-95.863131 41.093733,-95.863304 41.0941,-95.863279 41.094495,-95.863632 41.095392,-95.864396 41.097124,-95.864833 41.098487,-95.865076 41.09899,-95.865273 41.099913,-95.865726 41.1012,-95.86574 41.101263,-95.865862 41.101833,-95.865921 41.102108,-95.866172 41.10328,-95.866173 41.103287,-95.866213 41.103471,-95.866661 41.104975,-95.867072 41.108379,-95.866976 41.108714,-95.867102 41.109116,-95.867099 41.109511,-95.866818 41.110935,-95.8668 41.111074,-95.866397 41.114376,-95.866304 41.115116,-95.866415 41.117416,-95.866425 41.117812,-95.866641 41.119278,-95.867086 41.120603,-95.867228 41.121493,-95.867566 41.122086,-95.868382 41.123886,-95.868546 41.124103,-95.868689 41.124667,-95.868961 41.125166,-95.869092 41.125548,-95.869493 41.126034,-95.869624 41.126234,-95.870021 41.126839,-95.870087 41.127084,-95.870122 41.127135,-95.870716 41.128004,-95.871168 41.128795,-95.872053 41.129731,-95.872412 41.129966,-95.872679 41.130219,-95.872913 41.130571,-95.873121 41.130766,-95.87347 41.131397,-95.873902 41.131731,-95.874549 41.132486,-95.874757 41.132809,-95.874949 41.133025,-95.875633 41.133793,-95.875864 41.134021,-95.876048 41.134132,-95.876386 41.134649,-95.877042 41.135399,-95.877375 41.13586,-95.877476 41.136,-95.877699 41.136176,-95.87802 41.136658,-95.878302 41.136947,-95.878673 41.137685,-95.879316 41.138584,-95.879318 41.138586,-95.87933 41.138603,-95.879686 41.138938,-95.880044 41.139563,-95.880171 41.139947,-95.880609 41.140625,-95.880865 41.140883,-95.881222 41.141665,-95.881313 41.142127,-95.882032 41.143534,-95.882136 41.143994,-95.882138 41.144005,-95.882304 41.144738,-95.882591 41.145722,-95.882755 41.146021,-95.882875 41.146446,-95.882883 41.146657,-95.882776 41.146898,-95.882803 41.147221,-95.882961 41.147821,-95.883129 41.148196,-95.883172 41.148806,-95.883095 41.149342,-95.883346 41.151056,-95.883271 41.151484,-95.88309 41.151855,-95.883203 41.152652,-95.883144 41.153259,-95.882991 41.153674,-95.882944 41.154572,-95.882718 41.154937,-95.882643 41.155061,-95.882626 41.155193,-95.882625 41.155201,-95.88254 41.155881,-95.882392 41.156185,-95.882152 41.156495,-95.881958 41.156935,-95.881477 41.158361,-95.881062 41.158841,-95.880521 41.159874,-95.879959 41.160947,-95.879654 41.161267,-95.879171 41.161968,-95.877368 41.163914,-95.875798 41.165268,-95.875397 41.16552,-95.874398 41.166216,-95.873692 41.166642,-95.87271 41.167003,-95.872066 41.16731,-95.87111 41.167635,-95.870574 41.167784,-95.869178 41.168013,-95.867309 41.168153,-95.865929 41.168129,-95.864852 41.167939,-95.864399 41.167859,-95.863261 41.167495,-95.862345 41.167297,-95.86091 41.166911,-95.860316 41.166685,-95.859258 41.166535,-95.857637 41.166113,-95.856813 41.165935,-95.856296 41.165877,-95.853451 41.165518,-95.850882 41.165674,-95.849855 41.165833,-95.849363 41.165967,-95.848892 41.166018,-95.847938 41.166239,-95.846957 41.166604,-95.845451 41.167423,-95.844378 41.168241,-95.843883 41.168768,-95.843443 41.169096,-95.842731 41.170031,-95.842623 41.170345,-95.842039 41.171367,-95.842015 41.171654,-95.841789 41.172314,-95.841655 41.17317,-95.841611 41.173925,-95.841673 41.17486,-95.841616 41.175469,-95.84171 41.175858,-95.841816 41.175977,-95.841929 41.176256,-95.841956 41.176321,-95.842051 41.176772,-95.842188 41.177421,-95.84241 41.177778,-95.842746 41.178127,-95.843351 41.179677,-95.843657 41.180041,-95.844175 41.180411,-95.844535 41.180791,-95.844842 41.181155,-95.845121 41.181612,-95.845433 41.18193,-95.846295 41.182546,-95.847241 41.183351,-95.848798 41.184296,-95.849358 41.184545,-95.849861 41.18477,-95.850374 41.18509,-95.850757 41.185234,-95.851169 41.185321,-95.851268 41.185365,-95.852314 41.185833,-95.853094 41.185988,-95.854142 41.186411,-95.854836 41.186529,-95.855286 41.186697,-95.856125 41.186949,-95.85688 41.186995,-95.858032 41.18733,-95.858728 41.187375,-95.859224 41.187501,-95.86044 41.187666,-95.86097 41.187826,-95.861659 41.187965,-95.862084 41.188007,-95.862558 41.187993,-95.863797 41.188245,-95.864554 41.188205,-95.865782 41.188296,-95.866304 41.188277,-95.867624 41.188416,-95.868373 41.188324,-95.869538 41.188342,-95.870101 41.188272,-95.871288 41.188324,-95.872031 41.188209,-95.873608 41.188223,-95.873877 41.188153,-95.874537 41.188089,-95.875726 41.188048,-95.876582 41.188067,-95.877133 41.187961,-95.878133 41.187941,-95.878636 41.187836,-95.879586 41.187787,-95.880089 41.187681,-95.880658 41.187663,-95.885516 41.186773,-95.886131 41.186582,-95.886739 41.186496,-95.88784 41.186239,-95.888923 41.186128,-95.889651 41.185962,-95.891187 41.185721,-95.894134 41.185336,-95.898155 41.184775,-95.898962 41.184794,-95.8998 41.184655,-95.90051 41.184609,-95.90089 41.184626,-95.901348 41.184718,-95.901818 41.18466,-95.904169 41.184554,-95.904735 41.184499,-95.905591 41.184528,-95.90758 41.184368,-95.908668 41.184454,-95.910236 41.184403,-95.910787 41.184481,-95.911365 41.184642,-95.911984 41.18467,-95.912506 41.184638,-95.913168 41.184691,-95.913668 41.184809,-95.9142 41.18504,-95.914668 41.185111,-95.915428 41.185145,-95.916119 41.185279,-95.916699 41.185525,-95.9172 41.185907,-95.91891 41.18654,-95.919561 41.186901,-95.919916 41.187239,-95.920178 41.18766,-95.920783 41.188071,-95.92127 41.188401,-95.921454 41.188525,-95.922182 41.189092,-95.922358 41.189305,-95.922531 41.189678,-95.923205 41.190282,-95.923394 41.190532,-95.923548 41.19085,-95.924152 41.191474,-95.924454 41.192078,-95.925086 41.192796,-95.925235 41.19306,-95.925322 41.193376,-95.925901 41.194119,-95.926029 41.194683,-95.926334 41.195211,-95.926694 41.195633,-95.926771 41.196148,-95.92725 41.197086,-95.927296 41.197408,-95.927263 41.197802,-95.927663 41.199025,-95.927738 41.199453,-95.927908 41.199749,-95.927975 41.200069,-95.927932 41.200426,-95.927748 41.200833,-95.927733 41.200866,-95.927739 41.201082,-95.927752 41.201126,-95.927935 41.20175,-95.927985 41.202056,-95.928011 41.202211,-95.928071 41.202569,-95.928047 41.202892,-95.927934 41.20324,-95.928019 41.204895,-95.927988 41.205398,-95.927846 41.205888,-95.927528 41.206327,-95.927523 41.206369,-95.927459 41.206905,-95.927454 41.206947,-95.927453 41.206956,-95.927445 41.207023,-95.927262 41.208554,-95.927183 41.208971,-95.926912 41.20943,-95.926865 41.209585,-95.926838 41.209675,-95.92683 41.209998,-95.926765 41.210218,-95.926736 41.210314,-95.926572 41.210613,-95.92634 41.210885,-95.92628 41.211082,-95.926275 41.211098,-95.926117 41.211619,-95.925932 41.211911,-95.925678 41.212171,-95.92563 41.212284,-95.925461 41.212686,-95.925084 41.213269,-95.924683 41.213555,-95.924618 41.213601,-95.924538 41.213658,-95.924524 41.213668,-95.924522 41.21367,-95.924463 41.213712,-95.924189 41.214119,-95.924179 41.214134,-95.923686 41.214866,-95.923426 41.215369,-95.923045 41.215824,-95.922764 41.216069,-95.922488 41.216231,-95.922426 41.216267,-95.922319 41.216394,-95.922284 41.216436,-95.92228 41.216441,-95.922279 41.216442,-95.922278 41.216443,-95.922277 41.216445,-95.922273 41.21645,-95.922257 41.216469,-95.922225 41.216507,-95.922143 41.216605,-95.921865 41.216936,-95.92096 41.217676,-95.920512 41.218037,-95.920026 41.218428,-95.919342 41.218979,-95.919221 41.219076,-95.919037 41.219224,-95.919026 41.219233,-95.918502 41.219667,-95.91849 41.219677,-95.9173 41.220617,-95.917005 41.221027,-95.915814 41.222105,-95.915556 41.222506,-95.915523 41.222557,-95.914272 41.223858,-95.913843 41.22458,-95.913661 41.224988,-95.912977 41.225895,-95.912763 41.226294,-95.912711 41.226528,-95.912669 41.226719,-95.912374 41.227328,-95.911801 41.228316,-95.911734 41.228457,-95.911496 41.228962,-95.911363 41.22978,-95.91106 41.230461,-95.911053 41.230892,-95.910918 41.231531,-95.910912 41.231546,-95.910879 41.231631,-95.910676 41.23215,-95.910712 41.232685,-95.910666 41.233223,-95.910735 41.233615,-95.910724 41.233993,-95.910724 41.234002,-95.910716 41.234299,-95.910629 41.234579,-95.910712 41.235043,-95.910703 41.23623,-95.910955 41.237769,-95.910955 41.238032,-95.91113 41.23848,-95.911296 41.239442,-95.91164 41.239994,-95.91186 41.241023,-95.912225 41.241566,-95.912472 41.242295,-95.912932 41.242924,-95.913015 41.243098,-95.913109 41.243296,-95.913175 41.243542,-95.913265 41.24365,-95.91329 41.24368,-95.913305 41.243698,-95.913306 41.2437,-95.913308 41.243702,-95.913309 41.243703,-95.913338 41.243738,-95.913508 41.243942,-95.913961 41.244485,-95.914168 41.245,-95.914408 41.245352,-95.915113 41.246075,-95.915389 41.246647,-95.915408 41.246663,-95.915434 41.246685,-95.915793 41.246999,-95.91602 41.247274,-95.916211 41.247755,-95.91642 41.248012,-95.916469 41.248073,-95.916573 41.248201,-95.916732 41.248398,-95.916899 41.248831,-95.917399 41.249685,-95.917438 41.24976,-95.917443 41.249769,-95.917447 41.249778,-95.91745 41.249784,-95.917455 41.249792,-95.917459 41.249801,-95.91747 41.249821,-95.917472 41.249826,-95.917475 41.249832,-95.91748 41.249842,-95.917646 41.250164,-95.918352 41.251532,-95.918526 41.251805,-95.918534 41.251818,-95.918535 41.251819,-95.918587 41.2519,-95.919093 41.252698,-95.919532 41.253724,-95.919564 41.253876,-95.919567 41.253895,-95.919577 41.253943,-95.919582 41.253968,-95.919616 41.254123,-95.920225 41.255296,-95.920265 41.255374,-95.920445 41.255722,-95.920719 41.256712,-95.92134 41.258129,-95.921345 41.258139,-95.921364 41.258181,-95.921427 41.258456,-95.921429 41.258464,-95.92143 41.258469,-95.921432 41.258482,-95.921606 41.259279,-95.921627 41.259412,-95.921629 41.259426,-95.921631 41.25944,-95.921632 41.259444,-95.921633 41.259448,-95.921634 41.259455,-95.921645 41.259527,-95.92166 41.259626,-95.921669 41.259688,-95.92168 41.259763,-95.921692 41.259838,-95.921776 41.260387,-95.922104 41.261399,-95.922113 41.261722,-95.922042 41.262033,-95.922304 41.26302,-95.922308 41.263848,-95.922423 41.264635,-95.922395 41.26546,-95.921704 41.26687,-95.921643 41.267262,-95.921497 41.267604,-95.92056 41.26851,-95.920119 41.269085,-95.920034 41.269143,-95.919867 41.269255,-95.919701 41.269367,-95.918732 41.269824,-95.917927 41.270553,-95.917755 41.270709,-95.917646 41.270782,-95.91747 41.2709,-95.916662 41.271201,-95.916343 41.271468,-95.915741 41.271821,-95.915316 41.271985,-95.915181 41.272014,-95.915121 41.272027,-95.915097 41.272032,-95.914813 41.272092,-95.914671 41.272158,-95.914091 41.272428,-95.914909 41.272559,-95.91573 41.272691,-95.915832 41.272707,-95.915937 41.272725,-95.916045 41.272802,-95.916772 41.273322,-95.917872 41.274085,-95.917914 41.274115,-95.918041 41.274208,-95.918187 41.2743,-95.918585 41.274567,-95.91887 41.274758,-95.924811 41.278741,-95.924835 41.278759,-95.925884 41.27953,-95.925948 41.27957,-95.926036 41.279627,-95.927075 41.280306,-95.927076 41.280307,-95.927102 41.280324,-95.927332 41.280474,-95.927407 41.280523,-95.927453 41.280553,-95.927477 41.280569,-95.927496 41.280581,-95.927497 41.280582,-95.927503 41.280585,-95.927504 41.280586,-95.927526 41.2806,-95.927783 41.280768,-95.928646 41.281332,-95.929619 41.285077,-95.929602 41.290449,-95.929607 41.290629,-95.929651 41.292262,-95.929219 41.29359,-95.929066 41.29406,-95.928968 41.294361,-95.92896 41.294386,-95.928958 41.294392,-95.928957 41.294395,-95.928957 41.294397,-95.928952 41.294413,-95.928936 41.294462,-95.928925 41.294495,-95.92881 41.294849,-95.928341 41.296291,-95.928309 41.29639,-95.928295 41.296432,-95.928294 41.296435,-95.92828 41.296477,-95.928258 41.296545,-95.928128 41.296945,-95.928043 41.297206,-95.92797 41.297431,-95.927965 41.297446,-95.927959 41.297466,-95.927958 41.297469,-95.92795 41.297494,-95.927905 41.297632,-95.927642 41.29844,-95.926393 41.298887,-95.924691 41.299597,-95.92236 41.300392,-95.920247 41.301191,-95.913781 41.301377,-95.913576 41.301383,-95.912055 41.301258,-95.911712 41.301243,-95.911413 41.30123,-95.911305 41.301225,-95.911262 41.301223,-95.911251 41.301223,-95.911248 41.301223,-95.910806 41.301204,-95.910791 41.301203,-95.910788 41.301203,-95.909997 41.301168,-95.90972 41.301157,-95.909566 41.30115,-95.909545 41.301149,-95.909541 41.301149,-95.909539 41.301149,-95.909538 41.301148,-95.909535 41.301148,-95.909514 41.301147,-95.909491 41.301146,-95.909462 41.301145,-95.90944 41.301144,-95.909435 41.301144,-95.909431 41.301144,-95.909424 41.301144,-95.909422 41.301143,-95.90942 41.301143,-95.909418 41.301143,-95.909416 41.301143,-95.90935 41.30114,-95.909302 41.301138,-95.908971 41.301124,-95.908506 41.301103,-95.908387 41.3011,-95.908245 41.301096,-95.908198 41.301095,-95.908137 41.301094,-95.908111 41.301093,-95.908008 41.30109,-95.907557 41.301079,-95.907407 41.301075,-95.907307 41.301073,-95.907302 41.301073,-95.907256 41.301072,-95.907129 41.301069,-95.906079 41.301043,-95.905969 41.30104,-95.90596 41.30104,-95.905958 41.301039,-95.905956 41.301039,-95.905952 41.301039,-95.905939 41.301039,-95.905932 41.301039,-95.905899 41.301038,-95.904865 41.299787,-95.904683 41.299408,-95.904659 41.299359,-95.904518 41.299065,-95.904481 41.298987,-95.90441 41.298839,-95.904393 41.298804,-95.904356 41.298543,-95.904318 41.298275,-95.904241 41.297729,-95.904222 41.297591,-95.904229 41.297497,-95.904235 41.297424,-95.904242 41.297341,-95.904262 41.297102,-95.904262 41.297098,-95.904263 41.29709,-95.904379 41.295672,-95.904383 41.295613,-95.904384 41.295604,-95.90439 41.295536,-95.904392 41.295506,-95.904404 41.295358,-95.90441 41.29528,-95.904421 41.295149,-95.904427 41.295079,-95.904444 41.294873,-95.904444 41.29487,-95.904444 41.294868,-95.904445 41.294867,-95.904445 41.294865,-95.904445 41.294863,-95.904449 41.294821,-95.904457 41.29472,-95.904478 41.294457,-95.904505 41.294127,-95.904514 41.294016,-95.904514 41.294014,-95.904515 41.293999,-95.904518 41.293965,-95.904554 41.293519,-95.904576 41.293255,-95.904825 41.293255,-95.905146 41.293346,-95.905411 41.293176,-95.90539 41.293076,-95.904786 41.292959,-95.90471 41.292883,-95.904766 41.292586,-95.905216 41.291616,-95.905123 41.291341,-95.905322 41.291242,-95.905357 41.291224,-95.905482 41.29123,-95.905952 41.288572,-95.905961 41.288521,-95.905965 41.288502,-95.905965 41.2885,-95.905966 41.288497,-95.905966 41.288494,-95.905967 41.288488,-95.905969 41.288476,-95.905974 41.28845,-95.906144 41.287488,-95.906192 41.287369,-95.906666 41.286195,-95.906668 41.286139,-95.906682 41.286105,-95.906769 41.2859,-95.906814 41.285795,-95.906901 41.285589,-95.906906 41.285577,-95.906912 41.285563,-95.90693 41.285521,-95.907117 41.28508,-95.907118 41.285077,-95.907119 41.285074,-95.907122 41.285067,-95.907131 41.285046,-95.90716 41.284977,-95.907177 41.284938,-95.9074 41.284734,-95.907604 41.284528,-95.907705 41.28442,-95.907751 41.284366,-95.907793 41.284316,-95.908038 41.284069,-95.9082 41.283906,-95.909059 41.283051,-95.909558 41.282551,-95.910543 41.281564,-95.910791 41.281317,-95.910974 41.281134,-95.912581 41.279527,-95.912576 41.279524,-95.909856 41.277913,-95.90866 41.277198,-95.908151 41.276893,-95.90794 41.276767,-95.907121 41.276279,-95.906905 41.276141,-95.906891 41.276132,-95.906837 41.276097,-95.906707 41.276014,-95.906701 41.27601,-95.906695 41.276006,-95.906661 41.275984,-95.906651 41.275978,-95.906562 41.275921,-95.906551 41.275914,-95.906418 41.275829,-95.905998 41.275561,-95.905976 41.275547,-95.905907 41.275503,-95.905724 41.275386,-95.905178 41.275037,-95.90505 41.275,-95.90493 41.274967,-95.904921 41.274964,-95.904719 41.274907,-95.90388 41.274669,-95.9029 41.274391,-95.90284 41.274374,-95.902812 41.274378,-95.902793 41.274381,-95.902709 41.274393,-95.90266 41.2744,-95.902633 41.274404,-95.902618 41.274406,-95.902615 41.274406,-95.902599 41.274408,-95.902538 41.274417,-95.902468 41.274427,-95.902262 41.274456,-95.901985 41.274496,-95.901523 41.274683,-95.899846 41.2752,-95.898441 41.275537,-95.898031 41.27572,-95.897634 41.275977,-95.896875 41.276191,-95.896114 41.276322,-95.89611 41.276324,-95.896067 41.276342,-95.896058 41.276346,-95.896043 41.276352,-95.896026 41.276359,-95.896018 41.276362,-95.896 41.27637,-95.895432 41.276611,-95.893148 41.277348,-95.892506 41.277555,-95.892481 41.277563,-95.892478 41.277564,-95.892476 41.277564,-95.892474 41.277565,-95.892468 41.277567,-95.892446 41.277574,-95.892439 41.277576,-95.892438 41.277577,-95.892435 41.277578,-95.89238 41.277596,-95.891895 41.277752,-95.891849 41.277767,-95.891845 41.277768,-95.891841 41.277769,-95.891828 41.277773,-95.891823 41.277775,-95.89145 41.277895,-95.891214 41.277971,-95.891212 41.277972,-95.891209 41.277973,-95.891201 41.277976,-95.891191 41.277979,-95.891171 41.277985,-95.891155 41.27799,-95.891137 41.277996,-95.891115 41.278003,-95.89109 41.278011,-95.891078 41.278015,-95.891066 41.278019,-95.891 41.27804,-95.887615 41.279366,-95.884495 41.280689,-95.883316 41.281301,-95.881448 41.282171,-95.880659 41.28264,-95.880512 41.282727,-95.880496 41.282736,-95.879619 41.283238,-95.878881 41.283661,-95.878408 41.284016,-95.877481 41.284583,-95.877432 41.284623,-95.877191 41.284818,-95.877137 41.284861,-95.87681 41.285125,-95.876051 41.285739,-95.875593 41.286199,-95.875309 41.286615,-95.874716 41.287032,-95.874324 41.287394,-95.874139 41.287646,-95.874004 41.287953,-95.873723 41.288259,-95.873652 41.288337,-95.873378 41.28886,-95.873317 41.288951,-95.873231 41.289079,-95.872937 41.289517,-95.872917 41.289546,-95.872894 41.289581,-95.872892 41.289584,-95.872872 41.289614,-95.872817 41.289696,-95.872816 41.289698,-95.872814 41.2897,-95.872813 41.289701,-95.872807 41.28971,-95.872806 41.289712,-95.872667 41.289919,-95.872634 41.290019,-95.872631 41.290028,-95.872539 41.290302,-95.872128 41.291145,-95.871809 41.292378,-95.871589 41.292735,-95.871442 41.293408,-95.871483 41.294018,-95.871382 41.294295,-95.871368 41.295054,-95.871367 41.295083,-95.871192 41.296044,-95.871141 41.29669,-95.871304 41.297286,-95.871277 41.298077,-95.871391 41.298572,-95.871395 41.299482,-95.87161 41.300069,-95.871679 41.300967,-95.871819 41.301348,-95.871871 41.301813,-95.872269 41.30307,-95.87247 41.303435,-95.872705 41.30428,-95.873049 41.304871,-95.873404 41.305879,-95.873643 41.306287,-95.874071 41.307466,-95.874184 41.307924,-95.87435 41.308183,-95.874578 41.308414,-95.875109 41.309453,-95.875503 41.309857,-95.875725 41.310293,-95.876461 41.311087,-95.87676 41.311537,-95.877399 41.312071,-95.878292 41.313081,-95.878634 41.313331,-95.879163 41.313571,-95.879868 41.314381,-95.88022 41.314624,-95.880649 41.314825,-95.881868 41.315687,-95.882257 41.316179,-95.882916 41.316647,-95.883313 41.316847,-95.883793 41.317006,-95.884576 41.317417,-95.885554 41.318097,-95.88635 41.318422,-95.886941 41.318567,-95.887214 41.318604,-95.887316 41.318618,-95.887802 41.318833,-95.887808 41.318836,-95.888109 41.318969,-95.888236 41.319025,-95.888244 41.319029,-95.888414 41.319104,-95.888442 41.319116,-95.888481 41.319133,-95.888633 41.3192,-95.888926 41.31933,-95.889251 41.319474,-95.889914 41.319677,-95.890921 41.319902,-95.891506 41.320211,-95.892035 41.320374,-95.893449 41.320536,-95.893817 41.320612,-95.894405 41.320848,-95.894818 41.320937,-95.895244 41.320974,-95.896006 41.320945,-95.896839 41.321103,-95.897284 41.321234,-95.89879 41.321249,-95.899943 41.321481,-95.900878 41.321341,-95.901736 41.321474,-95.902577 41.321347,-95.904145 41.321238,-95.905181 41.321108,-95.907631 41.320656,-95.907696 41.320644,-95.907705 41.320643,-95.907918 41.320604,-95.908814 41.320498,-95.909734 41.320304,-95.910541 41.320286,-95.911457 41.320191,-95.912337 41.320079,-95.912371 41.320075,-95.912736 41.320075,-95.914116 41.320077,-95.914618 41.319962,-95.915091 41.319918,-95.915566 41.319948,-95.916495 41.320113,-95.917705 41.320136,-95.918174 41.320074,-95.918651 41.320079,-95.921378 41.320434,-95.922744 41.320861,-95.925185 41.321355,-95.925835 41.32158,-95.927206 41.322267,-95.927444 41.322386,-95.92779 41.322559,-95.929383 41.32313,-95.931008 41.324236,-95.931717 41.324602,-95.932144 41.324771,-95.932146 41.324772,-95.932859 41.325054,-95.934463 41.32596,-95.934972 41.326173,-95.93573 41.326473,-95.936383 41.326997,-95.93786 41.327853,-95.939024 41.328416,-95.939386 41.32851,-95.939578 41.328597,-95.939605 41.328609,-95.940121 41.328843,-95.940672 41.329337,-95.941778 41.330025,-95.941789 41.330032,-95.942955 41.330756,-95.94365 41.331243,-95.943654 41.331246,-95.944087 41.33155,-95.944843 41.33208,-95.946532 41.333139,-95.947626 41.334084,-95.947645 41.334101,-95.948258 41.334631,-95.950198 41.336221,-95.951208 41.337179,-95.951657 41.337502,-95.952646 41.338692,-95.953418 41.33947,-95.955294 41.341896,-95.955764 41.342633,-95.956038 41.343063,-95.956693 41.344721,-95.956994 41.345521,-95.957008 41.345558,-95.957052 41.345676,-95.957097 41.345794,-95.957137 41.345902,-95.957301 41.347369,-95.957249 41.348879,-95.95713 41.349593,-95.956635 41.350716,-95.956294 41.351062,-95.955671 41.351994,-95.954883 41.352808,-95.954271 41.353508,-95.954171 41.353592,-95.953953 41.353776,-95.953443 41.354042,-95.952317 41.354871,-95.950965 41.355479,-95.950402 41.355677,-95.949894 41.355777,-95.949341 41.355986,-95.948597 41.356115,-95.948268 41.35626,-95.946914 41.356609,-95.945699 41.356801,-95.945121 41.356865,-95.943337 41.357063,-95.943017 41.357135,-95.941359 41.35769,-95.940811 41.357813,-95.940586 41.357904,-95.940055 41.358119,-95.939718 41.358206,-95.939062 41.358375,-95.938294 41.35874,-95.936923 41.359247,-95.936555 41.359475,-95.936258 41.359756,-95.935468 41.360135,-95.935042 41.360521,-95.933642 41.361551,-95.933347 41.361834,-95.933128 41.362153,-95.932184 41.36296,-95.931711 41.363626,-95.931421 41.364035,-95.93135 41.364108,-95.931015 41.364454,-95.930965 41.364495,-95.930767 41.365008,-95.930296 41.365803,-95.930245 41.365889,-95.929681 41.367058,-95.928774 41.37008,-95.928824 41.370545,-95.928768 41.37166,-95.928951 41.372441,-95.929008 41.37316,-95.929173 41.373857,-95.929294 41.374365,-95.929457 41.375182,-95.929486 41.375326,-95.929714 41.375875,-95.929799 41.376228,-95.930281 41.376995,-95.930388 41.377345,-95.930575 41.377676,-95.932246 41.379815,-95.932485 41.379997,-95.93314 41.380875,-95.933878 41.381714,-95.934896 41.383136,-95.935286 41.383811,-95.935407 41.384021,-95.936073 41.385252,-95.936411 41.385876,-95.936644 41.386573,-95.936721 41.38729,-95.93699 41.387834,-95.937237 41.388334,-95.937418 41.389516,-95.937791 41.390752,-95.937666 41.391568,-95.937771 41.392255,-95.937806 41.393201,-95.937676 41.393834,-95.937726 41.394232,-95.937694 41.39477,-95.937272 41.396131,-95.937168 41.396735,-95.936871 41.397342,-95.936752 41.397873,-95.935793 41.399632,-95.935426 41.400135,-95.935062 41.400489,-95.934881 41.400836,-95.93421 41.401834,-95.933153 41.403155,-95.932439 41.404,-95.931587 41.405638,-95.930891 41.407205,-95.930214 41.409021,-95.930141 41.409219,-95.929932 41.410323,-95.929677 41.411071,-95.92961 41.412723,-95.929525 41.41344,-95.929596 41.414157,-95.929549 41.41473,-95.929472 41.415042,-95.929478 41.415329,-95.929875 41.416772,-95.93 41.417557,-95.930473 41.418894,-95.930479 41.418912,-95.930625 41.41924,-95.931224 41.420583,-95.93154 41.421066,-95.931731 41.421549,-95.931788 41.421869,-95.932254 41.423072,-95.932255 41.423073,-95.932519 41.423459,-95.932479 41.424212,-95.932767 41.425159,-95.932994 41.426186,-95.932972 41.42658,-95.933099 41.427219,-95.933135 41.428188,-95.933097 41.428941,-95.932885 41.429603,-95.932882 41.430107,-95.932778 41.430815,-95.932647 41.430946,-95.932525 41.431181,-95.932447 41.431608,-95.932311 41.432071,-95.931942 41.432571,-95.931724 41.433085,-95.931228 41.433613,-95.930921 41.434178,-95.930423 41.434747,-95.930101 41.43496,-95.929514 41.435659,-95.929108 41.435962,-95.928638 41.436501,-95.927326 41.437447,-95.926926 41.43785,-95.925961 41.438439,-95.925571 41.438802,-95.925268 41.438957,-95.924596 41.439519,-95.923285 41.440503,-95.922546 41.44134,-95.922106 41.441665,-95.921971 41.441837,-95.92174 41.44213,-95.921402 41.442838,-95.920857 41.443428,-95.920846 41.443449,-95.920618 41.443937,-95.920279 41.444978,-95.919905 41.445869,-95.91974 41.447373,-95.919537 41.448186,-95.919606 41.448975,-95.919566 41.450015,-95.919801 41.450747,-95.919779 41.451286,-95.919859 41.451494,-95.919989 41.452604,-95.920215 41.452795,-95.920498 41.45324,-95.920678 41.453627,-95.92096 41.454673,-95.921192 41.455138,-95.921655 41.455602,-95.922246 41.456358,-95.922786 41.457306,-95.923867 41.458236,-95.92451 41.458914,-95.924818 41.459127,-95.925384 41.459398,-95.925924 41.459901,-95.926464 41.46025,-95.927081 41.460811,-95.927442 41.461044,-95.927827 41.461257,-95.928419 41.461683,-95.929216 41.461993,-95.930425 41.462748,-95.933537 41.464277,-95.935054 41.464839,-95.935414 41.464936,-95.935903 41.465168,-95.93634 41.465304,-95.937137 41.465381,-95.937626 41.465536,-95.938706 41.465827,-95.939478 41.465982,-95.940481 41.466078,-95.941767 41.466349,-95.94349 41.466388,-95.944544 41.466485,-95.945007 41.466485,-95.946319 41.466272,-95.947142 41.46602,-95.947626 41.465946,-95.948016 41.465827,-95.949589 41.465538,-95.950717 41.46511,-95.952491 41.46451,-95.953829 41.464123,-95.95604 41.4636,-95.957764 41.463367,-95.95791 41.463331,-95.958372 41.463218,-95.95941 41.463154,-95.961363 41.46316,-95.962702 41.463077,-95.965042 41.463212,-95.966688 41.463425,-95.967511 41.463638,-95.968334 41.46389,-95.968745 41.464142,-95.969697 41.464491,-95.970906 41.465071,-95.97124 41.465323,-95.971574 41.465497,-95.972901 41.466082,-95.974223 41.467027,-95.975869 41.467899,-95.976435 41.468286,-95.97785 41.468809,-95.978776 41.469293,-95.979804 41.469719,-95.980499 41.469854,-95.98181 41.4703,-95.984279 41.470939,-95.986465 41.471326,-95.987327 41.471392,-95.987931 41.471481,-95.988497 41.471578,-95.990606 41.471675,-95.991086 41.471789,-95.991661 41.471849,-95.992561 41.47181,-95.993692 41.471675,-95.994207 41.471733,-95.99575 41.471578,-95.996316 41.471597,-95.997482 41.471505,-95.998052 41.47146,-95.998665 41.471528,-95.999668 41.471524,-96.00029 41.471605,-96.000468 41.471629,-96.001612 41.471695,-96.002053 41.471769,-96.004047 41.472146,-96.004936 41.472413,-96.006609 41.473038,-96.007264 41.473435,-96.007354 41.47349,-96.007683 41.473611,-96.00804 41.473742,-96.008519 41.474039,-96.009016 41.47452,-96.009457 41.474795,-96.010588 41.475721,-96.01125 41.476186,-96.011513 41.476443,-96.01176 41.476791,-96.012073 41.477075,-96.013585 41.478469,-96.014116 41.479151,-96.015 41.480048,-96.015386 41.480704,-96.016608 41.481918,-96.017836 41.483584,-96.018563 41.484991,-96.018767 41.485619,-96.019343 41.486926,-96.019532 41.488325,-96.0195 41.490451,-96.019543 41.491592,-96.019054 41.491684,-96.018591 41.491772,-96.017918 41.491976,-96.01738 41.492139,-96.01282 41.494904,-96.000649 41.50254,-96.000319 41.502857,-95.997022 41.506046,-95.996194 41.50696,-95.99328 41.512314,-95.992777 41.514596,-95.993651 41.520178,-95.994308 41.523743,-95.994751 41.524954,-95.995904 41.528152,-95.997233 41.53172,-95.998871 41.536599,-95.999537 41.538283,-95.999966 41.53948,-96.001392 41.540586,-96.003181 41.541825,-96.005112 41.54325,-96.013876 41.545315,-96.01915 41.54514,-96.022811 41.543721,-96.02349 41.543438,-96.024096 41.542833,-96.026657 41.540366,-96.027103 41.538662,-96.027747 41.534663,-96.028381 41.532125,-96.029053 41.529996,-96.02931 41.527981,-96.029915 41.526372,-96.030554 41.524564,-96.031127 41.523004,-96.031705 41.522181,-96.034441 41.521051,-96.033854 41.519935,-96.033675 41.517119,-96.034397 41.514446,-96.03429 41.513036,-96.034353 41.512761,-96.03465 41.512215,-96.034877 41.511704,-96.035076 41.511375,-96.035326 41.510962,-96.035575 41.510549,-96.035881 41.510137,-96.036603 41.509047,-96.037882 41.507899,-96.039046 41.507463,-96.039807 41.507061,-96.040613 41.506893,-96.042895 41.506927,-96.044686 41.506893,-96.046745 41.507061,-96.048311 41.507262,-96.05037 41.507732,-96.052765 41.508436,-96.055048 41.509508,-96.057331 41.511051,-96.060867 41.514101,-96.061717 41.514688,-96.06306 41.515694,-96.063796 41.51623,-96.064267 41.516727,-96.064444 41.516924,-96.064555 41.516934,-96.066842 41.517746,-96.067491 41.518292,-96.068633 41.519113,-96.069067 41.519579,-96.069386 41.519973,-96.069639 41.520284,-96.069893 41.520581,-96.069973 41.520674,-96.070543 41.521246,-96.070636 41.52134,-96.070994 41.521624,-96.071141 41.521741,-96.071384 41.52195,-96.071487 41.522031,-96.071637 41.522149,-96.072478 41.522957,-96.072651 41.523088,-96.073019 41.523299,-96.073389 41.523529,-96.073665 41.52368,-96.074156 41.523947,-96.07457 41.524157,-96.075152 41.52442,-96.075666 41.524633,-96.076761 41.525051,-96.077283 41.525233,-96.077898 41.525432,-96.078343 41.525563,-96.078802 41.52565,-96.079359 41.525781,-96.079922 41.525902,-96.080198 41.525955,-96.081431 41.525966,-96.082253 41.525973,-96.082654 41.525974,-96.084031 41.526875,-96.088305 41.530244,-96.090118 41.531702,-96.090837 41.532644,-96.091908 41.534049,-96.09343 41.537268,-96.094437 41.539463,-96.095109 41.541089,-96.095472 41.542308,-96.095758 41.543269,-96.096586 41.545397,-96.096596 41.545475,-96.096617 41.545626,-96.096359 41.549663,-96.096314 41.550948,-96.096313 41.550979,-96.096313 41.550987,-96.096313 41.550989,-96.095985 41.55164,-96.09587 41.551868,-96.095042 41.554768,-96.094706 41.555556,-96.093654 41.558288,-96.092222 41.560886,-96.091259 41.562244,-96.088842 41.56444,-96.088135 41.565047,-96.087428 41.565654,-96.084859 41.567859,-96.082486 41.571145,-96.081613 41.573274,-96.081188 41.574296,-96.081278 41.577045,-96.08139 41.5779,-96.082285 41.580683,-96.08327 41.582627,-96.083672 41.583197,-96.083963 41.583683,-96.084568 41.584337,-96.085396 41.585092,-96.087186 41.58655,-96.088898 41.58748,-96.089066 41.587572,-96.093453 41.589098,-96.093492 41.589108,-96.094591 41.589379,-96.094638 41.589391,-96.098869 41.590439,-96.101331 41.591411,-96.105296 41.593318,-96.105829 41.593574,-96.107709 41.59525,-96.109455 41.596775,-96.112386 41.600279,-96.11364 41.601955,-96.114647 41.603681,-96.11466 41.603703,-96.115811 41.605659,-96.116795 41.608073,-96.117584 41.610441,-96.118233 41.613291,-96.118277 41.614967,-96.118241 41.615278,-96.118079 41.6167,-96.118009 41.617314,-96.117718 41.618555,-96.116755 41.620901,-96.115995 41.622024,-96.114159 41.623969,-96.113107 41.62536,-96.111339 41.627137,-96.109437 41.62883,-96.108094 41.629568,-96.106796 41.630372,-96.105498 41.631495,-96.104692 41.632065,-96.103864 41.63287,-96.102163 41.634379,-96.100709 41.635451,-96.099411 41.637413,-96.097777 41.639625,-96.095937 41.644616,-96.095534 41.645856,-96.095131 41.647767,-96.095221 41.650114,-96.095254 41.650276,-96.096116 41.654472,-96.09621 41.654669,-96.097683 41.657758,-96.100055 41.661077,-96.103994 41.663223,-96.107186 41.665595,-96.107407 41.665586,-96.108918 41.666039,-96.111156 41.667547,-96.112454 41.668486,-96.114065 41.669961,-96.11505 41.670799,-96.116527 41.67204,-96.117959 41.673314,-96.118631 41.674185,-96.119347 41.674722,-96.120152 41.675694,-96.121048 41.677002,-96.121459 41.678104,-96.120865 41.678227,-96.121309 41.679552,-96.121832 41.681001,-96.122346 41.682489,-96.122578 41.683059,-96.122604 41.68304,-96.121853 41.689205,-96.121636 41.689579,-96.119861 41.692649,-96.118731 41.693781,-96.116161 41.695886,-96.115974 41.696016,-96.114222 41.697234,-96.113461 41.697569,-96.108559 41.699279,-96.107902 41.699274,-96.107695 41.699273,-96.107487 41.699271,-96.107155 41.699268,-96.106408 41.699263,-96.106405 41.699263,-96.105478 41.699345,-96.104887 41.699379,-96.104227 41.6994,-96.103814 41.699403,-96.10328 41.699398,-96.103239 41.699397,-96.102993 41.699391,-96.102981 41.699391,-96.102677 41.699379,-96.102425 41.699365,-96.102423 41.699365,-96.10242 41.699365,-96.102417 41.699365,-96.102416 41.699364,-96.102414 41.699364,-96.102302 41.699358,-96.102164 41.699348,-96.101685 41.699313,-96.101537 41.699298,-96.101052 41.699249,-96.10062 41.699194,-96.100565 41.699187,-96.100563 41.699187,-96.10056 41.699187,-96.100551 41.699186,-96.100546 41.699185,-96.100474 41.699176,-96.100421 41.699169,-96.10038 41.699163,-96.100276 41.699147,-96.100238 41.699141,-96.100236 41.69914,-96.100229 41.699139,-96.100221 41.699138,-96.100195 41.699134,-96.099669 41.699051,-96.099663 41.69905,-96.0996 41.69904,-96.099518 41.699025,-96.0995 41.699022,-96.099396 41.699002,-96.098866 41.698898,-96.098559 41.698831,-96.098172 41.69878,-96.098165 41.698779,-96.098164 41.698778,-96.098162 41.698778,-96.098155 41.698777,-96.098141 41.698775,-96.098047 41.698761,-96.097887 41.698738,-96.097845 41.698732,-96.097595 41.698697,-96.097588 41.698696,-96.097567 41.698693,-96.097505 41.698683,-96.097431 41.698671,-96.097042 41.698609,-96.097025 41.698606,-96.097008 41.698603,-96.096387 41.698495,-96.096374 41.698493,-96.096367 41.698492,-96.096357 41.69849,-96.095984 41.698425,-96.095591 41.698344,-96.095541 41.698333,-96.095539 41.698333,-96.095538 41.698332,-96.095451 41.698313,-96.09515 41.698252,-96.094857 41.698189,-96.094707 41.698156,-96.094705 41.698156,-96.0947 41.698155,-96.094697 41.698154,-96.094688 41.698152,-96.093691 41.697934,-96.093333 41.697845,-96.093013 41.697781,-96.092888 41.697761,-96.092758 41.697737,-96.092731 41.697732,-96.092729 41.697731,-96.092723 41.69773,-96.092721 41.69773,-96.092628 41.697712,-96.092534 41.697697,-96.09251 41.697693,-96.092504 41.697692,-96.092502 41.697692,-96.092495 41.697691,-96.092445 41.697683,-96.092116 41.697633,-96.091997 41.697617,-96.091995 41.697617,-96.091994 41.697616,-96.091992 41.697616,-96.091711 41.697578,-96.0916 41.697564,-96.091569 41.69756,-96.091553 41.697558,-96.091551 41.697558,-96.09149 41.697551,-96.091183 41.697516,-96.091024 41.697503,-96.090991 41.6975,-96.090529 41.697456,-96.090072 41.697425,-96.089913 41.697415,-96.089908 41.697415,-96.089906 41.697414,-96.089904 41.697414,-96.089901 41.697414,-96.089851 41.697412,-96.089819 41.697411,-96.089817 41.697411,-96.089813 41.697411,-96.089781 41.69741,-96.089547 41.697401,-96.089271 41.69739,-96.088831 41.697382,-96.088773 41.697382,-96.088684 41.697382,-96.088682 41.697382,-96.088681 41.697383,-96.088678 41.697383,-96.088672 41.697383,-96.088661 41.697383,-96.088643 41.697383,-96.088619 41.697383,-96.088592 41.697383,-96.088545 41.697383,-96.088529 41.697383,-96.088513 41.697383,-96.088328 41.697383,-96.088247 41.697385,-96.087496 41.697409,-96.087491 41.697409,-96.087483 41.697409,-96.087139 41.69742,-96.086596 41.697467,-96.086576 41.697469,-96.086556 41.697471,-96.086317 41.697492,-96.086307 41.697493,-96.086301 41.697493,-96.086269 41.697496,-96.086123 41.697512,-96.086118 41.697513,-96.085643 41.697565,-96.085628 41.697567,-96.085625 41.697567,-96.085358 41.697596,-96.08486 41.697674,-96.084816 41.697681,-96.08464 41.697709,-96.084391 41.697755,-96.084083 41.697812,-96.084078 41.697813,-96.084076 41.697813,-96.083822 41.69786,-96.083719 41.697884,-96.083717 41.697885,-96.082618 41.698143,-96.082614 41.698144,-96.082612 41.698144,-96.082608 41.698145,-96.082353 41.698205,-96.081315 41.698513,-96.081049 41.698612,-96.081047 41.698613,-96.081046 41.698614,-96.081043 41.698615,-96.081037 41.698617,-96.079875 41.69905,-96.079764 41.699095,-96.079553 41.699185,-96.079257 41.699312,-96.079053 41.699398,-96.078707 41.699544,-96.078302 41.699727,-96.078207 41.699774,-96.077712 41.700022,-96.077331 41.700232,-96.07695 41.700457,-96.076598 41.70068,-96.076248 41.700918,-96.075909 41.701165,-96.075582 41.701421,-96.075267 41.701686,-96.074964 41.701958,-96.074675 41.702238,-96.074398 41.702526,-96.074135 41.70282,-96.073886 41.703122,-96.073651 41.703429,-96.073431 41.703743,-96.073225 41.704062,-96.073128 41.704472,-96.073075 41.704731,-96.072986 41.705251,-96.072922 41.705773,-96.072883 41.706296,-96.072876 41.706517,-96.072867 41.70682,-96.072877 41.707344,-96.0729 41.707736,-96.072936 41.708129,-96.073006 41.70865,-96.073034 41.708817,-96.07305 41.70891,-96.0731 41.709169,-96.073219 41.709686,-96.073287 41.709943,-96.073361 41.710199,-96.073441 41.710454,-96.073527 41.710708,-96.073717 41.711212,-96.07393 41.711711,-96.074046 41.711959,-96.074293 41.712449,-96.074568 41.712938,-96.074707 41.713171,-96.075011 41.713643,-96.075171 41.713876,-96.075508 41.714335,-96.075865 41.714785,-96.075922 41.714853,-96.076052 41.715007,-96.07644 41.715443,-96.076642 41.715657,-96.077061 41.716077,-96.077498 41.716485,-96.077955 41.716882,-96.07819 41.717076,-96.078674 41.717455,-96.078922 41.717639,-96.079432 41.717998,-96.079693 41.718173,-96.079958 41.718344,-96.080226 41.718511,-96.080776 41.718836,-96.081313 41.719171,-96.081849 41.719495,-96.081919 41.719536,-96.081989 41.719577,-96.082135 41.719662,-96.082408 41.719821,-96.082767 41.720023,-96.082967 41.720135,-96.083533 41.720441,-96.084219 41.720798,-96.084564 41.720969,-96.084584 41.720979,-96.084594 41.720984,-96.0846 41.720987,-96.084603 41.720988,-96.084609 41.720991,-96.084685 41.721029,-96.085272 41.721313,-96.085543 41.721439,-96.085653 41.72149,-96.085865 41.721589,-96.08647 41.72186,-96.087287 41.722207,-96.087691 41.722373,-96.088303 41.722615,-96.088656 41.722748,-96.089023 41.722887,-96.089435 41.723036,-96.089524 41.723068,-96.08954 41.723074,-96.089551 41.723078,-96.089554 41.723079,-96.09018 41.723295,-96.090821 41.723508,-96.091481 41.723717,-96.091947 41.723857,-96.091967 41.723863,-96.091993 41.723871,-96.092779 41.724098,-96.093113 41.724186,-96.093205 41.724211,-96.093579 41.72431,-96.093836 41.72438,-96.09463 41.724565,-96.09468 41.724577,-96.09472 41.724586,-96.094814 41.724608,-96.094909 41.72463,-96.094986 41.724648,-96.095062 41.724666,-96.095215 41.724707,-96.095667 41.724826,-96.096118 41.72495,-96.096564 41.725083,-96.097007 41.725219,-96.097711 41.725451,-96.097972 41.725541,-96.099057 41.725935,-96.099441 41.726086,-96.099818 41.726248,-96.100187 41.726419,-96.100547 41.726601,-96.100899 41.72679,-96.101241 41.72699,-96.101576 41.727199,-96.101899 41.727415,-96.101992 41.727482,-96.102212 41.727641,-96.102515 41.727875,-96.102807 41.728116,-96.103087 41.728365,-96.103356 41.728621,-96.103402 41.728668,-96.103407 41.728673,-96.103459 41.728726,-96.103511 41.72878,-96.103512 41.728781,-96.103514 41.728783,-96.103516 41.728785,-96.103519 41.728788,-96.10352 41.72879,-96.103522 41.728792,-96.103562 41.728837,-96.103614 41.728894,-96.103856 41.729154,-96.104088 41.72943,-96.104306 41.729711,-96.104511 41.729998,-96.104702 41.730291,-96.10488 41.730588,-96.105044 41.73089,-96.105194 41.731195,-96.105329 41.731505,-96.10545 41.731818,-96.105556 41.732133,-96.10563 41.73239,-96.105648 41.732452,-96.105688 41.732612,-96.105807 41.732963,-96.105893 41.73324,-96.105973 41.733518,-96.106113 41.734078,-96.106173 41.734358,-96.106273 41.734922,-96.106314 41.735205,-96.106375 41.735772,-96.106409 41.73634,-96.106417 41.736909,-96.106399 41.737478,-96.10638 41.737762,-96.106354 41.738046,-96.106282 41.738612,-96.106237 41.738894,-96.106185 41.739176,-96.106126 41.739457,-96.106061 41.739737,-96.105911 41.740295,-96.105826 41.740572,-96.105735 41.740848,-96.105651 41.741118,-96.105471 41.741546,-96.105469 41.741634,-96.105451 41.74168,-96.105334 41.74195,-96.105211 41.742219,-96.105082 41.742487,-96.104946 41.742753,-96.104805 41.743016,-96.104657 41.743278,-96.104494 41.743552,-96.104338 41.743805,-96.104006 41.744306,-96.103828 41.744557,-96.103443 41.745069,-96.10326 41.745297,-96.102853 41.745777,-96.102396 41.746236,-96.102173 41.746466,-96.101773 41.746857,-96.101583 41.747035,-96.101462 41.747149,-96.101242 41.747346,-96.101057 41.747506,-96.101042 41.747519,-96.101025 41.747534,-96.101012 41.747545,-96.100981 41.747572,-96.100685 41.747752,-96.100313 41.747964,-96.100268 41.747989,-96.099992 41.748134,-96.099973 41.748144,-96.099969 41.748146,-96.099968 41.748147,-96.099811 41.748229,-96.099612 41.748328,-96.099609 41.74833,-96.099606 41.748331,-96.099399 41.748434,-96.099386 41.74844,-96.099384 41.748441,-96.099381 41.748442,-96.098911 41.748657,-96.098866 41.748677,-96.098536 41.748815,-96.098007 41.749023,-96.097702 41.749105,-96.097603 41.749129,-96.097597 41.749131,-96.097595 41.749132,-96.097593 41.749132,-96.097437 41.749181,-96.097331 41.74922,-96.096187 41.749605,-96.096166 41.74961,-96.096158 41.749612,-96.096125 41.74962,-96.095962 41.749659,-96.095954 41.749661,-96.095378 41.749799,-96.09513 41.749853,-96.09507 41.749866,-96.094937 41.749888,-96.09486 41.7499,-96.094782 41.749913,-96.094653 41.749934,-96.094477 41.749977,-96.094465 41.74998,-96.09427 41.750013,-96.093731 41.750093,-96.093375 41.750161,-96.092966 41.750249,-96.092706 41.750305,-96.092375 41.750383,-96.092045 41.750466,-96.091392 41.750644,-96.091293 41.75067,-96.091193 41.750701,-96.091182 41.750704,-96.091069 41.750739,-96.090972 41.750769,-96.090874 41.750798,-96.090828 41.750814,-96.090819 41.750817,-96.090673 41.750869,-96.089803 41.75118,-96.089801 41.751181,-96.089797 41.751182,-96.089794 41.751183,-96.089772 41.751191,-96.089708 41.751214,-96.089285 41.751367,-96.089105 41.751431,-96.088807 41.751546,-96.088219 41.751786,-96.08793 41.751912,-96.087644 41.752042,-96.087361 41.752176,-96.086805 41.752456,-96.086531 41.752601,-96.086261 41.75275,-96.085996 41.752903,-96.085734 41.753059,-96.085221 41.753383,-96.084969 41.753549,-96.08448 41.753893,-96.084008 41.75425,-96.083552 41.754621,-96.083114 41.755002,-96.082903 41.755198,-96.082493 41.755597,-96.082102 41.756008,-96.08173 41.75643,-96.081379 41.756861,-96.081211 41.757081,-96.08089 41.757526,-96.080738 41.757752,-96.080591 41.757981,-96.080313 41.758442,-96.080182 41.758677,-96.080057 41.758912,-96.079823 41.75939,-96.079714 41.75963,-96.079514 41.760117,-96.079337 41.760609,-96.079257 41.760856,-96.079183 41.761105,-96.078772 41.761734,-96.078696 41.761851,-96.078637 41.761939,-96.078635 41.761945,-96.07863 41.761961,-96.078629 41.761963,-96.078497 41.762632,-96.078466 41.76306,-96.078444 41.763488,-96.078432 41.763918,-96.078431 41.764346,-96.07844 41.764775,-96.078466 41.765327,-96.07858 41.766557,-96.078629 41.766914,-96.078697 41.76734,-96.078775 41.767776,-96.078889 41.768938,-96.078929 41.769438,-96.078958 41.769711,-96.07896 41.769838,-96.078964 41.7699,-96.078996 41.770205,-96.079028 41.770593,-96.079039 41.770817,-96.07905 41.771124,-96.07905 41.771455,-96.07905 41.771676,-96.07905 41.771711,-96.07905 41.771737,-96.079039 41.772044,-96.079021 41.77235,-96.078996 41.772654,-96.078979 41.772818,-96.078973 41.772879,-96.078964 41.772962,-96.078925 41.773267,-96.078878 41.773572,-96.078825 41.773876,-96.078764 41.77418,-96.078697 41.774482,-96.078622 41.774784,-96.078452 41.775384,-96.078356 41.775682,-96.078253 41.775979,-96.078144 41.776275,-96.078028 41.776569,-96.077904 41.776861,-96.077564 41.776861,-96.077077 41.776861,-96.077065 41.776901,-96.077223 41.777165,-96.077375 41.7773,-96.077462 41.777377,-96.077546 41.777456,-96.077546 41.777822,-96.077252 41.778171,-96.077026 41.778439,-96.076824 41.778579,-96.076534 41.77908,-96.076469 41.779168,-96.0764 41.779261,-96.075932 41.779904,-96.075091 41.780896,-96.074229 41.781911,-96.073197 41.783008,-96.071871 41.784186,-96.070687 41.78517,-96.069772 41.785953,-96.068319 41.787162,-96.06723 41.788097,-96.067089 41.788272,-96.06703 41.788378,-96.066984 41.78846,-96.06697 41.788485,-96.066849 41.788478,-96.066754 41.788507,-96.066677 41.788601,-96.066409 41.788941,-96.066377 41.788981,-96.066111 41.789491,-96.065243 41.790934,-96.065049 41.791347,-96.064897 41.791673,-96.064863 41.791889,-96.064801 41.792447,-96.064765 41.793006,-96.064757 41.793286,-96.064761 41.793846,-96.064772 41.794126,-96.06479 41.794406,-96.064846 41.794964,-96.064883 41.795243,-96.064978 41.795798,-96.065035 41.796075,-96.065098 41.796351,-96.065168 41.796626,-96.065327 41.797173,-96.065416 41.797445,-96.065613 41.797985,-96.065721 41.798253,-96.065881 41.798623,-96.066138 41.799027,-96.066404 41.799428,-96.066679 41.799824,-96.066964 41.800218,-96.067257 41.800607,-96.06756 41.800993,-96.067872 41.801375,-96.068192 41.801752,-96.068522 41.802125,-96.06886 41.802494,-96.069269 41.802922,-96.069561 41.803218,-96.069924 41.803573,-96.070296 41.803924,-96.070676 41.804269,-96.071063 41.80461,-96.071459 41.804945,-96.071862 41.805275,-96.072273 41.8056,-96.072691 41.805919,-96.073117 41.806233,-96.07355 41.806541,-96.073991 41.806844,-96.074438 41.80714,-96.074892 41.807431,-96.075353 41.807716,-96.07582 41.807995,-96.076294 41.808268,-96.076774 41.808534,-96.07726 41.808794,-96.077753 41.809048,-96.078685 41.80948,-96.079463 41.80974,-96.080072 41.809987,-96.080073 41.809988,-96.080077 41.809989,-96.080161 41.810023,-96.080348 41.810065,-96.081192 41.810254,-96.082219 41.810563,-96.083322 41.810811,-96.085906 41.811237,-96.086438 41.811357,-96.08644 41.811357,-96.086442 41.811358,-96.086643 41.811403,-96.087161 41.811469,-96.087164 41.811469,-96.087169 41.81147,-96.087175 41.811471,-96.087447 41.811506,-96.087519 41.811524,-96.087523 41.811525,-96.088044 41.811659,-96.089136 41.811792,-96.090845 41.812159,-96.091911 41.812311,-96.091932 41.812314,-96.091975 41.812324,-96.092851 41.812521,-96.093006 41.812588,-96.093008 41.812589,-96.093431 41.812773,-96.093924 41.812918,-96.095273 41.813182,-96.096031 41.813493,-96.097038 41.813788,-96.097042 41.813789,-96.09748 41.813917,-96.097696 41.814042,-96.097698 41.814043,-96.097935 41.814181,-96.098639 41.814422,-96.098642 41.814423,-96.098644 41.814424,-96.098722 41.814451,-96.100885 41.815389,-96.101272 41.815603,-96.101538 41.81581,-96.102332 41.816215,-96.103566 41.817072,-96.104224 41.817663,-96.105336 41.81846,-96.106565 41.819613,-96.108714 41.822099,-96.109384 41.823135,-96.109746 41.823992,-96.11056 41.826548,-96.110831 41.827914,-96.110962 41.828827,-96.111005 41.829519,-96.11101 41.829606,-96.111017 41.829726,-96.111029 41.831022,-96.110967 41.831596,-96.110797 41.832157,-96.110155 41.833605,-96.109954 41.834144,-96.10969 41.834853,-96.109514 41.835193,-96.109378 41.835457,-96.10911 41.836043,-96.108969 41.83635,-96.108963 41.83636,-96.108272 41.837698,-96.108271 41.837699,-96.108269 41.837704,-96.108267 41.837709,-96.108265 41.837714,-96.108263 41.837719,-96.108262 41.837721,-96.108171 41.837898,-96.108106 41.838109,-96.108105 41.838112,-96.10794 41.838644,-96.107939 41.838646,-96.10778 41.83916,-96.107617 41.8404,-96.107669 41.841421,-96.107669 41.841423,-96.107669 41.841425,-96.107669 41.841427,-96.107669 41.84143,-96.107669 41.841436,-96.107681 41.841678,-96.107681 41.841682,-96.107685 41.841766,-96.107754 41.842175,-96.107757 41.842194,-96.107894 41.843014,-96.107894 41.843016,-96.107936 41.843266,-96.108287 41.844535,-96.108501 41.845126,-96.108502 41.845127,-96.108503 41.845131,-96.108504 41.845134,-96.108704 41.845687,-96.109312 41.847128,-96.110548 41.849199,-96.111252 41.850229,-96.111631 41.850688,-96.112464 41.851571,-96.11451 41.853572,-96.115498 41.854348,-96.116597 41.855103,-96.120152 41.857188,-96.122354 41.858001,-96.124984 41.858892,-96.125366 41.859095,-96.125367 41.859096,-96.125369 41.859097,-96.125688 41.859266,-96.1312 41.861426,-96.132615 41.862053,-96.13496 41.863252,-96.136525 41.864151,-96.138381 41.865683,-96.138939 41.866212,-96.139008 41.866277,-96.13948 41.8668,-96.140417 41.86784,-96.140982 41.868279,-96.141539 41.868712,-96.141908 41.868999,-96.142694 41.869851,-96.143133 41.870328,-96.144359 41.871971,-96.145026 41.873433,-96.145312 41.873935,-96.145974 41.875093,-96.14605 41.875377,-96.146554 41.877244,-96.14666 41.877636,-96.146763 41.878634,-96.146755 41.880276,-96.146801 41.881998,-96.146927 41.883019,-96.146956 41.883254,-96.147292 41.884896,-96.1473 41.884917,-96.147319 41.884967,-96.147349 41.885043,-96.147379 41.885118,-96.147399 41.885169,-96.147491 41.885405,-96.14777 41.886114,-96.147772 41.88612,-96.147864 41.886351,-96.147919 41.886492,-96.148004 41.886706,-96.148087 41.886918,-96.148144 41.88706,-96.148186 41.887166,-96.148286 41.88734,-96.148757 41.888161,-96.148783 41.888205,-96.148942 41.888417,-96.149641 41.889351,-96.149874 41.889662,-96.150986 41.890934,-96.151979 41.891956,-96.152153 41.892135,-96.152881 41.892694,-96.153049 41.892823,-96.153556 41.893211,-96.1536 41.893245,-96.15372 41.893348,-96.153776 41.893396,-96.153862 41.893469,-96.153947 41.893541,-96.154004 41.89359,-96.154918 41.894368,-96.155198 41.894607,-96.156719 41.895806,-96.157668 41.89669,-96.158198 41.897184,-96.158521 41.897535,-96.158757 41.897791,-96.158926 41.897974,-96.159333 41.898411,-96.159451 41.898536,-96.159471 41.898559,-96.159643 41.898754,-96.1597 41.898823,-96.1602 41.89944,-96.161101 41.900551,-96.161538 41.901398,-96.161756 41.90182,-96.161835 41.902131,-96.162024 41.902881,-96.162067 41.903047,-96.16209 41.904022,-96.162011 41.905194,-96.161988 41.905553,-96.161841 41.90594,-96.161737 41.906214,-96.161501 41.906839,-96.16139 41.90702,-96.161238 41.907271,-96.161193 41.907344,-96.161058 41.907564,-96.161014 41.907638,-96.160943 41.907753,-96.160767 41.908044,-96.160728 41.908098,-96.160649 41.908208,-96.160013 41.909095,-96.159098 41.910057,-96.158617 41.910377,-96.15784 41.910896,-96.156302 41.91166,-96.154301 41.912421,-96.152582 41.912894,-96.150213 41.913361,-96.149332 41.913536,-96.14726 41.913862,-96.147193 41.91388,-96.147094 41.913908,-96.146994 41.913935,-96.146928 41.913954,-96.14669 41.91402,-96.146334 41.914119,-96.145976 41.914217,-96.145739 41.914283,-96.145702 41.914293,-96.145648 41.91431,-96.145592 41.914324,-96.145556 41.914334,-96.14459 41.914603,-96.144364 41.914678,-96.142265 41.915379,-96.140946 41.916115,-96.139863 41.91672,-96.139653 41.916838,-96.139275 41.917238,-96.1383 41.918273,-96.13776 41.919054,-96.137359 41.919637,-96.137321 41.919711,-96.136848 41.920622,-96.136743 41.920826,-96.136133 41.92353,-96.136134 41.923608,-96.136155 41.924635,-96.136158 41.92477,-96.136167 41.925175,-96.13617 41.925311,-96.136246 41.925631,-96.136362 41.926114,-96.136476 41.926594,-96.136553 41.926916,-96.136571 41.926993,-96.136613 41.927167,-96.136641 41.92722,-96.13668 41.92729,-96.137299 41.92842,-96.137672 41.9291,-96.1395 41.931529,-96.139526 41.931572,-96.140192 41.932676,-96.140278 41.932819,-96.140486 41.933163,-96.140549 41.933244,-96.140654 41.933376,-96.141869 41.93492,-96.142034 41.935173,-96.142718 41.936222,-96.143493 41.937387,-96.144041 41.93868,-96.14448 41.940013,-96.14457 41.941351,-96.144583 41.941544,-96.144368 41.942478,-96.143975 41.943522,-96.14366 41.944358,-96.143603 41.944512,-96.142597 41.945908,-96.141965 41.946393,-96.14164 41.946644,-96.141243 41.946918,-96.140988 41.947093,-96.140413 41.947491,-96.140223 41.947614,-96.139964 41.947782,-96.139745 41.947923,-96.139414 41.948138,-96.139173 41.948437,-96.139011 41.94864,-96.138803 41.948899,-96.138781 41.948926,-96.1381 41.949791,-96.137873 41.95008,-96.137743 41.950247,-96.137242 41.950641,-96.137076 41.950773,-96.137027 41.950798,-96.13695 41.95084,-96.136881 41.950876,-96.136833 41.950903,-96.136699 41.950968,-96.136658 41.951003,-96.136227 41.951383,-96.136203 41.951409,-96.136067 41.95156,-96.135876 41.951746,-96.135393 41.952223,-96.135334 41.952328,-96.135205 41.952562,-96.134997 41.952938,-96.134979 41.952966,-96.134241 41.954126,-96.134233 41.954138,-96.134 41.954539,-96.133476 41.955454,-96.133318 41.955732,-96.132293 41.958382,-96.131913 41.959366,-96.131742 41.959808,-96.131578 41.960234,-96.131368 41.960741,-96.131088 41.961053,-96.131034 41.961114,-96.130772 41.961407,-96.13064 41.961603,-96.130442 41.9619,-96.13025 41.962198,-96.130123 41.962398,-96.129978 41.962642,-96.129727 41.96343,-96.129186 41.965136,-96.12908 41.965973,-96.129037 41.96667,-96.128975 41.967689,-96.128971 41.967779,-96.128969 41.967797,-96.128967 41.967852,-96.128967 41.967871,-96.128933 41.968656,-96.128925 41.968883,-96.128902 41.96965,-96.1289 41.969727,-96.129254 41.97093,-96.129505 41.971673,-96.129877 41.972199,-96.130355 41.972863,-96.1312 41.97361,-96.131702 41.974,-96.132077 41.974292,-96.132537 41.974625,-96.133203 41.974944,-96.133321 41.97499,-96.133675 41.975128,-96.133794 41.975174,-96.135151 41.975701,-96.139223 41.977285,-96.140338 41.977719,-96.140581 41.977813,-96.140685 41.977852,-96.140842 41.977913,-96.140998 41.977971,-96.141103 41.978011,-96.141229 41.978063,-96.142715 41.978491,-96.144068 41.978645,-96.144439 41.978688,-96.147035 41.97905,-96.149977 41.979414,-96.15209 41.979661,-96.153136 41.979734,-96.154355 41.979819,-96.15616 41.980082,-96.156559 41.980141,-96.157212 41.980199,-96.158049 41.980275,-96.160382 41.980133,-96.160683 41.980115,-96.161437 41.980037,-96.161999 41.979978,-96.163338 41.979839,-96.16368 41.979769,-96.164234 41.979658,-96.165492 41.979404,-96.165716 41.97936,-96.168074 41.978996,-96.169285 41.978738,-96.170542 41.978472,-96.171888 41.977837,-96.173315 41.977189,-96.174154 41.976864,-96.174827 41.97671,-96.177204 41.976308,-96.177777 41.976298,-96.179567 41.976284,-96.18105 41.976365,-96.181945 41.976412,-96.182224 41.976436,-96.183179 41.976571,-96.183537 41.976663,-96.183909 41.976719,-96.184164 41.976793,-96.187219 41.977946,-96.188746 41.979027,-96.189052 41.979264,-96.189099 41.979301,-96.18925 41.979417,-96.189413 41.979542,-96.189546 41.97965,-96.189724 41.979831,-96.189781 41.979889,-96.190049 41.98016,-96.190476 41.980593,-96.190608 41.980729,-96.190802 41.980998,-96.191029 41.981312,-96.191326 41.981724,-96.191549 41.982032,-96.192141 41.984461,-96.191971 41.985347,-96.191567 41.986061,-96.191146 41.986745,-96.19037 41.987964,-96.190245 41.98816,-96.19006 41.988454,-96.189871 41.988751,-96.189747 41.988948,-96.18963 41.989134,-96.189517 41.989315,-96.189238 41.989664,-96.189102 41.989836,-96.188786 41.990238,-96.188389 41.990757,-96.187605 41.991786,-96.186427 41.993076,-96.186156 41.993441,-96.185573 41.994232,-96.185474 41.994385,-96.185407 41.994487,-96.18521 41.994795,-96.185144 41.994898,-96.184784 41.99546,-96.184255 41.996634,-96.183801 41.99776,-96.183627 41.99904,-96.183608 41.99999,-96.184188 42.001606,-96.184318 42.001831,-96.184327 42.001853,-96.184361 42.001938,-96.184465 42.002193,-96.1845 42.002279,-96.18422 42.002543,-96.184002 42.003063,-96.184258 42.003686,-96.184378 42.003978,-96.18462 42.004578,-96.184711 42.004972,-96.185249 42.005467,-96.185919 42.005914,-96.186823 42.00633,-96.187522 42.006626,-96.18773 42.006656,-96.189336 42.00689,-96.189553 42.006987,-96.189766 42.007082,-96.190207 42.007278,-96.190425 42.007376,-96.190819 42.007552,-96.190913 42.007579,-96.192436 42.008032,-96.192944 42.008183,-96.193002 42.0082,-96.193178 42.008252,-96.193237 42.00827,-96.193313 42.008292,-96.193544 42.008361,-96.193621 42.008384,-96.193714 42.008411,-96.193993 42.008495,-96.194086 42.008523,-96.194556 42.008662,-96.194838 42.008671,-96.200816 42.009196,-96.201487 42.009343,-96.203615 42.009304,-96.204042 42.009309,-96.205032 42.009289,-96.206083 42.009267,-96.207051 42.009064,-96.208486 42.008762,-96.210057 42.008432,-96.212703 42.007533,-96.213317 42.007325,-96.214095 42.007071,-96.215225 42.006701,-96.216496 42.005302,-96.216557 42.005236,-96.217637 42.003862,-96.219867 41.999987,-96.220503 41.999115,-96.22119 41.998152,-96.22161 41.997631,-96.221812 41.997382,-96.222641 41.996451,-96.223369 41.995931,-96.223827 41.995517,-96.223895 41.995457,-96.223964 41.995424,-96.224419 41.995215,-96.224571 41.995145,-96.224812 41.995033,-96.225463 41.994734,-96.225543 41.994715,-96.225802 41.994654,-96.226094 41.994584,-96.226973 41.994378,-96.227172 41.994332,-96.227269 41.994326,-96.227335 41.994321,-96.227436 41.994316,-96.227536 41.994309,-96.227603 41.994305,-96.227706 41.994298,-96.228015 41.994278,-96.228119 41.994272,-96.228197 41.994266,-96.228349 41.994257,-96.228431 41.994266,-96.22851 41.994275,-96.228646 41.99429,-96.229055 41.994335,-96.229192 41.99435,-96.229404 41.994373,-96.229739 41.99441,-96.230037 41.994465,-96.230247 41.994504,-96.230416 41.994535,-96.230671 41.994584,-96.230925 41.994631,-96.231095 41.994663,-96.231215 41.994687,-96.231395 41.994725,-96.231575 41.994761,-96.231696 41.994786,-96.231751 41.994801,-96.23325 41.995178,-96.233878 41.995384,-96.234952 41.995736,-96.236488 41.996429,-96.238183 41.99755,-96.239748 41.998605,-96.239795 41.998641,-96.240713 41.999351,-96.241372 41.999987,-96.241482 42.00014,-96.242035 42.000911,-96.242142 42.001315,-96.242323 42.001996,-96.24238 42.002898,-96.242351 42.004106,-96.242189 42.005382,-96.242162 42.005602,-96.241972 42.006732,-96.24192 42.006963,-96.24144 42.008015,-96.240832 42.009315,-96.239748 42.010889,-96.239303 42.011603,-96.23886 42.012315,-96.238382 42.012602,-96.23818 42.012722,-96.237576 42.013086,-96.237375 42.013207,-96.237365 42.013212,-96.237338 42.013229,-96.237329 42.013235,-96.23711 42.013366,-96.236702 42.013612,-96.236452 42.013755,-96.236232 42.013883,-96.236081 42.013969,-96.23563 42.014231,-96.23548 42.014318,-96.23433 42.014983,-96.232317 42.016148,-96.23087 42.016961,-96.229713 42.017613,-96.22872 42.018171,-96.227867 42.018651,-96.22603 42.020255,-96.225173 42.021005,-96.225007 42.021187,-96.224511 42.021737,-96.224346 42.02192,-96.224198 42.022066,-96.223758 42.022505,-96.223611 42.022652,-96.223101 42.023404,-96.222823 42.023817,-96.221895 42.025844,-96.22173 42.026205,-96.221747 42.026717,-96.22183 42.028262,-96.221901 42.029558,-96.222436 42.030946,-96.223364 42.032554,-96.223822 42.033346,-96.224266 42.033798,-96.224958 42.034505,-96.225656 42.035217,-96.227296 42.036315,-96.227561 42.036493,-96.22816 42.036798,-96.228258 42.036848,-96.228554 42.036999,-96.228653 42.03705,-96.228933 42.037193,-96.22907 42.037288,-96.230143 42.03803,-96.230267 42.038097,-96.230691 42.038328,-96.230973 42.038489,-96.231398 42.038732,-96.231821 42.038974,-96.232104 42.039137,-96.232356 42.039242,-96.232728 42.039398,-96.233112 42.039559,-96.233365 42.039665,-96.233447 42.039688,-96.233571 42.039723,-96.233693 42.039757,-96.233776 42.039781,-96.234027 42.039852,-96.234405 42.039959,-96.234782 42.040065,-96.235034 42.040137,-96.234747 42.040072,-96.23463 42.04004,-96.234021 42.039874,-96.233836 42.039834,-96.233764 42.039823,-96.233504 42.03978,-96.233418 42.039756,-96.233369 42.039743,-96.233151 42.039668,-96.233027 42.039618,-96.232911 42.039571,-96.232852 42.039548,-96.232568 42.03942,-96.232454 42.03937,-96.232256 42.039485,-96.232113 42.039457,-96.23194 42.039404,-96.231699 42.039296,-96.231577 42.039192,-96.231406 42.03914,-96.230775 42.039013,-96.230329 42.038923,-96.230156 42.038871,-96.229993 42.038801,-96.229844 42.038717,-96.229438 42.038429,-96.229005 42.038162,-96.228576 42.037893,-96.228183 42.037596,-96.228001 42.03743,-96.227877 42.037342,-96.227868 42.037328,-96.227871 42.037319,-96.227887 42.037311,-96.22785 42.037291,-96.227808 42.037278,-96.227761 42.037273,-96.227612 42.037273,-96.22742 42.037249,-96.227324 42.037249,-96.227229 42.037264,-96.227185 42.037278,-96.227064 42.037339,-96.227018 42.037349,-96.226977 42.037353,-96.228261 42.038302,-96.229426 42.038866,-96.230253 42.039433,-96.231294 42.039815,-96.233812 42.040553,-96.233954 42.040594,-96.234356 42.040712,-96.234969 42.040778,-96.238392 42.041088,-96.238822 42.041123,-96.24287 42.041455,-96.243104 42.041474,-96.246832 42.041616,-96.248411 42.041207,-96.248764 42.041116,-96.249754 42.040859,-96.250299 42.040718,-96.250662 42.040697,-96.251707 42.040446,-96.252696 42.040039,-96.253019 42.039907,-96.253635 42.039631,-96.254057 42.03952,-96.254489 42.039408,-96.25522 42.038945,-96.25559 42.038712,-96.255711 42.038625,-96.255719 42.038618,-96.256082 42.038305,-96.256203 42.038201,-96.256575 42.03822,-96.257068 42.038247,-96.257694 42.038263,-96.258067 42.038274,-96.258356 42.038338,-96.259223 42.038532,-96.259512 42.038597,-96.259915 42.038687,-96.260358 42.038786,-96.261116 42.038989,-96.261516 42.039097,-96.261663 42.039136,-96.262106 42.039255,-96.262254 42.039295,-96.263448 42.039616,-96.264247 42.039891,-96.265332 42.040266,-96.267195 42.04119,-96.268063 42.041705,-96.269684 42.042893,-96.269785 42.042967,-96.271146 42.044373,-96.271576 42.044904,-96.271969 42.04539,-96.272545 42.046202,-96.272767 42.046571,-96.27312 42.047158,-96.273139 42.04728,-96.273798 42.048283,-96.274393 42.049244,-96.274856 42.050118,-96.274908 42.050215,-96.276213 42.053215,-96.277202 42.056025,-96.277732 42.05753,-96.277907 42.058025,-96.278043 42.058412,-96.278646 42.06117,-96.278838 42.062049,-96.279211 42.064155,-96.279379 42.065495,-96.279536 42.068554,-96.27948 42.070804,-96.279479 42.07088,-96.279201 42.073544,-96.279086 42.07401,-96.278494 42.076404,-96.278484 42.076448,-96.277732 42.079097,-96.276753 42.081696,-96.275982 42.083346,-96.275798 42.083743,-96.274608 42.085386,-96.274559 42.085454,-96.274296 42.085779,-96.273688 42.086534,-96.273341 42.086941,-96.273333 42.086952,-96.273117 42.08739,-96.273045 42.087657,-96.272962 42.087777,-96.272246 42.088825,-96.272168 42.08894,-96.271998 42.089167,-96.271656 42.089621,-96.271492 42.089837,-96.270798 42.090761,-96.270652 42.090998,-96.270355 42.091483,-96.270181 42.091765,-96.26977 42.092437,-96.269677 42.092622,-96.26953 42.09292,-96.269129 42.093728,-96.26838 42.09524,-96.268039 42.096203,-96.267739 42.097055,-96.267439 42.098407,-96.267303 42.099027,-96.266898 42.102178,-96.266893 42.102522,-96.266875 42.103798,-96.26688 42.103907,-96.266972 42.105846,-96.266988 42.106172,-96.267313 42.10889,-96.267486 42.10978,-96.268126 42.111519,-96.26815 42.111585,-96.268618 42.112622,-96.268921 42.113291,-96.269156 42.113695,-96.269459 42.114216,-96.269909 42.114879,-96.270173 42.115267,-96.270376 42.115567,-96.270877 42.116304,-96.270995 42.116461,-96.271213 42.116752,-96.271467 42.117089,-96.272005 42.117695,-96.273001 42.118818,-96.273411 42.11928,-96.27458 42.120334,-96.274584 42.120337,-96.275575 42.121071,-96.276217 42.121547,-96.2764 42.121682,-96.277927 42.122623,-96.278265 42.122795,-96.278978 42.123159,-96.279348 42.123348,-96.2802 42.123783,-96.28047 42.123894,-96.280855 42.124054,-96.281242 42.124214,-96.282218 42.124619,-96.282398 42.124713,-96.28277 42.124909,-96.283002 42.125031,-96.283064 42.125044,-96.283604 42.125164,-96.28401 42.125296,-96.284321 42.125397,-96.285004 42.125618,-96.285952 42.125926,-96.286647 42.126,-96.287106 42.125992,-96.287564 42.125986,-96.287824 42.126007,-96.28791 42.126013,-96.288044 42.126024,-96.288165 42.126054,-96.28825 42.126075,-96.288959 42.126251,-96.289301 42.126292,-96.289532 42.126321,-96.290322 42.126497,-96.290998 42.126485,-96.292148 42.126598,-96.292501 42.126617,-96.293576 42.126675,-96.293861 42.12669,-96.294608 42.12673,-96.294714 42.126759,-96.29499 42.126835,-96.295331 42.126928,-96.295437 42.126988,-96.296035 42.127324,-96.296774 42.127387,-96.297249 42.127429,-96.29807 42.127499,-96.298245 42.127515,-96.299307 42.127552,-96.300154 42.127696,-96.300515 42.127806,-96.300959 42.127941,-96.301299 42.128065,-96.301304 42.128066,-96.30132 42.128072,-96.301326 42.128074,-96.301567 42.128162,-96.302292 42.128426,-96.302534 42.128514,-96.303309 42.128796,-96.303435 42.128842,-96.303989 42.12897,-96.305012 42.129295,-96.305658 42.12956,-96.305731 42.12959,-96.306384 42.129952,-96.308103 42.131058,-96.308681 42.131467,-96.309119 42.131778,-96.309758 42.132101,-96.310355 42.132572,-96.310695 42.132963,-96.311187 42.133308,-96.311612 42.133699,-96.311857 42.134009,-96.312624 42.134655,-96.312909 42.13503,-96.313062 42.135427,-96.314046 42.136449,-96.314488 42.137157,-96.31482 42.137688,-96.315173 42.13851,-96.315585 42.139672,-96.315728 42.140075,-96.315783 42.140282,-96.315925 42.140816,-96.316157 42.141211,-96.316384 42.141947,-96.316465 42.14207,-96.316821 42.142606,-96.316896 42.142784,-96.317121 42.14332,-96.317196 42.143499,-96.317252 42.143633,-96.317282 42.143702,-96.317493 42.143997,-96.317509 42.144019,-96.317597 42.1441,-96.317746 42.144238,-96.317812 42.1443,-96.318058 42.144758,-96.318155 42.144938,-96.318233 42.145084,-96.318333 42.145268,-96.318525 42.145483,-96.318574 42.145537,-96.318648 42.145595,-96.318797 42.145712,-96.318874 42.145772,-96.319144 42.146161,-96.319253 42.146318,-96.319406 42.146539,-96.31956 42.146761,-96.319991 42.147084,-96.320207 42.147246,-96.32074 42.147928,-96.320972 42.148114,-96.321069 42.148192,-96.321596 42.148507,-96.322349 42.149299,-96.322668 42.149519,-96.323148 42.149758,-96.323688 42.150259,-96.323993 42.150542,-96.324672 42.150855,-96.325302 42.151309,-96.325506 42.151456,-96.326415 42.151992,-96.326855 42.152324,-96.32731 42.152447,-96.32806 42.15265,-96.328388 42.152738,-96.328763 42.15284,-96.329333 42.153109,-96.329641 42.153255,-96.329818 42.153339,-96.330381 42.153444,-96.331017 42.153621,-96.331286 42.153697,-96.331773 42.153931,-96.332213 42.154279,-96.333303 42.154567,-96.334899 42.155311,-96.334918 42.155314,-96.33546 42.155419,-96.3363 42.155674,-96.336816 42.155997,-96.337202 42.156452,-96.338025 42.156796,-96.338755 42.157101,-96.339062 42.157331,-96.339305 42.157599,-96.340228 42.158057,-96.340709 42.158505,-96.341571 42.15908,-96.341904 42.159432,-96.342146 42.159938,-96.342666 42.1602,-96.342997 42.160438,-96.343013 42.16045,-96.343614 42.161012,-96.343901 42.161504,-96.344248 42.161754,-96.344431 42.161937,-96.344537 42.162043,-96.344905 42.162376,-96.345177 42.162874,-96.345666 42.163318,-96.346019 42.163789,-96.346203 42.164195,-96.346275 42.164268,-96.346688 42.164686,-96.347084 42.165302,-96.347215 42.165758,-96.347863 42.16664,-96.348015 42.167164,-96.348303 42.167496,-96.348602 42.168027,-96.348712 42.168486,-96.349168 42.169317,-96.349243 42.169815,-96.349688 42.170702,-96.349717 42.171526,-96.350043 42.172267,-96.350063 42.172312,-96.350145 42.17263,-96.350167 42.173205,-96.350101 42.173671,-96.350422 42.175227,-96.35039 42.175469,-96.350312 42.176076,-96.350319 42.176197,-96.350334 42.176442,-96.350343 42.176605,-96.350361 42.176903,-96.350343 42.177095,-96.350329 42.177259,-96.350321 42.177338,-96.350301 42.177575,-96.350294 42.177654,-96.350268 42.177943,-96.350242 42.178029,-96.349922 42.179102,-96.349911 42.179123,-96.349746 42.179466,-96.349676 42.179609,-96.349537 42.179899,-96.349521 42.180055,-96.349506 42.180214,-96.349469 42.180579,-96.349277 42.180908,-96.349206 42.181176,-96.349156 42.181366,-96.348742 42.18221,-96.348626 42.182633,-96.348261 42.183099,-96.348095 42.183398,-96.347936 42.183942,-96.347911 42.18403,-96.34786 42.184496,-96.347598 42.184881,-96.347493 42.185172,-96.347278 42.185774,-96.347251 42.186063,-96.347245 42.186132,-96.347279 42.186371,-96.347306 42.18656,-96.347307 42.186562,-96.347169 42.187119,-96.347123 42.187306,-96.346929 42.188089,-96.346914 42.18815,-96.346944 42.188545,-96.347083 42.189035,-96.346909 42.189996,-96.346927 42.190472,-96.346934 42.190643,-96.347043 42.191066,-96.347149 42.191235,-96.347206 42.191328,-96.347287 42.191457,-96.347278 42.191774,-96.347265 42.192324,-96.347374 42.193075,-96.347543 42.19347,-96.347565 42.193519,-96.347929 42.193899,-96.348097 42.194074,-96.348151 42.194195,-96.34818 42.194258,-96.348347 42.194821,-96.348374 42.19529,-96.348397 42.195665,-96.348406 42.195827,-96.348426 42.195925,-96.348474 42.196146,-96.348764 42.196627,-96.348829 42.196735,-96.348933 42.196824,-96.349057 42.196931,-96.349208 42.19706,-96.349288 42.197344,-96.349334 42.197503,-96.349411 42.197774,-96.349428 42.197834,-96.349567 42.198141,-96.349829 42.198484,-96.349841 42.198493,-96.350062 42.19867,-96.350191 42.198773,-96.350217 42.198809,-96.350583 42.199322,-96.350623 42.199377,-96.350693 42.199501,-96.35076 42.199619,-96.350961 42.199973,-96.351028 42.200092,-96.351109 42.200235,-96.351203 42.200401,-96.351427 42.200606,-96.351549 42.200718,-96.351656 42.200816,-96.351659 42.200818,-96.351935 42.201153,-96.352028 42.201266,-96.352259 42.201489,-96.352953 42.202161,-96.353119 42.202322,-96.353177 42.202393,-96.353394 42.20266,-96.35396 42.203053,-96.354025 42.203118,-96.354505 42.203603,-96.355075 42.20399,-96.355621 42.204494,-96.355919 42.204944,-96.356354 42.20523,-96.356506 42.205363,-96.357041 42.205832,-96.357249 42.206191,-96.357331 42.206333,-96.357639 42.206662,-96.357938 42.206982,-96.358182 42.207334,-96.358301 42.207608,-96.358723 42.208142,-96.358741 42.208165,-96.359029 42.208676,-96.359078 42.208762,-96.359155 42.209029,-96.359159 42.209043,-96.359551 42.209579,-96.359678 42.210016,-96.359714 42.21014,-96.359776 42.210249,-96.359837 42.210357,-96.35987 42.210553,-96.359813 42.21074,-96.35967 42.211213,-96.359345 42.21182,-96.35916 42.212168,-96.358888 42.212682,-96.358146 42.214086,-96.358007 42.21416,-96.357495 42.214436,-96.357469 42.214456,-96.357394 42.214519,-96.357369 42.21454,-96.35735 42.214551,-96.357296 42.214586,-96.357278 42.214598,-96.357264 42.214603,-96.357226 42.214621,-96.357213 42.214627,-96.357213 42.21464,-96.357213 42.21468,-96.357213 42.214694,-96.357171 42.214726,-96.357046 42.214825,-96.357005 42.214859,-96.356963 42.214872,-96.356839 42.214912,-96.356798 42.214926,-96.35675 42.21494,-96.356607 42.214986,-96.35656 42.215002,-96.356591 42.215182,-96.35558 42.215466,-96.35419 42.215714,-96.35326 42.215806,-96.352934 42.215837,-96.352606 42.215868,-96.352324 42.215895,-96.351697 42.215967,-96.351413 42.216014,-96.35062 42.216117,-96.350476 42.216153,-96.350338 42.216187,-96.3502 42.216221,-96.350155 42.216232,-96.350079 42.216251,-96.349143 42.216481,-96.345033 42.217495,-96.337025 42.217463,-96.336998 42.217466,-96.336767 42.217632,-96.336744 42.217674,-96.336598 42.217754,-96.33614 42.218084,-96.335766 42.218354,-96.335528 42.218517,-96.335355 42.21864,-96.335197 42.218737,-96.335043 42.218764,-96.334869 42.218862,-96.334402 42.21927,-96.334361 42.219339,-96.334307 42.219587,-96.334179 42.219872,-96.334052 42.21995,-96.333871 42.220102,-96.333118 42.220781,-96.332648 42.221101,-96.332511 42.221215,-96.332339 42.221318,-96.331149 42.222527,-96.330151 42.223361,-96.330041 42.223485,-96.329456 42.224256,-96.329269 42.224503,-96.329095 42.224733,-96.329003 42.224852,-96.32891 42.224959,-96.328802 42.224926,-96.328521 42.225378,-96.328308 42.225722,-96.328182 42.225795,-96.327368 42.226712,-96.327329 42.22676,-96.327251 42.226843,-96.327051 42.227082,-96.327013 42.227154,-96.326562 42.227862,-96.326256 42.228357,-96.326164 42.228448,-96.325949 42.228661,-96.324924 42.229057,-96.324792 42.229107,-96.32474 42.229128,-96.324546 42.229272,-96.323746 42.22987,-96.323723 42.229887,-96.323531 42.230246,-96.32344 42.230389,-96.323432 42.230436,-96.323368 42.230666,-96.323248 42.230666,-96.322904 42.231296,-96.322902 42.2313,-96.322827 42.231461,-96.32289 42.231655,-96.323054 42.232132,-96.322977 42.232245,-96.322793 42.232522,-96.322761 42.232564,-96.322794 42.23289,-96.322868 42.233637,-96.323032 42.23379,-96.323694 42.234409,-96.323926 42.23495,-96.324572 42.235342,-96.326423 42.236556,-96.326753 42.236949,-96.327016 42.237228,-96.3271 42.237318,-96.327387 42.237628,-96.327893 42.23816,-96.329243 42.23926,-96.329529 42.239461,-96.329545 42.239443,-96.32987 42.239748,-96.330006 42.240225,-96.329763 42.240625,-96.329693 42.241011,-96.329189 42.24158,-96.328958 42.241885,-96.328729 42.242996,-96.32861 42.243456,-96.328364 42.244453,-96.328093 42.245736,-96.328009 42.247082,-96.327982 42.248537,-96.327928 42.248818,-96.327817 42.249405,-96.327706 42.249992,-96.328897 42.254723,-96.331312 42.259408,-96.331991 42.26019,-96.332012 42.260214,-96.335964 42.264777,-96.33829 42.266615,-96.338336 42.266651,-96.339045 42.267212,-96.339801 42.26781,-96.339851 42.26785,-96.341387 42.269087,-96.342912 42.269787,-96.342977 42.269818,-96.342981 42.26982,-96.34905 42.272609,-96.349642 42.27292,-96.349708 42.272955,-96.349723 42.272963,-96.352923 42.274652,-96.355694 42.276115,-96.356023 42.276304,-96.356056 42.276326,-96.35626 42.276448,-96.356366 42.27648,-96.356395 42.276494,-96.356501 42.276562,-96.35655 42.276594,-96.360502 42.27961,-96.360732 42.279786,-96.361015 42.280089,-96.363009 42.282218,-96.365751 42.285814,-96.367453 42.28941,-96.367977 42.290681,-96.368005 42.290779,-96.368026 42.29085,-96.368467 42.292619,-96.36849 42.292822,-96.368514 42.293044,-96.368517 42.2931,-96.368721 42.296599,-96.368723 42.29662,-96.368585 42.301104,-96.368507 42.303622,-96.368675 42.304748,-96.369212 42.308344,-96.369402 42.308983,-96.369969 42.310878,-96.37179 42.314172,-96.373563 42.316189,-96.375307 42.318339,-96.379484 42.322197,-96.384169 42.325874,-96.385717 42.326511,-96.387274 42.327152,-96.390197 42.328354,-96.391863 42.329041,-96.396269 42.330857,-96.40255 42.333955,-96.402957 42.334156,-96.405124 42.335554,-96.40576 42.335964,-96.407979 42.337396,-96.407998 42.337408,-96.411867 42.341335,-96.411956 42.341425,-96.413895 42.343393,-96.415214 42.345936,-96.415251 42.346007,-96.415631 42.34674,-96.415738 42.346947,-96.416194 42.347826,-96.416344 42.348115,-96.417786 42.351449,-96.418168 42.354678,-96.417918 42.3587,-96.417093 42.361443,-96.416858 42.361784,-96.415073 42.364369,-96.413994 42.365932,-96.413887 42.366114,-96.41377 42.366312,-96.411451 42.370249,-96.40959 42.373967,-96.409396 42.374198,-96.40873 42.374993,-96.408436 42.376092,-96.408428 42.37779,-96.408519 42.378551,-96.408532 42.379078,-96.408687 42.379757,-96.409153 42.381491,-96.409539 42.382428,-96.40981 42.383084,-96.410328 42.384258,-96.410663 42.385018,-96.411124 42.38606,-96.411277 42.386305,-96.411752 42.387068,-96.412504 42.388521,-96.413257 42.389927,-96.414074 42.391193,-96.414606 42.392247,-96.41498 42.393442,-96.415348 42.395784,-96.415393 42.396276,-96.415483 42.397294,-96.415554 42.398899,-96.415509 42.400294,-96.415186 42.404203,-96.413609 42.407894,-96.411194 42.411485,-96.411262 42.411563,-96.410307 42.412965,-96.407383 42.415945,-96.403238 42.419181,-96.398808 42.422494,-96.397905 42.4233,-96.396967 42.424138,-96.396814 42.424274,-96.387608 42.432494,-96.384307 42.437294,-96.380705 42.446393,-96.380662 42.446749,-96.38066 42.446768,-96.380656 42.4468,-96.380648 42.446871,-96.380648 42.446873,-96.380629 42.447035,-96.380398 42.448999,-96.380105 42.451494,-96.380574 42.455994,-96.380905 42.459195,-96.381305 42.461695,-96.385407 42.473094,-96.385979 42.474441,-96.386003 42.474496,-96.386013 42.474506,-96.387298 42.475722,-96.392491 42.481243,-96.392648 42.48141,-96.395983 42.484241,-96.396124 42.484361,-96.401962 42.48644,-96.407217 42.487018,-96.407789 42.487168,-96.408361 42.487319,-96.409408 42.487595,-96.411641 42.487792,-96.412225 42.487844,-96.412808 42.487895,-96.41303 42.487916,-96.413217 42.487935,-96.413435 42.487953,-96.413583 42.487966,-96.413722 42.487979,-96.416773 42.488279,-96.417994 42.4884,-96.421678 42.488763,-96.422957 42.488888,-96.423892 42.48898,-96.426592 42.489057,-96.427908 42.489095,-96.428504 42.489126,-96.433808 42.489395,-96.434912 42.489429,-96.440308 42.489595,-96.441385 42.489811,-96.442462 42.490026,-96.443972 42.490328,-96.444908 42.490515,-96.445483 42.49063,-96.445508 42.49063,-96.44555 42.49064,-96.445589 42.490669,-96.445629 42.490691,-96.445819 42.490678,-96.446346 42.490625,-96.447675 42.49047,-96.447918 42.490442,-96.44794 42.490439,-96.447988 42.490439,-96.448128 42.490463,-96.448501 42.490545,-96.448963 42.490661,-96.449282 42.490752,-96.44955 42.490843,-96.449707 42.490896,-96.449728 42.490903,-96.449907 42.490953,-96.449952 42.490962,-96.449999 42.490952,-96.450046 42.490933,-96.450092 42.490925,-96.450182 42.490944,-96.45045 42.491038,-96.450729 42.491108,-96.451141 42.491212,-96.451411 42.491294,-96.452335 42.491617,-96.452595 42.491696,-96.452654 42.491714,-96.452695 42.491726,-96.453288 42.491887,-96.454482 42.492197,-96.454708 42.492263,-96.454969 42.492361,-96.455424 42.492498,-96.455609 42.492542,-96.45575 42.492564,-96.455941 42.492577,-96.456185 42.492576,-96.456291 42.492569,-96.45635 42.492565,-96.456479 42.492556,-96.456735 42.492529,-96.456852 42.492517,-96.456914 42.492511,-96.457204 42.492471,-96.457441 42.492428,-96.45767 42.492371,-96.4578 42.492326,-96.457964 42.492249,-96.458245 42.492101,-96.458371 42.492047,-96.458593 42.491975,-96.459421 42.49176,-96.460149 42.491556,-96.460461 42.491456,-96.460764 42.491341,-96.461481 42.491034,-96.461853 42.490862,-96.462025 42.490796,-96.462251 42.49073,-96.462531 42.490667,-96.462861 42.490604,-96.463243 42.490548,-96.463337 42.490528,-96.463472 42.490474,-96.463512 42.490464,-96.463535 42.490488,-96.463558 42.490524,-96.463629 42.490577,-96.463669 42.490595,-96.463714 42.490604,-96.463765 42.490606,-96.463814 42.490602,-96.463861 42.490593,-96.46418 42.490496,-96.464226 42.490488,-96.464322 42.490485,-96.464466 42.490491,-96.464951 42.49054,-96.465389 42.490572,-96.46558 42.490594,-96.465721 42.490618,-96.465904 42.490665,-96.466352 42.490809,-96.466719 42.490905,-96.467322 42.491046,-96.467698 42.491121,-96.468269 42.491213,-96.468796 42.491283,-96.468984 42.491317,-96.46926 42.49139,-96.469394 42.491437,-96.469474 42.491411,-96.469573 42.491406,-96.469619 42.491396,-96.469663 42.491377,-96.469696 42.491351,-96.469707 42.491318,-96.469708 42.49128,-96.469767 42.491067,-96.469823 42.49093,-96.469854 42.490901,-96.469893 42.49088,-96.469947 42.490886,-96.469969 42.490864,-96.469999 42.490573,-96.469991 42.490501,-96.469939 42.490323,-96.469941 42.490253,-96.469966 42.490184,-96.470047 42.490051,-96.47033 42.489712,-96.470389 42.489655,-96.470458 42.489606,-96.470576 42.489544,-96.470749 42.489473,-96.470926 42.489413,-96.471158 42.489357,-96.471299 42.489331,-96.471348 42.489334,-96.471397 42.489332,-96.471445 42.489323,-96.471505 42.489268,-96.471599 42.489262,-96.471748 42.489265,-96.472622 42.48922,-96.472962 42.48922,-96.473254 42.489234,-96.473496 42.489253,-96.473689 42.48928,-96.473827 42.489312,-96.473914 42.48935,-96.474038 42.489411,-96.474078 42.489423,-96.474114 42.489407,-96.474149 42.489376,-96.474194 42.489369,-96.474294 42.489371,-96.474436 42.489395,-96.475031 42.489533,-96.475413 42.489621,-96.475591 42.489677,-96.475852 42.489775,-96.476104 42.489887,-96.476262 42.489969,-96.476369 42.490043,-96.476461 42.490128,-96.476542 42.490218,-96.476609 42.490355,-96.476632 42.490386,-96.476663 42.490413,-96.476778 42.490482,-96.476812 42.490507,-96.47697 42.49069,-96.47711 42.490881,-96.477186 42.491011,-96.477185 42.491046,-96.477151 42.491155,-96.477121 42.491226,-96.477122 42.491261,-96.477159 42.49135,-96.477164 42.491362,-96.477313 42.49163,-96.477447 42.491901,-96.477614 42.492278,-96.477711 42.492556,-96.477775 42.492805,-96.47782 42.493054,-96.477814 42.493125,-96.477785 42.493233,-96.477785 42.49334,-96.477901 42.494346,-96.477914 42.494597,-96.477908 42.494668,-96.477873 42.494774,-96.477827 42.494877,-96.477782 42.494942,-96.477753 42.494971,-96.477574 42.495096,-96.477545 42.495124,-96.477522 42.495154,-96.477498 42.495213,-96.477489 42.495246,-96.477444 42.49547,-96.477445 42.495503,-96.477445 42.495511,-96.477456 42.495545,-96.47753 42.495659,-96.477541 42.495676,-96.477574 42.495782,-96.477577 42.495801,-96.477584 42.495854,-96.477577 42.495924,-96.477526 42.496084,-96.477498 42.496171,-96.477417 42.496468,-96.477297 42.496912,-96.477207 42.497192,-96.476975 42.497742,-96.476913 42.497879,-96.476744 42.498138,-96.476719 42.498183,-96.47653 42.498542,-96.476467 42.498638,-96.476439 42.498667,-96.476361 42.498711,-96.47632 42.498734,-96.476289 42.498761,-96.476252 42.498825,-96.476168 42.499072,-96.476094 42.499205,-96.475916 42.499467,-96.475634 42.499885,-96.475572 42.499978,-96.475523 42.499992,-96.475301 42.500266,-96.475287 42.500287,-96.474745 42.501095,-96.474712 42.501159,-96.474636 42.501305,-96.474367 42.501825,-96.47424 42.502261,-96.474168 42.502511,-96.47414 42.503179,-96.474138 42.50323,-96.474136 42.503288,-96.473334 42.503536,-96.476048 42.507783,-96.477468 42.509613,-96.478203 42.51021,-96.478784 42.510659,-96.478945 42.510783,-96.479023 42.510843,-96.479088 42.510834,-96.479231 42.510814,-96.479344 42.510799,-96.479517 42.510775,-96.479695 42.510751,-96.479917 42.510721,-96.480145 42.51069,-96.480494 42.510643,-96.480873 42.510591,-96.48263 42.510351,-96.483504 42.51028,-96.484213 42.510306,-96.484379 42.510312,-96.485269 42.510483,-96.4866 42.510739,-96.487669 42.511082,-96.488082 42.511251,-96.488163 42.511284,-96.48875 42.511621,-96.488885 42.511699,-96.488894 42.511704,-96.488971 42.511761,-96.489193 42.511925,-96.489383 42.512065,-96.489466 42.512126,-96.489741 42.512329,-96.490241 42.512697,-96.490306 42.512745,-96.490502 42.512889,-96.490752 42.513073,-96.491431 42.513465,-96.492444 42.514183,-96.493068 42.514821,-96.49357 42.515626,-96.493691 42.516309,-96.493714 42.516629,-96.493722 42.516742,-96.493617 42.517325,-96.493449 42.518259,-96.493188 42.51864,-96.493085 42.518791,-96.492869 42.51906,-96.492687 42.519191,-96.492085 42.519625,-96.492044 42.519649,-96.491557 42.519936,-96.49138 42.520018,-96.491216 42.520094,-96.491119 42.520139,-96.491042 42.520175,-96.490777 42.520239,-96.490646 42.52027,-96.490598 42.520404,-96.487687 42.521436,-96.484691 42.522499,-96.479949 42.524246,-96.479007 42.526394,-96.479041 42.526515,-96.479813 42.529601,-96.477707 42.535596,-96.477409 42.539888,-96.476866 42.544433,-96.476733 42.545383,-96.476834 42.546322,-96.476581 42.546467,-96.476838 42.546626,-96.476952 42.546696,-96.47697 42.549169,-96.476965 42.550348,-96.476948 42.554731,-96.47696 42.555972,-96.477358 42.5561,-96.478541 42.556378,-96.481281 42.556744,-96.486326 42.556961,-96.490128 42.556755,-96.491035 42.556593,-96.493098 42.556354,-96.49554 42.55644,-96.497265 42.556981,-96.498544 42.558116,-96.498835 42.55878,-96.498997 42.560832,-96.487075 42.572245,-96.48661 42.57269,-96.486288 42.573008,-96.486146 42.573149,-96.485742 42.57407,-96.485742 42.57422,-96.485742 42.574673,-96.485751 42.574732,-96.485761 42.574795,-96.485797 42.57502,-96.485863 42.575147,-96.485887 42.575192,-96.485921 42.575257,-96.486198 42.575784,-96.486669 42.576059,-96.487748 42.576365,-96.489903 42.576657,-96.491373 42.577067,-96.491748 42.577243,-96.492455 42.577575,-96.492995 42.577828,-96.493535 42.578082,-96.494107 42.578351,-96.494283 42.57845,-96.495002 42.578853,-96.495654 42.579541,-96.496026 42.580382,-96.495969 42.58175,-96.49597 42.581774,-96.495499 42.582792,-96.494909 42.583536,-96.49369 42.584555,-96.492359 42.585411,-96.491492 42.586103,-96.491336 42.586535,-96.491271 42.586806,-96.491476 42.587251,-96.49152 42.587346,-96.492338 42.587814,-96.493599 42.588141,-96.494289 42.588234,-96.494881 42.588314,-96.495474 42.588394,-96.495712 42.588425,-96.495892 42.588427,-96.497054 42.588444,-96.497851 42.588377,-96.499086 42.588425,-96.500167 42.588638,-96.500514 42.588802,-96.500995 42.589233,-96.501082 42.589311,-96.501127 42.58941,-96.501296 42.589786,-96.501466 42.590161,-96.50148 42.590187,-96.501477 42.590248,-96.501474 42.590327,-96.501477 42.590411,-96.501451 42.590519,-96.501446 42.590541,-96.501438 42.59061,-96.501358 42.59075,-96.501277 42.59089,-96.501146 42.591124,-96.501015 42.591358,-96.50072 42.591884,-96.50043 42.592396,-96.500234 42.59273,-96.500239 42.592841,-96.500216 42.593368,-96.500192 42.593896,-96.500183 42.594104,-96.500284 42.594351,-96.500554 42.595009,-96.500607 42.59514,-96.500824 42.595666,-96.500912 42.595882,-96.501001 42.596097,-96.501194 42.59657,-96.501386 42.597042,-96.503784 42.602876,-96.504624 42.604928,-96.504653 42.604998,-96.504689 42.605054,-96.504833 42.605287,-96.504978 42.60552,-96.506624 42.608166,-96.506842 42.608517,-96.50827 42.610811,-96.508499 42.611178,-96.509323 42.612497,-96.509453 42.612704,-96.510015 42.613211,-96.511842 42.614406,-96.513557 42.615469,-96.515024 42.616183,-96.51658 42.616598,-96.517582 42.616643,-96.517663 42.616647,-96.517712 42.616638,-96.518183 42.616552,-96.51834 42.616522,-96.518654 42.616465,-96.518747 42.616448,-96.518881 42.616398,-96.519169 42.616286,-96.519457 42.616174,-96.519694 42.616083,-96.51983 42.615966,-96.520371 42.615502,-96.521257 42.61457,-96.521318 42.614506,-96.521339 42.614462,-96.52204 42.612995,-96.522361 42.612641,-96.522807 42.612148,-96.523755 42.611252,-96.523876 42.611129,-96.524635 42.610355,-96.525718 42.609276,-96.525739 42.609271,-96.52653 42.60901,-96.527009 42.608946,-96.527026 42.608944,-96.527542 42.608955,-96.527816 42.608961,-96.528121 42.609095,-96.528493 42.609259,-96.528876 42.609425,-96.529169 42.609641,-96.529327 42.609757,-96.529937 42.610471,-96.530177 42.611086,-96.530456 42.611799,-96.530884 42.612795,-96.531023 42.613056,-96.531335 42.613642,-96.53147 42.614472,-96.531483 42.614539,-96.53155 42.614896,-96.531615 42.615318,-96.53147 42.615966,-96.53132 42.616624,-96.531312 42.616647,-96.531281 42.616677,-96.530929 42.617012,-96.53073 42.617266,-96.53059 42.617444,-96.529914 42.617859,-96.529034 42.61824,-96.528726 42.618308,-96.528131 42.61844,-96.527455 42.61834,-96.526844 42.618125,-96.526259 42.617925,-96.525176 42.617776,-96.524001 42.617624,-96.523754 42.617592,-96.522747 42.617578,-96.522581 42.617576,-96.521453 42.617676,-96.521073 42.617854,-96.520709 42.618024,-96.519039 42.618986,-96.518273 42.619673,-96.518114 42.619816,-96.518089 42.619855,-96.517324 42.621028,-96.516828 42.621891,-96.516426 42.622789,-96.516381 42.622941,-96.516245 42.623398,-96.516201 42.623551,-96.51607 42.623837,-96.515681 42.624696,-96.515551 42.624983,-96.5155 42.625261,-96.5155 42.625265,-96.515473 42.625515,-96.515469 42.625906,-96.515463 42.626013,-96.515448 42.626108,-96.515405 42.626388,-96.515368 42.626625,-96.515353 42.626723,-96.515226 42.627333,-96.515178 42.627569,-96.51514 42.628228,-96.515158 42.62837,-96.515335 42.629,-96.515359 42.629069,-96.515607 42.629611,-96.515661 42.629711,-96.515749 42.629838,-96.516084 42.630275,-96.516513 42.630709,-96.51684 42.630975,-96.51788 42.631727,-96.518305 42.632021,-96.518582 42.632273,-96.519159 42.632658,-96.519428 42.632858,-96.51957 42.632964,-96.51967 42.633043,-96.519824 42.633181,-96.520414 42.63375,-96.520796 42.634076,-96.520854 42.634134,-96.521158 42.634413,-96.521532 42.634832,-96.521601 42.634883,-96.521732 42.63493,-96.521779 42.634942,-96.52211 42.635003,-96.522403 42.63502,-96.522542 42.635001,-96.522681 42.634966,-96.523665 42.634649,-96.523801 42.634608,-96.523986 42.634562,-96.524271 42.634511,-96.524466 42.634498,-96.524612 42.634508,-96.524754 42.634534,-96.524799 42.634549,-96.525079 42.634692,-96.525143 42.634746,-96.525167 42.634777,-96.525377 42.635139,-96.525414 42.635242,-96.525422 42.635277,-96.525453 42.635555,-96.525458 42.635598,-96.525448 42.635919,-96.525412 42.636204,-96.525372 42.63638,-96.525248 42.636872,-96.525205 42.63737,-96.52523 42.637869,-96.525337 42.638289,-96.525573 42.639164,-96.525692 42.639474,-96.525802 42.63971,-96.525886 42.639878,-96.525945 42.640014,-96.525977 42.640118,-96.525993 42.640189,-96.526043 42.640363,-96.526188 42.640704,-96.526227 42.640807,-96.526414 42.641213,-96.526456 42.641277,-96.52663 42.64145,-96.527011 42.641675,-96.52708 42.641703,-96.527533 42.64184,-96.527671 42.641875,-96.528915 42.64208,-96.529492 42.64216,-96.529637 42.642176,-96.530267 42.642232,-96.531067 42.642398,-96.531665 42.642557,-96.53211 42.642706,-96.532453 42.642844,-96.532787 42.642994,-96.533272 42.643236,-96.533822 42.643535,-96.534007 42.643641,-96.535361 42.644419,-96.536301 42.644993,-96.536433 42.645098,-96.537075 42.645588,-96.537173 42.645668,-96.537391 42.645861,-96.537472 42.645951,-96.537708 42.646304,-96.537851 42.64657,-96.537917 42.646742,-96.538002 42.646909,-96.538073 42.647154,-96.538164 42.647686,-96.53817 42.6479,-96.538145 42.6484,-96.538116 42.648613,-96.538069 42.648788,-96.538066 42.648824,-96.538063 42.648837,-96.538035 42.648983,-96.538026 42.649032,-96.537989 42.649224,-96.537878 42.649803,-96.537842 42.649996,-96.537803 42.650203,-96.53757 42.651071,-96.537456 42.651501,-96.537413 42.651713,-96.53727 42.652781,-96.537299 42.653138,-96.537344 42.65335,-96.537361 42.653743,-96.537368 42.653815,-96.537495 42.65435,-96.537502 42.654378,-96.537579 42.654584,-96.537767 42.655425,-96.537818 42.6556,-96.537929 42.655874,-96.537976 42.655976,-96.538295 42.656458,-96.538493 42.656694,-96.538525 42.656732,-96.538595 42.656826,-96.538898 42.657193,-96.539248 42.657538,-96.539802 42.658211,-96.540167 42.65859,-96.54036 42.658797,-96.540757 42.659288,-96.540873 42.659485,-96.541012 42.659674,-96.541219 42.659916,-96.541363 42.660059,-96.541393 42.660089,-96.541455 42.660144,-96.541524 42.660194,-96.541754 42.660378,-96.541824 42.660428,-96.542237 42.660827,-96.542365 42.660934,-96.542488 42.661011,-96.542623 42.661097,-96.542858 42.661227,-96.542986 42.661279,-96.543389 42.66141,-96.543482 42.661433,-96.543913 42.661502,-96.544058 42.66152,-96.544349 42.661547,-96.545229 42.661591,-96.545816 42.661579,-96.546302 42.661534,-96.546588 42.661485,-96.547008 42.661387,-96.54712 42.661353,-96.54746 42.661251,-96.54779 42.661182,-96.547834 42.661166,-96.547999 42.661089,-96.548796 42.660816,-96.549386 42.660646,-96.549608 42.660571,-96.550022 42.66038,-96.550325 42.660264,-96.551901 42.659545,-96.5523 42.65934,-96.552591 42.659148,-96.552904 42.658977,-96.553581 42.658689,-96.554316 42.658407,-96.554446 42.658365,-96.554765 42.658274,-96.555483 42.658045,-96.55608 42.657885,-96.556542 42.657768,-96.556778 42.657725,-96.557547 42.657613,-96.557743 42.657605,-96.55833 42.657597,-96.558575 42.6576,-96.558672 42.657608,-96.558909 42.657653,-96.558999 42.657679,-96.559266 42.657769,-96.559388 42.657829,-96.559605 42.657974,-96.559751 42.658117,-96.559868 42.658352,-96.559918 42.658563,-96.559944 42.658848,-96.55994 42.658875,-96.559919 42.659025,-96.559677 42.659797,-96.55946 42.660454,-96.55943 42.660558,-96.559408 42.660664,-96.559401 42.660842,-96.559431 42.661018,-96.55955 42.661401,-96.559609 42.661537,-96.559745 42.661879,-96.559776 42.661946,-96.559797 42.662016,-96.559857 42.66255,-96.559823 42.662834,-96.559808 42.662868,-96.559674 42.663098,-96.559553 42.663254,-96.559454 42.663357,-96.559051 42.663541,-96.558869 42.663595,-96.558822 42.663605,-96.558145 42.66369,-96.557755 42.663715,-96.557021 42.663741,-96.556581 42.663724,-96.556192 42.66375,-96.556096 42.663764,-96.55605 42.663778,-96.555965 42.663812,-96.555808 42.663897,-96.555776 42.663924,-96.555761 42.663958,-96.555747 42.664029,-96.555749 42.664101,-96.55577 42.664207,-96.555867 42.664409,-96.556012 42.664596,-96.556073 42.664651,-96.556305 42.664836,-96.557292 42.665582,-96.557752 42.666045,-96.557907 42.666183,-96.558045 42.666321,-96.558339 42.666616,-96.558486 42.666802,-96.55853 42.666866,-96.558609 42.667219,-96.558672 42.667391,-96.558757 42.66752,-96.558812 42.667579,-96.558959 42.667843,-96.55931 42.668311,-96.559823 42.668834,-96.560019 42.668992,-96.56013 42.669061,-96.560164 42.669086,-96.560278 42.669152,-96.560795 42.669423,-96.561131 42.669567,-96.561306 42.669632,-96.561395 42.669661,-96.561943 42.669817,-96.562334 42.669966,-96.562651 42.670133,-96.562863 42.670276,-96.56326 42.670485,-96.563583 42.670703,-96.563687 42.670778,-96.564297 42.671244,-96.564389 42.671328,-96.564613 42.671604,-96.564741 42.671797,-96.564766 42.671828,-96.56509 42.672142,-96.565262 42.672358,-96.565301 42.672424,-96.565485 42.672676,-96.565504 42.672709,-96.565598 42.672987,-96.56563 42.673054,-96.565867 42.673447,-96.565905 42.673624,-96.565968 42.673796,-96.566003 42.673937,-96.566092 42.674791,-96.566184 42.675287,-96.566332 42.675665,-96.566463 42.675934,-96.566483 42.675967,-96.566532 42.676029,-96.566964 42.676417,-96.567001 42.67644,-96.567044 42.676458,-96.567091 42.676467,-96.567237 42.676477,-96.567384 42.676479,-96.567529 42.676464,-96.567575 42.676453,-96.56801 42.676289,-96.568049 42.676267,-96.568285 42.676086,-96.568508 42.675947,-96.56879 42.675804,-96.568904 42.675736,-96.56907 42.675605,-96.569707 42.67497,-96.569776 42.674875,-96.569895 42.674679,-96.570017 42.674334,-96.570124 42.673803,-96.570293 42.672632,-96.570424 42.672105,-96.570493 42.67201,-96.570575 42.671804,-96.570639 42.671707,-96.570939 42.67134,-96.571055 42.671218,-96.571079 42.671194,-96.571112 42.671167,-96.57122 42.671094,-96.571567 42.670896,-96.571653 42.670863,-96.572067 42.670753,-96.572304 42.670707,-96.572401 42.670697,-96.57279 42.670673,-96.573085 42.670667,-96.573134 42.670669,-96.57357 42.670721,-96.574587 42.670979,-96.57467 42.671018,-96.574716 42.671031,-96.574953 42.671077,-96.576817 42.671375,-96.577395 42.671443,-96.577852 42.671569,-96.578024 42.671636,-96.578365 42.671814,-96.578399 42.67184,-96.578428 42.671869,-96.578474 42.671932,-96.578513 42.671997,-96.578576 42.672207,-96.578574 42.672242,-96.578551 42.672312,-96.578517 42.672379,-96.578471 42.672442,-96.578441 42.67247,-96.578274 42.6726,-96.578236 42.672623,-96.577978 42.672724,-96.577344 42.672916,-96.57647 42.673003,-96.575655 42.673121,-96.575467 42.673163,-96.575336 42.673211,-96.574589 42.67355,-96.574429 42.673631,-96.574354 42.673677,-96.574257 42.673757,-96.573926 42.674017,-96.573863 42.674072,-96.573722 42.674218,-96.573338 42.674674,-96.573173 42.674893,-96.573061 42.675091,-96.572881 42.675223,-96.572745 42.675372,-96.572614 42.675564,-96.572142 42.676463,-96.572111 42.676531,-96.572053 42.676704,-96.571962 42.677055,-96.571944 42.677161,-96.571927 42.677482,-96.571962 42.67791,-96.572008 42.678157,-96.572029 42.678226,-96.57212 42.678429,-96.572141 42.678461,-96.572192 42.678522,-96.572301 42.67872,-96.572416 42.678836,-96.572448 42.678863,-96.572488 42.678884,-96.572623 42.678926,-96.572765 42.678951,-96.572861 42.678963,-96.57291 42.678965,-96.573008 42.678961,-96.57325 42.678931,-96.57353 42.678867,-96.573845 42.67877,-96.573975 42.67872,-96.574679 42.678332,-96.574981 42.67815,-96.575341 42.677964,-96.575428 42.677931,-96.57598 42.677783,-96.576263 42.677726,-96.577041 42.677651,-96.577676 42.67762,-96.577921 42.677632,-96.578016 42.67765,-96.578154 42.677685,-96.578289 42.677727,-96.578455 42.677803,-96.578492 42.677827,-96.578648 42.677964,-96.578722 42.678057,-96.578757 42.678123,-96.578768 42.678158,-96.578803 42.67837,-96.578781 42.678467,-96.578727 42.678567,-96.578704 42.678598,-96.578508 42.678757,-96.578439 42.678807,-96.578211 42.678943,-96.577848 42.679126,-96.577018 42.679432,-96.576932 42.679467,-96.576338 42.679782,-96.576119 42.679862,-96.575992 42.679914,-96.575953 42.679937,-96.575669 42.680133,-96.57523 42.68047,-96.575091 42.68061,-96.574972 42.68073,-96.574907 42.680826,-96.57467 42.681295,-96.574597 42.681502,-96.57458 42.681609,-96.574568 42.681966,-96.574573 42.682002,-96.574594 42.682071,-96.574702 42.682309,-96.574877 42.682524,-96.574906 42.682552,-96.57518 42.682757,-96.575295 42.682823,-96.575693 42.682962,-96.575784 42.682989,-96.576108 42.68307,-96.576252 42.683092,-96.576496 42.68311,-96.576692 42.683109,-96.577913 42.683035,-96.578254 42.683014,-96.578495 42.682982,-96.578691 42.682969,-96.578984 42.682981,-96.579131 42.682974,-96.580049 42.682864,-96.580292 42.682848,-96.580683 42.682872,-96.581365 42.682905,-96.582341 42.68294,-96.582684 42.68294,-96.583024 42.68291,-96.583903 42.682928,-96.584244 42.682959,-96.584635 42.682972,-96.584732 42.682982,-96.584828 42.682997,-96.585153 42.683065,-96.5852 42.683075,-96.585296 42.683088,-96.585443 42.683089,-96.585833 42.683059,-96.585881 42.683065,-96.585928 42.683076,-96.586186 42.683177,-96.586307 42.683289,-96.586332 42.68332,-96.586371 42.683385,-96.586382 42.68342,-96.58643 42.683668,-96.586431 42.683775,-96.586425 42.68381,-96.586307 42.68412,-96.58624 42.684254,-96.586015 42.684649,-96.585895 42.684844,-96.585752 42.685147,-96.585462 42.685829,-96.585451 42.685864,-96.585415 42.686076,-96.585402 42.68629,-96.585428 42.68661,-96.585493 42.687001,-96.58553 42.687141,-96.58566 42.687411,-96.58573 42.687544,-96.58587 42.687772,-96.586132 42.688115,-96.586295 42.688413,-96.586303 42.688448,-96.586252 42.689375,-96.586259 42.689589,-96.586268 42.689624,-96.586342 42.689794,-96.586383 42.689859,-96.586485 42.689981,-96.586516 42.690008,-96.586757 42.690131,-96.586801 42.690147,-96.586848 42.690158,-96.587188 42.690188,-96.587873 42.690174,-96.58797 42.690175,-96.588165 42.690168,-96.588262 42.690177,-96.588592 42.69024,-96.588636 42.690254,-96.588843 42.690348,-96.589185 42.690549,-96.589274 42.690635,-96.589428 42.690816,-96.589491 42.69087,-96.589595 42.690946,-96.589635 42.690966,-96.589721 42.691,-96.589842 42.691041,-96.590133 42.691075,-96.590427 42.691076,-96.590864 42.691039,-96.59105 42.690996,-96.591352 42.690879,-96.591592 42.690759,-96.591768 42.690634,-96.591852 42.690546,-96.591888 42.69048,-96.592014 42.690209,-96.59203 42.690139,-96.592032 42.690032,-96.592026 42.689996,-96.591922 42.689647,-96.59158 42.689055,-96.591547 42.688988,-96.591505 42.688885,-96.591418 42.688498,-96.591422 42.688356,-96.59143 42.688285,-96.591487 42.688111,-96.591656 42.687737,-96.591676 42.687705,-96.591969 42.687375,-96.592001 42.687348,-96.592295 42.68716,-96.592932 42.686893,-96.593062 42.686843,-96.593488 42.68676,-96.593827 42.686722,-96.594022 42.68671,-96.594609 42.686705,-96.595733 42.686736,-96.59666 42.686809,-96.596709 42.68681,-96.597289 42.686745,-96.597826 42.686742,-96.598884 42.686667,-96.598933 42.686672,-96.599315 42.686736,-96.599449 42.686781,-96.599639 42.686893,-96.599927 42.687137,-96.599954 42.687167,-96.599975 42.687199,-96.600042 42.687333,-96.600066 42.687365,-96.600133 42.687499,-96.60015 42.687605,-96.600177 42.687926,-96.600177 42.687997,-96.600172 42.688033,-96.60015 42.688102,-96.600098 42.688203,-96.600003 42.688367,-96.599773 42.688641,-96.599655 42.688754,-96.599621 42.688781,-96.599381 42.688904,-96.599161 42.688982,-96.599022 42.689015,-96.598974 42.689023,-96.598877 42.689031,-96.598637 42.688996,-96.598227 42.688877,-96.598184 42.68886,-96.597883 42.688678,-96.597318 42.688394,-96.596851 42.688287,-96.596754 42.688276,-96.596559 42.688288,-96.596324 42.688337,-96.59628 42.688354,-96.596074 42.688449,-96.595927 42.688544,-96.595614 42.688712,-96.595437 42.688773,-96.595251 42.688818,-96.595203 42.688824,-96.595154 42.688825,-96.594912 42.688844,-96.594828 42.688881,-96.594782 42.688895,-96.594494 42.688936,-96.594208 42.688986,-96.594119 42.689016,-96.594039 42.689057,-96.593936 42.689133,-96.593829 42.689253,-96.593735 42.689378,-96.593715 42.689411,-96.593614 42.689723,-96.593613 42.689794,-96.593663 42.690005,-96.593769 42.690205,-96.593907 42.690394,-96.594056 42.690536,-96.594157 42.690614,-96.594378 42.690755,-96.594499 42.690816,-96.594542 42.690834,-96.594851 42.690941,-96.594943 42.690966,-96.595904 42.691092,-96.596087 42.691145,-96.596125 42.691167,-96.5963 42.691291,-96.596387 42.691377,-96.596453 42.691472,-96.596466 42.691507,-96.596502 42.691647,-96.596539 42.691932,-96.596528 42.692003,-96.596429 42.692242,-96.596408 42.692275,-96.596051 42.692701,-96.596021 42.692729,-96.595922 42.6928,-96.595676 42.692917,-96.595503 42.692984,-96.594969 42.693237,-96.594418 42.693467,-96.594336 42.693505,-96.594292 42.69352,-96.594167 42.693576,-96.593906 42.693788,-96.59388 42.693818,-96.593822 42.693916,-96.593804 42.693986,-96.593829 42.694164,-96.593928 42.694327,-96.593952 42.694358,-96.593986 42.694384,-96.594252 42.694541,-96.594336 42.694579,-96.59452 42.694627,-96.594662 42.694656,-96.595338 42.694738,-96.59553 42.694767,-96.59572 42.694802,-96.595954 42.694854,-96.596635 42.695057,-96.597081 42.695278,-96.597307 42.695415,-96.597624 42.695637,-96.597746 42.695749,-96.597997 42.695968,-96.598234 42.696149,-96.598612 42.696565,-96.599028 42.696963,-96.599142 42.697121,-96.599154 42.697156,-96.59917 42.697439,-96.599115 42.69783,-96.599116 42.697866,-96.599138 42.697971,-96.599162 42.698041,-96.599168 42.698076,-96.599219 42.698214,-96.599263 42.698316,-96.599307 42.698379,-96.599505 42.698705,-96.59955 42.698769,-96.599603 42.698829,-96.599637 42.698854,-96.599858 42.698996,-96.599941 42.699032,-96.599987 42.699045,-96.600604 42.699158,-96.600653 42.699162,-96.600946 42.699159,-96.600994 42.699154,-96.601041 42.699144,-96.60113 42.699114,-96.601342 42.699024,-96.601381 42.699003,-96.601447 42.69895,-96.601494 42.698849,-96.601532 42.698708,-96.601549 42.698422,-96.601534 42.697851,-96.601537 42.697744,-96.601577 42.697641,-96.601651 42.697527,-96.601809 42.69739,-96.60189 42.697301,-96.601993 42.697224,-96.602241 42.697053,-96.602281 42.697033,-96.602555 42.696954,-96.6027 42.696938,-96.602847 42.696932,-96.603285 42.69697,-96.603857 42.697062,-96.603906 42.697065,-96.604003 42.697055,-96.604027 42.697048,-96.604186 42.697003,-96.604312 42.696948,-96.604346 42.696923,-96.604375 42.696894,-96.604523 42.696708,-96.604574 42.696533,-96.604572 42.696426,-96.60456 42.696355,-96.60456 42.69632,-96.604418 42.695903,-96.60442 42.695618,-96.604436 42.695584,-96.604526 42.695457,-96.60468 42.695275,-96.604769 42.695189,-96.604924 42.695051,-96.605265 42.694848,-96.605643 42.694683,-96.605875 42.694624,-96.605923 42.694615,-96.606116 42.694591,-96.60636 42.694572,-96.606801 42.694565,-96.607486 42.694607,-96.607873 42.694653,-96.608018 42.694667,-96.608654 42.694705,-96.608801 42.694706,-96.609143 42.69468,-96.609721 42.694598,-96.610159 42.69456,-96.610208 42.69456,-96.610687 42.694634,-96.610782 42.694652,-96.610922 42.694685,-96.611275 42.694809,-96.61158 42.694988,-96.611622 42.695006,-96.611698 42.69505,-96.611769 42.695099,-96.611795 42.69513,-96.612056 42.695512,-96.612086 42.69558,-96.612109 42.695649,-96.612181 42.695931,-96.612206 42.696108,-96.612199 42.696215,-96.612121 42.696495,-96.612045 42.696623,-96.61179 42.696885,-96.611167 42.697389,-96.611043 42.6975,-96.610583 42.69787,-96.610369 42.698017,-96.61018 42.698131,-96.609624 42.698425,-96.60958 42.698442,-96.609339 42.698473,-96.609192 42.698472,-96.609048 42.698449,-96.608922 42.698394,-96.608429 42.698101,-96.60835 42.69806,-96.608023 42.697906,-96.607896 42.697852,-96.60767 42.697785,-96.607622 42.69778,-96.60748 42.697755,-96.607432 42.69775,-96.606893 42.697733,-96.606358 42.697781,-96.605692 42.697897,-96.605602 42.697924,-96.605515 42.697956,-96.605381 42.697999,-96.605335 42.69801,-96.605247 42.698041,-96.605039 42.698136,-96.604963 42.698181,-96.604726 42.698361,-96.604603 42.698473,-96.604528 42.698565,-96.604419 42.698765,-96.604348 42.699009,-96.604331 42.699152,-96.604328 42.699295,-96.604349 42.699509,-96.604466 42.700038,-96.604683 42.700661,-96.6048 42.701189,-96.604825 42.701258,-96.604912 42.701463,-96.604946 42.70153,-96.605088 42.701694,-96.605147 42.701748,-96.605352 42.701901,-96.605469 42.701966,-96.605596 42.70202,-96.606423 42.702237,-96.60685 42.702317,-96.60787 42.702401,-96.608065 42.702414,-96.608505 42.702432,-96.60913 42.702424,-96.609436 42.702421,-96.609485 42.702416,-96.609577 42.702393,-96.609666 42.702364,-96.609713 42.702353,-96.609801 42.702324,-96.609993 42.702212,-96.610284 42.702082,-96.610426 42.701984,-96.610456 42.701956,-96.610619 42.701825,-96.610648 42.701796,-96.610791 42.70161,-96.610867 42.701519,-96.611134 42.701219,-96.611698 42.700152,-96.611767 42.700058,-96.611851 42.699969,-96.612392 42.699508,-96.612638 42.699333,-96.612956 42.699168,-96.613748 42.698978,-96.614013 42.698887,-96.614504 42.69865,-96.614687 42.698532,-96.614728 42.698513,-96.615002 42.698435,-96.61505 42.698429,-96.615148 42.698433,-96.615241 42.698457,-96.615409 42.69853,-96.615444 42.698554,-96.615508 42.698608,-96.615627 42.698765,-96.615644 42.698798,-96.615639 42.698869,-96.615627 42.698904,-96.615613 42.698975,-96.615617 42.69901,-96.61565 42.699151,-96.615649 42.699187,-96.615516 42.699713,-96.615516 42.699855,-96.615562 42.699994,-96.615722 42.700254,-96.615751 42.700283,-96.615825 42.70033,-96.615945 42.700391,-96.616053 42.700428,-96.616078 42.700437,-96.616169 42.700463,-96.616504 42.700515,-96.616601 42.700523,-96.616846 42.70051,-96.61694 42.700492,-96.616986 42.700479,-96.617294 42.700369,-96.617375 42.700329,-96.617492 42.700264,-96.617676 42.700147,-96.618278 42.69982,-96.618424 42.699742,-96.61847 42.699729,-96.618807 42.699684,-96.618905 42.699682,-96.618995 42.699711,-96.619122 42.699764,-96.619162 42.699786,-96.619194 42.699812,-96.619273 42.699903,-96.619415 42.700091,-96.619545 42.700322,-96.619604 42.700458,-96.619684 42.700663,-96.619723 42.700803,-96.619749 42.700871,-96.619776 42.700976,-96.619767 42.701011,-96.619764 42.701081,-96.619732 42.701329,-96.619726 42.701436,-96.619748 42.701721,-96.619761 42.701827,-96.619815 42.701964,-96.619964 42.702149,-96.619996 42.702176,-96.62045 42.702448,-96.620515 42.702501,-96.620822 42.702731,-96.620902 42.70282,-96.620941 42.702886,-96.620961 42.702943,-96.620991 42.703024,-96.62111 42.703589,-96.621243 42.703934,-96.62129 42.704109,-96.621408 42.704456,-96.621552 42.704768,-96.621642 42.704963,-96.621933 42.705275,-96.622 42.705347,-96.622217 42.705668,-96.622563 42.705971,-96.622666 42.706103,-96.622684 42.706126,-96.622967 42.706541,-96.623038 42.706635,-96.623358 42.70695,-96.623413 42.707009,-96.623881 42.707555,-96.623916 42.707579,-96.623957 42.7076,-96.624216 42.7077,-96.624263 42.707711,-96.624361 42.707717,-96.624497 42.70768,-96.62475 42.707571,-96.624981 42.707439,-96.6251 42.707326,-96.62517 42.707278,-96.625208 42.707253,-96.625287 42.707163,-96.625305 42.707129,-96.625392 42.706814,-96.625395 42.706493,-96.62535 42.706234,-96.625346 42.706209,-96.625275 42.705892,-96.625189 42.705686,-96.625102 42.70552,-96.624955 42.705105,-96.624916 42.704893,-96.624916 42.704822,-96.624991 42.704579,-96.62505 42.704481,-96.625077 42.704451,-96.625208 42.704345,-96.625246 42.704322,-96.625486 42.704198,-96.625529 42.704181,-96.62598 42.704042,-96.626108 42.704024,-96.626365 42.70399,-96.626414 42.703988,-96.626512 42.703995,-96.62675 42.704036,-96.626843 42.704058,-96.626978 42.7041,-96.62702 42.704119,-96.627214 42.704228,-96.627325 42.704297,-96.627406 42.704337,-96.62784 42.704677,-96.62791 42.704727,-96.628095 42.704843,-96.628384 42.704978,-96.628428 42.704995,-96.628521 42.705015,-96.629325 42.705017,-96.629372 42.705025,-96.629511 42.70506,-96.62992 42.705255,-96.630154 42.705438,-96.630207 42.705498,-96.630353 42.705684,-96.630413 42.705857,-96.630443 42.706569,-96.630467 42.706747,-96.630441 42.706888,-96.630299 42.707268,-96.630092 42.707592,-96.629832 42.707936,-96.629749 42.708065,-96.62944 42.708816,-96.629392 42.708918,-96.629333 42.709015,-96.629307 42.709046,-96.629276 42.709074,-96.62917 42.709148,-96.628962 42.709242,-96.62882 42.70927,-96.628706 42.709273,-96.628282 42.709289,-96.62814 42.709315,-96.627858 42.709374,-96.627631 42.709441,-96.627498 42.709486,-96.626888 42.7099,-96.626855 42.709926,-96.626779 42.710018,-96.626539 42.710329,-96.626345 42.710657,-96.626265 42.710747,-96.626043 42.710968,-96.625977 42.711063,-96.625951 42.711132,-96.625936 42.71118,-96.625833 42.711515,-96.625636 42.711881,-96.625448 42.712171,-96.625422 42.71224,-96.625238 42.712645,-96.625222 42.712671,-96.625144 42.712811,-96.625027 42.712924,-96.624904 42.713119,-96.62468 42.713475,-96.624559 42.713707,-96.62442 42.713977,-96.624406 42.714011,-96.624284 42.714468,-96.62426 42.714717,-96.62427 42.714895,-96.624315 42.71518,-96.624373 42.715316,-96.62453 42.715617,-96.624545 42.715643,-96.624568 42.715682,-96.624745 42.715896,-96.624799 42.715955,-96.624834 42.71598,-96.625042 42.716095,-96.625139 42.716148,-96.625186 42.716175,-96.625323 42.716277,-96.625418 42.716329,-96.62544 42.716341,-96.62551 42.716392,-96.625748 42.716771,-96.625773 42.716802,-96.625948 42.716926,-96.626146 42.717032,-96.626278 42.71708,-96.626629 42.717166,-96.626743 42.717194,-96.627141 42.717333,-96.627368 42.7174,-96.627636 42.717488,-96.627719 42.717527,-96.627936 42.717671,-96.627969 42.717697,-96.627992 42.717729,-96.628006 42.717763,-96.628011 42.717798,-96.628001 42.717833,-96.627923 42.718003,-96.627882 42.718068,-96.627826 42.718126,-96.627762 42.71818,-96.627488 42.718382,-96.627258 42.718516,-96.627019 42.718641,-96.626798 42.718718,-96.626593 42.718816,-96.626453 42.718916,-96.626389 42.71897,-96.626092 42.719251,-96.62589 42.719409,-96.625625 42.719617,-96.625498 42.719726,-96.625352 42.719821,-96.624967 42.720276,-96.624882 42.720405,-96.624545 42.720944,-96.624465 42.721113,-96.624402 42.721503,-96.624395 42.721574,-96.624413 42.721967,-96.624434 42.722073,-96.624455 42.722143,-96.624596 42.722485,-96.624745 42.72267,-96.625003 42.722929,-96.625247 42.723104,-96.625332 42.723191,-96.625393 42.723327,-96.625452 42.723609,-96.625454 42.723645,-96.625437 42.723823,-96.625428 42.723858,-96.625309 42.724168,-96.625227 42.724297,-96.624792 42.724729,-96.624755 42.724753,-96.624658 42.724832,-96.624535 42.724944,-96.624466 42.725039,-96.624447 42.725072,-96.624434 42.725106,-96.624414 42.725212,-96.624446 42.725317,-96.624464 42.72535,-96.624691 42.725626,-96.624723 42.725652,-96.624764 42.725673,-96.624937 42.72574,-96.625218 42.725803,-96.625555 42.725861,-96.6256 42.725869,-96.626039 42.725898,-96.627019 42.725934,-96.627503 42.725878,-96.628058 42.725737,-96.628626 42.725629,-96.628918 42.725599,-96.629348 42.725528,-96.629939 42.725351,-96.630466 42.725162,-96.630628 42.725081,-96.630672 42.725067,-96.631406 42.725033,-96.631598 42.725004,-96.631883 42.724951,-96.632164 42.724888,-96.632209 42.724874,-96.632425 42.72479,-96.632931 42.724507,-96.633236 42.724327,-96.63352 42.724188,-96.633703 42.724137,-96.633752 42.724135,-96.633801 42.724138,-96.633896 42.724156,-96.634118 42.724257,-96.634189 42.724306,-96.634278 42.724391,-96.634304 42.724421,-96.634509 42.724706,-96.634642 42.725011,-96.634665 42.725042,-96.6347 42.725108,-96.634812 42.725598,-96.634814 42.725741,-96.634806 42.725847,-96.634769 42.725988,-96.634743 42.726057,-96.634704 42.726122,-96.634363 42.726379,-96.634321 42.726397,-96.633781 42.726567,-96.633378 42.726699,-96.633287 42.726725,-96.633239 42.726733,-96.6331 42.726766,-96.632925 42.726831,-96.632807 42.726895,-96.632771 42.726919,-96.632572 42.727077,-96.632517 42.727136,-96.632476 42.727239,-96.632467 42.727274,-96.632462 42.727381,-96.632465 42.727453,-96.632522 42.728022,-96.632507 42.728129,-96.632376 42.728436,-96.632357 42.728469,-96.632243 42.728586,-96.632178 42.728639,-96.632056 42.728751,-96.632021 42.728776,-96.631897 42.728833,-96.631685 42.728922,-96.631186 42.729073,-96.630978 42.729167,-96.630677 42.72935,-96.630647 42.729378,-96.630623 42.72941,-96.630501 42.729605,-96.630484 42.729639,-96.630434 42.729777,-96.630426 42.729812,-96.630432 42.729919,-96.630522 42.730234,-96.630908 42.730772,-96.630985 42.730863,-96.631321 42.73117,-96.631801 42.731527,-96.632186 42.731851,-96.632289 42.731927,-96.632405 42.732042,-96.632467 42.732097,-96.632493 42.732127,-96.632587 42.732209,-96.632628 42.732228,-96.63302 42.732376,-96.633225 42.73243,-96.633343 42.732462,-96.633392 42.732466,-96.633578 42.732462,-96.634166 42.732492,-96.634509 42.732485,-96.634705 42.732491,-96.635187 42.732557,-96.635467 42.732622,-96.635643 42.732685,-96.635857 42.732773,-96.636077 42.732851,-96.63631 42.732983,-96.636398 42.733014,-96.636438 42.733034,-96.636511 42.733082,-96.636648 42.733183,-96.63672 42.733231,-96.636816 42.733312,-96.637575 42.734013,-96.637641 42.734066,-96.637988 42.734316,-96.638089 42.734394,-96.638325 42.734622,-96.638348 42.734654,-96.638947 42.735764,-96.638959 42.735786,-96.639097 42.736001,-96.639333 42.736367,-96.639364 42.736435,-96.639451 42.736758,-96.639468 42.736821,-96.639485 42.736928,-96.639484 42.736963,-96.639467 42.737034,-96.63941 42.73717,-96.639302 42.73729,-96.63927 42.737317,-96.639155 42.737383,-96.638984 42.737453,-96.638846 42.737491,-96.638749 42.737501,-96.638358 42.737476,-96.638262 42.737464,-96.638025 42.737419,-96.637551 42.737269,-96.636181 42.736838,-96.635159 42.736585,-96.635024 42.736543,-96.634545 42.736294,-96.63436 42.736177,-96.634109 42.735933,-96.634076 42.735907,-96.633715 42.735721,-96.63355 42.735644,-96.633353 42.735537,-96.633095 42.735434,-96.632964 42.735386,-96.632829 42.735343,-96.632365 42.735229,-96.632225 42.735198,-96.632034 42.735166,-96.631987 42.735155,-96.631943 42.735139,-96.631898 42.735128,-96.631751 42.735127,-96.631655 42.735113,-96.631602 42.735107,-96.631462 42.735092,-96.631367 42.735076,-96.631172 42.735078,-96.630886 42.735111,-96.630839 42.735122,-96.630684 42.735207,-96.630622 42.735261,-96.630586 42.735286,-96.630525 42.735341,-96.630436 42.735468,-96.630404 42.735536,-96.630393 42.73557,-96.630366 42.735748,-96.630383 42.735962,-96.630448 42.736171,-96.630489 42.736265,-96.630537 42.736375,-96.630728 42.736665,-96.631073 42.737054,-96.631108 42.73708,-96.631295 42.737195,-96.631808 42.737404,-96.632037 42.737468,-96.632078 42.737488,-96.632152 42.737535,-96.632247 42.737617,-96.632335 42.737703,-96.632643 42.737981,-96.633123 42.738479,-96.633326 42.738764,-96.633443 42.738878,-96.63351 42.738931,-96.633723 42.739079,-96.633876 42.739169,-96.63398 42.739245,-96.634067 42.739331,-96.634242 42.739456,-96.634442 42.73956,-96.634583 42.739659,-96.634809 42.739847,-96.634891 42.739936,-96.635148 42.74024,-96.635466 42.740683,-96.635561 42.740847,-96.635697 42.741299,-96.635728 42.741513,-96.635719 42.741555,-96.635679 42.74176,-96.635668 42.741795,-96.635612 42.741894,-96.635511 42.742017,-96.635456 42.742075,-96.63542 42.7421,-96.635079 42.742303,-96.634833 42.742421,-96.63431 42.74262,-96.633967 42.742759,-96.633875 42.742784,-96.633543 42.742845,-96.632761 42.742894,-96.632664 42.742891,-96.632371 42.742868,-96.632175 42.742868,-96.631592 42.742928,-96.631449 42.742954,-96.631356 42.742976,-96.629712 42.743452,-96.629586 42.743506,-96.629509 42.743551,-96.629479 42.743579,-96.629429 42.743641,-96.629381 42.743742,-96.629353 42.743847,-96.629351 42.743882,-96.629376 42.744024,-96.629387 42.744059,-96.62942 42.744126,-96.629496 42.744218,-96.629531 42.744243,-96.629611 42.744285,-96.629656 42.7443,-96.630407 42.744464,-96.630553 42.744475,-96.630992 42.744497,-96.631041 42.744496,-96.631378 42.744541,-96.631424 42.744554,-96.631727 42.744671,-96.631911 42.744789,-96.631944 42.744815,-96.632026 42.744904,-96.632107 42.745035,-96.632121 42.745069,-96.632155 42.74521,-96.632154 42.745281,-96.632137 42.745459,-96.632096 42.745599,-96.63198 42.745834,-96.631855 42.746028,-96.631701 42.746329,-96.631618 42.746417,-96.631549 42.746468,-96.631369 42.746589,-96.631203 42.746666,-96.631067 42.746706,-96.630408 42.746848,-96.630236 42.746916,-96.630199 42.746939,-96.630133 42.746992,-96.630022 42.747109,-96.630003 42.747142,-96.62987 42.747486,-96.62985 42.747555,-96.629819 42.747804,-96.629835 42.748018,-96.629937 42.74822,-96.629961 42.748251,-96.630294 42.74856,-96.630349 42.748619,-96.630392 42.748683,-96.630539 42.749023,-96.630559 42.749093,-96.630564 42.749129,-96.630559 42.749342,-96.630521 42.749519,-96.630412 42.749793,-96.630405 42.749865,-96.630364 42.750004,-96.630412 42.750136,-96.63042 42.750207,-96.630406 42.750241,-96.630384 42.750273,-96.630082 42.750641,-96.630019 42.750696,-96.629983 42.75072,-96.629856 42.750774,-96.629762 42.750794,-96.629618 42.750815,-96.629521 42.750823,-96.629474 42.750814,-96.629291 42.75076,-96.629024 42.75067,-96.628858 42.750594,-96.628723 42.750552,-96.628675 42.750544,-96.628384 42.750516,-96.628188 42.750518,-96.627994 42.750541,-96.627576 42.750642,-96.627532 42.750657,-96.627313 42.7508,-96.627151 42.750881,-96.627077 42.750928,-96.627046 42.750956,-96.626964 42.751086,-96.62695 42.75112,-96.62689 42.751582,-96.626895 42.751867,-96.62682 42.75211,-96.626634 42.752361,-96.626607 42.75239,-96.62648 42.7525,-96.626438 42.752515,-96.626343 42.752531,-96.626297 42.752544,-96.626255 42.752562,-96.626011 42.752699,-96.625908 42.752758,-96.625785 42.752816,-96.625748 42.752839,-96.625661 42.752926,-96.625626 42.752951,-96.625579 42.752953,-96.625436 42.752927,-96.62525 42.752882,-96.625007 42.752903,-96.624615 42.752901,-96.62408 42.752849,-96.623344 42.752852,-96.623198 42.752868,-96.622914 42.752925,-96.622552 42.753035,-96.622301 42.753147,-96.622072 42.753283,-96.621968 42.753359,-96.62193 42.753381,-96.621764 42.753459,-96.621719 42.753471,-96.621671 42.753477,-96.620887 42.75343,-96.62074 42.753438,-96.620306 42.753499,-96.620213 42.753521,-96.620126 42.753554,-96.6198 42.753713,-96.619695 42.753788,-96.619545 42.75393,-96.619491 42.753989,-96.619471 42.754022,-96.619278 42.754502,-96.619262 42.754609,-96.619269 42.754752,-96.619331 42.754849,-96.619361 42.754918,-96.6194 42.754983,-96.619711 42.755389,-96.619755 42.755453,-96.620005 42.75588,-96.620014 42.755915,-96.620064 42.756235,-96.62007 42.756342,-96.619995 42.756804,-96.619988 42.756946,-96.620004 42.757089,-96.62002 42.75716,-96.620025 42.757173,-96.62009 42.757331,-96.620212 42.757526,-96.620267 42.757585,-96.620702 42.757875,-96.621022 42.75804,-96.621399 42.758207,-96.621487 42.75824,-96.621812 42.758321,-96.622342 42.758397,-96.622391 42.758401,-96.622833 42.758398,-96.62293 42.758388,-96.623023 42.758365,-96.623396 42.758194,-96.623473 42.758149,-96.62369 42.758003,-96.624298 42.757397,-96.624367 42.757303,-96.624471 42.757102,-96.624598 42.75672,-96.624705 42.756298,-96.624878 42.755925,-96.624969 42.755611,-96.624983 42.755577,-96.625107 42.755423,-96.625176 42.755328,-96.625293 42.755095,-96.625369 42.754965,-96.625381 42.75493,-96.625389 42.75486,-96.625423 42.754757,-96.625436 42.754728,-96.625455 42.754691,-96.625503 42.754553,-96.62552 42.75452,-96.625566 42.754457,-96.625601 42.754433,-96.626086 42.754193,-96.626132 42.754179,-96.626421 42.754148,-96.62647 42.754151,-96.626661 42.75418,-96.626944 42.754232,-96.626989 42.754247,-96.627068 42.75429,-96.627243 42.754413,-96.627445 42.754738,-96.627515 42.754892,-96.627558 42.75503,-96.627594 42.755243,-96.627587 42.755314,-96.627567 42.755384,-96.627566 42.755491,-96.627525 42.755847,-96.62755 42.756132,-96.627628 42.756375,-96.627756 42.756662,-96.627811 42.756783,-96.627893 42.756913,-96.628129 42.757185,-96.628368 42.757412,-96.628672 42.757645,-96.628784 42.757714,-96.629563 42.758147,-96.629984 42.758495,-96.630012 42.758525,-96.630796 42.759759,-96.630834 42.759825,-96.630967 42.760094,-96.631022 42.760152,-96.631199 42.760276,-96.631465 42.760541,-96.631518 42.760594,-96.631905 42.760868,-96.632056 42.761009,-96.632157 42.761087,-96.632397 42.761313,-96.632523 42.761507,-96.632784 42.761786,-96.632882 42.761891,-96.633302 42.762215,-96.633557 42.762383,-96.634018 42.76265,-96.635139 42.763227,-96.635409 42.763381,-96.635607 42.76354,-96.6361 42.763986,-96.636353 42.764411,-96.636384 42.764478,-96.636489 42.764753,-96.636498 42.764824,-96.636493 42.76486,-96.636352 42.765126,-96.636275 42.765217,-96.636039 42.765399,-96.636 42.765421,-96.635792 42.765515,-96.635701 42.765542,-96.635512 42.765581,-96.635122 42.765611,-96.634877 42.765601,-96.634828 42.765595,-96.634725 42.765568,-96.634365 42.765476,-96.633799 42.765262,-96.632892 42.764989,-96.632796 42.764975,-96.632551 42.764977,-96.632307 42.764999,-96.632215 42.765023,-96.632046 42.765095,-96.632008 42.765118,-96.631975 42.765144,-96.631756 42.765382,-96.631625 42.765533,-96.631605 42.765565,-96.631551 42.765703,-96.631527 42.765845,-96.631522 42.766095,-96.63161 42.766446,-96.631655 42.766549,-96.632166 42.767631,-96.63223 42.767803,-96.632335 42.768041,-96.632641 42.768528,-96.632686 42.768592,-96.632715 42.76862,-96.63275 42.768645,-96.632904 42.768733,-96.632993 42.768765,-96.633088 42.768782,-96.633137 42.768783,-96.633323 42.768736,-96.63354 42.768653,-96.633622 42.768614,-96.634214 42.768138,-96.634291 42.768093,-96.634415 42.768036,-96.63474 42.767955,-96.634935 42.767938,-96.635081 42.767949,-96.635508 42.768034,-96.635553 42.768048,-96.635924 42.768223,-96.636036 42.768292,-96.636135 42.768371,-96.636344 42.768569,-96.636393 42.768631,-96.636551 42.769006,-96.636549 42.769077,-96.636526 42.769254,-96.636516 42.769396,-96.63647 42.769535,-96.636382 42.76974,-96.636338 42.769803,-96.636055 42.770096,-96.63567 42.770419,-96.635417 42.770589,-96.635176 42.770713,-96.635091 42.770748,-96.634036 42.771129,-96.633582 42.771262,-96.633487 42.771279,-96.633199 42.771319,-96.633054 42.771332,-96.633004 42.771329,-96.632812 42.7713,-96.63277 42.771283,-96.632733 42.77126,-96.632577 42.771122,-96.632511 42.771026,-96.632469 42.770924,-96.632365 42.770467,-96.632373 42.770397,-96.6324 42.770256,-96.632385 42.770185,-96.632312 42.770015,-96.632196 42.769858,-96.632133 42.769803,-96.631959 42.769678,-96.631918 42.769658,-96.631827 42.769631,-96.631682 42.769615,-96.631633 42.769613,-96.631486 42.769616,-96.631309 42.76965,-96.631183 42.769705,-96.631074 42.769778,-96.630961 42.769937,-96.630874 42.770023,-96.630776 42.770146,-96.630707 42.770317,-96.630648 42.770599,-96.630638 42.770669,-96.630583 42.770768,-96.630536 42.770831,-96.630306 42.771105,-96.629937 42.771482,-96.629929 42.77149,-96.629652 42.771815,-96.629587 42.771868,-96.629473 42.771934,-96.629431 42.771953,-96.629339 42.771977,-96.629245 42.771994,-96.629147 42.772001,-96.62905 42.771997,-96.628865 42.77195,-96.62865 42.771865,-96.62809 42.771576,-96.627705 42.77142,-96.627573 42.771372,-96.627126 42.771225,-96.626881 42.771215,-96.626832 42.771216,-96.626785 42.771225,-96.626398 42.77138,-96.626361 42.771403,-96.626223 42.771538,-96.626197 42.771568,-96.626073 42.771801,-96.626052 42.772194,-96.626054 42.77223,-96.626096 42.772442,-96.626264 42.772928,-96.626526 42.773312,-96.626813 42.773844,-96.626839 42.773874,-96.626905 42.773927,-96.626965 42.773983,-96.627055 42.77411,-96.627081 42.774178,-96.627138 42.774532,-96.627143 42.774711,-96.627107 42.774995,-96.627069 42.775135,-96.626985 42.775302,-96.626859 42.775456,-96.626627 42.775686,-96.626595 42.775713,-96.626523 42.775761,-96.626481 42.77578,-96.626219 42.775877,-96.626084 42.775921,-96.625614 42.776023,-96.625565 42.776026,-96.625517 42.776034,-96.625379 42.776071,-96.625216 42.77615,-96.625171 42.776163,-96.624733 42.776206,-96.624537 42.776211,-96.623608 42.776166,-96.623071 42.776202,-96.622975 42.776218,-96.622929 42.77623,-96.622621 42.77634,-96.622422 42.776445,-96.622283 42.776546,-96.622041 42.776771,-96.621985 42.77683,-96.62184 42.777134,-96.621815 42.777203,-96.621777 42.777343,-96.62177 42.777414,-96.621791 42.777628,-96.621949 42.778152,-96.621966 42.778667,-96.621968 42.778724,-96.621958 42.778795,-96.621921 42.778829,-96.621896 42.77886,-96.621767 42.779053,-96.621751 42.779086,-96.621594 42.779609,-96.621512 42.779777,-96.6215 42.779848,-96.621469 42.780132,-96.621469 42.780168,-96.621476 42.780203,-96.621503 42.780272,-96.621544 42.780337,-96.621509 42.780404,-96.621437 42.780575,-96.621191 42.781042,-96.620996 42.78137,-96.620756 42.781839,-96.620711 42.781941,-96.620502 42.782566,-96.620432 42.782737,-96.620331 42.78305,-96.620122 42.783413,-96.619849 42.783792,-96.619791 42.78385,-96.619503 42.784043,-96.619462 42.784064,-96.619285 42.784124,-96.619049 42.784174,-96.618471 42.784257,-96.617806 42.784381,-96.617306 42.784529,-96.617008 42.784653,-96.616487 42.784921,-96.616148 42.785066,-96.616101 42.785074,-96.616052 42.785075,-96.615905 42.785068,-96.615616 42.785026,-96.615569 42.785016,-96.615528 42.784997,-96.61542 42.784924,-96.615358 42.784868,-96.615284 42.784776,-96.61522 42.784603,-96.615201 42.784497,-96.615246 42.784249,-96.615367 42.783939,-96.615489 42.783557,-96.615518 42.783452,-96.615547 42.783238,-96.615549 42.783167,-96.615467 42.782815,-96.615432 42.782749,-96.615264 42.78253,-96.615204 42.782474,-96.615133 42.782425,-96.615051 42.782386,-96.614882 42.782314,-96.6148 42.782275,-96.61471 42.782247,-96.614612 42.782246,-96.614515 42.782254,-96.614326 42.782293,-96.61428 42.782306,-96.614148 42.782354,-96.614111 42.782377,-96.613945 42.782509,-96.61384 42.782629,-96.613716 42.782975,-96.613695 42.783045,-96.613624 42.783363,-96.613462 42.783812,-96.613264 42.784216,-96.613191 42.784309,-96.613073 42.784426,-96.61297 42.784503,-96.61293 42.784524,-96.612887 42.784541,-96.612707 42.784597,-96.612563 42.78462,-96.612465 42.784622,-96.612272 42.784595,-96.612226 42.784582,-96.611972 42.784475,-96.611932 42.784454,-96.61172 42.784306,-96.61169 42.784278,-96.611548 42.784012,-96.61132 42.783316,-96.611276 42.783214,-96.611046 42.782858,-96.610954 42.782775,-96.610918 42.782751,-96.610536 42.782589,-96.610489 42.782578,-96.61015 42.782538,-96.610052 42.782534,-96.610003 42.782536,-96.609907 42.78255,-96.609837 42.782526,-96.609745 42.782502,-96.609697 42.782494,-96.609599 42.78249,-96.609406 42.782518,-96.609363 42.782534,-96.609216 42.782629,-96.609152 42.782683,-96.609071 42.782772,-96.609049 42.782804,-96.609022 42.782873,-96.609014 42.782944,-96.609019 42.783016,-96.609028 42.78305,-96.609092 42.783104,-96.609102 42.783241,-96.609089 42.783384,-96.609109 42.783705,-96.609104 42.783741,-96.609052 42.783879,-96.609001 42.783979,-96.608979 42.784011,-96.608895 42.784099,-96.608791 42.784175,-96.608752 42.784197,-96.608417 42.784346,-96.608279 42.784384,-96.607993 42.784434,-96.607895 42.784441,-96.607846 42.784438,-96.607559 42.784392,-96.607384 42.784328,-96.607345 42.784306,-96.607312 42.784279,-96.60677 42.783726,-96.606719 42.783686,-96.606571 42.783569,-96.606228 42.783366,-96.606067 42.783284,-96.605982 42.783249,-96.605389 42.783078,-96.605008 42.783009,-96.604959 42.783005,-96.604862 42.783015,-96.60444 42.78311,-96.604179 42.783208,-96.60414 42.78323,-96.603964 42.783355,-96.603934 42.783382,-96.603909 42.783414,-96.603831 42.783544,-96.603695 42.783693,-96.603673 42.783725,-96.603619 42.783824,-96.603567 42.783962,-96.603565 42.783997,-96.603582 42.78414,-96.603684 42.784453,-96.603703 42.78456,-96.603859 42.785157,-96.603864 42.785192,-96.603859 42.785514,-96.603852 42.785549,-96.603723 42.785894,-96.603678 42.785996,-96.603275 42.786487,-96.602978 42.786815,-96.602686 42.787188,-96.602404 42.787759,-96.602355 42.788006,-96.602345 42.788113,-96.602358 42.788184,-96.602404 42.788323,-96.602557 42.78874,-96.60302 42.789014,-96.60331 42.789108,-96.603337 42.789138,-96.603704 42.789426,-96.604038 42.789635,-96.604234 42.789795,-96.604311 42.789839,-96.604521 42.789931,-96.604633 42.790049,-96.604682 42.790111,-96.604742 42.790167,-96.604848 42.790242,-96.604943 42.790286,-96.604981 42.790399,-96.605053 42.790532,-96.605066 42.790567,-96.605229 42.791163,-96.605231 42.79127,-96.605196 42.791447,-96.605007 42.791696,-96.60491 42.791776,-96.60484 42.791827,-96.604403 42.792112,-96.604245 42.792195,-96.604155 42.792224,-96.603732 42.792315,-96.603637 42.792332,-96.60354 42.792344,-96.603442 42.792347,-96.60305 42.792344,-96.602759 42.792315,-96.602712 42.792305,-96.602156 42.792077,-96.601761 42.791935,-96.601473 42.791871,-96.601244 42.791821,-96.600909 42.791766,-96.600765 42.791746,-96.600666 42.791749,-96.599882 42.791793,-96.599538 42.791782,-96.599293 42.791768,-96.598116 42.79181,-96.597628 42.791852,-96.597198 42.791924,-96.597108 42.791952,-96.596849 42.792055,-96.596646 42.792155,-96.596495 42.792247,-96.596059 42.792478,-96.595625 42.792645,-96.595343 42.792788,-96.595272 42.792838,-96.595214 42.792896,-96.595052 42.793115,-96.595035 42.793149,-96.595006 42.793254,-96.595003 42.793468,-96.595009 42.79354,-96.595017 42.793575,-96.59511 42.793778,-96.595173 42.793875,-96.595199 42.793906,-96.595378 42.794076,-96.595479 42.794154,-96.595552 42.794202,-96.595636 42.794238,-96.596228 42.794412,-96.596484 42.794517,-96.596596 42.794586,-96.596775 42.794757,-96.5968 42.794788,-96.596855 42.794887,-96.596968 42.795198,-96.596988 42.795268,-96.597001 42.79541,-96.596933 42.795872,-96.596937 42.795944,-96.596964 42.796049,-96.596991 42.796118,-96.597134 42.796422,-96.597175 42.796487,-96.59729 42.796603,-96.597375 42.79666,-96.597702 42.796819,-96.598145 42.796972,-96.599132 42.797372,-96.599298 42.797447,-96.599378 42.797489,-96.59941 42.797516,-96.599522 42.797634,-96.599589 42.797729,-96.599617 42.797798,-96.599637 42.797867,-96.599704 42.798186,-96.599707 42.798257,-96.599683 42.798435,-96.59946 42.798983,-96.599442 42.799016,-96.59921 42.799331,-96.598976 42.799603,-96.598945 42.799631,-96.598627 42.799854,-96.598506 42.799916,-96.598463 42.799932,-96.59827 42.799961,-96.598172 42.799966,-96.598041 42.799918,-96.597963 42.799875,-96.597832 42.799768,-96.597811 42.799736,-96.597802 42.799701,-96.597747 42.79931,-96.597607 42.79882,-96.597481 42.798512,-96.597317 42.798213,-96.597264 42.798153,-96.59688 42.797828,-96.596574 42.797648,-96.59645 42.79759,-96.596045 42.797461,-96.595857 42.79742,-96.595808 42.797415,-96.595465 42.797431,-96.595276 42.797471,-96.59496 42.79757,-96.594919 42.797588,-96.594588 42.797802,-96.594285 42.798037,-96.594076 42.798235,-96.593605 42.798779,-96.593296 42.799187,-96.592812 42.799969,-96.592594 42.800368,-96.592461 42.800637,-96.592432 42.800705,-96.592422 42.800761,-96.592345 42.801201,-96.592353 42.801272,-96.592425 42.801321,-96.592544 42.801434,-96.592598 42.801493,-96.592806 42.801776,-96.592887 42.801944,-96.592906 42.802014,-96.592911 42.802082,-96.593047 42.802178,-96.593105 42.802235,-96.593131 42.802304,-96.59318 42.802516,-96.593179 42.802587,-96.593172 42.802623,-96.593102 42.802794,-96.593005 42.802958,-96.592169 42.803968,-96.591892 42.804347,-96.591749 42.804575,-96.59161 42.804842,-96.591196 42.805451,-96.591057 42.805867,-96.591003 42.806078,-96.590981 42.806399,-96.590997 42.806578,-96.590997 42.806971,-96.590958 42.807074,-96.590884 42.807244,-96.59081 42.807377,-96.590539 42.807988,-96.590469 42.808233,-96.590465 42.808297,-96.590465 42.808304,-96.590485 42.808447,-96.590525 42.80855,-96.590673 42.808775,-96.590776 42.808897,-96.5909 42.809008,-96.591651 42.809622,-96.591968 42.809846,-96.592049 42.809887,-96.592088 42.809908,-96.592441 42.810035,-96.592912 42.810137,-96.593056 42.810159,-96.593154 42.810166,-96.593694 42.810142,-96.593936 42.810112,-96.594078 42.810085,-96.594466 42.810039,-96.594564 42.810036,-96.594613 42.810041,-96.595284 42.810146,-96.59533 42.810158,-96.595493 42.810239,-96.595566 42.810286,-96.595626 42.810343,-96.595697 42.810437,-96.595723 42.810607,-96.595712 42.810929,-96.59567 42.811177,-96.595639 42.811245,-96.595496 42.811433,-96.595263 42.811865,-96.595239 42.811934,-96.595144 42.812358,-96.595118 42.812643,-96.595121 42.812786,-96.595126 42.812822,-96.595214 42.8131,-96.595306 42.813266,-96.595374 42.813353,-96.59562 42.813671,-96.595798 42.813965,-96.595879 42.814171,-96.595962 42.81474,-96.595945 42.814954,-96.595934 42.814989,-96.595902 42.815057,-96.595677 42.815333,-96.595617 42.81539,-96.595409 42.815542,-96.595143 42.8157,-96.594896 42.815817,-96.594807 42.815846,-96.59457 42.815893,-96.594521 42.815898,-96.594375 42.815903,-96.593886 42.815873,-96.593647 42.815833,-96.592423 42.815463,-96.591632 42.81527,-96.591584 42.815262,-96.591437 42.815259,-96.591096 42.815293,-96.590724 42.815383,-96.590641 42.815421,-96.590605 42.815445,-96.590392 42.815642,-96.590165 42.815999,-96.590089 42.816131,-96.589986 42.816372,-96.589829 42.816746,-96.589782 42.816884,-96.589587 42.817913,-96.589495 42.818039,-96.589367 42.818148,-96.589239 42.8183,-96.589209 42.818328,-96.589174 42.818353,-96.588943 42.818487,-96.588824 42.818473,-96.588731 42.818449,-96.588646 42.818414,-96.588281 42.818232,-96.587665 42.817876,-96.587581 42.817839,-96.58749 42.817861,-96.587442 42.817869,-96.587345 42.817877,-96.586856 42.817848,-96.58671 42.817836,-96.58661 42.817833,-96.586415 42.817828,-96.586268 42.817835,-96.586219 42.817834,-96.586122 42.817822,-96.586029 42.8178,-96.585902 42.817847,-96.585819 42.817886,-96.585781 42.817908,-96.585455 42.818126,-96.585385 42.818177,-96.585158 42.818313,-96.585123 42.818338,-96.584813 42.818615,-96.584705 42.818689,-96.584109 42.819005,-96.584075 42.819031,-96.584051 42.819062,-96.583958 42.819339,-96.583955 42.81941,-96.583978 42.819624,-96.583997 42.819693,-96.584104 42.820004,-96.584168 42.8201,-96.584305 42.82029,-96.584446 42.820388,-96.584527 42.820429,-96.584564 42.820452,-96.584632 42.820504,-96.58488 42.820621,-96.585031 42.820712,-96.585097 42.820764,-96.585463 42.821101,-96.585793 42.821456,-96.585957 42.821715,-96.586037 42.821884,-96.586098 42.822057,-96.586131 42.822233,-96.586119 42.822805,-96.586094 42.822946,-96.585971 42.823402,-96.585808 42.823776,-96.585709 42.823899,-96.585589 42.824013,-96.585413 42.824138,-96.585197 42.824222,-96.584514 42.824423,-96.58434 42.824489,-96.583787 42.82472,-96.583652 42.824763,-96.583514 42.8248,-96.583225 42.824841,-96.583128 42.824851,-96.582833 42.824862,-96.582539 42.824859,-96.581515 42.824756,-96.581319 42.824755,-96.581026 42.824784,-96.580598 42.824865,-96.580554 42.82488,-96.580549 42.824882,-96.580231 42.825043,-96.580193 42.825066,-96.579928 42.825276,-96.579865 42.825331,-96.579808 42.825389,-96.579566 42.825699,-96.579514 42.825759,-96.57945 42.825856,-96.57912 42.826537,-96.579041 42.826668,-96.578809 42.826852,-96.578287 42.827119,-96.57825 42.827143,-96.578152 42.827222,-96.577852 42.827505,-96.577654 42.827791,-96.577635 42.827825,-96.577563 42.827995,-96.577531 42.828136,-96.57753 42.828315,-96.577556 42.828492,-96.57783 42.829067,-96.577891 42.829164,-96.578148 42.829469,-96.578355 42.829669,-96.578707 42.829967,-96.57921 42.830253,-96.579246 42.830277,-96.579398 42.830418,-96.579574 42.830542,-96.57961 42.830573,-96.579796 42.830733,-96.579958 42.830912,-96.580033 42.831004,-96.580192 42.831266,-96.580266 42.831436,-96.580396 42.832036,-96.580434 42.832321,-96.580448 42.832392,-96.580532 42.832671,-96.580561 42.83274,-96.580581 42.832772,-96.580643 42.832828,-96.580719 42.832874,-96.5808 42.832913,-96.580846 42.832927,-96.581224 42.833005,-96.581728 42.833146,-96.581815 42.833179,-96.58215 42.833488,-96.582217 42.833583,-96.582305 42.83375,-96.582326 42.833861,-96.582381 42.834139,-96.582391 42.834282,-96.582362 42.834604,-96.582324 42.83478,-96.582304 42.83485,-96.582164 42.835193,-96.582155 42.835228,-96.582135 42.835371,-96.58208 42.83543,-96.581968 42.835589,-96.581512 42.836381,-96.581498 42.836415,-96.581492 42.836446,-96.581481 42.836521,-96.581474 42.836736,-96.581553 42.837412,-96.581544 42.837519,-96.581489 42.837693,-96.58147 42.837726,-96.581443 42.837756,-96.581378 42.83781,-96.581075 42.837992,-96.580993 42.838031,-96.580907 42.838065,-96.580678 42.838131,-96.580583 42.838149,-96.580193 42.838182,-96.580046 42.838189,-96.579406 42.838186,-96.579112 42.83813,-96.579073 42.838123,-96.578793 42.838056,-96.578704 42.838026,-96.578618 42.837991,-96.578459 42.837907,-96.57833 42.837799,-96.578149 42.837586,-96.578079 42.837494,-96.578036 42.837311,-96.57804 42.837132,-96.578049 42.837097,-96.578172 42.836826,-96.578198 42.836795,-96.57829 42.836712,-96.578438 42.836618,-96.578524 42.836583,-96.57931 42.83629,-96.579389 42.836247,-96.579458 42.836197,-96.579485 42.836167,-96.579577 42.83604,-96.579692 42.835767,-96.579717 42.835626,-96.579705 42.835447,-96.579689 42.835377,-96.579676 42.835342,-96.579574 42.835141,-96.579545 42.835112,-96.579447 42.835032,-96.579377 42.834983,-96.579292 42.834947,-96.578887 42.834818,-96.578605 42.834755,-96.578164 42.834727,-96.578115 42.834729,-96.577907 42.834752,-96.577872 42.834756,-96.577682 42.834792,-96.577276 42.83492,-96.576755 42.83512,-96.576309 42.835343,-96.575848 42.835611,-96.575599 42.835783,-96.575409 42.835947,-96.575336 42.83604,-96.575193 42.836306,-96.575163 42.836411,-96.575101 42.837016,-96.575044 42.837335,-96.574937 42.837611,-96.574919 42.837644,-96.574808 42.837762,-96.574747 42.837818,-96.574675 42.837866,-96.574513 42.837948,-96.574424 42.837978,-96.574303 42.837963,-96.574258 42.837949,-96.57413 42.837896,-96.573966 42.837817,-96.573894 42.837768,-96.573444 42.837389,-96.573265 42.837267,-96.573189 42.837222,-96.572942 42.837104,-96.57285 42.837078,-96.572471 42.837002,-96.572375 42.836989,-96.572276 42.836988,-96.571932 42.836996,-96.57145 42.837065,-96.571172 42.837137,-96.570993 42.837196,-96.569579 42.837775,-96.569402 42.837838,-96.569079 42.837923,-96.569031 42.837928,-96.568932 42.837926,-96.568884 42.837919,-96.5686 42.837861,-96.568465 42.837819,-96.568382 42.837781,-96.568343 42.837759,-96.568046 42.837521,-96.567966 42.837431,-96.567948 42.837397,-96.567896 42.837186,-96.567893 42.837008,-96.567915 42.836902,-96.567939 42.836833,-96.567997 42.836734,-96.568117 42.836577,-96.568303 42.836411,-96.568614 42.836182,-96.56915 42.835868,-96.569184 42.835843,-96.569407 42.835652,-96.569535 42.835499,-96.569638 42.835316,-96.569723 42.835169,-96.569737 42.835135,-96.569772 42.83485,-96.569769 42.834814,-96.569714 42.834568,-96.569683 42.8345,-96.569547 42.834309,-96.569311 42.834038,-96.56928 42.83401,-96.56899 42.833817,-96.567876 42.833114,-96.567529 42.832844,-96.567255 42.832592,-96.567033 42.832355,-96.566858 42.83214,-96.56657 42.831749,-96.566185 42.831227,-96.565765 42.830743,-96.565259 42.830216,-96.564937 42.829814,-96.564844 42.829649,-96.564762 42.829481,-96.564715 42.829342,-96.564699 42.829236,-96.56468 42.829051,-96.564671 42.828951,-96.564668 42.82888,-96.564638 42.828775,-96.564609 42.828708,-96.564545 42.828611,-96.564358 42.828401,-96.564324 42.828375,-96.564032 42.828244,-96.563942 42.828215,-96.563895 42.828204,-96.563751 42.828183,-96.563604 42.828174,-96.563555 42.828177,-96.563177 42.828256,-96.563042 42.8283,-96.5624 42.828629,-96.561972 42.828924,-96.56194 42.828951,-96.561604 42.829302,-96.561554 42.829363,-96.561537 42.829397,-96.561518 42.829467,-96.561485 42.829644,-96.561487 42.82968,-96.561507 42.829712,-96.561535 42.829742,-96.561747 42.82989,-96.562147 42.830099,-96.562181 42.830125,-96.562428 42.830347,-96.562513 42.830434,-96.562749 42.830706,-96.562854 42.830907,-96.563012 42.831356,-96.563016 42.831392,-96.563019 42.831785,-96.563012 42.831821,-96.562973 42.831924,-96.562927 42.832026,-96.562645 42.832597,-96.562441 42.833187,-96.562387 42.833398,-96.562381 42.833745,-96.562395 42.833995,-96.562564 42.834609,-96.562646 42.834906,-96.562689 42.83519,-96.562714 42.835259,-96.562823 42.835496,-96.562834 42.835531,-96.562855 42.835673,-96.562862 42.835887,-96.562809 42.836422,-96.562671 42.836839,-96.562537 42.83703,-96.562511 42.83706,-96.562086 42.837455,-96.562029 42.837513,-96.561938 42.837585,-96.561529 42.837907,-96.561359 42.838082,-96.561316 42.838146,-96.56124 42.838427,-96.561219 42.83864,-96.561172 42.838779,-96.561125 42.838881,-96.560976 42.839106,-96.560944 42.839134,-96.56087 42.83918,-96.560405 42.839444,-96.560279 42.839501,-96.560235 42.839515,-96.560179 42.839528,-96.559721 42.839638,-96.559333 42.839681,-96.559284 42.83968,-96.558942 42.83965,-96.558896 42.839639,-96.558543 42.839512,-96.558459 42.839475,-96.558312 42.839381,-96.558205 42.839307,-96.557847 42.839012,-96.557818 42.838984,-96.557622 42.838736,-96.557296 42.838176,-96.557134 42.837839,-96.556877 42.837453,-96.556762 42.837294,-96.55668 42.837259,-96.55652 42.837176,-96.556484 42.837152,-96.556376 42.837038,-96.556289 42.836946,-96.556167 42.836834,-96.556116 42.836773,-96.556072 42.836709,-96.556 42.836538,-96.555787 42.836461,-96.554944 42.836267,-96.553986 42.836106,-96.553503 42.836041,-96.553389 42.836028,-96.553068 42.835992,-96.552826 42.835969,-96.552778 42.835961,-96.552239 42.83598,-96.552191 42.835988,-96.551568 42.836202,-96.551528 42.836222,-96.551381 42.836317,-96.551127 42.836535,-96.551037 42.83662,-96.550875 42.836799,-96.550592 42.837149,-96.550562 42.837178,-96.550322 42.837531,-96.550308 42.837555,-96.549652 42.8388,-96.54945 42.839277,-96.549417 42.839345,-96.549305 42.839543,-96.548963 42.84005,-96.548701 42.84044,-96.54864 42.840537,-96.548627 42.840572,-96.548586 42.840748,-96.548586 42.84082,-96.548598 42.840891,-96.548919 42.841057,-96.548964 42.841073,-96.549243 42.841142,-96.549337 42.841162,-96.549724 42.841214,-96.550631 42.841376,-96.550907 42.841452,-96.551128 42.84153,-96.552045 42.841876,-96.552258 42.841965,-96.552634 42.842133,-96.552714 42.842175,-96.553164 42.842506,-96.553224 42.842562,-96.553537 42.842896,-96.553805 42.843182,-96.553904 42.843306,-96.55425 42.843858,-96.554287 42.843925,-96.55459 42.844642,-96.55463 42.844782,-96.554771 42.845235,-96.554796 42.845377,-96.554815 42.845555,-96.554818 42.845877,-96.554838 42.846378,-96.55494 42.847196,-96.55497 42.847301,-96.555125 42.84764,-96.555301 42.847935,-96.55552 42.848215,-96.555839 42.848487,-96.555938 42.848606,-96.556036 42.84877,-96.556075 42.84891,-96.556081 42.848946,-96.556088 42.849196,-96.556083 42.849231,-96.556071 42.849266,-96.556036 42.849333,-96.555891 42.84952,-96.555828 42.849575,-96.555714 42.849642,-96.555629 42.849678,-96.555497 42.849726,-96.555266 42.849787,-96.555119 42.849791,-96.55507 42.849787,-96.555022 42.849778,-96.554884 42.849741,-96.55475 42.849695,-96.554554 42.849588,-96.554222 42.849325,-96.553577 42.848738,-96.553413 42.848605,-96.553153 42.848441,-96.552809 42.848238,-96.552722 42.848206,-96.551597 42.847848,-96.55077 42.847626,-96.55068 42.847599,-96.550586 42.84758,-96.550537 42.847574,-96.550195 42.847584,-96.549951 42.847605,-96.549764 42.847647,-96.54972 42.847663,-96.549556 42.847742,-96.549519 42.847765,-96.549452 42.847817,-96.549425 42.847847,-96.549321 42.848085,-96.549316 42.848121,-96.549338 42.848335,-96.549348 42.84837,-96.549438 42.848536,-96.549482 42.8486,-96.549541 42.848655,-96.549825 42.848854,-96.550132 42.849032,-96.550162 42.84906,-96.550206 42.84911,-96.550426 42.849362,-96.55047 42.849426,-96.550503 42.849493,-96.550538 42.849634,-96.550564 42.849848,-96.550558 42.850098,-96.550465 42.850302,-96.550423 42.850366,-96.550161 42.850626,-96.55011 42.850687,-96.549997 42.850804,-96.549945 42.850864,-96.549692 42.851083,-96.549657 42.851108,-96.549454 42.851208,-96.549407 42.851218,-96.549165 42.851251,-96.549116 42.851253,-96.548777 42.851211,-96.548733 42.851195,-96.548418 42.851023,-96.548351 42.850971,-96.548083 42.850715,-96.548001 42.850626,-96.547575 42.850063,-96.547548 42.850033,-96.547393 42.849895,-96.547289 42.849824,-96.547177 42.849748,-96.547138 42.849727,-96.547091 42.849715,-96.546615 42.849625,-96.546531 42.849624,-96.546076 42.849621,-96.545978 42.849628,-96.545694 42.849685,-96.545512 42.849739,-96.545262 42.849854,-96.545108 42.849943,-96.544947 42.850078,-96.544857 42.850162,-96.544831 42.850193,-96.544577 42.850657,-96.544469 42.850856,-96.544385 42.851062,-96.544367 42.851095,-96.544193 42.851351,-96.544051 42.851565,-96.543931 42.851837,-96.543759 42.852359,-96.543728 42.852427,-96.543428 42.852916,-96.543273 42.853083,-96.543209 42.853153,-96.543003 42.853477,-96.542912 42.853603,-96.542688 42.854038,-96.542219 42.855131,-96.542136 42.855337,-96.541717 42.856329,-96.541455 42.856868,-96.541357 42.857145,-96.541342 42.857215,-96.541323 42.857358,-96.541168 42.857882,-96.541152 42.857989,-96.541142 42.858167,-96.541152 42.858238,-96.541244 42.858516,-96.541253 42.858535,-96.541464 42.858952,-96.541509 42.859016,-96.541825 42.859378,-96.542108 42.859576,-96.54219 42.859616,-96.542234 42.859631,-96.54262 42.859688,-96.542701 42.859681,-96.542718 42.85968,-96.542763 42.859666,-96.54302 42.859561,-96.543112 42.859504,-96.543177 42.85945,-96.543204 42.85942,-96.543282 42.859289,-96.543315 42.858789,-96.543322 42.858754,-96.543346 42.858685,-96.543559 42.858247,-96.543581 42.858215,-96.543609 42.858185,-96.543768 42.858049,-96.544198 42.857754,-96.544547 42.857556,-96.544831 42.857358,-96.544933 42.85728,-96.54506 42.857171,-96.545212 42.857081,-96.545359 42.857068,-96.545798 42.857103,-96.545941 42.857129,-96.546176 42.857179,-96.546662 42.857352,-96.546784 42.857411,-96.546819 42.857436,-96.546937 42.85755,-96.546984 42.857613,-96.547062 42.85782,-96.547117 42.858139,-96.547117 42.85821,-96.547065 42.858746,-96.547014 42.858957,-96.546948 42.859129,-96.546864 42.859258,-96.546574 42.859632,-96.546534 42.859732,-96.546417 42.859967,-96.546254 42.860187,-96.546214 42.860252,-96.546147 42.860424,-96.546067 42.860667,-96.546035 42.860916,-96.546047 42.860987,-96.546074 42.861092,-96.5461 42.861161,-96.546122 42.861193,-96.546201 42.861283,-96.546409 42.861436,-96.546531 42.861496,-96.546575 42.861512,-96.547026 42.861652,-96.547074 42.861661,-96.54717 42.861675,-96.547318 42.861676,-96.547607 42.861635,-96.547745 42.861598,-96.548033 42.861556,-96.548132 42.861551,-96.548229 42.861561,-96.54847 42.861596,-96.548806 42.861651,-96.548944 42.861689,-96.548986 42.861708,-96.549121 42.861812,-96.549332 42.862053,-96.549989 42.862586,-96.550183 42.862793,-96.550206 42.862824,-96.550346 42.863091,-96.550392 42.863193,-96.550402 42.863228,-96.550451 42.863548,-96.550449 42.863576,-96.550445 42.863655,-96.550438 42.86369,-96.550414 42.863759,-96.550358 42.863859,-96.550289 42.863953,-96.550227 42.864008,-96.550116 42.864079,-96.550031 42.864114,-96.549898 42.864159,-96.54985 42.864168,-96.549801 42.864172,-96.549408 42.864168,-96.549311 42.864163,-96.549027 42.864106,-96.548764 42.864009,-96.548433 42.863854,-96.547998 42.863687,-96.547628 42.863592,-96.547533 42.863572,-96.547389 42.86355,-96.547243 42.863534,-96.547194 42.863533,-96.547097 42.86354,-96.546905 42.863575,-96.546861 42.86359,-96.546786 42.863637,-96.546692 42.863719,-96.546616 42.863811,-96.546573 42.863914,-96.546524 42.864125,-96.546396 42.864942,-96.546237 42.865502,-96.546217 42.86563,-96.546005 42.866291,-96.545984 42.866433,-96.545947 42.866861,-96.545956 42.866968,-96.546024 42.867249,-96.546174 42.867552,-96.546288 42.867711,-96.546405 42.867826,-96.54655 42.867921,-96.546672 42.867982,-96.547794 42.868347,-96.548217 42.86853,-96.548334 42.868595,-96.549422 42.869319,-96.549623 42.869565,-96.54964 42.869599,-96.549682 42.869738,-96.549701 42.86988,-96.549701 42.869916,-96.549674 42.870166,-96.549642 42.870307,-96.549626 42.870341,-96.549486 42.870569,-96.549436 42.87063,-96.549376 42.870687,-96.548849 42.871062,-96.548693 42.871201,-96.548636 42.871259,-96.548395 42.87161,-96.548267 42.871763,-96.547957 42.872249,-96.547733 42.872646,-96.547564 42.873057,-96.547509 42.873157,-96.547366 42.873596,-96.547146 42.873955,-96.547022 42.874065,-96.546964 42.874122,-96.54676 42.874366,-96.546729 42.874394,-96.546561 42.874524,-96.546468 42.874547,-96.546324 42.874573,-96.546226 42.874571,-96.546129 42.874559,-96.545702 42.874474,-96.545606 42.87446,-96.545164 42.874452,-96.544918 42.874468,-96.544588 42.874539,-96.543946 42.874719,-96.543857 42.874749,-96.543219 42.875083,-96.541829 42.876096,-96.541454 42.876428,-96.541426 42.876457,-96.541381 42.87652,-96.541345 42.876587,-96.541263 42.876716,-96.541167 42.87688,-96.540914 42.877143,-96.540852 42.877198,-96.540484 42.877485,-96.540445 42.877507,-96.540189 42.877614,-96.539788 42.87775,-96.539603 42.877798,-96.538938 42.877927,-96.538438 42.878078,-96.537855 42.878344,-96.53767 42.878462,-96.537652 42.878565,-96.537635 42.878743,-96.537636 42.878779,-96.537649 42.878813,-96.537837 42.879104,-96.537961 42.879215,-96.538416 42.879577,-96.538557 42.879689,-96.53881 42.879953,-96.539284 42.880363,-96.53942 42.880466,-96.539474 42.880526,-96.539532 42.880625,-96.539595 42.880658,-96.539627 42.880685,-96.539856 42.880917,-96.540352 42.881534,-96.540465 42.881651,-96.540534 42.881702,-96.540613 42.881745,-96.542392 42.882355,-96.542931 42.882605,-96.543174 42.882782,-96.543206 42.882809,-96.543258 42.88287,-96.543464 42.883233,-96.543485 42.883303,-96.543511 42.883552,-96.543498 42.88373,-96.543485 42.883765,-96.543379 42.883965,-96.543312 42.884061,-96.543284 42.88409,-96.54322 42.884144,-96.543138 42.884184,-96.543095 42.8842,-96.54228 42.884452,-96.540627 42.885007,-96.54024 42.885163,-96.540028 42.885255,-96.539102 42.885726,-96.538815 42.885864,-96.538245 42.886205,-96.537977 42.886414,-96.537885 42.886497,-96.537858 42.886527,-96.537695 42.886748,-96.53768 42.886782,-96.537662 42.886852,-96.537657 42.887066,-96.537663 42.887102,-96.53774 42.887271,-96.537793 42.887372,-96.53803 42.8876,-96.538144 42.887668,-96.538714 42.887856,-96.538826 42.887888,-96.53931 42.888026,-96.539396 42.88806,-96.539678 42.888205,-96.539715 42.888228,-96.540008 42.88847,-96.540353 42.888818,-96.540371 42.888851,-96.540439 42.889096,-96.540443 42.889131,-96.540376 42.889449,-96.540237 42.889596,-96.540204 42.889623,-96.540165 42.889645,-96.539838 42.889803,-96.539748 42.889832,-96.539467 42.889897,-96.539223 42.889921,-96.539075 42.889924,-96.538584 42.889916,-96.538536 42.889911,-96.538154 42.889844,-96.537751 42.889712,-96.537252 42.889484,-96.537074 42.889423,-96.536887 42.889381,-96.536741 42.889367,-96.536593 42.88937,-96.536401 42.8894,-96.536308 42.889423,-96.536221 42.889456,-96.536011 42.889549,-96.535778 42.88968,-96.534955 42.890393,-96.534804 42.890485,-96.534682 42.890546,-96.534598 42.890582,-96.534454 42.890607,-96.534357 42.890616,-96.534014 42.890629,-96.533528 42.890581,-96.533137 42.890554,-96.531661 42.890532,-96.530878 42.89056,-96.529057 42.890568,-96.528883 42.89056,-96.528812 42.890557,-96.528326 42.890502,-96.528265 42.890535,-96.528088 42.890599,-96.528041 42.890609,-96.527992 42.890614,-96.52765 42.890598,-96.527306 42.890604,-96.52721 42.890617,-96.527163 42.890628,-96.526714 42.890773,-96.526627 42.890807,-96.526134 42.8911,-96.52609 42.891117,-96.525907 42.891169,-96.525858 42.891175,-96.52576 42.891179,-96.525662 42.891174,-96.52559 42.891241,-96.525479 42.89136,-96.525435 42.891424,-96.525328 42.891661,-96.525287 42.89191,-96.525309 42.892267,-96.525329 42.892337,-96.525381 42.892437,-96.525478 42.892562,-96.525506 42.892591,-96.525664 42.892728,-96.525739 42.892774,-96.526039 42.892897,-96.526174 42.892939,-96.52627 42.892956,-96.526766 42.893012,-96.527532 42.8931,-96.527628 42.893116,-96.528358 42.893328,-96.529011 42.893578,-96.529168 42.893664,-96.529239 42.893714,-96.529434 42.893875,-96.529518 42.893963,-96.529568 42.894024,-96.529692 42.894218,-96.529781 42.894385,-96.529928 42.894837,-96.529946 42.895015,-96.529951 42.895194,-96.529938 42.895337,-96.530027 42.895419,-96.530076 42.895481,-96.530107 42.895549,-96.530113 42.895585,-96.530112 42.895764,-96.530104 42.895835,-96.530023 42.896004,-96.530003 42.896037,-96.529928 42.896128,-96.529834 42.896211,-96.529759 42.896257,-96.529716 42.896274,-96.529488 42.896342,-96.529149 42.89638,-96.528828 42.89647,-96.528786 42.896489,-96.528675 42.896559,-96.528552 42.896671,-96.528499 42.896731,-96.528375 42.896925,-96.528349 42.896995,-96.528346 42.897173,-96.528376 42.897387,-96.528412 42.897472,-96.528509 42.897636,-96.528536 42.897666,-96.528734 42.897825,-96.528873 42.897926,-96.528987 42.897994,-96.529355 42.898174,-96.529484 42.898226,-96.530627 42.898554,-96.530985 42.898674,-96.531304 42.898842,-96.532246 42.899416,-96.53293 42.899699,-96.533334 42.899902,-96.533458 42.89996,-96.533685 42.900029,-96.534054 42.900129,-96.534197 42.900156,-96.534534 42.900208,-96.534767 42.900266,-96.534854 42.900299,-96.535022 42.900372,-96.535625 42.900736,-96.535666 42.900756,-96.535934 42.900843,-96.536124 42.900879,-96.53637 42.90088,-96.536613 42.900854,-96.536752 42.900817,-96.537081 42.900657,-96.537161 42.900619,-96.537278 42.900554,-96.537685 42.900295,-96.537846 42.90016,-96.538164 42.899937,-96.538202 42.899916,-96.538248 42.899902,-96.538625 42.89982,-96.538771 42.899806,-96.539017 42.899803,-96.539113 42.899816,-96.539347 42.899871,-96.539531 42.899939,-96.539573 42.899958,-96.539761 42.900073,-96.539863 42.900151,-96.539923 42.900207,-96.540054 42.900359,-96.540547 42.901139,-96.540835 42.90171,-96.54091 42.901842,-96.541296 42.902381,-96.541478 42.902594,-96.542048 42.903085,-96.542506 42.90341,-96.542872 42.903699,-96.54293 42.903756,-96.543071 42.904061,-96.543081 42.904096,-96.543088 42.904202,-96.543087 42.904417,-96.543081 42.904438,-96.543069 42.904487,-96.543033 42.904553,-96.542877 42.904736,-96.542686 42.904899,-96.542651 42.904924,-96.542416 42.905054,-96.542325 42.905079,-96.542276 42.905085,-96.54208 42.905097,-96.541883 42.905096,-96.541786 42.905084,-96.541505 42.905019,-96.540642 42.904758,-96.539987 42.904602,-96.539748 42.90456,-96.539553 42.904541,-96.539406 42.904537,-96.538965 42.904571,-96.538675 42.90461,-96.537727 42.904802,-96.537547 42.904858,-96.53746 42.904893,-96.536964 42.905124,-96.536815 42.905218,-96.536601 42.905414,-96.536478 42.905609,-96.536467 42.905643,-96.536491 42.905928,-96.53651 42.905998,-96.536545 42.906065,-96.536588 42.906129,-96.536942 42.906557,-96.536976 42.906584,-96.537138 42.906678,-96.538089 42.907232,-96.538157 42.907283,-96.538421 42.907541,-96.538573 42.907805,-96.538586 42.90784,-96.538624 42.908052,-96.538632 42.908159,-96.53863 42.908195,-96.5386 42.908373,-96.53853 42.908581,-96.538458 42.908771,-96.538308 42.909166,-96.538063 42.909756,-96.537727 42.91057,-96.537733 42.910785,-96.537743 42.91082,-96.537809 42.910954,-96.537832 42.910986,-96.53786 42.911015,-96.53797 42.911098,-96.538207 42.911147,-96.538592 42.911209,-96.538835 42.911232,-96.539174 42.911277,-96.539363 42.911317,-96.539584 42.911395,-96.539625 42.911415,-96.53973 42.911491,-96.539829 42.91157,-96.539885 42.911628,-96.539909 42.91166,-96.540009 42.91201,-96.540014 42.912081,-96.540023 42.912117,-96.5401 42.912318,-96.540063 42.912401,-96.53996 42.912677,-96.539383 42.913626,-96.53926 42.913898,-96.539134 42.914317,-96.53909 42.914709,-96.538943 42.915306,-96.538901 42.915409,-96.538745 42.91571,-96.538688 42.915809,-96.538542 42.915994,-96.538335 42.916206,-96.538257 42.916286,-96.538055 42.916532,-96.537918 42.916761,-96.537847 42.916894,-96.537834 42.916929,-96.537772 42.917175,-96.537759 42.917318,-96.53777 42.917425,-96.537835 42.917634,-96.537874 42.9177,-96.538087 42.917982,-96.538122 42.918004,-96.538215 42.918028,-96.538302 42.918062,-96.538343 42.918082,-96.538571 42.918219,-96.538948 42.918388,-96.539939 42.918603,-96.539984 42.918617,-96.540156 42.918686,-96.540565 42.918886,-96.540862 42.919073,-96.540988 42.919183,-96.541164 42.919406,-96.541205 42.919484,-96.541269 42.919606,-96.541525 42.920261,-96.541675 42.920749,-96.541721 42.920997,-96.541739 42.921391,-96.541737 42.921927,-96.541705 42.922498,-96.541619 42.922922,-96.541451 42.923517,-96.541283 42.923927,-96.541034 42.924356,-96.540963 42.924448,-96.540974 42.924483,-96.540977 42.924519,-96.540966 42.924626,-96.540954 42.924661,-96.540903 42.924761,-96.54088 42.924792,-96.540812 42.924843,-96.540665 42.924938,-96.540595 42.924988,-96.540555 42.925009,-96.540511 42.925025,-96.540463 42.925033,-96.54022 42.925058,-96.540171 42.92506,-96.540073 42.925057,-96.539926 42.925066,-96.539758 42.924973,-96.539674 42.924935,-96.539629 42.92492,-96.539349 42.924852,-96.539258 42.924825,-96.538791 42.924626,-96.538547 42.924505,-96.538282 42.924345,-96.537965 42.924121,-96.537598 42.923831,-96.537298 42.923548,-96.53684 42.923277,-96.5365 42.923132,-96.536409 42.923106,-96.536065 42.923091,-96.535967 42.923092,-96.535679 42.923139,-96.535538 42.923172,-96.534765 42.923401,-96.534543 42.923478,-96.534255 42.923616,-96.534106 42.92371,-96.533294 42.924331,-96.533133 42.924466,-96.532981 42.92465,-96.532953 42.924719,-96.532952 42.924754,-96.532964 42.924861,-96.532975 42.924896,-96.533018 42.92496,-96.533187 42.925136,-96.533254 42.925188,-96.533327 42.925234,-96.533423 42.925251,-96.533469 42.925264,-96.533555 42.925299,-96.533588 42.925325,-96.533828 42.925595,-96.534236 42.925999,-96.534438 42.926244,-96.534475 42.926311,-96.534562 42.926553,-96.534584 42.926659,-96.534604 42.926837,-96.534592 42.92698,-96.534575 42.92705,-96.534468 42.927399,-96.534223 42.927905,-96.534105 42.928102,-96.534061 42.928166,-96.533986 42.928258,-96.533761 42.928492,-96.532509 42.929687,-96.532156 42.930016,-96.531862 42.930303,-96.52967 42.932636,-96.528843 42.933391,-96.527595 42.934253,-96.527457 42.934355,-96.527264 42.934517,-96.526437 42.935078,-96.52597 42.935337,-96.525889 42.935382,-96.525606 42.935525,-96.525312 42.935627,-96.525078 42.935684,-96.524458 42.935798,-96.524164 42.935816,-96.523771 42.935819,-96.523137 42.935756,-96.522854 42.935694,-96.522767 42.93566,-96.522177 42.935402,-96.52178 42.93519,-96.521704 42.935145,-96.521373 42.93488,-96.52132 42.93482,-96.521178 42.934592,-96.520722 42.933958,-96.52058 42.933691,-96.520543 42.933587,-96.52052 42.933445,-96.52053 42.932765,-96.520484 42.932517,-96.52047 42.932482,-96.52045 42.93245,-96.520324 42.932296,-96.520295 42.932267,-96.519884 42.932011,-96.519799 42.931976,-96.51929 42.931841,-96.518607 42.931776,-96.51846 42.931766,-96.518312 42.931762,-96.518245 42.931784,-96.518055 42.931822,-96.517604 42.931965,-96.517176 42.932142,-96.516868 42.932321,-96.516835 42.932347,-96.516685 42.932489,-96.516505 42.932743,-96.51647 42.93281,-96.516415 42.933058,-96.516342 42.933735,-96.516337 42.934343,-96.516347 42.934521,-96.51645 42.9352,-96.51658 42.935507,-96.516745 42.935767,-96.516819 42.93586,-96.517474 42.936293,-96.517725 42.936483,-96.517815 42.936551,-96.518193 42.93678,-96.518234 42.9368,-96.518784 42.936957,-96.519045 42.937057,-96.519123 42.937101,-96.519179 42.937159,-96.519858 42.937989,-96.520073 42.938464,-96.520162 42.938888,-96.520164 42.938924,-96.520089 42.939422,-96.519996 42.939736,-96.51987 42.939969,-96.519824 42.940033,-96.519587 42.940305,-96.519345 42.94053,-96.518928 42.940834,-96.518522 42.941093,-96.517844 42.941518,-96.517285 42.94181,-96.517049 42.941939,-96.516755 42.942129,-96.516503 42.942299,-96.515596 42.942955,-96.514857 42.943531,-96.514775 42.94362,-96.514675 42.943743,-96.514627 42.943845,-96.514575 42.943983,-96.514566 42.944018,-96.514567 42.944054,-96.514608 42.944338,-96.514604 42.944374,-96.514586 42.944444,-96.514529 42.944543,-96.514506 42.944575,-96.51431 42.944781,-96.514276 42.944807,-96.514198 42.944851,-96.513924 42.94493,-96.513875 42.944934,-96.513631 42.944913,-96.513535 42.944896,-96.513442 42.944874,-96.512798 42.94461,-96.512708 42.944582,-96.512431 42.944506,-96.512286 42.944485,-96.511606 42.944404,-96.511312 42.944391,-96.510683 42.944479,-96.510258 42.94457,-96.510079 42.94463,-96.509869 42.944723,-96.50949 42.944951,-96.509426 42.945005,-96.509288 42.945153,-96.509246 42.945217,-96.509138 42.945417,-96.509127 42.945452,-96.509117 42.945523,-96.509126 42.945774,-96.509136 42.945845,-96.509224 42.946012,-96.509247 42.946081,-96.50926 42.946188,-96.509242 42.94651,-96.509222 42.946616,-96.509187 42.94672,-96.50918 42.946729,-96.509081 42.946882,-96.508864 42.947121,-96.50845 42.947475,-96.508224 42.947709,-96.508047 42.947924,-96.508023 42.947971,-96.507931 42.948249,-96.507907 42.948391,-96.507907 42.948427,-96.507962 42.948962,-96.50797 42.948997,-96.508041 42.949168,-96.508082 42.949234,-96.508135 42.949294,-96.5082 42.949348,-96.508216 42.94938,-96.508234 42.949487,-96.508254 42.949556,-96.508343 42.949723,-96.508354 42.949758,-96.508431 42.950183,-96.508558 42.950566,-96.50863 42.950737,-96.509148 42.951818,-96.50921 42.951991,-96.509317 42.952413,-96.509325 42.952514,-96.509323 42.952729,-96.50929 42.952942,-96.509192 42.953256,-96.509031 42.953556,-96.508945 42.953685,-96.508919 42.953715,-96.508886 42.953742,-96.508709 42.953866,-96.508626 42.953901,-96.508449 42.953963,-96.508352 42.953976,-96.508303 42.953978,-96.507752 42.953969,-96.507466 42.953965,-96.506777 42.953987,-96.506729 42.953992,-96.506392 42.954046,-96.506061 42.954114,-96.505647 42.95423,-96.505288 42.954347,-96.504853 42.954514,-96.504731 42.954576,-96.504555 42.954701,-96.504203 42.955045,-96.50415 42.955105,-96.504011 42.955334,-96.50394 42.955467,-96.503759 42.95606,-96.503721 42.956237,-96.503684 42.956341,-96.503646 42.956407,-96.5035 42.956551,-96.503196 42.956785,-96.502744 42.957061,-96.502661 42.957099,-96.502357 42.957216,-96.502261 42.957232,-96.502068 42.957257,-96.501924 42.95728,-96.501317 42.957291,-96.50109 42.957296,-96.500975 42.957295,-96.500337 42.957334,-96.500091 42.957334,-96.499606 42.957393,-96.499559 42.957402,-96.499375 42.957454,-96.499293 42.957494,-96.49922 42.957541,-96.499186 42.957568,-96.498982 42.957769,-96.498963 42.957802,-96.498923 42.957924,-96.498894 42.958011,-96.498878 42.958153,-96.498888 42.95826,-96.498909 42.95833,-96.499012 42.958471,-96.499117 42.958615,-96.499175 42.958672,-96.499283 42.958745,-96.500287 42.959378,-96.500445 42.959492,-96.500531 42.959555,-96.500942 42.959811,-96.501324 42.96009,-96.501552 42.960323,-96.501655 42.960445,-96.501719 42.960542,-96.501839 42.960777,-96.501968 42.961269,-96.502176 42.962547,-96.502234 42.963007,-96.502266 42.963259,-96.50231 42.963974,-96.502314 42.965297,-96.502367 42.966084,-96.502512 42.966787,-96.502682 42.967605,-96.502719 42.967709,-96.502966 42.968214,-96.503005 42.968279,-96.504143 42.969707,-96.504419 42.970045,-96.504755 42.970562,-96.50516 42.971095,-96.50534 42.971265,-96.505375 42.97129,-96.505664 42.971427,-96.505846 42.971482,-96.506136 42.971523,-96.506283 42.971527,-96.506628 42.971522,-96.506726 42.971515,-96.506891 42.971491,-96.506918 42.971464,-96.506951 42.971434,-96.507126 42.971309,-96.507166 42.971287,-96.507246 42.971258,-96.507531 42.971202,-96.507776 42.971192,-96.507825 42.971197,-96.50792 42.971216,-96.507964 42.971233,-96.508044 42.971274,-96.508111 42.971319,-96.508436 42.971289,-96.508535 42.971284,-96.509027 42.971287,-96.509224 42.971292,-96.510422 42.971389,-96.510497 42.971395,-96.510643 42.971412,-96.511368 42.971514,-96.511654 42.971569,-96.512503 42.971753,-96.513242 42.971951,-96.513921 42.972163,-96.51405 42.972215,-96.514386 42.972381,-96.514863 42.972618,-96.515119 42.972706,-96.515351 42.972765,-96.515643 42.972794,-96.515689 42.972806,-96.515773 42.972844,-96.515958 42.972962,-96.515992 42.972988,-96.516174 42.973157,-96.516223 42.973219,-96.516241 42.973253,-96.516353 42.973526,-96.516474 42.974309,-96.516618 42.974834,-96.516657 42.974938,-96.516788 42.975245,-96.516925 42.975435,-96.517021 42.975516,-96.517301 42.975717,-96.517383 42.975757,-96.51785 42.975955,-96.518244 42.976102,-96.518627 42.976264,-96.518746 42.976327,-96.518861 42.976394,-96.519176 42.97662,-96.519307 42.976727,-96.519525 42.976921,-96.51958 42.97698,-96.520053 42.977607,-96.520118 42.977703,-96.520414 42.978271,-96.520564 42.978832,-96.520647 42.979257,-96.520755 42.979642,-96.52082 42.979923,-96.52084 42.980278,-96.520838 42.980314,-96.520828 42.980349,-96.520771 42.980448,-96.520741 42.980476,-96.520703 42.980499,-96.520539 42.980578,-96.5205 42.980601,-96.520431 42.980652,-96.520338 42.980651,-96.52029 42.980646,-96.520052 42.980598,-96.519829 42.980536,-96.519269 42.980383,-96.518881 42.980226,-96.518706 42.980161,-96.518326 42.980085,-96.518277 42.980082,-96.518032 42.980094,-96.517935 42.980106,-96.517795 42.98014,-96.517707 42.980172,-96.517666 42.980192,-96.517409 42.980358,-96.517283 42.980468,-96.517199 42.980556,-96.517041 42.980779,-96.516903 42.980927,-96.516831 42.98102,-96.516702 42.981328,-96.516688 42.981399,-96.516685 42.98166,-96.516683 42.981791,-96.5167 42.981898,-96.516823 42.982131,-96.517 42.982303,-96.517247 42.982477,-96.517282 42.982495,-96.517805 42.982769,-96.517922 42.982834,-96.518379 42.98321,-96.518496 42.983325,-96.518776 42.983661,-96.518798 42.983693,-96.518897 42.983895,-96.518914 42.984038,-96.518906 42.984323,-96.518847 42.984497,-96.518767 42.984665,-96.51861 42.984914,-96.518502 42.985086,-96.518472 42.985114,-96.518364 42.985187,-96.518323 42.985206,-96.518206 42.985272,-96.518113 42.985294,-96.518015 42.9853,-96.517769 42.985296,-96.517672 42.985282,-96.517518 42.985105,-96.517221 42.984866,-96.516622 42.984442,-96.516429 42.984331,-96.516299 42.98428,-96.51598 42.984185,-96.515932 42.984178,-96.515441 42.984143,-96.515342 42.984141,-96.51505 42.984174,-96.514909 42.984205,-96.514819 42.984234,-96.514192 42.984515,-96.513569 42.984865,-96.512657 42.98541,-96.51258 42.985445,-96.512574 42.985449,-96.51243 42.985596,-96.512382 42.985658,-96.512249 42.985849,-96.51198 42.986349,-96.511841 42.986765,-96.51182 42.986871,-96.511832 42.987121,-96.51185 42.987191,-96.512258 42.987958,-96.512366 42.988234,-96.512385 42.98834,-96.512392 42.988626,-96.512364 42.988839,-96.512355 42.988874,-96.512253 42.989076,-96.512159 42.989201,-96.512098 42.989257,-96.51147 42.989759,-96.511406 42.989814,-96.511324 42.989903,-96.51119 42.990133,-96.511176 42.990167,-96.511169 42.990203,-96.511163 42.99031,-96.511182 42.990416,-96.511244 42.990551,-96.511319 42.990644,-96.51135 42.990672,-96.511604 42.990841,-96.51168 42.990886,-96.51176 42.990927,-96.511969 42.991021,-96.512233 42.991117,-96.512322 42.991145,-96.512408 42.99118,-96.512521 42.991248,-96.512562 42.991268,-96.51267 42.99134,-96.512701 42.991368,-96.5128 42.991491,-96.51285 42.991592,-96.512857 42.991627,-96.512834 42.991912,-96.512732 42.992189,-96.512545 42.992409,-96.51249 42.992468,-96.511591 42.993363,-96.511325 42.993665,-96.510564 42.994585,-96.51021 42.994928,-96.50986 42.995228,-96.50951 42.995479,-96.509013 42.995826,-96.50823 42.996259,-96.507866 42.996443,-96.507824 42.996461,-96.507511 42.996565,-96.507373 42.996604,-96.506796 42.996696,-96.50592 42.996793,-96.505662 42.996797,-96.505034 42.996808,-96.504246 42.996804,-96.503856 42.996843,-96.503139 42.996973,-96.502491 42.997144,-96.502215 42.997221,-96.50208 42.997265,-96.501908 42.997333,-96.501161 42.997679,-96.501069 42.997705,-96.50036 42.997857,-96.500239 42.997867,-96.500064 42.997907,-96.500007 42.99792,-96.499863 42.997954,-96.499838 42.997957,-96.499781 42.997964,-96.499523 42.997996,-96.499474 42.997995,-96.499387 42.997989,-96.498818 42.998003,-96.498436 42.998081,-96.498427 42.998079,-96.49831 42.998051,-96.498184 42.998029,-96.498152 42.998033,-96.497866 42.998086,-96.497774 42.998112,-96.497359 42.998276,-96.497342 42.998283,-96.497061 42.998428,-96.496823 42.998609,-96.496757 42.998705,-96.496709 42.998807,-96.496639 42.998901,-96.496619 42.998934,-96.496606 42.998968,-96.496604 42.999039,-96.496638 42.999144,-96.496873 42.999537,-96.49693 42.999756,-96.496947 42.999818,-96.496966 42.999996,-96.496961 43.000068,-96.496907 43.000242,-96.496814 43.000408,-96.49679 43.000439,-96.496643 43.000583,-96.496571 43.000632,-96.495737 43.000941,-96.49561 43.000997,-96.495566 43.001012,-96.495243 43.0011,-96.495072 43.001171,-96.494876 43.001279,-96.494777 43.001359,-96.494588 43.001568,-96.494488 43.001731,-96.494443 43.00187,-96.494416 43.002084,-96.494423 43.002156,-96.494445 43.002225,-96.494534 43.002392,-96.494582 43.00253,-96.494665 43.002883,-96.494675 43.00299,-96.494655 43.003598,-96.494644 43.003633,-96.494468 43.003956,-96.494446 43.003998,-96.494422 43.00403,-96.494393 43.004058,-96.49426 43.004164,-96.494109 43.004256,-96.494074 43.004281,-96.493655 43.004679,-96.493444 43.00492,-96.493056 43.005244,-96.493 43.00532,-96.492811 43.00565,-96.492713 43.006,-96.492685 43.006393,-96.492589 43.006853,-96.492487 43.007166,-96.492206 43.007814,-96.492189 43.007885,-96.492174 43.008171,-96.4921 43.008669,-96.492049 43.008868,-96.492029 43.00895,-96.491991 43.009557,-96.491968 43.009699,-96.491961 43.009771,-96.491987 43.009876,-96.492002 43.009983,-96.491981 43.010197,-96.492116 43.010541,-96.492159 43.010605,-96.492239 43.010696,-96.492564 43.010916,-96.493072 43.011197,-96.493185 43.011266,-96.493421 43.011448,-96.493497 43.01154,-96.493711 43.011901,-96.493764 43.012002,-96.493791 43.01207,-96.493821 43.012248,-96.493874 43.012386,-96.493879 43.012421,-96.493902 43.012994,-96.493929 43.013136,-96.494113 43.013729,-96.494166 43.013829,-96.494762 43.01485,-96.495014 43.015115,-96.495281 43.015456,-96.495639 43.015841,-96.495672 43.015869,-96.495744 43.015917,-96.495935 43.01603,-96.496482 43.016272,-96.496526 43.016288,-96.497019 43.016181,-96.497308 43.016136,-96.497894 43.016082,-96.498535 43.016075,-96.498569 43.016077,-96.498583 43.016079,-96.498678 43.0161,-96.499087 43.016224,-96.499292 43.016323,-96.499358 43.016376,-96.49948 43.016488,-96.499532 43.016548,-96.499638 43.016787,-96.499646 43.016822,-96.499649 43.016893,-96.499594 43.017104,-96.499575 43.017138,-96.499551 43.017169,-96.499441 43.017287,-96.499153 43.017532,-96.49884 43.017896,-96.498775 43.017993,-96.498759 43.018027,-96.498709 43.018201,-96.498688 43.018415,-96.49869 43.018451,-96.498709 43.018557,-96.498799 43.018762,-96.498818 43.018795,-96.498861 43.018851,-96.49901 43.019045,-96.49904 43.019073,-96.499256 43.019219,-96.499452 43.019327,-96.499574 43.019388,-96.499619 43.019403,-96.500093 43.019499,-96.500289 43.019515,-96.500782 43.019528,-96.501125 43.019556,-96.501899 43.019664,-96.501995 43.019681,-96.502644 43.01985,-96.50318 43.02003,-96.503937 43.020366,-96.50399 43.0204,-96.50451 43.020781,-96.505049 43.021272,-96.505493 43.021676,-96.505637 43.021821,-96.505899 43.022034,-96.506122 43.02227,-96.506188 43.022323,-96.506713 43.022648,-96.506793 43.022689,-96.507349 43.022922,-96.508032 43.023332,-96.50811 43.023375,-96.508435 43.023536,-96.508522 43.02357,-96.509247 43.023797,-96.509594 43.023933,-96.510209 43.024229,-96.510318 43.024301,-96.510523 43.024456,-96.510553 43.024484,-96.51076 43.024728,-96.510805 43.024792,-96.511528 43.025516,-96.511698 43.025734,-96.512032 43.026212,-96.512435 43.026865,-96.512467 43.026933,-96.512579 43.027207,-96.51269 43.027444,-96.513173 43.028267,-96.513208 43.028334,-96.513534 43.029047,-96.51359 43.029185,-96.513754 43.029522,-96.513952 43.030075,-96.513958 43.030111,-96.513971 43.030324,-96.513968 43.03036,-96.513927 43.030536,-96.513846 43.030679,-96.513822 43.03071,-96.513732 43.030796,-96.513507 43.030935,-96.513467 43.030955,-96.512894 43.031165,-96.512534 43.031281,-96.511741 43.031569,-96.511694 43.031581,-96.511071 43.031685,-96.511026 43.0317,-96.510984 43.031719,-96.510948 43.031743,-96.510824 43.031855,-96.510666 43.032036,-96.510623 43.032101,-96.510581 43.032203,-96.510416 43.032763,-96.51006 43.034207,-96.509942 43.034516,-96.509731 43.034956,-96.509626 43.035194,-96.509624 43.035199,-96.509498 43.035686,-96.509378 43.036033,-96.509267 43.036455,-96.509138 43.036763,-96.509134 43.036798,-96.509169 43.037011,-96.509191 43.037547,-96.509252 43.037794,-96.509291 43.037898,-96.509308 43.037931,-96.509333 43.037962,-96.509525 43.038125,-96.509596 43.038175,-96.509918 43.03834,-96.510181 43.038438,-96.5103 43.038501,-96.510336 43.038526,-96.510495 43.038663,-96.5106 43.038784,-96.510746 43.03905,-96.510772 43.039119,-96.510831 43.039329,-96.510886 43.039466,-96.510998 43.039665,-96.511048 43.039727,-96.51108 43.039754,-96.511487 43.040013,-96.51165 43.040094,-96.511881 43.040228,-96.512146 43.04044,-96.512222 43.040486,-96.512317 43.040465,-96.512454 43.040426,-96.512502 43.040416,-96.512599 43.040407,-96.51309 43.040377,-96.513233 43.04035,-96.513785 43.040197,-96.513921 43.040155,-96.514052 43.040105,-96.514257 43.039992,-96.514643 43.039782,-96.514904 43.039681,-96.515878 43.039197,-96.516188 43.03902,-96.516307 43.038956,-96.516945 43.038684,-96.517209 43.038587,-96.517849 43.038399,-96.518203 43.038274,\ +-96.518522 43.038179,-96.519511 43.037856,-96.5197 43.037816,-96.519941 43.03778,-96.520086 43.037762,-96.520153 43.037765,-96.520874 43.0378,-96.520922 43.037808,-96.521087 43.037846,-96.521335 43.038089,-96.521519 43.038781,-96.521492 43.038889,-96.521302 43.03966,-96.520887 43.040138,-96.520254 43.04024,-96.520201 43.040249,-96.519185 43.040178,-96.518393 43.040105,-96.51799 43.040182,-96.517634 43.040422,-96.517086 43.041064,-96.51655 43.041271,-96.516237 43.041359,-96.515759 43.041383,-96.51531 43.041233,-96.514935 43.041039,-96.514636 43.040943,-96.514573 43.040936,-96.514125 43.040892,-96.513745 43.040912,-96.513393 43.040967,-96.513272 43.040982,-96.51308 43.040999,-96.512735 43.041049,-96.512628 43.041065,-96.512521 43.04108,-96.512553 43.041167,-96.512595 43.041379,-96.512636 43.041481,-96.512705 43.04169,-96.512775 43.041824,-96.5129 43.041978,-96.513015 43.042094,-96.513357 43.042351,-96.513444 43.042438,-96.513663 43.042676,-96.513807 43.04279,-96.513996 43.04294,-96.514325 43.043338,-96.514523 43.043666,-96.514688 43.04404,-96.514818 43.044495,-96.514833 43.044638,-96.514819 43.045103,-96.514812 43.045174,-96.51476 43.045385,-96.514747 43.04542,-96.514588 43.04572,-96.514093 43.0465,-96.513681 43.04703,-96.513604 43.047122,-96.513521 43.047211,-96.513197 43.047526,-96.512844 43.047825,-96.512486 43.04821,-96.51244 43.048245,-96.512283 43.048366,-96.512256 43.048396,-96.512092 43.048617,-96.511572 43.049093,-96.511302 43.049392,-96.510851 43.04977,-96.510523 43.049913,-96.510393 43.049964,-96.510069 43.050049,-96.509882 43.050094,-96.509643 43.050137,-96.509547 43.050151,-96.509154 43.050181,-96.508859 43.050187,-96.50881 43.050183,-96.508347 43.05006,-96.508165 43.050006,-96.507608 43.049773,-96.507566 43.049756,-96.506355 43.049138,-96.506267 43.049105,-96.506035 43.049045,-96.505317 43.048915,-96.504459 43.048749,-96.504363 43.048735,-96.503824 43.048687,-96.503677 43.048677,-96.503332 43.048667,-96.501804 43.048658,-96.501509 43.04868,-96.500978 43.048756,-96.500882 43.048774,-96.500837 43.048788,-96.500493 43.048927,-96.500484 43.048966,-96.500458 43.049035,-96.500439 43.049068,-96.500368 43.049162,-96.50029 43.049332,-96.500115 43.049547,-96.499993 43.049659,-96.499928 43.049713,-96.499791 43.049815,-96.499557 43.049946,-96.499514 43.049965,-96.499288 43.050034,-96.499241 43.050045,-96.498804 43.0501,-96.498226 43.050147,-96.498216 43.050148,-96.496737 43.050184,-96.496048 43.050219,-96.495607 43.050256,-96.495126 43.050334,-96.494868 43.050395,-96.494564 43.050466,-96.493995 43.050584,-96.493752 43.050614,-96.493703 43.050621,-96.493556 43.050621,-96.493164 43.050583,-96.492277 43.050603,-96.492032 43.050621,-96.491595 43.05068,-96.491306 43.050726,-96.49093 43.050812,-96.490522 43.05094,-96.490389 43.05099,-96.489912 43.051172,-96.489581 43.051329,-96.489341 43.05143,-96.48924 43.051472,-96.489201 43.051494,-96.489031 43.051754,-96.488861 43.052014,-96.488825 43.052363,-96.488832 43.052434,-96.48894 43.052734,-96.488957 43.05278,-96.489009 43.05288,-96.489022 43.052914,-96.489035 43.053021,-96.48903 43.053093,-96.489006 43.053162,-96.489001 43.053197,-96.489009 43.05334,-96.488995 43.053482,-96.488967 43.05366,-96.488949 43.05373,-96.488815 43.053961,-96.488727 43.054165,-96.488641 43.054253,-96.488511 43.05436,-96.488362 43.054392,-96.488274 43.054424,-96.488234 43.054445,-96.488092 43.054544,-96.487994 43.054624,-96.487966 43.054654,-96.487877 43.054781,-96.487845 43.054849,-96.487836 43.054884,-96.487686 43.05501,-96.48753 43.055192,-96.487466 43.055247,-96.487408 43.055304,-96.487136 43.055602,-96.486967 43.055779,-96.486833 43.055882,-96.486686 43.055894,-96.486591 43.055913,-96.486546 43.055929,-96.486418 43.055984,-96.486165 43.056093,-96.485825 43.056298,-96.485604 43.056491,-96.485232 43.056725,-96.485198 43.056751,-96.485148 43.056799,-96.485109 43.056836,-96.485038 43.056931,-96.484905 43.0572,-96.484688 43.05748,-96.484659 43.057508,-96.484557 43.057586,-96.484478 43.057629,-96.484391 43.057662,-96.4843 43.057789,-96.484049 43.05801,-96.483872 43.058134,-96.483829 43.058147,-96.483682 43.058143,-96.483632 43.058146,-96.483584 43.058154,-96.483227 43.058275,-96.48321 43.058283,-96.482788 43.058505,-96.482526 43.058719,-96.482313 43.058916,-96.481804 43.059444,-96.481529 43.059697,-96.481463 43.05975,-96.481392 43.0598,-96.480787 43.060167,-96.480472 43.060339,-96.480255 43.060424,-96.479984 43.060509,-96.479795 43.060551,-96.47975 43.060565,-96.479365 43.060725,-96.478548 43.061124,-96.47808 43.061493,-96.477913 43.06167,-96.477815 43.061834,-96.477766 43.061896,-96.477611 43.062035,-96.477474 43.062225,-96.477417 43.062283,-96.477317 43.062362,-96.476966 43.062559,-96.476953 43.062567,-96.476822 43.062657,-96.47674 43.062697,-96.476429 43.062805,-96.475717 43.06301,-96.475405 43.063117,-96.475268 43.063157,-96.475173 43.063179,-96.474503 43.063298,-96.474339 43.063355,-96.473872 43.063518,-96.473846 43.06352,-96.473673 43.063534,-96.473377 43.063645,-96.473191 43.063692,-96.473048 43.063717,-96.472949 43.063721,-96.472753 43.063711,-96.472657 43.063694,-96.47261 43.063681,-96.472435 43.063615,-96.472316 43.063552,-96.472186 43.063463,-96.471455 43.062963,-96.471322 43.062857,-96.470832 43.062504,-96.470641 43.062391,-96.470465 43.062327,-96.4701 43.062219,-96.469298 43.06204,-96.469011 43.06199,-96.468768 43.061959,-96.468326 43.061933,-96.467934 43.061966,-96.46786 43.061962,-96.467491 43.061946,-96.467442 43.061947,-96.467345 43.06196,-96.466704 43.062145,-96.466515 43.062186,-96.466325 43.062231,-96.465859 43.062343,-96.465518 43.062377,-96.465421 43.06239,-96.46501 43.062511,-96.46483 43.06257,-96.464777 43.062556,-96.46468 43.062541,-96.464581 43.062541,-96.464533 43.062548,-96.464167 43.062652,-96.464124 43.062669,-96.463973 43.062761,-96.463939 43.062787,-96.463881 43.062845,-96.463836 43.062909,-96.463184 43.063141,-96.463099 43.063177,-96.462895 43.063277,-96.462592 43.06346,-96.462472 43.063523,-96.461401 43.063963,-96.460958 43.064214,-96.460742 43.064337,-96.460723 43.064392,-96.460689 43.064459,-96.460509 43.064713,-96.46037 43.065018,-96.460353 43.065124,-96.460341 43.065159,-96.460303 43.065225,-96.460394 43.065419,-96.460546 43.065602,-96.460667 43.065836,-96.460675 43.065871,-96.460674 43.065907,-96.460644 43.066084,-96.460572 43.066292,-96.46055 43.066324,-96.460522 43.066354,-96.460323 43.066459,-96.459851 43.066653,-96.459645 43.066751,-96.459559 43.066787,-96.459248 43.066962,-96.459167 43.067003,-96.458882 43.067061,-96.458618 43.067157,-96.458577 43.067177,-96.458405 43.067305,-96.458305 43.067428,-96.458233 43.067561,-96.458185 43.067773,-96.458185 43.067809,-96.458202 43.067951,-96.458234 43.068056,-96.458253 43.068089,-96.458324 43.068183,-96.458406 43.068272,-96.459016 43.068786,-96.459282 43.069044,-96.459307 43.069075,-96.459392 43.069242,-96.459447 43.069489,-96.459445 43.069561,-96.459403 43.069809,-96.459387 43.069843,-96.459337 43.069904,-96.459298 43.069926,-96.458921 43.070096,-96.458842 43.070139,-96.458699 43.070238,-96.458573 43.070264,-96.458483 43.070293,-96.458095 43.07045,-96.457627 43.070766,-96.457566 43.070822,-96.457448 43.070979,-96.457429 43.071012,-96.457375 43.071186,-96.457367 43.071257,-96.457349 43.071757,-96.457358 43.071828,-96.457356 43.072043,-96.457294 43.072252,-96.457213 43.072421,-96.457107 43.072583,-96.457079 43.072612,-96.456942 43.072715,-96.456859 43.072754,-96.456771 43.072838,-96.456669 43.072916,-96.456639 43.072945,-96.456429 43.073187,-96.456398 43.073215,-96.455903 43.073614,-96.455722 43.073908,-96.455677 43.07401,-96.455555 43.074319,-96.45547 43.074743,-96.455455 43.074922,-96.455444 43.074955,-96.45541 43.074982,-96.45536 43.075043,-96.455343 43.075077,-96.455332 43.075112,-96.455328 43.075148,-96.455324 43.075469,-96.455437 43.075963,-96.455454 43.076321,-96.455543 43.076636,-96.455648 43.077203,-96.455671 43.077367,-96.455718 43.077702,-96.455725 43.078167,-96.455719 43.078524,-96.455761 43.079526,-96.455843 43.080059,-96.455798 43.080845,-96.455786 43.080916,-96.455726 43.081127,-96.455654 43.081298,-96.455519 43.081528,-96.455494 43.081558,-96.455408 43.081593,-96.455329 43.081636,-96.455294 43.081662,-96.455206 43.081748,-96.454969 43.081929,-96.454478 43.082421,-96.454407 43.082471,-96.454376 43.082498,-96.454336 43.082555,-96.454259 43.082709,-96.454224 43.08278,-96.454228 43.082851,-96.454223 43.082887,-96.454204 43.082957,-96.454187 43.083063,-96.454155 43.083209,-96.454142 43.083274,-96.45413 43.08338,-96.454118 43.08348,-96.454083 43.083782,-96.45408 43.08381,-96.45408 43.083845,-96.454084 43.083883,-96.454105 43.084059,-96.454126 43.084165,-96.454229 43.084515,-96.454253 43.08462,-96.454266 43.084763,-96.45431 43.085549,-96.454359 43.085976,-96.454401 43.086152,-96.454603 43.086777,-96.454705 43.08709,-96.454779 43.087261,-96.455139 43.087886,-96.455155 43.08792,-96.455432 43.088258,-96.455467 43.088283,-96.455634 43.088359,-96.455948 43.088463,-96.456187 43.088507,-96.456529 43.088539,-96.456575 43.088551,-96.456972 43.088696,-96.457156 43.088746,-96.457381 43.088819,-96.457893 43.088933,-96.458622 43.089016,-96.45872 43.089021,-96.459065 43.089028,-96.459552 43.089082,-96.460753 43.089281,-96.461588 43.089493,-96.461682 43.089512,-96.462274 43.089526,-96.462428 43.089565,-96.462506 43.089586,-96.462672 43.089662,-96.463028 43.089908,-96.463083 43.089967,-96.463168 43.090096,-96.463184 43.090152,-96.463189 43.090331,-96.46316 43.090544,-96.463134 43.09065,-96.463121 43.090684,-96.462777 43.091275,-96.462644 43.091465,-96.462539 43.091703,-96.462535 43.091739,-96.462569 43.091952,-96.462521 43.092116,-96.462484 43.092183,-96.462434 43.092393,-96.462422 43.092464,-96.462407 43.092643,-96.46232 43.092885,-96.462268 43.092985,-96.462243 43.093016,-96.462035 43.093215,-96.461941 43.093297,-96.461752 43.093411,-96.461466 43.093551,-96.461326 43.093652,-96.461264 43.093707,-96.461218 43.09377,-96.461189 43.093838,-96.461095 43.094226,-96.461072 43.094295,-96.461042 43.094363,-96.460935 43.094563,-96.460797 43.094792,-96.460729 43.094887,-96.460498 43.095118,-96.460242 43.095284,-96.460158 43.095321,-96.45987 43.095413,-96.459706 43.095466,-96.459659 43.095477,-96.459562 43.095486,-96.459219 43.095505,-96.458977 43.09554,-96.458782 43.095552,-96.458586 43.095539,-96.458537 43.095541,-96.458244 43.095573,-96.458195 43.095575,-96.458146 43.095572,-96.457907 43.095529,-96.45781 43.095518,-96.457762 43.095523,-96.457483 43.095592,-96.457391 43.095618,-96.45726 43.095667,-96.457053 43.095763,-96.456911 43.095861,-96.456846 43.095915,-96.456798 43.095978,-96.456777 43.096048,-96.456802 43.096369,-96.456819 43.096439,-96.456851 43.096507,-96.457071 43.096827,-96.457115 43.096929,-96.457149 43.096995,-96.457173 43.097064,-96.457166 43.097314,-96.457168 43.097386,-96.457132 43.097489,-96.45705 43.097619,-96.457023 43.097647,-96.456741 43.097789,-96.456697 43.097805,-96.456508 43.097843,-96.456264 43.097864,-96.456215 43.097859,-96.455976 43.097813,-96.455714 43.097714,-96.455381 43.09756,-96.455249 43.097513,-96.455101 43.097501,-96.454855 43.097502,-96.454806 43.097499,-96.454661 43.097476,-96.454492 43.097404,-96.454448 43.097388,-96.454401 43.097377,-96.45416 43.097339,-96.454063 43.097331,-96.453868 43.097355,-96.453591 43.097431,-96.453372 43.097512,-96.453212 43.097596,-96.452696 43.097926,-96.452564 43.098077,-96.452446 43.098139,-96.45241 43.098164,-96.45238 43.098193,-96.45236 43.098225,-96.452314 43.098364,-96.452314 43.0984,-96.452303 43.098471,-96.452311 43.098542,-96.452357 43.098569,-96.452706 43.0987,-96.452946 43.098825,-96.452974 43.098855,-96.453017 43.098919,-96.453115 43.099195,-96.453117 43.099266,-96.45309 43.099515,-96.453021 43.099796,-96.452901 43.100143,-96.452673 43.100575,-96.452567 43.100696,-96.452462 43.100771,-96.45242 43.10079,-96.452371 43.100789,-96.452177 43.100763,-96.45213 43.100753,-96.452086 43.100736,-96.45201 43.100691,-96.451941 43.10064,-96.451832 43.100568,-96.451801 43.100554,-96.451706 43.100512,-96.451617 43.100483,-96.451523 43.100464,-96.451328 43.100484,-96.45123 43.10049,-96.451134 43.100502,-96.450863 43.100586,-96.450576 43.100723,-96.450403 43.10085,-96.450346 43.100908,-96.450254 43.101074,-96.450143 43.101385,-96.450115 43.10149,-96.450079 43.101775,-96.450072 43.101882,-96.450093 43.10206,-96.450104 43.102095,-96.450158 43.102195,-96.450483 43.102595,-96.450576 43.10276,-96.450623 43.102972,-96.450672 43.103399,-96.450601 43.103679,-96.45055 43.103754,-96.450536 43.103775,-96.450408 43.103883,-96.450396 43.103889,-96.45037 43.103906,-96.450161 43.104001,-96.449928 43.104058,-96.449813 43.104074,-96.44961 43.104105,-96.449445 43.10413,-96.449113 43.104196,-96.448803 43.104306,-96.448506 43.104433,-96.448296 43.104584,-96.447994 43.104866,-96.447809 43.105077,-96.447694 43.105145,-96.447652 43.105164,-96.447294 43.105283,-96.447214 43.105324,-96.447046 43.105455,-96.446992 43.105514,-96.44697 43.105546,-96.446944 43.105615,-96.446924 43.105685,-96.446911 43.105828,-96.446928 43.105861,-96.446997 43.105956,-96.447011 43.105991,-96.44707 43.106047,-96.447217 43.106142,-96.447248 43.10617,-96.447355 43.10629,-96.447404 43.106352,-96.447452 43.106454,-96.447358 43.106584,-96.447327 43.106652,-96.447306 43.10683,-96.447231 43.107291,-96.447178 43.107502,-96.447104 43.107709,-96.447085 43.107742,-96.446848 43.108014,-96.446788 43.108112,-96.446774 43.108146,-96.44654 43.108987,-96.446518 43.109093,-96.446495 43.10927,-96.446457 43.109374,-96.446374 43.109542,-96.446367 43.109551,-96.446268 43.109703,-96.446195 43.109796,-96.44604 43.109979,-96.445943 43.110059,-96.445764 43.110182,-96.445725 43.110204,-96.445596 43.110257,-96.44546 43.110299,-96.445413 43.11031,-96.444489 43.110457,-96.44445 43.110464,-96.444125 43.110547,-96.444035 43.110575,-96.443882 43.110666,-96.443784 43.110745,-96.443514 43.111043,-96.443368 43.11123,-96.443037 43.111495,-96.442962 43.111541,-96.442844 43.111605,-96.442453 43.111759,-96.442165 43.111897,-96.441006 43.112505,-96.440932 43.112552,-96.440863 43.112603,-96.439832 43.113423,-96.439668 43.113601,-96.439644 43.113632,-96.438959 43.1147,-96.438916 43.114803,-96.438905 43.114905,-96.43894 43.116047,-96.438936 43.116083,-96.438795 43.116573,-96.438683 43.116772,-96.438492 43.117215,-96.438395 43.117339,-96.438292 43.11746,-96.438245 43.117523,-96.438143 43.117645,-96.437956 43.117896,-96.43727 43.118762,-96.436988 43.119137,-96.43684 43.119361,-96.436567 43.119818,-96.436515 43.119956,-96.436472 43.120166,-96.436473 43.120202,-96.436486 43.120272,-96.436499 43.120414,-96.436555 43.120661,-96.436634 43.120867,-96.436814 43.121161,-96.436951 43.121264,-96.436989 43.121287,-96.437031 43.121305,-96.43767 43.121493,-96.438083 43.12161,-96.438407 43.121697,-96.438689 43.12176,-96.439059 43.121857,-96.439239 43.121916,-96.43932 43.121956,-96.439864 43.122194,-96.440158 43.122434,-96.440268 43.122553,-96.440311 43.122617,-96.440454 43.122996,-96.440598 43.123262,-96.440659 43.123359,-96.441005 43.123834,-96.441429 43.124271,-96.441529 43.12442,-96.441581 43.124496,-96.44165 43.124702,-96.441685 43.124879,-96.441699 43.124986,-96.441658 43.125305,-96.441646 43.12534,-96.441451 43.125668,-96.441405 43.12577,-96.44136 43.125834,-96.441353 43.125866,-96.44136 43.125938,-96.44134 43.126044,-96.441326 43.126078,-96.441059 43.126459,-96.440956 43.126581,-96.440926 43.126609,-96.440743 43.126728,-96.440586 43.126814,-96.440419 43.126945,-96.440233 43.127155,-96.440163 43.127288,-96.440122 43.127391,-96.44013 43.127426,-96.440124 43.12782,-96.440136 43.12807,-96.440191 43.128317,-96.440352 43.128615,-96.44043 43.128706,-96.440581 43.128848,-96.440622 43.128868,-96.440798 43.128933,-96.440845 43.128941,-96.440894 43.128932,-96.440939 43.128918,-96.441215 43.128768,-96.441247 43.128741,-96.441299 43.12868,-96.441329 43.128652,-96.441366 43.128628,-96.441407 43.128608,-96.441709 43.128487,-96.441754 43.128473,-96.441946 43.12844,-96.441995 43.128436,-96.442241 43.128435,-96.442291 43.128439,-96.442484 43.12847,-96.44253 43.128482,-96.442615 43.128519,-96.44269 43.128565,-96.44276 43.128615,-96.44278 43.128648,-96.442906 43.128957,-96.442978 43.129201,-96.442994 43.129271,-96.443022 43.129449,-96.443034 43.129622,-96.443051 43.129841,-96.443046 43.129948,-96.443057 43.130447,-96.443242 43.131364,-96.443236 43.131471,-96.443229 43.131507,-96.443186 43.131968,-96.443096 43.1325,-96.443088 43.132751,-96.443084 43.132928,-96.443098 43.133106,-96.443152 43.133425,-96.443159 43.133496,-96.443128 43.133601,-96.443134 43.133636,-96.443154 43.133669,-96.443185 43.133697,-96.443236 43.133756,-96.443289 43.133817,-96.443324 43.133842,-96.443448 43.1339,-96.443608 43.134115,-96.443646 43.134181,-96.443725 43.13435,-96.443747 43.134382,-96.444009 43.134685,-96.444077 43.134742,-96.444524 43.13512,-96.444858 43.135429,-96.445018 43.13565,-96.44522 43.136015,-96.445409 43.136305,-96.445458 43.136367,-96.445599 43.136513,-96.445678 43.136556,-96.445787 43.136629,-96.445828 43.136649,-96.445875 43.136658,-96.445923 43.136653,-96.446111 43.13661,-96.446198 43.136576,-96.446239 43.136556,-96.446638 43.136191,-96.446785 43.136095,-96.446939 43.136006,-96.446952 43.135999,-96.446981 43.135987,-96.447114 43.13594,-96.447256 43.135908,-96.447304 43.135902,-96.44755 43.135886,-96.447648 43.13589,-96.44798 43.135959,-96.44807 43.135989,-96.448243 43.136022,-96.448356 43.136044,-96.448394 43.136066,-96.448501 43.136141,-96.448562 43.136197,-96.448587 43.136228,-96.448624 43.136294,-96.448706 43.136462,-96.448704 43.136496,-96.44867 43.136601,-96.44849 43.136896,-96.448462 43.136925,-96.448389 43.136973,-96.448011 43.13714,-96.447873 43.137178,-96.447701 43.137246,-96.447386 43.137377,-96.447362 43.137388,-96.447287 43.137434,-96.447157 43.137542,-96.447131 43.137573,-96.447091 43.137637,-96.447056 43.137704,-96.447008 43.137879,-96.447009 43.138093,-96.44708 43.138338,-96.447095 43.138371,-96.447398 43.13874,-96.447533 43.13889,-96.447667 43.138995,-96.448511 43.139543,-96.448577 43.139596,-96.448606 43.139625,-96.448823 43.139864,-96.448991 43.140083,-96.449252 43.140508,-96.44976 43.141242,-96.450057 43.141811,-96.450237 43.142143,-96.450352 43.1423,-96.450381 43.142329,-96.45043 43.14239,-96.450598 43.142521,-96.450956 43.14264,-96.451003 43.142652,-96.451021 43.142655,-96.451099 43.142669,-96.451246 43.14268,-96.451295 43.142677,-96.451917 43.142562,-96.452553 43.142502,-96.453145 43.142491,-96.453381 43.142508,-96.453783 43.142538,-96.453879 43.142553,-96.453964 43.142588,-96.454046 43.142628,-96.45408 43.142654,-96.454138 43.142711,-96.454212 43.142803,-96.454272 43.1429,-96.454535 43.143511,-96.454568 43.143577,-96.45465 43.143707,-96.454753 43.143828,-96.454951 43.143988,-96.455147 43.144096,-96.455231 43.144133,-96.455512 43.144199,-96.455754 43.144235,-96.455852 43.144239,-96.45595 43.144231,-96.456147 43.144224,-96.456245 43.144217,-96.45639 43.144196,-96.456898 43.144062,-96.457533 43.143865,-96.457963 43.143692,-96.458009 43.143668,-96.458328 43.14351,-96.458678 43.143379,-96.458776 43.143369,-96.459318 43.14337,-96.459466 43.143375,-96.459756 43.143419,-96.460039 43.143481,-96.460176 43.143522,-96.460349 43.14359,-96.46043 43.14363,-96.460468 43.143653,-96.460677 43.143804,-96.460708 43.143832,-96.460856 43.144018,-96.460868 43.144075,-96.460858 43.14411,-96.460824 43.144177,-96.460564 43.144522,-96.460544 43.144555,-96.460414 43.144863,-96.460392 43.144933,-96.460382 43.14504,-96.460414 43.145397,-96.460425 43.145431,-96.460451 43.145572,-96.460471 43.145605,-96.4605 43.145634,-96.460575 43.14568,-96.460596 43.14575,-96.460601 43.145821,-96.4606 43.146035,-96.460573 43.146103,-96.460551 43.146135,-96.460405 43.146279,-96.46037 43.146305,-96.46033 43.146326,-96.460192 43.146364,-96.459557 43.14643,-96.459325 43.146491,-96.459208 43.146557,-96.45918 43.146586,-96.459046 43.146778,-96.45902 43.146846,-96.459003 43.146953,-96.459012 43.147024,-96.459036 43.14713,-96.459093 43.147266,-96.459141 43.147328,-96.459248 43.147488,-96.459432 43.147656,-96.459468 43.14768,-96.459511 43.147698,-96.459649 43.147737,-96.459795 43.147753,-96.46019 43.147766,-96.460479 43.147812,-96.460526 43.147823,-96.460658 43.147871,-96.460787 43.147923,-96.460858 43.147973,-96.460887 43.148002,-96.460935 43.148064,-96.460992 43.148163,-96.461069 43.148444,-96.461093 43.148513,-96.461126 43.148579,-96.46118 43.148715,-96.461221 43.14878,-96.461268 43.148842,-96.461286 43.148874,-96.461312 43.148905,-96.461435 43.149015,-96.461514 43.149058,-96.461781 43.149151,-96.461878 43.14916,-96.462075 43.149171,-96.462172 43.14918,-96.462271 43.149177,-96.462547 43.1491,-96.462675 43.149047,-96.462715 43.149025,-96.462785 43.148975,-96.462876 43.148891,-96.46305 43.148634,-96.463052 43.148599,-96.463046 43.148527,-96.463027 43.148473,-96.462939 43.148346,-96.462753 43.148179,-96.46274 43.148144,-96.462737 43.148109,-96.462761 43.147967,-96.462793 43.1479,-96.462814 43.147867,-96.463 43.147701,-96.463074 43.147653,-96.463281 43.147557,-96.463813 43.147368,-96.463859 43.147356,-96.463908 43.14735,-96.464252 43.147329,-96.464645 43.147333,-96.464741 43.147347,-96.465102 43.147458,-96.465144 43.147475,-96.465182 43.147499,-96.465296 43.147614,-96.465357 43.147712,-96.465369 43.147746,-96.465388 43.147853,-96.465387 43.14788,-96.465385 43.148067,-96.465294 43.148599,-96.465298 43.14867,-96.465316 43.148813,-96.465336 43.148919,-96.465459 43.149153,-96.46554 43.149242,-96.466422 43.150013,-96.466482 43.15007,-96.467187 43.150801,-96.467452 43.151014,-96.467867 43.151251,-96.46829 43.151552,-96.468459 43.151823,-96.468482 43.151854,-96.468499 43.151887,-96.468533 43.151984,-96.468559 43.152061,-96.468569 43.152167,-96.468557 43.152237,-96.468545 43.152272,-96.468411 43.152422,-96.46825 43.152505,-96.468203 43.152515,-96.468154 43.15252,-96.467809 43.152517,-96.46776 43.152513,-96.467616 43.152487,-96.46697 43.152311,-96.466876 43.15229,-96.466684 43.152255,-96.46634 43.152239,-96.465946 43.152249,-96.465896 43.152253,-96.465628 43.152342,-96.4655 43.152396,-96.465085 43.152649,-96.464981 43.152726,-96.464963 43.152742,-96.464592 43.153094,-96.464443 43.153279,-96.464422 43.153311,-96.464406 43.153418,-96.464428 43.153882,-96.464421 43.153953,-96.464412 43.153988,-96.464404 43.154059,-96.464285 43.154588,-96.464072 43.155137,-96.463981 43.155489,-96.463957 43.156058,-96.46395 43.156129,-96.463954 43.156165,-96.463968 43.156199,-96.464009 43.156264,-96.464056 43.156326,-96.46412 43.156381,-96.464161 43.156401,-96.464251 43.156429,-96.464544 43.156424,-96.464636 43.15645,-96.464685 43.156456,-96.464734 43.156457,-96.465006 43.156422,-96.465074 43.156413,-96.465639 43.15629,-96.465685 43.156277,-96.465728 43.156259,-96.465793 43.156223,-96.466089 43.156231,-96.466333 43.156205,-96.466383 43.156204,-96.466579 43.156213,-96.466723 43.156236,-96.467044 43.156328,-96.467085 43.156348,-96.467261 43.156473,-96.467326 43.156526,-96.467346 43.156559,-96.467737 43.15737,-96.467758 43.157402,-96.467812 43.157462,-96.46794 43.157571,-96.467994 43.15763,-96.468054 43.157687,-96.468088 43.157713,-96.468127 43.157736,-96.468259 43.157784,-96.468454 43.157893,-96.468498 43.15791,-96.468544 43.157922,-96.468642 43.157934,-96.468685 43.157918,-96.468724 43.157898,-96.468773 43.157901,-96.468872 43.157895,-96.468921 43.157898,-96.468969 43.157906,-96.469108 43.157942,-96.469187 43.157984,-96.469435 43.158159,-96.469545 43.158318,-96.469556 43.158353,-96.46954 43.158384,-96.46951 43.158413,-96.469468 43.158478,-96.469233 43.158661,-96.469151 43.1587,-96.468612 43.158875,-96.468525 43.158909,-96.468446 43.158952,-96.467985 43.159274,-96.467919 43.159305,-96.467452 43.159532,-96.467019 43.159769,-96.466744 43.159975,-96.466465 43.16027,-96.466441 43.160301,-96.466235 43.160626,-96.465955 43.161422,-96.465942 43.161492,-96.465944 43.161635,-96.465989 43.161811,-96.466004 43.161845,-96.466025 43.161877,-96.466365 43.162312,-96.466413 43.162366,-96.466633 43.162613,-96.466993 43.163081,-96.467012 43.163114,-96.467034 43.163184,-96.467054 43.163214,-96.467139 43.163251,-96.467213 43.163298,-96.467242 43.163327,-96.467264 43.163359,-96.467468 43.163603,-96.467514 43.163666,-96.467532 43.163699,-96.467544 43.163734,-96.467557 43.163841,-96.467557 43.163948,-96.46755 43.163983,-96.467478 43.164191,-96.467466 43.164369,-96.467333 43.164515,-96.467304 43.164548,-96.467172 43.164654,-96.467142 43.164683,-96.467053 43.164795,-96.467045 43.164902,-96.467067 43.165008,-96.467163 43.165285,-96.467168 43.165569,-96.467157 43.165819,-96.467158 43.16589,-96.467152 43.165961,-96.467108 43.166063,-96.467085 43.166095,-96.467027 43.166152,-96.466896 43.1674,-96.466881 43.167649,-96.466903 43.168112,-96.466987 43.168354,-96.467011 43.168386,-96.46704 43.168414,-96.467138 43.168494,-96.467226 43.168527,-96.467369 43.168554,-96.467616 43.168564,-96.468157 43.168532,-96.468352 43.168551,-96.468446 43.168574,-96.468663 43.168658,-96.468743 43.1687,-96.468798 43.168759,-96.468931 43.16891,-96.468996 43.169006,-96.469121 43.169314,-96.46913 43.169421,-96.469097 43.16967,-96.469062 43.169774,-96.468841 43.170133,-96.468827 43.170167,-96.46881 43.170274,-96.468781 43.170342,-96.468757 43.170373,-96.468615 43.170468,-96.468577 43.170495,-96.468536 43.170514,-96.468313 43.170591,-96.468173 43.170626,-96.46769 43.170695,-96.467347 43.170726,-96.467002 43.17074,-96.466904 43.170729,-96.466811 43.170704,-96.466689 43.17073,-96.466672 43.170734,-96.466573 43.170742,-96.466329 43.170716,-96.466231 43.170709,-96.466132 43.17071,-96.466083 43.170715,-96.466036 43.170725,-96.466018 43.170734,-96.465997 43.170745,-96.465903 43.170828,-96.465866 43.170852,-96.465738 43.170907,-96.465723 43.17094,-96.465693 43.171081,-96.46568 43.171116,-96.46557 43.171315,-96.465536 43.171454,-96.465351 43.172231,-96.465332 43.172444,-96.465337 43.172837,-96.465343 43.172873,-96.4654 43.17301,-96.465423 43.173041,-96.465479 43.1731,-96.46562 43.1732,-96.466154 43.173458,-96.466422 43.173556,-96.46655 43.173604,-96.466589 43.173626,-96.466691 43.173704,-96.46676 43.173799,-96.466779 43.173832,-96.466857 43.174112,-96.466871 43.174219,-96.466867 43.174276,-96.466859 43.174311,-96.46683 43.17438,-96.466695 43.17457,-96.466567 43.174723,-96.466509 43.174781,-96.466108 43.175145,-96.465933 43.175361,-96.465888 43.175424,-96.465666 43.175782,-96.465541 43.176053,-96.465518 43.176195,-96.46552 43.176445,-96.465526 43.176481,-96.465538 43.176515,-96.46562 43.176645,-96.465672 43.176706,-96.465703 43.176732,-96.46618 43.177143,-96.466287 43.177263,-96.466309 43.177295,-96.466375 43.177429,-96.466386 43.177464,-96.466393 43.177571,-96.46639 43.177678,-96.466375 43.177748,-96.466242 43.178055,-96.466062 43.178348,-96.466034 43.178378,-96.465965 43.178429,-96.465846 43.178491,-96.465413 43.179366,-96.465403 43.179401,-96.465353 43.17972,-96.465344 43.179755,-96.465312 43.179823,-96.465263 43.179885,-96.465135 43.179994,-96.465097 43.180018,-96.465073 43.180046,-96.464883 43.180711,-96.464825 43.181532,-96.464856 43.18196,-96.464893 43.182137,-96.465026 43.182481,-96.465247 43.182916,-96.465285 43.182982,-96.46531 43.183013,-96.465343 43.18304,-96.465427 43.183077,-96.465471 43.183092,-96.465943 43.183194,-96.465987 43.183211,-96.466187 43.183314,-96.466367 43.183436,-96.466487 43.18355,-96.46657 43.18368,-96.466714 43.184014,-96.466895 43.184431,-96.466913 43.184464,-96.466937 43.184495,-96.467196 43.184709,-96.467266 43.18476,-96.467307 43.18478,-96.467352 43.184794,-96.4676 43.184841,-96.467649 43.184834,-96.467696 43.184822,-96.467921 43.18475,-96.468565 43.184484,-96.468657 43.184458,-96.468765 43.184443,-96.468816 43.184504,-96.46888 43.184558,-96.468919 43.184581,-96.469005 43.184616,-96.469125 43.184704,-96.469212 43.184768,-96.469284 43.184816,-96.469386 43.184893,-96.469582 43.185138,-96.469609 43.185207,-96.469629 43.18524,-96.469656 43.185269,-96.469689 43.185296,-96.469744 43.18533,-96.46972 43.185476,-96.46973 43.185583,-96.469769 43.185723,-96.469835 43.185857,-96.469886 43.185918,-96.470159 43.186124,-96.470237 43.186167,-96.470373 43.18621,-96.470617 43.186197,-96.470757 43.186161,-96.470845 43.186129,-96.470882 43.186105,-96.47101 43.185996,-96.471091 43.185956,-96.471149 43.185898,-96.471398 43.18555,-96.471467 43.185498,-96.471546 43.185457,-96.471582 43.185432,-96.471668 43.185397,-96.471716 43.185392,-96.471864 43.185389,-96.471912 43.185395,-96.472011 43.185396,-96.472106 43.185414,-96.472148 43.185432,-96.472487 43.185578,-96.472525 43.185601,-96.4726 43.185693,-96.472861 43.186037,-96.472879 43.18607,-96.472923 43.186246,-96.472924 43.186389,-96.472779 43.186615,-96.472752 43.186645,-96.472604 43.186738,-96.472536 43.18679,-96.472494 43.186854,-96.472385 43.187053,-96.472372 43.187087,-96.472363 43.187158,-96.472372 43.187229,-96.472397 43.187585,-96.47273 43.188102,-96.472758 43.188132,-96.473588 43.188883,-96.47372 43.189034,-96.473789 43.189129,-96.473929 43.189396,-96.474009 43.189639,-96.474109 43.19017,-96.474085 43.190275,-96.47407 43.190309,-96.473588 43.191093,-96.473575 43.191128,-96.473569 43.191163,-96.473565 43.191413,-96.473578 43.191519,-96.473709 43.191863,-96.473766 43.192182,-96.473731 43.192611,-96.473724 43.192646,-96.473606 43.192956,-96.473331 43.193376,-96.473276 43.193475,-96.472899 43.19425,-96.47288 43.19432,-96.472859 43.194534,-96.472906 43.194693,-96.47291 43.194729,-96.472906 43.194943,-96.472911 43.194979,-96.472954 43.195118,-96.47296 43.19519,-96.472939 43.195222,-96.472826 43.195339,-96.472779 43.195402,-96.472742 43.195424,-96.472657 43.195461,-96.472619 43.195484,-96.472454 43.195617,-96.472217 43.195889,-96.472148 43.196023,-96.472136 43.196073,-96.472108 43.196199,-96.472103 43.19627,-96.472106 43.196342,-96.472151 43.196553,-96.472179 43.196622,-96.472396 43.197021,-96.472673 43.197401,-96.4729 43.197678,-96.473015 43.197794,-96.473512 43.198141,-96.473581 43.198193,-96.473731 43.198335,-96.47381 43.198426,-96.473918 43.198586,-96.473977 43.198685,-96.474092 43.198958,-96.474124 43.199063,-96.474189 43.19951,-96.474232 43.199809,-96.474239 43.199963,-96.474264 43.200487,-96.474248 43.200736,-96.474277 43.200913,-96.474328 43.201013,-96.474351 43.201045,-96.47438 43.201074,-96.474564 43.201199,-96.474596 43.201221,-96.47462 43.201251,-96.474785 43.20155,-96.474799 43.201585,-96.474919 43.201932,-96.475007 43.202319,-96.475016 43.202498,-96.475007 43.202569,-96.474976 43.202674,-96.474958 43.202707,-96.474934 43.202738,-96.474896 43.202774,-96.474792 43.202764,-96.474645 43.202778,-96.474596 43.202779,-96.474353 43.202799,-96.474261 43.202825,-96.474218 43.202842,-96.474155 43.202879,-96.474103 43.202909,-96.47402 43.202948,-96.473987 43.202974,-96.473959 43.203004,-96.473913 43.203067,-96.473897 43.203101,-96.473881 43.203172,-96.473881 43.203208,-96.473825 43.203263,-96.473576 43.203572,-96.473547 43.203601,-96.473301 43.203786,-96.472933 43.204065,-96.472896 43.204089,-96.472856 43.204109,-96.471426 43.20468,-96.471304 43.204741,-96.470964 43.204948,-96.470772 43.205112,-96.47075 43.205143,-96.470744 43.205179,-96.470791 43.205463,-96.470803 43.205497,-96.470826 43.205529,-96.470893 43.205581,-96.47097 43.205625,-96.471002 43.205653,-96.471109 43.205773,-96.471147 43.205838,-96.471161 43.205873,-96.471201 43.205938,-96.471233 43.206042,-96.471245 43.206113,-96.471234 43.206255,-96.471222 43.20629,-96.471188 43.206357,-96.471093 43.206482,-96.471073 43.206515,-96.470998 43.206607,-96.470774 43.206842,-96.470681 43.207007,-96.470692 43.207038,-96.470742 43.2071,-96.470757 43.207134,-96.470758 43.207204,-96.470746 43.207239,-96.470721 43.20727,-96.47066 43.207326,-96.470665 43.207359,-96.470779 43.207517,-96.470811 43.207543,-96.471387 43.20775,-96.471634 43.207867,-96.471673 43.207889,-96.471777 43.207965,-96.471859 43.208062,-96.47188 43.208094,-96.47198 43.208333,-96.472 43.208476,-96.472003 43.208868,-96.472065 43.20915,-96.472096 43.209218,-96.472117 43.20925,-96.472315 43.209454,-96.473063 43.210024,-96.473127 43.210078,-96.473182 43.210138,-96.473341 43.210373,-96.473378 43.210426,-96.473487 43.210545,-96.473518 43.210573,-96.473759 43.210752,-96.474493 43.211282,-96.475085 43.211763,-96.475701 43.212275,-96.475906 43.212435,-96.47624 43.212695,-96.47627 43.212723,-96.476404 43.212873,-96.476469 43.212969,-96.476504 43.213073,-96.476548 43.213357,-96.476546 43.213429,-96.476525 43.213535,-96.476408 43.213808,-96.476372 43.213875,-96.476291 43.214005,-96.476088 43.21429,-96.476062 43.214321,-96.47603 43.214348,-96.475851 43.214471,-96.4758 43.214532,-96.475756 43.214596,-96.475655 43.214835,-96.475549 43.215256,-96.475536 43.215327,-96.475493 43.215681,-96.475467 43.215786,-96.475462 43.215793,-96.475315 43.216011,-96.47517 43.216156,-96.47516 43.216191,-96.475104 43.21651,-96.475085 43.216688,-96.475066 43.216758,-96.475018 43.216897,-96.47496 43.217396,-96.474962 43.217431,-96.47525 43.218775,-96.475201 43.218827,-96.475158 43.218891,-96.475146 43.218926,-96.475122 43.219103,-96.475145 43.219354,-96.475157 43.219493,-96.475178 43.219811,-96.475241 43.220123,-96.475292 43.220259,-96.475388 43.220458,-96.475426 43.22052,-96.475644 43.220606,-96.475735 43.220635,-96.475776 43.2207,-96.476217 43.221301,-96.476509 43.221754,-96.476654 43.221941,-96.476729 43.221987,-96.476991 43.222087,-96.47725 43.222192,-96.477296 43.222203,-96.477394 43.222212,-96.477493 43.222204,-96.477584 43.222178,-96.477897 43.222072,-96.478041 43.222047,-96.478337 43.222043,-96.478436 43.222047,-96.478583 43.22206,-96.478965 43.222134,-96.479236 43.222221,-96.479366 43.222273,-96.479491 43.222331,-96.47963 43.222432,-96.480019 43.222801,-96.480339 43.223024,-96.480456 43.22309,-96.480499 43.223108,-96.48112 43.223316,-96.481305 43.223379,-96.481584 43.223451,-96.481632 43.223457,-96.482274 43.223451,-96.482815 43.223409,-96.482963 43.223409,-96.483552 43.22345,-96.483699 43.223464,-96.483937 43.223513,-96.484254 43.223612,-96.484297 43.22363,-96.484333 43.223654,-96.484545 43.223851,-96.484656 43.223922,-96.484884 43.224004,-96.484915 43.224015,-96.485008 43.224049,-96.48504 43.224061,-96.485274 43.224146,-96.485459 43.224197,-96.485575 43.224212,-96.485653 43.224222,-96.4859 43.224222,-96.486241 43.224185,-96.486497 43.224132,-96.486688 43.224094,-96.487112 43.223998,-96.487223 43.223965,-96.487525 43.223878,-96.487762 43.223819,-96.488086 43.223739,-96.488277 43.223698,-96.488749 43.223598,-96.488845 43.223581,-96.489772 43.22348,-96.489849 43.223468,-96.489916 43.223458,-96.490037 43.223345,-96.490074 43.223321,-96.490118 43.223304,-96.490165 43.223296,-96.490263 43.223292,-96.490323 43.223293,-96.490409 43.223295,-96.490458 43.2233,-96.490556 43.223302,-96.490562 43.223303,-96.490604 43.223312,-96.490684 43.223353,-96.490718 43.223379,-96.490762 43.223388,-96.491108 43.223395,-96.491261 43.223386,-96.491451 43.223375,-96.491501 43.223374,-96.491603 43.223372,-96.491673 43.223321,-96.491716 43.223303,-96.4919 43.223257,-96.491949 43.223254,-96.491995 43.223259,-96.492088 43.223249,-96.492135 43.22325,-96.492229 43.223244,-96.492323 43.223232,-96.492371 43.223238,-96.492416 43.223254,-96.492452 43.223278,-96.492502 43.223319,-96.492517 43.223332,-96.492565 43.223322,-96.492806 43.223286,-96.494189 43.223266,-96.494337 43.223272,-96.494775 43.223329,-96.495208 43.223404,-96.495643 43.223446,-96.495648 43.223447,-96.495694 43.223461,-96.495734 43.223481,-96.495888 43.223572,-96.49596 43.223621,-96.496044 43.223658,-96.496091 43.22367,-96.496332 43.223707,-96.496579 43.223718,-96.496639 43.223716,-96.496915 43.223638,-96.49705 43.223595,-96.497117 43.223542,-96.497176 43.223485,-96.497826 43.222811,-96.498716 43.221954,-96.498781 43.2219,-96.499146 43.221648,-96.499956 43.221072,-96.500072 43.221005,-96.500434 43.220819,-96.500599 43.22074,-96.500776 43.220677,-96.501198 43.220574,-96.501296 43.220564,-96.501632 43.22062,-96.501771 43.220657,-96.501856 43.220694,-96.502513 43.221012,-96.50264 43.221067,-96.503161 43.221274,-96.50324 43.221316,-96.503315 43.221363,-96.503393 43.221406,-96.503658 43.221504,-96.503841 43.221557,-96.503935 43.221579,-96.504706 43.221705,-96.504852 43.221723,-96.505226 43.221756,-96.505293 43.221763,-96.505787 43.221782,-96.506721 43.221711,-96.506964 43.22168,-96.507248 43.221617,-96.507568 43.221525,-96.508226 43.221277,-96.508268 43.221258,-96.508551 43.221117,-96.508749 43.220958,-96.508775 43.220928,-96.50883 43.22079,-96.508847 43.220576,-96.508841 43.220469,-96.508771 43.220261,-96.508509 43.219647,-96.508166 43.218975,-96.50811 43.218765,-96.508069 43.218301,-96.508069 43.21823,-96.50809 43.218053,-96.508144 43.217806,-96.508224 43.217601,-96.508244 43.217569,-96.508447 43.217368,-96.508644 43.217262,-96.50886 43.217174,-96.508906 43.217162,-96.50906 43.21716,-96.509204 43.217184,-96.509342 43.217224,-96.510186 43.217521,-96.51111 43.217771,-96.511201 43.2178,-96.511243 43.217818,-96.511357 43.217887,-96.511613 43.218105,-96.511682 43.218156,-96.512166 43.218461,-96.512686 43.218668,-96.512731 43.218683,-96.512826 43.218703,-96.513309 43.218773,-96.513801 43.218806,-96.514294 43.218805,-96.514588 43.218782,-96.515117 43.218691,-96.51539 43.218611,-96.515483 43.218587,-96.515619 43.218544,-96.515873 43.218435,-96.516081 43.218339,-96.516674 43.218163,-96.516837 43.218083,-96.51754 43.217825,-96.51768 43.217788,-96.517971 43.217753,-96.518367 43.217749,-96.519649 43.217778,-96.519748 43.217785,-96.52004 43.21782,-96.520182 43.217848,-96.520417 43.217902,-96.520692 43.217983,-96.520853 43.218065,-96.520891 43.218088,-96.521084 43.218251,-96.521168 43.218333,-96.521213 43.218348,-96.52133 43.218414,-96.521366 43.218439,-96.521394 43.218468,-96.521415 43.218501,-96.521444 43.218569,-96.521518 43.218776,-96.521542 43.218917,-96.521543 43.218953,-96.521523 43.21913,-96.521506 43.219164,-96.521481 43.219195,-96.521414 43.219251,-96.521446 43.219355,-96.521451 43.219391,-96.521415 43.219782,-96.521404 43.219818,-96.521387 43.219851,-96.521348 43.219903,-96.521354 43.219999,-96.521363 43.220034,-96.521451 43.220276,-96.521656 43.220561,-96.521718 43.220617,-96.522098 43.220989,-96.522226 43.221098,-96.522295 43.22115,-96.522481 43.221267,-96.522521 43.221288,-96.522968 43.221441,-96.523338 43.221539,-96.523437 43.221539,-96.523582 43.221562,-96.523671 43.221593,-96.523711 43.221614,-96.523747 43.221639,-96.523777 43.221667,-96.523856 43.221758,-96.523954 43.22196,-96.523966 43.221995,-96.523974 43.222065,-96.524027 43.222125,-96.524307 43.222375,-96.524373 43.222428,-96.525335 43.223095,-96.526012 43.223664,-96.526488 43.224025,-96.526524 43.224052,-96.526635 43.224122,-96.52672 43.224159,-96.526808 43.22419,-96.526997 43.224233,-96.527239 43.224267,-96.527338 43.224264,-96.527583 43.224248,-96.527963 43.224173,-96.52806 43.224159,-96.528207 43.22415,-96.528651 43.224149,-96.528797 43.224167,-96.529363 43.224296,-96.529449 43.224331,-96.530235 43.224703,-96.53095 43.225085,-96.531363 43.22534,-96.531539 43.225466,-96.531665 43.225575,-96.531752 43.225662,-96.531997 43.22593,-96.53215 43.22607,-96.532361 43.226221,-96.532401 43.226242,-96.532573 43.226311,-96.53262 43.226322,-96.532818 43.226329,-96.533063 43.226307,-96.533161 43.226302,-96.53321 43.226307,-96.533257 43.226318,-96.533375 43.226383,-96.533637 43.226546,-96.533669 43.226573,-96.533795 43.226767,-96.533854 43.226915,-96.53397 43.226982,-96.534013 43.227,-96.534107 43.227022,-96.534497 43.227063,-96.534639 43.227095,-96.535172 43.227355,-96.535299 43.227404,-96.535823 43.227606,-96.535915 43.227634,-96.536061 43.227647,-96.536308 43.227643,-96.536357 43.227636,-96.536546 43.227594,-96.536672 43.227538,-96.536711 43.227516,-96.536744 43.22749,-96.536762 43.227457,-96.536807 43.227281,-96.536877 43.227187,-96.53693 43.227127,-96.536952 43.227095,-96.536967 43.227061,-96.53699 43.226919,-96.537 43.226884,-96.537108 43.226764,-96.537144 43.22674,-96.5374 43.226632,-96.537628 43.226564,-96.537721 43.22654,-96.538384 43.226395,-96.538791 43.226267,-96.539183 43.226115,-96.539476 43.225985,-96.539907 43.225747,-96.540119 43.225655,-96.540246 43.225632,-96.540295 43.22563,-96.540345 43.225633,-96.540586 43.22567,-96.540678 43.225695,-96.540972 43.225827,-96.541239 43.22592,-96.541379 43.225956,-96.541661 43.226021,-96.5419 43.226065,-96.542195 43.226086,-96.542491 43.226095,-96.54259 43.226091,-96.54296 43.226056,-96.543323 43.226023,-96.543815 43.225989,-96.544301 43.22593,-96.544695 43.225915,-96.545286 43.225945,-96.54548 43.225973,-96.545838 43.226094,-96.546056 43.226178,-96.546537 43.226428,-96.547043 43.226651,-96.547223 43.226711,-96.547541 43.22681,-96.547914 43.226903,-96.548201 43.226955,-96.548349 43.226965,-96.548497 43.226965,-96.549631 43.226943,-96.550268 43.226992,-96.550413 43.227015,-96.550561 43.227027,-96.551597 43.227028,-96.551889 43.227064,-96.552038 43.227065,-96.552628 43.227045,-96.552725 43.227038,-96.552922 43.227034,-96.553602 43.226956,-96.554566 43.226804,-96.555281 43.226666,-96.555555 43.226587,-96.555946 43.226433,-96.556029 43.226394,-96.556065 43.22637,-96.556146 43.22633,-96.556183 43.226307,-96.556242 43.22625,-96.556322 43.226161,-96.556388 43.226065,-96.556445 43.225928,-96.556472 43.225751,-96.556506 43.225108,-96.55654 43.224895,-96.556604 43.224723,-96.556624 43.22469,-96.556848 43.224414,-96.556903 43.224355,-96.557181 43.224104,-96.557321 43.223958,-96.5574 43.223867,-96.557686 43.223491,-96.557738 43.22343,-96.557795 43.223372,-96.557888 43.223288,-96.558178 43.223094,-96.55834 43.223012,-96.558616 43.222935,-96.558664 43.222925,-96.558989 43.222887,-96.559186 43.222901,-96.559326 43.222934,-96.559491 43.223012,-96.559631 43.223113,-96.559659 43.223142,-96.559696 43.223208,-96.560003 43.223961,-96.560396 43.224727,-96.560409 43.224761,-96.560462 43.224861,-96.560659 43.225148,-96.560708 43.22521,-96.560844 43.225313,-96.561002 43.225397,-96.561194 43.225509,-96.561716 43.225831,-96.562123 43.226143,-96.562751 43.226694,-96.563346 43.227263,-96.5634 43.227323,-96.563508 43.227483,-96.563698 43.227879,-96.563703 43.227888,-96.563748 43.22799,-96.563971 43.228576,-96.564114 43.228842,-96.564344 43.229199,-96.565207 43.230281,-96.56529 43.23037,-96.565431 43.23047,-96.565583 43.230561,-96.56583 43.230679,-96.565876 43.230694,-96.566016 43.230728,-96.566556 43.230758,-96.566847 43.230798,-96.567658 43.231062,-96.567788 43.231113,-96.567957 43.231187,-96.568044 43.23122,-96.568261 43.231314,-96.568289 43.231344,-96.568521 43.23166,-96.568697 43.231916,-96.569178 43.2327,-96.56939 43.233356,-96.569483 43.233641,-96.569513 43.233818,-96.56952 43.233853,-96.569528 43.233924,-96.569531 43.234103,-96.569576 43.234387,-96.569591 43.234457,-96.569594 43.234493,-96.569572 43.23467,-96.569423 43.235231,-96.569405 43.235303,-96.569264 43.235684,-96.56915 43.235991,-96.569027 43.236411,-96.569024 43.236424,-96.56896 43.236764,-96.568974 43.237014,-96.569023 43.237189,-96.569059 43.237256,-96.569291 43.23753,-96.56956 43.23774,-96.5696 43.23776,-96.569913 43.237866,-96.570004 43.237893,-96.570307 43.237972,-96.571061 43.23817,-96.571165 43.238197,-96.571275 43.238253,-96.571316 43.238274,-96.571378 43.23833,-96.571403 43.238361,-96.571486 43.238491,-96.571502 43.238561,-96.571509 43.238633,-96.571486 43.238847,-96.571483 43.238857,-96.571438 43.239022,-96.571414 43.239078,-96.571278 43.239179,-96.569864 43.240181,-96.569712 43.240322,-96.569302 43.240811,-96.569068 43.241126,-96.568762 43.241654,-96.568661 43.241777,-96.568524 43.241926,-96.568163 43.242221,-96.567908 43.24239,-96.567679 43.242577,-96.567152 43.242955,-96.566735 43.243309,-96.566494 43.243489,-96.566244 43.243704,-96.56567 43.244201,-96.565633 43.244226,-96.565191 43.244454,-96.565146 43.244469,-96.565052 43.244492,-96.565004 43.244499,-96.564855 43.244498,-96.564661 43.244472,-96.564568 43.244448,-96.563929 43.244256,-96.563763 43.244178,-96.563604 43.244093,-96.563138 43.24389,-96.562812 43.243806,-96.562425 43.243749,-96.562031 43.243725,-96.561784 43.243724,-96.561537 43.243737,-96.561146 43.243779,-96.560713 43.24385,-96.56049 43.243927,-96.56045 43.243947,-96.560395 43.244007,-96.560335 43.244064,-96.560313 43.244096,-96.560251 43.244231,-96.560241 43.244267,-96.560225 43.244409,-96.560192 43.244513,-96.560085 43.244661,-96.559614 43.245027,-96.559301 43.245201,-96.558977 43.245365,-96.55876 43.245451,-96.558669 43.245479,-96.556862 43.24596,-96.555831 43.246307,-96.555329 43.246459,-96.554934 43.246607,-96.554424 43.246826,-96.553702 43.2472,-96.553367 43.247413,-96.553285 43.247453,-96.553241 43.247469,-96.553103 43.247508,-96.553054 43.247513,-96.552511 43.247512,-96.552166 43.247492,-96.551974 43.247458,-96.551104 43.247202,-96.549883 43.246728,-96.549598 43.246669,-96.5495 43.246663,-96.549402 43.246671,-96.549316 43.246707,-96.549279 43.24673,-96.549187 43.246814,-96.549168 43.246848,-96.549125 43.24695,-96.549109 43.247093,-96.549195 43.247626,-96.54934 43.248005,-96.549534 43.248372,-96.549607 43.248465,-96.549826 43.248659,-96.550009 43.248801,-96.551038 43.249603,-96.551102 43.249658,-96.551248 43.249802,-96.55133 43.249932,-96.551429 43.250172,-96.551387 43.250255,-96.551371 43.250326,-96.551373 43.250361,-96.551425 43.250608,-96.551509 43.250851,-96.551531 43.250883,-96.551621 43.250969,-96.551656 43.250993,-96.551698 43.251013,-96.55174 43.251102,-96.551975 43.251266,-96.552346 43.251637,-96.552537 43.251789,-96.552567 43.251817,-96.55266 43.251943,-96.552689 43.252012,-96.552705 43.252082,-96.552677 43.25226,-96.552591 43.252466,-96.55247 43.252528,-96.552433 43.252551,-96.552337 43.252632,-96.552211 43.252689,-96.552174 43.252712,-96.552045 43.25282,-96.55202 43.252851,-96.551988 43.252918,-96.551911 43.253049,-96.551855 43.253222,-96.551879 43.253507,-96.551919 43.25361,-96.552001 43.25374,-96.552026 43.253771,-96.552336 43.254087,-96.552541 43.254296,-96.552601 43.254352,-96.552665 43.254448,-96.552767 43.25476,-96.55272 43.255044,-96.552722 43.255069,-96.552733 43.255187,-96.552733 43.255579,-96.552699 43.255972,-96.552591 43.256247,-96.552512 43.256378,-96.552422 43.256582,-96.552411 43.256689,-96.552418 43.25676,-96.552463 43.256935,-96.552475 43.257006,-96.552484 43.257291,-96.552514 43.257504,-96.552574 43.257601,-96.552599 43.257632,-96.552657 43.257728,-96.552673 43.257761,-96.552675 43.257795,-96.552662 43.2579,-96.552648 43.25797,-96.552644 43.258042,-96.552658 43.258112,-96.55266 43.258148,-96.552683 43.258179,-96.552774 43.258264,-96.552793 43.258287,-96.552799 43.258294,-96.552879 43.258464,-96.552898 43.258496,-96.553052 43.258585,-96.553143 43.258614,-96.553151 43.258685,-96.553194 43.258786,-96.553198 43.258821,-96.553235 43.258883,-96.553275 43.258903,-96.553391 43.25895,-96.553379 43.259042,-96.553354 43.259111,-96.55338 43.259141,-96.553395 43.259175,-96.553408 43.259282,-96.553484 43.259451,-96.553499 43.259472,-96.553507 43.259482,-96.553536 43.25951,-96.553688 43.259601,-96.55373 43.25962,-96.553777 43.259631,-96.553875 43.25964,-96.553987 43.259641,-96.554118 43.25969,-96.554213 43.259712,-96.554319 43.259717,-96.55442 43.259796,-96.554583 43.259882,-96.554619 43.259901,-96.554664 43.259917,-96.554907 43.259949,-96.554969 43.259951,-96.555006 43.259952,-96.555117 43.259956,-96.555154 43.259958,-96.555541 43.259901,-96.555585 43.259886,-96.555617 43.259859,-96.555717 43.259736,-96.555819 43.259658,-96.556198 43.259329,-96.556485 43.259132,-96.556525 43.259111,-96.556656 43.259062,-96.556747 43.259033,-96.556794 43.259023,-96.55694 43.259006,-96.55699 43.259008,-96.557136 43.259024,-96.557184 43.259034,-96.557231 43.259033,-96.557865 43.258956,-96.558556 43.258944,-96.558852 43.258957,-96.559511 43.259036,-96.560847 43.259198,-96.561514 43.25933,-96.561686 43.259399,-96.561731 43.259414,-96.561815 43.259452,-96.562108 43.259642,-96.562306 43.259749,-96.562435 43.259802,-96.562573 43.259839,-96.563696 43.259973,-96.564535 43.260181,-96.56463 43.260199,-96.564823 43.260227,-96.564955 43.260277,-96.565097 43.260376,-96.56516 43.260431,-96.565306 43.260575,-96.565506 43.260821,-96.565899 43.261318,-96.565986 43.261404,-96.566702 43.261898,-96.566784 43.261936,-96.567085 43.262055,-96.567454 43.262158,-96.567501 43.262168,-96.567988 43.262227,-96.56822 43.262289,-96.568579 43.262452,-96.568863 43.262514,-96.569249 43.26257,-96.569345 43.262587,-96.569529 43.262639,-96.569784 43.262748,-96.569859 43.262794,-96.570334 43.263155,-96.570363 43.263184,-96.570523 43.263318,-96.570581 43.263375,-96.570599 43.263408,-96.570608 43.263444,-96.570692 43.263648,-96.570877 43.263816,-96.570903 43.263846,-96.571079 43.264102,-96.571127 43.264164,-96.571355 43.264353,-96.571504 43.264538,-96.571573 43.264589,-96.571707 43.264677,-96.571719 43.264685,-96.57178 43.264741,-96.571991 43.264891,-96.572065 43.264938,-96.572357 43.265071,-96.572394 43.265095,-96.572489 43.265177,-96.572529 43.265197,-96.572787 43.265302,-96.572921 43.265346,-96.573001 43.265387,-96.573075 43.265433,-96.573695 43.265785,-96.57377 43.265831,-96.574333 43.266232,-96.574397 43.266286,-96.574573 43.266458,-96.574755 43.266711,-96.574805 43.266773,-96.575124 43.267046,-96.575297 43.267263,-96.575393 43.267345,-96.575448 43.267376,-96.575947 43.267823,-96.576411 43.267995,-96.576515 43.268071,-96.576991 43.268259,-96.577037 43.268273,-96.577318 43.268339,-96.577708 43.26839,-96.577807 43.268388,-96.578002 43.268369,-96.578145 43.268344,-96.578585 43.268185,-96.57882 43.268129,-96.578868 43.268122,-96.578917 43.268121,-96.579065 43.268127,-96.579114 43.268122,-96.57945 43.268066,-96.579846 43.268067,-96.580043 43.26808,-96.580386 43.268112,-96.580485 43.268117,-96.581078 43.268103,-96.581322 43.268072,-96.581464 43.268041,-96.581553 43.26801,-96.581634 43.267969,-96.581737 43.267892,-96.581775 43.267869,-96.582096 43.267703,-96.582231 43.26766,-96.582328 43.267645,-96.582601 43.267566,-96.582699 43.267563,-96.582945 43.267568,-96.583569 43.267662,-96.583623 43.267676,-96.58407 43.26783,-96.584229 43.267915,-96.584431 43.268118,-96.584463 43.268145,-96.584644 43.268267,-96.584817 43.268395,-96.585313 43.268839,-96.585341 43.268869,-96.58566 43.269352,-96.585699 43.269455,-96.585724 43.269739,-96.585715 43.269775,-96.585647 43.269946,-96.585615 43.270013,-96.585524 43.27014,-96.585423 43.270219,-96.585407 43.270252,-96.585398 43.270287,-96.585378 43.27032,-96.585348 43.270348,-96.585308 43.270369,-96.585179 43.270422,-96.585145 43.270446,-96.585103 43.270511,-96.58505 43.270684,-96.585046 43.27072,-96.585048 43.270897,-96.58508 43.271074,-96.585112 43.271141,-96.585164 43.271278,-96.585196 43.271346,-96.585241 43.271409,-96.585293 43.271469,-96.58539 43.271594,-96.586096 43.272239,-96.586239 43.272385,-96.586291 43.272445,-96.586335 43.272547,-96.586454 43.273003,-96.586521 43.273429,-96.586534 43.273572,-96.586525 43.273715,-96.586504 43.273784,-96.586463 43.273888,-96.586452 43.273958,-96.586423 43.274027,-96.586337 43.274194,-96.586324 43.274228,-96.586196 43.274417,-96.586149 43.27448,-96.58607 43.27457,-96.585825 43.274794,-96.585722 43.274871,-96.58554 43.274991,-96.585264 43.275141,-96.584932 43.275494,-96.584865 43.27559,-96.5846 43.276013,-96.584194 43.276501,-96.584127 43.276553,-96.583957 43.276626,-96.583765 43.27666,-96.583716 43.27666,-96.583667 43.276655,-96.583573 43.276636,-96.583527 43.276623,-96.583353 43.276556,-96.583312 43.276536,-96.583277 43.27651,-96.583142 43.276361,-96.583125 43.276327,-96.58305 43.276084,-96.583038 43.276013,-96.583033 43.275942,-96.583036 43.275907,-96.58303 43.275836,-96.583124 43.275375,-96.583216 43.275098,-96.58323 43.275027,-96.583234 43.274885,-96.583216 43.274707,-96.583191 43.274638,-96.583171 43.274605,-96.583111 43.274549,-96.582937 43.274422,-96.582575 43.274308,-96.58243 43.274287,-96.582184 43.274282,-96.581943 43.274321,-96.581683 43.274423,-96.581196 43.274669,-96.581159 43.274692,-96.5811 43.274749,-96.580881 43.274941,-96.580846 43.275007,-96.580804 43.275326,-96.5808 43.275397,-96.580775 43.275573,-96.580724 43.275711,-96.580693 43.275779,-96.580522 43.276036,-96.580491 43.276063,-96.580096 43.276332,-96.57999 43.276408,-96.579239 43.277027,-96.578575 43.277604,-96.578339 43.277833,-96.578133 43.278077,-96.57795 43.278169,-96.577916 43.278196,-96.577754 43.278373,-96.577729 43.278477,-96.577726 43.278512,-96.57764 43.278862,-96.577608 43.278929,-96.577597 43.278964,-96.577557 43.279177,-96.57756 43.279212,-96.577592 43.279317,-96.577703 43.279591,-96.577819 43.279938,-96.578016 43.280266,-96.578138 43.280539,-96.578315 43.281424,-96.578319 43.281456,-96.578439 43.282492,-96.578499 43.283383,-96.578532 43.283631,-96.578643 43.283942,-96.578685 43.284044,-96.578776 43.284171,-96.578828 43.284232,-96.57893 43.284309,-96.57909 43.284394,-96.579337 43.284617,-96.579624 43.284908,-96.57985 43.285185,-96.579924 43.285316,-96.580293 43.285699,-96.580753 43.286071,-96.580789 43.286125,-96.580913 43.286396,-96.581042 43.286779,-96.581136 43.287202,-96.581398 43.287815,-96.581416 43.287958,-96.58142 43.288029,-96.581383 43.288742,-96.581351 43.288884,-96.581018 43.289706,-96.580817 43.290073,-96.580637 43.290518,-96.580605 43.290586,-96.580261 43.291217,-96.580235 43.291247,-96.580024 43.291398,-96.579995 43.291427,-96.57996 43.291453,-96.579803 43.291539,-96.57976 43.291556,-96.579666 43.291581,-96.579639 43.291609,-96.579541 43.291734,-96.579457 43.291863,-96.579176 43.292473,-96.579115 43.292863,-96.579145 43.293757,-96.579178 43.293898,-96.579277 43.294211,-96.579495 43.294646,-96.579538 43.294748,-96.579541 43.294783,-96.579511 43.295068,-96.579508 43.295139,-96.579472 43.295243,-96.579411 43.295378,-96.579401 43.295413,-96.57941 43.295448,-96.579428 43.295481,-96.579448 43.29555,-96.579448 43.295621,-96.579454 43.295657,-96.579473 43.295689,-96.579503 43.295718,-96.579543 43.295739,-96.579822 43.29581,-96.57987 43.295819,-96.580113 43.295839,-96.580212 43.295842,-96.580309 43.295834,-96.580405 43.29582,-96.580452 43.295809,-96.580491 43.295787,-96.580526 43.295761,-96.580555 43.295733,-96.580867 43.295722,-96.580964 43.295712,-96.58135 43.295657,-96.581397 43.295645,-96.581528 43.295595,-96.582066 43.29523,-96.582345 43.295035,-96.582757 43.294783,-96.58307 43.294464,-96.583246 43.29425,-96.583378 43.2941,-96.583708 43.293703,-96.583822 43.293586,-96.58402 43.293427,-96.584158 43.293325,-96.584345 43.293209,-96.584547 43.293106,-96.584891 43.292968,-96.585574 43.292758,-96.585761 43.292711,-96.585809 43.292704,-96.586006 43.29269,-96.586103 43.292679,-96.586678 43.292572,-96.587163 43.292507,-96.587645 43.292423,-96.587985 43.292377,-96.588033 43.292379,-96.588272 43.292427,-96.588314 43.292444,-96.588435 43.292505,-96.588465 43.292533,-96.588534 43.292584,-96.588636 43.292706,-96.588676 43.292771,-96.588702 43.292801,-96.588738 43.292868,-96.588876 43.293284,-96.588899 43.293425,-96.588916 43.293496,-96.588918 43.293507,-96.588994 43.29392,-96.589065 43.294128,-96.589069 43.294162,-96.589039 43.294266,-96.589041 43.294512,-96.589059 43.29458,-96.589093 43.294826,-96.589113 43.294932,-96.589192 43.295175,-96.589274 43.295265,-96.589239 43.295325,-96.589213 43.295355,-96.58915 43.29541,-96.589056 43.295687,-96.588999 43.295787,-96.588881 43.295901,-96.588492 43.296175,-96.5882 43.296309,-96.588112 43.296341,-96.58751 43.296502,-96.587462 43.296504,-96.587417 43.296487,-96.587322 43.296468,-96.586879 43.29646,-96.586296 43.296535,-96.585965 43.296607,-96.585779 43.296656,-96.585647 43.296705,-96.584869 43.297015,-96.584791 43.297059,-96.584683 43.297132,-96.584421 43.297392,-96.584367 43.297451,-96.584335 43.297478,-96.584281 43.297538,-96.584249 43.297565,-96.583956 43.297756,-96.583619 43.297904,-96.583311 43.298016,-96.583225 43.298051,-96.582947 43.298125,-96.5828 43.298136,-96.582503 43.298135,-96.582307 43.29812,-96.58221 43.298107,-96.581829 43.298031,-96.58156 43.297941,-96.581176 43.297873,-96.581029 43.297858,-96.580634 43.297853,-96.580437 43.297863,-96.58034 43.297872,-96.579966 43.297968,-96.579924 43.297986,-96.579684 43.298112,-96.579501 43.298165,-96.579262 43.298292,-96.579135 43.298348,-96.578447 43.298547,-96.577842 43.298657,-96.57779 43.298666,-96.577634 43.298694,-96.577583 43.298704,-96.576839 43.298838,-96.57655 43.298883,-96.576501 43.298891,-96.575959 43.298921,-96.575279 43.299014,-96.574591 43.299063,-96.574542 43.299064,-96.574209 43.299047,-96.573902 43.299032,-96.573706 43.299026,-96.573467 43.298987,-96.573422 43.298973,-96.573286 43.298931,-96.573201 43.298895,-96.573026 43.298829,-96.572586 43.2986,-96.572479 43.298526,-96.572179 43.298291,-96.57215 43.298262,-96.571733 43.297736,-96.571625 43.297575,-96.571148 43.29675,-96.571034 43.296592,-96.570786 43.296324,-96.570714 43.296275,-96.570483 43.296141,-96.569938 43.295895,-96.569319 43.295539,-96.569009 43.295427,-96.568764 43.295361,-96.568632 43.295313,-96.568498 43.295269,-96.568357 43.295235,-96.568309 43.295227,-96.568063 43.295209,-96.568013 43.295212,-96.567981 43.295238,-96.567906 43.295322,-96.567821 43.295419,-96.567748 43.295512,-96.567707 43.295577,-96.567527 43.295746,-96.567324 43.295901,-96.567284 43.295923,-96.567014 43.29601,-96.566819 43.296032,-96.566525 43.296048,-96.566475 43.296048,-96.566427 43.296039,-96.566295 43.29599,-96.566168 43.295935,-96.566133 43.295911,-96.565717 43.295556,-96.565297 43.29516,-96.564964 43.294896,-96.564606 43.294709,-96.564518 43.294676,-96.564471 43.294666,-96.56438 43.294639,-96.564282 43.294628,-96.564233 43.294629,-96.563988 43.294649,-96.563698 43.294692,-96.563651 43.294703,-96.563224 43.294883,-96.563067 43.294969,-96.56258 43.295325,-96.562426 43.295414,-96.562383 43.295522,-96.562347 43.295588,-96.562317 43.295617,-96.562243 43.295663,-96.562108 43.295765,-96.562038 43.295813,-96.56186 43.295873,-96.561768 43.295895,-96.561718 43.295897,-96.56167 43.295888,-96.561621 43.295885,-96.561532 43.295933,-96.561488 43.295948,-96.561024 43.296066,-96.560879 43.296087,-96.5596 43.296141,-96.557881 43.296275,-96.557733 43.296281,-96.557294 43.296332,-96.557244 43.296335,-96.557096 43.296332,-96.556359 43.296373,-96.555736 43.296483,-96.55495 43.296547,-96.554902 43.296545,-96.554805 43.296532,-96.554712 43.296509,-96.554669 43.296491,-96.554632 43.296467,-96.554605 43.296437,-96.55459 43.29638,-96.554599 43.296237,-96.554626 43.296168,-96.554646 43.296136,-96.554751 43.296015,-96.555025 43.295764,-96.555092 43.295712,-96.555303 43.295563,-96.555396 43.295481,-96.555442 43.295418,-96.555454 43.295383,-96.555473 43.295241,-96.555466 43.29517,-96.555386 43.294891,-96.555351 43.294751,-96.555323 43.294682,-96.55498 43.294089,-96.554938 43.294025,-96.554738 43.293821,-96.554574 43.29369,-96.554243 43.293426,-96.554139 43.29335,-96.5541 43.293329,-96.55403 43.293279,-96.553955 43.293232,-96.553757 43.293126,-96.553589 43.293053,-96.553305 43.292911,-96.553128 43.292846,-96.553082 43.292835,-96.553036 43.292848,-96.55294 43.292864,-96.552693 43.29287,-96.552351 43.292838,-96.552252 43.292833,-96.552203 43.292834,-96.552056 43.29285,-96.551914 43.292877,-96.551639 43.292958,-96.551251 43.293009,-96.551205 43.29302,-96.550935 43.293108,-96.550896 43.293129,-96.550869 43.293159,-96.550799 43.293254,-96.550765 43.29332,-96.550742 43.293352,-96.550611 43.293696,-96.550579 43.293763,-96.550553 43.293794,-96.550494 43.29385,-96.550367 43.293959,-96.550152 43.294107,-96.550072 43.294148,-96.550035 43.294172,-96.54995 43.294209,-96.549548 43.294343,-96.549501 43.294355,-96.549452 43.29436,-96.549206 43.294371,-96.549058 43.29436,-96.548916 43.294332,-96.547882 43.294102,-96.547495 43.294041,-96.547397 43.294035,-96.547068 43.294052,-96.547053 43.294053,-96.546957 43.29407,-96.546401 43.294221,-96.545832 43.294436,-96.545699 43.294483,-96.545604 43.294502,-96.545411 43.294529,-96.545219 43.294562,-96.544775 43.294573,-96.544727 43.294581,-96.544692 43.294607,-96.544533 43.29469,-96.544485 43.294701,-96.544388 43.294709,-96.544146 43.294742,-96.544096 43.294745,-96.544047 43.294742,-96.54395 43.294729,-96.543821 43.294742,-96.543403 43.294852,-96.541856 43.295222,-96.541387 43.295331,-96.541342 43.295345,-96.541247 43.295365,-96.541156 43.295392,-96.540943 43.29548,-96.54086 43.295519,-96.540815 43.295533,-96.540774 43.295553,-96.540667 43.295626,-96.54057 43.295707,-96.540517 43.295767,-96.540498 43.2958,-96.54035 43.296129,-96.540315 43.296208,-96.540156 43.296507,-96.539655 43.297283,-96.53961 43.297346,-96.539473 43.297495,-96.539425 43.297557,-96.539262 43.297857,-96.539091 43.298184,-96.539053 43.298257,-96.539032 43.29829,-96.538839 43.298498,-96.538764 43.298544,-96.538717 43.298551,-96.538618 43.298555,-96.538569 43.298548,-96.538524 43.298535,-96.538138 43.298375,-96.537905 43.298317,-96.537856 43.29831,-96.537758 43.298309,-96.537512 43.298315,-96.537326 43.298361,-96.53634 43.29859,-96.535612 43.298697,-96.534929 43.298762,-96.53488 43.298764,-96.534588 43.298803,-96.533965 43.298916,-96.532976 43.299137,-96.531296 43.299558,-96.531018 43.299632,-96.530691 43.299713,-96.530467 43.299788,-96.530381 43.299823,-96.530195 43.299941,-96.530145 43.3,-96.530099 43.300063,-96.530082 43.300096,-96.53008 43.300274,-96.530087 43.30031,-96.530154 43.300405,-96.530302 43.300547,-96.530517 43.30073,-96.530718 43.300934,-96.531134 43.301287,-96.531603 43.301701,-96.531723 43.301814,-96.531921 43.30202,-96.532018 43.302144,-96.532058 43.302209,-96.532111 43.302309,-96.532222 43.302546,-96.532255 43.302866,-96.532255 43.302938,-96.532243 43.30308,-96.532175 43.303435,-96.532029 43.303924,-96.531905 43.304233,-96.53186 43.304296,-96.53183 43.304325,-96.531694 43.304428,-96.531654 43.30445,-96.531612 43.304467,-96.53114 43.304571,-96.530947 43.304588,-96.53085 43.304591,-96.530704 43.304607,-96.530221 43.304674,-96.529407 43.304933,-96.529359 43.30494,-96.529163 43.304947,-96.529016 43.304932,-96.528872 43.30491,-96.528827 43.304896,-96.528572 43.30479,-96.528491 43.304752,-96.528214 43.304677,-96.528116 43.304678,-96.527674 43.304701,-96.52763 43.304716,-96.527473 43.304802,-96.527431 43.304821,-96.527355 43.304867,-96.527055 43.305104,-96.527026 43.305132,-96.526962 43.305229,-96.526904 43.305365,-96.526869 43.305469,-96.526861 43.305611,-96.526901 43.305895,-96.526883 43.306037,-96.526885 43.306215,-96.526909 43.306284,-96.527091 43.306538,-96.52721 43.306682,-96.527305 43.306764,-96.527343 43.306787,-96.527588 43.306907,-96.527744 43.306995,-96.527825 43.307035,-96.527871 43.30705,-96.527965 43.307071,-96.52801 43.307086,-96.528086 43.307132,-96.528177 43.3072,-96.528325 43.307312,-96.528353 43.307342,-96.528571 43.307701,-96.52859 43.307878,-96.528598 43.307913,-96.528678 43.308043,-96.528887 43.308284,-96.529158 43.308972,-96.529165 43.309007,-96.529163 43.309042,-96.529154 43.309078,-96.529138 43.309111,-96.529105 43.30913,-96.529011 43.309153,-96.528877 43.309198,-96.528805 43.309247,-96.528761 43.309264,-96.528664 43.309277,-96.528617 43.309273,-96.528091 43.309078,-96.527807 43.309025,-96.527758 43.309026,-96.527713 43.30904,-96.527631 43.309078,-96.527572 43.309215,-96.527565 43.309286,-96.527499 43.30942,-96.527495 43.309455,-96.527458 43.309521,-96.527434 43.309552,-96.527417 43.309586,-96.527382 43.309726,-96.527359 43.309758,-96.527295 43.309812,-96.527217 43.309856,-96.52709 43.30991,-96.527044 43.309922,-96.526995 43.309928,-96.526945 43.309929,-96.526601 43.309903,-96.526527 43.309945,-96.526483 43.309961,-96.526435 43.309971,-96.52605 43.310013,-96.525809 43.310022,-96.525678 43.310045,-96.525475 43.310082,-96.525247 43.310146,-96.525153 43.310168,-96.525103 43.310171,-96.525005 43.310163,-96.524921 43.310205,-96.524596 43.310422,-96.524493 43.310499,-96.524433 43.310555,-96.524224 43.310797,-96.524159 43.310892,-96.524104 43.311029,-96.524057 43.311345,-96.524059 43.311381,-96.524095 43.311557,-96.524175 43.311726,-96.524195 43.311758,-96.524321 43.311911,-96.524485 43.312044,-96.524522 43.312067,-96.524614 43.312091,-96.524708 43.31211,-96.524895 43.312156,-96.525231 43.312209,-96.525373 43.31224,-96.525466 43.312265,-96.5261 43.312464,-96.526538 43.312627,-96.527127 43.312888,-96.527166 43.31291,-96.527383 43.313055,-96.52754 43.313192,-96.527606 43.313244,-96.527635 43.313273,-96.527731 43.313354,-96.527873 43.313453,-96.528114 43.313631,-96.528199 43.313719,-96.528314 43.313875,-96.52842 43.313996,-96.528441 43.314028,-96.528506 43.314236,-96.528532 43.314304,-96.528636 43.314504,-96.528662 43.314609,-96.528669 43.31468,-96.528639 43.314964,-96.528641 43.314999,-96.528682 43.315211,-96.528704 43.315281,-96.528853 43.315657,-96.529005 43.316217,-96.529011 43.316395,-96.529001 43.316428,-96.528942 43.316486,-96.528906 43.316509,-96.528865 43.316529,-96.528819 43.316544,-96.528674 43.316566,-96.528629 43.316559,-96.528508 43.316498,-96.528294 43.31641,-96.52822 43.316364,-96.528141 43.316321,-96.528067 43.316274,-96.527755 43.316045,-96.527683 43.315998,-96.527313 43.315824,-96.527177 43.315785,-96.527083 43.315766,-96.527034 43.315761,-96.526692 43.315794,-96.526646 43.315808,-96.526519 43.315863,-96.526503 43.315893,-96.526507 43.315929,-96.5265 43.315965,-96.526483 43.315998,-96.52645 43.316025,-96.526312 43.316214,-96.526061 43.31664,-96.525976 43.316681,-96.525939 43.316705,-96.52591 43.316734,-96.525866 43.316797,-96.525824 43.316899,-96.525737 43.317359,-96.525664 43.317998,-96.525623 43.318137,-96.525608 43.318208,-96.525603 43.318279,-96.525611 43.318492,-96.525622 43.318563,-96.525639 43.318633,-96.525653 43.318667,-96.525777 43.3189,-96.525801 43.318932,-96.52583 43.31896,-96.525899 43.31901,-96.525938 43.319033,-96.525985 43.319041,-96.526134 43.31904,-96.526277 43.319018,-96.526324 43.319006,-96.52642 43.31899,-96.526464 43.318975,-96.526731 43.318817,-96.526829 43.318828,-96.526879 43.318825,-96.526972 43.318805,-96.527062 43.31878,-96.52711 43.318775,-96.527277 43.318702,-96.527364 43.318673,-96.52741 43.318664,-96.527459 43.318661,-96.527551 43.318645,-96.527695 43.318632,-96.527849 43.318642,-96.527936 43.318648,-96.527985 43.318646,-96.528126 43.318667,-96.528171 43.318677,-96.52821 43.318696,-96.528259 43.318698,-96.528416 43.318762,-96.528464 43.318765,-96.528498 43.318788,-96.528585 43.31881,-96.52862 43.318833,-96.528667 43.318838,-96.528705 43.318857,-96.52884 43.318894,-96.528887 43.318901,-96.528927 43.318922,-96.528973 43.318936,-96.529021 43.318944,-96.529104 43.318947,-96.529206 43.31907,-96.529472 43.31928,-96.529531 43.319337,-96.529843 43.319784,-96.530035 43.31999,-96.530107 43.320039,-96.53019 43.320077,-96.530455 43.320237,-96.53051 43.320296,-96.530752 43.320471,-96.530939 43.320638,-96.530962 43.320669,-96.530975 43.320703,-96.530973 43.320775,-96.530935 43.320878,-96.530912 43.32091,-96.530881 43.320938,-96.530686 43.321044,-96.530495 43.321082,-96.52986 43.321147,-96.529789 43.321195,-96.529725 43.321248,-96.529689 43.321272,-96.529663 43.321303,-96.529647 43.321336,-96.529499 43.321559,-96.529332 43.322005,-96.529189 43.322308,-96.529159 43.322337,-96.529056 43.322413,-96.529009 43.322472,-96.52897 43.322649,-96.52895 43.322718,-96.528933 43.322825,-96.528901 43.323251,-96.528906 43.323892,-96.528959 43.32403,-96.528945 43.324061,-96.528926 43.324097,-96.52888 43.324161,-96.528866 43.324195,-96.528848 43.324337,-96.528865 43.324371,-96.52903 43.32455,-96.529552 43.324979,-96.530019 43.325245,-96.530385 43.325535,-96.530699 43.325857,-96.530798 43.325922,-96.531021 43.326001,-96.531468 43.326225,-96.531573 43.326301,-96.531604 43.326329,-96.531705 43.326453,-96.53181 43.326616,-96.531964 43.32688,-96.532071 43.327042,-96.532106 43.32711,-96.53218 43.32728,-96.532199 43.327313,-96.532607 43.327764,-96.532702 43.327891,-96.533189 43.328714,-96.533218 43.328783,-96.533224 43.328818,-96.533207 43.328962,-96.533187 43.328994,-96.532863 43.329311,-96.532819 43.329375,-96.53273 43.329542,-96.532689 43.329608,-96.532423 43.32999,-96.532375 43.330052,-96.531931 43.330692,-96.531477 43.3316,-96.531464 43.331667,-96.531458 43.331883,-96.531498 43.331986,-96.531604 43.332223,-96.531895 43.332756,-96.532078 43.333048,-96.532123 43.333112,-96.53237 43.333418,-96.532415 43.333481,-96.532491 43.333573,-96.532507 43.333606,-96.532501 43.33382,-96.53242 43.334245,-96.532418 43.334388,-96.532432 43.334458,-96.53245 43.334491,-96.53248 43.33452,-96.532516 43.334545,-96.532559 43.334561,-96.532607 43.334569,-96.532755 43.334576,-96.533396 43.334547,-96.533593 43.334549,-96.533642 43.334553,-96.534073 43.334636,-96.534117 43.334652,-96.534235 43.334716,-96.534429 43.334878,-96.534645 43.335159,-96.534914 43.33562,-96.534949 43.335687,-96.535089 43.336467,-96.535087 43.336502,-96.535074 43.336536,-96.535034 43.336639,-96.535022 43.336674,-96.534515 43.336937,-96.534072 43.337259,-96.533625 43.337408,-96.533494 43.337457,-96.533456 43.33748,-96.533329 43.337588,-96.533058 43.337795,-96.532531 43.33822,-96.532337 43.338406,-96.532324 43.33842,-96.532102 43.338657,-96.531665 43.339335,-96.531596 43.339544,-96.531561 43.339969,-96.531601 43.340143,-96.531632 43.340211,-96.53169 43.340309,-96.531719 43.340377,-96.531736 43.340446,-96.531856 43.340827,-96.531895 43.340931,-96.531936 43.340996,-96.532009 43.341128,-96.532087 43.341219,-96.532119 43.341246,-96.532416 43.341434,-96.53251 43.341517,-96.532631 43.341671,-96.532663 43.341699,-96.532828 43.341778,-96.53287 43.341917,-96.532897 43.341986,-96.532914 43.342092,-96.53292 43.342163,-96.532929 43.342198,-96.532931 43.342268,-96.532892 43.342406,-96.53288 43.342476,-96.532843 43.342616,-96.532829 43.34265,-96.532785 43.342714,-96.532759 43.342744,-96.532362 43.343107,-96.532283 43.34315,-96.532239 43.343166,-96.532146 43.343189,-96.532096 43.343193,-96.531851 43.3432,-96.531731 43.343137,-96.531699 43.343111,-96.531682 43.343077,-96.531658 43.343008,-96.531625 43.342511,-96.531607 43.342405,-96.531596 43.34237,-96.531546 43.342308,-96.531359 43.342143,-96.531058 43.34202,-96.531011 43.342009,-96.530383 43.34191,-96.530339 43.341916,-96.530213 43.341973,-96.530167 43.341986,-96.530118 43.341988,-96.529677 43.342036,-96.529282 43.34205,-96.529177 43.342068,-96.528661 43.342162,-96.528527 43.342207,-96.528444 43.342246,-96.528063 43.342472,-96.527919 43.34257,-96.527798 43.342683,-96.527719 43.342773,-96.527672 43.342835,-96.527643 43.342864,-96.527621 43.342897,-96.527505 43.343243,-96.527478 43.343311,-96.527459 43.343381,-96.527383 43.34377,-96.527372 43.343805,-96.52726 43.344004,-96.52711 43.344187,-96.527008 43.344311,-96.526682 43.3448,-96.526558 43.344987,-96.526276 43.345323,-96.526186 43.345408,-96.52609 43.345489,-96.525461 43.345944,-96.525092 43.34636,-96.524877 43.346605,-96.524567 43.346892,-96.524542 43.346915,-96.524376 43.347134,-96.524354 43.347203,-96.524339 43.347345,-96.524293 43.347388,-96.524279 43.347422,-96.524268 43.347492,-96.52427 43.347527,-96.52426 43.347597,-96.52429 43.347702,-96.52431 43.347735,-96.524337 43.347764,-96.524442 43.347841,-96.524452 43.34793,-96.524516 43.348176,-96.524531 43.34821,-96.524672 43.348356,-96.524706 43.348382,-96.524841 43.348427,-96.524984 43.348454,-96.525033 43.348454,-96.52523 43.348438,-96.525368 43.348399,-96.525411 43.348381,-96.525531 43.348319,-96.52563 43.348239,-96.525843 43.347999,-96.525976 43.347895,-96.526214 43.347767,-96.526259 43.347751,-96.526308 43.347745,-96.526652 43.34775,-96.526847 43.347769,-96.526896 43.347771,-96.527036 43.347807,-96.527126 43.347836,-96.527253 43.347891,-96.527372 43.347955,-96.527442 43.348005,-96.527643 43.348162,-96.527667 43.348194,-96.527704 43.34826,-96.527716 43.348295,-96.527766 43.348543,-96.527766 43.348578,-96.527758 43.348614,-96.527733 43.348682,-96.527593 43.348902,-96.527528 43.349007,-96.527459 43.349046,-96.527427 43.349073,-96.527403 43.349105,-96.527368 43.349171,-96.527288 43.3493,-96.527269 43.349369,-96.527268 43.349405,-96.527277 43.34944,-96.527294 43.349474,-96.527319 43.349505,-96.527337 43.349538,-96.527184 43.350042,-96.527146 43.350253,-96.527105 43.350274,-96.527069 43.350298,-96.527038 43.350326,-96.527015 43.350358,-96.52696 43.350531,-96.526958 43.350567,-96.526966 43.350637,-96.526965 43.350779,-96.52697 43.35085,-96.52698 43.350885,-96.527022 43.35095,-96.527037 43.350981,-96.527017 43.351088,-96.527022 43.351267,-96.527036 43.351299,-96.527064 43.351329,-96.527086 43.351361,-96.527044 43.351463,-96.527004 43.351528,-96.52692 43.351614,-96.52686 43.351668,-96.526718 43.351766,-96.526668 43.351827,-96.526638 43.351856,-96.526581 43.351992,-96.526555 43.352022,-96.526523 43.35205,-96.526486 43.352074,-96.526443 43.352092,-96.526386 43.352226,-96.526365 43.352259,-96.526218 43.3524,-96.526185 43.352426,-96.526035 43.352519,-96.525604 43.352759,-96.525542 43.352814,-96.525313 43.353089,-96.525142 43.353347,-96.525108 43.353472,-96.525105 43.353508,-96.525114 43.353579,-96.525176 43.353752,-96.525195 43.353785,-96.525262 43.353836,-96.525514 43.354006,-96.525582 43.354057,-96.5261 43.354388,-96.526165 43.354442,-96.526256 43.354527,-96.526653 43.354793,-96.527084 43.355138,-96.527145 43.355194,-96.52722 43.355286,-96.527386 43.355623,-96.527415 43.355691,-96.527684 43.35619,-96.527748 43.356325,-96.527826 43.356604,-96.527829 43.356675,-96.527825 43.356711,-96.527825 43.356996,-96.52781 43.357066,-96.527348 43.357974,-96.527304 43.358076,-96.527243 43.358321,-96.5272 43.358642,-96.527464 43.359655,-96.527487 43.359761,-96.527489 43.359796,-96.527475 43.360118,-96.527436 43.360329,-96.527411 43.360434,-96.52735 43.36057,-96.527291 43.360743,-96.52724 43.36099,-96.527233 43.361061,-96.527233 43.361133,-96.527222 43.361275,-96.527228 43.361382,-96.527261 43.361559,-96.527292 43.361664,-96.527557 43.362164,-96.527564 43.362199,-96.527569 43.362556,-96.527575 43.362591,-96.527589 43.362625,-96.527614 43.362657,-96.527644 43.362685,-96.527738 43.362708,-96.527787 43.362714,-96.527836 43.362713,-96.528023 43.362666,-96.528255 43.362532,-96.528428 43.362463,-96.528613 43.362414,-96.528692 43.362401,-96.528804 43.362384,-96.528853 43.362382,-96.529098 43.3624,-96.529143 43.362413,-96.529373 43.36255,-96.52946 43.362636,-96.529522 43.362772,-96.529539 43.36295,-96.529528 43.363057,-96.529507 43.363126,-96.529419 43.363366,-96.529391 43.36347,-96.52936 43.363681,-96.529388 43.364037,-96.529426 43.364248,-96.529439 43.364283,-96.529622 43.364493,-96.529644 43.364525,-96.529676 43.364553,-96.529784 43.364625,-96.52985 43.364677,-96.530344 43.365025,-96.530403 43.365083,-96.530537 43.365232,-96.530676 43.36542,-96.530821 43.365648,-96.530838 43.365681,-96.530867 43.365785,-96.530907 43.365997,-96.530873 43.366887,-96.53088 43.366975,-96.530852 43.36726,-96.530921 43.367685,-96.530936 43.367755,-96.530942 43.367817,-96.530944 43.367826,-96.530968 43.36822,-96.530958 43.368397,-96.530961 43.368433,-96.530874 43.36871,-96.530724 43.368891,-96.530422 43.369072,-96.530217 43.369172,-96.529992 43.369245,-96.529944 43.369253,-96.529401 43.3693,-96.529306 43.369309,-96.528911 43.369317,-96.528715 43.369298,-96.528557 43.369257,-96.528417 43.369197,-96.528271 43.3691,-96.528144 43.368991,-96.528123 43.368959,-96.528108 43.368925,-96.528035 43.368718,-96.52801 43.368612,-96.527996 43.368399,-96.527982 43.368365,-96.527903 43.368274,-96.527872 43.368246,-96.527823 43.368185,-96.52779 43.368159,-96.527747 43.368141,-96.52765 43.368127,-96.527109 43.368107,-96.52692 43.368114,-96.526911 43.368115,-96.526766 43.368136,-96.526718 43.368147,-96.526552 43.368223,-96.526433 43.368286,-96.526365 43.368335,-96.526006 43.368571,-96.52598 43.368601,-96.525963 43.368635,-96.525944 43.368741,-96.525946 43.368776,-96.525937 43.368808,-96.525852 43.368896,-96.52583 43.368928,-96.525802 43.368958,-96.525768 43.368984,-96.525711 43.369042,-96.525559 43.369182,-96.525314 43.369358,-96.525247 43.369411,-96.524841 43.369769,-96.524762 43.369859,-96.524751 43.369929,-96.524751 43.370071,-96.524763 43.370106,-96.524828 43.37024,-96.524887 43.370376,-96.525147 43.370637,-96.525267 43.37075,-96.525323 43.370809,-96.525468 43.370996,-96.525484 43.37103,-96.525493 43.371065,-96.525491 43.371101,-96.525499 43.371208,-96.52549 43.371242,-96.525381 43.371442,-96.525361 43.371548,-96.525343 43.371619,-96.525138 43.372281,-96.52505 43.372485,-96.52501 43.372551,-96.524982 43.37258,-96.524911 43.372629,-96.524796 43.372697,-96.524751 43.372712,-96.524658 43.372735,-96.524509 43.372736,-96.524461 43.372731,-96.524373 43.372698,-96.524333 43.372676,-96.524298 43.372651,-96.524136 43.372509,-96.523697 43.372127,-96.523568 43.372019,-96.523531 43.371996,-96.523464 43.371944,-96.52339 43.371896,-96.5232 43.371784,-96.523165 43.371759,-96.523125 43.371738,-96.523076 43.371733,-96.522829 43.371726,-96.522733 43.371741,-96.522686 43.371752,-96.522463 43.371829,-96.522194 43.371985,-96.522163 43.372013,-96.522059 43.37219,-96.522046 43.372224,-96.52202 43.372403,-96.52204 43.372432,-96.522076 43.372458,-96.522106 43.372486,-96.522124 43.37252,-96.522146 43.372589,-96.52216 43.37273,-96.522175 43.372801,-96.522195 43.372978,-96.522182 43.373192,-96.522155 43.373333,-96.522041 43.373568,-96.521889 43.373946,-96.521834 43.37397,-96.521796 43.373992,-96.521765 43.374021,-96.521725 43.374085,-96.521677 43.374185,-96.52166 43.374254,-96.521644 43.374288,-96.521625 43.374358,-96.521652 43.374499,-96.521686 43.374566,-96.521662 43.374653,-96.521531 43.374922,-96.521519 43.374957,-96.521435 43.375381,-96.521422 43.375415,-96.521349 43.375696,-96.521352 43.375839,-96.521358 43.375874,-96.521383 43.375905,-96.521867 43.376402,-96.521968 43.376481,-96.522006 43.376503,-96.522052 43.376516,-96.522139 43.376549,-96.522178 43.376572,-96.522212 43.376598,-96.522475 43.376758,-96.522701 43.376991,-96.522863 43.377127,-96.523005 43.377273,-96.522999 43.377312,-96.523005 43.377383,-96.523087 43.377551,-96.523112 43.377582,-96.523147 43.377607,-96.523275 43.377661,-96.523367 43.377687,-96.523611 43.377994,-96.523979 43.3785,-96.524027 43.378601,-96.524047 43.378671,-96.524073 43.378739,-96.524133 43.378837,-96.524351 43.379076,-96.524373 43.379108,-96.524405 43.379175,-96.524509 43.379705,-96.524517 43.379776,-96.524512 43.379812,-96.524448 43.379984,-96.524334 43.380182,-96.524309 43.380213,-96.524008 43.380449,-96.523936 43.380497,-96.523813 43.380556,-96.523235 43.380758,-96.522334 43.380951,-96.522289 43.380965,-96.522249 43.380987,-96.522144 43.381062,-96.521999 43.381207,-96.521978 43.381239,-96.521974 43.381274,-96.521983 43.38138,-96.522008 43.381448,-96.522077 43.381544,-96.522104 43.381572,-96.522141 43.381595,-96.522185 43.38161,-96.522233 43.381613,-96.522282 43.381606,-96.522469 43.381565,-96.522818 43.381437,-96.522906 43.381411,-96.522948 43.381391,-96.523038 43.381366,-96.523229 43.381346,-96.523323 43.381331,-96.523421 43.381327,-96.52347 43.381331,-96.523659 43.381367,-96.523751 43.381394,-96.523879 43.381446,-96.524077 43.38155,-96.524152 43.381595,-96.524459 43.381825,-96.524484 43.381856,-96.5245 43.381889,-96.524606 43.382271,-96.524589 43.382377,-96.524504 43.382617,-96.52448 43.382647,-96.524368 43.382717,-96.523968 43.382922,-96.52346 43.383256,-96.52322 43.383378,-96.522992 43.383513,-96.522581 43.383815,-96.522517 43.383867,-96.522441 43.383958,-96.522335 43.384077,-96.522241 43.384201,-96.521938 43.384525,-96.521917 43.384557,-96.521795 43.384711,-96.521747 43.384811,-96.521739 43.384846,-96.521756 43.385022,-96.521757 43.385093,-96.521704 43.385266,-96.521697 43.385301,-96.521671 43.385762,-96.521662 43.385905,-96.521686 43.386151,-96.521698 43.386229,-96.521792 43.386509,-96.521742 43.386612,-96.521722 43.386683,-96.52172 43.386755,-96.521734 43.386862,-96.52175 43.386932,-96.52181 43.38699,-96.521882 43.387085,-96.521912 43.387153,-96.521982 43.387362,-96.522038 43.387462,-96.522162 43.387659,-96.522191 43.387688,-96.522259 43.38774,-96.522251 43.387818,-96.522261 43.388107,-96.522288 43.388357,-96.52233 43.388499,-96.52237 43.388602,-96.522417 43.388704,-96.522459 43.38877,-96.522583 43.388926,-96.522613 43.388955,-96.522683 43.389007,-96.523124 43.389295,-96.523311 43.389416,-96.523377 43.38947,-96.523578 43.389716,-96.523622 43.389732,-96.523746 43.389792,-96.523782 43.389817,-96.523813 43.389845,-96.523861 43.389908,-96.524058 43.390197,-96.524098 43.390262,-96.524113 43.390296,-96.524307 43.391032,-96.524363 43.391132,-96.524434 43.391231,-96.524379 43.391551,-96.524074 43.392858,-96.524005 43.393212,-96.523993 43.393319,-96.523987 43.393499,-96.524 43.393606,-96.524001 43.393677,-96.523996 43.393713,-96.523976 43.393783,-96.524 43.393853,-96.524218 43.394698,-96.524275 43.394835,-96.524447 43.395171,-96.524677 43.395529,-96.524702 43.39556,-96.525149 43.395988,-96.525242 43.396072,-96.525445 43.396176,-96.525576 43.396227,-96.525974 43.396371,-96.526114 43.396406,-96.52706 43.396618,-96.527291 43.396684,-96.527872 43.396885,-96.528236 43.397072,-96.528852 43.397432,-96.529063 43.397526,-96.52924 43.397589,-96.529336 43.397607,-96.529583 43.397624,-96.529828 43.39761,-96.530007 43.397548,-96.530046 43.397527,-96.530138 43.397443,-96.530184 43.39738,-96.530195 43.397345,-96.530199 43.397309,-96.530195 43.397238,-96.530059 43.39669,-96.530016 43.396514,-96.53 43.396408,-96.530015 43.396157,-96.530082 43.395985,-96.530123 43.39592,-96.530174 43.395859,-96.530324 43.395716,-96.530396 43.395667,-96.53064 43.395544,-96.530948 43.395429,-96.531314 43.395321,-96.531362 43.395311,-96.531996 43.395229,-96.533204 43.395094,-96.533269 43.395087,-96.533704 43.395018,-96.534691 43.394942,-96.535519 43.394836,-96.535742 43.394815,-96.536157 43.394777,-96.536552 43.394766,-96.536849 43.394773,-96.537045 43.394794,-96.537235 43.394833,-96.537373 43.394873,-96.537459 43.394909,-96.53771 43.395083,-96.537742 43.39511,-96.53788 43.395258,-96.538608 43.396354,-96.538663 43.396414,-96.53914 43.396825,-96.539527 43.397049,-96.540057 43.397243,-96.540194 43.397285,-96.540334 43.39732,-96.54091 43.397426,-96.540959 43.397432,-96.541205 43.397446,-96.54155 43.397424,-96.54169 43.397391,-96.54173 43.39737,-96.541764 43.397344,-96.541895 43.397193,-96.541972 43.397023,-96.54199 43.396953,-96.542083 43.396567,-96.542096 43.396533,-96.542119 43.396501,-96.542199 43.396411,-96.542449 43.396189,-96.542559 43.396117,-96.542639 43.396074,-96.542765 43.396019,-96.543004 43.395974,-96.543053 43.395968,-96.543399 43.395958,-96.543547 43.395966,-96.543887 43.396015,-96.543935 43.396025,-96.544211 43.396104,-96.544429 43.396189,-96.544716 43.396329,-96.5448 43.396366,-96.544921 43.396428,-96.545372 43.396757,-96.54543 43.396815,-96.545517 43.396943,-96.545588 43.397151,-96.5456 43.397222,-96.545605 43.397293,-96.545595 43.397436,-96.545525 43.39757,-96.545363 43.397831,-96.54515 43.398232,-96.544838 43.398948,-96.544774 43.399302,-96.544783 43.399694,-96.544881 43.400007,-96.54492 43.400073,-96.544948 43.400103,-96.544981 43.400129,-96.545039 43.400187,-96.545219 43.400309,-96.545303 43.400348,-96.545852 43.400509,-96.545896 43.400526,-96.546088 43.400639,-96.546417 43.400855,-96.546588 43.400984,-96.546644 43.401043,-96.546748 43.401165,-96.547117 43.401629,-96.547169 43.40173,-96.547246 43.401937,-96.547295 43.402038,-96.547669 43.402737,-96.547817 43.402963,-96.548043 43.40325,-96.548159 43.403397,-96.548349 43.403605,-96.548408 43.403662,-96.548476 43.403713,-96.548629 43.403802,-96.548702 43.403849,-96.548781 43.403892,-96.548825 43.403908,-96.548917 43.403933,-96.548961 43.403949,-96.549251 43.403993,-96.549336 43.403997,-96.549622 43.40394,-96.550458 43.403715,-96.550506 43.403706,-96.550653 43.40369,-96.550998 43.403692,-96.551691 43.403726,-96.551985 43.403756,-96.552702 43.403897,-96.55298 43.403971,-96.55351 43.404165,-96.553742 43.404226,-96.554241 43.404455,-96.554302 43.404511,-96.554329 43.404556,-96.554362 43.404609,-96.554376 43.404643,-96.554375 43.404786,-96.554353 43.404891,-96.554327 43.404943,-96.554267 43.405065,-96.554221 43.40524,-96.554218 43.405491,-96.554267 43.405738,-96.554432 43.406056,-96.554458 43.406106,-96.554485 43.406136,-96.55452 43.406161,-96.55469 43.406234,-96.554782 43.406258,-96.554924 43.406288,-96.55517 43.406295,-96.555318 43.406292,-96.555462 43.406266,-96.555497 43.40624,-96.555537 43.406219,-96.555582 43.406203,-96.555767 43.406155,-96.556343 43.406052,-96.55649 43.406037,-96.556539 43.406037,-96.556637 43.406027,-96.556686 43.406027,-96.556978 43.406062,-96.557023 43.406076,-96.557234 43.406166,-96.557305 43.406215,-96.557334 43.406244,-96.557385 43.406286,-96.557561 43.406431,-96.557653 43.406557,-96.557678 43.406626,-96.557737 43.406762,-96.557777 43.406902,-96.557847 43.407185,-96.557894 43.407468,-96.55799 43.40778,-96.558061 43.407913,-96.558127 43.408009,-96.558181 43.408069,-96.558335 43.408208,-96.558445 43.40828,-96.558675 43.40841,-96.558677 43.408412,-96.558718 43.408432,-96.559046 43.408648,-96.559246 43.408806,-96.559385 43.408954,-96.559474 43.409081,-96.559627 43.409421,-96.55965 43.40967,-96.55963 43.410741,-96.559689 43.411168,-96.559711 43.411274,-96.559876 43.411795,-96.55989 43.411829,-96.559987 43.411994,-96.560313 43.412352,-96.560425 43.41245,-96.560439 43.412462,-96.560514 43.412508,-96.560682 43.412584,-96.560911 43.412651,-96.560959 43.412659,-96.561301 43.412692,-96.561543 43.412721,-96.561592 43.412723,-96.561943 43.41271,-96.562725 43.412637,-96.56282 43.412618,-96.562918 43.412631,-96.563057 43.412666,-96.563106 43.412673,-96.563205 43.41267,-96.563303 43.412661,-96.563401 43.412658,-96.563779 43.412742,-96.563874 43.412772,-96.564051 43.412828,-96.564138 43.412861,-96.564391 43.412972,-96.564637 43.413094,-96.564793 43.413181,-96.564866 43.413228,-96.565046 43.413398,-96.565678 43.414367,-96.565704 43.414397,-96.566438 43.415116,-96.566691 43.41567,-96.566856 43.415867,-96.567046 43.416046,-96.568012 43.416721,-96.568087 43.416774,-96.568258 43.416903,-96.568584 43.417064,-96.56889 43.417181,-96.568936 43.417194,-96.569129 43.417227,-96.569372 43.417256,-96.569823 43.417397,-96.569873 43.417412,-96.570199 43.41748,-96.570255 43.417539,-96.57029 43.417564,-96.570331 43.417585,-96.571034 43.417843,-96.571158 43.417901,-96.571341 43.417953,-96.57139 43.417959,-96.571494 43.417956,-96.571947 43.418232,-96.572249 43.418354,-96.57273 43.418661,-96.572819 43.418691,-96.572866 43.418702,-96.573364 43.418857,-96.573483 43.41892,-96.573555 43.418969,-96.573585 43.418997,-96.573663 43.419088,-96.573726 43.419185,-96.573746 43.419255,-96.573749 43.419291,-96.573767 43.419361,-96.573764 43.419396,-96.573729 43.419536,-96.573693 43.419603,-96.573645 43.419664,-96.573573 43.419713,-96.573132 43.419942,-96.57305 43.419981,-96.572922 43.420035,-96.572803 43.420099,-96.572771 43.420126,-96.572656 43.420242,-96.572541 43.420589,-96.572522 43.420659,-96.572486 43.421195,-96.572469 43.421301,-96.572446 43.42137,-96.572427 43.421403,-96.572413 43.421439,-96.572403 43.421471,-96.57237 43.421538,-96.572178 43.421787,-96.572037 43.421934,-96.571749 43.422188,-96.5716 43.422414,-96.571545 43.422514,-96.57126 43.42293,-96.571179 43.42306,-96.570891 43.423551,-96.570479 43.424583,-96.570339 43.42485,-96.570136 43.425253,-96.57 43.425633,-96.569839 43.426265,-96.569806 43.426478,-96.569766 43.426799,-96.569794 43.427228,-96.569859 43.427509,-96.56988 43.427578,-96.569894 43.427649,-96.569922 43.428005,-96.569935 43.42804,-96.569986 43.42814,-96.570232 43.42849,-96.570289 43.428547,-96.570512 43.42869,-96.570638 43.428745,-96.570991 43.428874,-96.571085 43.428896,-96.571277 43.428931,-96.571323 43.428944,-96.571662 43.429093,-96.571862 43.429198,-96.57205 43.429313,-96.572128 43.429356,-96.572355 43.429426,-96.57259 43.429481,-96.57281 43.429563,-96.573257 43.429784,-96.573454 43.429891,-96.57352 43.429944,-96.573558 43.429968,-96.573624 43.43002,-96.573828 43.430265,-96.573982 43.430489,-96.574023 43.43054,-96.574009 43.430599,-96.573993 43.430705,-96.574059 43.430798,-96.57411 43.430855,-96.574179 43.430945,-96.574207 43.430972,-96.574252 43.431033,-96.57433 43.43112,-96.574401 43.431165,-96.574477 43.431205,-96.574508 43.43123,-96.57455 43.43125,-96.574645 43.431263,-96.574695 43.431266,-96.574744 43.431264,-96.574956 43.431413,-96.57535 43.431624,-96.575426 43.431669,-96.575593 43.431745,-96.575774 43.431799,-96.576103 43.431874,-96.57615 43.431882,-96.576349 43.431878,-96.576445 43.431863,-96.577348 43.431678,-96.577594 43.431672,-96.577741 43.431689,-96.577827 43.431724,-96.577969 43.431756,-96.578164 43.431777,-96.578312 43.431785,-96.579205 43.431778,-96.579497 43.431814,-96.579594 43.43183,-96.580021 43.431921,-96.580166 43.431945,-96.581091 43.432061,-96.581449 43.432086,-96.581745 43.4321,-96.581844 43.4321,-96.582625 43.432014,-96.582918 43.432039,-96.583216 43.432065,-96.583265 43.432061,-96.583409 43.432036,-96.583456 43.432024,-96.583681 43.43195,-96.58403 43.43175,-96.584321 43.431615,-96.584367 43.431603,-96.58461 43.431575,-96.584659 43.431574,-96.584903 43.431595,-96.585107 43.431696,-96.58515 43.431713,-96.585308 43.431798,-96.585808 43.432086,-96.585886 43.432128,-96.585982 43.432146,-96.586031 43.43215,-96.586223 43.432178,-96.586272 43.432175,-96.586316 43.432158,-96.586407 43.432133,-96.586449 43.432113,-96.586486 43.432089,-96.586884 43.431726,-96.586992 43.431653,-96.587072 43.431611,-96.587256 43.43156,-96.587345 43.43153,-96.587392 43.431518,-96.587703 43.431464,-96.588047 43.431465,-96.588379 43.431531,-96.588424 43.431547,-96.588607 43.431598,-96.588691 43.431636,-96.588921 43.43177,-96.589544 43.432202,-96.589706 43.432314,-96.589827 43.432375,-96.589913 43.43241,-96.590052 43.432445,-96.590171 43.432469,-96.590194 43.432474,-96.590341 43.432487,-96.591325 43.432539,-96.591519 43.432567,-96.592217 43.432741,-96.592501 43.432813,-96.592765 43.43291,-96.592807 43.432929,-96.592874 43.432951,-96.593141 43.433045,-96.593247 43.433101,-96.593339 43.433151,-96.593444 43.433227,-96.593661 43.433422,-96.59391 43.433594,-96.594038 43.433702,-96.594141 43.433824,-96.594168 43.433872,-96.594208 43.433944,-96.594312 43.434249,-96.59438 43.434675,-96.594381 43.434852,-96.594375 43.434887,-96.594299 43.435056,-96.59426 43.435121,-96.594207 43.435195,-96.594224 43.435228,-96.59423 43.435264,-96.594213 43.43537,-96.594115 43.435532,-96.594068 43.435595,-96.594016 43.435655,-96.593948 43.435705,-96.593921 43.435734,-96.593823 43.435814,-96.593781 43.435833,-96.593706 43.435854,-96.593674 43.435915,-96.593596 43.436157,-96.593563 43.436405,-96.593574 43.436654,-96.59363 43.4369,-96.593661 43.436967,-96.593824 43.437187,-96.593999 43.437359,-96.594092 43.437421,-96.594359 43.437602,-96.594439 43.437643,-96.594482 43.43766,-96.594562 43.437702,-96.594687 43.437758,-96.594736 43.437765,-96.595378 43.43778,-96.595428 43.437784,-96.595566 43.437824,-96.596046 43.43801,-96.596086 43.438032,-96.596255 43.438162,-96.596284 43.438191,-96.596324 43.438256,-96.59634 43.438386,-96.596332 43.438457,-96.596306 43.438598,-96.596297 43.438704,-96.596299 43.43874,-96.596291 43.438811,-96.596292 43.438882,-96.596321 43.439131,-96.596367 43.439269,-96.596396 43.439337,-96.596404 43.439372,-96.596456 43.439473,-96.59653 43.439565,-96.596571 43.439586,-96.597303 43.439882,-96.597417 43.439951,-96.597477 43.440008,-96.597499 43.44004,-96.597545 43.440179,-96.597541 43.440214,-96.597522 43.440284,-96.597506 43.440318,-96.597489 43.440387,-96.59747 43.44042,-96.597414 43.440479,-96.597344 43.440528,-96.597085 43.440694,-96.596755 43.44096,-96.596716 43.440982,-96.596508 43.441079,-96.596475 43.441106,-96.596433 43.44117,-96.596363 43.441304,-96.596354 43.441339,-96.596331 43.44148,-96.596344 43.441874,-96.596367 43.442087,-96.596392 43.442156,-96.596416 43.442188,-96.596502 43.442275,-96.59654 43.442298,-96.596584 43.442313,-96.596632 43.442323,-96.59683 43.442326,-96.597027 43.44231,-96.597074 43.442301,-96.597119 43.442286,-96.597201 43.442246,-96.597329 43.442191,-96.597446 43.442127,-96.597713 43.44197,-96.597796 43.441932,-96.597841 43.441917,-96.597981 43.441883,-96.59803 43.441877,-96.598324 43.441879,-96.598422 43.44189,-96.598729 43.442002,-96.59881 43.442041,-96.598888 43.442084,-96.59893 43.442102,-96.598969 43.442124,-96.59904 43.442173,-96.599391 43.442471,-96.599443 43.442527,-96.599446 43.44253,-96.599571 43.442722,-96.599659 43.442888,-96.599671 43.442923,-96.599691 43.443029,-96.5997 43.443279,-96.599671 43.44349,-96.59966 43.443525,-96.599619 43.44359,-96.59957 43.443651,-96.59954 43.443718,-96.599518 43.443787,-96.599397 43.444094,-96.59935 43.444157,-96.599311 43.444179,-96.599089 43.444258,-96.599052 43.444281,-96.598943 43.444441,-96.598921 43.444582,-96.598905 43.444652,-96.598902 43.444794,-96.598906 43.44483,-96.598925 43.444899,-96.598995 43.445069,-96.599043 43.445205,-96.599072 43.445273,-96.599091 43.445306,-96.599166 43.445398,-96.599198 43.445425,-96.599235 43.445449,-96.599276 43.445469,-96.599494 43.44555,-96.599729 43.445633,-96.599809 43.445675,-96.599893 43.445712,-96.600011 43.445776,-96.600076 43.445828,-96.600355 43.446027,-96.600404 43.446088,-96.600422 43.446121,-96.600503 43.446322,-96.600521 43.44639,-96.600605 43.446591,-96.600629 43.446622,-96.600838 43.446814,-96.600888 43.446875,-96.600919 43.446902,-96.600959 43.446954,-96.601159 43.447189,-96.601246 43.447349,-96.601302 43.447443,-96.601328 43.447544,-96.601346 43.447645,-96.601344 43.447679,-96.601355 43.44778,-96.601367 43.447848,-96.601372 43.44795,-96.601366 43.447978,-96.601354 43.448054,-96.601352 43.448125,-96.60145 43.448206,-96.601493 43.44827,-96.601587 43.448353,-96.601623 43.448377,-96.602029 43.448578,-96.602105 43.448624,-96.602138 43.44865,-96.602194 43.448707,-96.602299 43.448826,-96.602342 43.448889,-96.602374 43.448955,-96.602547 43.449249,-96.602679 43.449663,-96.602717 43.449766,-96.60289 43.450168,-96.602942 43.450304,-96.603031 43.450581,-96.603056 43.450793,-96.603051 43.450828,-96.603034 43.450862,-96.602953 43.450951,-96.60291 43.450966,-96.60286 43.450967,-96.602664 43.450944,-96.602578 43.45091,-96.60254 43.450887,-96.602479 43.450833,-96.602452 43.450802,-96.602389 43.450748,-96.602152 43.450477,-96.601998 43.45034,-96.601718 43.450141,-96.601678 43.45012,-96.601635 43.450103,-96.601218 43.44999,-96.601073 43.449972,-96.600975 43.449976,-96.60083 43.449988,-96.600734 43.450002,-96.600651 43.45004,-96.600463 43.450154,-96.600422 43.450174,-96.600259 43.450309,-96.600017 43.450577,-96.599995 43.450609,-96.599889 43.450808,-96.599878 43.450843,-96.599847 43.45091,-96.599776 43.451116,-96.59967 43.451536,-96.599603 43.451706,-96.599587 43.451776,-96.599586 43.451811,-96.599644 43.452058,-96.599644 43.452094,-96.599619 43.452163,-96.599606 43.452233,-96.59973 43.452317,-96.59976 43.452346,-96.599781 43.452377,-96.599813 43.452445,-96.599833 43.452478,-96.599943 43.452547,-96.600062 43.452662,-96.600148 43.452788,-96.600186 43.452854,-96.600318 43.452959,-96.600343 43.45299,-96.600706 43.453372,-96.600719 43.453405,-96.600719 43.453476,-96.600728 43.453512,-96.600806 43.453642,-96.60087 43.453777,-96.600968 43.454125,-96.600983 43.454159,-96.601168 43.454448,-96.60123 43.454584,-96.601238 43.454619,-96.601267 43.454904,-96.601261 43.455046,-96.601246 43.455117,-96.601153 43.455467,-96.601139 43.455537,-96.601038 43.455777,-96.600949 43.455943,-96.600829 43.456099,-96.600718 43.456217,-96.600695 43.456248,-96.600592 43.45645,-96.60057 43.456482,-96.600407 43.456659,-96.600358 43.456719,-96.600148 43.45692,-96.600111 43.456943,-96.60007 43.456963,-96.599931 43.457002,-96.599896 43.457046,-96.599866 43.457075,-96.599832 43.457102,-96.599793 43.457122,-96.599697 43.457137,-96.599648 43.45714,-96.599602 43.457132,-96.599506 43.457131,-96.599409 43.457121,-96.599271 43.457084,-96.599093 43.457028,-96.598775 43.456938,-96.598688 43.456909,-96.59855 43.456872,-96.598403 43.456861,-96.598355 43.456862,-96.598223 43.456912,-96.59814 43.45695,-96.59811 43.456979,-96.59807 43.457045,-96.598044 43.457162,-96.598051 43.457196,-96.598076 43.457226,-96.598092 43.45726,-96.598099 43.457295,-96.598069 43.457472,-96.59805 43.457542,-96.598037 43.457576,-96.597897 43.457765,-96.597743 43.457903,-96.597589 43.458085,-96.597493 43.458207,-96.597433 43.458263,-96.597355 43.458352,-96.597268 43.458438,-96.597139 43.458545,-96.597015 43.458604,-96.596923 43.458629,-96.596742 43.458687,-96.596694 43.458695,-96.5964 43.458716,-96.596353 43.458705,-96.596172 43.45865,-96.596016 43.458564,-96.595974 43.458545,-96.595902 43.458548,-96.595805 43.458545,-96.595757 43.458533,-96.595721 43.458513,-96.595673 43.458506,-96.595596 43.458467,-96.595502 43.458457,-96.595406 43.45844,-96.595165 43.458429,-96.59507 43.458435,-96.594973 43.458434,-96.594924 43.458437,-96.594876 43.458429,-96.594831 43.458415,-96.594747 43.458377,-96.59463 43.458393,-96.594599 43.458427,-96.594555 43.458492,-96.594522 43.458517,-96.594314 43.458611,-96.594238 43.458656,-96.594129 43.458728,-96.594037 43.45881,-96.593999 43.458832,-96.593952 43.458844,-96.593903 43.458851,-96.593853 43.458852,-96.593669 43.459036,-96.59358 43.459163,-96.593601 43.459232,-96.593602 43.459268,-96.593476 43.45965,-96.593428 43.459969,-96.593429 43.459975,-96.59344 43.460183,-96.59348 43.46043,-96.59349 43.460465,-96.5936 43.460702,-96.593625 43.460732,-96.593691 43.460786,-96.593727 43.46081,-96.594069 43.460953,-96.594145 43.460999,-96.594317 43.461127,-96.594342 43.461158,-96.594402 43.461214,-96.594455 43.461274,-96.594687 43.461591,-96.594735 43.461692,-96.594743 43.461805,-96.594653 43.46223,-96.594572 43.46231,-96.594506 43.462363,-96.594351 43.462502,-96.594174 43.462626,-96.594133 43.462646,-96.593675 43.462783,-96.593333 43.462825,-96.59329 43.46284,-96.593251 43.462862,-96.593206 43.462878,-96.593158 43.462888,-96.59306 43.462892,-96.59262 43.462846,-96.592318 43.462802,-96.591501 43.462686,-96.591205 43.46267,-96.591106 43.462675,-96.59079 43.462778,-96.590551 43.462903,-96.590259 43.463235,-96.590203 43.463335,-96.590149 43.463472,-96.590136 43.463543,-96.590134 43.463614,-96.59007 43.463668,-96.590047 43.4637,-96.590035 43.463735,-96.590014 43.463876,-96.590009 43.464267,-96.590031 43.464408,-96.59003 43.464444,-96.590075 43.464545,-96.590095 43.464578,-96.590121 43.464608,-96.590186 43.464663,-96.590201 43.464693,-96.590205 43.464836,-96.590202 43.464907,-96.59006 43.465322,-96.590012 43.465534,-96.589989 43.465603,-96.589919 43.465773,-96.589872 43.465911,-96.589819 43.46601,-96.589705 43.466204,-96.589623 43.466293,-96.589503 43.466404,-96.589436 43.466456,-96.589362 43.466503,-96.589235 43.466557,-96.588836 43.466694,-96.588787 43.466699,-96.58859 43.466689,-96.588542 43.466682,-96.588463 43.466639,-96.588394 43.466587,-96.58837 43.466556,-96.588336 43.466489,-96.588309 43.466383,-96.588313 43.466312,-96.588366 43.466138,-96.588416 43.466038,-96.588745 43.465597,-96.588763 43.465564,-96.588768 43.465528,-96.588767 43.465458,-96.58878 43.465317,-96.588777 43.465246,-96.588769 43.465211,-96.58875 43.465179,-96.588702 43.465116,-96.588526 43.464946,-96.588423 43.464871,-96.588293 43.464821,-96.588194 43.46478,-96.587867 43.464646,-96.587778 43.464616,-96.587684 43.464594,-96.58764 43.464579,-96.587545 43.464559,-96.587205 43.464513,-96.587155 43.464513,-96.587058 43.464503,-96.587008 43.464502,-96.58691 43.464509,-96.586716 43.464538,-96.586577 43.464574,-96.586532 43.464589,-96.586256 43.46474,-96.586175 43.46478,-96.585954 43.464858,-96.585813 43.464891,-96.585765 43.464899,-96.585568 43.464895,-96.585387 43.464949,-96.585259 43.465002,-96.585215 43.465029,-96.585147 43.465081,-96.584969 43.465205,-96.584694 43.465356,-96.584592 43.465434,-96.584564 43.465463,-96.584368 43.465711,-96.584335 43.465737,-96.583858 43.46605,-96.583515 43.466254,-96.583308 43.466352,-96.583265 43.466368,-96.582976 43.466421,-96.582882 43.466442,-96.58267 43.466533,-96.58245 43.466677,-96.582419 43.466706,-96.582402 43.466739,-96.582401 43.466846,-96.582417 43.466953,-96.582427 43.466988,-96.582475 43.467051,-96.582624 43.467193,-96.583118 43.467566,-96.583377 43.467761,-96.583477 43.467841,-96.583879 43.468373,-96.583908 43.468402,-96.58398 43.468495,-96.584186 43.468818,-96.584265 43.468909,-96.584385 43.469022,-96.584373 43.469078,-96.584382 43.46915,-96.58456 43.469482,-96.584656 43.469718,-96.584691 43.46982,-96.58474 43.469882,-96.584852 43.469989,-96.584918 43.470052,-96.584996 43.470097,-96.585083 43.47013,-96.585247 43.470691,-96.585292 43.470974,-96.585302 43.47108,-96.5853 43.471115,-96.585306 43.471186,-96.585319 43.471257,-96.585312 43.4714,-96.585302 43.471435,-96.585285 43.471469,-96.585249 43.471512,-96.585011 43.471624,-96.584917 43.471698,-96.584804 43.471881,-96.584795 43.47191,-96.584794 43.471955,-96.584801 43.472122,-96.58473 43.472221,-96.584348 43.472794,-96.584161 43.473516,-96.583702 43.474302,-96.583685 43.474336,-96.583639 43.474399,-96.583626 43.474434,-96.583622 43.474469,-96.583634 43.474576,-96.583741 43.475106,-96.583783 43.475171,-96.583831 43.475233,-96.58385 43.475266,-96.583875 43.475297,-96.584126 43.475563,-96.584157 43.475591,-96.584409 43.475762,-96.584494 43.475799,-96.584779 43.475939,-96.585076 43.476068,-96.585394 43.476168,-96.585625 43.476302,-96.585771 43.476399,-96.585804 43.476426,-96.586535 43.477145,-96.58672 43.477357,-96.586743 43.477389,-96.586812 43.477523,-96.586864 43.477661,-96.586893 43.477802,-96.586892 43.477873,-96.586865 43.477978,-96.58684 43.478047,-96.58672 43.478258,-96.586709 43.478278,-96.586622 43.478406,-96.58657 43.478466,-96.586549 43.478499,-96.58652 43.478528,-96.586417 43.478604,-96.586306 43.478675,-96.586219 43.478709,-96.58566 43.478856,-96.585464 43.478965,-96.584948 43.479179,-96.58471 43.479307,-96.584492 43.479452,-96.584423 43.479503,-96.584118 43.479782,-96.584004 43.479851,-96.583613 43.480005,-96.58271 43.480197,-96.582647 43.480333,-96.582564 43.480422,-96.582504 43.480477,-96.582469 43.480503,-96.58243 43.480524,-96.582325 43.480598,-96.582198 43.480652,-96.582103 43.480673,-96.581807 43.480673,-96.581711 43.48069,-96.581661 43.480689,-96.581467 43.480817,-96.581246 43.481054,-96.581206 43.481118,-96.58118 43.481149,-96.581084 43.481462,-96.581082 43.481498,-96.581088 43.481569,-96.581198 43.481769,-96.581374 43.481942,-96.581399 43.481972,-96.581808 43.482624,-96.581919 43.482832,-96.582058 43.48309,-96.5822 43.483278,-96.58223 43.483306,-96.58238 43.483399,-96.582422 43.483418,-96.582667 43.483444,-96.582714 43.483431,-96.582889 43.483364,-96.58293 43.483343,-96.583113 43.483224,-96.583153 43.483203,-96.583296 43.483172,-96.583392 43.483157,-96.583689 43.483154,-96.583837 43.483163,-96.584076 43.483208,-96.584727 43.483373,-96.584822 43.483392,-96.585017 43.483414,-96.585311 43.483432,-96.585361 43.48343,-96.585903 43.483435,-96.586002 43.483433,-96.586144 43.483463,-96.586275 43.483514,-96.586358 43.483551,-96.586404 43.483564,-96.586446 43.483584,-96.586599 43.483675,-96.586721 43.483781,-96.586787 43.483833,-96.58683 43.483898,-96.586931 43.484137,-96.587002 43.48427,-96.587166 43.484448,-96.587225 43.484506,-96.587262 43.484572,-96.587293 43.484677,-96.587288 43.484748,-96.587261 43.484853,-96.587213 43.484915,-96.587124 43.485001,-96.587055 43.485052,-96.586977 43.485096,-96.586941 43.485121,-96.586862 43.485164,-96.586409 43.485383,-96.586371 43.485406,-96.58631 43.485462,-96.58625 43.48556,-96.586191 43.485807,-96.586203 43.486021,-96.586242 43.486161,-96.586258 43.486195,-96.586341 43.486325,-96.58651 43.486544,-96.586576 43.486657,-96.586838 43.487041,-96.586995 43.487262,-96.587013 43.487296,-96.587025 43.487367,-96.587002 43.487579,-96.586957 43.487643,-96.586924 43.48767,-96.586814 43.487742,-96.586728 43.487776,-96.586536 43.487811,-96.586438 43.487815,-96.5859 43.487856,-96.585803 43.48787,-96.585565 43.487915,-96.585526 43.487927,-96.58552 43.48793,-96.585436 43.487967,-96.585125 43.488141,-96.58505 43.488187,-96.584958 43.488271,-96.584644 43.488715,-96.584615 43.488783,-96.584593 43.488852,-96.58455 43.489028,-96.584551 43.489099,-96.584589 43.489202,-96.584691 43.489364,-96.58474 43.489426,-96.584858 43.489689,-96.585443 43.490299,-96.585831 43.490503,-96.585954 43.4906,-96.586077 43.490697,-96.586232 43.490819,-96.586435 43.491022,-96.586741 43.491388,-96.586771 43.491417,-96.586915 43.491514,-96.587078 43.491595,-96.587169 43.491622,-96.587601 43.491699,-96.587699 43.491706,-96.587749 43.491705,-96.587995 43.491686,-96.588044 43.491678,-96.588326 43.491611,-96.588573 43.491618,-96.588816 43.491653,-96.589149 43.491723,-96.58959 43.491786,-96.589684 43.4918,-96.589965 43.491864,-96.590008 43.491881,-96.590043 43.491906,-96.590121 43.491949,-96.590156 43.491975,-96.590184 43.492005,-96.590204 43.492037,-96.590235 43.492105,-96.590242 43.492141,-96.59024 43.492176,-96.590208 43.492244,-96.590184 43.492275,-96.590027 43.492413,-96.589991 43.492438,-96.589911 43.492479,-96.589839 43.492529,-96.589815 43.492559,-96.589827 43.492666,-96.589825 43.492702,-96.589751 43.492946,-96.58977 43.49316,-96.589832 43.493403,-96.58983 43.493439,-96.589816 43.493473,-96.5898 43.493543,-96.589798 43.493579,-96.589784 43.493649,-96.5899 43.493806,-96.589954 43.493866,-96.590211 43.494123,-96.590459 43.494296,-96.590621 43.494375,-96.590699 43.494418,-96.590783 43.494453,-96.590877 43.494477,-96.59102 43.494503,-96.591118 43.494505,-96.59126 43.494477,-96.591536 43.494399,-96.592044 43.494264,-96.592378 43.494209,-96.592405 43.494179,-96.592438 43.494152,-96.592478 43.494131,-96.592608 43.494079,-96.592648 43.494059,-96.592734 43.494026,-96.592782 43.494016,-96.592852 43.494009,-96.592919 43.493956,-96.593225 43.493776,-96.593425 43.493671,-96.593711 43.49353,-96.59379 43.493486,-96.593982 43.493278,-96.594049 43.493225,-96.594086 43.493202,-96.594169 43.493162,-96.594213 43.493147,-96.594502 43.493096,-96.594551 43.493095,-96.594797 43.493114,-96.595034 43.493164,-96.595126 43.49319,-96.595521 43.493337,-96.596274 43.493683,-96.596507 43.493816,-96.59663 43.493895,-96.596727 43.49391,-96.596775 43.493921,-96.596819 43.493936,-96.597112 43.494069,-96.597284 43.494139,-96.59733 43.49417,-96.597375 43.494223,-96.597678 43.494344,-96.597723 43.494359,-96.597764 43.49438,-96.597987 43.494515,-96.59803 43.494541,-96.599013 43.495348,-96.59909 43.495439,-96.599206 43.495597,-96.599304 43.495721,-96.599348 43.495785,-96.599375 43.495815,-96.599454 43.495985,-96.599457 43.496068,-96.599452 43.496104,-96.599327 43.496486,-96.599133 43.496775,-96.599121 43.49681,-96.599088 43.497023,-96.599088 43.497273,-96.599011 43.4977,-96.598995 43.497937,-96.59899 43.498019,-96.59899 43.49809,-96.598962 43.498195,-96.598881 43.498436,-96.598874 43.498508,-96.598904 43.498632,-96.598961 43.498864,-96.599012 43.499073,-96.59902 43.499106,-96.59903 43.499356,-96.599036 43.499391,-96.599099 43.499564,-96.599105 43.499707,-96.599116 43.499923,-96.599117 43.499955,-96.599122 43.500054,-96.599124 43.500087,-96.599125 43.500118,-96.599128 43.500171,-96.599137 43.500213,-96.599145 43.500245,-96.59915 43.500266,-96.599165 43.500331,-96.599171 43.500353,-96.599175 43.500373,-96.599186 43.500418,-96.599188 43.500435,-96.599191 43.500456,-96.599137 43.500456,-96.599059 43.500456,-96.598978 43.500456,-96.598926 43.500457,-96.598716 43.500457,-96.598089 43.500459,-96.59788 43.50046,-96.597455 43.500461,-96.596183 43.500464,-96.595759 43.500466,-96.595484 43.500468,-96.59466 43.500476,-96.594386 43.50048,-96.594136 43.500501,-96.593978 43.500515,-96.593723 43.50051,-96.593385 43.500485,-96.593136 43.500467,-96.592765 43.500464,-96.591652 43.500456,-96.591282 43.500454,-96.586367 43.500446,-96.581159 43.50044,-96.577873 43.500444,-96.573946 43.500447,-96.569957 43.500441,-96.566325 43.500437,-96.565917 43.500436,-96.560373 43.500423,-96.555266 43.500422,-96.554527 43.500422,-96.55231 43.500422,-96.551572 43.500422,-96.550547 43.50042,-96.547472 43.500414,-96.546448 43.500413,-96.546432 43.500413,-96.546384 43.500413,-96.546368 43.500413,-96.544262 43.500408,-96.542993 43.500406,-96.539026 43.500423,-96.537948 43.500421,-96.535843 43.50042,-96.535008 43.500419,-96.532701 43.500418,-96.532504 43.500418,-96.53167 43.500421,-96.52648 43.500434,-96.525311 43.500436,-96.522852 43.500412,-96.521272 43.500397,-96.518097 43.500407,-96.513824 43.500395,-96.513087 43.500393,-96.511725 43.500395,-96.509441 43.500397,-96.508257 43.500407,-96.50727 43.500396,-96.504239 43.500403,-96.504007 43.500403,-96.497753 43.500389,-96.496219 43.500385,-96.491748 43.500389,-96.490724 43.500389,-96.487652 43.500391,-96.486629 43.500392,-96.486383 43.500392,-96.485648 43.500392,-96.485403 43.500392,-96.484077 43.500392,-96.480102 43.500394,-96.478777 43.500395,-96.478358 43.500395,-96.473865 43.500393,-96.47187 43.500393,-96.471391 43.500393,-96.471223 43.500393,-96.470722 43.500393,-96.470555 43.500393,-96.466737 43.500393,-96.46541 43.500393,-96.462449 43.500393,-96.454174 43.500427,-96.454074 43.500427,-96.453975 43.500426,-96.45326 43.50039,-96.453255 43.50039,-96.446758 43.500411,-96.445288 43.500403,-96.440878 43.500379,-96.439408 43.500372,-96.437929 43.50036,-96.437617 43.500359,-96.433493 43.500352,-96.43203 43.50035,-96.432015 43.500351,-96.431584 43.500352,-96.43029 43.500356,-96.42986 43.500359,-96.427876 43.500357,-96.427351 43.500357,-96.423819 43.500372,-96.421925 43.500369,-96.419943 43.500367,-96.418366 43.50036,-96.415121 43.500349,-96.413643 43.500349,-96.41207 43.50035,-96.41207 43.500339,-96.411597 43.500338,-96.410176 43.500338,-96.409704 43.500338,-96.407555 43.500337,-96.407354 43.500336,-96.400304 43.500318,-96.400218 43.500318,-96.39799 43.50032,-96.397953 43.50032,-96.396786 43.500321,-96.393285 43.500325,-96.39212 43.50033,-96.39212 43.500323,-96.391716 43.500322,-96.390507 43.500324,-96.390105 43.500325,-96.388165 43.50033,-96.387917 43.500332,-96.384336 43.500329,-96.382347 43.500333,-96.380409 43.500336,-96.378768 43.500331,-96.373849 43.500322,-96.372211 43.500319,-96.37221 43.50033,-96.371848 43.500329,-96.370765 43.500329,-96.370407 43.500331,-96.37035 43.500327,-96.370182 43.500318,-96.370127 43.500316,-96.368888 43.500309,-96.368738 43.500307,-96.364574 43.500299,-96.363187 43.500297,-96.362881 43.500298,-96.361969 43.500304,-96.361901 43.500305,-96.361665 43.500305,-96.361399 43.500303,-96.360603 43.500298,-96.360338 43.500297,-96.35973 43.500293,-96.35791 43.500284,-96.357304 43.500282,-96.35708 43.500283,-96.356415 43.50029,-96.356196 43.500295,-96.355123 43.500256,-96.352764 43.500178,-96.351905 43.500166,-96.351126 43.500158,-96.350845 43.500238,-96.348675 43.500256,-96.346331 43.500277,-96.342168 43.500334,-96.340001 43.500364,-96.33974 43.500364,-96.338958 43.500367,-96.338698 43.500369,-96.33737 43.500378,-96.333389 43.500405,-96.332062 43.500415,-96.332021 43.500414,-96.331898 43.500411,-96.331858 43.500411,-96.331418 43.500388,-96.330099 43.500322,-96.32966 43.5003,-96.326202 43.500285,-96.325227 43.500282,-96.315831 43.500297,-96.314966 43.500298,-96.312373 43.5003,-96.31234 43.5003,-96.310215 43.500307,-96.308656 43.500313,-96.303742 43.50031,-96.301586 43.50031,-96.300798 43.500308,-96.300236 43.500307,-96.298444 43.500303,-96.29766 43.500303,-96.296865 43.500298,-96.2965 43.500296,-96.295477 43.500296,-96.294483 43.500296,-96.293689 43.500297,-96.29343 43.500297,-96.293032 43.500299,-96.292653 43.5003,-96.292395 43.5003,-96.29239 43.5003,-96.290669 43.500295,-96.285497 43.50028,-96.283774 43.500277,-96.282206 43.500274,-96.277896 43.500269,-96.277505 43.500269,-96.275939 43.500271,-96.275379 43.500271,-96.273702 43.500272,-96.273566 43.500273,-96.273143 43.500273,-96.273105 43.500273,-96.272991 43.500273,-96.272954 43.500273,-96.272817 43.500273,-96.272408 43.500273,-96.272272 43.500273,-96.271295 43.500273,-96.270896 43.500274,-96.268366 43.500268,-96.26739 43.500267,-96.264678 43.500261,-96.264301 43.500261,-96.256598 43.500271,-96.256543 43.50027,-96.253832 43.500268,-96.253573 43.500265,-96.252799 43.500261,-96.25255 43.50026,-96.25254 43.500259,-96.250556 43.500252,-96.247406 43.500244,-96.246905 43.500251,-96.24518 43.50025,-96.244608 43.500252,-96.243655 43.500258,-96.242939 43.500241,-96.242627 43.500243,-96.24252 43.500245,-96.242204 43.500251,-96.242099 43.500255,-96.241792 43.500255,-96.24026 43.500252,-96.239154 43.500251,-96.2377 43.500256,-96.236759 43.500245,-96.236326 43.500253,-96.235921 43.500265,-96.234744 43.500259,-96.232906 43.50025,-96.232835 43.500249,-96.232783 43.50025,-96.232629 43.50025,-96.23256 43.50025,-96.23254 43.50025,-96.23208 43.500247,-96.230991 43.500256,-96.230326 43.500257,-96.229199 43.500258,-96.228044 43.500265,-96.226331 43.500258,-96.225576 43.500266,-96.224387 43.500273,-96.223632 43.500265,-96.223028 43.500258,-96.222076 43.500257,-96.221402 43.500261,-96.221396 43.500261,-96.220765 43.500264,-96.219924 43.500264,-96.219313 43.500266,-96.21806 43.500273,-96.216118 43.500275,-96.215491 43.500284,-96.215354 43.500287,-96.214014 43.50029,-96.213688 43.500284,-96.21272 43.50027,-96.212714 43.50027,-96.212389 43.500274,-96.211691 43.500283,-96.211217 43.500289,-96.210484 43.500303,-96.210253 43.500311,-96.2096 43.500341,-96.2094 43.50035,-96.208903 43.500365,-96.208558 43.500349,-96.207208 43.500333,-96.207175 43.500333,-96.202572 43.500315,-96.202121 43.500309,-96.200426 43.500289,-96.200162 43.500287,-96.199851 43.500285,-96.199563 43.500277,-96.199374 43.500275,-96.199275 43.500274,-96.199112 43.500256,-96.199043 43.500263,-96.198983 43.500267,-96.19875 43.500287,-96.198597 43.500289,-96.198469 43.500292,-96.198008 43.500299,-96.197913 43.500298,-96.196245 43.500289,-96.195689 43.500287,-96.195083 43.500279,-96.193324 43.500263,-96.193265 43.500262,-96.19269 43.50026,-96.192659 43.500261,-96.191021 43.500255,-96.190745 43.500254,-96.186111 43.500227,-96.184703 43.500219,-96.18459 43.500218,-96.184479 43.50022,-96.184365 43.500219,-96.18425 43.500219,-96.183695 43.50022,-96.182762 43.50022,-96.177617 43.500224,-96.177511 43.500224,-96.175903 43.500233,-96.175537 43.500228,-96.174439 43.500216,-96.174366 43.500215,-96.17422 43.500213,-96.174074 43.500211,-96.174003 43.50021,-96.17393 43.500209,-96.173785 43.500209,-96.173753 43.500209,-96.172872 43.500209,-96.17279 43.500209,-96.172469 43.500209,-96.17241 43.50021,-96.172344 43.500208,-96.171975 43.500201,-96.171853 43.5002,-96.171205 43.500202,-96.16868 43.5002,-96.164214 43.500199,-96.159165 43.500213,-96.155994 43.500222,-96.155684 43.500214,-96.154757 43.500195,-96.15445 43.50019,-96.154181 43.500189,-96.153377 43.500191,-96.15311 43.500194,-96.15293 43.50019,-96.152758 43.500189,-96.151706 43.50019,-96.151355 43.500191,-96.151342 43.50019,-96.151146 43.50019,-96.15052 43.500189,-96.150312 43.50019,-96.150287 43.50019,-96.148762 43.500187,-96.146913 43.500188,-96.142801 43.500191,-96.139072 43.500202,-96.136719 43.500191,-96.133323 43.50018,-96.133215 43.500179,-96.13317 43.500178,-96.132715 43.500179,-96.132564 43.50018,-96.13253 43.50018,-96.131825 43.500174,-96.13083 43.500171,-96.125628 43.500153,-96.123895 43.50015,-96.123159 43.500148,-96.120957 43.500145,-96.120224 43.500146,-96.120053 43.500153,-96.119541 43.500176,-96.119372 43.500184,-96.11878 43.500183,-96.118125 43.500183,-96.117004 43.500188,-96.116413 43.500191,-96.114193 43.500195,-96.112014 43.5002,-96.109246 43.500172,-96.107538 43.500166,-96.10532 43.50016,-96.10375 43.500163,-96.103495 43.500164,-96.09802 43.500184,-96.096197 43.500191,-96.095883 43.500186,-96.094944 43.500174,-96.094631 43.500171,-96.09462 43.50017,-96.094519 43.500168,-96.094183 43.500165,-96.094071 43.500165,-96.09335 43.50016,-96.092369 43.500155,-96.090864 43.500158,-96.085279 43.500169,-96.082795 43.500185,-96.081246 43.500185,-96.07804 43.500189,-96.077058 43.500183,-96.074115 43.500166,-96.073134 43.500161,-96.07312 43.50016,-96.070084 43.500155,-96.060936 43.500143,-96.057888 43.50014,-96.057436 43.500144,-96.056082 43.500158,-96.055632 43.500164,-96.055593 43.500163,-96.055476 43.500163,-96.055437 43.500163,-96.055027 43.500163,-96.053162 43.500155,-96.053162 43.500121,-96.050687 43.500125,-96.047966 43.500121,-96.046766 43.500118,-96.043445 43.500121,-96.041065 43.500114,-96.038215 43.500115,-96.038215 43.500116,-96.038181 43.500117,-96.036566 43.500106,-96.035696 43.5001,-96.033215 43.500102,-96.032078 43.500104,-96.028702 43.500108,-96.025692 43.500102,-96.02282 43.500108,-96.022223 43.500105,-96.020357 43.500097,-96.018242 43.500098,-96.016207 43.500101,-96.000333 43.500124,-96.000002 43.500126,-95.999002 43.500114,-95.998239 43.500113,-95.997146 43.500111,-95.99603 43.50011,-95.993351 43.500123,-95.993077 43.500124,-95.990095 43.500093,-95.981109 43.500068,-95.979671 43.500068,-95.97954 43.500067,-95.978286 43.500067,-95.978103 43.500067,-95.974901 43.500069,-95.973402 43.500065,-95.97247 43.500063,-95.968367 43.500069,-95.966619 43.500073,-95.964372 43.500077,-95.961302 43.500069,-95.960608 43.500069,-95.960608 43.50007,-95.960604 43.50007,-95.960599 43.500071,-95.958364 43.500077,-95.955872 43.500067,-95.953809 43.500074,-95.95344 43.500102,-95.952329 43.500096,-95.950935 43.500088,-95.950625 43.500086,-95.948335 43.500094,-95.947238 43.50009,-95.945792 43.500085,-95.943752 43.500086,-95.943301 43.500086,-95.94085 43.500106,-95.940504 43.500104,-95.940478 43.500104,-95.938236 43.500091,-95.937497 43.500093,-95.935959 43.50008,-95.935869 43.500079,-95.933729 43.500074,-95.933494 43.500074,-95.93225 43.500055,-95.925605 43.500036,-95.922595 43.500037,-95.920478 43.500042,-95.920478 43.500043,-95.920476 43.500043,-95.920458 43.500044,-95.918301 43.500051,-95.915353 43.500051,-95.912189 43.500039,-95.911659 43.50004,-95.909097 43.500042,-95.9073 43.50004,-95.903591 43.500035,-95.901052 43.500033,-95.90063 43.500033,-95.900622 43.500032,-95.900618 43.500032,-95.898728 43.500032,-95.896558 43.500043,-95.8936 43.500057,-95.88966 43.500049,-95.88855 43.500053,-95.885771 43.500048,-95.882707 43.500056,-95.880867 43.500071,-95.880867 43.500072,-95.880806 43.500073,-95.880136 43.500079,-95.87945 43.50008,-95.878544 43.50008,-95.878544 43.500081,-95.878493 43.500082,-95.878327 43.500082,-95.876243 43.500117,-95.873482 43.500092,-95.869043 43.50009,-95.866462 43.500059,-95.864835 43.500065,-95.863244 43.500043,-95.860947 43.500036,-95.859793 43.50003,-95.858315 43.500027,-95.858266 43.500027,-95.857028 43.500025,-95.855494 43.500017,-95.850959 43.500045,-95.849494 43.500048,-95.849366 43.500048,-95.849318 43.500048,-95.849006 43.500049,-95.844009 43.500024,-95.843998 43.500024,-95.843859 43.500023,-95.843799 43.500023,-95.843636 43.500022,-95.843562 43.500021,-95.843529 43.500021,-95.843495 43.500021,-95.843459 43.500021,-95.842941 43.500017,-95.841098 43.500018,-95.841089 43.500018,-95.839039 43.500023,-95.837089 43.500014,-95.833847 43.500009,-95.833813 43.500009,-95.831704 43.499997,-95.831694 43.499997,-95.831425 43.499996,-95.82977 43.500007,-95.827653 43.499996,-95.824663 43.499975,-95.821146 43.499992,-95.821129 43.499992,-95.820329 43.499994,-95.820294 43.499994,-95.817867 43.500001,-95.816114 43.499997,-95.815911 43.499996,-95.815613 43.499995,-95.815602 43.499995,-95.81543 43.499995,-95.812489 43.500002,-95.802763 43.499975,-95.802742 43.499975,-95.802415 43.499974,-95.802226 43.499963,-95.802199 43.499961,-95.801972 43.499947,-95.80172 43.499904,-95.801228 43.499807,-95.801174 43.499796,-95.801072 43.499747,-95.800982 43.499703,-95.79751 43.499965,-95.797176 43.500012,-95.796847 43.500001,-95.794541 43.49999,-95.793181 43.499983,-95.793051 43.499971,-95.792742 43.499968,-95.790672 43.499946,-95.788061 43.499942,-95.78772 43.499941,-95.784525 43.499939,-95.781374 43.499945,-95.781358 43.499945,-95.779861 43.499941,-95.779718 43.499941,-95.777205 43.499935,-95.77399 43.499938,-95.773976 43.499938,-95.772934 43.499932,-95.772511 43.499931,-95.772352 43.499931,-95.768962 43.499924,-95.768544 43.499925,-95.766446 43.49993,-95.766278 43.49993,-95.76574 43.499931,-95.765499 43.499932,-95.764548 43.499929,-95.764271 43.499928,-95.761325 43.499918,-95.75807 43.499909,-95.756845 43.499902,-95.756658 43.499901,-95.756649 43.499901,-95.756622 43.499901,-95.754738 43.499891,-95.754034 43.499917,-95.754007 43.499918,-95.752708 43.499923,-95.752699 43.499923,-95.752683 43.499923,-95.752638 43.499923,-95.7519 43.499925,-95.750421 43.499925,-95.750373 43.499925,-95.749751 43.499925,-95.749696 43.499925,-95.746444 43.499924,-95.745993 43.49992,-95.745985 43.49992,-95.745977 43.49992,-95.74597 43.49992,-95.74596 43.49992,-95.745946 43.49992,-95.745925 43.49992,-95.745894 43.49992,-95.745845 43.49992,-95.7432 43.499905,-95.74241 43.499907,-95.742386 43.499907,-95.741448 43.49991,-95.741413 43.49991,-95.740838 43.499912,-95.740809 43.499912,-95.739986 43.499915,-95.736292 43.499912,-95.73404 43.49991,-95.732111 43.499918,-95.730162 43.499905,-95.726704 43.499923,-95.726697 43.499923,-95.726683 43.499923,-95.726668 43.499923,-95.726643 43.499923,-95.726402 43.499924,-95.724742 43.499923,-95.724128 43.499922,-95.724116 43.499922,-95.724095 43.499922,-95.724032 43.499922,-95.722669 43.499917,-95.722655 43.499917,-95.718691 43.4999,-95.716281 43.4999,-95.715947 43.4999,-95.714257 43.4999,-95.711143 43.4999,-95.707677 43.49991,-95.707642 43.49991,-95.70458 43.499908,-95.701286 43.499901,-95.698005 43.499899,-95.695787 43.499901,-95.695544 43.499901,-95.695154 43.499892,-95.694249 43.499881,-95.693884 43.499887,-95.693864 43.499888,-95.692673 43.499891,-95.692613 43.499891,-95.689365 43.499881,-95.689309 43.499881,-95.6875 43.499898,-95.684432 43.499892,-95.684344 43.499892,-95.684078 43.499894,-95.684051 43.499894,-95.683605 43.499896,-95.682959 43.499899,-95.682937 43.499899,-95.682505 43.499901,-95.682135 43.499903,-95.682106 43.499903,-95.68167 43.499905,-95.681261 43.499907,-95.677127 43.499896,-95.67323 43.499901,-95.672293 43.499896,-95.672272 43.499896,-95.671346 43.499891,-95.671086 43.49989,-95.669613 43.499883,-95.668164 43.499885,-95.666191 43.499888,-95.663105 43.499884,-95.663098 43.499884,-95.660867 43.499884,-95.660848 43.499884,-95.658205 43.499886,-95.652297 43.499904,-95.649093 43.499891,-95.649038 43.499891,-95.648266 43.499887,-95.643177 43.499885,-95.643156 43.499885,-95.640352 43.499885,-95.636566 43.499884,-95.631728 43.499891,-95.631714 43.499891,-95.631159 43.499892,-95.630566 43.49989,-95.630406 43.499889,-95.628489 43.499883,-95.628482 43.499883,-95.626879 43.499888,-95.626572 43.499889,-95.626552 43.499889,-95.625003 43.499893,-95.623272 43.499905,-95.623257 43.499905,-95.621022 43.499907,-95.6206 43.499903,-95.620584 43.499903,-95.619454 43.499894,-95.618856 43.499889,-95.616768 43.499886,-95.61606 43.499888,-95.614241 43.499895,-95.614226 43.499895,-95.609861 43.499903,-95.609624 43.499903,-95.608619 43.499905,-95.606638 43.499902,-95.604395 43.499914,-95.604366 43.499914,-95.602512 43.499915,-95.60002 43.499901,-95.597618 43.49991,-95.594277 43.499902,-95.594244 43.499902,-95.592499 43.499905,-95.590278 43.499899,-95.588487 43.49991,-95.584545 43.499906,-95.58453 43.499906,-95.57987 43.499908,-95.577327 43.499904,-95.574428 43.499911,-95.574285 43.499911,-95.574273 43.499911,-95.574135 43.499912,-95.570443 43.499927,-95.569395 43.499922,-95.56915 43.499921,-95.569004 43.49992,-95.567655 43.499914,-95.565163 43.499934,-95.565127 43.499934,-95.565013 43.499935,-95.564872 43.499927,-95.564786 43.499926,-95.564744 43.499926,-95.564473 43.499923,-95.562494 43.499941,-95.559951 43.499926,-95.552454 43.499951,-95.549808 43.499925,-95.547574 43.499937,-95.544538 43.499927,-95.544504 43.499927,-95.5354 43.499947,-95.535187 43.499947,-95.524921 43.49997,-95.524788 43.499965,-95.52478 43.499965,-95.524764 43.499961,-95.524755 43.499959,-95.524676 43.499936,-95.524608 43.499917,-95.524576 43.499908,-95.5245 43.499856,-95.521726 43.499992,-95.52067 43.499986,-95.518115 43.499993,-95.517329 43.499986,-95.516169 43.499931,-95.515856 43.499916,-95.514664 43.499867,-95.514656 43.499867,-95.504899 43.499964,-95.502536 43.499967,-95.500283 43.499964,-95.500145 43.499967,-95.499985 43.499971,-95.499306 43.499986,-95.498536 43.500005,-95.498425 43.500008,-95.49835 43.50001,-95.493053 43.500157,-95.492723 43.500164,-95.492711 43.500164,-95.489028 43.500049,-95.488837 43.500043,-95.488773 43.500041,-95.488749 43.50004,-95.488731 43.50004,-95.487389 43.499998,-95.487376 43.499998,-95.486793 43.499995,-95.486552 43.499994,-95.486318 43.499995,-95.484345 43.500001,-95.484295 43.500001,-95.482895 43.5,-95.480367 43.500026,-95.478276 43.500024,-95.477448 43.500301,-95.476738 43.500304,-95.476218 43.500307,-95.475596 43.50031,-95.475421 43.500314,-95.475062 43.500338,-95.474805 43.500331,-95.474776 43.500331,-95.460478 43.500468,-95.460448 43.500469,-95.457975 43.500509,-95.457491 43.500517,-95.456563 43.500532,-95.454706 43.500563,-95.454704 43.500563,-95.454656 43.500564,-95.454608 43.500565,-95.454614 43.500647,-95.454433 43.500646,-95.453048 43.500626,-95.448895 43.500566,-95.447511 43.500546,-95.444858 43.500519,-95.436902 43.500441,-95.43425 43.500415,-95.434244 43.50037,-95.434238 43.500315,-95.434204 43.500257,-95.434193 43.500248,-95.434159 43.50022,-95.434108 43.500177,-95.434025 43.500135,-95.433945 43.500121,-95.433839 43.500113,-95.4338 43.500113,-95.432644 43.500118,-95.432421 43.50012,-95.432259 43.500119,-95.431305 43.500115,-95.428446 43.500103,-95.427494 43.500099,-95.426956 43.500101,-95.425345 43.500111,-95.424809 43.500114,-95.423165 43.500123,-95.418233 43.50015,-95.416589 43.500159,-95.416178 43.50016,-95.414945 43.500167,-95.414535 43.50017,-95.413701 43.50018,-95.41343 43.50018,-95.410115 43.500188,-95.409011 43.500191,-95.406691 43.500195,-95.399734 43.500211,-95.397415 43.500217,-95.397325 43.500217,-95.397059 43.500217,-95.39697 43.500218,-95.396493 43.500218,-95.395064 43.500221,-95.394588 43.500223,-95.393405 43.500224,-95.389859 43.500229,-95.388771 43.500231,-95.388677 43.500231,-95.387787 43.500226,-95.382621 43.500249,-95.376466 43.500248,-95.37539 43.500246,-95.375248 43.500246,-95.374823 43.50025,-95.374682 43.500251,-95.374651 43.500252,-95.374488 43.500251,-95.373911 43.500254,-95.373719 43.500255,-95.373713 43.500255,-95.373326 43.500253,-95.372814 43.500252,-95.370099 43.500246,-95.369195 43.500246,-95.369116 43.500246,-95.368881 43.500246,-95.368803 43.500246,-95.367578 43.500248,-95.364808 43.500264,-95.360275 43.500292,-95.352823 43.500317,-95.351873 43.500321,-95.348829 43.500326,-95.348068 43.500331,-95.345788 43.500349,-95.345028 43.500355,-95.345 43.500356,-95.344347 43.500355,-95.343069 43.500359,-95.337194 43.50038,-95.335315 43.500387,-95.335236 43.500387,-95.33399 43.500384,-95.333456 43.500383,-95.330252 43.500391,-95.329007 43.500395,-95.328426 43.500405,-95.328179 43.500406,-95.325695 43.500422,-95.324868 43.500428,-95.324792 43.500428,-95.323859 43.500421,-95.323218 43.500417,-95.320832 43.500418,-95.319824 43.500419,-95.319672 43.500419,-95.319216 43.500423,-95.319065 43.500424,-95.317741 43.500431,-95.317076 43.500431,-95.311111 43.500438,-95.310972 43.500439,-95.309123 43.500446,-95.308451 43.500456,-95.306442 43.500489,-95.306202 43.500493,-95.305772 43.500465,-95.30559 43.500462,-95.305053 43.500456,-95.304874 43.500455,-95.304868 43.500412,-95.299811 43.500437,-95.284638 43.500511,-95.279583 43.500538,-95.278623 43.500539,-95.275751 43.500548,-95.274794 43.500551,-95.274749 43.500551,-95.273629 43.500549,-95.270133 43.500544,-95.270077 43.500544,-95.268968 43.500548,-95.266128 43.50056,-95.263343 43.500573,-95.25761 43.500579,-95.254771 43.500582,-95.254371 43.500582,-95.254022 43.500584,-95.251776 43.500598,-95.251028 43.500603,-95.251011 43.500597,-95.250963 43.500579,-95.250947 43.500573,-95.250927 43.500565,-95.250899 43.500555,-95.250875 43.500533,-95.25086 43.50052,-95.250365 43.500661,-95.24967 43.500728,-95.24605 43.501079,-95.244844 43.501196,-95.242824 43.50116,-95.238847 43.501092,-95.236769 43.501073,-95.234861 43.501057,-95.234751 43.501056,-95.232277 43.501034,-95.224856 43.50097,-95.222383 43.500949,-95.220752 43.500963,-95.215861 43.501007,-95.21493 43.501016,-95.214233 43.500965,-95.213641 43.500934,-95.21187 43.500844,-95.21128 43.500815,-95.209996 43.500818,-95.208155 43.500826,-95.206147 43.500826,-95.205022 43.500828,-95.204943 43.500828,-95.204865 43.500828,-95.204783 43.500829,-95.204702 43.500829,-95.204343 43.500829,-95.203859 43.500831,-95.200845 43.500849,-95.199841 43.500857,-95.198827 43.500859,-95.196561 43.500865,-95.195791 43.500865,-95.194779 43.500867,-95.194738 43.500867,-95.192461 43.500876,-95.185509 43.500905,-95.183193 43.500916,-95.182537 43.500914,-95.180572 43.50091,-95.179917 43.500909,-95.179306 43.50092,-95.179164 43.50092,-95.177231 43.500931,-95.176996 43.500931,-95.176908 43.500931,-95.176157 43.500933,-95.175833 43.500937,-95.175165 43.500944,-95.174863 43.500949,-95.17454 43.500955,-95.174229 43.500961,-95.173214 43.500964,-95.171657 43.500971,-95.169233 43.501005,-95.168429 43.501017,-95.167907 43.501019,-95.167806 43.501019,-95.167595 43.501021,-95.167506 43.501014,-95.167417 43.501,-95.167408 43.500998,-95.167085 43.500951,-95.166119 43.500813,-95.165798 43.500768,-95.163445 43.500698,-95.161364 43.500637,-95.15693 43.500507,-95.156391 43.500491,-95.154936 43.500448,-95.15404 43.500458,-95.153488 43.500463,-95.151834 43.50048,-95.151283 43.500486,-95.150328 43.500495,-95.147465 43.500524,-95.146511 43.500535,-95.146492 43.500535,-95.144569 43.500554,-95.141331 43.500588,-95.138746 43.500597,-95.136805 43.500604,-95.136799 43.500604,-95.136225 43.500605,-95.134484 43.500611,-95.133906 43.500614,-95.132485 43.500619,-95.128225 43.500636,-95.126806 43.500643,-95.126288 43.500638,-95.125271 43.500631,-95.124751 43.500748,-95.124249 43.500863,-95.12393 43.500886,-95.122981 43.500962,-95.122666 43.500989,-95.122478 43.500989,-95.122377 43.500989,-95.121917 43.500983,-95.12173 43.500981,-95.121689 43.50098,-95.120644 43.500967,-95.120596 43.500966,-95.117194 43.500946,-95.117178 43.500946,-95.11606 43.500945,-95.116001 43.500945,-95.115826 43.500945,-95.115768 43.500945,-95.115561 43.500919,-95.114945 43.500851,-95.114741 43.500829,-95.114606 43.500824,-95.114371 43.500832,-95.113465 43.500834,-95.11313 43.50084,-95.112156 43.500854,-95.106934 43.500851,-95.105686 43.500859,-95.104951 43.500864,-95.104192 43.500902,-95.104178 43.500903,-95.103697 43.500931,-95.102789 43.500952,-95.10266 43.500954,-95.102471 43.500959,-95.102273 43.500957,-95.102145 43.500957,-95.101314 43.500952,-95.099147 43.50094,-95.098823 43.50094,-95.097993 43.500943,-95.097579 43.500943,-95.09714 43.500945,-95.09634 43.500974,-95.095928 43.500989,-95.095541 43.501002,-95.095532 43.501004,-95.094383 43.501008,-95.093997 43.501011,-95.093987 43.501012,-95.093226 43.501012,-95.091859 43.501013,-95.090917 43.501009,-95.090154 43.501009,-95.090149 43.501009,-95.088583 43.501007,-95.086882 43.501007,-95.086071 43.501007,-95.083889 43.501008,-95.082326 43.50101,-95.081235 43.50101,-95.077963 43.50101,-95.077754 43.501011,-95.076873 43.50101,-95.076315 43.501007,-95.074641 43.500998,-95.074104 43.500996,-95.074083 43.500996,-95.072232 43.500987,-95.0701 43.500985,-95.062582 43.500983,-95.058154 43.500975,-95.056368 43.500973,-95.05433 43.500945,-95.054172 43.500955,-95.054056 43.500962,-95.053709 43.500985,-95.053594 43.500993,-95.049635 43.500984,-95.03776 43.500963,-95.033802 43.500955,-95.033563 43.500956,-95.033553 43.500956,-95.032808 43.500949,-95.03256 43.500948,-95.030943 43.500957,-95.02892 43.500972,-95.026095 43.500969,-95.02448 43.500968,-95.024161 43.500968,-95.023808 43.500968,-95.021793 43.500969,-95.021123 43.500971,-95.019721 43.500968,-95.015518 43.500963,-95.014223 43.500962,-95.014117 43.500962,-95.010188 43.500958,-95.008717 43.500957,-95.001886 43.500947,-94.998406 43.500937,-94.997477 43.500935,-94.99448 43.500923,-94.994439 43.500924,-94.991921 43.500936,-94.990525 43.50093,-94.989961 43.500929,-94.988673 43.500933,-94.98588 43.500932,-94.984521 43.500937,-94.979915 43.500934,-94.978661 43.500928,-94.978363 43.500927,-94.974793 43.500923,-94.974707 43.500925,-94.974618 43.500932,-94.974373 43.500953,-94.974353 43.500955,-94.974349 43.500956,-94.974265 43.50096,-94.974198 43.500964,-94.973644 43.500979,-94.972704 43.500981,-94.971334 43.500968,-94.97029 43.500977,-94.968699 43.500992,-94.967597 43.500992,-94.966081 43.500998,-94.965025 43.501007,-94.964417 43.500996,-94.963695 43.500991,-94.96274 43.500975,-94.961549 43.500968,-94.959657 43.500968,-94.958866 43.500956,-94.958364 43.500964,-94.958191 43.500966,-94.956295 43.500953,-94.955018 43.500931,-94.95471 43.500922,-94.95439 43.500926,-94.954333 43.500945,-94.954262 43.500945,-94.953906 43.50095,-94.95388 43.500949,-94.953753 43.500947,-94.952183 43.500915,-94.951411 43.500917,-94.951194 43.500916,-94.949767 43.50091,-94.948856 43.500897,-94.947981 43.500902,-94.945647 43.500903,-94.943517 43.500897,-94.942989 43.500896,-94.941753 43.500895,-94.940959 43.500901,-94.940816 43.500902,-94.939975 43.500897,-94.939691 43.500898,-94.937882 43.500906,-94.936675 43.500917,-94.935892 43.500921,-94.935243 43.500924,-94.934626 43.500931,-94.934528 43.500932,-94.934236 43.500939,-94.934139 43.500941,-94.93403 43.500943,-94.932942 43.500951,-94.932784 43.500953,-94.931714 43.500969,-94.930316 43.501007,-94.929698 43.501013,-94.929353 43.501007,-94.929298 43.501007,-94.928563 43.500974,-94.928159 43.500949,-94.92799 43.500937,-94.927434 43.500917,-94.927067 43.500923,-94.926833 43.500926,-94.926238 43.500945,-94.92564 43.500956,-94.925059 43.500952,-94.924485 43.500927,-94.923792 43.500909,-94.923682 43.500907,-94.922701 43.500897,-94.922304 43.500893,-94.92196 43.50089,-94.921116 43.500887,-94.92072 43.500886,-94.919581 43.500882,-94.917793 43.500865,-94.916341 43.500881,-94.915303 43.500884,-94.914921 43.500868,-94.914582 43.500853,-94.914574 43.500872,-94.904034 43.500478,-94.903818 43.50047,-94.889122 43.500499,-94.884418 43.500933,-94.883617 43.500946,-94.882895 43.500929,-94.882116 43.500925,-94.881207 43.500906,-94.880286 43.500921,-94.879146 43.500927,-94.878343 43.500936,-94.876719 43.500934,-94.875339 43.500923,-94.874795 43.500907,-94.874561 43.500924,-94.874418 43.500955,-94.873625 43.500967,-94.872751 43.500988,-94.872682 43.50097,-94.872456 43.500937,-94.872246 43.500917,-94.871695 43.500927,-94.870826 43.500926,-94.869582 43.500944,-94.868771 43.500939,-94.865694 43.500927,-94.86283 43.500926,-94.86178 43.500942,-94.861145 43.500935,-94.86076 43.500898,-94.860491 43.500842,-94.860138 43.500757,-94.859783 43.500617,-94.859696 43.500574,-94.85882 43.500591,-94.858521 43.500799,-94.858354 43.50087,-94.858195 43.500899,-94.857932 43.500924,-94.854566 43.500925,-94.854555 43.500925,-94.850814 43.500937,-94.847566 43.500912,-94.846326 43.500908,-94.844158 43.500918,-94.8424 43.500899,-94.838842 43.500888,-94.837944 43.500881,-94.83749 43.500874,-94.836469 43.500876,-94.834778 43.500868,-94.834413 43.500868,-94.834413 43.500869,-94.834413 43.50087,-94.834413 43.500871,-94.834413 43.500872,-94.834413 43.500873,-94.834413 43.500874,-94.834413 43.500875,-94.834413 43.500876,-94.834413 43.500877,-94.834413 43.500878,-94.834402 43.500879,-94.833922 43.50088,-94.833089 43.500882,-94.826577 43.500882,-94.82561 43.500897,-94.824375 43.500877,-94.821298 43.500922,-94.819236 43.500939,-94.818023 43.500942,-94.816625 43.500949,-94.814036 43.500971,-94.810194 43.500978,-94.808979 43.500975,-94.807898 43.500989,-94.805546 43.501004,-94.804189 43.500993,-94.804084 43.500993,-94.803946 43.500994,-94.801856 43.501002,-94.801022 43.500999,-94.799408 43.500985,-94.798194 43.501001,-94.797932 43.501005,-94.797227 43.501004,-94.796226 43.50102,-94.794387 43.501012,-94.794218 43.501011,-94.792206 43.501039,-94.790385 43.501024,-94.788172 43.501031,-94.785826 43.501025,-94.785071 43.501027,-94.781969 43.501034,-94.77755 43.501032,-94.774407 43.501042,-94.774283 43.501042,-94.773256 43.501046,-94.770068 43.50104,-94.765747 43.501052,-94.761873 43.501049,-94.761265 43.501046,-94.76081 43.501043,-94.758323 43.501033,-94.754238 43.501014,-94.754187 43.501014,-94.752623 43.50103,-94.74944 43.50102,-94.74746 43.501035,-94.74454 43.501023,-94.744535 43.501022,-94.744142 43.501021,-94.739661 43.500985,-94.73837 43.50096,-94.73812 43.500959,-94.734204 43.500936,-94.734179 43.500934,-94.734017 43.500926,-94.732268 43.500927,-94.731463 43.500917,-94.729451 43.500918,-94.729074 43.500923,-94.726893 43.500924,-94.726099 43.500919,-94.725419 43.500928,-94.723779 43.500919,-94.723237 43.500908,-94.722452 43.500901,-94.721784 43.500904,-94.721322 43.500903,-94.720951 43.500902,-94.720731 43.500902,-94.719997 43.500889,-94.718466 43.500885,-94.71821 43.500883,-94.71684 43.500872,-94.7162 43.500873,-94.71458 43.500867,-94.714277 43.500866,-94.714245 43.500865,-94.713386 43.500861,-94.712614 43.500845,-94.711961 43.500846,-94.711056 43.500858,-94.710361 43.500851,-94.706346 43.50085,-94.705118 43.500853,-94.703187 43.500844,-94.701328 43.500849,-94.700303 43.500839,-94.698423 43.500837,-94.698129 43.500839,-94.697713 43.500842,-94.695265 43.500838,-94.694223 43.500831,-94.694182 43.50083,-94.693288 43.500826,-94.68989 43.500819,-94.687758 43.500821,-94.686671 43.500827,-94.685546 43.500825,-94.683567 43.500808,-94.682289 43.500811,-94.681201 43.500797,-94.680159 43.500799,-94.679197 43.500809,-94.678474 43.500808,-94.678041 43.500808,-94.677431 43.5008,-94.67421 43.500797,-94.674191 43.500796,-94.673504 43.500784,-94.671593 43.500793,-94.670274 43.500795,-94.669115 43.500792,-94.668024 43.500784,-94.667615 43.500786,-94.666614 43.500789,-94.664876 43.500777,-94.661583 43.500784,-94.660428 43.50077,-94.658776 43.500762,-94.658045 43.500759,-94.656835 43.500748,-94.654285 43.500746,-94.654278 43.500745,-94.653373 43.500739,-94.652584 43.500733,-94.649709 43.500737,-94.647182 43.500729,-94.645294 43.500732,-94.642594 43.500727,-94.640508 43.500742,-94.640125 43.500734,-94.639491 43.500721,-94.638047 43.500716,-94.636966 43.500721,-94.63427 43.500713,-94.630435 43.500703,-94.630389 43.500702,-94.630195 43.500701,-94.628239 43.500682,-94.625 43.500687,-94.624394 43.500687,-94.624296 43.500687,-94.620603 43.500686,-94.620463 43.500677,-94.620303 43.500653,-94.6202 43.500613,-94.619886 43.500414,-94.61972 43.500506,-94.619514 43.500591,-94.619402 43.500625,-94.619125 43.500684,-94.619071 43.500688,-94.618929 43.500699,-94.616937 43.500698,-94.616723 43.500701,-94.61645 43.500704,-94.616179 43.500726,-94.615697 43.500864,-94.615454 43.500961,-94.615289 43.501036,-94.615158 43.501108,-94.615129 43.501128,-94.61502 43.50111,-94.611635 43.500532,-94.611128 43.500528,-94.609804 43.500521,-94.609697 43.500521,-94.608223 43.500516,-94.607205 43.500513,-94.567272 43.500373,-94.56687 43.500372,-94.566525 43.500354,-94.566087 43.500584,-94.565973 43.500598,-94.565841 43.500599,-94.565102 43.500583,-94.564831 43.500577,-94.564396 43.500567,-94.563081 43.500557,-94.560835 43.500549,-94.557445 43.500525,-94.552345 43.500525,-94.552345 43.500526,-94.552345 43.500527,-94.552345 43.500528,-94.552345 43.500529,-94.552343 43.50053,-94.549149 43.500524,-94.546867 43.50051,-94.544824 43.500514,-94.542522 43.500519,-94.535985 43.500527,-94.532385 43.500534,-94.53238 43.500535,-94.526832 43.5005,-94.52259 43.500494,-94.520215 43.500495,-94.514995 43.500473,-94.514078 43.500472,-94.512393 43.50049,-94.512391 43.500489,-94.508342 43.500465,-94.505798 43.500459,-94.502525 43.500462,-94.501657 43.500456,-94.497644 43.500469,-94.492413 43.500463,-94.49239 43.500462,-94.492241 43.500462,-94.491599 43.50046,-94.490615 43.500456,-94.488232 43.500446,-94.485675 43.500452,-94.482607 43.500443,-94.481887 43.500458,-94.480987 43.500445,-94.479855 43.500443,-94.478373 43.50046,-94.476289 43.500441,-94.475275 43.50045,-94.472935 43.50045,-94.472135 43.500436,-94.471858 43.500431,-94.470957 43.500431,-94.470946 43.500432,-94.470912 43.500432,-94.470807 43.500424,-94.470708 43.500406,-94.470653 43.500384,-94.470585 43.50035,-94.470566 43.500336,-94.470547 43.500321,-94.470413 43.500333,-94.470332 43.50033,-94.463842 43.500358,-94.457341 43.500385,-94.452754 43.500407,-94.448156 43.500429,-94.447424 43.500405,-94.447094 43.500396,-94.445377 43.500404,-94.44285 43.500422,-94.442602 43.500424,-94.439781 43.500387,-94.43893 43.500402,-94.437474 43.500389,-94.437471 43.500389,-94.437325 43.500388,-94.434488 43.500397,-94.432757 43.500371,-94.431547 43.500381,-94.429936 43.500374,-94.427727 43.500341,-94.427146 43.500338,-94.424434 43.500336,-94.423343 43.500333,-94.420397 43.500347,-94.419062 43.500354,-94.415452 43.500345,-94.41444 43.500357,-94.412804 43.500341,-94.411753 43.500317,-94.41033 43.500332,-94.410303 43.500331,-94.408529 43.500312,-94.406276 43.500287,-94.404584 43.500324,-94.403424 43.500322,-94.40339 43.500322,-94.403038 43.500322,-94.400229 43.50032,-94.399727 43.500319,-94.398409 43.500322,-94.393772 43.500299,-94.390776 43.500284,-94.390662 43.500292,-94.390599 43.500317,-94.390555 43.50035,-94.390511 43.500405,-94.390486 43.500468,-94.389248 43.500458,-94.387739 43.500446,-94.386233 43.500437,-94.385241 43.50043,-94.383169 43.500418,-94.382754 43.500415,-94.380106 43.5004,-94.37927 43.500391,-94.378462 43.500385,-94.377961 43.500381,-94.377795 43.50038,-94.377599 43.500379,-94.377466 43.500379,-94.377367 43.500236,-94.377157 43.500235,-94.377049 43.500235,-94.376927 43.500234,-94.376756 43.500233,-94.376537 43.500232,-94.376215 43.50023,-94.376177 43.500229,-94.374965 43.500221,-94.369234 43.500255,-94.36807 43.500243,-94.368066 43.500242,-94.366554 43.500228,-94.365403 43.500241,-94.364213 43.500265,-94.363622 43.500264,-94.35839 43.500251,-94.357708 43.50026,-94.357697 43.500261,-94.357027 43.500249,-94.355819 43.500259,-94.354746 43.500279,-94.352119 43.500254,-94.349308 43.500259,-94.348156 43.50025,-94.347213 43.500248,-94.345278 43.500244,-94.344526 43.500251,-94.343287 43.500253,-94.343016 43.500253,-94.341754 43.50023,-94.339184 43.500238,-94.337406 43.500235,-94.336537 43.500243,-94.334894 43.500237,-94.332953 43.500244,-94.332237 43.50023,-94.33048 43.500224,-94.329679 43.500233,-94.329175 43.500237,-94.327724 43.500225,-94.327237 43.500233,-94.32606 43.500251,-94.323977 43.500237,-94.323298 43.500232,-94.322228 43.500233,-94.320863 43.500266,-94.319947 43.500237,-94.318484 43.500221,-94.316213 43.500216,-94.315406 43.500223,-94.31407 43.500205,-94.311093 43.500197,-94.309481 43.500178,-94.307404 43.500175,-94.306895 43.500185,-94.302612 43.500188,-94.300734 43.500196,-94.298466 43.500181,-94.296353 43.500188,-94.289893 43.500181,-94.288864 43.500192,-94.287774 43.500189,-94.287754 43.500188,-94.287267 43.500182,-94.286242 43.500182,-94.285786 43.500174,-94.283782 43.500171,-94.281445 43.500167,-94.279546 43.500179,-94.278682 43.50017,-94.277143 43.500155,-94.274829 43.500141,-94.272814 43.500152,-94.272475 43.500145,-94.267815 43.500135,-94.267796 43.500135,-94.26739 43.500139,-94.266298 43.500149,-94.265648 43.500138,-94.264561 43.500163,-94.264394 43.500158,-94.263182 43.500123,-94.260751 43.500128,-94.258589 43.50012,-94.255427 43.500128,-94.254526 43.50013,-94.251081 43.50012,-94.250505 43.500118,-94.24959 43.500139,-94.248556 43.500128,-94.247968 43.500133,-94.245416 43.500143,-94.245389 43.500143,-94.23972 43.50012,-94.237771 43.500137,-94.236259 43.500131,-94.234093 43.500138,-94.232244 43.500155,-94.230807 43.500145,-94.22931 43.500153,-94.227969 43.500148,-94.22608 43.500125,-94.225056 43.500136,-94.224689 43.50014,-94.223202 43.500126,-94.220625 43.500157,-94.218938 43.500137,-94.217391 43.500145,-94.215848 43.500131,-94.2127 43.500126,-94.212064 43.500154,-94.212041 43.500155,-94.210486 43.500137,-94.20879 43.500135,-94.208036 43.50015,-94.207027 43.500125,-94.206422 43.500133,-94.204752 43.500126,-94.20296 43.500136,-94.201095 43.500132,-94.200991 43.500131,-94.196025 43.500097,-94.19323 43.500101,-94.192743 43.500102,-94.190219 43.500081,-94.188674 43.500088,-94.188089 43.500091,-94.18803 43.500091,-94.186993 43.500082,-94.186135 43.500093,-94.185762 43.500091,-94.183399 43.500076,-94.182191 43.500078,-94.181288 43.500072,-94.179406 43.500084,-94.178022 43.500092,-94.176763 43.500084,-94.175095 43.500094,-94.172468 43.500083,-94.170926 43.500096,-94.16931 43.500093,-94.167991 43.500106,-94.16723 43.500096,-94.167112 43.500094,-94.166857 43.500091,-94.165964 43.50008,-94.165457 43.500082,-94.163387 43.500088,-94.16059 43.500079,-94.159471 43.500083,-94.157449 43.500091,-94.156733 43.500094,-94.156535 43.500093,-94.154701 43.500088,-94.152104 43.500092,-94.151095 43.500111,-94.150897 43.500105,-94.150388 43.500089,-94.149012 43.50008,-94.147128 43.500088,-94.144988 43.500089,-94.143759 43.50009,-94.142246 43.500099,-94.140461 43.50011,-94.133707 43.500077,-94.132768 43.500101,-94.129974 43.50012,-94.128144 43.500103,-94.125016 43.500104,-94.124122 43.500115,-94.122133 43.500101,-94.120069 43.500104,-94.118322 43.500087,-94.117763 43.500087,-94.117706 43.500087,-94.1163 43.500087,-94.114622 43.500101,-94.113645 43.500102,-94.112881 43.500118,-94.109752 43.500106,-94.108313 43.50011,-94.108152 43.500111,-94.108051 43.500112,-94.107942 43.500114,-94.10779 43.500116,-94.106864 43.500128,-94.106392 43.500105,-94.105009 43.500097,-94.101929 43.500098,-94.098251 43.500082,-94.098084 43.500082,-94.096052 43.500082,-94.094675 43.500074,-94.094656 43.500074,-94.094311 43.500073,-94.094148 43.500073,-94.093594 43.500071,-94.093439 43.500075,-94.09335 43.50009,-94.088586 43.500092,-94.086482 43.500063,-94.085023 43.500051,-94.082891 43.500049,-94.082853 43.500049,-94.079887 43.500042,-94.078227 43.500031,-94.07523 43.500016,-94.07261 43.500021,-94.069231 43.500015,-94.06862 43.500014,-94.068409 43.500013,-94.068271 43.500013,-94.068162 43.500013,-94.067989 43.500013,-94.066204 43.500019,-94.065217 43.500015,-94.064781 43.500013,-94.060935 43.499995,-94.058345 43.500013,-94.053641 43.499992,-94.050801 43.499997,-94.043846 43.499974,-94.043833 43.499974,-94.042451 43.499962,-94.040133 43.499961,-94.034051 43.499957,-94.030804 43.499955,-94.030751 43.499955,-94.029526 43.499951,-94.028194 43.499947,-94.025898 43.499941,-94.023329 43.499944,-94.020781 43.499934,-94.018063 43.499923,-94.016289 43.499925,-94.010752 43.499916,-94.008389 43.49991,-94.008241 43.49991,-94.007876 43.49991,-94.00607 43.499911,-94.002028 43.499913,-94.000441 43.499907,-94.000215 43.499907,-93.999208 43.499903,-93.995154 43.499917,-93.991554 43.499912,-93.990844 43.49992,-93.988231 43.499915,-93.982302 43.499902,-93.981692 43.499901,-93.979328 43.499906,-93.977081 43.49989,-93.973497 43.499885,-93.970762 43.499886,-93.970608 43.499866,-93.968285 43.499873,-93.962102 43.499867,-93.961593 43.499845,-93.952288 43.499874,-93.952286 43.499873,-93.94833 43.499852,-93.94266 43.499855,-93.938465 43.499803,-93.930401 43.499814,-93.930406 43.499825,-93.928585 43.499797,-93.919858 43.499766,-93.919733 43.499766,-93.914717 43.499822,-93.912203 43.499812,-93.912196 43.499811,-93.908599 43.499784,-93.908328 43.499787,-93.905323 43.499781,-93.897069 43.499779,-93.893269 43.499769,-93.893073 43.499769,-93.892288 43.499774,-93.892285 43.499773,-93.88843 43.499769,-93.887961 43.499768,-93.876574 43.499757,-93.875224 43.499728,-93.872447 43.499743,-93.872352 43.499737,-93.872002 43.499722,-93.868404 43.499749,-93.863355 43.499715,-93.852263 43.499742,-93.852262 43.499743,-93.84849 43.499739,-93.835852 43.499749,-93.828496 43.499727,-93.817563 43.499711,-93.813272 43.499699,-93.813269 43.499698,-93.812981 43.49971,-93.808507 43.499697,-93.806438 43.499724,-93.805588 43.499702,-93.801789 43.499677,-93.799286 43.4997,-93.797851 43.499711,-93.797016 43.499697,-93.796428 43.499704,-93.79638 43.499704,-93.796353 43.4997,-93.793979 43.499681,-93.793974 43.49968,-93.793238 43.499686,-93.791251 43.499685,-93.788429 43.499703,-93.785878 43.499689,-93.785875 43.499688,-93.78188 43.499676,-93.780568 43.49967,-93.773734 43.499682,-93.77371 43.49968,-93.769112 43.499695,-93.768498 43.499709,-93.767265 43.499688,-93.767264 43.499687,-93.764461 43.499681,-93.7563 43.499682,-93.753668 43.499678,-93.753655 43.499677,-93.748443 43.499697,-93.743573 43.499689,-93.743568 43.49969,-93.738623 43.499674,-93.738374 43.499673,-93.738007 43.499671,-93.73395 43.499686,-93.733906 43.499685,-93.72846 43.499696,-93.726049 43.499683,-93.722532 43.499676,-93.716122 43.499688,-93.716091 43.499687,-93.708835 43.49967,-93.708489 43.499648,-93.708267 43.499648,-93.704634 43.499661,-93.704623 43.49966,-93.703848 43.499654,-93.700707 43.499683,-93.700306 43.499673,-93.70008 43.499675,-93.697206 43.499675,-93.697206 43.499676,-93.696593 43.49968,-93.694644 43.499672,-93.691067 43.499656,-93.683226 43.49967,-93.677303 43.499687,-93.676303 43.49968,-93.676227 43.499677,-93.668495 43.499675,-93.66785 43.499673,-93.66164 43.499674,-93.656597 43.499664,-93.65656 43.499663,-93.653036 43.499669,-93.651282 43.499667,-93.651281 43.499667,-93.648533 43.49968,-93.648043 43.499677,-93.647542 43.499677,-93.645295 43.49968,-93.644571 43.499682,-93.643581 43.499687,-93.64267 43.499691,-93.64224 43.499689,-93.64132 43.499685,-93.640643 43.499689,-93.638218 43.499682,-93.636878 43.499679,-93.634401 43.499675,-93.633224 43.499679,-93.632771 43.499681,-93.631706 43.499675,-93.629955 43.499684,-93.628325 43.499671,-93.626701 43.499687,-93.624988 43.499668,-93.623015 43.499673,-93.622261 43.499677,-93.621214 43.499683,-93.618609 43.49967,-93.618269 43.499669,-93.617249 43.499666,-93.61691 43.499666,-93.616528 43.499666,-93.615837 43.499659,-93.613431 43.499637,-93.61262 43.49964,-93.611548 43.499646,-93.610148 43.499652,-93.609883 43.499654,-93.605951 43.499641,-93.604833 43.499638,-93.604552 43.499637,-93.602504 43.499627,-93.600839 43.49962,-93.59636 43.499618,-93.594312 43.499618,-93.594096 43.499618,-93.593321 43.499622,-93.5928 43.499626,-93.59035 43.499633,-93.589571 43.499636,-93.58936 43.499635,-93.589185 43.499634,-93.58866 43.499632,-93.588485 43.499632,-93.587638 43.49963,-93.586486 43.499629,-93.584099 43.499633,-93.580836 43.499631,-93.580489 43.499632,-93.578491 43.499639,-93.578097 43.499638,-93.576915 43.499643,-93.576522 43.499644,-93.575796 43.499646,-93.574592 43.49964,-93.572066 43.49963,-93.569053 43.499632,-93.568801 43.499632,-93.566872 43.499638,-93.565206 43.499642,-93.560755 43.499654,-93.560209 43.499651,-93.558544 43.499644,-93.558457 43.499643,-93.558198 43.499643,-93.558112 43.499643,-93.557891 43.499641,-93.557231 43.499638,-93.557011 43.499639,-93.555557 43.499643,-93.554758 43.499646,-93.551197 43.499632,-93.550459 43.499631,-93.549744 43.499633,-93.549506 43.499632,-93.548791 43.499634,-93.548555 43.499635,-93.548126 43.499635,-93.546847 43.499639,-93.546842 43.499638,-93.546414 43.499637,-93.546296 43.499637,-93.54484 43.499632,-93.544045 43.49963,-93.540122 43.499637,-93.538608 43.499641,-93.538549 43.499641,-93.537493 43.499643,-93.535801 43.499647,-93.534326 43.499657,-93.533646 43.499662,-93.533272 43.49966,-93.53305 43.499658,-93.532384 43.499654,-93.532163 43.499653,-93.532107 43.499652,-93.531942 43.499651,-93.531887 43.499651,-93.531408 43.499648,-93.531206 43.499646,-93.529163 43.499642,-93.528483 43.499641,-93.528433 43.49964,-93.528286 43.49964,-93.528237 43.49964,-93.527904 43.499639,-93.526907 43.499636,-93.526575 43.499636,-93.526118 43.499635,-93.525533 43.499637,-93.523831 43.499643,-93.52241 43.499637,-93.52137 43.499633,-93.521255 43.499632,-93.520912 43.499631,-93.520798 43.499631,-93.520246 43.499628,-93.519277 43.499625,-93.51859 43.499625,-93.518039 43.499626,-93.517873 43.499626,-93.517375 43.499626,-93.517209 43.499626,-93.516993 43.499626,-93.516801 43.499626,-93.516382 43.499626,-93.51558 43.49962,-93.515173 43.499618,-93.514654 43.499614,-93.513103 43.499603,-93.512586 43.4996,-93.512304 43.499598,-93.511811 43.499596,-93.509487 43.499587,-93.508713 43.499585,-93.508067 43.499586,-93.507449 43.499588,-93.50613 43.499593,-93.505485 43.499596,-93.505378 43.499596,-93.505057 43.499597,-93.504951 43.499598,-93.504439 43.499599,-93.503775 43.499602,-93.502906 43.499588,-93.502396 43.499581,-93.502165 43.499577,-93.502104 43.499576,-93.501229 43.499586,-93.500939 43.49959,-93.500436 43.499595,-93.500227 43.499598,-93.498927 43.499607,-93.498425 43.499613,-93.49821 43.499614,-93.497567 43.49962,-93.497353 43.499622,-93.492638 43.499612,-93.492146 43.499603,-93.492138 43.499603,-93.491757 43.499597,-93.491391 43.499596,-93.489844 43.49959,-93.489836 43.49959,-93.489327 43.499588,-93.489302 43.499588,-93.488802 43.499583,-93.488765 43.499583,-93.488209 43.499591,-93.487799 43.499578,-93.483151 43.499475,-93.483137 43.499475,-93.483123 43.499475,-93.483087 43.499474,-93.481438 43.499491,-93.478554 43.499489,-93.47854 43.499489,-93.472767 43.499484,-93.472301 43.499466,-93.468321 43.499565,-93.46829 43.499572,-93.467961 43.499574,-93.466793 43.499562,-93.461403 43.499574,-93.458595 43.499579,-93.458583 43.499579,-93.453642 43.499567,-93.453435 43.499567,-93.448679 43.499551,-93.448667 43.499551,-93.445822 43.499546,-93.441911 43.499561,-93.438797 43.499562,-93.43879 43.499562,-93.438119 43.499562,-93.437503 43.499548,-93.434755 43.499535,-93.431223 43.499559,-93.428574 43.499556,-93.428507 43.499556,-93.428138 43.499555,-93.427864 43.499555,-93.427645 43.499555,-93.422931 43.499563,-93.420353 43.499562,-93.418627 43.499562,-93.414451 43.499544,-93.413585 43.499544,-93.413506 43.499544,-93.410613 43.499542,-93.408616 43.499545,-93.403334 43.499554,-93.399042 43.499534,-93.398993 43.499534,-93.398613 43.499532,-93.398583 43.499532,-93.393519 43.499536,-93.39348 43.499536,-93.39109 43.499534,-93.391025 43.499534,-93.390121 43.499534,-93.386356 43.499516,-93.382139 43.499534,-93.381849 43.499533,-93.381688 43.499533,-93.379932 43.499528,-93.379924 43.499528,-93.379545 43.499527,-93.379536 43.499527,-93.378607 43.499525,-93.378579 43.499525,-93.375826 43.499515,-93.375813 43.499515,-93.375212 43.499513,-93.375175 43.499513,-93.374998 43.499512,-93.372084 43.499515,-93.370052 43.499513,-93.370007 43.499513,-93.369066 43.499512,-93.369012 43.499512,-93.367003 43.499509,-93.36261 43.499487,-93.358793 43.499487,-93.358698 43.499487,-93.354921 43.499508,-93.353709 43.499504,-93.353307 43.499502,-93.35297 43.499501,-93.350161 43.499491,-93.350151 43.499491,-93.346711 43.499479,-93.346014 43.499476,-93.345461 43.499478,-93.345298 43.499479,-93.34516 43.499479,-93.344207 43.499481,-93.344041 43.499482,-93.343972 43.499482,-93.34312 43.499485,-93.343096 43.499485,-93.341819 43.499489,-93.340151 43.499484,-93.340139 43.499484,-93.338793 43.49948,-93.337354 43.499476,-93.33713 43.499477,-93.336927 43.499478,-93.333982 43.499492,-93.330244 43.499485,-93.330232 43.499485,-93.328744 43.499485,-93.325799 43.499486,-93.321954 43.499471,-93.321442 43.499477,-93.321411 43.499477,-93.320556 43.499488,-93.317759 43.49948,-93.316846 43.499474,-93.316835 43.499474,-93.31449 43.499459,-93.313897 43.49946,-93.313881 43.49946,-93.312498 43.499461,-93.307152 43.499482,-93.306628 43.49948,-93.306586 43.49948,-93.30387 43.499468,-93.303218 43.499465,-93.302999 43.499464,-93.302813 43.499464,-93.301057 43.499462,-93.300617 43.499461,-93.300584 43.499461,-93.298923 43.499459,-93.298874 43.499459,-93.296303 43.499467,-93.295089 43.499472,-93.290601 43.499467,-93.289043 43.49947,-93.286249 43.499474,-93.281792 43.499492,-93.281766 43.499492,-93.280413 43.499493,-93.276308 43.499493,-93.273899 43.49949,-93.273879 43.49949,-93.272112 43.499496,-93.271831 43.499488,-93.267809 43.499369,-93.267571 43.499362,-93.267531 43.499361,-93.26744 43.499361,-93.267349 43.499361,-93.266987 43.499361,-93.260984 43.499369,-93.250204 43.499386,-93.247786 43.499485,-93.247216 43.499511,-93.247207 43.499511,-93.24608 43.499501,-93.245997 43.4995,-93.244747 43.499505,-93.243588 43.499504,-93.233845 43.499506,-93.228738 43.499508,-93.228659 43.499508,-93.228396 43.49951,-93.228378 43.49951,-93.227905 43.499513,-93.226058 43.499524,-93.220569 43.499531,-93.220555 43.499531,-93.217806 43.499535,-93.21557 43.499535,-93.215542 43.499535,-93.213102 43.499535,-93.210671 43.49953,-93.209696 43.499528,-93.209116 43.499531,-93.20909 43.499531,-93.201098 43.49956,-93.197936 43.499565,-93.193443 43.499566,-93.189138 43.499558,-93.189114 43.499558,-93.187505 43.49957,-93.183794 43.499567,-93.183772 43.499567,-93.176845 43.499559,-93.171999 43.499563,-93.171406 43.499564,-93.169173 43.499571,-93.16914 43.499571,-93.162294 43.499593,-93.161068 43.499588,-93.161056 43.499588,-93.156954 43.499572,-93.155423 43.499576,-93.149191 43.499596,-93.149175 43.499596,-93.144363 43.499611,-93.14122 43.499608,-93.141195 43.499608,-93.137393 43.499605,-93.137375 43.499605,-93.13643 43.499605,-93.132918 43.499618,-93.131713 43.499623,-93.128996 43.499622,-93.127744 43.499621,-93.124281 43.49962,-93.124274 43.49962,-93.12404 43.49962,-93.123593 43.499621,-93.117792 43.499624,-93.116697 43.499629,-93.114068 43.49964,-93.111522 43.49963,-93.110124 43.499644,-93.109339 43.499645,-93.109311 43.499645,-93.107728 43.499646,-93.107366 43.499646,-93.102842 43.499641,-93.100456 43.499642,-93.095208 43.499645,-93.093961 43.499644,-93.093949 43.499644,-93.092362 43.499643,-93.092015 43.499642,-93.091692 43.499642,-93.089294 43.499637,-93.089287 43.499637,-93.086651 43.49963,-93.084283 43.499641,-93.082469 43.499649,-93.082183 43.499649,-93.081882 43.499649,-93.081858 43.499649,-93.078494 43.499651,-93.075636 43.49965,-93.072425 43.499643,-93.071156 43.499641,-93.069321 43.499639,-93.069274 43.499639,-93.06536 43.499643,-93.062479 43.499646,-93.062258 43.499648,-93.062034 43.49965,-93.060461 43.499665,-93.06044 43.499665,-93.059957 43.49967,-93.057505 43.499679,-93.05587 43.499694,-93.055393 43.499694,-93.052766 43.499693,-93.050191 43.499703,-93.050167 43.499703,-93.049192 43.499705,-93.045953 43.499705,-93.044382 43.499717,-93.042809 43.499725,-93.041064 43.499711,-93.039356 43.499703,-93.038827 43.499701,-93.036193 43.499708,-93.03361 43.499724,-93.029556 43.499725,-93.028096 43.49972,-93.024345 43.499733,-93.019507 43.499715,-93.014151 43.499744,-93.009604 43.499746,-93.009545 43.499747,-93.009544 43.499736,-93.008754 43.49974,-93.007706 43.499755,-93.006525 43.499758,-93.002984 43.499768,-93.00199 43.499771,-93.001897 43.499772,-93.001804 43.499772,-93.001722 43.499772,-93.001641 43.499772,-93.001015 43.499774,-92.99982 43.499774,-92.99811 43.499776,-92.993869 43.499767,-92.991956 43.499763,-92.991886 43.499763,-92.991261 43.499763,-92.989388 43.499763,-92.988764 43.499763,-92.988536 43.499763,-92.987852 43.499763,-92.987625 43.499763,-92.987445 43.499763,-92.986907 43.499763,-92.986728 43.499763,-92.985943 43.499763,-92.983591 43.499763,-92.983054 43.499764,-92.982807 43.499764,-92.9826 43.499764,-92.981982 43.499765,-92.981776 43.499766,-92.981625 43.499766,-92.981174 43.499766,-92.981024 43.499767,-92.980928 43.499767,-92.980642 43.499767,-92.980547 43.499768,-92.980338 43.499768,-92.979712 43.499768,-92.979504 43.499769,-92.978397 43.49974,-92.975076 43.499656,-92.973969 43.499628,-92.972338 43.499608,-92.967447 43.499548,-92.965817 43.499529,-92.964046 43.499585,-92.958733 43.499757,-92.956964 43.499815,-92.956413 43.499759,-92.954766 43.499596,-92.954217 43.499542,-92.953292 43.499596,-92.950523 43.499759,-92.9496 43.499814,-92.949549 43.499813,-92.949407 43.499816,-92.949364 43.499815,-92.94946 43.499595,-92.949446 43.499584,-92.949403 43.499552,-92.94939 43.499542,-92.949253 43.499542,-92.948842 43.499542,-92.948706 43.499543,-92.948302 43.499597,-92.947092 43.499761,-92.947017 43.499771,-92.94669 43.499816,-92.946587 43.49976,-92.946282 43.499596,-92.946183 43.499543,-92.945853 43.49959,-92.944866 43.499736,-92.944539 43.499786,-92.94449 43.499784,-92.944345 43.499778,-92.944298 43.499777,-92.943906 43.499739,-92.943806 43.499731,-92.94233 43.499606,-92.941839 43.499566,-92.941726 43.499561,-92.941391 43.499547,-92.941279 43.499544,-92.941227 43.499775,-92.941027 43.499729,-92.940433 43.499597,-92.940236 43.499555,-92.940107 43.499548,-92.939727 43.499529,-92.9396 43.499523,-92.939577 43.499786,-92.937961 43.499786,-92.933125 43.499791,-92.931513 43.499794,-92.931151 43.499751,-92.930067 43.499627,-92.929708 43.499586,-92.929627 43.499805,-92.928733 43.499754,-92.926058 43.499603,-92.925167 43.499554,-92.923811 43.499604,-92.919747 43.499759,-92.918394 43.499812,-92.918355 43.499592,-92.917842 43.499585,-92.916305 43.499564,-92.915793 43.499558,-92.913739 43.499559,-92.907581 43.499562,-92.905528 43.499564,-92.90512 43.499561,-92.9039 43.499554,-92.903494 43.499553,-92.90344 43.499847,-92.902641 43.499845,-92.900249 43.499845,-92.899456 43.499848,-92.899427 43.49958,-92.897621 43.499622,-92.892203 43.499751,-92.890397 43.499796,-92.890283 43.499806,-92.889948 43.49984,-92.889838 43.499853,-92.88965 43.499537,-92.888312 43.499601,-92.884304 43.499797,-92.882969 43.499863,-92.882899 43.499551,-92.882042 43.499554,-92.87947 43.499568,-92.878614 43.499573,-92.878525 43.49987,-92.876869 43.499836,-92.875069 43.499802,-92.871911 43.499635,-92.87026 43.49955,-92.867925 43.499547,-92.860921 43.499541,-92.858588 43.499539,-92.858586 43.499912,-92.858212 43.499908,-92.857096 43.499907,-92.856726 43.499907,-92.856656 43.499535,-92.855236 43.499533,-92.850979 43.499532,-92.849561 43.499534,-92.849466 43.499904,-92.846739 43.499831,-92.838565 43.499623,-92.835841 43.499553,-92.83583 43.499917,-92.834714 43.499843,-92.831371 43.499626,-92.830258 43.499555,-92.829931 43.499549,-92.828954 43.499534,-92.828629 43.499529,-92.828605 43.49992,-92.827231 43.49993,-92.823113 43.499969,-92.821742 43.499982,-92.819447 43.499974,-92.812568 43.499956,-92.810276 43.499951,-92.809946 43.499951,-92.808956 43.499952,-92.808627 43.499953,-92.808608 43.499953,-92.806177 43.499958,-92.804958 43.499962,-92.802246 43.499974,-92.793952 43.499982,-92.790284 43.499986,-92.789954 43.499985,-92.788967 43.499985,-92.788644 43.499985,-92.788638 43.499985,-92.78741 43.499984,-92.783726 43.499984,-92.78367 43.499984,-92.782499 43.499991,-92.781346 43.499998,-92.780775 43.500002,-92.777888 43.499996,-92.776736 43.499995,-92.775343 43.499992,-92.774267 43.49999,-92.771165 43.499998,-92.769773 43.500002,-92.769592 43.500002,-92.769051 43.500004,-92.768871 43.500005,-92.767524 43.500004,-92.763483 43.500003,-92.762137 43.500003,-92.761375 43.500003,-92.760109 43.500006,-92.754027 43.500023,-92.752 43.500029,-92.751634 43.500029,-92.750538 43.500032,-92.750173 43.500034,-92.749988 43.500034,-92.749433 43.500035,-92.749249 43.500036,-92.748941 43.500036,-92.748019 43.500039,-92.747712 43.50004,-92.747599 43.50004,-92.747485 43.500041,-92.747474 43.50004,-92.747261 43.50004,-92.747149 43.50004,-92.744232 43.500034,-92.740278 43.500027,-92.739571 43.500056,-92.737097 43.500045,-92.735484 43.500044,-92.732568 43.500043,-92.732501 43.500043,-92.732302 43.500043,-92.732236 43.500043,-92.732003 43.500043,-92.731556 43.500043,-92.729519 43.500044,-92.72884 43.500045,-92.728652 43.500045,-92.728634 43.500045,-92.72817 43.500045,-92.726487 43.500047,-92.726163 43.500048,-92.725495 43.500051,-92.724954 43.500052,-92.723334 43.500059,-92.722794 43.500062,-92.720721 43.500069,-92.71781 43.500081,-92.714505 43.500079,-92.712433 43.500079,-92.712023 43.500078,-92.710796 43.500078,-92.710387 43.500078,-92.710158 43.500078,-92.709474 43.500078,-92.709246 43.500078,-92.708885 43.500078,-92.708573 43.500078,-92.706556 43.500078,-92.705999 43.500079,-92.705998 43.500079,-92.705884 43.50008,-92.704662 43.500065,-92.703238 43.500073,-92.698329 43.500101,-92.695302 43.500095,-92.693961 43.500093,-92.692657 43.500096,-92.691931 43.500097,-92.689757 43.5001,-92.689032 43.500102,-92.688735 43.500102,-92.688718 43.500102,-92.688717 43.500102,-92.685742 43.500109,-92.676236 43.500132,-92.675872 43.500132,-92.672582 43.500134,-92.672236 43.500134,-92.671202 43.500134,-92.670857 43.500135,-92.670483 43.500135,-92.669362 43.500135,-92.668989 43.500136,-92.668723 43.500136,-92.668717 43.500136,-92.666963 43.500143,-92.660888 43.500171,-92.659117 43.50018,-92.659048 43.50018,-92.658863 43.500182,-92.657727 43.50019,-92.655662 43.500206,-92.65432 43.50021,-92.653185 43.500215,-92.652698 43.500216,-92.652386 43.500213,-92.649991 43.500192,-92.649193 43.500185,-92.648819 43.500182,-92.648798 43.500182,-92.6482 43.500177,-92.64806 43.500176,-92.645221 43.500183,-92.644228 43.500186,-92.64392 43.500186,-92.643751 43.500187,-92.642997 43.500186,-92.64269 43.500186,-92.641358 43.500185,-92.637363 43.500183,-92.636351 43.500183,-92.636032 43.500184,-92.635406 43.500186,-92.633532 43.500193,-92.632907 43.500196,-92.632616 43.500196,-92.631743 43.5002,-92.631453 43.500202,-92.631017 43.500203,-92.629709 43.500208,-92.629274 43.50021,-92.62892 43.500211,-92.628825 43.500211,-92.628789 43.500212,-92.627859 43.500215,-92.627506 43.500217,-92.627502 43.500217,-92.627403 43.500217,-92.627076 43.500213,-92.625786 43.500201,-92.625357 43.500197,-92.624351 43.500187,-92.622932 43.500191,-92.615657 43.500212,-92.614265 43.500217,-92.613233 43.500213,-92.612372 43.500209,-92.609789 43.500198,-92.608928 43.500195,-92.608822 43.500195,-92.60882 43.500195,-92.608311 43.500192,-92.60712 43.500188,-92.606463 43.500191,-92.605847 43.500194,-92.60478 43.500198,-92.60158 43.500212,-92.601476 43.500213,-92.600514 43.500214,-92.599029 43.500215,-92.594574 43.500221,-92.59309 43.500224,-92.592672 43.500224,-92.591421 43.500225,-92.591004 43.500226,-92.590612 43.500226,-92.589437 43.500228,-92.589046 43.500229,-92.588846 43.500229,-92.58883 43.500229,-92.585861 43.500237,-92.582232 43.500247,-92.576309 43.500258,-92.57487 43.500261,-92.573125 43.500261,-92.572282 43.500261,-92.569755 43.500262,-92.568913 43.500263,-92.568899 43.500263,-92.566923 43.500263,-92.56156 43.500266,-92.560956 43.500269,-92.558968 43.500279,-92.558872 43.500279,-92.558031 43.500284,-92.557421 43.500276,-92.553128 43.500218,-92.553128 43.500231,-92.552189 43.500207,-92.549786 43.50019,-92.547619 43.500201,-92.545338 43.500194,-92.543362 43.5002,-92.543274 43.5002,-92.540806 43.500223,-92.538879 43.500223,-92.538844 43.500223,-92.53622 43.50023,-92.53407 43.500228,-92.532116 43.500241,-92.531897 43.500243,-92.528958 43.500234,-92.528934 43.500234,-92.527893 43.500231,-92.525578 43.500242,-92.51471 43.500244,-92.514389 43.500244,-92.508949 43.500245,-92.508018 43.500242,-92.505649 43.500237,-92.505064 43.500243,-92.505036 43.500244,-92.504148 43.500257,-92.504039 43.500257,-92.503833 43.500257,-92.501937 43.500263,-92.500003 43.500262,-92.499169 43.500267,-92.499073 43.500273,-92.498984 43.500295,-92.498977 43.500298,-92.498902 43.500335,-92.498818 43.500382,-92.498817 43.500388,-92.498816 43.500388,-92.498815 43.500388,-92.498802 43.500392,-92.498621 43.500388,-92.497683 43.500383,-92.497544 43.500376,-92.497529 43.500376,-92.497101 43.500364,-92.496897 43.500349,-92.496829 43.500333,-92.496192 43.500285,-92.495537 43.500254,-92.495509 43.500258,-92.495393 43.500276,-92.494918 43.500284,-92.49466 43.500284,-92.489556 43.500287,-92.489339 43.500288,-92.487972 43.500293,-92.487613 43.500295,-92.487469 43.500295,-92.487254 43.500296,-92.484047 43.500309,-92.475706 43.500281,-92.47521 43.50028,-92.473717 43.500282,-92.471011 43.500293,-92.4689 43.500286,-92.467738 43.500288,-92.464712 43.50029,-92.464473 43.50029,-92.464436 43.500286,-92.464386 43.500281,-92.46305 43.500305,-92.462726 43.500311,-92.46174 43.500356,-92.458974 43.500413,-92.457664 43.50041,-92.455542 43.500404,-92.455483 43.500403,-92.454727 43.500416,-92.454599 43.500417,-92.452943 43.500429,-92.452761 43.500431,-92.448948 43.500459,-92.446609 43.500455,-92.445532 43.500478,-92.444491 43.5005,-92.441276 43.500483,-92.439035 43.500534,-92.437649 43.500551,-92.435833 43.500513,-92.435606 43.5005,-92.43553 43.500508,-92.4351 43.500551,-92.432482 43.500551,-92.432202 43.500538,-92.431679 43.500513,-92.431629 43.500514,-92.430806 43.500538,-92.428789 43.500536,-92.426605 43.500581,-92.423699 43.500542,-92.420718 43.500551,-92.418921 43.500606,-92.418841 43.500615,-92.418822 43.500615,-92.416393 43.500568,-92.414187 43.500548,-92.413617 43.500546,-92.41345 43.500546,-92.412302 43.500586,-92.410517 43.500535,-92.409641 43.500587,-92.409227 43.500605,-92.40894 43.500599,-92.40893 43.500599,-92.40892 43.500591,-92.408853 43.500554,-92.408759 43.50053,-92.408616 43.500524,-92.406555 43.50054,-92.406228 43.500543,-92.405959 43.500512,-92.40466 43.500518,-92.402362 43.500539,-92.400735 43.500538,-92.396296 43.500558,-92.395014 43.500565,-92.391775 43.500564,-92.388307 43.500486,-92.38781 43.500486,-92.387773 43.500486,-92.386601 43.500481,-92.386078 43.500504,-92.386019 43.500506,-92.384228 43.500589,-92.384065 43.500593,-92.384008 43.500595,-92.382653 43.500628,-92.381652 43.500643,-92.381278 43.500648,-92.381127 43.500645,-92.38042 43.500629,-92.380036 43.500622,-92.379948 43.500623,-92.379582 43.500623,-92.379289 43.500634,-92.378222 43.50067,-92.37796 43.500684,-92.377876 43.50069,-92.377828 43.500661,-92.377476 43.500633,-92.376803 43.500609,-92.376589 43.500603,-92.376194 43.500603,-92.37518 43.500623,-92.369057 43.500491,-92.368858 43.500519,-92.368032 43.500548,-92.366616 43.500546,-92.36631 43.500545,-92.36176 43.500541,-92.361281 43.50054,-92.35871 43.500537,-92.355731 43.500559,-92.352429 43.500584,-92.351275 43.500582,-92.349768 43.500606,-92.349383 43.500607,-92.34938 43.500607,-92.347494 43.500612,-92.34683 43.500604,-92.346811 43.500604,-92.346192 43.500596,-92.345492 43.500588,-92.343149 43.5006,-92.329381 43.500554,-92.329374 43.500553,-92.328613 43.500551,-92.327558 43.500542,-92.326344 43.500549,-92.325508 43.500553,-92.323193 43.500549,-92.320967 43.500538,-92.320451 43.500539,-92.318606 43.500543,-92.316282 43.500538,-92.316214 43.500538,-92.314296 43.500545,-92.312499 43.500541,-92.310125 43.500551,-92.309347 43.500549,-92.301706 43.500567,-92.301323 43.500564,-92.301187 43.500562,-92.29472 43.500555,-92.28939 43.500555,-92.28855 43.50058,-92.288185 43.500576,-92.288119 43.500576,-92.287322 43.50057,-92.286244 43.500573,-92.285067 43.500591,-92.282894 43.500597,-92.282099 43.500587,-92.279708 43.500599,-92.279473 43.5006,-92.27922 43.500594,-92.27907 43.50059,-92.27892 43.500582,-92.278896 43.500577,-92.278842 43.500565,-92.278778 43.500551,-92.278471 43.500443,-92.278464 43.50044,-92.277505 43.500466,-92.277498 43.500468,-92.277403 43.5005,-92.277212 43.500544,-92.277178 43.500548,-92.277023 43.500565,-92.276898 43.500579,-92.276858 43.500579,-92.275192 43.500593,-92.275019 43.500592,-92.27205 43.500585,-92.271699 43.500596,-92.271669 43.5006,-92.27162 43.500598,-92.271119 43.500582,-92.27024 43.500588,-92.269424 43.500593,-92.266586 43.50058,-92.264362 43.500588,-92.262145 43.500586,-92.261385 43.500588,-92.260896 43.50059,-92.25702 43.500593,-92.257018 43.500594,-92.255848 43.500598,-92.254146 43.500587,-92.252375 43.50061,-92.250977 43.500606,-92.250004 43.500617,-92.249945 43.500617,-92.249369 43.500615,-92.249353 43.500615,-92.247813 43.500611,-92.243719 43.500595,-92.241129 43.500599,-92.239329 43.500592,-92.239307 43.500592,-92.238203 43.500587,-92.237466 43.500582,-92.237314 43.50058,-92.23671 43.500577,-92.234549 43.500591,-92.232801 43.50058,-92.23119 43.500589,-92.231136 43.50059,-92.2307 43.500594,-92.22927 43.500592,-92.227861 43.500596,-92.227197 43.500604,-92.22678 43.500604,-92.226305 43.500603,-92.225956 43.500617,-92.225663 43.500633,-92.225306 43.500633,-92.224874 43.500613,-92.224693 43.500605,-92.224379 43.500601,-92.224169 43.50061,-92.224037 43.500627,-92.22389 43.500657,-92.223696 43.500723,-92.222514 43.500614,-92.2128 43.500525,-92.212735 43.500525,-92.208954 43.500518,-92.201511 43.500539,-92.20144 43.500559,-92.200699 43.500564,-92.200386 43.500572,-92.200242 43.500571,-92.199971 43.500559,-92.199609 43.500551,-92.199207 43.500547,-92.199023 43.500545,-92.198952 43.500544,-92.198875 43.500556,-92.182557 43.500677,-92.182517 43.500677,-92.178863 43.500713,-92.178723 43.500714,-92.1719 43.50078,-92.171899 43.500781,-92.171888 43.500778,-92.171807 43.500763,-92.171616 43.500735,-92.171355 43.500722,-92.171267 43.500718,-92.170091 43.500704,-92.169264 43.500713,-92.168706 43.500728,-92.16789 43.500731,-92.166118 43.500729,-92.16471 43.500743,-92.164145 43.500732,-92.163238 43.50073,-92.16313 43.500733,-92.162965 43.500738,-92.16235 43.500741,-92.159514 43.500725,-92.158845 43.500715,-92.158146 43.500722,-92.157158 43.500724,-92.15689 43.500718,-92.156538 43.500709,-92.156315 43.500704,-92.155701 43.50071,-92.15446 43.500719,-92.151798 43.500714,-92.150666 43.50072,-92.150135 43.500719,-92.149913 43.500727,-92.1499 43.500727,-92.147595 43.50079,-92.147548 43.500791,-92.140197 43.500776,-92.139994 43.500778,-92.139258 43.500782,-92.137138 43.500749,-92.136868 43.500733,-92.136736 43.500715,-92.136463 43.500699,-92.136155 43.5007,-92.134966 43.500701,-92.134439 43.500699,-92.132813 43.500694,-92.132077 43.500692,-92.130374 43.500667,-92.12985 43.500656,-92.129169 43.500665,-92.127838 43.500681,-92.127034 43.500677,-92.124906 43.500707,-92.124265 43.500715,-92.12262 43.500736,-92.11937 43.500779,-92.119243 43.500783,-92.118916 43.500792,-92.118674 43.500798,-92.118499 43.500821,-92.118489 43.50082,-92.117583 43.500806,-92.117376 43.500802,-92.103886 43.500735,-92.10124 43.500722,-92.101065 43.500721,-92.092871 43.500683,-92.092823 43.500683,-92.092391 43.500671,-92.09206 43.500674,-92.090828 43.500684,-92.089973 43.500683,-92.089478 43.500683,-92.087042 43.500697,-92.082925 43.50071,-92.082748 43.500728,-92.082684 43.500769,-92.081222 43.500674,-92.079802 43.500621,-92.079777 43.500636,-92.079469 43.500675,-92.07852 43.500693,-92.077428 43.500684,-92.076851 43.50068,-92.076255 43.50068,-92.074754 43.500731,-92.073968 43.500737,-92.072667 43.500758,-92.071646 43.500745,-92.067914 43.500758,-92.065552 43.500754,-92.062646 43.500771,-92.060569 43.500722,-92.060462 43.500719,-92.060107 43.500718,-92.060001 43.500717,-92.059314 43.500716,-92.058988 43.500711,-92.05886 43.500709,-92.058849 43.500708,-92.057973 43.500695,-92.056941 43.5007,-92.056517 43.500703,-92.055722 43.500718,-92.05415 43.50071,-92.053883 43.500708,-92.053508 43.500706,-92.053501 43.500705,-92.052295 43.5007,-92.051354 43.500718,-92.048969 43.5007,-92.048341 43.500712,-92.04829 43.500713,-92.048253 43.500714,-92.047247 43.500736,-92.042822 43.500711,-92.041213 43.500719,-92.037644 43.500668,-92.037016 43.500662,-92.035947 43.500687,-92.034642 43.500676,-92.032444 43.500637,-92.030688 43.500624,-92.030586 43.500624,-92.029845 43.500621,-92.029679 43.500622,-92.0296 43.500622,-92.02577 43.500638,-92.025008 43.500656,-92.024616 43.500656,-92.024576 43.500656,-92.024213 43.500657,-92.023262 43.500637,-92.021677 43.500642,-92.020136 43.500642,-92.018899 43.500673,-92.017114 43.500681,-92.017095 43.500681,-92.01684 43.500682,-92.015969 43.500656,-92.014479 43.500683,-92.013342 43.500674,-92.013171 43.500676,-92.012506 43.500686,-92.012368 43.500688,-92.012275 43.50069,-92.011151 43.500681,-92.011101 43.50068,-92.011093 43.500679,-92.0102 43.500668,-92.009689 43.500683,-92.00968 43.500683,-92.00934 43.500694,-92.006445 43.500723,-92.005055 43.500749,-92.003959 43.500748,-92.002046 43.500765,-92.000624 43.500758,-92.000083 43.500762,-91.99997 43.500763,-91.998647 43.50074,-91.996169 43.500722,-91.995624 43.500718,-91.991285 43.500687,-91.990863 43.500685,-91.990589 43.500683,-91.989923 43.500687,-91.98985 43.500687,-91.989841 43.500687,-91.989599 43.500688,-91.989577 43.500689,-91.988524 43.500691,-91.987773 43.500675,-91.985337 43.500678,-91.983872 43.500683,-91.98259 43.500672,-91.979335 43.50067,-91.978914 43.500668,-91.978092 43.500666,-91.976486 43.500681,-91.975676 43.500682,-91.974797 43.500682,-91.97322 43.500672,-91.971462 43.500645,-91.970713 43.500645,-91.970359 43.500645,-91.969928 43.500652,-91.969679 43.500655,-91.969639 43.500656,-91.969387 43.500654,-91.968418 43.500675,-91.967015 43.500669,-91.966086 43.500665,-91.962082 43.50067,-91.961928 43.50068,-91.961681 43.500689,-91.96132 43.500711,-91.960671 43.500719,-91.959574 43.500693,-91.958887 43.5007,-91.958567 43.500718,-91.957824 43.500732,-91.957376 43.500723,-91.957009 43.500715,-91.957008 43.500714,-91.956355 43.500693,-91.955574 43.500691,-91.954954 43.500678,-91.954383 43.500659,-91.953803 43.500658,-91.953245 43.500676,-91.952609 43.500679,-91.951935 43.500694,-91.951302 43.500686,-91.9505 43.500673,-91.950264 43.500667,-91.95005 43.500672,-91.95001 43.500674,-91.947548 43.500504,-91.947543 43.500504,-91.941701 43.500705,-91.9417 43.500705,-91.94171 43.500716,-91.941595 43.500703,-91.941265 43.500692,-91.940642 43.500705,-91.940393 43.500702,-91.940385 43.500701,-91.939155 43.50069,-91.938072 43.500699,-91.936704 43.500705,-91.934309 43.500694,-91.930994 43.500716,-91.930795 43.500717,-91.93074 43.500713,-91.930695 43.500711,-91.930499 43.500699,-91.930194 43.500639,-91.930043 43.500895,-91.929845 43.500825,-91.929644 43.500762,-91.92939 43.500734,-91.929186 43.500733,-91.928715 43.500728,-91.922328 43.500765,-91.921429 43.500783,-91.921279 43.500783,-91.920746 43.500782,-91.919506 43.500794,-91.918487 43.500791,-91.917557 43.500793,-91.917396 43.500794,-91.915063 43.500801,-91.914287 43.500799,-91.91331 43.500818,-91.911801 43.500807,-91.911035 43.500815,-91.91071 43.500817,-91.909918 43.500819,-91.90972 43.500819,-91.909708 43.500819,-91.908596 43.500803,-91.908144 43.500797,-91.906802 43.500796,-91.905887 43.500803,-91.905587 43.500799,-91.90328 43.500772,-91.901058 43.500768,-91.899426 43.50075,-91.897334 43.500749,-91.895946 43.500742,-91.894177 43.500727,-91.892278 43.50073,-91.890218 43.500744,-91.889963 43.500743,-91.88982 43.500743,-91.889816 43.500743,-91.88801 43.500742,-91.887626 43.500741,-91.883156 43.500741,-91.881628 43.50076,-91.878468 43.500752,-91.87826 43.500751,-91.87487 43.500742,-91.873325 43.500731,-91.871121 43.500724,-91.87088 43.500713,-91.870643 43.500728,-91.87045 43.500776,-91.870278 43.50084,-91.870238 43.500861,-91.8702 43.500848,-91.870011 43.500787,-91.86978 43.500752,-91.869558 43.500731,-91.869305 43.500719,-91.868244 43.500709,-91.866153 43.500721,-91.863916 43.500715,-91.862172 43.500735,-91.851188 43.500733,-91.850941 43.500719,-91.84986 43.50074,-91.8497 43.50074,-91.8497 43.500741,-91.8497 43.500742,-91.848686 43.500758,-91.846408 43.500753,-91.846386 43.500752,-91.844793 43.500749,-91.842748 43.500782,-91.837725 43.500774,-91.836378 43.50079,-91.836117 43.500789,-91.83595 43.500788,-91.834974 43.500782,-91.830666 43.500731,-91.830608 43.500729,-91.83022 43.500751,-91.830089 43.500758,-91.829682 43.500732,-91.828112 43.50074,-91.828025 43.50074,-91.825303 43.500755,-91.825054 43.50074,-91.824969 43.500729,-91.824949 43.500684,-91.820262 43.500688,-91.820092 43.500688,-91.815625 43.500673,-91.811008 43.500659,-91.807175 43.500647,-91.807093 43.500678,-91.807002 43.500703,-91.806924 43.500716,-91.806827 43.500726,-91.806809 43.500728,-91.80651 43.500735,-91.806312 43.500733,-91.806015 43.500722,-91.805718 43.500699,-91.80562 43.500699,-91.805577 43.500704,-91.798876 43.500688,-91.798844 43.500688,-91.790616 43.5007,-91.782494 43.500712,-91.782124 43.500723,-91.778969 43.500809,-91.77892 43.500809,-91.778914 43.500809,-91.778802 43.500811,-91.778518 43.500827,-91.778221 43.500844,-91.777526 43.500842,-91.777161 43.50083,-91.777002 43.500794,-91.776876 43.500707,-91.761252 43.500829,-91.760797 43.500802,-91.760017 43.500801,-91.759261 43.500782,-91.758876 43.500777,-91.758608 43.500774,-91.758606 43.500774,-91.756934 43.500753,-91.756812 43.500751,-91.754728 43.500725,-91.753756 43.500733,-91.752901 43.500717,-91.749998 43.500696,-91.749302 43.500702,-91.748348 43.5007,-91.746319 43.500716,-91.745681 43.500722,-91.741754 43.500735,-91.741323 43.500743,-91.740479 43.500746,-91.739618 43.500743,-91.738637 43.500751,-91.737529 43.500761,-91.737398 43.500756,-91.737321 43.500739,-91.73726 43.500699,-91.737226 43.500655,-91.737217 43.500548,-91.736558 43.500561,-91.73333 43.500623,-91.7314 43.50066,-91.730879 43.500711,-91.730487 43.50075,-91.73033 43.500769,-91.730217 43.500806,-91.730196 43.500795,-91.730095 43.500753,-91.730068 43.500745,-91.730041 43.500737,-91.729967 43.500736,-91.72568 43.500746,-91.725461 43.500744,-91.720531 43.500695,-91.716017 43.500721,-91.715533 43.500719,-91.715288 43.500722,-91.711846 43.500761,-91.700717 43.500704,-91.680517 43.500631,-91.675275 43.50058,-91.670808 43.500598,-91.663398 43.500628,-91.663364 43.500628,-91.662242 43.500611,-91.662234 43.500611,-91.661723 43.500603,-91.660844 43.50056,-91.658394 43.500532,-91.656019 43.500506,-91.655847 43.500504,-91.651397 43.500454,-91.65074 43.500462,-91.65052 43.500501,-91.650032 43.500538,-91.649537 43.500532,-91.648948 43.500534,-91.648519 43.500544,-91.648452 43.500542,-91.647692 43.500559,-91.646744 43.500562,-91.645711 43.500555,-91.645106 43.500551,-91.645022 43.500564,-91.644942 43.500577,-91.644853 43.50062,-91.644788 43.500652,-91.644603 43.500643,-91.644456 43.500636,-91.643804 43.500638,-91.643263 43.500612,-91.642966 43.500588,-91.642821 43.500539,-91.642704 43.500539,-91.640866 43.500547,-91.640758 43.500563,-91.640678 43.500548,-91.640419 43.500552,-91.640334 43.500554,-91.639681 43.500573,-91.635626 43.500463,-91.634495 43.50044,-91.634369 43.500437,-91.634367 43.500459,-91.634366 43.500481,-91.634244 43.500479,-91.632178 43.500441,-91.631363 43.500481,-91.628625 43.500617,-91.625611 43.500727,-91.625478 43.500704,-91.625145 43.500707,-91.621114 43.500679,-91.620899 43.500678,-91.620225 43.500679,-91.617551 43.500688,-91.617394 43.500628,-91.617276 43.500615,-91.617199 43.500632,-91.617114 43.500663,-91.61578 43.500672,-91.615646 43.500651,-91.61559 43.500642,-91.615519 43.500636,-91.615145 43.500628,-91.614202 43.500621,-91.613181 43.500632,-91.610835 43.500657,-91.610429 43.500679,-91.608269 43.500669,-91.60562 43.500663,-91.603088 43.500671,-91.59446 43.500668,-91.591682 43.50067,-91.591428 43.500671,-91.59098 43.50068,-91.5897 43.500656,-91.573642 43.500554,-91.571002 43.500538,-91.570817 43.500537,-91.570263 43.500537,-91.570079 43.500537,-91.569047 43.500537,-91.565954 43.500537,-91.564923 43.500537,-91.563753 43.500537,-91.560245 43.500537,-91.559076 43.500538,-91.551043 43.500668,-91.550693 43.500675,-91.549274 43.50069,-91.546324 43.500684,-91.545258 43.500686,-91.542352 43.500692,-91.541558 43.500684,-91.533806 43.50056,-91.532847 43.500568,-91.529973 43.500594,-91.529015 43.500604,-91.527462 43.500605,-91.522803 43.500611,-91.521788 43.500613,-91.52125 43.500613,-91.521218 43.500613,-91.521122 43.500613,-91.521091 43.500614,-91.520394 43.500614,-91.518306 43.500616,-91.51761 43.500618,-91.51487 43.500621,-91.506649 43.500631,-91.50391 43.500635,-91.50014 43.50064,-91.499092 43.500659,-91.493896 43.500754,-91.493832 43.500755,-91.493522 43.500777,-91.49104 43.500714,-91.490612 43.500703,-91.48537 43.500704,-91.484196 43.500704,-91.482559 43.500697,-91.480499 43.500689,-91.480106 43.500687,-91.47948 43.500684,-91.479462 43.500684,-91.479452 43.500684,-91.475036 43.500624,-91.471954 43.500685,-91.471895 43.500686,-91.469097 43.500726,-91.465349 43.500686,-91.465287 43.500686,-91.465268 43.500684,-91.464667 43.500612,-91.464014 43.500617,-91.462055 43.500635,-91.461403 43.500642,-91.460591 43.500639,-91.458157 43.50063,-91.457346 43.500628,-91.455647 43.500621,-91.450552 43.500603,-91.448854 43.500598,-91.448219 43.500605,-91.446318 43.500626,-91.445684 43.500633,-91.445642 43.500633,-91.445516 43.500633,-91.445475 43.500633,-91.444766 43.500632,-91.442641 43.500629,-91.442385 43.500629,-91.442164 43.500633,-91.441934 43.500618,-91.441872 43.500629,-91.441162 43.50063,-91.438843 43.500635,-91.438071 43.500637,-91.438035 43.500638,-91.437984 43.500641,-91.43793 43.500643,-91.437895 43.500645,-91.436624 43.500701,-91.436545 43.500705,-91.432808 43.500719,-91.431536 43.500724,-91.429618 43.500732,-91.423866 43.500759,-91.421949 43.500768,-91.419972 43.500776,-91.416064 43.500793,-91.414041 43.5008,-91.412064 43.500808,-91.411155 43.500811,-91.408429 43.500821,-91.407521 43.500825,-91.406908 43.500827,-91.405071 43.500834,-91.404625 43.500836,-91.404459 43.500837,-91.403368 43.500844,-91.400096 43.500869,-91.399006 43.500877,-91.398456 43.500881,-91.397581 43.500885,-91.397319 43.500887,-91.393308 43.500805,-91.391884 43.500777,-91.390655 43.500752,-91.386968 43.500678,-91.38589 43.500657,-91.385739 43.500656,-91.383917 43.500624,-91.378451 43.500531,-91.376629 43.500501,-91.375136 43.500596,-91.372672 43.500577,-91.372029 43.500773,-91.371786 43.500837,-91.371716 43.500856,-91.371542 43.500925,-91.371256 43.500837,-91.369325 43.500827,-91.367422 43.500819,-91.361713 43.500796,-91.35981 43.500789,-91.359742 43.500788,-91.359538 43.500788,-91.359471 43.500788,-91.35915 43.500786,-91.352583 43.500754,-91.352329 43.500753,-91.342974 43.500704,-91.336052 43.500721,-91.331679 43.500731,-91.331163 43.500732,-91.329618 43.500735,-91.329103 43.500737,-91.32907 43.500737,-91.328974 43.500737,-91.328942 43.500737,-91.328908 43.500737,-91.328858 43.500738,-91.328808 43.500738,-91.328775 43.500738,-91.327711 43.50074,-91.324519 43.500747,-91.323456 43.50075,-91.323287 43.50075,-91.322781 43.500752,-91.322613 43.500753,-91.317025 43.500771,-91.300262 43.500825,-91.295077 43.500842,-91.294675 43.500837,-91.294156 43.50083,-91.29356 43.500822,-91.292599 43.500827,-91.29208 43.500831,-91.290606 43.50084,-91.286187 43.50087,-91.284714 43.500881,-91.284651 43.50088,-91.284463 43.500879,-91.284401 43.500879,-91.284271 43.500878,-91.283883 43.500876,-91.283754 43.500876,-91.282878 43.500873,-91.282695 43.500872,-91.282652 43.500871,-91.282523 43.500871,-91.282481 43.500871,-91.280282 43.500877,-91.273688 43.500896,-91.27149 43.500903,-91.271481 43.500903,-91.271465 43.500903,-91.271454 43.500903,-91.271446 43.500903,-91.271339 43.500903,-91.271223 43.500904,-91.271018 43.500905,-91.270912 43.500906,-91.270378 43.500908,-91.268777 43.500916,-91.268244 43.50092,-91.268199 43.50092,-91.268189 43.50092,-91.268025 43.500921,-91.267971 43.500922,-91.267916 43.500922,-91.267834 43.500923,-91.267751 43.500923,-91.267696 43.500923,-91.266534 43.500937,-91.263048 43.500978,-91.261887 43.500993,-91.261852 43.500993,-91.261781 43.500993,-91.261749 43.500994,-91.261715 43.500996,-91.261499 43.501004,-91.261283 43.501013,-91.261184 43.501015,-91.25748 43.500984,-91.253803 43.500901,-91.253729 43.500899,-91.253623 43.500897,-91.25351 43.500894,-91.253437 43.500893,-91.253418 43.50089,-91.253246 43.500865,-91.252673 43.500785,-91.252482 43.500759,-91.252101 43.50074,-91.251812 43.500747,-91.250132 43.50079,-91.249814 43.500697,-91.249353 43.500562,-91.249166 43.500532,-91.248933 43.500527,-91.248586 43.500522,-91.248237 43.500515,-91.248005 43.500511,-91.247728 43.500505,-91.246899 43.50049,-91.246623 43.500485,-91.246522 43.500486,-91.246218 43.500489,-91.246118 43.500491,-91.246016 43.500491,-91.245892 43.500491,-91.245712 43.500491,-91.245611 43.500492,-91.245381 43.500492,-91.24469 43.500494,-91.244461 43.500495,-91.244349 43.500494,-91.244182 43.500494,-91.244015 43.500494,-91.243904 43.500494,-91.243862 43.500493,-91.243799 43.500492,-91.243736 43.500491,-91.243694 43.500491,-91.24351 43.500488,-91.242958 43.500479,-91.242774 43.500476,-91.242615 43.500473,-91.242382 43.50047,-91.242138 43.50047,-91.241979 43.50047,-91.24168 43.500471,-91.241629 43.500471,-91.240582 43.500476,-91.240234 43.500479,-91.239778 43.50048,-91.238412 43.500485,-91.237957 43.500487,-91.236414 43.500491,-91.231786 43.500504,-91.230244 43.500509,-91.229982 43.50051,-91.229197 43.500513,-91.228936 43.500515,-91.228832 43.500516,-91.228519 43.50052,-91.228416 43.500522,-91.228187 43.500515,-91.227679 43.500559,-91.227589 43.500564,-91.227045 43.500562,-91.227026 43.500563,-91.226188 43.500558,-91.225967 43.500556,-91.225479 43.500553,-91.225094 43.50055,-91.224503 43.500543,-91.224473 43.500543,-91.223507 43.50054,-91.22273 43.50055,-91.222371 43.500555,-91.22214 43.500555,-91.22197 43.500555,-91.221716 43.500556,-91.221462 43.500556,-91.221293 43.500557,-91.220995 43.500557,-91.220102 43.500557,-91.219805 43.500557,-91.21945 43.500556,-91.218921 43.500555,-91.218388 43.500552,-91.218035 43.500551,-91.217969 43.50055,-91.21787 43.50055,-91.217771 43.50055,-91.217706 43.50055,-91.21774 43.500401,-91.217839 43.499973,-91.217841 43.499954,-91.217858 43.499803,-91.218008 43.498863,-91.218026 43.498752,-91.218194 43.497701,-91.21827 43.497228,-91.218247 43.497012,-91.218146 43.49605,-91.218144 43.496035,-91.218044 43.495089,-91.218022 43.494879,-91.217956 43.494252,-91.217934 43.494043,-91.217777 43.49255,-91.217615 43.491008,-91.216474 43.488287,-91.216314 43.487905,-91.215925 43.486979,-91.215902 43.486901,-91.215282 43.484798,-91.215508 43.4837,-91.216035 43.481142,-91.216202 43.480764,-91.216676 43.479697,-91.220399 43.471306,-91.223054 43.467641,-91.224586 43.465525,-91.22582 43.464626,-91.226464 43.464158,-91.229503 43.462607,-91.231051 43.461144,-91.232241 43.460018,-91.233187 43.457784,-91.233367 43.455168,-91.233255 43.454735,-91.23323 43.45464,-91.233205 43.454544,-91.232966 43.453618,-91.232802 43.452983,-91.232727 43.452693,-91.232682 43.452522,-91.232638 43.45235,-91.232571 43.452091,-91.232504 43.451832,-91.232276 43.450952,-91.231813 43.45024,-91.23083 43.448732,-91.229848 43.447223,-91.22875 43.445537,-91.223314 43.440179,-91.222734 43.439607,-91.222154 43.439035,-91.22135 43.438242,-91.220546 43.43745,-91.220425 43.437331,-91.218401 43.434946,-91.218182 43.434688,-91.21571 43.432795,-91.213575 43.431159,-91.209049 43.426845,-91.209017 43.426814,-91.208288 43.42612,-91.207145 43.425031,-91.206188 43.423782,-91.20555 43.422949,-91.203144 43.419805,-91.202828 43.419163,-91.202182 43.417852,-91.201537 43.41654,-91.201224 43.415903,-91.200841 43.414486,-91.200359 43.412701,-91.200527 43.408486,-91.199568 43.403812,-91.199561 43.403779,-91.199408 43.403032,-91.198986 43.401851,-91.198962 43.401785,-91.198939 43.401718,-91.198579 43.40071,-91.198219 43.399702,-91.198048 43.399223,-91.197804 43.396708,-91.19767 43.395334,-91.197934 43.394204,-91.198238 43.3929,-91.198542 43.391595,-91.198953 43.389835,-91.199222 43.389235,-91.199283 43.389098,-91.199344 43.388962,-91.19942 43.388792,-91.199497 43.388621,-91.199605 43.388378,-91.199714 43.388134,-91.199741 43.388074,-91.200701 43.38593,-91.201843 43.383983,-91.204831 43.378887,-91.205053 43.378186,-91.205346 43.377262,-91.205679 43.376059,-91.205878 43.375338,-91.206072 43.374976,-91.206165 43.374888,-91.206797 43.374289,-91.207352 43.373676,-91.207367 43.373659,-91.207417 43.373632,-91.208641 43.372971,-91.210233 43.372064,-91.212044 43.371035,-91.212777 43.370512,-91.21336 43.370097,-91.214172 43.369378,-91.214499 43.369033,-91.21473 43.368788,-91.21499 43.368006,-91.215056 43.367185,-91.215062 43.367105,-91.214971 43.366389,-91.21477 43.365874,-91.214453 43.365398,-91.214451 43.365394,-91.213274 43.363628,-91.210497 43.359457,-91.210256 43.359002,-91.209108 43.356838,-91.208646 43.355998,-91.208517 43.355763,-91.208388 43.355529,-91.207574 43.35405,-91.206682 43.352623,-91.20662 43.352524,-91.206601 43.352505,-91.205325 43.351184,-91.20465 43.350478,-91.203964 43.349852,-91.202795 43.349437,-91.20234 43.349277,-91.201847 43.349103,-91.198128 43.348699,-91.194408 43.348296,-91.194274 43.348282,-91.194139 43.348267,-91.188014 43.347602,-91.187595 43.347522,-91.187392 43.347483,-91.187189 43.347445,-91.18666 43.347344,-91.186132 43.347243,-91.185524 43.347127,-91.185474 43.347113,-91.182603 43.346308,-91.181115 43.345926,-91.17935 43.34507,-91.176773 43.34377,-91.175114 43.342934,-91.171493 43.341179,-91.171055 43.340967,-91.169782 43.340512,-91.168288 43.339977,-91.163328 43.338277,-91.154806 43.334826,-91.143678 43.331639,-91.140366 43.330655,-91.140222 43.330612,-91.140077 43.330569,-91.139914 43.33052,-91.13975 43.330472,-91.139546 43.330412,-91.139342 43.330351,-91.137343 43.329757,-91.132813 43.32803,-91.129121 43.32635,-91.127025 43.325076,-91.125129 43.323898,-91.124398 43.323438,-91.122684 43.32236,-91.12097 43.321282,-91.120496 43.321002,-91.120021 43.320723,-91.119959 43.320686,-91.118964 43.3201,-91.11797 43.319514,-91.117661 43.319332,-91.109936 43.315148,-91.109846 43.315099,-91.109755 43.31505,-91.108384 43.314308,-91.107237 43.313645,-91.106579 43.312959,-91.105201 43.311522,-91.102668 43.308934,-91.100283 43.30677,-91.09995 43.306467,-91.098037 43.304182,-91.097769 43.303862,-91.096485 43.302371,-91.095303 43.301264,-91.089124 43.295476,-91.086371 43.292617,-91.085652 43.29187,-91.084309 43.289495,-91.083811 43.288597,-91.083302 43.287678,-91.082792 43.286758,-91.082099 43.285507,-91.081434 43.28469,-91.079875 43.282773,-91.078259 43.280776,-91.077111 43.279177,-91.075679 43.277183,-91.07461 43.27586,-91.07371 43.274746,-91.071724 43.271392,-91.071693 43.26976,-91.071574 43.268193,-91.07193 43.267051,-91.07234 43.265747,-91.072782 43.264363,-91.072863 43.263149,-91.072649 43.262129,-91.071698 43.261014,-91.069937 43.260272,-91.068474 43.259979,-91.067077 43.26011,-91.065456 43.260262,-91.061798 43.259952,-91.05975 43.259074,-91.058644 43.257679,-91.057918 43.255366,-91.057917 43.25524,-91.057914 43.254604,-91.05791 43.253968,-91.059118 43.249979,-91.059684 43.248566,-91.061696 43.244791,-91.061723 43.24474,-91.062132 43.243972,-91.062541 43.243204,-91.062562 43.243165,-91.064438 43.241271,-91.066398 43.239293,-91.067389 43.238543,-91.071857 43.235164,-91.074216 43.232969,-91.079278 43.228259,-91.079573 43.22803,-91.079792 43.227859,-91.080012 43.227688,-91.087456 43.221891,-91.095445 43.216049,-91.096004 43.21564,-91.096562 43.215232,-91.097536 43.21452,-91.097903 43.214252,-91.100436 43.212314,-91.10608 43.207994,-91.10646 43.207704,-91.106839 43.207414,-91.107931 43.206578,-91.109425 43.205635,-91.111198 43.204516,-91.112971 43.203398,-91.113749 43.202908,-91.114281 43.202656,-91.114662 43.202476,-91.115043 43.202295,-91.117057 43.201341,-91.119071 43.200387,-91.119115 43.200366,-91.120391 43.199066,-91.120874 43.198574,-91.121356 43.198083,-91.12217 43.197255,-91.123896 43.193536,-91.124015 43.192277,-91.124217 43.190141,-91.124428 43.187886,-91.124453 43.18786,-91.125129 43.187181,-91.127698 43.183702,-91.134173 43.174405,-91.134364 43.174297,-91.135917 43.173422,-91.138649 43.169993,-91.139002 43.169316,-91.139212 43.168914,-91.139421 43.168513,-91.139692 43.167994,-91.139964 43.167475,-91.139991 43.167422,-91.14116 43.164094,-91.141356 43.163537,-91.141491 43.163037,-91.141662 43.162402,-91.141834 43.161768,-91.142082 43.160853,-91.142329 43.159938,-91.142381 43.159747,-91.142433 43.159557,-91.142504 43.159296,-91.142574 43.159035,-91.142645 43.158772,-91.142716 43.158509,-91.143283 43.156413,-91.144647 43.154539,-91.144972 43.154093,-91.145297 43.153646,-91.145601 43.153228,-91.145905 43.152811,-91.146018 43.152654,-91.1462 43.152405,-91.155859 43.143268,-91.1562 43.142945,-91.160449 43.140575,-91.163576 43.139444,-91.165016 43.138924,-91.169557 43.137618,-91.170372 43.137384,-91.173873 43.135434,-91.175253 43.134665,-91.175394 43.134438,-91.177003 43.131846,-91.177932 43.128875,-91.178251 43.124982,-91.178081 43.124043,-91.178034 43.122379,-91.178018 43.122185,-91.177873 43.120459,-91.177728 43.118733,-91.17772 43.118627,-91.177477 43.117301,-91.177363 43.116498,-91.177243 43.115654,-91.176688 43.111926,-91.176067 43.108326,-91.17567 43.106776,-91.175382 43.10535,-91.175305 43.104968,-91.175193 43.103771,-91.175311 43.102333,-91.175331 43.101248,-91.175471 43.099745,-91.175634 43.09882,-91.175751 43.097181,-91.176021 43.094938,-91.176269 43.092447,-91.17631 43.090743,-91.176307 43.089377,-91.176449 43.088201,-91.176523 43.087584,-91.176889 43.08559,-91.176914 43.085455,-91.177117 43.082026,-91.177169 43.081136,-91.177222 43.080247,-91.177242 43.07893,-91.177236 43.075956,-91.177264 43.072983,-91.177427 43.07242,-91.177832 43.071961,-91.178761 43.070578,-91.179132 43.069637,-91.179382 43.068624,-91.179457 43.067427,-91.17929 43.06643,-91.178982 43.065925,-91.178202 43.064841,-91.177894 43.064206,-91.178087 43.062044,-91.177177 43.055705,-91.176744 43.052523,-91.176608 43.051502,-91.176388 43.04984,-91.176124 43.048107,-91.175658 43.045027,-91.175525 43.044008,-91.175525 43.044004,-91.175395 43.043028,-91.175167 43.041267,-91.174943 43.040063,-91.174692 43.038713,-91.174408 43.038001,-91.174255 43.037619,-91.174208 43.0375,-91.173504 43.035966,-91.172656 43.033468,-91.171873 43.03076,-91.171365 43.028366,-91.170923 43.026052,-91.170718 43.025414,-91.170182 43.023768,-91.169572 43.022153,-91.168722 43.02023,-91.168171 43.019206,-91.167886 43.018679,-91.166997 43.0168,-91.166796 43.016289,-91.166781 43.016251,-91.166726 43.01611,-91.16667 43.01597,-91.166007 43.014286,-91.165202 43.012156,-91.165039 43.011723,-91.164456 43.009988,-91.163246 43.006478,-91.163192 43.006334,-91.162155 43.003561,-91.161118 43.000788,-91.159364 42.996674,-91.158283 42.993675,-91.15749 42.991475,-91.156812 42.98817,-91.156743 42.98783,-91.1568 42.985939,-91.156803 42.985843,-91.156805 42.985775,-91.156806 42.985755,-91.156807 42.985712,-91.156818 42.985346,-91.156829 42.984975,-91.156869 42.983657,-91.156875 42.98282,-91.156892 42.980896,-91.156893 42.980815,-91.156562 42.978226,-91.155519 42.975774,-91.155248 42.975456,-91.153416 42.973303,-91.150906 42.970514,-91.149885 42.969036,-91.149303 42.968192,-91.149122 42.967909,-91.148001 42.966155,-91.147122 42.964454,-91.14655 42.963345,-91.146395 42.962696,-91.146198 42.961871,-91.146003 42.961053,-91.145935 42.96077,-91.14543 42.958211,-91.145488 42.957299,-91.145534 42.956601,-91.14554 42.95651,-91.146528 42.953519,-91.147822 42.949961,-91.14909 42.946554,-91.149555 42.944499,-91.149701 42.943855,-91.14988 42.941955,-91.149784 42.940244,-91.148448 42.937162,-91.147955 42.936134,-91.146939 42.934015,-91.145743 42.930957,-91.145517 42.930378,-91.145165 42.929271,-91.144315 42.926592,-91.144305 42.926517,-91.14425 42.926126,-91.144196 42.925736,-91.144136 42.925301,-91.144075 42.924863,-91.1438 42.922877,-91.143878 42.920646,-91.144287 42.919265,-91.144627 42.918117,-91.144656 42.918043,-91.144755 42.917792,-91.144855 42.917538,-91.145308 42.916388,-91.145761 42.915239,-91.145868 42.914967,-91.145933 42.914421,-91.146182 42.912338,-91.146181 42.911935,-91.146203 42.909868,-91.145615 42.908006,-91.144794 42.905997,-91.143491 42.904698,-91.142221 42.904484,-91.140951 42.904271,-91.140524 42.904199,-91.139427 42.904015,-91.138308 42.903827,-91.138011 42.903777,-91.137699 42.90365,-91.137386 42.903523,-91.132928 42.901713,-91.132802 42.901662,-91.132682 42.901613,-91.132563 42.901565,-91.132281 42.90145,-91.131436 42.901107,-91.131155 42.900993,-91.130937 42.9009,-91.130368 42.900658,-91.130281 42.900631,-91.130055 42.900562,-91.129069 42.900259,-91.126114 42.899352,-91.125129 42.89905,-91.123156 42.898277,-91.120369 42.897187,-91.1184 42.896288,-91.118218 42.896205,-91.118035 42.896122,-91.117741 42.895988,-91.117628 42.895936,-91.117521 42.895887,-91.117411 42.895837,-91.117317 42.895779,-91.117305 42.895772,-91.117186 42.895699,-91.11707 42.895628,-91.116816 42.895472,-91.116164 42.895072,-91.115512 42.894672,-91.114871 42.893939,-91.11462 42.893652,-91.114531 42.893562,-91.114206 42.893231,-91.113182 42.89219,-91.11284 42.891842,-91.112158 42.891149,-91.11115 42.890435,-91.108129 42.888294,-91.107123 42.887581,-91.107111 42.887575,-91.107078 42.887557,-91.107067 42.887551,-91.105659 42.886813,-91.104051 42.885971,-91.101825 42.884033,-91.101038 42.883348,-91.100565 42.883078,-91.100471 42.882833,-91.100374 42.882581,-91.099472 42.88023,-91.09882 42.878528,-91.098238 42.875798,-91.098554 42.872588,-91.098676 42.871345,-91.098681 42.871286,-91.098796 42.870129,-91.09882 42.869881,-91.09882 42.868244,-91.09882 42.866868,-91.09882 42.865627,-91.09882 42.865492,-91.09882 42.864421,-91.097656 42.859871,-91.096768 42.858136,-91.096057 42.856746,-91.095817 42.856276,-91.095577 42.855807,-91.095329 42.85532,-91.094571 42.854431,-91.094162 42.853951,-91.09388 42.853621,-91.093591 42.853282,-91.093493 42.853167,-91.093389 42.853045,-91.092848 42.852411,-91.092307 42.851777,-91.091837 42.851225,-91.091613 42.85052,-91.091555 42.85034,-91.091402 42.84986,-91.091586 42.848664,-91.091885 42.847566,-91.091887 42.847558,-91.092183 42.846475,-91.092628 42.844844,-91.092792 42.844242,-91.092985 42.843536,-91.093094 42.843137,-91.093939 42.840042,-91.093939 42.840039,-91.094511 42.837571,-91.094942 42.835708,-91.095114 42.834966,-91.094855 42.833944,-91.094718 42.833404,-91.09463 42.833059,-91.094542 42.832711,-91.094383 42.832086,-91.094224 42.83146,-91.094085 42.830911,-91.09406 42.830813,-91.093934 42.830754,-91.091064 42.829411,-91.091023 42.829403,-91.090807 42.829362,-91.090159 42.829241,-91.090136 42.829237,-91.089942 42.829263,\ +-91.08958 42.829314,-91.089219 42.829362,-91.088818 42.829417,-91.088752 42.829425,-91.087377 42.829614,-91.085718 42.829908,-91.085189 42.829928,-91.084158 42.829969,-91.08399 42.82997,-91.083466 42.829973,-91.08316 42.829975,-91.082947 42.829976,-91.082827 42.829977,-91.08277 42.829977,-91.082525 42.829888,-91.082205 42.829772,-91.082032 42.829602,-91.082009 42.82958,-91.081706 42.829283,-91.081619 42.829236,-91.081357 42.829093,-91.081352 42.829091,-91.081056 42.828931,-91.08077 42.828776,-91.08057 42.828668,-91.080549 42.828657,-91.080309 42.828527,-91.07998 42.828357,-91.078993 42.827847,-91.078665 42.827678,-91.078678 42.826563,-91.078757 42.826184,-91.078959 42.825222,-91.078969 42.825156,-91.07932 42.822977,-91.079314 42.820309,-91.079155 42.817534,-91.079153 42.817497,-91.078894 42.814996,-91.0787 42.813115,-91.078605 42.811441,-91.078587 42.811132,-91.078586 42.811109,-91.078569 42.809626,-91.078377 42.808668,-91.078233 42.807976,-91.078097 42.806526,-91.077643 42.803798,-91.076742 42.800525,-91.07656 42.799723,-91.076171 42.798007,-91.075616 42.795965,-91.075481 42.795466,-91.075443 42.795391,-91.074636 42.793784,-91.074367 42.793248,-91.074337 42.793187,-91.074219 42.792698,-91.073947 42.791562,-91.073867 42.791024,-91.073785 42.79046,-91.07327 42.789191,-91.072447 42.787732,-91.072159 42.786849,-91.072008 42.786382,-91.071938 42.785568,-91.071948 42.784899,-91.071972 42.784455,-91.071911 42.784085,-91.071553 42.783588,-91.071138 42.783004,-91.07083 42.782225,-91.070864 42.781168,-91.07091 42.780096,-91.070724 42.778283,-91.070614 42.777083,-91.070716 42.775502,-91.070725 42.775288,-91.070747 42.774817,-91.070303 42.773829,-91.070202 42.773341,-91.07016 42.773137,-91.069913 42.771575,-91.069879 42.77145,-91.06955 42.770224,-91.069549 42.769628,-91.069369 42.769283,-91.069282 42.769199,-91.068359 42.768309,-91.066477 42.766872,-91.065938 42.766461,-91.064571 42.765063,-91.063254 42.763947,-91.062305 42.763335,-91.061361 42.762973,-91.060676 42.762447,-91.060261 42.761847,-91.060129 42.759986,-91.060471 42.759174,-91.06062 42.75882,-91.061432 42.757974,-91.062271 42.757509,-91.062306 42.75749,-91.06312 42.757273,-91.063894 42.757282,-91.064493 42.757276,-91.064896 42.757272,-91.065265 42.757212,-91.065492 42.757081,-91.065606 42.756766,-91.065577 42.755677,-91.065574 42.755564,-91.06557 42.755396,-91.065789 42.754297,-91.065814 42.753895,-91.065783 42.753387,-91.065422 42.752133,-91.065059 42.751338,-91.064711 42.750949,-91.06468 42.750914,-91.063781 42.750568,-91.06286 42.750541,-91.062309 42.750526,-91.061324 42.750566,-91.061013 42.750578,-91.060172 42.750481,-91.060113 42.750447,-91.059314 42.749991,-91.059402 42.749991,-91.059487 42.749991,-91.059491 42.749991,-91.059835 42.749991,-91.060006 42.749991,-91.060179 42.749991,-91.059509 42.749751,-91.059333 42.749689,-91.058486 42.749387,-91.058091 42.749246,-91.057787 42.748924,-91.05766 42.748789,-91.05748 42.748598,-91.057173 42.748272,-91.056297 42.747341,-91.055867 42.746777,-91.05577 42.74665,-91.055674 42.746523,-91.055127 42.745806,-91.054967 42.745241,-91.05481 42.744686,-91.054834 42.743494,-91.054812 42.742335,-91.054798 42.741628,-91.054797 42.74152,-91.054801 42.740641,-91.054801 42.740529,-91.054467 42.739288,-91.053733 42.738238,-91.052226 42.737396,-91.051275 42.737001,-91.050203 42.736922,-91.049972 42.736905,-91.048 42.736912,-91.046953 42.737099,-91.046571 42.737167,-91.046279 42.737402,-91.046028 42.737604,-91.045625 42.737866,-91.044881 42.738349,-91.044455 42.738496,-91.044139 42.738605,-91.042199 42.738605,-91.040529 42.73853,-91.039383 42.738478,-91.037464 42.738068,-91.036556 42.737745,-91.035986 42.737542,-91.035418 42.73734,-91.03539 42.737322,-91.033389 42.736025,-91.032013 42.734484,-91.030984 42.73255,-91.030956 42.7324,-91.030724 42.731158,-91.030718 42.729684,-91.030638 42.729476,-91.030225 42.72839,-91.029692 42.726774,-91.028713 42.725614,-91.026786 42.724228,-91.024852 42.723439,-91.024821 42.723421,-91.022243 42.721968,-91.021755 42.721693,-91.021394 42.721523,-91.018793 42.720297,-91.017926 42.719889,-91.017393 42.719638,-91.017239 42.719566,-91.016949 42.719503,-91.015687 42.719229,-91.015682 42.719229,-91.015101 42.71932,-91.014085 42.719479,-91.011829 42.71997,-91.010668 42.720246,-91.010572 42.720269,-91.009577 42.720123,-91.007663 42.71939,-91.004267 42.717983,-91.001898 42.716956,-91.000128 42.716189,-90.995536 42.713704,-90.994158 42.712788,-90.992233 42.711508,-90.992096 42.711398,-90.990865 42.710407,-90.989634 42.709415,-90.988776 42.708724,-90.987497 42.7073,-90.985241 42.704787,-90.983478 42.702225,-90.98254 42.701159,-90.980578 42.698932,-90.977735 42.696816,-90.977153 42.69648,-90.976314 42.695996,-90.974237 42.695249,-90.971779 42.694834,-90.968927 42.694331,-90.967659 42.693972,-90.965048 42.693233,-90.96147 42.691587,-90.959824 42.690831,-90.956273 42.688985,-90.955617 42.688644,-90.954448 42.687963,-90.954338 42.687899,-90.952415 42.686778,-90.95034 42.685997,-90.949303 42.685607,-90.94921 42.685569,-90.949083 42.685541,-90.948999 42.685521,-90.94882 42.685481,-90.947268 42.685088,-90.945919 42.684751,-90.945521 42.684652,-90.941567 42.683844,-90.938364 42.683529,-90.937045 42.683399,-90.936927 42.683407,-90.932858 42.683709,-90.932614 42.683727,-90.932371 42.683745,-90.929881 42.684128,-90.92797 42.684534,-90.926307 42.684969,-90.925895 42.685077,-90.923634 42.6855,-90.921155 42.685406,-90.91719 42.684355,-90.916847 42.684228,-90.9134 42.682949,-90.909774 42.681179,-90.908745 42.680587,-90.906147 42.679094,-90.906019 42.679023,-90.904685 42.678286,-90.903351 42.677549,-90.903011 42.677406,-90.901991 42.676979,-90.901652 42.676837,-90.900726 42.676448,-90.900261 42.676254,-90.899073 42.67583,-90.897899 42.675411,-90.897127 42.675137,-90.896951 42.675082,-90.896548 42.674958,-90.896145 42.674833,-90.893847 42.674123,-90.892682 42.673763,-90.88743 42.67247,-90.884379 42.671835,-90.883106 42.671571,-90.881197 42.671184,-90.878237 42.670584,-90.875127 42.66994,-90.873968 42.669803,-90.872863 42.669638,-90.871255 42.669397,-90.870547 42.669282,-90.869962 42.669188,-90.867125 42.668728,-90.864527 42.668146,-90.862493 42.66769,-90.857392 42.666304,-90.852497 42.664822,-90.848727 42.664111,-90.845806 42.66348,-90.84391 42.663071,-90.840163 42.662562,-90.838867 42.662358,-90.837723 42.662179,-90.832702 42.661662,-90.828921 42.660975,-90.828174 42.660771,-90.825957 42.660166,-90.823655 42.659926,-90.821742 42.659728,-90.817679 42.65892,-90.813398 42.658177,-90.810015 42.657634,-90.808561 42.657401,-90.807193 42.65721,-90.806322 42.657088,-90.805452 42.656967,-90.803301 42.656667,-90.80054 42.656273,-90.797017 42.655772,-90.79291 42.655003,-90.788226 42.653888,-90.785914 42.653604,-90.785214 42.653518,-90.781996 42.653123,-90.780985 42.653073,-90.780662 42.653057,-90.779695 42.65301,-90.779373 42.652995,-90.778752 42.652965,-90.775977 42.652483,-90.773818 42.652108,-90.769495 42.651443,-90.765848 42.65058,-90.765018 42.650384,-90.762523 42.649709,-90.760389 42.649131,-90.759365 42.648722,-90.758268 42.648284,-90.757171 42.647846,-90.756946 42.647756,-90.750126 42.647057,-90.748941 42.646782,-90.743677 42.64556,-90.738126 42.644659,-90.73454 42.644103,-90.731132 42.643437,-90.730598 42.64331,-90.72607 42.642237,-90.720209 42.640758,-90.717821 42.639914,-90.715133 42.638904,-90.711749 42.637472,-90.709204 42.636078,-90.708692 42.635741,-90.706303 42.634169,-90.704059 42.63219,-90.702671 42.630756,-90.70183 42.629131,-90.701649 42.62878,-90.700856 42.626445,-90.700757 42.624997,-90.700095 42.622461,-90.699242 42.621336,-90.698978 42.620988,-90.697439 42.618957,-90.693999 42.614509,-90.692568 42.611496,-90.692501 42.611356,-90.692031 42.610366,-90.691061 42.607595,-90.689215 42.602318,-90.688687 42.600964,-90.687999 42.599198,-90.687743 42.59611,-90.687775 42.594606,-90.686975 42.591774,-90.686735 42.591426,-90.685487 42.589614,-90.681967 42.584955,-90.680447 42.582943,-90.680025 42.582376,-90.679375 42.581503,-90.677935 42.580031,-90.677055 42.579215,-90.675752 42.578329,-90.675289 42.578014,-90.674827 42.577699,-90.674319 42.577433,-90.673811 42.577167,-90.672727 42.576599,-90.671991 42.576033,-90.671287 42.575493,-90.666407 42.571746,-90.664143 42.570007,-90.661527 42.567999,-90.661506 42.567912,-90.661444 42.567652,-90.661424 42.567566,-90.660964 42.565632,-90.66041 42.563296,-90.659586 42.559833,-90.659395 42.559027,-90.659261 42.558464,-90.659127 42.5579,-90.657036 42.554554,-90.654127 42.5499,-90.648885 42.546323,-90.645627 42.5441,-90.645287 42.54336,-90.644267 42.54114,-90.643927 42.540401,-90.643822 42.539862,-90.64367 42.539074,-90.64351 42.538248,-90.643413 42.537746,-90.643406 42.53771,-90.64339 42.537628,-90.643342 42.537382,-90.643327 42.537301,-90.643109 42.536528,-90.642457 42.534209,-90.64224 42.533436,-90.641797 42.531862,-90.640627 42.527701,-90.640395 42.527167,-90.639746 42.525668,-90.639014 42.52398,-90.63682 42.518917,-90.636727 42.518702,-90.636785 42.517099,-90.636792 42.516891,-90.636815 42.516267,-90.636823 42.51606,-90.636825 42.515992,-90.636832 42.515791,-90.636835 42.515724,-90.636836 42.515692,-90.636839 42.515595,-90.636841 42.515564,-90.636842 42.515539,-90.636845 42.515468,-90.636845 42.51546,-90.636852 42.515258,-90.636856 42.515151,-90.63686 42.515048,-90.63686 42.515029,-90.636862 42.514975,-90.636863 42.514958,-90.636864 42.514913,-90.636869 42.514777,-90.636871 42.514733,-90.636872 42.514706,-90.636874 42.514627,-90.636876 42.514601,-90.636877 42.514571,-90.636879 42.514483,-90.636881 42.514454,-90.636882 42.514423,-90.636885 42.514329,-90.636887 42.514299,-90.636889 42.51424,-90.636895 42.514063,-90.636898 42.514005,-90.636927 42.513202,-90.63732 42.512719,-90.640025 42.509406,-90.640927 42.508302,-90.641273 42.507843,-90.642312 42.506465,-90.642659 42.506007,-90.643854 42.504422,-90.647439 42.499668,-90.648635 42.498084,-90.650092 42.496808,-90.654466 42.492982,-90.655924 42.491708,-90.656044 42.491207,-90.656406 42.489703,-90.656527 42.489203,-90.656376 42.484997,-90.656327 42.483603,-90.654027 42.478503,-90.649848 42.474725,-90.646727 42.471904,-90.645586 42.471184,-90.642166 42.469024,-90.641027 42.468304,-90.630376 42.462308,-90.624328 42.458904,-90.606328 42.451505,-90.604016 42.450805,-90.597488 42.449477,-90.596344 42.449156,-90.590416 42.447493,-90.584736 42.445365,-90.582128 42.444437,-90.580444 42.444064,-90.575104 42.442885,-90.570736 42.441701,-90.5696 42.441205,-90.567968 42.440389,-90.56761 42.440172,-90.565248 42.438742,-90.564036 42.437506,-90.564059 42.437404,-90.563821 42.43715,-90.562896 42.436161,-90.562588 42.435832,-90.561841 42.435034,-90.561048 42.433881,-90.560439 42.432897,-90.56017 42.43224,-90.560027 42.431889,-90.559451 42.430695,-90.558985 42.429445,-90.558801 42.428517,-90.558542 42.426912,-90.558326 42.425404,-90.558252 42.423718,-90.558298 42.422024,-90.558168 42.420984,-90.557876 42.420097,-90.557667 42.41956,-90.55755 42.419258,-90.55667 42.418114,-90.556149 42.417283,-90.555464 42.416614,-90.555018 42.416138,-90.554974 42.416115,-90.554845 42.416047,-90.554802 42.416025,-90.55281 42.414985,-90.549465 42.413665,-90.548068 42.413115,-90.541954 42.411374,-90.535863 42.409205,-90.532824 42.408191,-90.527282 42.406342,-90.525401 42.40568,-90.522725 42.404801,-90.522232 42.404621,-90.520094 42.403842,-90.517516 42.403019,-90.513504 42.401293,-90.510264 42.400155,-90.507269 42.398966,-90.506829 42.398792,-90.505057 42.397904,-90.502894 42.396984,-90.502419 42.396751,-90.50196 42.396508,-90.500586 42.395781,-90.500128 42.395539,-90.49912 42.394785,-90.497785 42.393822,-90.496809 42.392949,-90.496157 42.392689,-90.495766 42.392406,-90.4952 42.391824,-90.495191 42.391815,-90.494498 42.391015,-90.493642 42.389996,-90.492514 42.388985,-90.491332 42.387894,-90.490334 42.387093,-90.489064 42.386388,-90.488217 42.385814,-90.487154 42.385141,-90.486404 42.384906,-90.485698 42.384662,-90.484621 42.38453,-90.482934 42.384623,-90.481475 42.384684,-90.480148 42.384616,-90.479174 42.384416,-90.479072 42.384396,-90.477941 42.384151,-90.477279 42.383794,-90.475814 42.382839,-90.474891 42.38228,-90.474121 42.381729,-90.473798 42.381458,-90.472446 42.380325,-90.472007 42.379957,-90.471085 42.379116,-90.470273 42.378355,-90.469007 42.376666,-90.468869 42.376464,-90.467875 42.375009,-90.46785 42.374983,-90.467775 42.374906,-90.46775 42.374881,-90.46753 42.374438,-90.466465 42.372475,-90.465879 42.371147,-90.465476 42.370546,-90.464788 42.369452,-90.464053 42.36859,-90.462619 42.367253,-90.461818 42.366688,-90.46103 42.366133,-90.458808 42.364507,-90.456298 42.362548,-90.454646 42.360652,-90.452724 42.359303,-90.450836 42.358527,-90.448685 42.357886,-90.44632 42.357041,-90.445167 42.356441,-90.443874 42.355218,-90.44248 42.353511,-90.441401 42.351774,-90.440264 42.349683,-90.438951 42.34771,-90.438841 42.347585,-90.437726 42.346319,-90.435649 42.343887,-90.433949 42.341701,-90.432562 42.339622,-90.432494 42.33953,-90.432475 42.339504,-90.43242 42.339429,-90.432402 42.339404,-90.431832 42.338623,-90.430546 42.33686,-90.430004 42.336395,-90.429272 42.335766,-90.427989 42.334665,-90.427164 42.334021,-90.425363 42.332615,-90.422758 42.331292,-90.42135 42.330472,-90.420303 42.329586,-90.419027 42.328505,-90.418398 42.327648,-90.416535 42.325109,-90.41616 42.3236,-90.415937 42.322699,-90.4162 42.321314,-90.416626 42.320576,-90.417125 42.319943,-90.417974 42.319234,-90.419017 42.318574,-90.420075 42.317681,-90.420753 42.3168,-90.421047 42.316109,-90.421182 42.315255,-90.420861 42.313759,-90.420303 42.311703,-90.4203 42.31169,-90.419425 42.310189,-90.419008 42.309217,-90.418915 42.308328,-90.419354 42.307429,-90.419878 42.306603,-90.420454 42.305374,-90.420608 42.304976,-90.420954 42.304088,-90.421676 42.302506,-90.42216 42.300937,-90.422675 42.298804,-90.423224 42.297156,-90.423231 42.297138,-90.423744 42.295975,-90.424257 42.294576,-90.42427 42.294326,-90.424312 42.293575,-90.424326 42.293326,-90.425482 42.292298,-90.42556 42.292211,-90.426909 42.290719,-90.42826 42.288064,-90.428426 42.28774,-90.429069 42.286611,-90.429332 42.286147,-90.430124 42.284757,-90.430218 42.284593,-90.430494 42.284388,-90.430735 42.284211,-90.431039 42.282452,-90.431027 42.281399,-90.431012 42.279965,-90.430884 42.27823,-90.429313 42.275588,-90.428204 42.273405,-90.427825 42.272798,-90.426846 42.271228,-90.426156 42.270143,-90.426056 42.269968,-90.425757 42.269443,-90.425658 42.269269,-90.425648 42.269253,-90.425621 42.269205,-90.425612 42.269189,-90.424884 42.267912,-90.424708 42.267566,-90.424098 42.266364,-90.423242 42.264411,-90.422889 42.262883,-90.422836 42.26236,-90.422732 42.261316,-90.422439 42.260563,-90.422181 42.259899,-90.421842 42.259175,-90.421659 42.258781,-90.42098 42.257282,-90.420191 42.255846,-90.419757 42.255155,-90.419326 42.254467,-90.418877 42.253932,-90.418274 42.253215,-90.418257 42.253202,-90.416315 42.251679,-90.415974 42.25146,-90.415169 42.250942,-90.414868 42.250748,-90.414481 42.2505,-90.413951 42.250194,-90.413642 42.250016,-90.412441 42.249098,-90.411397 42.248383,-90.410471 42.247749,-90.408038 42.245681,-90.405023 42.24303,-90.403648 42.241821,-90.402926 42.241211,-90.401872 42.240321,-90.400653 42.239293,-90.399097 42.237283,-90.398253 42.236193,-90.397314 42.23498,-90.395883 42.233133,-90.395276 42.230953,-90.394865 42.229476,-90.394749 42.229059,-90.394654 42.228947,-90.393526 42.227626,-90.393151 42.227186,-90.391177 42.225531,-90.391108 42.225473,-90.386781 42.222967,-90.384655 42.221408,-90.382579 42.219886,-90.382251 42.219646,-90.381094 42.218863,-90.377656 42.216535,-90.376618 42.215827,-90.375129 42.214811,-90.374714 42.214587,-90.372097 42.21354,-90.370925 42.213072,-90.365138 42.210526,-90.363212 42.209299,-90.360626 42.207653,-90.360435 42.207538,-90.356964 42.205445,-90.35177 42.204667,-90.349162 42.204277,-90.338169 42.203321,-90.332177 42.202232,-90.328273 42.201047,-90.324927 42.198727,-90.319838 42.195199,-90.317774 42.193789,-90.317202 42.193572,-90.315141 42.192792,-90.314465 42.192537,-90.308776 42.191032,-90.306647 42.19047,-90.306531 42.190439,-90.304516 42.189725,-90.298442 42.187576,-90.298151 42.187424,-90.296151 42.18638,-90.293905 42.185208,-90.285429 42.180612,-90.282173 42.178846,-90.276623 42.176996,-90.26908 42.1745,-90.262345 42.173451,-90.255456 42.171821,-90.250338 42.171482,-90.250129 42.171469,-90.249216 42.171056,-90.248007 42.170672,-90.244913 42.169362,-90.242132 42.168279,-90.239017 42.167048,-90.234919 42.165431,-90.231127 42.163425,-90.22881 42.162281,-90.224244 42.160028,-90.216107 42.15673,-90.213136 42.15505,-90.211328 42.15401,-90.209479 42.15268,-90.20826 42.15099,-90.207421 42.149109,-90.206369 42.1455,-90.205703 42.141267,-90.20536 42.139079,-90.204614 42.137722,-90.204145 42.136995,-90.203071 42.134733,-90.201404 42.130937,-90.200128 42.130066,-90.197342 42.128163,-90.196171 42.127702,-90.195704 42.127519,-90.194717 42.127185,-90.193865 42.126896,-90.192472 42.126425,-90.191302 42.126058,-90.190467 42.125796,-90.190452 42.125779,-90.187474 42.125423,-90.186456 42.12534,-90.18471 42.125198,-90.174402 42.125198,-90.17097 42.125198,-90.17041 42.125021,-90.169784 42.124518,-90.169641 42.124397,-90.168853 42.123733,-90.167533 42.122475,-90.167459 42.122387,-90.166821 42.121631,-90.166632 42.121406,-90.166104 42.12078,-90.166067 42.120731,-90.165893 42.120496,-90.165444 42.119893,-90.164327 42.11867,-90.163992 42.118303,-90.162895 42.116718,-90.162225 42.11488,-90.161884 42.11378,-90.161622 42.112109,-90.161602 42.111979,-90.161352 42.11017,-90.161326 42.109721,-90.161268 42.10867,-90.161266 42.108647,-90.16116 42.106372,-90.16114 42.105424,-90.16112 42.104404,-90.161125 42.10435,-90.16113 42.104298,-90.161311 42.102674,-90.161325 42.102375,-90.161412 42.100628,-90.161504 42.098912,-90.161779 42.096836,-90.161874 42.096456,-90.162088 42.095607,-90.162326 42.094524,-90.162401 42.094185,-90.162404 42.094126,-90.162487 42.092913,-90.162515 42.092509,-90.162516 42.09248,-90.162521 42.092394,-90.162524 42.092366,-90.162559 42.091838,-90.162615 42.090995,-90.162782 42.090273,-90.162902 42.089758,-90.163034 42.089222,-90.163188 42.088601,-90.163405 42.087613,-90.163406 42.087609,-90.163581 42.087086,-90.163718 42.086674,-90.16414 42.085664,-90.164482 42.08487,-90.164499 42.084826,-90.164814 42.084036,-90.16532 42.082689,-90.16573 42.081751,-90.166512 42.080568,-90.166925 42.079477,-90.167556 42.078316,-90.16759 42.078221,-90.168074 42.076888,-90.168327 42.075898,-90.168358 42.075779,-90.168212 42.074657,-90.168098 42.073524,-90.168054 42.073083,-90.167734 42.071484,-90.16729 42.069705,-90.166874 42.068579,-90.166482 42.067333,-90.166345 42.066583,-90.166204 42.065806,-90.165897 42.064234,-90.165857 42.064028,-90.165668 42.063158,-90.165555 42.062638,-90.165526 42.061518,-90.165765 42.060047,-90.165808 42.059897,-90.166116 42.058842,-90.166251 42.058005,-90.166367 42.057298,-90.166808 42.055933,-90.166825 42.055544,-90.166853 42.05491,-90.166705 42.054758,-90.166495 42.054543,-90.165889 42.05277,-90.165756 42.052368,-90.165294 42.050973,-90.164884 42.048171,-90.164537 42.045007,-90.164528 42.044701,-90.164483 42.043145,-90.164485 42.042105,-90.164337 42.041515,-90.163983 42.040954,-90.163446 42.040407,-90.162807 42.040002,-90.162681 42.039922,-90.161629 42.039248,-90.160073 42.03851,-90.158829 42.037769,-90.156663 42.035412,-90.156169 42.035051,-90.155529 42.034584,-90.154384 42.033305,-90.154221 42.033073,-90.153956 42.032695,-90.153334 42.032066,-90.152488 42.031394,-90.152288 42.031227,-90.151579 42.030633,-90.151279 42.030073,-90.150916 42.02944,-90.150291 42.028046,-90.149733 42.026564,-90.149389 42.025141,-90.149188 42.024058,-90.149182 42.023965,-90.149112 42.022679,-90.148644 42.021488,-90.148603 42.021377,-90.148096 42.020014,-90.147194 42.018479,-90.146639 42.017905,-90.146026 42.017271,-90.145414 42.016747,-90.144683 42.016198,-90.143776 42.014881,-90.143017 42.013227,-90.142306 42.011839,-90.141666 42.010187,-90.141167 42.008931,-90.140966 42.007856,-90.14092 42.007559,-90.140639 42.005733,-90.14027 42.004431,-90.140125 42.003613,-90.140061 42.003252,-90.14012 42.002898,-90.140286 42.001916,-90.140404 42.001015,-90.14044 42.000745,-90.140536 42.000026,-90.140613 41.995999,-90.141249 41.995127,-90.144241 41.991031,-90.146033 41.988139,-90.146399 41.985375,-90.146225 41.981329,-90.146231 41.98132,-90.148599 41.978269,-90.149599 41.977528,-90.150691 41.976718,-90.152107 41.97567,-90.153828 41.974121,-90.153834 41.974116,-90.154381 41.97288,-90.155064 41.971337,-90.156315 41.969113,-90.157597 41.967988,-90.158592 41.967117,-90.160458 41.964016,-90.162141 41.961293,-90.164135 41.956178,-90.164593 41.952001,-90.164939 41.948861,-90.164193 41.946178,-90.163847 41.944934,-90.162905 41.94373,-90.160648 41.940845,-90.156902 41.938181,-90.156785 41.938024,-90.155104 41.935762,-90.154947 41.935551,-90.15431 41.934527,-90.154195 41.934425,-90.152659 41.933058,-90.1516 41.931002,-90.151689 41.930512,-90.15198 41.928917,-90.151988 41.928868,-90.152014 41.928723,-90.152024 41.928675,-90.152138 41.928052,-90.152452 41.924847,-90.153362 41.915593,-90.153374 41.913334,-90.153382 41.912087,-90.153478 41.909483,-90.153507 41.908692,-90.153584 41.906614,-90.153692 41.906342,-90.153986 41.905608,-90.154328 41.90475,-90.155355 41.902179,-90.155698 41.901322,-90.156273 41.899884,-90.156843 41.898459,-90.156871 41.89839,-90.157019 41.898019,-90.157459 41.897239,-90.158321 41.895714,-90.160977 41.891013,-90.163633 41.886312,-90.164029 41.885609,-90.165065 41.883777,-90.166329 41.881799,-90.166629 41.881329,-90.166778 41.881094,-90.166929 41.880859,-90.167865 41.879395,-90.168152 41.879,-90.168645 41.878325,-90.169096 41.877706,-90.169287 41.877446,-90.170041 41.876439,-90.170258 41.875757,-90.170491 41.875028,-90.170566 41.874266,-90.170569 41.874227,-90.17064 41.873505,-90.170763 41.872265,-90.170806 41.871826,-90.170885 41.871025,-90.170903 41.870846,-90.170909 41.870784,-90.170962 41.87031,-90.170984 41.870133,-90.171165 41.869598,-90.17119 41.869527,-90.171577 41.868677,-90.17186 41.868053,-90.171901 41.867964,-90.172101 41.867543,-90.172133 41.867476,-90.172229 41.867275,-90.172261 41.867208,-90.17248 41.866747,-90.172765 41.866149,-90.173019 41.865321,-90.17317 41.864834,-90.173213 41.864693,-90.173237 41.86461,-90.173257 41.864438,-90.173284 41.864024,-90.173329 41.863671,-90.17331 41.863445,-90.173301 41.863341,-90.173225 41.862465,-90.173073 41.861122,-90.173048 41.8609,-90.173046 41.860884,-90.17287 41.858409,-90.173009 41.857393,-90.173918 41.855575,-90.174701 41.854225,-90.174702 41.854225,-90.175057 41.853615,-90.17684 41.85099,-90.178942 41.847898,-90.179184 41.847542,-90.179911 41.846476,-90.180049 41.846276,-90.180169 41.846133,-90.180565 41.845654,-90.181401 41.844647,-90.181583 41.844124,-90.181788 41.843538,-90.181873 41.843293,-90.181901 41.843216,-90.181984 41.842526,-90.182016 41.842269,-90.182028 41.842171,-90.182188 41.841355,-90.182272 41.840932,-90.182593 41.839308,-90.182608 41.839235,-90.18288 41.83867,-90.182955 41.838517,-90.183251 41.837906,-90.183275 41.83783,-90.183283 41.837801,-90.183367 41.83753,-90.183643 41.836631,-90.183736 41.836332,-90.183765 41.83624,-90.18387 41.83517,-90.183883 41.834907,-90.183884 41.834901,-90.183973 41.83307,-90.183693 41.83161,-90.183649 41.831381,-90.183405 41.830679,-90.183187 41.830047,-90.18281 41.829381,-90.182672 41.829138,-90.182535 41.828895,-90.182375 41.828613,-90.182215 41.82833,-90.181889 41.827755,-90.18188 41.827566,-90.181813 41.826077,-90.181904 41.824605,-90.18172 41.822599,-90.181619 41.821603,-90.181599 41.821414,-90.181505 41.82048,-90.181392 41.819368,-90.18122 41.81768,-90.180707 41.812617,-90.180643 41.811979,-90.180772 41.810932,-90.180855 41.810256,-90.180881 41.810019,-90.180954 41.809354,-90.181141 41.808935,-90.181973 41.80707,-90.182866 41.806487,-90.185046 41.805068,-90.187969 41.803163,-90.190437 41.802408,-90.191316 41.802139,-90.192194 41.801871,-90.196165 41.800656,-90.196955 41.800491,-90.202003 41.799439,-90.203131 41.799204,-90.203588 41.799109,-90.206465 41.798294,-90.207573 41.79798,-90.207574 41.79798,-90.207962 41.79787,-90.208115 41.797827,-90.208474 41.797726,-90.208656 41.797673,-90.209087 41.79755,-90.209128 41.797538,-90.209517 41.797426,-90.209659 41.797385,-90.210087 41.797264,-90.21023 41.797224,-90.216889 41.795335,-90.217011 41.795284,-90.219662 41.794199,-90.220354 41.793916,-90.221046 41.793632,-90.221203 41.793568,-90.22136 41.793503,-90.22176 41.793339,-90.222161 41.793175,-90.222263 41.793133,-90.231239 41.788596,-90.236084 41.786146,-90.23681 41.78578,-90.24238 41.782964,-90.243235 41.782531,-90.2458 41.781234,-90.246656 41.780802,-90.246926 41.780666,-90.247197 41.780529,-90.247242 41.780506,-90.248631 41.779805,-90.249014 41.77965,-90.249623 41.779404,-90.249739 41.779357,-90.250087 41.779216,-90.250128 41.7792,-90.25016 41.779185,-90.250202 41.779166,-90.251885 41.778391,-90.256938 41.776069,-90.258622 41.775295,-90.258862 41.775053,-90.259503 41.77464,-90.259846 41.774419,-90.260614 41.773848,-90.260966 41.773588,-90.261623 41.773101,-90.263286 41.772112,-90.264696 41.771475,-90.265838 41.771186,-90.265854 41.771182,-90.267295 41.770867,-90.268279 41.770653,-90.268471 41.770611,-90.268659 41.770569,-90.269694 41.770286,-90.269758 41.770258,-90.271888 41.769561,-90.278478 41.767408,-90.278633 41.767358,-90.280418 41.766162,-90.280451 41.766139,-90.280551 41.766073,-90.280585 41.766051,-90.280596 41.766043,-90.280631 41.766019,-90.280643 41.766012,-90.280893 41.765844,-90.281643 41.765341,-90.281894 41.765174,-90.28191 41.765167,-90.282098 41.765077,-90.282287 41.764987,-90.282329 41.764966,-90.283552 41.764386,-90.283597 41.764302,-90.28383 41.763879,-90.283941 41.76368,-90.284551 41.763082,-90.284845 41.76281,-90.285356 41.762341,-90.286353 41.761562,-90.287697 41.760545,-90.288402 41.760157,-90.289206 41.759715,-90.289732 41.759507,-90.292321 41.757584,-90.294383 41.756054,-90.30016 41.75191,-90.302415 41.750294,-90.302598 41.750162,-90.302782 41.750031,-90.303686 41.749243,-90.304525 41.74861,-90.305275 41.747879,-90.30608 41.747131,-90.30668 41.746595,-90.307244 41.746092,-90.308015 41.745304,-90.308946 41.744403,-90.309074 41.744281,-90.309973 41.74342,-90.310651 41.7426,-90.310878 41.742325,-90.31157 41.741359,-90.312252 41.740402,-90.312609 41.739939,-90.313658 41.737905,-90.31447 41.736485,-90.314647 41.736177,-90.315002 41.735626,-90.315281 41.734994,-90.315549 41.734426,-90.315653 41.734039,-90.315771 41.7336,-90.315907 41.732281,-90.316403 41.729511,-90.316562 41.728928,-90.31613 41.728769,-90.316262 41.728235,-90.316396 41.727701,-90.316581 41.726961,-90.316625 41.726809,-90.317048 41.725355,-90.317243 41.724536,-90.317426 41.723694,-90.317668 41.722689,-90.317538 41.722063,-90.317552 41.721279,-90.317555 41.721079,-90.317566 41.72089,-90.317609 41.720223,-90.317542 41.719563,-90.317474 41.718878,-90.317421 41.718333,-90.316997 41.717294,-90.316543 41.716038,-90.316455 41.715819,-90.316106 41.71495,-90.315491 41.713697,-90.315187 41.713032,-90.314896 41.712396,-90.314278 41.711353,-90.313769 41.710339,-90.31332 41.709494,-90.313166 41.708804,-90.313013 41.708087,-90.312893 41.707528,-90.312817 41.706887,-90.312722 41.70608,-90.312667 41.704581,-90.312783 41.70366,-90.312774 41.702851,-90.31277 41.702426,-90.312861 41.701417,-90.312924 41.700496,-90.312933 41.700431,-90.313052 41.699631,-90.31327 41.698836,-90.313435 41.698082,-90.313824 41.69701,-90.313986 41.696525,-90.314191 41.695913,-90.314687 41.69483,-90.315351 41.693921,-90.315981 41.692989,-90.31639 41.69257,-90.316632 41.692322,-90.317315 41.69167,-90.318346 41.690835,-90.319309 41.690218,-90.319924 41.689721,-90.32037 41.689461,-90.321005 41.689093,-90.322258 41.688486,-90.323234 41.687933,-90.323369 41.687852,-90.323776 41.687611,-90.323912 41.687532,-90.324973 41.686861,-90.325896 41.686292,-90.326957 41.68564,-90.328071 41.685129,-90.328971 41.684717,-90.330222 41.683954,-90.330688 41.68361,-90.331443 41.683054,-90.331875 41.682675,-90.332481 41.682146,-90.333274 41.681322,-90.33353 41.681026,-90.333621 41.68092,-90.333625 41.680917,-90.333857 41.680573,-90.333936 41.680458,-90.333991 41.680376,-90.334525 41.679559,-90.334721 41.678595,-90.334831 41.678056,-90.33509 41.676279,-90.335349 41.674577,-90.335475 41.67325,-90.335502 41.672967,-90.335545 41.672481,-90.335729 41.670432,-90.335915 41.669285,-90.336429 41.666132,-90.336482 41.665847,-90.336696 41.664706,-90.336729 41.664532,-90.337074 41.663492,-90.337185 41.663031,-90.337287 41.662614,-90.337631 41.661494,-90.337653 41.661424,-90.33802 41.660539,-90.338283 41.659963,-90.338508 41.659471,-90.338844 41.658632,-90.339085 41.658163,-90.339332 41.657686,-90.339759 41.656862,-90.339859 41.656599,-90.339933 41.656405,-90.340154 41.655825,-90.340229 41.655632,-90.340404 41.655184,-90.34058 41.654736,-90.341193 41.653169,-90.341743 41.652101,-90.342231 41.650964,-90.342506 41.650056,-90.342872 41.649225,-90.343162 41.648141,-90.343246 41.647797,-90.343452 41.646959,-90.343458 41.646828,-90.343513 41.645692,-90.343498 41.644533,-90.343329 41.643232,-90.34333 41.641075,-90.34333 41.640855,-90.342811 41.638071,-90.342506 41.634317,-90.342277 41.630907,-90.342128 41.629247,-90.342059 41.628472,-90.34199 41.627698,-90.341971 41.627489,-90.342016 41.625124,-90.341999 41.624757,-90.34194 41.623476,-90.341823 41.622677,-90.34165 41.621484,-90.341342 41.619771,-90.341284 41.619447,-90.341131 41.617883,-90.340918 41.616472,-90.340989 41.615096,-90.341008 41.614714,-90.341028 41.614332,-90.341007 41.614115,-90.340986 41.613898,-90.34098 41.61384,-90.340975 41.613782,-90.340971 41.613745,-90.340942 41.613438,-90.340908 41.613095,-90.340826 41.612247,-90.3408 41.611985,-90.340744 41.611399,-90.340728 41.611232,-90.340564 41.609526,-90.340316 41.606936,-90.340078 41.604487,-90.340023 41.60391,-90.339845 41.602038,-90.339827 41.601842,-90.339809 41.601641,-90.339753 41.601058,-90.339723 41.600739,-90.339642 41.599835,-90.339528 41.598633,-90.339652 41.598134,-90.339891 41.597181,-90.340127 41.596236,-90.340835 41.593402,-90.341072 41.592458,-90.341228 41.591835,-90.341383 41.591212,-90.341528 41.590633,-90.342392 41.589208,-90.343228 41.587833,-90.347056 41.586337,-90.351642 41.584544,-90.351928 41.584433,-90.352184 41.584334,-90.354952 41.583267,-90.355189 41.583172,-90.3559 41.58289,-90.356138 41.582797,-90.357647 41.582198,-90.358548 41.581842,-90.360958 41.580888,-90.361966 41.58049,-90.362176 41.580406,-90.362973 41.580091,-90.363329 41.57995,-90.363686 41.579808,-90.364128 41.579633,-90.364566 41.579557,-90.36467 41.579539,-90.365298 41.579428,-90.370998 41.578435,-90.37238 41.578194,-90.372554 41.578163,-90.373762 41.577953,-90.376271 41.577516,-90.377223 41.577349,-90.37878 41.577078,-90.37942 41.576966,-90.380059 41.576855,-90.381329 41.576633,-90.382244 41.57639,-90.382648 41.576283,-90.38367 41.576013,-90.385096 41.575635,-90.387411 41.575021,-90.389726 41.574407,-90.393828 41.57332,-90.394109 41.573245,-90.39793 41.572233,-90.39949 41.571453,-90.399829 41.571283,-90.40417 41.569113,-90.40573 41.568333,-90.407186 41.567716,-90.411554 41.565866,-90.412825 41.565329,-90.412984 41.565206,-90.413546 41.564742,-90.41453 41.563933,-90.415252 41.563377,-90.41583 41.562933,-90.415871 41.562881,-90.415997 41.562727,-90.416039 41.562676,-90.416401 41.56223,-90.416763 41.561783,-90.416871 41.561649,-90.41694 41.561565,-90.417116 41.561347,-90.41883 41.559233,-90.419309 41.558527,-90.420053 41.557435,-90.420641 41.556568,-90.421075 41.555932,-90.422097 41.554429,-90.42223 41.554233,-90.422506 41.554076,-90.423418 41.553561,-90.42364 41.553435,-90.424307 41.553058,-90.42453 41.552933,-90.42457 41.552912,-90.42469 41.552849,-90.424731 41.552829,-90.424814 41.552785,-90.425063 41.552656,-90.425147 41.552613,-90.426023 41.552158,-90.426094 41.552122,-90.42704 41.551632,-90.427231 41.551533,-90.428738 41.550984,-90.429667 41.550647,-90.430582 41.550314,-90.432731 41.549533,-90.433192 41.549095,-90.4339 41.548426,-90.43425 41.548093,-90.435301 41.547097,-90.435652 41.546765,-90.436679 41.545791,-90.436814 41.545664,-90.437975 41.544564,-90.438431 41.544133,-90.439464 41.542898,-90.439608 41.542724,-90.43999 41.542268,-90.440517 41.541638,-90.441119 41.540917,-90.442928 41.538753,-90.443531 41.538033,-90.443871 41.537653,-90.444891 41.536513,-90.445231 41.536133,-90.445438 41.535968,-90.445645 41.535802,-90.446003 41.535514,-90.447731 41.534133,-90.44833 41.533669,-90.449113 41.533064,-90.449474 41.532784,-90.450559 41.531944,-90.450921 41.531665,-90.451132 41.531501,-90.451765 41.531011,-90.451766 41.531011,-90.451978 41.530848,-90.453868 41.529385,-90.456546 41.527314,-90.459541 41.524996,-90.461113 41.52378,-90.461272 41.523656,-90.461432 41.523533,-90.464012 41.522773,-90.471752 41.520492,-90.474332 41.519733,-90.474876 41.51968,-90.475007 41.519667,-90.475421 41.519628,-90.476565 41.519518,-90.477034 41.519472,-90.47771 41.519408,-90.478068 41.519374,-90.478174 41.519363,-90.478425 41.519339,-90.47923 41.519262,-90.479569 41.519229,-90.480034 41.519185,-90.48837 41.518383,-90.489487 41.518277,-90.489933 41.518233,-90.490014 41.518231,-90.491482 41.518203,-90.495283 41.518133,-90.496043 41.518119,-90.496803 41.518105,-90.497476 41.518092,-90.498139 41.51808,-90.499475 41.518055,-90.49955 41.518053,-90.499776 41.518049,-90.499852 41.518048,-90.500307 41.518039,-90.500633 41.518033,-90.50167 41.518101,-90.502125 41.518132,-90.502426 41.518152,-90.503331 41.518212,-90.503633 41.518233,-90.505333 41.518533,-90.505478 41.518551,-90.507634 41.518833,-90.511051 41.519268,-90.512887 41.519502,-90.512909 41.519505,-90.512965 41.519512,-90.513134 41.519533,-90.513189 41.519548,-90.513971 41.519761,-90.516434 41.520433,-90.517157 41.520629,-90.525308 41.52284,-90.528234 41.523633,-90.529065 41.523858,-90.532682 41.524837,-90.532858 41.524885,-90.533035 41.524933,-90.533875 41.525073,-90.536394 41.525493,-90.537235 41.525633,-90.537974 41.525732,-90.540195 41.526032,-90.540935 41.526133,-90.541038 41.526124,-90.541141 41.526115,-90.541855 41.526052,-90.543338 41.525924,-90.544615 41.525812,-90.545535 41.525732,-90.547415 41.525471,-90.553054 41.524691,-90.554935 41.524432,-90.55521 41.52439,-90.555484 41.524348,-90.555592 41.524331,-90.556235 41.524232,-90.55659 41.524019,-90.557387 41.52354,-90.557735 41.523332,-90.557957 41.523197,-90.559574 41.522216,-90.563836 41.519632,-90.564407 41.519243,-90.565972 41.51818,-90.565981 41.518173,-90.566007 41.518155,-90.566017 41.518149,-90.566158 41.518053,-90.566223 41.518008,-90.566238 41.517998,-90.566256 41.517985,-90.566309 41.517949,-90.566328 41.517937,-90.566336 41.517932,-90.566509 41.517855,-90.567054 41.517612,-90.567236 41.517532,-90.568035 41.517285,-90.570434 41.516547,-90.571136 41.516332,-90.571238 41.516321,-90.571505 41.516291,-90.572036 41.516232,-90.572307 41.516204,-90.572575 41.516177,-90.57333 41.516096,-90.573492 41.516079,-90.575596 41.515866,-90.575936 41.515832,-90.576352 41.515787,-90.577591 41.515652,-90.57883 41.515518,-90.579683 41.515425,-90.580536 41.515332,-90.581615 41.515188,-90.582029 41.515133,-90.582767 41.514888,-90.582851 41.51486,-90.582936 41.514832,-90.583632 41.514706,-90.584327 41.514579,-90.584874 41.51448,-90.58542 41.51438,-90.586236 41.514232,-90.586599 41.514125,-90.58848 41.513578,-90.588605 41.513542,-90.58873 41.513505,-90.590079 41.513111,-90.591037 41.512832,-90.591226 41.512738,-90.591448 41.512627,-90.591669 41.512516,-90.591837 41.512432,-90.591868 41.512419,-90.593872 41.511632,-90.594426 41.511415,-90.594541 41.51137,-90.594637 41.511332,-90.595237 41.511032,-90.596115 41.510395,-90.600631 41.507122,-90.601678 41.506365,-90.601908 41.506198,-90.602137 41.506032,-90.602397 41.504892,-90.602681 41.503648,-90.603177 41.501472,-90.603225 41.501263,-90.603331 41.500798,-90.603437 41.500332,-90.603645 41.499471,-90.604237 41.497032,-90.604307 41.496905,-90.604737 41.496132,-90.604935 41.495818,-90.605529 41.494876,-90.605728 41.494563,-90.605936 41.494232,-90.605937 41.494232,-90.606217 41.494026,-90.606533 41.493797,-90.60757 41.49304,-90.608009 41.492719,-90.608606 41.492283,-90.610592 41.490832,-90.61276 41.48925,-90.61655 41.486482,-90.616913 41.486218,-90.617725 41.485625,-90.618537 41.485032,-90.618862 41.484899,-90.619186 41.484766,-90.620737 41.484132,-90.620996 41.484017,-90.621512 41.483791,-90.625968 41.481831,-90.628302 41.480803,-90.630423 41.479871,-90.63058 41.479802,-90.630738 41.479732,-90.630991 41.47959,-90.631373 41.479378,-90.631753 41.479166,-90.632008 41.479025,-90.632032 41.47901,-90.63207 41.47899,-90.632107 41.478968,-90.632133 41.478955,-90.632538 41.478732,-90.638986 41.47421,-90.640238 41.473332,-90.641991 41.471877,-90.643678 41.470477,-90.645364 41.469077,-90.650238 41.465032,-90.655839 41.462132,-90.657919 41.461832,-90.660526 41.461456,-90.664159 41.460932,-90.665213 41.46078,-90.665726 41.460706,-90.666239 41.460632,-90.666779 41.460632,-90.667048 41.460632,-90.668399 41.460632,-90.668939 41.460632,-90.670414 41.460672,-90.67148 41.4607,-90.67189 41.460711,-90.673806 41.460762,-90.675721 41.460813,-90.676439 41.460832,-90.677208 41.460604,-90.678818 41.460126,-90.678997 41.460073,-90.679321 41.459978,-90.681435 41.459352,-90.68211 41.459152,-90.682596 41.459008,-90.682786 41.458952,-90.685013 41.458292,-90.686079 41.457976,-90.68724 41.457632,-90.687826 41.457476,-90.688412 41.45732,-90.690951 41.456643,-90.698254 41.455284,-90.699667 41.45502,-90.701159 41.454743,-90.705475 41.454099,-90.706241 41.453985,-90.707007 41.45387,-90.707858 41.453743,-90.710315 41.453494,-90.711245 41.4534,-90.714802 41.453049,-90.718311 41.452767,-90.720951 41.452416,-90.723275 41.452265,-90.723325 41.452261,-90.723545 41.452248,-90.726093 41.451836,-90.727 41.451733,-90.728382 41.451577,-90.728687 41.451536,-90.730656 41.451272,-90.732639 41.450997,-90.733441 41.450931,-90.735645 41.450448,-90.737302 41.450167,-90.737446 41.450142,-90.737537 41.450127,-90.745946 41.44973,-90.749592 41.449645,-90.749867 41.449638,-90.750142 41.449632,-90.750593 41.449644,-90.75084 41.449652,-90.751538 41.449673,-90.751706 41.449678,-90.751948 41.449692,-90.7524 41.44972,-90.75435 41.44984,-90.758255 41.450098,-90.758375 41.450106,-90.76042 41.450317,-90.762079 41.450405,-90.762252 41.450424,-90.763471 41.450559,-90.76451 41.450583,-90.765484 41.450672,-90.766223 41.450704,-90.767122 41.450737,-90.768021 41.450769,-90.76922 41.450728,-90.770719 41.450745,-90.771672 41.450761,-90.772486 41.450809,-90.773417 41.450882,-90.775591 41.451067,-90.775815 41.451088,-90.777583 41.451261,-90.7783 41.451398,-90.779317 41.451608,-90.780686 41.451839,-90.781147 41.451917,-90.781608 41.451995,-90.78255 41.452132,-90.783107 41.452213,-90.784841 41.452576,-90.78535 41.452692,-90.785819 41.4528,-90.78603 41.452849,-90.786282 41.452888,-90.788203 41.453147,-90.789006 41.453244,-90.789801 41.453341,-90.790516 41.453429,-90.791447 41.453502,-90.793128 41.453631,-90.794497 41.453756,-90.795066 41.453808,-90.795283 41.45382,-90.79725 41.453928,-90.798931 41.454025,-90.800419 41.454096,-90.803319 41.454235,-90.803642 41.45425,-90.803964 41.454266,-90.805634 41.454362,-90.806769 41.454434,-90.807283 41.454466,-90.80845 41.45449,-90.8094 41.454531,-90.809574 41.454538,-90.810035 41.45457,-90.810795 41.45457,-90.810852 41.454569,-90.811319 41.454562,-90.811973 41.454594,-90.813151 41.454505,-90.814211 41.454448,-90.815087 41.454397,-90.815336 41.454383,-90.815416 41.454379,-90.81677 41.454326,-90.817682 41.454342,-90.818055 41.454349,-90.818701 41.454362,-90.819222 41.454373,-90.820689 41.454396,-90.822552 41.454396,-90.823098 41.454411,-90.823751 41.454427,-90.824678 41.454465,-90.824736 41.454467,-90.825636 41.45458,-90.827253 41.454805,-90.82744 41.454843,-90.828516 41.455063,-90.82903 41.455127,-90.830069 41.455183,-90.831215 41.455271,-90.832275 41.455375,-90.832544 41.455398,-90.833817 41.455512,-90.835359 41.455575,-90.836265 41.455596,-90.837414 41.455623,-90.839513 41.455621,-90.840755 41.455621,-90.841547 41.455564,-90.842311 41.455518,-90.843303 41.455458,-90.844749 41.455295,-90.846558 41.455141,-90.846625 41.455132,-90.847458 41.455019,-90.849427 41.454549,-90.849663 41.454515,-90.850157 41.454444,-90.851634 41.454233,-90.85226 41.454132,-90.852886 41.45403,-90.853604 41.453909,-90.853914 41.453804,-90.854942 41.45344,-90.856398 41.453108,-90.857554 41.452751,-90.858485 41.452291,-90.859502 41.451886,-90.860626 41.451393,-90.861079 41.451152,-90.861533 41.450911,-90.862712 41.450285,-90.862754 41.450266,-90.864082 41.449679,-90.865238 41.449073,-90.866501 41.448587,-90.867282 41.448215,-90.86786 41.447884,-90.868352 41.447617,-90.86924 41.447204,-90.869276 41.447176,-90.869818 41.44676,-90.870578 41.446445,-90.871352 41.445981,-90.872129 41.445582,-90.872183 41.445555,-90.87275 41.445256,-90.87352 41.444747,-90.874098 41.444448,-90.874395 41.444269,-90.875145 41.443819,-90.875902 41.443452,-90.876117 41.443349,-90.876958 41.442871,-90.877583 41.442479,-90.877812 41.442336,-90.87808 41.442178,-90.878806 41.441753,-90.878902 41.441697,-90.879778 41.441065,-90.880996 41.440468,-90.881076 41.440421,-90.881395 41.440237,-90.88284 41.439544,-90.883972 41.438946,-90.884759 41.438532,-90.88587 41.437942,-90.887617 41.437115,-90.888138 41.436831,-90.889612 41.43603,-90.890471 41.435593,-90.890676 41.435488,-90.890787 41.435432,-90.89132 41.435242,-90.891537 41.435165,-90.89179 41.435075,-90.892042 41.434985,-90.892143 41.434949,-90.893169 41.434494,-90.893974 41.434138,-90.895623 41.433366,-90.896583 41.432823,-90.897002 41.432645,-90.897959 41.432237,-90.898338 41.432076,-90.899588 41.431552,-90.899764 41.431472,-90.900294 41.431233,-90.900471 41.431154,-90.90116 41.430901,-90.90201 41.4306,-90.902667 41.430313,-90.90315 41.430212,-90.90401 41.430048,-90.905279 41.429685,-90.906804 41.429332,-90.907911 41.429082,-90.908225 41.42902,-90.90935 41.428801,-90.910185 41.428532,-90.91021 41.428524,-90.911263 41.428216,-90.912178 41.427891,-90.91321 41.4276,-90.914662 41.427108,-90.91649 41.426531,-90.917824 41.426047,-90.91829 41.425907,-90.918716 41.425778,-90.919351 41.425589,-90.919803 41.42535,-90.920321 41.425103,-90.920968 41.424679,-90.92181 41.424184,-90.922672 41.423705,-90.923415 41.423411,-90.924343 41.42286,-90.924967 41.42263,-90.925795 41.422337,-90.927041 41.422071,-90.928276 41.421772,-90.929254 41.421544,-90.930016 41.421404,-90.930691 41.421368,-90.930978 41.421396,-90.931386 41.421438,-90.932124 41.421556,-90.932817 41.421827,-90.933802 41.421882,-90.934817 41.422091,-90.936078 41.422423,-90.937061 41.422583,-90.938045 41.422767,-90.938867 41.422927,-90.939668 41.423142,-90.940706 41.42323,-90.941659 41.423341,-90.942482 41.423436,-90.943756 41.423509,-90.944922 41.423638,-90.945452 41.423715,-90.94596 41.42379,-90.946794 41.423933,-90.947682 41.424044,-90.948464 41.424057,-90.949032 41.424053,-90.949791 41.424163,-90.950282 41.42436,-90.951179 41.42456,-90.95214 41.424841,-90.953198 41.425075,-90.95402 41.425339,-90.954735 41.42561,-90.955128 41.425879,-90.955543 41.426124,-90.95629 41.426428,-90.956736 41.426746,-90.957247 41.427016,-90.957833 41.427327,-90.958353 41.42771,-90.958864 41.428029,-90.959461 41.428275,-90.96008 41.428489,-90.960678 41.428695,-90.96131 41.428989,-90.961349 41.429007,-90.962095 41.429327,-90.962533 41.429451,-90.963025 41.42951,-90.963645 41.429652,-90.964167 41.429724,-90.964319 41.429745,-90.965014 41.429847,-90.965838 41.429925,-90.966662 41.430051,-90.967356 41.430225,-90.967879 41.430407,-90.968317 41.430539,-90.968892 41.430809,-90.969554 41.431031,-90.970173 41.431238,-90.970557 41.431394,-90.970995 41.431703,-90.97111 41.431785,-90.971875 41.432404,-90.972255 41.432705,-90.972512 41.432909,-90.972689 41.433022,-90.972701 41.43303,-90.972739 41.433054,-90.972752 41.433062,-90.972834 41.433114,-90.97308 41.433272,-90.973086 41.433276,-90.97317 41.43331,-90.973609 41.43349,-90.974185 41.433712,-90.974858 41.433902,-90.975168 41.433985,-90.975767 41.434029,-90.97673 41.434124,-90.97794 41.434229,-90.978669 41.434264,-90.979815 41.434321,-90.980329 41.434292,-90.981766 41.434221,-90.982913 41.434171,-90.983932 41.434024,-90.984898 41.433869,-90.985886 41.433689,-90.986789 41.433404,-90.987585 41.433054,-90.988436 41.432712,-90.989319 41.432273,-90.989976 41.431962,-90.99088 41.431555,-90.991859 41.431174,-90.992892 41.430752,-90.993969 41.430282,-90.994759 41.430007,-90.995022 41.429917,-90.996205 41.429496,-90.997162 41.429179,-90.998248 41.428854,-90.999257 41.428618,-91.000148 41.428414,-91.000251 41.42838,-91.000354 41.428345,-91.001177 41.428069,-91.003368 41.427337,-91.00422 41.426923,-91.005197 41.426449,-91.005846 41.426135,-91.006024 41.426105,-91.008691 41.425656,-91.009079 41.425591,-91.009445 41.425519,-91.009578 41.425493,-91.01188 41.425043,-91.01198 41.425024,-91.014619 41.424806,-91.018896 41.424514,-91.019426 41.424478,-91.021234 41.424318,-91.023708 41.424099,-91.024136 41.424062,-91.027787 41.423603,-91.030618 41.422464,-91.030886 41.422353,-91.033107 41.421439,-91.033184 41.421409,-91.033492 41.421293,-91.034416 41.420947,-91.034725 41.420832,-91.034991 41.420733,-91.035053 41.420712,-91.036046 41.420379,-91.036378 41.420269,-91.037131 41.420017,-91.03777 41.419669,-91.03824 41.419412,-91.039872 41.418523,-91.041441 41.417471,-91.04158 41.417378,-91.041719 41.417284,-91.042109 41.417023,-91.043497 41.41619,-91.043988 41.415897,-91.045063 41.414872,-91.045464 41.414489,-91.04589 41.414085,-91.046478 41.413183,-91.046581 41.413026,-91.046782 41.412719,-91.047011 41.412368,-91.047034 41.412334,-91.047642 41.41131,-91.047652 41.411286,-91.047818 41.410902,-91.048259 41.409748,-91.048261 41.409736,-91.048628 41.407972,-91.049023 41.406073,-91.049103 41.405692,-91.04931 41.40486,-91.049735 41.403153,-91.049849 41.402579,-91.050067 41.401496,-91.050328 41.400049,-91.050637 41.397327,-91.050749 41.39597,-91.050766 41.395616,-91.050843 41.394104,-91.05087 41.393275,-91.05087 41.392841,-91.050874 41.391539,-91.050875 41.391106,-91.050876 41.390872,-91.050876 41.390639,-91.050876 41.3906,-91.05092 41.389798,-91.050955 41.389187,-91.05101 41.387556,-91.051106 41.386651,-91.051358 41.385921,-91.05158 41.385283,-91.051835 41.384704,-91.051933 41.384482,-91.052512 41.383743,-91.053081 41.383101,-91.053535 41.38259,-91.054651 41.381178,-91.055598 41.380026,-91.056813 41.378928,-91.057129 41.378532,-91.057676 41.377849,-91.05851 41.377036,-91.059607 41.375867,-91.059714 41.375618,-91.059971 41.375028,-91.06109 41.373823,-91.062285 41.372535,-91.063276 41.371309,-91.063714 41.370769,-91.063746 41.370729,-91.064453 41.369851,-91.065058 41.369101,-91.065656 41.367996,-91.066232 41.366367,-91.06652 41.365246,-91.066913 41.363417,-91.066985 41.362941,-91.067105 41.362151,-91.067586 41.359319,-91.068376 41.355852,-91.068962 41.353876,-91.069763 41.350302,-91.070498 41.34627,-91.070642 41.345353,-91.070682 41.345096,-91.070723 41.344838,-91.070766 41.344565,-91.070809 41.344291,-91.070894 41.343749,-91.070979 41.343206,-91.071139 41.34219,-91.071331 41.341016,-91.071555 41.339648,-91.071682 41.338576,-91.071757 41.337615,-91.071853 41.336374,-91.07197 41.334995,-91.072002 41.334116,-91.072045 41.333599,-91.072049 41.333551,-91.072065 41.333408,-91.07207 41.333361,-91.07206 41.33282,-91.072051 41.332279,-91.072038 41.331546,-91.072113 41.32957,-91.07218 41.328537,-91.072316 41.326465,-91.072667 41.322997,-91.072784 41.321417,-91.072871 41.319941,-91.072876 41.319829,-91.073009 41.317094,-91.073147 41.314924,-91.073194 41.314092,-91.073232 41.31344,-91.073553 41.310481,-91.073795 41.309308,-91.073952 41.308543,-91.074149 41.307594,-91.074841 41.305578,-91.075545 41.304433,-91.075992 41.303715,-91.077505 41.301828,-91.079689 41.300079,-91.081553 41.2987,-91.082991 41.29755,-91.084036 41.296717,-91.086379 41.294854,-91.08688 41.294371,-91.087753 41.293129,-91.089212 41.291209,-91.090703 41.289097,-91.092034 41.286911,-91.093503 41.284,-91.094162 41.282605,-91.094652 41.281411,-91.095365 41.279661,-91.096426 41.277251,-91.098035 41.273597,-91.09894 41.271524,-91.100099 41.26904,-91.101142 41.267169,-91.103153 41.264008,-91.104462 41.262104,-91.107325 41.258943,-91.107448 41.258828,-91.109368 41.257056,-91.110304 41.256088,-91.112783 41.252531,-91.113832 41.25066,-91.114186 41.250029,-91.114182 41.249474,-91.114177 41.248814,-91.114172 41.248155,-91.114162 41.24695,-91.114153 41.245744,-91.114137 41.24504,-91.114074 41.244439,-91.114008 41.243808,-91.113934 41.243099,-91.113657 41.241401,-91.113331 41.240617,-91.113062 41.239968,-91.112348 41.239002,-91.111871 41.238573,-91.111836 41.238541,-91.110743 41.237555,-91.110422 41.237281,-91.109582 41.236566,-91.108782 41.235964,-91.107813 41.235434,-91.106215 41.234558,-91.105832 41.234269,-91.104426 41.233272,-91.102997 41.232147,-91.100856 41.230539,-91.100845 41.230531,-91.099993 41.229831,-91.098937 41.228964,-91.097477 41.2275,-91.095675 41.225513,-91.094718 41.224491,-91.09399 41.223712,-91.09303 41.222634,-91.090186 41.220431,-91.087076 41.218222,-91.083497 41.215851,-91.081452 41.214429,-91.079529 41.212776,-91.077767 41.211268,-91.076005 41.209759,-91.075725 41.209518,-91.075445 41.209276,-91.073979 41.20801,-91.072984 41.207151,-91.067832 41.2016,-91.066866 41.200558,-91.065899 41.199517,-91.064819 41.198056,-91.063758 41.19662,-91.061475 41.193756,-91.060343 41.192336,-91.060306 41.19229,-91.060268 41.192243,-91.059847 41.19172,-91.059841 41.191712,-91.059414 41.191181,-91.058362 41.189873,-91.057981 41.1894,-91.057866 41.189259,-91.055068 41.185789,-91.054349 41.184731,-91.05151 41.180553,-91.049815 41.178057,-91.047991 41.175337,-91.046392 41.172954,-91.044756 41.170446,-91.043894 41.169123,-91.041875 41.166484,-91.041557 41.166162,-91.041196 41.166041,-91.038592 41.165551,-91.038408 41.165506,-91.036923 41.165139,-91.036464 41.165009,-91.03588 41.164843,-91.034424 41.164431,-91.032192 41.164004,-91.030045 41.163553,-91.027231 41.163383,-91.025446 41.163464,-91.024086 41.163625,-91.022365 41.164036,-91.020196 41.164646,-91.020021 41.164687,-91.019825 41.164732,-91.01905 41.164912,-91.017839 41.165163,-91.016554 41.165315,-91.015076 41.165516,-91.01445 41.165616,-91.013451 41.165779,-91.01257 41.165924,-91.011463 41.166044,-91.010441 41.166154,-91.009607 41.166245,-91.009435 41.16624,-91.008112 41.166201,-91.007594 41.166187,-91.006486 41.165913,-91.005505 41.165624,-91.004326 41.165108,-91.003475 41.164736,-91.003105 41.164602,-91.001718 41.164084,-91.000813 41.163687,-91.000149 41.163396,-90.997905 41.162562,-90.997054 41.162003,-90.994963 41.16063,-90.994391 41.160119,-90.992551 41.158475,-90.992193 41.158156,-90.990867 41.156872,-90.990612 41.156625,-90.989662 41.155707,-90.987878 41.15327,-90.986069 41.150608,-90.986057 41.15058,-90.985863 41.150174,-90.985526 41.149466,-90.984996 41.148353,-90.984958 41.148274,-90.984657 41.147785,-90.983268 41.145529,-90.983064 41.145229,-90.981597 41.143073,-90.981327 41.142675,-90.979077 41.139775,-90.977542 41.138242,-90.977275 41.137976,-90.977009 41.137711,-90.976429 41.137131,-90.97493 41.13527,-90.97333 41.133285,-90.970856 41.130109,-90.970851 41.130103,-90.970721 41.12988,-90.970384 41.129306,-90.969347 41.127388,-90.968999 41.126382,-90.968851 41.125653,-90.968662 41.125254,-90.968607 41.125138,-90.968552 41.125022,-90.967949 41.123778,-90.967412 41.12267,-90.967346 41.122533,-90.966627 41.121046,-90.965908 41.119559,-90.965218 41.118912,-90.964528 41.118266,-90.963526 41.117327,-90.962708 41.116561,-90.962523 41.116387,-90.961662 41.11558,-90.960802 41.114774,-90.959502 41.11341,-90.958643 41.11251,-90.958201 41.112047,-90.957265 41.111067,-90.954444 41.107426,-90.952739 41.105227,-90.952324 41.104691,-90.950844 41.10274,-90.950453 41.102214,-90.949752 41.101271,-90.949316 41.100611,-90.949178 41.100403,-90.949066 41.100234,-90.948955 41.100065,-90.948815 41.09986,-90.948395 41.099245,-90.948256 41.099041,-90.947722 41.098246,-90.946625 41.096628,-90.946623 41.09662,-90.946444 41.095687,-90.94626 41.09473,-90.946328 41.094448,-90.946731 41.092768,-90.947476 41.090507,-90.9479 41.088106,-90.948027 41.087392,-90.948161 41.085574,-90.948162 41.085563,-90.948178 41.085203,-90.948217 41.084411,-90.948309 41.082693,-90.948397 41.081075,-90.948715 41.078364,-90.948871 41.07737,-90.949064 41.07614,-90.949208 41.075212,-90.949256 41.074909,-90.949322 41.074484,-90.949328 41.074346,-90.94939 41.072783,-90.949381 41.07271,-90.949322 41.072212,-90.949147 41.070721,-90.949135 41.07061,-90.948989 41.07025,-90.947139 41.065713,-90.946024 41.063644,-90.945549 41.06173,-90.945613 41.060336,-90.945933 41.057594,-90.945999 41.056336,-90.944577 41.052255,-90.943652 41.048637,-90.94395 41.045154,-90.943739 41.043101,-90.94297 41.040747,-90.94232 41.038472,-90.942253 41.034702,-90.943232 41.029581,-90.943826 41.026473,-90.943947 41.025842,-90.945324 41.019279,-90.945427 41.015548,-90.945054 41.011917,-90.945949 41.006495,-90.947859 41.001499,-90.948453 41.000036,-90.948763 40.998779,-90.949634 40.995248,-90.951811 40.991407,-90.955201 40.986805,-90.95546 40.986185,-90.958142 40.979767,-90.958089 40.976643,-90.956827 40.973562,-90.955111 40.969858,-90.952715 40.962087,-90.951967 40.958238,-90.952233 40.954047,-90.953542 40.95062,-90.953587 40.950502,-90.953891 40.94991,-90.957215 40.943431,-90.957249 40.94336,-90.959947 40.937736,-90.960462 40.936356,-90.961112 40.933537,-90.961892 40.928473,-90.96256 40.925831,-90.962916 40.924957,-90.965344 40.921633,-90.968995 40.919127,-90.973985 40.917392,-90.97919 40.915522,-90.985462 40.912141,-90.992922 40.909681,-90.995569 40.909136,-90.9985 40.90812,-91.000142 40.90733,-91.001684 40.906385,-91.003536 40.905146,-91.005022 40.904119,-91.007519 40.902092,-91.009536 40.900565,-91.011798 40.898437,-91.011941 40.898302,-91.012135 40.898121,-91.01324 40.896622,-91.013899 40.895619,-91.015008 40.893933,-91.01613 40.892161,-91.0174 40.890224,-91.01758 40.88995,-91.018075 40.889128,-91.019541 40.886675,-91.020013 40.885965,-91.02035 40.885567,-91.02095 40.884834,-91.021562 40.884021,-91.022337 40.883468,-91.023192 40.882775,-91.024518 40.881699,-91.026321 40.880237,-91.026853 40.87964,-91.027489 40.879173,-91.027899 40.87895,-91.028154 40.878812,-91.029075 40.878373,-91.030371 40.877771,-91.031066 40.877466,-91.032459 40.876808,-91.034552 40.876039,-91.036789 40.875038,-91.039097 40.873565,-91.041998 40.871097,-91.044653 40.868356,-91.047344 40.864654,-91.050241 40.858514,-91.051656 40.856038,-91.052778 40.854015,-91.054269 40.851747,-91.05643 40.848387,-91.058749 40.846309,-91.062226 40.844645,-91.067159 40.841997,-91.071451 40.838682,-91.077521 40.833405,-91.078285 40.832838,-91.081307 40.8306,-91.08501 40.828135,-91.090072 40.824638,-91.091569 40.822813,-91.092993 40.821079,-91.093559 40.819862,-91.095367 40.815974,-91.096683 40.812189,-91.096703 40.81213,-91.096713 40.812102,-91.096742 40.812014,-91.096946 40.811403,-91.097456 40.808865,-91.097553 40.808433,-91.097649 40.805575,-91.097319 40.804127,-91.097031 40.802471,-91.095524 40.799348,-91.094728 40.797833,-91.094331 40.797078,-91.093128 40.794841,-91.092256 40.792909,-91.091917 40.791397,-91.091664 40.790265,-91.091607 40.789789,-91.091246 40.786724,-91.091262 40.78306,-91.091332 40.782529,-91.091628 40.780275,-91.091703 40.779708,-91.092476 40.776838,-91.093426 40.774306,-91.093536 40.774016,-91.094773 40.770926,-91.096133 40.767134,-91.098105 40.763233,-91.100485 40.760017,-91.102486 40.757076,-91.105227 40.754107,-91.105473 40.753838,-91.10613 40.753117,-91.107318 40.751981,-91.1082 40.750935,-91.108566 40.750376,-91.108765 40.75004,-91.110424 40.745528,-91.114249 40.730864,-91.115735 40.725168,-91.11569 40.723938,-91.115158 40.721895,-91.114473 40.720624,-91.113885 40.719532,-91.111095 40.708282,-91.110927 40.703262,-91.11194 40.697018,-91.112467 40.696301,-91.113321 40.695142,-91.115407 40.691825,-91.117831 40.683431,-91.119632 40.675892,-91.12082 40.672777,-91.122421 40.670675,-91.123928 40.669152,-91.125144 40.668738,-91.128415 40.666762,-91.133478 40.663678,-91.138055 40.660893,-91.144963 40.657632,-91.154293 40.653596,-91.15912 40.651139,-91.163471 40.648925,-91.1751 40.643027,-91.185295 40.637803,-91.18698 40.637297,-91.192558 40.636508,-91.195505 40.636287,-91.197906 40.636107,-91.202384 40.636594,-91.211352 40.637882,-91.218437 40.638437,-91.2246 40.638473,-91.228188 40.638309,-91.23244 40.638115,-91.247851 40.63839,-91.250148 40.638175,-91.251001 40.63816,-91.251868 40.638113,-91.253074 40.637962,-91.255402 40.63745,-91.256128 40.637273,-91.256788 40.637113,-91.258249 40.636672,-91.259932 40.635981,-91.262528 40.634752,-91.26368 40.634305,-91.263831 40.634246,-91.264326 40.63409,-91.264953 40.633893,-91.26636 40.633588,-91.268116 40.633325,-91.269787 40.633142,-91.271479 40.632951,-91.273139 40.632752,-91.276175 40.63224,-91.279444 40.631503,-91.282205 40.630894,-91.284142 40.63042,-91.28667 40.6299,-91.290351 40.629275,-91.293291 40.628731,-91.295523 40.628347,-91.296037 40.628252,-91.297152 40.628052,-91.297252 40.628034,-91.297394 40.627995,-91.298134 40.627883,-91.300936 40.627372,-91.303486 40.626923,-91.305043 40.626615,-91.306568 40.626219,-91.307937 40.625814,-91.309077 40.625433,-91.310048 40.625041,-91.339719 40.613488,-91.34127 40.612835,-91.348733 40.609695,-91.353989 40.606553,-91.354874 40.605892,-91.357058 40.604263,-91.359873 40.601805,-91.364667 40.595505,-91.364752 40.595395,-91.369661 40.588822,-91.37114 40.586841,-91.374252 40.58259,-91.375152 40.581279,-91.379752 40.57445,-91.381192 40.573234,-91.381964 40.572738,-91.385219 40.570646,-91.388179 40.568608,-91.401482 40.559458,-91.405241 40.554641,-91.406331 40.551934,-91.406373 40.551831,-91.406851 40.547557,-91.406202 40.542698,-91.404125 40.539127,-91.403077 40.538406,-91.400725 40.536789,-91.394475 40.534543,-91.388067 40.533069,-91.384531 40.530948,-91.381857 40.528247,-91.377689 40.523032,-91.375152 40.519858,-91.369059 40.512532,-91.368517 40.511591,-91.367876 40.510479,-91.366846 40.507743,-91.366489 40.506795,-91.364211 40.500043,-91.363879 40.498062,-91.363683 40.494211,-91.363744 40.493124,-91.363748 40.493052,-91.36391 40.490122,-91.364915 40.484168,-91.366463 40.478869,-91.36747 40.476227,-91.368074 40.474642,-91.369572 40.47138,-91.371649 40.468185,-91.373267 40.465495,-91.373855 40.464557,-91.374208 40.463993,-91.375152 40.461983,-91.376695 40.459234,-91.378144 40.456394,-91.379907 40.45211,-91.380037 40.451697,-91.381045 40.44849,-91.381468 40.44604,-91.381769 40.442555,-91.38155 40.438885,-91.381503 40.438092,-91.381457 40.437308,-91.380965 40.435395,-91.380177 40.432904,-91.379635 40.431497,-91.378844 40.429441,-91.377738 40.426622,-91.377625 40.426335,-91.376515 40.424321,-91.375151 40.421656,-91.373721 40.417891,-91.37328 40.416496,-91.372826 40.414279,-91.37245 40.411475,-91.372402 40.408729,-91.372392 40.40726,-91.372379 40.405379,-91.372399 40.4044,-91.372435 40.40277,-91.372554 40.4012,-91.372921 40.399108,-91.372937 40.399065,-91.375151 40.393172,-91.375429 40.392719,-91.375712 40.391925,-91.376397 40.391256,-91.37647 40.391188,-91.376652 40.391018,-91.376736 40.390939,-91.376911 40.390776,-91.377672 40.390213,-91.378422 40.38967,-91.379943 40.388944,-91.381958 40.387632,-91.384201 40.38643,-91.38836 40.384929,-91.390073 40.38446,-91.391613 40.384038,-91.392179 40.383942,-91.396996 40.383127,-91.402392 40.382692,-91.411037 40.382413,-91.413011 40.382277,-91.414338 40.38195,-91.415051 40.381747,-91.415695 40.381381,-91.417066 40.380402,-91.418957 40.3787,-91.419422 40.378264,-91.419979 40.378765,-91.421028 40.37971,-91.421635 40.380286,-91.42218 40.380802,-91.422324 40.380939,-91.422408 40.380991,-91.422419 40.380998,-91.422733 40.381206,-91.423053 40.381414,-91.423282 40.381563,-91.424294 40.381961,-91.424384 40.381997,-91.424582 40.382073,-91.425662 40.382491,-91.42668 40.382601,-91.427819 40.38285,-91.428069 40.382901,-91.428708 40.38303,-91.429762 40.383318,-91.430678 40.383619,-91.43249 40.384212,-91.432699 40.384281,-91.434553 40.384876,-91.435117 40.384973,-91.435124 40.384974,-91.435145 40.384978,-91.435153 40.38498,-91.435507 40.385041,-91.435924 40.385114,-91.436565 40.385263,-91.436792 40.385317,-91.436916 40.385346,-91.437956 40.385588,-91.437979 40.385594,-91.439552 40.38607,-91.439761 40.386133,-91.441076 40.386241,-91.441243 40.386255,-91.441266 40.386237,-91.441843 40.385788,-91.441957 40.385705,-91.442306 40.385449,-91.442748 40.385128,-91.443275 40.384591,-91.443579 40.384283,-91.445168 40.382461,-91.445099 40.380837,-91.445371 40.379388,-91.446627 40.377918,-91.448742 40.376804,-91.451627 40.376019,-91.454535 40.37544,-91.458089 40.375457,-91.460395 40.375472,-91.463322 40.375628,-91.463895 40.375659,-91.465009 40.376223,-91.465619 40.377069,-91.465702 40.377464,-91.465858 40.378201,-91.465891 40.378365,-91.465809 40.378651,-91.46561 40.379314,-91.464681 40.380949,-91.463912 40.381999,-91.463656 40.382348,-91.463514 40.382543,-91.463008 40.384041,-91.463137 40.385052,-91.463146 40.385119,-91.463161 40.385136,-91.463386 40.385394,-91.463494 40.385492,-91.46355 40.385543,-91.463554 40.385547,-91.464017 40.385636,-91.464606 40.385491,-91.464885 40.385362,-91.465116 40.385257,-91.465334 40.385168,-91.467115 40.38444,-91.467855 40.384139,-91.471967 40.382884,-91.472635 40.382788,-91.474378 40.382538,-91.474538 40.382503,-91.474703 40.382468,-91.474789 40.382449,-91.474868 40.382432,-91.475333 40.382333,-91.475545 40.382287,-91.475797 40.382234,-91.476551 40.382072,-91.477735 40.381989,-91.478401 40.381943,-91.480251 40.381783,-91.482322 40.382057,-91.483153 40.382492,-91.483352 40.382745,-91.483804 40.383321,-91.484507 40.3839,-91.484627 40.383991,-91.484738 40.384114,-91.484775 40.384145,-91.484887 40.384241,-91.484925 40.384273,-91.485047 40.384471,-91.485255 40.384657,-91.48537 40.384806,-91.48555 40.385162,-91.48573 40.385574,-91.485866 40.385777,-91.485955 40.385864,-91.485996 40.385904,-91.486118 40.386134,-91.486226 40.386392,-91.486477 40.386689,-91.486542 40.386804,-91.486794 40.387095,-91.486937 40.387238,-91.487167 40.387402,-91.487275 40.387523,-91.487491 40.387852,-91.487634 40.38805,-91.487836 40.388253,-91.487929 40.388379,-91.488001 40.38844,-91.488253 40.388785,-91.488864 40.389521,-91.488971 40.389686,-91.489208 40.389944,-91.489494 40.390351,-91.48964 40.390558,-91.489755 40.390756,-91.489827 40.390943,-91.489999 40.391256,-91.490115 40.391503,-91.490273 40.391711,-91.490416 40.391964,-91.49044 40.392013,-91.49051 40.392157,-91.490721 40.392589,-91.490792 40.392733,-91.490816 40.392782,-91.490888 40.39293,-91.490912 40.39298,-91.491007 40.393706,-91.491037 40.393929,-91.490816 40.395225,-91.490502 40.395804,-91.490153 40.396449,-91.490041 40.396655,-91.490014 40.396706,-91.489875 40.396964,-91.489705 40.397277,-91.489604 40.397465,-91.489596 40.397485,-91.489099 40.398738,-91.488597 40.400009,-91.488334 40.401265,-91.487955 40.402465,-91.487935 40.402598,-91.487818 40.403415,-91.487829 40.403866,-91.487889 40.403906,-91.488166 40.404098,-91.488481 40.404317,-91.4885 40.404318,-91.489108 40.40436,-91.48928 40.404373,-91.48937 40.404375,-91.489445 40.404384,-91.489521 40.404393,-91.489553 40.404398,-91.489742 40.404339,-91.489816 40.404317,-91.490272 40.404061,-91.491641 40.403292,-91.492098 40.403037,-91.492202 40.402982,-91.492498 40.402828,-91.492517 40.402821,-91.492629 40.402782,-91.493644 40.402433,-91.494525 40.402268,-91.494592 40.402256,-91.49619 40.402192,-91.498093 40.401926,-91.498788 40.402111,-91.500156 40.402555,-91.500317 40.402578,-91.502267 40.402862,-91.502757 40.402933,-91.503461 40.403095,-91.504764 40.403395,-91.505272 40.403512,-91.506745 40.404335,-91.506792 40.404431,-91.507202 40.405266,-91.507391 40.405483,-91.507398 40.405491,-91.507419 40.405515,-91.507427 40.405524,-91.50823 40.406138,-91.509063 40.406775,-91.510288 40.407258,-91.510926 40.407458,-91.511891 40.407762,-91.512181 40.407853,-91.512664 40.408005,-91.513042 40.408156,-91.513325 40.40827,-91.513993 40.408537,-91.514297 40.40855,-91.514822 40.408573,-91.516534 40.408567,-91.517369 40.408618,-91.518392 40.408682,-91.518769 40.408753,-91.519755 40.40894,-91.519901 40.40898,-91.520272 40.409082,-91.522333 40.409648,-91.523496 40.410218,-91.524612 40.410765,-91.525309 40.411779,-91.526425 40.413404,-91.527057 40.416689,-91.527043 40.418214,-91.526839 40.418908,-91.526706 40.41936,-91.526573 40.419813,-91.526555 40.419872,-91.525268 40.421599,-91.523164 40.424176,-91.521388 40.426488,-91.521027 40.42715,-91.520884 40.427413,-91.520344 40.428403,-91.519804 40.429392,-91.51966 40.429654,-91.519517 40.429917,-91.519492 40.429951,-91.519366 40.430318,-91.519268 40.430588,-91.51917 40.430859,-91.519012 40.431298,-91.51904 40.431805,-91.519082 40.432558,-91.519086 40.432578,-91.519134 40.432822,-91.519157 40.432847,-91.51974 40.433466,-91.519935 40.433673,-91.521016 40.433719,-91.522551 40.433695,-91.522638 40.433687,-91.525 40.433483,-91.527645 40.433772,-91.529132 40.434272,-91.530355 40.434964,-91.530438 40.435033,-91.531523 40.435938,-91.532603 40.436647,-91.532807 40.436784,-91.532898 40.436911,-91.533471 40.437715,-91.533524 40.437929,-91.533623 40.43832,-91.533589 40.439454,-91.533555 40.440589,-91.533548 40.440804,-91.532837 40.44164,-91.532816 40.441666,-91.531912 40.44273,-91.529613 40.444451,-91.529098 40.444836,-91.527733 40.445657,-91.526108 40.446634,-91.524368 40.44816,-91.524053 40.448437,-91.523271 40.450061,-91.523147 40.451416,-91.523143 40.451459,-91.523131 40.451588,-91.523127 40.451631,-91.523072 40.452254,-91.5231 40.452441,-91.523379 40.454255,-91.523517 40.454847,-91.523703 40.455641,-91.523864 40.456331,-91.524518 40.457139,-91.524956 40.457679,-91.52509 40.457845,-91.526155 40.458625,-91.526783 40.458767,-91.527581 40.458948,-91.5286 40.459002,-91.530534 40.45902,-91.531915 40.458985,-91.533704 40.458942,-91.533761 40.458941,-91.534356 40.458879,-91.53452 40.458862,-91.534615 40.458851,-91.534684 40.458844,-91.535169 40.458793,-91.535394 40.458769,-91.535654 40.458742,-91.538348 40.458402,-91.541324 40.458265,-91.542566 40.458208,-91.543785 40.458149,-91.54694 40.458205,-91.548366 40.45823,-91.550598 40.458508,-91.551625 40.458636,-91.552652 40.458764,-91.552691 40.458769,-91.553752 40.458958,-91.554376 40.459068,-91.556805 40.4595,-91.558266 40.459845,-91.56057 40.460391,-91.563844 40.460988,-91.564485 40.461214,-91.566411 40.461893,-91.566638 40.461973,-91.566998 40.4621,-91.567055 40.462115,-91.567178 40.462146,-91.5673 40.462177,-91.567362 40.462192,-91.567743 40.46229,-91.568088 40.462443,-91.568254 40.462516,-91.568317 40.462544,-91.568545 40.462645,-91.570114 40.463338,-91.570916 40.463693,-91.574594 40.465586,-91.574713 40.465647,-91.574746 40.465664,-91.575598 40.466406,-91.575806 40.466586,-91.576013 40.466767,-91.577875 40.468454,-91.578189 40.468778,-91.580233 40.470889,-91.580355 40.471015,-91.581011 40.472059,-91.581528 40.472876,-91.582437 40.474703,-91.582697 40.476219,-91.582828 40.476979,-91.583315 40.479118,-91.585169 40.483611,-91.585259 40.483802,-91.585733 40.484801,-91.586884 40.487233,-91.589377 40.490898,-91.590817 40.492292,-91.592551 40.493696,-91.594644 40.494997,-91.594913 40.495103,-91.599634 40.49696,-91.599748 40.497005,-91.600155 40.497142,-91.601724 40.497672,-91.602248 40.497849,-91.602267 40.497855,-91.602324 40.497874,-91.602343 40.497881,-91.602634 40.49798,-91.602926 40.498078,-91.603136 40.49815,-91.603346 40.498221,-91.605097 40.498813,-91.608268 40.50001,-91.608347 40.50004,-91.611977 40.501938,-91.612821 40.502377,-91.616357 40.504448,-91.616948 40.504794,-91.619486 40.507134,-91.621181 40.509801,-91.621353 40.510072,-91.621554 40.510928,-91.621576 40.511018,-91.621821 40.512064,-91.622362 40.514362,-91.622192 40.517039,-91.622053 40.517404,-91.621866 40.517921,-91.621762 40.518218,-91.621532 40.518842,-91.621302 40.519467,-91.620414 40.52188,-91.61989 40.523303,-91.619526 40.524294,-91.618793 40.526286,-91.618772 40.526487,-91.618693 40.527294,-91.618608 40.528154,-91.618523 40.529015,-91.618028 40.53403,-91.61818 40.535283,-91.618367 40.536818,-91.618999 40.539084,-91.61907 40.539199,-91.620071 40.540817,-91.620412 40.541092,-91.621902 40.542292,-91.622986 40.54272,-91.623545 40.54294,-91.623906 40.543082,-91.624886 40.543408,-91.625161 40.5435,-91.625252 40.543515,-91.625845 40.543616,-91.626939 40.543807,-91.630293 40.54437,-91.630791 40.544454,-91.631167 40.544563,-91.632783 40.545029,-91.634911 40.545162,-91.635404 40.545221,-91.638082 40.545541,-91.638499 40.545626,-91.639066 40.545742,-91.639633 40.545857,-91.640018 40.545936,-91.640404 40.546014,-91.640557 40.546045,-91.64071 40.546076,-91.641498 40.546236,-91.642285 40.546397,-91.6436 40.546664,-91.64371 40.546687,-91.644995 40.546973,-91.646674 40.547371,-91.647334 40.547528,-91.647995 40.547684,-91.654345 40.549189,-91.656548 40.54942,-91.660063 40.549789,-91.662197 40.550014,-91.664213 40.55023,-91.664265 40.550236,-91.664455 40.550255,-91.664582 40.550263,-91.664886 40.550296,-91.665189 40.550328,-91.666028 40.550416,-91.666867 40.550504,-91.668448 40.55067,-91.67003 40.550836,-91.670993 40.550937,-91.678315 40.55237,-91.681714 40.553035,-91.681742 40.553052,-91.682382 40.55343,-91.683254 40.553944,-91.683943 40.554351,-91.684765 40.554837,-91.685196 40.555092,-91.687328 40.55649,-91.6887 40.55739,-91.689526 40.558373,-91.689948 40.558874,-91.690369 40.559376,-91.690547 40.559588,-91.690804 40.559893,-91.691065 40.560666,-91.691581 40.562223,-91.691563 40.564133,-91.691557 40.564839,-91.691561 40.564867,-91.691538 40.564911,-91.691382 40.565282,-91.691225 40.565652,-91.690761 40.566751,-91.690297 40.567851,-91.689967 40.568632,-91.686638 40.575026,-91.685723 40.576785,-91.685657 40.577193,-91.685381 40.578892,-91.68565 40.579439,-91.686357 40.580875,-91.686754 40.581283,-91.68882 40.583409,-91.696339 40.588135,-91.696359 40.588148,-91.697102 40.588459,-91.701242 40.59019,-91.705382 40.591921,-91.707251 40.592703,-91.707786 40.592968,-91.707932 40.593039,-91.708214 40.593176,-91.708641 40.593385,-91.709902 40.594004,-91.711163 40.594623,-91.712025 40.595046,-91.712199 40.595174,-91.712284 40.595237,-91.712369 40.595301,-91.714569 40.596915,-91.716769 40.59853,-91.720058 40.601527,-91.729115 40.61364,-91.730422 40.613581,-91.730482 40.613578,-91.737047 40.613287,-91.740622 40.613129,-91.740866 40.613119,-91.750164 40.612724,-91.762441 40.612297,-91.763056 40.612276,-91.766085 40.612173,-91.766674 40.612152,-91.769718 40.612046,-91.769735 40.612045,-91.778604 40.611815,-91.779227 40.611801,-91.785899 40.611593,-91.786395 40.611567,-91.786827 40.611545,-91.792952 40.611317,-91.794195 40.611264,-91.794992 40.611241,-91.795 40.611241,-91.795 40.611249,-91.795051 40.611249,-91.79908 40.610989,-91.799241 40.610979,-91.799746 40.610961,-91.800133 40.610949,-91.813828 40.61053,-91.813963 40.610521,-91.824194 40.610298,-91.824199 40.610304,-91.824202 40.6103,-91.824247 40.610265,-91.824309 40.610247,-91.824732 40.61022,-91.825314 40.610198,-91.826155 40.610165,-91.827063 40.610118,-91.829575 40.610021,-91.831016 40.609969,-91.831415 40.609948,-91.831431 40.609948,-91.832162 40.609912,-91.832277 40.609884,-91.832317 40.609866,-91.832343 40.609854,-91.832392 40.609805,-91.83325 40.609759,-91.833369 40.609753,-91.840663 40.6094,-91.840824 40.609392,-91.852849 40.608811,-91.8684 40.608056,-91.868731 40.608048,-91.869553 40.608032,-91.87517 40.607901,-91.883338 40.607636,-91.889747 40.607465,-91.902038 40.607138,-91.905608 40.607043,-91.907348 40.606998,-91.915831 40.606773,-91.915832 40.606772,-91.915957 40.606768,-91.916083 40.606765,-91.916201 40.606762,-91.916319 40.606759,-91.921313 40.606626,-91.922356 40.606599,-91.925396 40.606518,-91.925398 40.606518,-91.925866 40.606505,-91.936804 40.606211,-91.939305 40.606144,-91.939451 40.606146,-91.939676 40.606135,-91.943112 40.605827,-91.944167 40.605731,-91.945621 40.605599,-91.947337 40.605493,-91.947709 40.605471,-91.948153 40.605462,-91.948396 40.605457,-91.948448 40.605455,-91.948605 40.605451,-91.948658 40.605451,-91.949348 40.605437,-91.951418 40.605395,-91.952109 40.605382,-91.961695 40.605191,-91.966754 40.605151,-91.969984 40.605125,-91.970177 40.60516,-91.970755 40.605109,-91.970988 40.605112,-91.9715 40.605106,-91.982346 40.604804,-91.998645 40.604434,-91.998941 40.604436,-91.999832 40.604442,-92.000129 40.604444,-92.000303 40.604445,-92.000828 40.604451,-92.001003 40.604453,-92.006918 40.604419,-92.007824 40.604398,-92.008406 40.604378,-92.009498 40.604365,-92.012646 40.604299,-92.015248 40.604223,-92.017132 40.604157,-92.017757 40.604129,-92.018095 40.604122,-92.020963 40.604058,-92.021131 40.604053,-92.021309 40.604047,-92.022041 40.604023,-92.02345 40.603969,-92.024349 40.603928,-92.025075 40.603901,-92.026131 40.603871,-92.027849 40.603848,-92.029624 40.603819,-92.030235 40.603769,-92.030674 40.603754,-92.03081 40.603747,-92.031235 40.603739,-92.031513 40.603734,-92.031866 40.603727,-92.032276 40.60372,-92.033448 40.603684,-92.034397 40.603657,-92.034629 40.603652,-92.036467 40.603615,-92.036938 40.603595,-92.037385 40.603587,-92.037787 40.603567,-92.038382 40.603543,-92.038868 40.603541,-92.040157 40.603516,-92.04049 40.603507,-92.041501 40.60348,-92.042304 40.603452,-92.043331 40.603407,-92.043787 40.603399,-92.04418 40.603379,-92.04452 40.603376,-92.044837 40.603367,-92.045122 40.603369,-92.04584 40.603358,-92.046002 40.603341,-92.047014 40.603303,-92.049176 40.60325,-92.049817 40.603226,-92.050774 40.603198,-92.05238 40.603176,-92.052735 40.603183,-92.052736 40.603183,-92.052897 40.603186,-92.053067 40.603193,-92.053117 40.603203,-92.053236 40.603229,-92.053398 40.603226,-92.053615 40.603222,-92.053751 40.603187,-92.053773 40.603182,-92.053924 40.603151,-92.054111 40.603104,-92.054386 40.603101,-92.05481 40.603085,-92.05524 40.603089,-92.055478 40.603078,-92.055973 40.603076,-92.056461 40.603063,-92.056978 40.603057,-92.057697 40.603041,-92.058343 40.603041,-92.058702 40.603022,-92.058875 40.602979,-92.059098 40.602932,-92.059315 40.602862,-92.06022 40.60282,-92.061656 40.602794,-92.062092 40.602772,-92.062123 40.60277,-92.062605 40.602774,-92.063316 40.602747,-92.063811 40.602735,-92.063932 40.60273,-92.065464 40.602671,-92.066297 40.602645,-92.067662 40.602618,-92.067884 40.60262,-92.06791 40.602622,-92.067928 40.602623,-92.068601 40.60268,-92.068845 40.602688,-92.069233 40.602691,-92.069779 40.602684,-92.069863 40.602756,-92.069871 40.602755,-92.069872 40.602755,-92.071274 40.60269,-92.082318 40.602177,-92.082345 40.602198,-92.082453 40.602257,-92.08265 40.602268,-92.083172 40.602263,-92.083212 40.602265,-92.083279 40.602267,-92.083289 40.602271,-92.083292 40.602273,-92.08398 40.602228,-92.084961 40.602207,-92.085306 40.602203,-92.086341 40.602195,-92.086687 40.602193,-92.087255 40.602182,-92.088963 40.602152,-92.089532 40.602142,-92.090121 40.602131,-92.09189 40.602097,-92.09248 40.602087,-92.093036 40.602077,-92.093138 40.602064,-92.093155 40.602062,-92.09373 40.602025,-92.09533 40.602032,-92.095968 40.602013,-92.096239 40.602015,-92.09641 40.602005,-92.09652 40.601976,-92.096583 40.601905,-92.096611 40.601831,-92.112642 40.60187,-92.113216 40.601851,-92.114016 40.601826,-92.118141 40.601698,-92.119517 40.601656,-92.119939 40.601642,-92.121205 40.601603,-92.121628 40.60159,-92.122225 40.601571,-92.124019 40.601515,-92.124617 40.601497,-92.12463 40.601496,-92.124669 40.601495,-92.124683 40.601495,-92.125187 40.601481,-92.126458 40.601457,-92.131783 40.601358,-92.133559 40.601326,-92.144198 40.601133,-92.149659 40.601033,-92.149682 40.601033,-92.149745 40.601031,-92.159939 40.600861,-92.160255 40.600856,-92.17535 40.600603,-92.176236 40.600588,-92.178893 40.600543,-92.17978 40.600529,-92.181537 40.600489,-92.181726 40.600485,-92.195084 40.600207,-92.19532 40.600203,-92.195327 40.600203,-92.196162 40.600069,-92.196793 40.600134,-92.198165 40.600125,-92.19876 40.600129,-92.198783 40.600114,-92.198827 40.600086,-92.198951 40.600061,-92.199224 40.600057,-92.200306 40.600052,-92.200943 40.600025,-92.20162 40.600023,-92.201673 40.600018,-92.201744 40.600011,-92.201819 40.59998,-92.204903 40.599974,-92.204926 40.599974,-92.204972 40.599974,-92.205042 40.599974,-92.205114 40.599974,-92.205138 40.599974,-92.205151 40.599974,-92.205158 40.599973,-92.205165 40.599973,-92.205212 40.599973,-92.207443 40.599969,-92.20913 40.599966,-92.209175 40.599966,-92.217414 40.599834,-92.217497 40.599833,-92.217547 40.599832,-92.217555 40.599832,-92.22155 40.599769,-92.222717 40.59975,-92.226877 40.599684,-92.226884 40.599684,-92.226892 40.599684,-92.227199 40.599627,-92.228258 40.599602,-92.228814 40.599583,-92.229544 40.59958,-92.230264 40.599561,-92.231518 40.599569,-92.233368 40.599535,-92.234345 40.599502,-92.23599 40.599489,-92.236474 40.599481,-92.245792 40.59916,-92.246035 40.59915,-92.247669 40.599195,-92.250183 40.599264,-92.255586 40.59916,-92.256219 40.599148,-92.261396 40.599057,-92.265487 40.598985,-92.265861 40.598978,-92.265929 40.598977,-92.265973 40.598976,-92.265988 40.598976,-92.265996 40.598975,-92.266008 40.598975,-92.266113 40.598973,-92.266138 40.598973,-92.266357 40.598969,-92.267142 40.598955,-92.267513 40.598949,-92.267554 40.598948,-92.267581 40.598948,-92.267591 40.598948,-92.26778 40.598945,-92.267815 40.598944,-92.267985 40.598941,-92.269747 40.598909,-92.269758 40.598909,-92.26977 40.598909,-92.269781 40.598908,-92.269791 40.598908,-92.269897 40.598905,-92.270063 40.598904,-92.270076 40.598904,-92.270104 40.598904,-92.270187 40.598903,-92.293333 40.598551,-92.293392 40.59855,-92.293399 40.59855,-92.293411 40.598549,-92.293426 40.598549,-92.29344 40.598549,-92.293593 40.598546,-92.296394 40.598504,-92.29657 40.598502,-92.296747 40.598499,-92.298732 40.598469,-92.298774 40.598468,-92.299454 40.598454,-92.300291 40.598429,-92.300963 40.598415,-92.301448 40.598409,-92.301939 40.598402,-92.302096 40.5984,-92.302386 40.598394,-92.302715 40.598378,-92.302808 40.598379,-92.302973 40.59838,-92.303199 40.598369,-92.304341 40.598352,-92.305772 40.598319,-92.309517 40.598246,-92.311371 40.598204,-92.313685 40.598158,-92.31467 40.598128,-92.317537 40.598058,-92.317546 40.598057,-92.317553 40.598057,-92.317561 40.598057,-92.317816 40.598051,-92.317939 40.598049,-92.317949 40.598049,-92.317957 40.598049,-92.319362 40.59803,-92.32048 40.598007,-92.321051 40.597993,-92.321622 40.597972,-92.32471 40.597919,-92.325852 40.597896,-92.327885 40.59783,-92.328792 40.597812,-92.329699 40.597787,-92.330849 40.59777,-92.331185 40.59776,-92.331354 40.597754,-92.331443 40.597732,-92.331451 40.597729,-92.331492 40.597712,-92.336941 40.597526,-92.341658 40.597365,-92.341668 40.597365,-92.349484 40.597227,-92.350807 40.597275,-92.35089 40.59726,-92.351044 40.597245,-92.351949 40.597224,-92.355999 40.597187,-92.356355 40.597175,-92.358163 40.597117,-92.359074 40.597081,-92.359708 40.597061,-92.360384 40.597032,-92.362206 40.596987,-92.363051 40.596971,-92.368119 40.596844,-92.37539 40.596594,-92.378417 40.596485,-92.379576 40.596469,-92.386009 40.596415,-92.394114 40.596251,-92.399095 40.596151,-92.399715 40.596138,-92.400045 40.596131,-92.405632 40.59602,-92.408879 40.595954,-92.409047 40.59595,-92.415709 40.595819,-92.415802 40.595817,-92.417334 40.595786,-92.421971 40.595693,-92.423827 40.595656,-92.427773 40.595567,-92.433019 40.595451,-92.433046 40.59545,-92.433139 40.595448,-92.433159 40.595448,-92.434602 40.595416,-92.436705 40.59537,-92.438154 40.595338,-92.442055 40.595356,-92.442158 40.595355,-92.44508 40.595331,-92.447871 40.595308,-92.448097 40.595306,-92.448105 40.595306,-92.4482 40.595306,-92.453405 40.595296,-92.453438 40.5953,-92.453667 40.595326,-92.455796 40.595325,-92.456662 40.595331,-92.45824 40.595342,-92.461102 40.59531,-92.461304 40.595316,-92.461408 40.595334,-92.461461 40.595377,-92.462804 40.595275,-92.462826 40.595273,-92.469984 40.59517,-92.470221 40.595173,-92.470311 40.595174,-92.471481 40.595187,-92.473042 40.595153,-92.479345 40.595011,-92.47956 40.595006,-92.480416 40.594988,-92.481114 40.594973,-92.481148 40.594971,-92.481208 40.594968,-92.481223 40.594967,-92.481509 40.59495,-92.481517 40.59495,-92.481525 40.594949,-92.481571 40.594947,-92.481643 40.594963,-92.48183 40.595003,-92.482194 40.595038,-92.482988 40.595059,-92.483981 40.595063,-92.484513 40.595077,-92.484741 40.595083,-92.484947 40.595084,-92.485216 40.595085,-92.486396 40.595032,-92.488658 40.594949,-92.491449 40.594861,-92.492883 40.594842,-92.494075 40.594805,-92.495652 40.59477,-92.4967 40.594766,-92.497105 40.594743,-92.497163 40.59474,-92.499514 40.59461,-92.50107 40.594524,-92.502361 40.594437,-92.502624 40.594429,-92.505935 40.594328,-92.506465 40.594312,-92.508771 40.594229,-92.511275 40.594156,-92.51334 40.594089,-92.51357 40.594082,-92.51913 40.593953,-92.522245 40.593868,-92.523631 40.593829,-92.523643 40.593829,-92.523738 40.593827,-92.526499 40.593765,-92.529224 40.593684,-92.532975 40.593565,-92.535054 40.593511,-92.535141 40.593509,-92.539319 40.593399,-92.540251 40.593376,-92.540356 40.593374,-92.541889 40.59334,-92.542327 40.593342,-92.542422 40.593342,-92.542617 40.593343,-92.54394 40.593323,-92.545639 40.593279,-92.549931 40.593144,-92.552964 40.593064,-92.556693 40.592977,-92.557482 40.592973,-92.557642 40.592972,-92.557664 40.592972,-92.558568 40.592934,-92.559328 40.592909,-92.55937 40.592908,-92.560057 40.592889,-92.560742 40.592833,-92.5613 40.592814,-92.561889 40.592778,-92.562992 40.59274,-92.563776 40.592727,-92.564305 40.592712,-92.564813 40.592688,-92.565276 40.592673,-92.566589 40.592645,-92.568034 40.592608,-92.570306 40.592516,-92.572943 40.592441,-92.575204 40.592366,-92.577058 40.592322,-92.579549 40.592249,-92.579617 40.592247,-92.580025 40.592249,-92.580266 40.592258,-92.580747 40.592237,-92.580883 40.592232,-92.581019 40.592226,-92.587073 40.591966,-92.601171 40.591651,-92.60891 40.591479,-92.610664 40.59144,-92.610685 40.59144,-92.612931 40.59139,-92.615439 40.591334,-92.615565 40.591331,-92.618372 40.591268,-92.625191 40.591044,-92.631441 40.590997,-92.631711 40.590995,-92.631946 40.590994,-92.632282 40.590992,-92.634164 40.590944,-92.634175 40.590944,-92.635043 40.590922,-92.637898 40.590853,-92.639225 40.590829,-92.647773 40.59065,-92.650407 40.590597,-92.657248 40.590429,-92.658187 40.590407,-92.658372 40.590397,-92.659409 40.590341,-92.668484 40.590205,-92.685885 40.589869,-92.686282 40.589861,-92.686693 40.589809,-92.686851 40.589839,-92.688412 40.589922,-92.689074 40.589882,-92.689546 40.5899,-92.690437 40.589852,-92.696639 40.589704,-92.696787 40.589701,-92.696828 40.589701,-92.713177 40.589423,-92.713462 40.589425,-92.714371 40.589431,-92.714596 40.589427,-92.714677 40.589426,-92.714758 40.589424,-92.722891 40.589286,-92.72521 40.589277,-92.729024 40.589261,-92.742316 40.589205,-92.742316 40.589204,-92.742458 40.58917,-92.746547 40.589054,-92.748276 40.589031,-92.748277 40.58903,-92.748755 40.589023,-92.748809 40.589023,-92.752892 40.588968,-92.752893 40.588968,-92.753682 40.588957,-92.757123 40.588909,-92.757123 40.588908,-92.757123 40.588907,-92.757123 40.588906,-92.757123 40.588905,-92.757123 40.588904,-92.757123 40.588903,-92.757123 40.588902,-92.775087 40.588713,-92.775414 40.588708,-92.781167 40.588592,-92.781269 40.58859,-92.781317 40.58859,-92.785411 40.588619,-92.795734 40.588688,-92.795772 40.588688,-92.802642 40.588622,-92.802713 40.588622,-92.816228 40.588542,-92.816429 40.588543,-92.823192 40.588578,-92.823269 40.588578,-92.823346 40.588578,-92.824756 40.588585,-92.827853 40.588602,-92.827845 40.588593,-92.827785 40.58845,-92.828016 40.588468,-92.828719 40.588469,-92.830805 40.588456,-92.831273 40.588465,-92.834058 40.588451,-92.834785 40.588452,-92.834977 40.588451,-92.846811 40.588418,-92.848765 40.588406,-92.854627 40.588374,-92.856445 40.588365,-92.856582 40.588363,-92.857044 40.588358,-92.857045 40.588358,-92.857053 40.588269,-92.857117 40.58822,-92.857219 40.588199,-92.857568 40.588199,-92.857571 40.588199,-92.859457 40.588201,-92.861478 40.588212,-92.86148 40.588213,-92.862452 40.588219,-92.862896 40.588227,-92.863072 40.588213,-92.863192 40.588178,-92.867308 40.588178,-92.8752 40.588392,-92.879178 40.588341,-92.879462 40.588337,-92.886341 40.588248,-92.889746 40.588042,-92.889806 40.588004,-92.889963 40.587978,-92.890181 40.587958,-92.890661 40.587958,-92.891533 40.587966,-92.893618 40.587968,-92.896191 40.587984,-92.899514 40.587973,-92.903081 40.587956,-92.903587 40.587943,-92.903744 40.587924,-92.903899 40.587864,-92.9074 40.587978,-92.916344 40.587863,-92.918038 40.587842,-92.932269 40.587657,-92.932555 40.587654,-92.941592 40.587614,-92.94174 40.587606,-92.94198 40.587611,-92.942651 40.587611,-92.943061 40.587604,-92.943241 40.587649,-92.943361 40.587695,-92.943472 40.587762,-92.943541 40.587695,-92.943701 40.587619,-92.943911 40.587574,-92.944282 40.587558,-92.945632 40.587544,-92.947243 40.587514,-92.948924 40.587492,-92.950395 40.587462,-92.951405 40.587432,-92.952616 40.587425,-92.954437 40.587372,-92.954937 40.587369,-92.955628 40.587365,-92.956595 40.587365,-92.957268 40.587366,-92.957646 40.587361,-92.957827 40.587359,-92.959029 40.587343,-92.96099 40.587336,-92.961591 40.587337,-92.962591 40.587322,-92.963782 40.587292,-92.965283 40.587262,-92.966422 40.587223,-92.966623 40.587216,-92.968604 40.587179,-92.970165 40.587141,-92.971856 40.587108,-92.97192 40.587106,-92.972436 40.587096,-92.973889 40.587074,-92.973891 40.587074,-92.976038 40.587042,-92.97634 40.587037,-92.978169 40.587005,-92.97963 40.586975,-92.980721 40.586957,-92.980869 40.586955,-92.981151 40.586953,-92.982372 40.586931,-92.982872 40.586922,-92.984102 40.586892,-92.985008 40.586887,-92.985385 40.586875,-92.985613 40.586867,-92.985613 40.586866,-92.985773 40.586862,-92.987203 40.586841,-92.990425 40.586786,-92.991896 40.586771,-92.992186 40.586764,-92.997109 40.586649,-92.998349 40.586626,-92.99894 40.586603,-92.99958 40.586588,-93.00034 40.586557,-93.0012 40.586546,-93.00121 40.586546,-93.006463 40.586443,-93.010305 40.586359,-93.011786 40.586336,-93.012156 40.586326,-93.012915 40.586301,-93.0135 40.586297,-93.01722 40.586201,-93.019166 40.586147,-93.023544 40.586045,-93.024413 40.586013,-93.024849 40.585997,-93.028037 40.585908,-93.028741 40.585889,-93.030805 40.585817,-93.031082 40.585809,-93.031175 40.585807,-93.034017 40.585726,-93.034416 40.585722,-93.034488 40.585721,-93.034488 40.58572,-93.0363 40.585702,-93.039012 40.585643,-93.040348 40.585617,-93.040418 40.585614,-93.040542 40.58561,-93.043889 40.585484,-93.044499 40.585461,-93.048712 40.585328,-93.051306 40.585279,-93.053229 40.585248,-93.054941 40.585206,-93.055465 40.585182,-93.056559 40.585116,-93.057098 40.58508,-93.057395 40.585066,-93.057739 40.58505,-93.05883 40.58504,-93.058906 40.585039,-93.059201 40.585037,-93.060562 40.584999,-93.061374 40.584976,-93.064113 40.584862,-93.065435 40.584826,-93.066599 40.5848,-93.067114 40.584789,-93.067921 40.58476,-93.069703 40.584687,-93.070116 40.58466,-93.070993 40.584639,-93.071243 40.584633,-93.07262 40.584607,-93.074186 40.584584,-93.077319 40.584518,-93.07775 40.584513,-93.077788 40.584512,-93.079138 40.584495,-93.083922 40.58437,-93.085272 40.584341,-93.085852 40.584326,-93.089784 40.584262,-93.092044 40.584188,-93.092086 40.584186,-93.092213 40.584182,-93.092256 40.584181,-93.092286 40.58418,-93.092377 40.584176,-93.092408 40.584176,-93.092604 40.584169,-93.09276 40.584163,-93.09285 40.584161,-93.093817 40.584129,-93.09417 40.584118,-93.094795 40.584097,-93.09667 40.584034,-93.097296 40.584014,-93.098529 40.583956,-93.098593 40.583953,-93.101499 40.583874,-93.101566 40.583872,-93.1016 40.583871,-93.101603 40.583871,-93.101615 40.583871,-93.103065 40.583832,-93.103067 40.583832,-93.117909 40.583339,-93.117949 40.583338,-93.117972 40.583337,-93.117977 40.583337,-93.117984 40.583337,-93.117985 40.583336,-93.117988 40.583336,-93.117991 40.583336,-93.117998 40.583336,-93.119806 40.583276,-93.119894 40.583274,-93.119901 40.583274,-93.119905 40.583274,-93.11991 40.583274,-93.119993 40.583272,-93.120004 40.583272,-93.125208 40.583164,-93.127294 40.583103,-93.127315 40.583102,-93.129571 40.583036,-93.129595 40.583036,-93.12981 40.583029,-93.130557 40.583007,-93.130604 40.583006,-93.13253 40.582949,-93.132541 40.582949,-93.132778 40.582942,-93.13278 40.582942,-93.135802 40.582854,-93.141078 40.582793,-93.1444 40.582732,-93.147701 40.58267,-93.147723 40.58267,-93.147776 40.58267,-93.147983 40.582665,-93.14799 40.582665,-93.147997 40.582665,-93.148038 40.582665,-93.157224 40.582497,-93.157602 40.58249,-93.157762 40.582487,-93.157807 40.582486,-93.157833 40.582486,-93.157851 40.582486,-93.157857 40.582486,-93.15792 40.582485,-93.157962 40.582484,-93.159127 40.582463,-93.159222 40.582461,-93.166399 40.58233,-93.166404 40.58233,-93.177659 40.582115,-93.177721 40.582114,-93.177773 40.582113,-93.177778 40.582113,-93.177779 40.582112,-93.177781 40.582112,-93.177788 40.582112,-93.177795 40.582112,-93.1778 40.582112,-93.177833 40.582111,-93.178154 40.582105,-93.183697 40.581999,-93.18371 40.581999,-93.184755 40.581986,-93.185734 40.581973,-93.1858 40.581972,-93.189123 40.58193,-93.189199 40.581929,-93.189208 40.581929,-93.189232 40.581929,-93.189298 40.581928,-93.191332 40.581902,-93.196448 40.581837,-93.196785 40.581833,-93.196877 40.581832,-93.197212 40.58183,-93.197294 40.581829,-93.201236 40.581806,-93.20179 40.581804,-93.204098 40.581765,-93.210739 40.581653,-93.211568 40.581639,-93.212396 40.581625,-93.221626 40.581449,-93.225861 40.581407,-93.230117 40.581363,-93.230848 40.581358,-93.231188 40.581354,-93.231559 40.58135,-93.2316 40.58135,-93.232178 40.581343,-93.232247 40.581342,-93.232261 40.581342,-93.232269 40.581342,-93.232275 40.581342,-93.23228 40.581342,-93.232283 40.581342,-93.232285 40.581341,-93.232307 40.581341,-93.232396 40.58134,-93.232816 40.581335,-93.233744 40.581324,-93.233942 40.581322,-93.233994 40.581321,-93.234372 40.581317,-93.241467 40.58124,-93.241476 40.58124,-93.250552 40.580939,-93.251695 40.580924,-93.258559 40.580838,-93.258566 40.580838,-93.258571 40.580838,-93.258574 40.580838,-93.258577 40.580838,-93.258578 40.580837,-93.258597 40.580837,-93.258613 40.580837,-93.258615 40.580837,-93.258621 40.580837,-93.258627 40.580837,-93.258635 40.580837,-93.258643 40.580837,-93.25865 40.580837,-93.258654 40.580837,-93.258656 40.580836,-93.258658 40.580836,-93.258689 40.580836,-93.260429 40.580814,-93.260444 40.580814,-93.260492 40.580818,-93.260494 40.580818,-93.260496 40.580818,-93.260498 40.580818,-93.2605 40.580818,-93.260502 40.580818,-93.260504 40.580818,-93.260506 40.580818,-93.260508 40.580818,-93.26051 40.580818,-93.260512 40.580818,-93.260514 40.580818,-93.260516 40.580818,-93.260518 40.580818,-93.26052 40.580818,-93.260522 40.580818,-93.260524 40.580818,-93.260526 40.580818,-93.260528 40.580818,-93.26053 40.580818,-93.260532 40.580818,-93.260534 40.580818,-93.260536 40.580818,-93.260538 40.580818,-93.26054 40.580818,-93.260542 40.580818,-93.260544 40.580818,-93.260546 40.580818,-93.260548 40.580818,-93.26055 40.580818,-93.260552 40.580818,-93.260554 40.580818,-93.260556 40.580818,-93.260558 40.580818,-93.260561 40.580818,-93.260564 40.580818,-93.260567 40.580818,-93.26057 40.580818,-93.260573 40.580818,-93.260576 40.580818,-93.260579 40.580818,-93.260582 40.580818,-93.260586 40.580818,-93.26059 40.580818,-93.260594 40.580818,-93.260598 40.580818,-93.260602 40.580818,-93.260607 40.580818,-93.260612 40.580818,-93.260617 40.580818,-93.260623 40.580818,-93.260629 40.580818,-93.260635 40.580818,-93.260642 40.580818,-93.260649 40.580818,-93.260657 40.580818,-93.260665 40.580818,-93.260674 40.580818,-93.260684 40.580818,-93.260695 40.580818,-93.260707 40.580818,-93.26072 40.580818,-93.260734 40.580818,-93.26075 40.580818,-93.260767 40.580818,-93.260786 40.580818,-93.260808 40.580818,-93.260833 40.580818,-93.260861 40.580818,-93.261794 40.580826,-93.262615 40.580817,-93.262637 40.580817,-93.262649 40.580817,-93.262655 40.580817,-93.262656 40.580816,-93.262658 40.580816,-93.262661 40.580816,-93.262706 40.580816,-93.262751 40.580816,-93.262795 40.580816,-93.262837 40.580816,-93.262878 40.580816,-93.262917 40.580816,-93.262955 40.580816,-93.262991 40.580816,-93.263025 40.580816,-93.263057 40.580816,-93.263087 40.580816,-93.263115 40.580816,-93.263141 40.580816,-93.263165 40.580816,-93.263187 40.580816,-93.263207 40.580816,-93.266231 40.580795,-93.266863 40.580794,-93.26687 40.580794,-93.268949 40.580789,-93.268952 40.580789,-93.268963 40.580789,-93.270147 40.580786,-93.271492 40.580781,-93.271494 40.580781,-93.271497 40.580781,-93.271502 40.580781,-93.271512 40.580781,-93.271529 40.580781,-93.271561 40.580781,-93.271622 40.580781,-93.273024 40.580777,-93.273199 40.580778,-93.273211 40.580778,-93.273215 40.580778,-93.273217 40.580778,-93.273541 40.580781,-93.274237 40.580765,-93.275488 40.580748,-93.27782 40.580759,-93.278109 40.580758,-93.278111 40.580758,-93.278118 40.580758,-93.279429 40.580754,-93.279454 40.580754,-93.280406 40.580759,-93.281879 40.58075,-93.281883 40.58075,-93.281901 40.58075,-93.282014 40.580749,-93.282016 40.580749,-93.282018 40.580749,-93.28202 40.580749,-93.282023 40.580749,-93.282027 40.580749,-93.282035 40.580749,-93.282048 40.580749,-93.2829 40.580744,-93.284247 40.580754,-93.284409 40.580755,-93.284542 40.580756,-93.28739 40.580761,-93.287846 40.580762,-93.288331 40.580763,-93.288858 40.580764,-93.288893 40.580774,-93.288897 40.580775,-93.288912 40.580779,-93.288918 40.580781,-93.288971 40.580772,-93.288972 40.580771,-93.288974 40.580771,-93.289082 40.580753,-93.289123 40.580752,-93.28955 40.580746,-93.289678 40.580739,-93.289695 40.580738,-93.289697 40.580738,-93.289702 40.580738,-93.290313 40.580706,-93.290315 40.580706,-93.290317 40.580706,-93.290319 40.580706,-93.29034 40.580705,-93.291041 40.580704,-93.291931 40.580708,-93.291949 40.580715,-93.291951 40.580715,-93.291953 40.580715,-93.291955 40.580715,-93.291957 40.580715,-93.291959 40.580715,-93.291961 40.580715,-93.291963 40.580715,-93.291965 40.580715,-93.291967 40.580715,-93.291969 40.580715,-93.291971 40.580715,-93.291973 40.580715,-93.291975 40.580715,-93.291977 40.580715,-93.291979 40.580715,-93.291981 40.580715,-93.291983 40.580715,-93.291985 40.580715,-93.291987 40.580715,-93.291989 40.580715,-93.291991 40.580715,-93.291993 40.580715,-93.291995 40.580715,-93.291997 40.580715,-93.291999 40.580715,-93.292001 40.580715,-93.292003 40.580715,-93.292006 40.580715,-93.292009 40.580715,-93.292012 40.580715,-93.292015 40.580715,-93.292018 40.580715,-93.292021 40.580715,-93.292024 40.580715,-93.292028 40.580715,-93.292032 40.580715,-93.292036 40.580715,-93.29204 40.580715,-93.292044 40.580715,-93.292049 40.580715,-93.292054 40.580715,-93.292059 40.580715,-93.292064 40.580715,-93.29207 40.580715,-93.292076 40.580715,-93.292082 40.580715,-93.292089 40.580715,-93.292096 40.580715,-93.292103 40.580715,-93.292111 40.580715,-93.292119 40.580715,-93.292128 40.580715,-93.292138 40.580715,-93.292148 40.580715,-93.292159 40.580715,-93.292171 40.580715,-93.292184 40.580715,-93.292198 40.580715,-93.292213 40.580715,-93.29223 40.580715,-93.292248 40.580715,-93.292268 40.580715,-93.293364 40.580736,-93.294442 40.58074,-93.295242 40.580738,-93.295255 40.580738,-93.295296 40.580738,-93.295691 40.580705,-93.296319 40.580712,-93.296325 40.580712,-93.296336 40.580712,-93.2967 40.580716,-93.296734 40.580716,-93.296761 40.580716,-93.296783 40.580716,-93.296801 40.580716,-93.296816 40.580716,-93.296828 40.580716,-93.296838 40.580716,-93.296847 40.580716,-93.296854 40.580716,-93.29686 40.580716,-93.296866 40.580716,-93.296871 40.580716,-93.296875 40.580716,-93.296879 40.580716,-93.296882 40.580716,-93.296885 40.580716,-93.296887 40.580716,-93.296889 40.580716,-93.296891 40.580716,-93.296894 40.580716,-93.296896 40.580716,-93.296898 40.580716,-93.2969 40.580716,-93.296902 40.580716,-93.297272 40.580723,-93.297664 40.580722,-93.297935 40.580731,-93.299702 40.580726,-93.299835 40.580709,-93.300007 40.580687,-93.301175 40.580703,-93.30152 40.580712,-93.30153 40.580712,-93.301535 40.580712,-93.301537 40.580712,-93.301539 40.580712,-93.30154 40.580713,-93.301559 40.580713,-93.302101 40.580726,-93.302934 40.580731,-93.303249 40.580728,-93.303251 40.580728,-93.303254 40.580728,-93.30326 40.580728,-93.303274 40.580728,-93.303807 40.580724,-93.303811 40.580724,-93.305251 40.580713,-93.305439 40.580711,-93.306095 40.58069,-93.307033 40.580715,-93.307037 40.580715,-93.30704 40.580715,-93.307043 40.580715,-93.307045 40.580715,-93.307047 40.580715,-93.307049 40.580715,-93.307051 40.580715,-93.307053 40.580715,-93.307627 40.580731,-93.309059 40.580714,-93.309424 40.580695,-93.309427 40.580695,-93.309431 40.580695,-93.309436 40.580695,-93.309442 40.580695,-93.309449 40.580695,-93.309457 40.580695,-93.30946 40.580695,-93.309462 40.580694,-93.309466 40.580694,-93.309688 40.580681,-93.310441 40.580685,-93.310444 40.580685,-93.310452 40.580685,-93.310475 40.580685,-93.312266 40.580693,-93.312398 40.580688,-93.312456 40.580686,-93.316478 40.58065,-93.317108 40.580653,-93.317278 40.580663,-93.317377 40.58066,-93.317401 40.580659,-93.317404 40.580659,-93.317448 40.580658,-93.317468 40.580657,-93.317486 40.580657,-93.317486 40.580661,-93.317486 40.580674,-93.327761 40.580527,-93.33395 40.580441,-93.334534 40.580442,-93.334628 40.580443,-93.334682 40.580443,-93.334791 40.580443,-93.33837 40.580452,-93.341955 40.580461,-93.345489 40.580514,-93.345496 40.580514,-93.345501 40.580514,-93.345503 40.580514,-93.345529 40.580515,-93.345947 40.580486,-93.345949 40.580486,-93.345952 40.580486,-93.345954 40.580485,-93.345957 40.580485,-93.346392 40.580455,-93.346439 40.580452,-93.346449 40.580458,-93.346451 40.580458,-93.346453 40.580458,-93.346455 40.580458,-93.346457 40.580458,-93.346459 40.580458,-93.346461 40.580458,-93.346463 40.580458,-93.346465 40.580458,-93.346467 40.580458,-93.346469 40.580458,-93.34652 40.580468,-93.346944 40.580464,-93.346946 40.580464,-93.346948 40.580464,-93.34695 40.580464,-93.346952 40.580464,-93.346954 40.580464,-93.346956 40.580464,-93.346958 40.580464,-93.34696 40.580464,-93.346962 40.580464,-93.346964 40.580464,-93.346966 40.580464,-93.346968 40.580464,-93.34697 40.580464,-93.346972 40.580464,-93.346974 40.580464,-93.346976 40.580464,-93.346978 40.580464,-93.346981 40.580464,-93.346984 40.580464,-93.346987 40.580464,-93.34699 40.580464,-93.346993 40.580464,-93.346996 40.580464,-93.347 40.580464,-93.347004 40.580464,-93.347008 40.580464,-93.347012 40.580464,-93.347017 40.580464,-93.347022 40.580464,-93.347027 40.580464,-93.347033 40.580464,-93.347039 40.580464,-93.347046 40.580464,-93.347053 40.580464,-93.347061 40.580464,-93.34707 40.580464,-93.347079 40.580464,-93.347089 40.580464,-93.3471 40.580464,-93.347112 40.580464,-93.347125 40.580464,-93.34714 40.580464,-93.347156 40.580464,-93.347174 40.580464,-93.347194 40.580464,-93.347216 40.580464,-93.347241 40.580464,-93.347269 40.580464,-93.347301 40.580464,-93.347746 40.580462,-93.347929 40.580464,-93.347948 40.580464,-93.347953 40.580464,-93.347954 40.580465,-93.347956 40.580465,-93.347958 40.580465,-93.347967 40.580465,-93.34801 40.580465,-93.348059 40.580465,-93.348114 40.580465,-93.348174 40.580465,-93.348238 40.580465,-93.348305 40.580465,-93.348374 40.580465,-93.348442 40.580465,-93.348476 40.580465,-93.348506 40.580465,-93.348534 40.580465,-93.34856 40.580465,-93.348583 40.580465,-93.348605 40.580465,-93.348625 40.580465,-93.348644 40.580465,-93.348661 40.580465,-93.348677 40.580465,-93.348692 40.580465,-93.348706 40.580465,-93.348719 40.580465,-93.348731 40.580465,-93.348742 40.580465,-93.348753 40.580465,-93.348763 40.580465,-93.348773 40.580465,-93.348782 40.580465,-93.348791 40.580465,-93.348799 40.580465,-93.348807 40.580465,-93.348814 40.580465,-93.348821 40.580465,-93.348827 40.580465,-93.348833 40.580465,-93.348839 40.580465,-93.348845 40.580465,-93.34885 40.580465,-93.348855 40.580465,-93.34886 40.580465,-93.348865 40.580465,-93.348869 40.580465,-93.348873 40.580465,-93.348877 40.580465,-93.348881 40.580465,-93.348885 40.580465,-93.348888 40.580465,-93.348891 40.580465,-93.348894 40.580465,-93.348897 40.580465,-93.3489 40.580465,-93.348903 40.580465,-93.348906 40.580465,-93.348909 40.580465,-93.348911 40.580465,-93.348913 40.580465,-93.348915 40.580465,-93.348917 40.580465,-93.348919 40.580465,-93.348921 40.580465,-93.348923 40.580465,-93.348925 40.580465,-93.348927 40.580465,-93.348929 40.580465,-93.348931 40.580465,-93.348934 40.580465,-93.348936 40.580465,-93.348938 40.580465,-93.34894 40.580465,-93.348942 40.580465,-93.348944 40.580465,-93.348946 40.580465,-93.348948 40.580465,-93.34895 40.580465,-93.348952 40.580465,-93.348954 40.580465,-93.348956 40.580465,-93.348958 40.580465,-93.34896 40.580465,-93.34954 40.580457,-93.350133 40.580444,-93.350884 40.580439,-93.350962 40.58044,-93.351442 40.580446,-93.354122 40.580409,-93.354147 40.580409,-93.354148 40.580408,-93.35415 40.580408,-93.354154 40.580408,-93.354161 40.580408,-93.354171 40.580408,-93.354919 40.580398,-93.355606 40.580398,-93.356633 40.580398,-93.357339 40.580386,-93.357342 40.580386,-93.357344 40.580385,-93.357348 40.580385,-93.357356 40.580385,-93.357369 40.580385,-93.357382 40.580385,-93.358105 40.580372,-93.360776 40.58036,-93.36079 40.58036,-93.360807 40.58036,-93.361701 40.580348,-93.362626 40.580337,-93.363247 40.580329,-93.363268 40.580329,-93.363284 40.580329,-93.363288 40.580329,-93.36329 40.580329,-93.363291 40.580328,-93.363293 40.580328,-93.363302 40.580328,-93.363337 40.580328,-93.363358 40.580328,-93.363453 40.580327,-93.363731 40.580326,-93.363767 40.580326,-93.363795 40.580326,-93.364048 40.580325,-93.364173 40.580324,-93.365978 40.580316,-93.367935 40.580323,-93.36826 40.58032,-93.369436 40.58031,-93.369446 40.58031,-93.369448 40.580311,-93.36945 40.580311,-93.369453 40.580311,-93.369665 40.580331,-93.369667 40.580331,-93.369687 40.580333,-93.369697 40.580335,-93.369699 40.580336,-93.369707 40.580338,-93.36972 40.580341,-93.369736 40.580345,-93.36974 40.580346,-93.369742 40.580347,-93.369746 40.580348,-93.369761 40.580352,-93.369817 40.580366,-93.369917 40.580391,-93.369919 40.580392,-93.369924 40.580393,-93.369958 40.580401,-93.370069 40.580443,-93.370173 40.580501,-93.370181 40.580505,-93.370187 40.580508,-93.370202 40.580527,-93.370336 40.580437,-93.370519 40.580357,-93.370653 40.580327,-93.370858 40.580319,-93.371613 40.580303,-93.372707 40.5803,-93.372956 40.580304,-93.372991 40.580305,-93.373044 40.580306,-93.373066 40.580306,-93.373084 40.580306,-93.373677 40.580315,-93.37419 40.580328,-93.374194 40.580328,-93.37421 40.580328,-93.374257 40.580329,-93.374263 40.580329,-93.374265 40.580329,-93.374268 40.58033,-93.374271 40.58033,-93.37428 40.58033,-93.374286 40.58033,-93.374386 40.580333,-93.375499 40.58036,-93.376185 40.580369,-93.376542 40.580369,-93.376994 40.580333,-93.378484 40.580215,-93.378954 40.580199,-93.379774 40.580214,-93.380262 40.580237,-93.380422 40.580238,-93.381041 40.58023,-93.381312 40.580227,-93.385535 40.580194,-93.385601 40.580193,-93.385893 40.580194,-93.3861 40.580195,-93.386299 40.580223,-93.38657 40.580273,-93.38683 40.580331,-93.387026 40.580346,-93.387346 40.580321,-93.387798 40.580295,-93.388225 40.580289,-93.389032 40.580269,-93.389888 40.580262,-93.390791 40.58027,-93.392543 40.580255,-93.392935 40.580238,-93.393318 40.580204,-93.393716 40.580158,-93.39851 40.580105,-93.398817 40.580106,-93.401005 40.580115,-93.402822 40.580102,-93.404136 40.580104,-93.406476 40.580096,-93.408386 40.580111,-93.409229 40.580097,-93.41003 40.580091,-93.41022 40.58009,-93.410954 40.580084,-93.412247 40.580086,-93.412793 40.580096,-93.412847 40.580113,-93.413102 40.58011,-93.41457 40.580095,-93.416373 40.580092,-93.417482 40.580081,-93.419111 40.580075,-93.420341 40.580066,-93.42199 40.580029,-93.422546 40.580018,-93.424809 40.580017,-93.425074 40.580016,-93.42995 40.580001,-93.432014 40.579979,-93.435348 40.579967,-93.435786 40.579962,-93.4375 40.57994,-93.441647 40.579951,-93.455089 40.580013,-93.465297 40.580164,-93.466887 40.580072,-93.485195 40.580386,-93.488329 40.580384,-93.489046 40.580384,-93.496899 40.580381,-93.4978 40.580351,-93.497877 40.580348,-93.497915 40.580347,-93.500218 40.58027,-93.504584 40.580294,-93.509487 40.580322,-93.509685 40.580346,-93.509896 40.580351,-93.510053 40.580355,-93.510157 40.580358,-93.513635 40.580393,-93.515441 40.580411,-93.5155 40.580412,-93.516593 40.58042,-93.518876 40.58044,-93.520244 40.580439,-93.520549 40.580439,-93.520934 40.580439,-93.521154 40.58044,-93.521651 40.580442,-93.521919 40.580443,-93.5224 40.580445,-93.52274 40.580446,-93.522902 40.580447,-93.523242 40.580455,-93.523307 40.580457,-93.52332 40.580457,-93.523397 40.580459,-93.523939 40.580473,-93.524597 40.580489,-93.525227 40.580505,-93.525695 40.580503,-93.525792 40.580503,-93.526315 40.580502,-93.526709 40.580501,-93.526918 40.580499,-93.526969 40.580499,-93.527057 40.580498,-93.527116 40.580497,-93.527274 40.580496,-93.527331 40.580487,-93.527609 40.580448,-93.528177 40.580367,-93.532717 40.580335,-93.532803 40.580335,-93.53293 40.580335,-93.542464 40.580333,-93.547578 40.580407,-93.54762 40.580394,-93.54765 40.580391,-93.547848 40.580371,-93.547975 40.580368,-93.548615 40.58037,-93.552386 40.580315,-93.55339 40.5803,-93.553417 40.5803,-93.553557 40.580287,-93.553988 40.58028,-93.556899 40.580235,-93.558951 40.580112,-93.55917 40.580148,-93.560029 40.580158,-93.560796 40.580147,-93.565176 40.580087,-93.565657 40.580093,-93.56575 40.580094,-93.565809 40.580095,-93.565951 40.580097,-93.566181 40.580106,-93.566184 40.580106,-93.566186 40.580106,-93.566196 40.580106,-93.566406 40.580115,-93.566414 40.580115,-93.566423 40.580115,-93.566428 40.580115,-93.56643 40.580115,-93.566431 40.580116,-93.566433 40.580116,-93.566519 40.58012,-93.566524 40.58012,-93.566528 40.58012,-93.566975 40.580138,-93.567659 40.580111,-93.567662 40.580111,-93.567666 40.580111,-93.567814 40.580105,-93.567816 40.580105,-93.568517 40.580077,-93.568705 40.580072,-93.573431 40.579957,-93.57344 40.579957,-93.573452 40.579957,-93.573454 40.579957,-93.573456 40.579957,-93.573457 40.579956,-93.573461 40.579956,-93.573468 40.579956,-93.573522 40.579955,-93.573541 40.579955,-93.578183 40.579843,-93.580762 40.579793,-93.58339 40.579734,-93.583392 40.579734,-93.58596 40.579677,-93.586994 40.579662,-93.588063 40.579634,-93.589545 40.579618,-93.591164 40.579568,-93.596292 40.579455,-93.596295 40.579455,-93.596693 40.579446,-93.59713 40.579444,-93.597133 40.579443,-93.597135 40.579443,-93.597137 40.579443,-93.597139 40.579443,-93.597141 40.579443,-93.597144 40.579443,-93.597377 40.579431,-93.597381 40.579431,-93.610511 40.57918,-93.613997 40.579132,-93.616406 40.579099,-93.62316 40.579005,-93.623317 40.579001,-93.625221 40.578812,-93.631012 40.578724,-93.637169 40.578631,-93.64662 40.578493,-93.646992 40.578488,-93.647003 40.578488,-93.656211 40.578352,-93.659272 40.57833,-93.660353 40.578376,-93.661913 40.578354,-93.662538 40.57834,-93.66254 40.57834,-93.662542 40.57834,-93.662546 40.57834,-93.662551 40.57834,-93.662556 40.57834,-93.66256 40.57834,-93.662561 40.578339,-93.662563 40.578339,-93.662786 40.578334,-93.663027 40.578329,-93.666912 40.578242,-93.666985 40.57824,-93.668825 40.578241,-93.668845 40.578241,-93.669106 40.578232,-93.670629 40.578177,-93.671249 40.578173,-93.675748 40.57815,-93.677155 40.578088,-93.677407 40.578095,-93.677567 40.578099,-93.682522 40.578037,-93.682542 40.578037,-93.682567 40.578037,-93.682629 40.578036,-93.682673 40.578035,-93.682897 40.578032,-93.684023 40.578014,-93.684029 40.578014,-93.684031 40.578014,-93.684033 40.578013,-93.684036 40.578013,-93.684047 40.578013,-93.684059 40.578013,-93.684133 40.578012,-93.68422 40.578011,-93.686819 40.577971,-93.690051 40.577899,-93.690216 40.577902,-93.69035 40.5779,-93.694367 40.577841,-93.694461 40.57784,-93.699638 40.577798,-93.699862 40.577796,-93.720223 40.577629,-93.722386 40.577641,-93.72239 40.577641,-93.7224 40.577641,-93.722443 40.577641,-93.725671 40.577589,-93.725884 40.577586,-93.725893 40.577586,-93.725908 40.577586,-93.725923 40.577586,-93.725928 40.577586,-93.725929 40.577585,-93.725931 40.577585,-93.725933 40.577585,-93.725942 40.577585,-93.727214 40.577565,-93.728144 40.57755,-93.728355 40.577547,-93.737259 40.577542,-93.738236 40.577537,-93.741156 40.577525,-93.74182 40.577522,-93.742929 40.577516,-93.742978 40.577489,-93.742996 40.577477,-93.74306 40.577456,-93.74316 40.577448,-93.74324 40.577453,-93.743242 40.577453,-93.743244 40.577453,-93.743247 40.577453,-93.743811 40.577483,-93.743817 40.577483,-93.744285 40.577483,-93.745101 40.577507,-93.745215 40.57751,-93.745669 40.577524,-93.745674 40.577524,-93.74568 40.577524,-93.746218 40.57754,-93.747848 40.577567,-93.747866 40.577567,-93.749506 40.577593,-93.749515 40.577593,-93.750219 40.577605,-93.750225 40.577605,-93.750671 40.577612,-93.751901 40.577606,-93.751924 40.577606,-93.753676 40.577598,-93.757507 40.57758,-93.761462 40.577536,-93.76348 40.577519,-93.763485 40.577519,-93.765968 40.577498,-93.766777 40.577481,-93.76698 40.577489,-93.769341 40.577497,-93.770336 40.577507,-93.770348 40.577507,-93.770358 40.577507,-93.770366 40.577507,-93.770373 40.577507,-93.770378 40.577507,-93.770382 40.577507,-93.770386 40.577507,-93.770389 40.577507,-93.770391 40.577507,-93.770393 40.577507,-93.770395 40.577507,-93.770397 40.577507,-93.770399 40.577507,-93.770401 40.577507,-93.770403 40.577507,-93.774344 40.577548,-93.779954 40.577459,-93.780952 40.577437,-93.781204 40.577432,-93.781219 40.577432,-93.781242 40.577432,-93.781287 40.577431,-93.781391 40.577429,-93.781539 40.577426,-93.783462 40.57739,-93.783652 40.577367,-93.783817 40.577348,-93.783853 40.577344,-93.784062 40.577346,-93.784422 40.577349,-93.784451 40.577349,-93.784503 40.577349,-93.784606 40.57735,-93.784845 40.577352,-93.785302 40.577356,-93.786869 40.577369,-93.789408 40.577391,-93.801249 40.577338,-93.801374 40.577338,-93.802679 40.577334,-93.807953 40.577319,-93.815485 40.577277,-93.815718 40.577263,-93.815749 40.577237,-93.815805 40.577164,-93.816054 40.577124,-93.816667 40.57711,-93.817605 40.577104,-93.818666 40.577081,-93.818857 40.577096,-93.818861 40.577096,-93.819416 40.57714,-93.824774 40.577046,-93.831056 40.576936,-93.84093 40.576791,-93.843248 40.576758,-93.849243 40.576672,-93.853906 40.576605,-93.854319 40.576589,-93.855644 40.576589,-93.856105 40.576577,-93.856121 40.576577,-93.858219 40.576522,-93.859169 40.576505,-93.859461 40.576501,-93.860557 40.576486,-93.860919 40.576476,-93.860924 40.576476,-93.861316 40.576465,-93.861329 40.576465,-93.863007 40.57642,-93.863037 40.57642,-93.866407 40.576375,-93.868983 40.576356,-93.870646 40.576334,-93.871226 40.576332,-93.873322 40.576323,-93.875434 40.576298,-93.877604 40.576279,-93.877648 40.576279,-93.877923 40.576274,-93.880198 40.576233,-93.881885 40.57622,-93.88316 40.576191,-93.883985 40.576156,-93.885285 40.576137,-93.886648 40.576126,-93.887848 40.576088,-93.88851 40.576055,-93.889023 40.576022,-93.889048 40.576021,-93.889054 40.576021,-93.890535 40.575991,-93.89166 40.575983,-93.892448 40.575967,-93.893148 40.575933,-93.894155 40.575919,-93.894189 40.575919,-93.894856 40.57591,-93.895587 40.575905,-93.897491 40.575876,-93.897768 40.575865,-93.898175 40.575849,-93.898361 40.575827,-93.898437 40.575818,-93.899348 40.575799,-93.899542 40.575838,-93.899905 40.575845,-93.900855 40.575815,-93.900857 40.575815,-93.90128 40.575806,-93.902905 40.575793,-93.904643 40.57576,-93.905956 40.57574,-93.907992 40.575717,-93.908025 40.575717,-93.910894 40.575682,-93.911299 40.575671,-93.911336 40.57567,-93.913719 40.575603,-93.914179 40.575596,-93.914227 40.575595,-93.915319 40.575581,-93.915644 40.575597,-93.916132 40.575584,-93.916656 40.575551,-93.917069 40.575538,-93.919456 40.575481,-93.919891 40.575482,-93.919919 40.575482,-93.920482 40.575483,-93.921844 40.575472,-93.924232 40.575424,-93.925288 40.575395,-93.926232 40.57537,-93.926982 40.575354,-93.927521 40.57535,-93.927835 40.575347,-93.928382 40.575343,-93.929095 40.575347,-93.929695 40.575313,-93.930407 40.575298,-93.930826 40.575295,-93.931195 40.575292,-93.93392 40.57526,-93.934476 40.57525,-93.934489 40.57525,-93.934845 40.575243,-93.935445 40.575238,-93.935758 40.575227,-93.935769 40.575227,-93.936028 40.575219,-93.936111 40.575216,-93.936545 40.575201,-93.937106 40.575209,-93.937383 40.575213,-93.937644 40.575207,-93.93765 40.575207,-93.937675 40.575207,-93.93778 40.575205,-93.937989 40.575201,-93.938622 40.57519,-93.938631 40.57519,-93.938838 40.575186,-93.938954 40.575184,-93.938997 40.575183,-93.939024 40.575183,-93.939036 40.575183,-93.939207 40.57518,-93.939235 40.57518,-93.9395 40.575175,-93.939846 40.575186,-93.939856 40.575186,-93.939967 40.57519,-93.9404 40.575168,-93.940987 40.575164,-93.941425 40.57515,-93.942512 40.575142,-93.943 40.575138,-93.943562 40.575114,-93.9452 40.575081,-93.946087 40.575074,-93.946112 40.575074,-93.948088 40.575048,-93.949613 40.575017,-93.949643 40.575017,-93.950438 40.575019,-93.951688 40.57499,-93.954425 40.574949,-93.954551 40.574947,-93.955901 40.574936,-93.957689 40.574912,-93.958789 40.574893,-93.960989 40.574865,-93.962314 40.574835,-93.963743 40.574823,-93.963777 40.574823,-93.964827 40.574795,-93.966002 40.574785,-93.967965 40.574759,-93.970478 40.574718,-93.970515 40.574717,-93.972052 40.574687,-93.97279 40.574681,-93.973815 40.574663,-93.974516 40.57464,-93.974528 40.57464,-93.974621 40.574637,-93.974653 40.574637,-93.97519 40.574632,-93.97533 40.574628,-93.975338 40.574628,-93.975505 40.574623,-93.976115 40.574605,-93.976502 40.574602,-93.976667 40.574633,-93.97677 40.574635,-93.983283 40.574461,-93.987095 40.574364,-93.98743 40.57436,-94.000197 40.574335,-94.001512 40.574229,-94.001546 40.574226,-94.003323 40.574096,-94.011799 40.573961,-94.013902 40.573927,-94.014513 40.573924,-94.014629 40.573923,-94.014743 40.573922,-94.014847 40.573921,-94.015082 40.573918,-94.015492 40.573914))"; + + + diff --git a/mgist-postgis/liblwgeom/cunit/cu_geos.c b/mgist-postgis/liblwgeom/cunit/cu_geos.c new file mode 100644 index 0000000..ed648dd --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_geos.c @@ -0,0 +1,208 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright 2011 Sandro Santilli + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "lwgeom_geos.h" +#include "cu_tester.h" + +#include "liblwgeom_internal.h" + +static void +test_geos_noop(void) +{ + size_t i; + char *in_ewkt; + char *out_ewkt; + LWGEOM *geom_in; + LWGEOM *geom_out; + + char *ewkt[] = { + "POINT(0 0.2)", + "LINESTRING(-1 -1,-1 2.5,2 2,2 -1)", + "MULTIPOINT(0.9 0.9,0.9 0.9,0.9 0.9,0.9 0.9,0.9 0.9,0.9 0.9)", + "SRID=1;MULTILINESTRING((-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1))", + "SRID=1;MULTILINESTRING((-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1))", + "POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0))", + "SRID=4326;POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0))", + "SRID=4326;POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5))", + "SRID=100000;POLYGON((-1 -1 3,-1 2.5 3,2 2 3,2 -1 3,-1 -1 3),(0 0 3,0 1 3,1 1 3,1 0 3,0 0 3),(-0.5 -0.5 3,-0.5 -0.4 3,-0.4 -0.4 3,-0.4 -0.5 3,-0.5 -0.5 3))", + "SRID=4326;MULTIPOLYGON(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)),((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)))", + "SRID=4326;GEOMETRYCOLLECTION(POINT(0 1),POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0)),MULTIPOLYGON(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5))))", + }; + + for (i = 0; i < (sizeof ewkt / sizeof(char *)); i++) + { + in_ewkt = ewkt[i]; + geom_in = lwgeom_from_wkt(in_ewkt, LW_PARSER_CHECK_NONE); + geom_out = lwgeom_geos_noop(geom_in); + CU_ASSERT_PTR_NOT_NULL_FATAL(geom_out); + out_ewkt = lwgeom_to_ewkt(geom_out); + ASSERT_STRING_EQUAL(out_ewkt, in_ewkt); + lwfree(out_ewkt); + lwgeom_free(geom_out); + lwgeom_free(geom_in); + } + + /* TINs become collections of Polygons */ + in_ewkt = "TIN(((0 0, 1 1, 2 2, 0 0)), ((0 0, 1 1, 2 2, 0 0)))"; + geom_in = lwgeom_from_wkt(in_ewkt, LW_PARSER_CHECK_NONE); + geom_out = lwgeom_geos_noop(geom_in); + out_ewkt = lwgeom_to_ewkt(geom_out); + ASSERT_STRING_EQUAL(out_ewkt, "GEOMETRYCOLLECTION(POLYGON((0 0,1 1,2 2,0 0)),POLYGON((0 0,1 1,2 2,0 0)))"); + lwfree(out_ewkt); + lwgeom_free(geom_in); + lwgeom_free(geom_out); + + /* Empties disappear */ + in_ewkt = "GEOMETRYCOLLECTION( LINESTRING (1 1, 2 2), POINT EMPTY, TRIANGLE ((0 0, 1 0, 1 1, 0 0)) )"; + geom_in = lwgeom_from_wkt(in_ewkt, LW_PARSER_CHECK_NONE); + geom_out = lwgeom_geos_noop(geom_in); + out_ewkt = lwgeom_to_ewkt(geom_out); + ASSERT_STRING_EQUAL(out_ewkt, "GEOMETRYCOLLECTION(LINESTRING(1 1,2 2),POINT EMPTY,POLYGON((0 0,1 0,1 1,0 0)))"); + lwfree(out_ewkt); + lwgeom_free(geom_in); + lwgeom_free(geom_out); +} + +static void test_geos_linemerge(void) +{ + char *ewkt; + char *out_ewkt; + LWGEOM *geom1; + LWGEOM *geom2; + + ewkt = "MULTILINESTRING((0 0, 0 100),(0 -5, 0 0))"; + geom1 = lwgeom_from_wkt(ewkt, LW_PARSER_CHECK_NONE); + geom2 = lwgeom_linemerge(geom1); + out_ewkt = lwgeom_to_ewkt((LWGEOM*)geom2); + ASSERT_STRING_EQUAL(out_ewkt, "LINESTRING(0 -5,0 0,0 100)"); + lwfree(out_ewkt); + lwgeom_free(geom1); + lwgeom_free(geom2); + + ewkt = "MULTILINESTRING EMPTY"; + geom1 = lwgeom_from_wkt(ewkt, LW_PARSER_CHECK_NONE); + geom2 = lwgeom_linemerge(geom1); + out_ewkt = lwgeom_to_ewkt((LWGEOM*)geom2); + ASSERT_STRING_EQUAL(out_ewkt, "MULTILINESTRING EMPTY"); + lwfree(out_ewkt); + lwgeom_free(geom1); + lwgeom_free(geom2); +} + +static void +test_geos_offsetcurve(void) +{ + char* ewkt; + char* out_ewkt; + LWGEOM* geom1; + LWGEOM* geom2; + + ewkt = "MULTILINESTRING((-10 0, -10 100), (0 -5, 0 0))"; + geom1 = lwgeom_from_wkt(ewkt, LW_PARSER_CHECK_NONE); + geom2 = lwgeom_offsetcurve(geom1, 2, 10, 1, 1); + out_ewkt = lwgeom_to_ewkt((LWGEOM*)geom2); + ASSERT_STRING_EQUAL(out_ewkt, "MULTILINESTRING((-12 0,-12 100),(-2 -5,-2 0))"); + lwfree(out_ewkt); + lwgeom_free(geom1); + lwgeom_free(geom2); +} + +static void +test_geos_offsetcurve_crash(void) +{ + // Test for Trac #4143. The specific output or lack of output is not important, + // we're just testing that we don't crash. + LWGEOM* in = lwgeom_from_wkt("LINESTRING(362194.505 5649993.044,362197.451 5649994.125,362194.624 5650001.876,362189.684 5650000.114,362192.542 5649992.324,362194.505 5649993.044)", LW_PARSER_CHECK_NONE); + LWGEOM* out = lwgeom_offsetcurve(in, -0.045, 8, 2, 5.0); + + lwgeom_free(in); + if (out) { + lwgeom_free(out); + } +} + +static void +test_geos_makevalid(void) +{ + uint8_t* wkb; + char* out_ewkt; + LWGEOM* geom1; + LWGEOM* geom2; + LWGEOM* geom3; + + wkb = (uint8_t*) "\001\003\000\000\000\001\000\000\000\011\000\000\000b\020X9 }\366@7\211A\340\235I\034A\316\326t18}\366@\306g\347\323\230I\034Ay\351&18}\366@\331\316\367\323\230I\034A\372~j\274\370}\366@\315\314\314LpI\034A\343\245\233\304R}\366@R\270\036\005?I\034A\315\314\314\314Z~\366@\343\245\233\304\007I\034A\004V\016-\242}\366@\252\361\322M\323H\034A\351&1\010\306{\366@H\341z\0247I\034Ab\020X9 }\366@7\211A\340\235I\034A"; + geom1 = lwgeom_from_wkb(wkb, 157, LW_PARSER_CHECK_NONE); + geom2 = lwgeom_make_valid(geom1); + geom3 = lwgeom_normalize(geom2); //so GEOS 3.9 and 3.10 agree + out_ewkt = lwgeom_to_ewkt((LWGEOM*)geom3); + +#if POSTGIS_GEOS_VERSION < 30900 + ASSERT_STRING_EQUAL( + out_ewkt, + "GEOMETRYCOLLECTION(POLYGON((92092.377 463437.77,92114.014 463463.469,92115.51207431706 463462.206937429,92115.512 463462.207,92127.546 463452.075,92117.173 463439.755,92133.675 463425.942,92122.136 463412.82600000006,92092.377 463437.77)),MULTIPOINT(92122.136 463412.826,92115.51207431706 463462.2069374289))"); +#else + ASSERT_STRING_EQUAL( + out_ewkt, + "POLYGON((92092.377 463437.77,92114.014 463463.469,92115.512 463462.207,92115.51207431706 463462.2069374289,92127.546 463452.075,92117.173 463439.755,92133.675 463425.942,92122.136 463412.826,92092.377 463437.77))"); +#endif + lwfree(out_ewkt); + lwgeom_free(geom1); + lwgeom_free(geom2); + lwgeom_free(geom3); +} + + +static void test_geos_subdivide(void) +{ + char *ewkt = "LINESTRING(0 0, 10 10)"; + char *out_ewkt; + LWGEOM *geom1 = lwgeom_from_wkt(ewkt, LW_PARSER_CHECK_NONE); + /* segmentize as geography to generate a non-simple curve */ + LWGEOM *geom2 = lwgeom_segmentize_sphere(geom1, 0.002); + + LWCOLLECTION *geom3 = lwgeom_subdivide(geom2, 80); + out_ewkt = lwgeom_to_ewkt((LWGEOM*)geom3); + // printf("\n--------\n%s\n--------\n", out_ewkt); + CU_ASSERT_EQUAL(2, geom3->ngeoms); + lwfree(out_ewkt); + lwcollection_free(geom3); + + geom3 = lwgeom_subdivide(geom2, 20); + out_ewkt = lwgeom_to_ewkt((LWGEOM*)geom3); + // printf("\n--------\n%s\n--------\n", out_ewkt); + CU_ASSERT_EQUAL(8, geom3->ngeoms); + lwfree(out_ewkt); + lwcollection_free(geom3); + + lwgeom_free(geom2); + lwgeom_free(geom1); +} + +/* +** Used by test harness to register the tests in this file. +*/ +void geos_suite_setup(void); +void geos_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("geos", NULL, NULL); + PG_ADD_TEST(suite, test_geos_noop); + PG_ADD_TEST(suite, test_geos_subdivide); + PG_ADD_TEST(suite, test_geos_linemerge); + PG_ADD_TEST(suite, test_geos_offsetcurve); + PG_ADD_TEST(suite, test_geos_offsetcurve_crash); + PG_ADD_TEST(suite, test_geos_makevalid); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_geos_cluster.c b/mgist-postgis/liblwgeom/cunit/cu_geos_cluster.c new file mode 100644 index 0000000..78d8c35 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_geos_cluster.c @@ -0,0 +1,384 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright 2015 Daniel Baston + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include + +#include "CUnit/Basic.h" + +#include "../lwgeom_log.h" +#include "../lwgeom_geos.h" +#include "cu_tester.h" + +static void assert_all_results_found(LWGEOM** results, size_t num_outputs, LWGEOM** expected, size_t num_expected_outputs) +{ + size_t i, j; + + char found_equal = 0; + for (i = 0; i < num_outputs; i++) + { + for (j = 0; j < num_expected_outputs; j++) + { + if (lwgeom_same(results[i], expected[j])) + { + found_equal = 1; + break; + } + } + + CU_ASSERT_TRUE(found_equal); + } +} + +static GEOSGeometry** LWGEOMARRAY2GEOS(LWGEOM** lw_array, size_t num_geoms) +{ + size_t i; + GEOSGeometry** geos_geoms = lwalloc(num_geoms * sizeof(GEOSGeometry*)); + + for (i = 0; i < num_geoms; i++) + { + geos_geoms[i] = LWGEOM2GEOS(lw_array[i], 0); + } + + return geos_geoms; +} + +static LWGEOM** GEOSARRAY2LWGEOM(GEOSGeometry** geos_array, size_t num_geoms) +{ + size_t i; + LWGEOM** lw_geoms = lwalloc(num_geoms * sizeof(LWGEOM*)); + + for (i = 0; i < num_geoms; i++) + { + lw_geoms[i] = GEOS2LWGEOM(geos_array[i], 0); + } + + return lw_geoms; +} + +static LWGEOM** WKTARRAY2LWGEOM(char** wkt_array, size_t num_geoms) +{ + size_t i; + + LWGEOM** lw_geoms = lwalloc(num_geoms * sizeof(LWGEOM*)); + + for (i = 0; i < num_geoms; i++) + { + lw_geoms[i] = lwgeom_from_wkt(wkt_array[i], LW_PARSER_CHECK_NONE); + } + + return lw_geoms; +} + +static void perform_cluster_intersecting_test(char** wkt_inputs, uint32_t num_inputs, char** wkt_outputs, uint32_t num_outputs) +{ + GEOSGeometry** geos_results; + LWGEOM** lw_results; + uint32_t num_clusters; + + LWGEOM** expected_outputs = WKTARRAY2LWGEOM(wkt_outputs, num_outputs); + LWGEOM** lw_inputs = WKTARRAY2LWGEOM(wkt_inputs, num_inputs); + GEOSGeometry** geos_inputs = LWGEOMARRAY2GEOS(lw_inputs, num_inputs); + + cluster_intersecting(geos_inputs, num_inputs, &geos_results, &num_clusters); + CU_ASSERT_EQUAL(num_outputs, num_clusters); + + lw_results = GEOSARRAY2LWGEOM(geos_results, num_clusters); + + assert_all_results_found(lw_results, num_clusters, expected_outputs, num_outputs); + + /* Cleanup */ + uint32_t i; + for(i = 0; i < num_clusters; i++) + { + GEOSGeom_destroy(geos_results[i]); + } + lwfree(geos_inputs); + lwfree(geos_results); + + for(i = 0; i < num_outputs; i++) + { + lwgeom_free(expected_outputs[i]); + lwgeom_free(lw_results[i]); + } + lwfree(expected_outputs); + lwfree(lw_results); + + for(i = 0; i < num_inputs; i++) + { + lwgeom_free(lw_inputs[i]); + } + lwfree(lw_inputs); +} + +static void perform_cluster_within_distance_test(double tolerance, char** wkt_inputs, uint32_t num_inputs, char** wkt_outputs, uint32_t num_outputs) +{ + LWGEOM** lw_results; + uint32_t num_clusters; + + LWGEOM** expected_outputs = WKTARRAY2LWGEOM(wkt_outputs, num_outputs); + LWGEOM** lw_inputs = WKTARRAY2LWGEOM(wkt_inputs, num_inputs); + + cluster_within_distance(lw_inputs, num_inputs, tolerance, &lw_results, &num_clusters); + + CU_ASSERT_EQUAL(num_outputs, num_clusters); + + assert_all_results_found(lw_results, num_clusters, expected_outputs, num_outputs); + + /* Cleanup */ + uint32_t i; + for(i = 0; i < num_outputs; i++) + { + lwgeom_free(expected_outputs[i]); + lwgeom_free(lw_results[i]); + } + lwfree(lw_results); + lwfree(expected_outputs); + lwfree(lw_inputs); +} + +static int init_geos_cluster_suite(void) +{ + initGEOS(lwnotice, lwgeom_geos_error); + return 0; +} + +static int clean_geos_cluster_suite(void) +{ + finishGEOS(); + return 0; +} + +static void basic_test(void) +{ + char* a = "LINESTRING (0 0, 1 1)"; + char* b = "LINESTRING (1 1, 2 2)"; + char* c = "LINESTRING (5 5, 6 6)"; + + char* wkt_inputs_a[] = {a, b, c}; + char* wkt_inputs_b[] = {b, c, a}; + char* wkt_inputs_c[] = {c, a, b}; + + char* expected_outputs_a[] = { "GEOMETRYCOLLECTION(LINESTRING (0 0, 1 1), LINESTRING (1 1, 2 2))", + "GEOMETRYCOLLECTION(LINESTRING (5 5, 6 6))" + }; + + char* expected_outputs_b[] = { "GEOMETRYCOLLECTION(LINESTRING (1 1, 2 2), LINESTRING (0 0, 1 1))", + "GEOMETRYCOLLECTION(LINESTRING (5 5, 6 6))" + }; + + char* expected_outputs_c[] = { "GEOMETRYCOLLECTION(LINESTRING (0 0, 1 1), LINESTRING (1 1, 2 2))", + "GEOMETRYCOLLECTION(LINESTRING (5 5, 6 6))" + }; + + perform_cluster_intersecting_test(wkt_inputs_a, 3, expected_outputs_a, 2); + perform_cluster_intersecting_test(wkt_inputs_b, 3, expected_outputs_b, 2); + perform_cluster_intersecting_test(wkt_inputs_c, 3, expected_outputs_c, 2); + + perform_cluster_within_distance_test(0, wkt_inputs_a, 3, expected_outputs_a, 2); + perform_cluster_within_distance_test(0, wkt_inputs_b, 3, expected_outputs_b, 2); + perform_cluster_within_distance_test(0, wkt_inputs_c, 3, expected_outputs_c, 2); +} + +static void basic_distance_test(void) +{ + char* a = "LINESTRING (0 0, 1 1)"; + char* b = "LINESTRING (1 1, 2 2)"; + char* c = "LINESTRING (5 5, 6 6)"; + + char* wkt_inputs[] = {a, b, c}; + + char* expected_outputs_all[] = {"GEOMETRYCOLLECTION(LINESTRING(0 0, 1 1), LINESTRING(1 1, 2 2), LINESTRING(5 5, 6 6))"}; + char* expected_outputs_partial[] = {"GEOMETRYCOLLECTION(LINESTRING(0 0, 1 1), LINESTRING(1 1, 2 2))", + "GEOMETRYCOLLECTION(LINESTRING(5 5, 6 6))" + }; + + perform_cluster_within_distance_test(0, wkt_inputs, 3, expected_outputs_partial, 2); + perform_cluster_within_distance_test(sqrt(18) - 0.0000001, wkt_inputs, 3, expected_outputs_partial, 2); + perform_cluster_within_distance_test(sqrt(18) + 0.0000001, wkt_inputs, 3, expected_outputs_all, 1); +} + +static void nonsequential_test(void) +{ + char* wkt_inputs[] = { "LINESTRING (0 0, 1 1)", + "LINESTRING (1 1, 2 2)", + "LINESTRING (5 5, 6 6)", + "LINESTRING (5 5, 4 4)", + "LINESTRING (3 3, 2 2)", + "LINESTRING (3 3, 4 4)" + }; + + char* expected_outputs[] = { "GEOMETRYCOLLECTION (LINESTRING (0 0, 1 1), LINESTRING (1 1, 2 2), LINESTRING (5 5, 6 6), LINESTRING (5 5, 4 4), LINESTRING (3 3, 2 2), LINESTRING (3 3, 4 4))" }; + + perform_cluster_intersecting_test(wkt_inputs, 6, expected_outputs, 1); + perform_cluster_within_distance_test(0, wkt_inputs, 6, expected_outputs, 1); +} + +static void single_input_test(void) +{ + char* wkt_inputs[] = { "POINT (0 0)" }; + char* expected_outputs[] = { "GEOMETRYCOLLECTION (POINT (0 0))" }; + + perform_cluster_intersecting_test(wkt_inputs, 1, expected_outputs, 1); + perform_cluster_within_distance_test(1, wkt_inputs, 1, expected_outputs, 1); +} + +static void empty_inputs_test(void) +{ + char* wkt_inputs[] = { "POLYGON EMPTY", "LINESTRING EMPTY"}; + char* expected_outputs[] = { "GEOMETRYCOLLECTION( LINESTRING EMPTY )", "GEOMETRYCOLLECTION( POLYGON EMPTY )" }; + + perform_cluster_intersecting_test(wkt_inputs, 2, expected_outputs, 2); + perform_cluster_within_distance_test(1, wkt_inputs, 2, expected_outputs, 2); +} + +static void multipoint_test(void) +{ + /* See #3433 */ + char* wkt_inputs_mp[] = { "MULTIPOINT ((0 0), (0 1))", "POINT (0 0)"}; + char* expected_outputs_mp[] = { "GEOMETRYCOLLECTION(MULTIPOINT ((0 0), (0 1)), POINT (0 0))"}; + + char* wkt_inputs_gc[] = { "GEOMETRYCOLLECTION (POINT (0 0), POINT (0 1))", "POINT (0 0)"}; + char* expected_outputs_gc[] = { "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION (POINT (0 0), POINT (0 1)), POINT (0 0))"}; + + char* wkt_inputs_pt[] = { "POINT (3 3)", "POINT (3 3)"}; + char* expected_outputs_pt[] = { "GEOMETRYCOLLECTION(POINT (3 3), POINT (3 3))"}; + + perform_cluster_intersecting_test(wkt_inputs_mp, 2, expected_outputs_mp, 1); + perform_cluster_intersecting_test(wkt_inputs_gc, 2, expected_outputs_gc, 1); + perform_cluster_intersecting_test(wkt_inputs_pt, 2, expected_outputs_pt, 1); +} + +struct dbscan_test_info { + double eps; + uint32_t min_points; + uint32_t num_geoms; + char** wkt_inputs; + uint32_t* expected_ids; + int* expected_in_cluster; +}; + +static void do_dbscan_test(struct dbscan_test_info test) +{ + LWGEOM** geoms = WKTARRAY2LWGEOM(test.wkt_inputs, test.num_geoms); + UNIONFIND* uf = UF_create(test.num_geoms); + uint32_t* ids; + char* in_a_cluster; + uint32_t i; + + union_dbscan(geoms, test.num_geoms, uf, test.eps, test.min_points, &in_a_cluster); + ids = UF_get_collapsed_cluster_ids(uf, in_a_cluster); + + for (i = 0; i < test.num_geoms; i++) + { + ASSERT_INT_EQUAL(in_a_cluster[i], test.expected_in_cluster[i]); + if (in_a_cluster[i]) + ASSERT_INT_EQUAL(ids[i], test.expected_ids[i]); + } + + UF_destroy(uf); + for (i = 0; i < test.num_geoms; i++) + { + lwgeom_free(geoms[i]); + } + lwfree(geoms); + lwfree(in_a_cluster); + lwfree(ids); +} + +static void dbscan_test(void) +{ + struct dbscan_test_info test; + char* wkt_inputs[] = { "POINT (0 0)", "POINT (-1 0)", "POINT (-1 -0.1)", "POINT (-1 0.1)", + "POINT (1 0)", + "POINT (2 0)", "POINT (3 0)", "POINT ( 3 -0.1)", "POINT ( 3 0.1)" }; + /* Although POINT (1 0) and POINT (2 0) are within eps distance of each other, + * they do not connect the two clusters because POINT (1 0) is not a core point. + * See #3572 + */ + test.eps = 1.01; + test.min_points = 5; + uint32_t expected_ids[] = { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1 }; + int expected_in_cluster[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + test.num_geoms = sizeof(wkt_inputs) / sizeof(char*); + + test.expected_ids = expected_ids; + test.wkt_inputs = wkt_inputs; + test.expected_in_cluster = expected_in_cluster; + do_dbscan_test(test); +} + +static void dbscan_test_3612a(void) +{ + struct dbscan_test_info test; + char* wkt_inputs[] = { "POINT (1 1)" }; + + test.eps = 0.0; + test.min_points = 5; + uint32_t expected_ids[] = { rand() }; + int expected_in_cluster[] = { 0 }; + test.num_geoms = sizeof(wkt_inputs) / sizeof(char*); + + test.expected_ids = expected_ids; + test.expected_in_cluster = expected_in_cluster; + test.wkt_inputs = wkt_inputs; + do_dbscan_test(test); +} + +static void dbscan_test_3612b(void) +{ + struct dbscan_test_info test; + char* wkt_inputs[] = { "POINT (1 1)" }; + + test.eps = 0.0; + test.min_points = 1; + uint32_t expected_ids[] = { 0 }; + int expected_in_cluster[] = { 1 }; + test.num_geoms = sizeof(wkt_inputs) / sizeof(char*); + + test.expected_ids = expected_ids; + test.expected_in_cluster = expected_in_cluster; + test.wkt_inputs = wkt_inputs; + do_dbscan_test(test); +} + +static void dbscan_test_3612c(void) +{ + struct dbscan_test_info test; + char* wkt_inputs[] = { "POLYGONM((-71.1319 42.2503 1,-71.132 42.2502 3,-71.1323 42.2504 -2,-71.1322 42.2505 1,-71.1319 42.2503 0))", + "POLYGONM((-71.1319 42.2512 0,-71.1318 42.2511 20,-71.1317 42.2511 -20,-71.1317 42.251 5,-71.1317 42.2509 4,-71.132 42.2511 6,-71.1319 42.2512 30))" }; + test.eps = 20.1; + test.min_points = 5; + uint32_t expected_ids[] = { rand(), rand() }; + int expected_in_cluster[] = { 0, 0 }; + test.num_geoms = sizeof(wkt_inputs) / sizeof(char*); + + test.expected_ids = expected_ids; + test.expected_in_cluster = expected_in_cluster; + test.wkt_inputs = wkt_inputs; + do_dbscan_test(test); +} + +void geos_cluster_suite_setup(void); +void geos_cluster_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("clustering", init_geos_cluster_suite, clean_geos_cluster_suite); + PG_ADD_TEST(suite, basic_test); + PG_ADD_TEST(suite, nonsequential_test); + PG_ADD_TEST(suite, basic_distance_test); + PG_ADD_TEST(suite, single_input_test); + PG_ADD_TEST(suite, empty_inputs_test); + PG_ADD_TEST(suite, multipoint_test); + PG_ADD_TEST(suite, dbscan_test); + PG_ADD_TEST(suite, dbscan_test_3612a); + PG_ADD_TEST(suite, dbscan_test_3612b); + PG_ADD_TEST(suite, dbscan_test_3612c); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_gserialized1.c b/mgist-postgis/liblwgeom/cunit/cu_gserialized1.c new file mode 100644 index 0000000..66cc0a5 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_gserialized1.c @@ -0,0 +1,1289 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2009 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "gserialized1.c" /* for gserialized_peek_gbox_p */ +#include "cu_tester.h" + +static void test_typmod_macros(void) +{ + int32_t typmod = 0; + int32_t srid = 4326; + int type = 6; + int z = 1; + int rv; + + TYPMOD_SET_SRID(typmod,srid); + rv = TYPMOD_GET_SRID(typmod); + CU_ASSERT_EQUAL(rv, srid); + + srid = -5005; + TYPMOD_SET_SRID(typmod,srid); + rv = TYPMOD_GET_SRID(typmod); + CU_ASSERT_EQUAL(rv, srid); + + srid = 999999; + TYPMOD_SET_SRID(typmod,srid); + rv = TYPMOD_GET_SRID(typmod); + CU_ASSERT_EQUAL(rv, srid); + + srid = -999999; + TYPMOD_SET_SRID(typmod,srid); + rv = TYPMOD_GET_SRID(typmod); + CU_ASSERT_EQUAL(rv, srid); + + srid = SRID_UNKNOWN; + TYPMOD_SET_SRID(typmod,srid); + rv = TYPMOD_GET_SRID(typmod); + CU_ASSERT_EQUAL(rv, srid); + + srid = 0; + TYPMOD_SET_SRID(typmod,srid); + rv = TYPMOD_GET_SRID(typmod); + CU_ASSERT_EQUAL(rv, srid); + + srid = 1; + TYPMOD_SET_SRID(typmod,srid); + rv = TYPMOD_GET_SRID(typmod); + CU_ASSERT_EQUAL(rv, srid); + + TYPMOD_SET_TYPE(typmod,type); + rv = TYPMOD_GET_TYPE(typmod); + CU_ASSERT_EQUAL(rv,type); + + TYPMOD_SET_Z(typmod); + rv = TYPMOD_GET_Z(typmod); + CU_ASSERT_EQUAL(rv,z); + + rv = TYPMOD_GET_M(typmod); + CU_ASSERT_EQUAL(rv,0); + +} + +static void test_flags_macros(void) +{ + uint8_t flags = 0; + + CU_ASSERT_EQUAL(0, G1FLAGS_GET_Z(flags)); + G1FLAGS_SET_Z(flags, 1); + CU_ASSERT_EQUAL(1, G1FLAGS_GET_Z(flags)); + G1FLAGS_SET_Z(flags, 0); + CU_ASSERT_EQUAL(0, G1FLAGS_GET_Z(flags)); + CU_ASSERT_EQUAL(0, G1FLAGS_GET_BBOX(flags)); + + CU_ASSERT_EQUAL(0, G1FLAGS_GET_M(flags)); + G1FLAGS_SET_M(flags, 1); + CU_ASSERT_EQUAL(1, G1FLAGS_GET_M(flags)); + + CU_ASSERT_EQUAL(0, G1FLAGS_GET_BBOX(flags)); + G1FLAGS_SET_BBOX(flags, 1); + CU_ASSERT_EQUAL(1, G1FLAGS_GET_BBOX(flags)); + + CU_ASSERT_EQUAL(0, G1FLAGS_GET_GEODETIC(flags)); + G1FLAGS_SET_GEODETIC(flags, 1); + CU_ASSERT_EQUAL(1, G1FLAGS_GET_GEODETIC(flags)); + + flags = g1flags(1, 0, 1); /* z=1, m=0, geodetic=1 */ + + CU_ASSERT_EQUAL(1, G1FLAGS_GET_GEODETIC(flags)); + CU_ASSERT_EQUAL(1, G1FLAGS_GET_Z(flags)); + CU_ASSERT_EQUAL(0, G1FLAGS_GET_M(flags)); + CU_ASSERT_EQUAL(2, G1FLAGS_GET_ZM(flags)); + + flags = g1flags(1, 1, 1); /* z=1, m=1, geodetic=1 */ + + CU_ASSERT_EQUAL(1, G1FLAGS_GET_GEODETIC(flags)); + CU_ASSERT_EQUAL(1, G1FLAGS_GET_Z(flags)); + CU_ASSERT_EQUAL(1, G1FLAGS_GET_M(flags)); + CU_ASSERT_EQUAL(3, G1FLAGS_GET_ZM(flags)); + + flags = g1flags(0, 1, 0); /* z=0, m=1, geodetic=0 */ + + CU_ASSERT_EQUAL(0, G1FLAGS_GET_GEODETIC(flags)); + CU_ASSERT_EQUAL(0, G1FLAGS_GET_Z(flags)); + CU_ASSERT_EQUAL(1, G1FLAGS_GET_M(flags)); + CU_ASSERT_EQUAL(1, G1FLAGS_GET_ZM(flags)); +} + +static void test_serialized1_srid(void) +{ + GSERIALIZED s; + int32_t srid, rv; + + srid = 4326; + gserialized1_set_srid(&s, srid); + rv = gserialized1_get_srid(&s); + CU_ASSERT_EQUAL(rv, srid); + + srid = -3005; + gserialized1_set_srid(&s, srid); + rv = gserialized1_get_srid(&s); + //printf("srid=%d rv=%d\n",srid,rv); + CU_ASSERT_EQUAL(rv, SRID_UNKNOWN); + + srid = SRID_UNKNOWN; + gserialized1_set_srid(&s, srid); + rv = gserialized1_get_srid(&s); + CU_ASSERT_EQUAL(rv, srid); + + srid = SRID_UNKNOWN; + gserialized1_set_srid(&s, srid); + rv = gserialized1_get_srid(&s); + CU_ASSERT_EQUAL(rv, srid); + + srid = 100000; + gserialized1_set_srid(&s, srid); + rv = gserialized1_get_srid(&s); + CU_ASSERT_EQUAL(rv, srid); +} + +static void test_gserialized1_from_lwgeom_size(void) +{ + LWGEOM *g; + size_t size = 0; + + g = lwgeom_from_wkt("POINT(0 0)", LW_PARSER_CHECK_NONE); + size = gserialized1_from_lwgeom_size(g); + CU_ASSERT_EQUAL( size, 32 ); + lwgeom_free(g); + + g = lwgeom_from_wkt("POINT(0 0 0)", LW_PARSER_CHECK_NONE); + size = gserialized1_from_lwgeom_size(g); + CU_ASSERT_EQUAL( size, 40 ); + lwgeom_free(g); + + g = lwgeom_from_wkt("MULTIPOINT(0 0 0, 1 1 1)", LW_PARSER_CHECK_NONE); + size = gserialized1_from_lwgeom_size(g); + CU_ASSERT_EQUAL( size, 80 ); + lwgeom_free(g); + + g = lwgeom_from_wkt("LINESTRING(0 0, 1 1)", LW_PARSER_CHECK_NONE); + size = gserialized1_from_lwgeom_size(g); + CU_ASSERT_EQUAL( size, 48 ); + lwgeom_free(g); + + g = lwgeom_from_wkt("MULTILINESTRING((0 0, 1 1),(0 0, 1 1))", LW_PARSER_CHECK_NONE); + size = gserialized1_from_lwgeom_size(g); + CU_ASSERT_EQUAL( size, 96 ); + lwgeom_free(g); + + g = lwgeom_from_wkt("POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))", LW_PARSER_CHECK_NONE); + size = gserialized1_from_lwgeom_size(g); + CU_ASSERT_EQUAL( size, 104 ); + lwgeom_free(g); + + g = lwgeom_from_wkt("POLYGON((-1 -1, -1 2, 2 2, 2 -1, -1 -1), (0 0, 0 1, 1 1, 1 0, 0 0))", LW_PARSER_CHECK_NONE); + size = gserialized1_from_lwgeom_size(g); + CU_ASSERT_EQUAL( size, 184 ); + lwgeom_free(g); + +} + +static void test_lwgeom_calculate_gbox(void) +{ + LWGEOM *g; + GBOX b; + + g = lwgeom_from_wkt("POINT(0 0)", LW_PARSER_CHECK_NONE); + lwgeom_calculate_gbox_cartesian(g, &b); + CU_ASSERT_DOUBLE_EQUAL(b.xmin, 0.0, 0.0000001); + lwgeom_free(g); + + /* Inf = 0x7FF0000000000000 */ + /* POINT(0 0) = 00 00000001 0000000000000000 0000000000000000 */ + /* POINT(0 Inf) = 00 00000001 0000000000000000 7FF0000000000000 */ + g = lwgeom_from_hexwkb("000000000100000000000000007FF0000000000000", LW_PARSER_CHECK_NONE); + lwgeom_calculate_gbox_cartesian(g, &b); + CU_ASSERT_DOUBLE_EQUAL(b.xmin, 0.0, 0.0000001); + CU_ASSERT(isinf(b.ymax)); + lwgeom_free(g); + + /* LINESTRING(0 0, 0 Inf) = 00 00000002 00000002 0000000000000000 7FF0000000000000 0000000000000000 0000000000000000 */ + /* Inf should show up in bbox */ + g = lwgeom_from_hexwkb("00000000020000000200000000000000007FF000000000000000000000000000000000000000000000", LW_PARSER_CHECK_NONE); + lwgeom_calculate_gbox_cartesian(g, &b); + CU_ASSERT_DOUBLE_EQUAL(b.xmin, 0.0, 0.0000001); + CU_ASSERT(isinf(b.ymax)); + lwgeom_free(g); + + /* Geometry with NaN 0101000020E8640000000000000000F8FF000000000000F8FF */ + /* NaN should show up in bbox for "SRID=4326;POINT(0 NaN)" */ + g = lwgeom_from_hexwkb("0101000020E86400000000000000000000000000000000F8FF", LW_PARSER_CHECK_NONE); + lwgeom_calculate_gbox_cartesian(g, &b); + CU_ASSERT(isnan(b.ymax)); + lwgeom_free(g); + +} + +static void test_lwgeom_from_gserialized(void) +{ + LWGEOM *geom; + GSERIALIZED *g; + char *in_ewkt; + char *out_ewkt; + size_t i = 0; + + char *ewkt[] = + { + "POINT EMPTY", + "POINT(0 0.2)", + "LINESTRING EMPTY", + "LINESTRING(-1 -1,-1 2.5,2 2,2 -1)", + "MULTIPOINT EMPTY", + "MULTIPOINT(0.9 0.9,0.9 0.9,0.9 0.9,0.9 0.9,0.9 0.9,0.9 0.9)", + "SRID=1;MULTILINESTRING EMPTY", + "SRID=1;MULTILINESTRING((-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1))", + "SRID=1;MULTILINESTRING((-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1))", + "POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0))", + "POLYGON EMPTY", + "SRID=4326;POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0))", + "SRID=4326;POLYGON EMPTY", + "SRID=4326;POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5))", + "SRID=100000;POLYGON((-1 -1 3,-1 2.5 3,2 2 3,2 -1 3,-1 -1 3),(0 0 3,0 1 3,1 1 3,1 0 3,0 0 3),(-0.5 -0.5 3,-0.5 -0.4 3,-0.4 -0.4 3,-0.4 -0.5 3,-0.5 -0.5 3))", + "SRID=4326;MULTIPOLYGON(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)),((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)))", + "SRID=4326;MULTIPOLYGON EMPTY", + "SRID=4326;GEOMETRYCOLLECTION(POINT(0 1),POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0)),MULTIPOLYGON(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5))))", + "SRID=4326;GEOMETRYCOLLECTION EMPTY", + "SRID=4326;GEOMETRYCOLLECTION(POINT EMPTY,MULTIPOLYGON EMPTY)", + "MULTICURVE((5 5 1 3,3 5 2 2,3 3 3 1,0 3 1 1),CIRCULARSTRING(0 0 0 0,0.26794 1 3 -2,0.5857864 1.414213 1 2))", + "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(-2 0,-1 -1,0 0,1 -1,2 0,0 2,-2 0),(-1 0,0 0.5,1 0,0 1,-1 0)),((7 8,10 10,6 14,4 11,7 8)))", + "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING EMPTY))", + }; + + for ( i = 0; i < (sizeof ewkt/sizeof(char*)); i++ ) + { + LWGEOM* geom2; + size_t sz1, sz2; + + in_ewkt = ewkt[i]; + geom = lwgeom_from_wkt(in_ewkt, LW_PARSER_CHECK_NONE); + lwgeom_add_bbox(geom); + if ( geom->bbox ) gbox_float_round(geom->bbox); + sz1 = gserialized1_from_lwgeom_size(geom); + g = gserialized1_from_lwgeom(geom, &sz2); + CU_ASSERT_EQUAL(sz1, sz2); + + geom2 = lwgeom_from_gserialized(g); + out_ewkt = lwgeom_to_ewkt(geom2); + + /* printf("\n in = %s\nout = %s\n", in_ewkt, out_ewkt); */ + CU_ASSERT_STRING_EQUAL(in_ewkt, out_ewkt); + + /* either both or none of the bboxes are null */ + CU_ASSERT( (geom->bbox != NULL) || (geom2->bbox == NULL) ); + + /* either both are null or they are the same */ + CU_ASSERT(geom->bbox == NULL || gbox_same(geom->bbox, geom2->bbox)); + + lwgeom_free(geom); + lwgeom_free(geom2); + lwfree(g); + lwfree(out_ewkt); + } + +} + + +static void test_gserialized1_is_empty(void) +{ + int i = 0; + struct gserialized_empty_cases { + const char* wkt; + int isempty; + }; + + struct gserialized_empty_cases cases[] = { + { "POINT EMPTY", 1 }, + { "POINT(1 1)", 0 }, + { "LINESTRING EMPTY", 1 }, + { "MULTILINESTRING EMPTY", 1 }, + { "MULTILINESTRING(EMPTY)", 1 }, + { "MULTILINESTRING(EMPTY,EMPTY)", 1 }, + { "MULTILINESTRING(EMPTY,(0 0,1 1))", 0 }, + { "MULTILINESTRING((0 0,1 1),EMPTY)", 0 }, + { "MULTILINESTRING(EMPTY,(0 0,1 1),EMPTY)", 0 }, + { "MULTILINESTRING(EMPTY,EMPTY,EMPTY)", 1 }, + { "GEOMETRYCOLLECTION(POINT EMPTY,MULTILINESTRING(EMPTY,EMPTY,EMPTY))", 1 }, + { "GEOMETRYCOLLECTION(POINT EMPTY,MULTILINESTRING(EMPTY),POINT(1 1))", 0 }, + { "GEOMETRYCOLLECTION(POINT EMPTY,MULTILINESTRING(EMPTY, (0 0)),POINT EMPTY)", 0 }, + { "GEOMETRYCOLLECTION(POLYGON EMPTY,POINT EMPTY,MULTILINESTRING(EMPTY,EMPTY),POINT EMPTY)", 1 }, + { "GEOMETRYCOLLECTION(POLYGON EMPTY,GEOMETRYCOLLECTION(POINT EMPTY),MULTILINESTRING(EMPTY,EMPTY),POINT EMPTY)", 1 }, + { NULL, 0 } + }; + + while( cases[i].wkt ) + { + // i = 11; + LWGEOM *lw = lwgeom_from_wkt(cases[i].wkt, LW_PARSER_CHECK_NONE); + GSERIALIZED *g = gserialized1_from_lwgeom(lw, 0); + int ie = gserialized1_is_empty(g); + // printf("%s: we say %d, they say %d\n", cases[i].wkt, cases[i].isempty, ie); + CU_ASSERT_EQUAL(ie, cases[i].isempty); + lwgeom_free(lw); + lwfree(g); + i++; + } +} + + +static void test_geometry_type_from_string(void) +{ + int rv; + uint8_t type = 0; + int z = 0, m = 0; + char *str; + + str = " POINTZ"; + rv = geometry_type_from_string(str, &type, &z, &m); + //printf("\n in type: %s\nout type: %d\n out z: %d\n out m: %d", str, type, z, m); + CU_ASSERT_EQUAL(rv, LW_SUCCESS); + CU_ASSERT_EQUAL(type, POINTTYPE); + CU_ASSERT_EQUAL(z, 1); + CU_ASSERT_EQUAL(m, 0); + + str = "LINESTRINGM "; + rv = geometry_type_from_string(str, &type, &z, &m); + //printf("\n in type: %s\nout type: %d\n out z: %d\n out m: %d", str, type, z, m); + CU_ASSERT_EQUAL(rv, LW_SUCCESS); + CU_ASSERT_EQUAL(type, LINETYPE); + CU_ASSERT_EQUAL(z, 0); + CU_ASSERT_EQUAL(m, 1); + + str = "MULTIPOLYGONZM"; + rv = geometry_type_from_string(str, &type, &z, &m); + //printf("\n in type: %s\nout type: %d\n out z: %d\n out m: %d", str, type, z, m); + CU_ASSERT_EQUAL(rv, LW_SUCCESS); + CU_ASSERT_EQUAL(type, MULTIPOLYGONTYPE); + CU_ASSERT_EQUAL(z, 1); + CU_ASSERT_EQUAL(m, 1); + + str = " GEOMETRYCOLLECTIONZM "; + rv = geometry_type_from_string(str, &type, &z, &m); + //printf("\n in type: %s\nout type: %d\n out z: %d\n out m: %d", str, type, z, m); + CU_ASSERT_EQUAL(rv, LW_SUCCESS); + CU_ASSERT_EQUAL(type, COLLECTIONTYPE); + CU_ASSERT_EQUAL(z, 1); + CU_ASSERT_EQUAL(m, 1); + + str = " GEOMERYCOLLECTIONZM "; + rv = geometry_type_from_string(str, &type, &z, &m); + //printf("\n in type: %s\nout type: %d\n out z: %d\n out m: %d", str, type, z, m); + CU_ASSERT_EQUAL(rv, LW_FAILURE); + +} + +static void test_lwgeom_count_vertices(void) +{ + LWGEOM *geom; + + geom = lwgeom_from_wkt("MULTIPOINT(-1 -1,-1 2.5,2 2,2 -1)", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(lwgeom_count_vertices(geom),4); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("SRID=1;MULTILINESTRING((-1 -131,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1))", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(lwgeom_count_vertices(geom),16); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("SRID=4326;MULTIPOLYGON(((-1 -1,-1 2.5,211 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)),((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(lwgeom_count_vertices(geom),30); + lwgeom_free(geom); + +} + +static void test_on_gser_lwgeom_count_vertices(void) +{ + LWGEOM *lwgeom; + GSERIALIZED *g_ser1; + size_t ret_size; + + lwgeom = lwgeom_from_wkt("MULTIPOINT(-1 -1,-1 2.5,2 2,2 -1,1 1,2 2,4 5)", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(lwgeom_count_vertices(lwgeom),7); + g_ser1 = gserialized1_from_lwgeom(lwgeom, &ret_size); + lwgeom_free(lwgeom); + + lwgeom = lwgeom_from_gserialized(g_ser1); + CU_ASSERT_EQUAL(lwgeom_count_vertices(lwgeom),7); + lwgeom_free(lwgeom); + + lwgeom = lwgeom_from_gserialized(g_ser1); + + CU_ASSERT_EQUAL(lwgeom_count_vertices(lwgeom),7); + lwgeom_free(lwgeom); + + lwfree(g_ser1); + +} + +static void test_lwcollection_extract(void) +{ + + LWGEOM *geom; + LWCOLLECTION *col; + + geom = lwgeom_from_wkt("GEOMETRYCOLLECTION(POINT(0 0))", LW_PARSER_CHECK_NONE); + + col = lwcollection_extract((LWCOLLECTION*)geom, 1); + CU_ASSERT_EQUAL(col->type, MULTIPOINTTYPE); + lwcollection_free(col); + + col = lwcollection_extract((LWCOLLECTION*)geom, 2); + CU_ASSERT_EQUAL(col->type, MULTILINETYPE); + lwcollection_free(col); + + col = lwcollection_extract((LWCOLLECTION*)geom, 3); + CU_ASSERT_EQUAL(col->type, MULTIPOLYGONTYPE); + lwcollection_free(col); + + lwgeom_free(geom); + + geom = lwgeom_from_wkt("GEOMETRYCOLLECTION EMPTY", LW_PARSER_CHECK_NONE); + + col = lwcollection_extract((LWCOLLECTION*)geom, 1); + CU_ASSERT_EQUAL(col->type, MULTIPOINTTYPE); + lwcollection_free(col); + + col = lwcollection_extract((LWCOLLECTION*)geom, 2); + CU_ASSERT_EQUAL(col->type, MULTILINETYPE); + lwcollection_free(col); + + col = lwcollection_extract((LWCOLLECTION*)geom, 3); + CU_ASSERT_EQUAL(col->type, MULTIPOLYGONTYPE); + lwcollection_free(col); + + lwgeom_free(geom); +} + +static void test_lwgeom_free(void) +{ + LWGEOM *geom; + + /* Empty geometries don't seem to free properly (#370) */ + geom = lwgeom_from_wkt("GEOMETRYCOLLECTION EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(geom->type, COLLECTIONTYPE); + lwgeom_free(geom); + + /* Empty geometries don't seem to free properly (#370) */ + geom = lwgeom_from_wkt("POLYGON EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(geom->type, POLYGONTYPE); + lwgeom_free(geom); + + /* Empty geometries don't seem to free properly (#370) */ + geom = lwgeom_from_wkt("LINESTRING EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(geom->type, LINETYPE); + lwgeom_free(geom); + + /* Empty geometries don't seem to free properly (#370) */ + geom = lwgeom_from_wkt("POINT EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(geom->type, POINTTYPE); + lwgeom_free(geom); + +} + +static void do_lwgeom_swap_ordinates(char *in, char *out) +{ + LWGEOM *g; + char * t; + double xmax, ymax; + int testbox; + + g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwgeom_add_bbox(g); + + testbox = (g->bbox != NULL); + if ( testbox ) + { + xmax = g->bbox->xmax; + ymax = g->bbox->ymax; + } + + lwgeom_swap_ordinates(g,LWORD_X,LWORD_Y); + + if ( testbox ) + { + CU_ASSERT_DOUBLE_EQUAL(g->bbox->xmax, ymax, 0.00001); + CU_ASSERT_DOUBLE_EQUAL(g->bbox->ymax, xmax, 0.00001); + } + + t = lwgeom_to_wkt(g, WKT_EXTENDED, 8, NULL); + if (t == NULL) fprintf(stderr, "In:%s", in); + if (strcmp(t, out)) + fprintf(stderr, "\nIn: %s\nOut: %s\nTheo: %s\n", in, t, out); + + CU_ASSERT_STRING_EQUAL(t, out) + + lwgeom_free(g); + lwfree(t); +} + +static void test_lwgeom_swap_ordinates(void) +{ + /* + * 2D geometries types + */ + do_lwgeom_swap_ordinates( + "POINT(1 2)", + "POINT(2 1)" + ); + + do_lwgeom_swap_ordinates( + "LINESTRING(1 2,3 4)", + "LINESTRING(2 1,4 3)" + ); + + do_lwgeom_swap_ordinates( + "POLYGON((1 2,3 4,5 6,1 2))", + "POLYGON((2 1,4 3,6 5,2 1))" + ); + + do_lwgeom_swap_ordinates( + "POLYGON((1 2,3 4,5 6,1 2),(7 8,9 10,11 12,7 8))", + "POLYGON((2 1,4 3,6 5,2 1),(8 7,10 9,12 11,8 7))" + ); + + do_lwgeom_swap_ordinates( + "MULTIPOINT(1 2,3 4)", + "MULTIPOINT(2 1,4 3)" + ); + + do_lwgeom_swap_ordinates( + "MULTILINESTRING((1 2,3 4),(5 6,7 8))", + "MULTILINESTRING((2 1,4 3),(6 5,8 7))" + ); + + do_lwgeom_swap_ordinates( + "MULTIPOLYGON(((1 2,3 4,5 6,7 8)),((9 10,11 12,13 14,10 9)))", + "MULTIPOLYGON(((2 1,4 3,6 5,8 7)),((10 9,12 11,14 13,9 10)))" + ); + + do_lwgeom_swap_ordinates( + "GEOMETRYCOLLECTION EMPTY", + "GEOMETRYCOLLECTION EMPTY" + ); + + do_lwgeom_swap_ordinates( + "GEOMETRYCOLLECTION(POINT(1 2),LINESTRING(3 4,5 6))", + "GEOMETRYCOLLECTION(POINT(2 1),LINESTRING(4 3,6 5))" + ); + + do_lwgeom_swap_ordinates( + "GEOMETRYCOLLECTION(POINT(1 2),GEOMETRYCOLLECTION(LINESTRING(3 4,5 6)))", + "GEOMETRYCOLLECTION(POINT(2 1),GEOMETRYCOLLECTION(LINESTRING(4 3,6 5)))" + ); + + do_lwgeom_swap_ordinates( + "CIRCULARSTRING(-2 0,0 2,2 0,0 2,2 4)", + "CIRCULARSTRING(0 -2,2 0,0 2,2 0,4 2)" + ); + + do_lwgeom_swap_ordinates( + "COMPOUNDCURVE(CIRCULARSTRING(0 1,1 1,1 0),(1 0,0 1))", + "COMPOUNDCURVE(CIRCULARSTRING(1 0,1 1,0 1),(0 1,1 0))" + ); + + do_lwgeom_swap_ordinates( + "CURVEPOLYGON(CIRCULARSTRING(-2 0,-1 -1,0 0,1 -1,2 0,0 2,-2 0),(-1 0,0 0.5,1 0,0 1,-1 0))", + "CURVEPOLYGON(CIRCULARSTRING(0 -2,-1 -1,0 0,-1 1,0 2,2 0,0 -2),(0 -1,0.5 0,0 1,1 0,0 -1))" + ); + + do_lwgeom_swap_ordinates( + "MULTICURVE((5 5,3 5,3 3,0 3),CIRCULARSTRING(0 0,2 1,2 3))", + "MULTICURVE((5 5,5 3,3 3,3 0),CIRCULARSTRING(0 0,1 2,3 2))" + ); + + do_lwgeom_swap_ordinates( + "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(-2 0,-1 -1,0 0,1 -1,2 0,0 2,-2 0),(-1 0,0 0.5,1 0,0 1,-1 0)),((7 8,10 10,6 14,4 11,7 8)))", + "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(0 -2,-1 -1,0 0,-1 1,0 2,2 0,0 -2),(0 -1,0.5 0,0 1,1 0,0 -1)),((8 7,10 10,14 6,11 4,8 7)))" + ); + + + /* + * Ndims + */ + + do_lwgeom_swap_ordinates( + "POINT(1 2 3)", + "POINT(2 1 3)" + ); + + do_lwgeom_swap_ordinates( + "POINTM(1 2 3)", + "POINTM(2 1 3)" + ); + + do_lwgeom_swap_ordinates( + "POINT(1 2 3 4)", + "POINT(2 1 3 4)" + ); + + + /* + * Srid + */ + + do_lwgeom_swap_ordinates( + "SRID=4326;POINT(1 2)", + "SRID=4326;POINT(2 1)" + ); + + do_lwgeom_swap_ordinates( + "SRID=0;POINT(1 2)", + "POINT(2 1)" + ); +} + +static void test_f2d(void) +{ + double d = 1000000.123456789123456789; + float f; + double e; + + f = next_float_down(d); + d = next_float_down(f); + CU_ASSERT_DOUBLE_EQUAL(f,d, 0.0000001); + + e = (double)f; + CU_ASSERT_DOUBLE_EQUAL(f,e, 0.0000001); + + f = next_float_down(d); + d = next_float_down(f); + CU_ASSERT_DOUBLE_EQUAL(f,d, 0.0000001); + + f = next_float_up(d); + d = next_float_up(f); + CU_ASSERT_DOUBLE_EQUAL(f,d, 0.0000001); + + f = next_float_up(d); + d = next_float_up(f); + CU_ASSERT_DOUBLE_EQUAL(f,d, 0.0000001); + + d = DBL_MAX; + f = next_float_up(d); + d = next_float_up(f); + CU_ASSERT_DOUBLE_EQUAL(f, d, 0.0000001); + + d = DBL_MAX; + f = next_float_down(d); + d = next_float_down(f); + CU_ASSERT_DOUBLE_EQUAL(f, d, 0.0000001); + + d = -DBL_MAX; + f = next_float_up(d); + d = next_float_up(f); + CU_ASSERT_DOUBLE_EQUAL(f, d, 0.0000001); + + d = -DBL_MAX; + f = next_float_down(d); + d = next_float_down(f); + CU_ASSERT_DOUBLE_EQUAL(f, d, 0.0000001); +} + +/* + * This is a test for memory leaks, can't really test + * w/out checking with a leak detector (ie: valgrind) + * + * See http://trac.osgeo.org/postgis/ticket/1102 + */ +static void test_lwgeom_clone(void) +{ + size_t i; + + char *ewkt[] = + { + "POINT(0 0.2)", + "LINESTRING(-1 -1,-1 2.5,2 2,2 -1)", + "MULTIPOINT(0.9 0.9,0.9 0.9,0.9 0.9,0.9 0.9,0.9 0.9,0.9 0.9)", + "SRID=1;MULTILINESTRING((-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1))", + "SRID=1;MULTILINESTRING((-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1))", + "POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0))", + "SRID=4326;POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0))", + "SRID=4326;POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5))", + "SRID=100000;POLYGON((-1 -1 3,-1 2.5 3,2 2 3,2 -1 3,-1 -1 3),(0 0 3,0 1 3,1 1 3,1 0 3,0 0 3),(-0.5 -0.5 3,-0.5 -0.4 3,-0.4 -0.4 3,-0.4 -0.5 3,-0.5 -0.5 3))", + "SRID=4326;MULTIPOLYGON(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)),((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)))", + "SRID=4326;GEOMETRYCOLLECTION(POINT(0 1),POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0)),MULTIPOLYGON(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5))))", + "MULTICURVE((5 5 1 3,3 5 2 2,3 3 3 1,0 3 1 1),CIRCULARSTRING(0 0 0 0,0.26794 1 3 -2,0.5857864 1.414213 1 2))", + "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(-2 0,-1 -1,0 0,1 -1,2 0,0 2,-2 0),(-1 0,0 0.5,1 0,0 1,-1 0)),((7 8,10 10,6 14,4 11,7 8)))", + "TIN(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))" + }; + + + for ( i = 0; i < (sizeof ewkt/sizeof(char *)); i++ ) + { + LWGEOM *geom, *cloned; + char *in_ewkt; + char *out_ewkt; + + in_ewkt = ewkt[i]; + geom = lwgeom_from_wkt(in_ewkt, LW_PARSER_CHECK_NONE); + cloned = lwgeom_clone(geom); + out_ewkt = lwgeom_to_ewkt(cloned); + if (strcmp(in_ewkt, out_ewkt)) + fprintf(stderr, "\nExp: %s\nObt: %s\n", in_ewkt, out_ewkt); + CU_ASSERT_STRING_EQUAL(in_ewkt, out_ewkt); + lwfree(out_ewkt); + lwgeom_free(cloned); + lwgeom_free(geom); + } + + +} + +/* + * Test lwgeom_force_clockwise + */ +static void test_lwgeom_force_clockwise(void) +{ + LWGEOM *geom; + LWGEOM *geom2; + char *in_ewkt, *out_ewkt; + + /* counterclockwise, must be reversed */ + geom = lwgeom_from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))", LW_PARSER_CHECK_NONE); + CU_ASSERT_FALSE(lwgeom_is_clockwise(geom)); + lwgeom_force_clockwise(geom); + in_ewkt = "POLYGON((0 0,0 10,10 10,10 0,0 0))"; + out_ewkt = lwgeom_to_ewkt(geom); + if (strcmp(in_ewkt, out_ewkt)) + fprintf(stderr, "\nExp: %s\nObt: %s\n", in_ewkt, out_ewkt); + CU_ASSERT_STRING_EQUAL(in_ewkt, out_ewkt); + lwfree(out_ewkt); + lwgeom_free(geom); + + /* clockwise, fine as is */ + geom = lwgeom_from_wkt("POLYGON((0 0, 0 10, 10 10, 10 0, 0 0))", LW_PARSER_CHECK_NONE); + CU_ASSERT_TRUE(lwgeom_is_clockwise(geom)); + lwgeom_force_clockwise(geom); + in_ewkt = "POLYGON((0 0,0 10,10 10,10 0,0 0))"; + out_ewkt = lwgeom_to_ewkt(geom); + if (strcmp(in_ewkt, out_ewkt)) + fprintf(stderr, "\nExp: %s\nObt: %s\n", in_ewkt, out_ewkt); + CU_ASSERT_STRING_EQUAL(in_ewkt, out_ewkt); + lwfree(out_ewkt); + lwgeom_free(geom); + + /* counterclockwise shell (must be reversed), mixed-wise holes */ + geom = lwgeom_from_wkt("POLYGON((0 0,10 0,10 10,0 10,0 0),(2 2,2 4,4 2,2 2),(6 2,8 2,8 4,6 2))", LW_PARSER_CHECK_NONE); + CU_ASSERT_FALSE(lwgeom_is_clockwise(geom)); + lwgeom_force_clockwise(geom); + in_ewkt = "POLYGON((0 0,0 10,10 10,10 0,0 0),(2 2,4 2,2 4,2 2),(6 2,8 2,8 4,6 2))"; + out_ewkt = lwgeom_to_ewkt(geom); + if (strcmp(in_ewkt, out_ewkt)) + fprintf(stderr, "\nExp: %s\nObt: %s\n", in_ewkt, out_ewkt); + CU_ASSERT_STRING_EQUAL(in_ewkt, out_ewkt); + lwfree(out_ewkt); + lwgeom_free(geom); + + /* clockwise shell (fine), mixed-wise holes */ + geom = lwgeom_from_wkt("POLYGON((0 0,0 10,10 10,10 0,0 0),(2 2,4 2,2 4,2 2),(6 2,8 4,8 2,6 2))", LW_PARSER_CHECK_NONE); + CU_ASSERT_FALSE(lwgeom_is_clockwise(geom)); + lwgeom_force_clockwise(geom); + in_ewkt = "POLYGON((0 0,0 10,10 10,10 0,0 0),(2 2,4 2,2 4,2 2),(6 2,8 2,8 4,6 2))"; + out_ewkt = lwgeom_to_ewkt(geom); + if (strcmp(in_ewkt, out_ewkt)) + fprintf(stderr, "\nExp: %s\nObt: %s\n", in_ewkt, out_ewkt); + CU_ASSERT_STRING_EQUAL(in_ewkt, out_ewkt); + lwfree(out_ewkt); + lwgeom_free(geom); + + /* clockwise narrow ring, fine as-is */ + /* NOTE: this is a narrow ring, see ticket #1302 */ + in_ewkt = "0103000000010000000500000000917E9BA468294100917E9B8AEA2841C976BE1FA4682941C976BE9F8AEA2841B39ABE1FA46829415ACCC29F8AEA284137894120A4682941C976BE9F8AEA284100917E9BA468294100917E9B8AEA2841"; + geom = lwgeom_from_hexwkb(in_ewkt, LW_PARSER_CHECK_NONE); + geom2 = lwgeom_from_hexwkb(in_ewkt, LW_PARSER_CHECK_NONE); + lwgeom_force_clockwise(geom2); + + /** use same check instead of strcmp to account + for difference in endianness **/ + CU_ASSERT( lwgeom_same(geom, geom2) ); + lwgeom_free(geom); + lwgeom_free(geom2); +} + +/* + * Test lwgeom_is_empty + */ +static void test_lwgeom_is_empty(void) +{ + LWGEOM *geom; + + geom = lwgeom_from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))", LW_PARSER_CHECK_NONE); + CU_ASSERT( !lwgeom_is_empty(geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("POINT EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_is_empty(geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("LINESTRING EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_is_empty(geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("POLYGON EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_is_empty(geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("MULTIPOINT EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_is_empty(geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("MULTILINESTRING EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_is_empty(geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("MULTIPOLYGON EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_is_empty(geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY, POINT EMPTY, LINESTRING EMPTY, POLYGON EMPTY, MULTIPOINT EMPTY, MULTILINESTRING EMPTY, MULTIPOLYGON EMPTY, GEOMETRYCOLLECTION(MULTIPOLYGON EMPTY))", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_is_empty(geom) ); + lwgeom_free(geom); + +} + +/* + * Test lwgeom_same + */ +static void test_lwgeom_same(void) +{ + LWGEOM *geom, *geom2; + + geom = lwgeom_from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_add_bbox(geom); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0)))", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_add_bbox(geom); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("LINESTRING(0 0, 2 0)", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_add_bbox(geom); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("MULTILINESTRING((0 0, 2 0))", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_add_bbox(geom); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("POINT(0 0)", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_add_bbox(geom); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("MULTIPOINT((0 0),(4 5))", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_add_bbox(geom); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("POINT EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_add_bbox(geom); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("LINESTRING EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_add_bbox(geom); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("POLYGON EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("MULTIPOINT EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("MULTILINESTRING EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("MULTIPOLYGON EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY, POINT EMPTY, LINESTRING EMPTY, POLYGON EMPTY, MULTIPOINT EMPTY, MULTILINESTRING EMPTY, MULTIPOLYGON EMPTY, GEOMETRYCOLLECTION(MULTIPOLYGON EMPTY))", LW_PARSER_CHECK_NONE); + CU_ASSERT( lwgeom_same(geom, geom) ); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))", LW_PARSER_CHECK_NONE); + geom2 = lwgeom_from_wkt("GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY, POINT EMPTY, LINESTRING EMPTY, POLYGON EMPTY, MULTIPOINT EMPTY, MULTILINESTRING EMPTY, MULTIPOLYGON EMPTY, GEOMETRYCOLLECTION(MULTIPOLYGON EMPTY))", LW_PARSER_CHECK_NONE); + CU_ASSERT( ! lwgeom_same(geom, geom2) ); + lwgeom_free(geom); + lwgeom_free(geom2); + + geom = lwgeom_from_wkt("POINT(0 0)", LW_PARSER_CHECK_NONE); + geom2 = lwgeom_from_wkt("MULTIPOINT((0 0))", LW_PARSER_CHECK_NONE); + CU_ASSERT( ! lwgeom_same(geom, geom2) ); + lwgeom_free(geom); + lwgeom_free(geom2); + + geom = lwgeom_from_wkt("POINT EMPTY", LW_PARSER_CHECK_NONE); + geom2 = lwgeom_from_wkt("POINT Z EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT( ! lwgeom_same(geom, geom2) ); + lwgeom_free(geom); + lwgeom_free(geom2); + +} + +/* + * Test lwgeom_force_curve + */ +static void test_lwgeom_as_curve(void) +{ + LWGEOM *geom; + LWGEOM *geom2; + char *in_ewkt, *out_ewkt; + + geom = lwgeom_from_wkt("LINESTRING(0 0, 10 0)", LW_PARSER_CHECK_NONE); + geom2 = lwgeom_as_curve(geom); + in_ewkt = "COMPOUNDCURVE((0 0,10 0))"; + out_ewkt = lwgeom_to_ewkt(geom2); + if (strcmp(in_ewkt, out_ewkt)) + fprintf(stderr, "\nExp: %s\nObt: %s\n", in_ewkt, out_ewkt); + CU_ASSERT_STRING_EQUAL(in_ewkt, out_ewkt); + lwfree(out_ewkt); + lwgeom_free(geom); + lwgeom_free(geom2); + + geom = lwgeom_from_wkt("MULTILINESTRING((0 0, 10 0))", LW_PARSER_CHECK_NONE); + geom2 = lwgeom_as_curve(geom); + in_ewkt = "MULTICURVE((0 0,10 0))"; + out_ewkt = lwgeom_to_ewkt(geom2); + if (strcmp(in_ewkt, out_ewkt)) + fprintf(stderr, "\nExp: %s\nObt: %s\n", in_ewkt, out_ewkt); + CU_ASSERT_STRING_EQUAL(in_ewkt, out_ewkt); + lwfree(out_ewkt); + lwgeom_free(geom); + lwgeom_free(geom2); + + geom = lwgeom_from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))", LW_PARSER_CHECK_NONE); + geom2 = lwgeom_as_curve(geom); + in_ewkt = "CURVEPOLYGON((0 0,10 0,10 10,0 10,0 0))"; + out_ewkt = lwgeom_to_ewkt(geom2); + if (strcmp(in_ewkt, out_ewkt)) + fprintf(stderr, "\nExp: %s\nObt: %s\n", in_ewkt, out_ewkt); + CU_ASSERT_STRING_EQUAL(in_ewkt, out_ewkt); + lwfree(out_ewkt); + lwgeom_free(geom); + lwgeom_free(geom2); + + geom = lwgeom_from_wkt("MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0)))", LW_PARSER_CHECK_NONE); + geom2 = lwgeom_as_curve(geom); + in_ewkt = "MULTISURFACE(((0 0,10 0,10 10,0 10,0 0)))"; + out_ewkt = lwgeom_to_ewkt(geom2); + if (strcmp(in_ewkt, out_ewkt)) + fprintf(stderr, "\nExp: %s\nObt: %s\n", in_ewkt, out_ewkt); + CU_ASSERT_STRING_EQUAL(in_ewkt, out_ewkt); + lwfree(out_ewkt); + lwgeom_free(geom); + lwgeom_free(geom2); + +} + +static void test_lwline_from_lwmpoint(void) +{ + LWLINE *line; + LWMPOINT *mpoint; + + mpoint = (LWMPOINT*)lwgeom_from_wkt("MULTIPOINT(0 0, 0 1, 1 1, 1 2, 2 2)", LW_PARSER_CHECK_NONE); + line = lwline_from_lwmpoint(SRID_DEFAULT, mpoint); + CU_ASSERT_EQUAL(line->points->npoints, mpoint->ngeoms); + CU_ASSERT_DOUBLE_EQUAL(lwline_length_2d(line), 4.0, 0.000001); + + lwline_free(line); + lwmpoint_free(mpoint); +} + +/* + * Test lwgeom_scale + */ +static void test_lwgeom_scale(void) +{ + LWGEOM *geom; + POINT4D factor; + char *out_ewkt; + GBOX *box; + + geom = lwgeom_from_wkt("SRID=4326;GEOMETRYCOLLECTION(POINT(0 1 2 3),POLYGON((-1 -1 0 1,-1 2.5 0 1,2 2 0 1,2 -1 0 1,-1 -1 0 1),(0 0 1 2,0 1 1 2,1 1 1 2,1 0 2 3,0 0 1 2)),LINESTRING(0 0 0 0, 1 2 3 4))", LW_PARSER_CHECK_NONE); + factor.x = 2; factor.y = 3; factor.z = 4; factor.m = 5; + lwgeom_scale(geom, &factor); + out_ewkt = lwgeom_to_ewkt(geom); + ASSERT_STRING_EQUAL(out_ewkt, "SRID=4326;GEOMETRYCOLLECTION(POINT(0 3 8 15),POLYGON((-2 -3 0 5,-2 7.5 0 5,4 6 0 5,4 -3 0 5,-2 -3 0 5),(0 0 4 10,0 3 4 10,2 3 4 10,2 0 8 15,0 0 4 10)),LINESTRING(0 0 0 0,2 6 12 20))"); + lwgeom_free(geom); + lwfree(out_ewkt); + + geom = lwgeom_from_wkt("POINT(1 1 1 1)", LW_PARSER_CHECK_NONE); + lwgeom_add_bbox(geom); + factor.x = 2; factor.y = 3; factor.z = 4; factor.m = 5; + lwgeom_scale(geom, &factor); + box = geom->bbox; + ASSERT_DOUBLE_EQUAL(box->xmin, 2); + ASSERT_DOUBLE_EQUAL(box->xmax, 2); + ASSERT_DOUBLE_EQUAL(box->ymin, 3); + ASSERT_DOUBLE_EQUAL(box->ymax, 3); + ASSERT_DOUBLE_EQUAL(box->zmin, 4); + ASSERT_DOUBLE_EQUAL(box->zmax, 4); + ASSERT_DOUBLE_EQUAL(box->mmin, 5); + ASSERT_DOUBLE_EQUAL(box->mmax, 5); + lwgeom_free(geom); +} + +static void test_gbox_same_2d(void) +{ + LWGEOM* g1 = lwgeom_from_wkt("LINESTRING(0 0, 1 1)", LW_PARSER_CHECK_NONE); + LWGEOM* g2 = lwgeom_from_wkt("LINESTRING(0 0, 0 1, 1 1)", LW_PARSER_CHECK_NONE); + LWGEOM* g3 = lwgeom_from_wkt("LINESTRING(0 0, 1 1.000000000001)", LW_PARSER_CHECK_NONE); + + lwgeom_add_bbox(g1); + lwgeom_add_bbox(g2); + lwgeom_add_bbox(g3); + + CU_ASSERT_TRUE(gbox_same_2d(g1->bbox, g2->bbox)); + CU_ASSERT_FALSE(gbox_same_2d(g1->bbox, g3->bbox)); + + /* Serializing a GBOX with precise coordinates renders the boxes not strictly equal, + * but still equal according to gbox_same_2d_float. + */ + GSERIALIZED* s3 = gserialized1_from_lwgeom(g3, NULL); + GBOX s3box; + gserialized1_read_gbox_p(s3, &s3box); + + CU_ASSERT_FALSE(gbox_same_2d(g3->bbox, &s3box)); + CU_ASSERT_TRUE(gbox_same_2d_float(g3->bbox, &s3box)); + + /* The serialized box equals itself by either the exact or closest-float compares */ + CU_ASSERT_TRUE(gbox_same_2d(&s3box, &s3box)); + CU_ASSERT_TRUE(gbox_same_2d_float(&s3box, &s3box)); + + lwgeom_free(g1); + lwgeom_free(g2); + lwgeom_free(g3); + lwfree(s3); +} + +static void test_gserialized1_peek_gbox_p_no_box_when_empty(void) +{ + uint32_t i; + + char *ewkt[] = + { + "POINT EMPTY", + "LINESTRING EMPTY", + "MULTIPOINT EMPTY", + "MULTIPOINT (EMPTY)", + "MULTILINESTRING EMPTY", + "MULTILINESTRING (EMPTY)" + }; + + for ( i = 0; i < (sizeof ewkt/sizeof(char*)); i++ ) + { + LWGEOM* geom = lwgeom_from_wkt(ewkt[i], LW_PARSER_CHECK_NONE); + GBOX box; + gbox_init(&box); + + GSERIALIZED* gser = gserialized1_from_lwgeom(geom, NULL); + + CU_ASSERT_FALSE(gserialized1_has_bbox(gser)); + + CU_ASSERT_EQUAL(LW_FAILURE, gserialized1_peek_gbox_p(gser, &box)); + + lwgeom_free(geom); + lwfree(gser); + } +} + +static void test_gserialized1_peek_gbox_p_gets_correct_box(void) +{ + uint32_t i; + + char *ewkt[] = + { + "POINT (2.2945672355 48.85822923236)", + "POINTZ (2.2945672355 48.85822923236 15)", + "POINTM (2.2945672355 48.85822923236 12)", + "POINT ZM (2.2945672355 48.85822923236 12 2)", + "MULTIPOINT ((-76.45402132523 44.225406213532))", + "MULTIPOINT Z ((-76.45402132523 44.225406213532 112))", + "MULTIPOINT ZM ((-76.45402132523 44.225406213532 112 44))", + "LINESTRING (2.2945672355 48.85822923236, -76.45402132523 44.225406213532)", + "LINESTRING Z (2.2945672355 48.85822923236 6, -76.45402132523 44.225406213532 8)", + "LINESTRING ZM (2.2945672355 48.85822923236 3 2, -76.45402132523 44.225406213532 9 4)", + "MULTILINESTRING ((2.2945672355 48.85822923236, -76.45402132523 44.225406213532))", + "MULTILINESTRING Z ((2.2945672355 48.85822923236 4, -76.45402132523 44.225406213532 3))" + }; + + for ( i = 0; i < (sizeof ewkt/sizeof(char*)); i++ ) + { + LWGEOM* geom = lwgeom_from_wkt(ewkt[i], LW_PARSER_CHECK_NONE); + GBOX box_from_peek; + GBOX box_from_lwgeom; + gbox_init(&box_from_peek); + gbox_init(&box_from_lwgeom); + + GSERIALIZED* gser = gserialized1_from_lwgeom(geom, NULL); + + CU_ASSERT_FALSE(gserialized1_has_bbox(gser)); + + lwgeom_calculate_gbox(geom, &box_from_lwgeom); + gserialized1_peek_gbox_p(gser, &box_from_peek); + + gbox_float_round(&box_from_lwgeom); + + CU_ASSERT_TRUE(gbox_same(&box_from_peek, &box_from_lwgeom)); + + lwgeom_free(geom); + lwfree(gser); + } +} + +static void test_gserialized1_peek_gbox_p_fails_for_unsupported_cases(void) +{ + uint32_t i; + + char *ewkt[] = + { + "MULTIPOINT ((-76.45402132523 44.225406213532), (-72 33))", + "LINESTRING (2.2945672355 48.85822923236, -76.45402132523 44.225406213532, -72 33)", + "MULTILINESTRING ((2.2945672355 48.85822923236, -76.45402132523 44.225406213532, -72 33))", + "MULTILINESTRING ((2.2945672355 48.85822923236, -76.45402132523 44.225406213532), (-72 33, -71 32))" + }; + + for ( i = 0; i < (sizeof ewkt/sizeof(char*)); i++ ) + { + LWGEOM* geom = lwgeom_from_wkt(ewkt[i], LW_PARSER_CHECK_NONE); + GBOX box; + gbox_init(&box); + lwgeom_drop_bbox(geom); + + /* Construct a GSERIALIZED* that doesn't have a box, so that we can test the + * actual logic of the peek function */ + size_t expected_size = gserialized1_from_lwgeom_size(geom); + GSERIALIZED* gser = lwalloc(expected_size); + uint8_t* ptr = (uint8_t*) gser; + + ptr += 8; // Skip header + gserialized1_from_lwgeom_any(geom, ptr); + gser->gflags = lwflags_get_g1flags(geom->flags); + + CU_ASSERT_FALSE(gserialized1_has_bbox(gser)); + CU_ASSERT_EQUAL(LW_FAILURE, gserialized1_peek_gbox_p(gser, &box)); + + lwgeom_free(geom); + lwfree(gser); + } +} + +static void test_signum_macro(void) +{ + CU_ASSERT_EQUAL(SIGNUM(-5.0),-1); + CU_ASSERT_EQUAL(SIGNUM( 5.0), 1); + CU_ASSERT_EQUAL(SIGNUM( 0.0), 0); + CU_ASSERT_EQUAL(SIGNUM(10) * 5, 5); + CU_ASSERT_EQUAL(SIGNUM(-10) * 5, -5); +} + +static int +peek1_point_helper(char *geometry, POINT4D *p) +{ + cu_error_msg_reset(); + p->x = p->y = p->z = p->m = 0; + LWGEOM *geom = lwgeom_from_wkt(geometry, LW_PARSER_CHECK_NONE); + CU_ASSERT(geom != NULL); + GSERIALIZED *g = gserialized1_from_lwgeom(geom, NULL); + CU_ASSERT(g != NULL); + + int ret = gserialized1_peek_first_point(g, p); + lwfree(g); + lwgeom_free(geom); + + return ret; +} + +static void +test_gserialized1_peek_first_point(void) +{ + POINT4D p = {0}; + + CU_ASSERT(peek1_point_helper("POINT(1 2)", &p) == LW_SUCCESS); + CU_ASSERT_EQUAL(p.x, 1); + CU_ASSERT_EQUAL(p.y, 2); + + CU_ASSERT(peek1_point_helper("POINTZ(10 20 30)", &p) == LW_SUCCESS); + CU_ASSERT_EQUAL(p.x, 10); + CU_ASSERT_EQUAL(p.y, 20); + CU_ASSERT_EQUAL(p.z, 30); + + CU_ASSERT(peek1_point_helper("POINTM(100 200 300)", &p) == LW_SUCCESS); + CU_ASSERT_EQUAL(p.x, 100); + CU_ASSERT_EQUAL(p.y, 200); + CU_ASSERT_EQUAL(p.m, 300); + + CU_ASSERT(peek1_point_helper("POINTZM(1000 2000 3000 4000)", &p) == LW_SUCCESS); + CU_ASSERT_EQUAL(p.x, 1000); + CU_ASSERT_EQUAL(p.y, 2000); + CU_ASSERT_EQUAL(p.z, 3000); + CU_ASSERT_EQUAL(p.m, 4000); + + CU_ASSERT(peek1_point_helper("MULTIPOINT((0 0), (1 1))", &p) == LW_FAILURE); + CU_ASSERT(peek1_point_helper("LINESTRING(0 0, 1 1)", &p) == LW_FAILURE); + CU_ASSERT(peek1_point_helper("MULTILINESTRING((0 0, 1 1), (0 0, 1 1))", &p) == LW_FAILURE); + CU_ASSERT(peek1_point_helper("POLYGON((0 0, 1 1, 1 0, 0 0))", &p) == LW_FAILURE); +} + +/* +** Used by test harness to register the tests in this file. +*/ +void gserialized1_suite_setup(void); +void gserialized1_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("serialization/deserialization v1", NULL, NULL); + PG_ADD_TEST(suite, test_typmod_macros); + PG_ADD_TEST(suite, test_flags_macros); + PG_ADD_TEST(suite, test_serialized1_srid); + PG_ADD_TEST(suite, test_gserialized1_from_lwgeom_size); + PG_ADD_TEST(suite, test_lwgeom_from_gserialized); + PG_ADD_TEST(suite, test_lwgeom_count_vertices); + PG_ADD_TEST(suite, test_on_gser_lwgeom_count_vertices); + PG_ADD_TEST(suite, test_geometry_type_from_string); + PG_ADD_TEST(suite, test_lwcollection_extract); + PG_ADD_TEST(suite, test_lwgeom_free); + PG_ADD_TEST(suite, test_lwgeom_swap_ordinates); + PG_ADD_TEST(suite, test_f2d); + PG_ADD_TEST(suite, test_lwgeom_clone); + PG_ADD_TEST(suite, test_lwgeom_force_clockwise); + PG_ADD_TEST(suite, test_lwgeom_calculate_gbox); + PG_ADD_TEST(suite, test_lwgeom_is_empty); + PG_ADD_TEST(suite, test_lwgeom_same); + PG_ADD_TEST(suite, test_lwline_from_lwmpoint); + PG_ADD_TEST(suite, test_lwgeom_as_curve); + PG_ADD_TEST(suite, test_lwgeom_scale); + PG_ADD_TEST(suite, test_gserialized1_is_empty); + PG_ADD_TEST(suite, test_gserialized1_peek_gbox_p_no_box_when_empty); + PG_ADD_TEST(suite, test_gserialized1_peek_gbox_p_gets_correct_box); + PG_ADD_TEST(suite, test_gserialized1_peek_gbox_p_fails_for_unsupported_cases); + PG_ADD_TEST(suite, test_gbox_same_2d); + PG_ADD_TEST(suite, test_signum_macro); + PG_ADD_TEST(suite, test_gserialized1_peek_first_point); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_gserialized2.c b/mgist-postgis/liblwgeom/cunit/cu_gserialized2.c new file mode 100644 index 0000000..1f86dd0 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_gserialized2.c @@ -0,0 +1,516 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2019 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "gserialized2.c" /* for gserialized_peek_gbox_p */ +#include "cu_tester.h" + + +static void test_g2flags_macros(void) +{ + uint8_t flags = 0; + + CU_ASSERT_EQUAL(0, G2FLAGS_GET_Z(flags)); + G2FLAGS_SET_Z(flags, 1); + CU_ASSERT_EQUAL(1, G2FLAGS_GET_Z(flags)); + G2FLAGS_SET_Z(flags, 0); + CU_ASSERT_EQUAL(0, G2FLAGS_GET_Z(flags)); + CU_ASSERT_EQUAL(0, G2FLAGS_GET_BBOX(flags)); + + CU_ASSERT_EQUAL(0, G2FLAGS_GET_M(flags)); + G2FLAGS_SET_M(flags, 1); + CU_ASSERT_EQUAL(1, G2FLAGS_GET_M(flags)); + + CU_ASSERT_EQUAL(0, G2FLAGS_GET_BBOX(flags)); + G2FLAGS_SET_BBOX(flags, 1); + CU_ASSERT_EQUAL(1, G2FLAGS_GET_BBOX(flags)); + + CU_ASSERT_EQUAL(0, G2FLAGS_GET_GEODETIC(flags)); + G2FLAGS_SET_GEODETIC(flags, 1); + CU_ASSERT_EQUAL(1, G2FLAGS_GET_GEODETIC(flags)); + + flags = g2flags(1, 0, 1); /* z=1, m=0, geodetic=1 */ + + CU_ASSERT_EQUAL(1, G2FLAGS_GET_GEODETIC(flags)); + CU_ASSERT_EQUAL(1, G2FLAGS_GET_Z(flags)); + CU_ASSERT_EQUAL(0, G2FLAGS_GET_M(flags)); + CU_ASSERT_EQUAL(2, G2FLAGS_GET_ZM(flags)); + + flags = g2flags(1, 1, 1); /* z=1, m=1, geodetic=1 */ + + CU_ASSERT_EQUAL(1, G2FLAGS_GET_GEODETIC(flags)); + CU_ASSERT_EQUAL(1, G2FLAGS_GET_Z(flags)); + CU_ASSERT_EQUAL(1, G2FLAGS_GET_M(flags)); + CU_ASSERT_EQUAL(3, G2FLAGS_GET_ZM(flags)); + + flags = g2flags(0, 1, 0); /* z=0, m=1, geodetic=0 */ + + CU_ASSERT_EQUAL(0, G2FLAGS_GET_GEODETIC(flags)); + CU_ASSERT_EQUAL(0, G2FLAGS_GET_Z(flags)); + CU_ASSERT_EQUAL(1, G2FLAGS_GET_M(flags)); + CU_ASSERT_EQUAL(1, G2FLAGS_GET_ZM(flags)); + + flags = 0; + G2FLAGS_SET_VERSION(flags, 0); + CU_ASSERT_EQUAL(0, G2FLAGS_GET_VERSION(flags)); + G2FLAGS_SET_VERSION(flags, 1); + CU_ASSERT_EQUAL(1, G2FLAGS_GET_VERSION(flags)); +} + +static void test_gserialized2_extended_flags(void) +{ + LWGEOM *lwg1, *lwg2; + GSERIALIZED *g; + size_t size = 0; + + /* No extended flags, so 32 bytes for a point */ + lwg1 = lwgeom_from_wkt("POINT(100 100)", LW_PARSER_CHECK_NONE); + size = gserialized2_from_lwgeom_size(lwg1); + CU_ASSERT_EQUAL(size, 32); + + /* Add extended flag, we now expect 8 extra bytes */ + FLAGS_SET_SOLID(lwg1->flags, 1); + CU_ASSERT_EQUAL(1, FLAGS_GET_SOLID(lwg1->flags)); + size = gserialized2_from_lwgeom_size(lwg1); + CU_ASSERT_EQUAL(size, 40); + + /* We expect that the extended bit will be set on the serialization */ + g = gserialized2_from_lwgeom(lwg1, &size); + CU_ASSERT_EQUAL(1, G2FLAGS_GET_EXTENDED(g->gflags)); + /* And that the serialization in fact hits 40 bytes */ + CU_ASSERT_EQUAL(size, 40); + lwgeom_free(lwg1); + lwg2 = lwgeom_from_gserialized2(g); + lwfree(g); + /* And that when deserialized again, the solid flag is still there */ + CU_ASSERT_EQUAL(1, FLAGS_GET_SOLID(lwg2->flags)); + lwgeom_free(lwg2); + +} + +static void test_gserialized2_srid(void) +{ + GSERIALIZED s; + int32_t srid, rv; + + srid = 4326; + gserialized2_set_srid(&s, srid); + rv = gserialized2_get_srid(&s); + CU_ASSERT_EQUAL(rv, srid); + + srid = -3005; + gserialized2_set_srid(&s, srid); + rv = gserialized2_get_srid(&s); + //printf("srid=%d rv=%d\n",srid,rv); + CU_ASSERT_EQUAL(rv, SRID_UNKNOWN); + + srid = SRID_UNKNOWN; + gserialized2_set_srid(&s, srid); + rv = gserialized2_get_srid(&s); + CU_ASSERT_EQUAL(rv, srid); + + srid = SRID_UNKNOWN; + gserialized2_set_srid(&s, srid); + rv = gserialized2_get_srid(&s); + CU_ASSERT_EQUAL(rv, srid); + + srid = 100000; + gserialized2_set_srid(&s, srid); + rv = gserialized2_get_srid(&s); + CU_ASSERT_EQUAL(rv, srid); +} + +static void test_gserialized2_from_lwgeom_size(void) +{ + LWGEOM *g; + size_t size = 0; + + g = lwgeom_from_wkt("POINT(0 0)", LW_PARSER_CHECK_NONE); + size = gserialized2_from_lwgeom_size(g); + CU_ASSERT_EQUAL( size, 32 ); + lwgeom_free(g); + + g = lwgeom_from_wkt("POINT(0 0 0)", LW_PARSER_CHECK_NONE); + size = gserialized2_from_lwgeom_size(g); + CU_ASSERT_EQUAL( size, 40 ); + lwgeom_free(g); + + g = lwgeom_from_wkt("MULTIPOINT(0 0 0, 1 1 1)", LW_PARSER_CHECK_NONE); + size = gserialized2_from_lwgeom_size(g); + CU_ASSERT_EQUAL( size, 80 ); + lwgeom_free(g); + + g = lwgeom_from_wkt("LINESTRING(0 0, 1 1)", LW_PARSER_CHECK_NONE); + size = gserialized2_from_lwgeom_size(g); + CU_ASSERT_EQUAL( size, 48 ); + lwgeom_free(g); + + g = lwgeom_from_wkt("MULTILINESTRING((0 0, 1 1),(0 0, 1 1))", LW_PARSER_CHECK_NONE); + size = gserialized2_from_lwgeom_size(g); + CU_ASSERT_EQUAL( size, 96 ); + lwgeom_free(g); + + g = lwgeom_from_wkt("POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))", LW_PARSER_CHECK_NONE); + size = gserialized2_from_lwgeom_size(g); + CU_ASSERT_EQUAL( size, 104 ); + lwgeom_free(g); + + g = lwgeom_from_wkt("POLYGON((-1 -1, -1 2, 2 2, 2 -1, -1 -1), (0 0, 0 1, 1 1, 1 0, 0 0))", LW_PARSER_CHECK_NONE); + size = gserialized2_from_lwgeom_size(g); + CU_ASSERT_EQUAL( size, 184 ); + lwgeom_free(g); + +} + + +static void test_lwgeom_from_gserialized2(void) +{ + LWGEOM *geom1, *geom2; + GSERIALIZED *g1, *g2; + char *in_ewkt, *out_ewkt; + size_t i = 0; + + char *ewkt[] = + { + "POINT EMPTY", + "POINT(0 0.2)", + "LINESTRING EMPTY", + "LINESTRING(-1 -1,-1 2.5,2 2,2 -1)", + "MULTIPOINT EMPTY", + "MULTIPOINT(0.9 0.9,0.9 0.9,0.9 0.9,0.9 0.9,0.9 0.9,0.9 0.9)", + "MULTIPOINT(0.9 0.9,0.9 0.9,EMPTY,0.9 0.9,0.9 0.9,0.9 0.9)", + "SRID=1;MULTILINESTRING EMPTY", + "SRID=1;MULTILINESTRING((-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1))", + "POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0))", + "POLYGON EMPTY", + "SRID=4326;POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0))", + "SRID=4326;POLYGON EMPTY", + "SRID=4326;POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5))", + "SRID=100000;POLYGON((-1 -1 3,-1 2.5 3,2 2 3,2 -1 3,-1 -1 3),(0 0 3,0 1 3,1 1 3,1 0 3,0 0 3),(-0.5 -0.5 3,-0.5 -0.4 3,-0.4 -0.4 3,-0.4 -0.5 3,-0.5 -0.5 3))", + "SRID=4326;MULTIPOLYGON(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)),((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)))", + "SRID=4326;MULTIPOLYGON EMPTY", + "SRID=4326;GEOMETRYCOLLECTION(POINT(0 1),POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0)),MULTIPOLYGON(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5))))", + "SRID=4326;GEOMETRYCOLLECTION EMPTY", + "SRID=4326;GEOMETRYCOLLECTION(POINT EMPTY,MULTIPOLYGON EMPTY)", + "SRID=4326;GEOMETRYCOLLECTION(POINT(0 0.2),POINT EMPTY,POINT(0 0.2))", + "MULTICURVE((5 5 1 3,3 5 2 2,3 3 3 1,0 3 1 1),CIRCULARSTRING(0 0 0 0,0.26794 1 3 -2,0.5857864 1.414213 1 2))", + "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(-2 0,-1 -1,0 0,1 -1,2 0,0 2,-2 0),(-1 0,0 0.5,1 0,0 1,-1 0)),((7 8,10 10,6 14,4 11,7 8)))", + "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING EMPTY))", + "POLYHEDRALSURFACE(((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),((0 0 1,1 0 1,1 1 1,0 1 1,0 0 1)),((0 0 0,0 0 1,0 1 1,0 1 0,0 0 0)),((1 0 0,1 1 0,1 1 1,1 0 1,1 0 0)),((0 0 0,1 0 0,1 0 1,0 0 1,0 0 0)),((0 1 0,0 1 1,1 1 1,1 1 0,0 1 0)))", + }; + + for ( i = 0; i < (sizeof ewkt/sizeof(char*)); i++ ) + { + size_t size1, size2; + + in_ewkt = ewkt[i]; + geom1 = lwgeom_from_wkt(in_ewkt, LW_PARSER_CHECK_NONE); + lwgeom_add_bbox(geom1); + if (geom1->bbox) gbox_float_round(geom1->bbox); + size1 = gserialized2_from_lwgeom_size(geom1); + g1 = gserialized2_from_lwgeom(geom1, &size2); + CU_ASSERT_EQUAL(size1, size2); + + geom2 = lwgeom_from_gserialized2(g1); + out_ewkt = lwgeom_to_ewkt(geom2); + + /* printf("\n in = %s\nout = %s\n", in_ewkt, out_ewkt); */ + CU_ASSERT_STRING_EQUAL(in_ewkt, out_ewkt); + g2 = gserialized2_from_lwgeom(geom2, &size2); + CU_ASSERT_EQUAL(g1->gflags, g2->gflags); + + /* either both or none of the bboxes are null */ + CU_ASSERT(geom1->bbox != NULL || geom2->bbox == NULL); + + /* either both are null or they are the same */ + CU_ASSERT(geom1->bbox == NULL || gbox_same(geom1->bbox, geom2->bbox)); + + lwfree(out_ewkt); + lwfree(g1); + lwfree(g2); + lwgeom_free(geom1); + lwgeom_free(geom2); + + /* SOLID / BOX INTERACTION */ + geom1 = lwgeom_from_wkt(in_ewkt, LW_PARSER_CHECK_NONE); + lwgeom_add_bbox(geom1); + FLAGS_SET_SOLID(geom1->flags, 1); + size1 = gserialized2_from_lwgeom_size(geom1); + g1 = gserialized2_from_lwgeom(geom1, &size2); + CU_ASSERT_EQUAL(size1, size2); + CU_ASSERT_EQUAL(LWSIZE_GET(g1->size), size2); + geom2 = lwgeom_from_gserialized2(g1); + CU_ASSERT_EQUAL(geom1->flags, geom2->flags); + g2 = gserialized2_from_lwgeom(geom2, &size2); + CU_ASSERT_EQUAL(LWSIZE_GET(g2->size), size2); + CU_ASSERT_EQUAL(g1->gflags, g2->gflags); + CU_ASSERT_EQUAL(FLAGS_GET_SOLID(geom2->flags), 1); + lwfree(g1); + lwfree(g2); + lwgeom_free(geom1); + lwgeom_free(geom2); + } + +} + + +static void test_gserialized2_is_empty(void) +{ + int i = 0; + struct gserialized_empty_cases { + const char* wkt; + int isempty; + }; + + struct gserialized_empty_cases cases[] = { + { "POINT EMPTY", 1 }, + { "POINT(1 1)", 0 }, + { "LINESTRING EMPTY", 1 }, + { "MULTILINESTRING EMPTY", 1 }, + { "MULTILINESTRING(EMPTY)", 1 }, + { "MULTILINESTRING(EMPTY,EMPTY)", 1 }, + { "MULTILINESTRING(EMPTY,(0 0,1 1))", 0 }, + { "MULTILINESTRING((0 0,1 1),EMPTY)", 0 }, + { "MULTILINESTRING(EMPTY,(0 0,1 1),EMPTY)", 0 }, + { "MULTILINESTRING(EMPTY,EMPTY,EMPTY)", 1 }, + { "GEOMETRYCOLLECTION(POINT EMPTY,MULTILINESTRING(EMPTY,EMPTY,EMPTY))", 1 }, + { "GEOMETRYCOLLECTION(POINT EMPTY,MULTILINESTRING(EMPTY),POINT(1 1))", 0 }, + { "GEOMETRYCOLLECTION(POINT EMPTY,MULTILINESTRING(EMPTY, (0 0)),POINT EMPTY)", 0 }, + { "GEOMETRYCOLLECTION(POLYGON EMPTY,POINT EMPTY,MULTILINESTRING(EMPTY,EMPTY),POINT EMPTY)", 1 }, + { "GEOMETRYCOLLECTION(POLYGON EMPTY,GEOMETRYCOLLECTION(POINT EMPTY),MULTILINESTRING(EMPTY,EMPTY),POINT EMPTY)", 1 }, + { NULL, 0 } + }; + + while( cases[i].wkt ) + { + // i = 11; + LWGEOM *lw = lwgeom_from_wkt(cases[i].wkt, LW_PARSER_CHECK_NONE); + GSERIALIZED *g = gserialized2_from_lwgeom(lw, 0); + int ie = gserialized2_is_empty(g); + // printf("%s: we say %d, they say %d\n", cases[i].wkt, cases[i].isempty, ie); + CU_ASSERT_EQUAL(ie, cases[i].isempty); + lwgeom_free(lw); + lwfree(g); + i++; + } +} + + + +static void test_on_gser2_lwgeom_count_vertices(void) +{ + LWGEOM *lwgeom; + GSERIALIZED *g_ser1; + size_t ret_size; + + lwgeom = lwgeom_from_wkt("MULTIPOINT(-1 -1,-1 2.5,2 2,2 -1,1 1,2 2,4 5)", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(lwgeom_count_vertices(lwgeom),7); + g_ser1 = gserialized2_from_lwgeom(lwgeom, &ret_size); + lwgeom_free(lwgeom); + + lwgeom = lwgeom_from_gserialized2(g_ser1); + CU_ASSERT_EQUAL(lwgeom_count_vertices(lwgeom),7); + lwgeom_free(lwgeom); + + lwgeom = lwgeom_from_gserialized2(g_ser1); + + CU_ASSERT_EQUAL(lwgeom_count_vertices(lwgeom),7); + lwgeom_free(lwgeom); + + lwfree(g_ser1); + +} + + +static void test_gserialized2_peek_gbox_p_no_box_when_empty(void) +{ + uint32_t i; + + char *ewkt[] = + { + "POINT EMPTY", + "LINESTRING EMPTY", + "MULTIPOINT EMPTY", + "MULTIPOINT (EMPTY)", + "MULTILINESTRING EMPTY", + "MULTILINESTRING (EMPTY)" + }; + + for ( i = 0; i < (sizeof ewkt/sizeof(char*)); i++ ) + { + LWGEOM* geom = lwgeom_from_wkt(ewkt[i], LW_PARSER_CHECK_NONE); + GBOX box; + gbox_init(&box); + + GSERIALIZED* gser = gserialized2_from_lwgeom(geom, NULL); + + CU_ASSERT_FALSE(gserialized2_has_bbox(gser)); + + CU_ASSERT_EQUAL(LW_FAILURE, gserialized2_peek_gbox_p(gser, &box)); + + lwgeom_free(geom); + lwfree(gser); + } +} + +static void test_gserialized2_peek_gbox_p_gets_correct_box(void) +{ + uint32_t i; + + char *ewkt[] = + { + "POINT (2.2945672355 48.85822923236)", + "POINTZ (2.2945672355 48.85822923236 15)", + "POINTM (2.2945672355 48.85822923236 12)", + "POINT ZM (2.2945672355 48.85822923236 12 2)", + "MULTIPOINT ((-76.45402132523 44.225406213532))", + "MULTIPOINT Z ((-76.45402132523 44.225406213532 112))", + "MULTIPOINT ZM ((-76.45402132523 44.225406213532 112 44))", + "LINESTRING (2.2945672355 48.85822923236, -76.45402132523 44.225406213532)", + "LINESTRING Z (2.2945672355 48.85822923236 6, -76.45402132523 44.225406213532 8)", + "LINESTRING ZM (2.2945672355 48.85822923236 3 2, -76.45402132523 44.225406213532 9 4)", + "MULTILINESTRING ((2.2945672355 48.85822923236, -76.45402132523 44.225406213532))", + "MULTILINESTRING Z ((2.2945672355 48.85822923236 4, -76.45402132523 44.225406213532 3))" + }; + + for ( i = 0; i < (sizeof ewkt/sizeof(char*)); i++ ) + { + LWGEOM* geom = lwgeom_from_wkt(ewkt[i], LW_PARSER_CHECK_NONE); + GBOX box_from_peek; + GBOX box_from_lwgeom; + gbox_init(&box_from_peek); + gbox_init(&box_from_lwgeom); + + GSERIALIZED* gser = gserialized2_from_lwgeom(geom, NULL); + + CU_ASSERT_FALSE(gserialized2_has_bbox(gser)); + + lwgeom_calculate_gbox(geom, &box_from_lwgeom); + gserialized2_peek_gbox_p(gser, &box_from_peek); + + gbox_float_round(&box_from_lwgeom); + + CU_ASSERT_TRUE(gbox_same(&box_from_peek, &box_from_lwgeom)); + + lwgeom_free(geom); + lwfree(gser); + } +} + +static void test_gserialized2_peek_gbox_p_fails_for_unsupported_cases(void) +{ + uint32_t i; + + char *ewkt[] = + { + "MULTIPOINT ((-76.45402132523 44.225406213532), (-72 33))", + "LINESTRING (2.2945672355 48.85822923236, -76.45402132523 44.225406213532, -72 33)", + "MULTILINESTRING ((2.2945672355 48.85822923236, -76.45402132523 44.225406213532, -72 33))", + "MULTILINESTRING ((2.2945672355 48.85822923236, -76.45402132523 44.225406213532), (-72 33, -71 32))" + }; + + for ( i = 0; i < (sizeof ewkt/sizeof(char*)); i++ ) + { + LWGEOM* geom = lwgeom_from_wkt(ewkt[i], LW_PARSER_CHECK_NONE); + GBOX box; + gbox_init(&box); + lwgeom_drop_bbox(geom); + + /* Construct a GSERIALIZED* that doesn't have a box, so that we can test the + * actual logic of the peek function */ + size_t expected_size = gserialized2_from_lwgeom_size(geom); + GSERIALIZED* gser = lwalloc(expected_size); + uint8_t* ptr = (uint8_t*) gser; + + ptr += 8; // Skip header + gserialized2_from_lwgeom_any(geom, ptr); + gser->gflags = lwflags_get_g2flags(geom->flags); + + CU_ASSERT_FALSE(gserialized2_has_bbox(gser)); + CU_ASSERT_EQUAL(LW_FAILURE, gserialized2_peek_gbox_p(gser, &box)); + + lwgeom_free(geom); + lwfree(gser); + } +} + +static int +peek2_point_helper(char *geometry, POINT4D *p) +{ + cu_error_msg_reset(); + p->x = p->y = p->z = p->m = 0; + LWGEOM *geom = lwgeom_from_wkt(geometry, LW_PARSER_CHECK_NONE); + CU_ASSERT(geom != NULL); + GSERIALIZED *g = gserialized2_from_lwgeom(geom, NULL); + CU_ASSERT(g != NULL); + + int ret = gserialized2_peek_first_point(g, p); + lwfree(g); + lwgeom_free(geom); + + return ret; +} + +static void +test_gserialized2_peek_first_point(void) +{ + POINT4D p = {0}; + + CU_ASSERT(peek2_point_helper("POINT(1 2)", &p) == LW_SUCCESS); + CU_ASSERT_EQUAL(p.x, 1); + CU_ASSERT_EQUAL(p.y, 2); + + CU_ASSERT(peek2_point_helper("POINTZ(10 20 30)", &p) == LW_SUCCESS); + CU_ASSERT_EQUAL(p.x, 10); + CU_ASSERT_EQUAL(p.y, 20); + CU_ASSERT_EQUAL(p.z, 30); + + CU_ASSERT(peek2_point_helper("POINTM(100 200 300)", &p) == LW_SUCCESS); + CU_ASSERT_EQUAL(p.x, 100); + CU_ASSERT_EQUAL(p.y, 200); + CU_ASSERT_EQUAL(p.m, 300); + + CU_ASSERT(peek2_point_helper("POINTZM(1000 2000 3000 4000)", &p) == LW_SUCCESS); + CU_ASSERT_EQUAL(p.x, 1000); + CU_ASSERT_EQUAL(p.y, 2000); + CU_ASSERT_EQUAL(p.z, 3000); + CU_ASSERT_EQUAL(p.m, 4000); + + CU_ASSERT(peek2_point_helper("MULTIPOINT((0 0), (1 1))", &p) == LW_FAILURE); + CU_ASSERT(peek2_point_helper("LINESTRING(0 0, 1 1)", &p) == LW_FAILURE); + CU_ASSERT(peek2_point_helper("MULTILINESTRING((0 0, 1 1), (0 0, 1 1))", &p) == LW_FAILURE); + CU_ASSERT(peek2_point_helper("POLYGON((0 0, 1 1, 1 0, 0 0))", &p) == LW_FAILURE); +} + +/* +** Used by test harness to register the tests in this file. +*/ +void gserialized2_suite_setup(void); +void gserialized2_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("serialization/deserialization v2", NULL, NULL); + PG_ADD_TEST(suite, test_g2flags_macros); + PG_ADD_TEST(suite, test_gserialized2_srid); + PG_ADD_TEST(suite, test_gserialized2_from_lwgeom_size); + PG_ADD_TEST(suite, test_lwgeom_from_gserialized2); + PG_ADD_TEST(suite, test_on_gser2_lwgeom_count_vertices); + PG_ADD_TEST(suite, test_gserialized2_is_empty); + PG_ADD_TEST(suite, test_gserialized2_peek_gbox_p_no_box_when_empty); + PG_ADD_TEST(suite, test_gserialized2_peek_gbox_p_gets_correct_box); + PG_ADD_TEST(suite, test_gserialized2_peek_gbox_p_fails_for_unsupported_cases); + PG_ADD_TEST(suite, test_gserialized2_extended_flags); + PG_ADD_TEST(suite, test_gserialized2_peek_first_point); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_homogenize.c b/mgist-postgis/liblwgeom/cunit/cu_homogenize.c new file mode 100644 index 0000000..2fbda97 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_homogenize.c @@ -0,0 +1,263 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2009 Olivier Courtin + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +static void do_geom_test(char * in, char * out) +{ + LWGEOM *g, *h; + char *tmp; + + g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + h = lwgeom_homogenize(g); + tmp = lwgeom_to_ewkt(h); + if (strcmp(tmp, out)) + fprintf(stderr, "\nIn: %s\nOut: %s\nExp: %s\n", + in, tmp, out); + CU_ASSERT_STRING_EQUAL(tmp, out); + lwfree(tmp); + lwgeom_free(g); + /* See http://trac.osgeo.org/postgis/ticket/1104 */ + lwgeom_free(h); +} + + +static void test_coll_point(void) +{ + do_geom_test("GEOMETRYCOLLECTION(POINT(1 2))", + "POINT(1 2)"); + + do_geom_test("GEOMETRYCOLLECTION(POINT(1 2),POINT(3 4))", + "MULTIPOINT(1 2,3 4)"); + + do_geom_test("GEOMETRYCOLLECTION(POINT(1 2),POINT(3 4),POINT(5 6))", + "MULTIPOINT(1 2,3 4,5 6)"); + + do_geom_test("GEOMETRYCOLLECTION(MULTIPOINT(1 2,3 4),POINT(5 6))", + "MULTIPOINT(1 2,3 4,5 6)"); + + do_geom_test("GEOMETRYCOLLECTION(POINT(1 2),MULTIPOINT(3 4,5 6))", + "MULTIPOINT(1 2,3 4,5 6)"); + + do_geom_test("GEOMETRYCOLLECTION(MULTIPOINT(1 2,3 4),MULTIPOINT(5 6,7 8))", + "MULTIPOINT(1 2,3 4,5 6,7 8)"); +} + + +static void test_coll_line(void) +{ + do_geom_test("GEOMETRYCOLLECTION(LINESTRING(1 2,3 4))", + "LINESTRING(1 2,3 4)"); + + do_geom_test("GEOMETRYCOLLECTION(LINESTRING(1 2,3 4),LINESTRING(5 6,7 8))", + "MULTILINESTRING((1 2,3 4),(5 6,7 8))"); + + do_geom_test("GEOMETRYCOLLECTION(LINESTRING(1 2,3 4),LINESTRING(5 6,7 8),LINESTRING(9 10,11 12))", + "MULTILINESTRING((1 2,3 4),(5 6,7 8),(9 10,11 12))"); + + do_geom_test("GEOMETRYCOLLECTION(MULTILINESTRING((1 2,3 4),(5 6,7 8)),LINESTRING(9 10,11 12))", + "MULTILINESTRING((1 2,3 4),(5 6,7 8),(9 10,11 12))"); + + do_geom_test("GEOMETRYCOLLECTION(LINESTRING(1 2,3 4),MULTILINESTRING((5 6,7 8),(9 10,11 12)))", + "MULTILINESTRING((1 2,3 4),(5 6,7 8),(9 10,11 12))"); + + do_geom_test("GEOMETRYCOLLECTION(MULTILINESTRING((1 2,3 4),(5 6,7 8)),MULTILINESTRING((9 10,11 12),(13 14,15 16)))", + "MULTILINESTRING((1 2,3 4),(5 6,7 8),(9 10,11 12),(13 14,15 16))"); +} + + +static void test_coll_poly(void) +{ + do_geom_test("GEOMETRYCOLLECTION(POLYGON((1 2,3 4,5 6,1 2)))", + "POLYGON((1 2,3 4,5 6,1 2))"); + + do_geom_test("GEOMETRYCOLLECTION(POLYGON((1 2,3 4,5 6,1 2)),POLYGON((7 8,9 10,11 12,7 8)))", + "MULTIPOLYGON(((1 2,3 4,5 6,1 2)),((7 8,9 10,11 12,7 8)))"); + + do_geom_test("GEOMETRYCOLLECTION(POLYGON((1 2,3 4,5 6,1 2)),POLYGON((7 8,9 10,11 12,7 8)),POLYGON((13 14,15 16,17 18,13 14)))", + "MULTIPOLYGON(((1 2,3 4,5 6,1 2)),((7 8,9 10,11 12,7 8)),((13 14,15 16,17 18,13 14)))"); + + do_geom_test("GEOMETRYCOLLECTION(MULTIPOLYGON(((1 2,3 4,5 6,1 2)),((7 8,9 10,11 12,7 8))),POLYGON((13 14,15 16,17 18,13 14)))", + "MULTIPOLYGON(((1 2,3 4,5 6,1 2)),((7 8,9 10,11 12,7 8)),((13 14,15 16,17 18,13 14)))"); + + do_geom_test("GEOMETRYCOLLECTION(POLYGON((1 2,3 4,5 6,1 2)),MULTIPOLYGON(((7 8,9 10,11 12,7 8)),((13 14,15 16,17 18,13 14))))", + "MULTIPOLYGON(((1 2,3 4,5 6,1 2)),((7 8,9 10,11 12,7 8)),((13 14,15 16,17 18,13 14)))"); + + do_geom_test("GEOMETRYCOLLECTION(MULTIPOLYGON(((1 2,3 4,5 6,1 2)),((7 8,9 10,11 12,7 8))),MULTIPOLYGON(((13 14,15 16,17 18,13 14)),((19 20,21 22,23 24,19 20))))", + "MULTIPOLYGON(((1 2,3 4,5 6,1 2)),((7 8,9 10,11 12,7 8)),((13 14,15 16,17 18,13 14)),((19 20,21 22,23 24,19 20)))"); +} + + +static void test_coll_coll(void) +{ + /* Two different types together must produce a Collection as output */ + do_geom_test("GEOMETRYCOLLECTION(POINT(1 2),LINESTRING(3 4,5 6))", + "GEOMETRYCOLLECTION(POINT(1 2),LINESTRING(3 4,5 6))"); + + do_geom_test("GEOMETRYCOLLECTION(LINESTRING(1 2,3 4),POLYGON((5 6,7 8,9 10,5 6)))", + "GEOMETRYCOLLECTION(LINESTRING(1 2,3 4),POLYGON((5 6,7 8,9 10,5 6)))"); + + + /* Ability to produce a single MULTI with same type */ + do_geom_test("GEOMETRYCOLLECTION(POINT(1 2),LINESTRING(3 4,5 6),POINT(7 8))", + "GEOMETRYCOLLECTION(MULTIPOINT(1 2,7 8),LINESTRING(3 4,5 6))"); + + do_geom_test("GEOMETRYCOLLECTION(POINT(1 2),LINESTRING(3 4,5 6),MULTIPOINT(7 8,9 10))", + "GEOMETRYCOLLECTION(MULTIPOINT(1 2,7 8,9 10),LINESTRING(3 4,5 6))"); + + + /* Recursive Collection handle */ + do_geom_test("GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(1 2))))", + "POINT(1 2)"); + + do_geom_test("GEOMETRYCOLLECTION(POINT(1 2),GEOMETRYCOLLECTION(LINESTRING(3 4,5 6)))", + "GEOMETRYCOLLECTION(POINT(1 2),LINESTRING(3 4,5 6))"); + + + /* EMPTY Collection */ + do_geom_test("GEOMETRYCOLLECTION EMPTY", + "GEOMETRYCOLLECTION EMPTY"); + + + /* Recursive EMPTY Collection */ + do_geom_test("GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY)", + "GEOMETRYCOLLECTION EMPTY"); + + do_geom_test("GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION EMPTY))", + "GEOMETRYCOLLECTION EMPTY"); +} + +static void test_coll_curve(void) +{ + /* Two different types together must produce a Collection as output */ + do_geom_test("GEOMETRYCOLLECTION(CIRCULARSTRING(0 0,1 1,2 2))", + "CIRCULARSTRING(0 0,1 1,2 2)"); + + do_geom_test("GEOMETRYCOLLECTION(CIRCULARSTRING(0 0,1 1,2 2),CIRCULARSTRING(0 0,1 1,2 2))", + "MULTICURVE(CIRCULARSTRING(0 0,1 1,2 2),CIRCULARSTRING(0 0,1 1,2 2))"); +} + +static void test_geom(void) +{ + /* Already simple geometry */ + do_geom_test("POINT(1 2)", + "POINT(1 2)"); + + do_geom_test("LINESTRING(1 2,3 4)", + "LINESTRING(1 2,3 4)"); + + do_geom_test("POLYGON((1 2,3 4,5 6,1 2))", + "POLYGON((1 2,3 4,5 6,1 2))"); + + do_geom_test("POLYGON((1 2,3 4,5 6,1 2),(7 8,9 10,11 12,7 8))", + "POLYGON((1 2,3 4,5 6,1 2),(7 8,9 10,11 12,7 8))"); + + + /* Empty geometry */ + do_geom_test("GEOMETRYCOLLECTION EMPTY", + "GEOMETRYCOLLECTION EMPTY"); + + + /* A MULTI with a single geometry inside */ + do_geom_test("MULTIPOINT(1 2)", + "POINT(1 2)"); + + do_geom_test("MULTILINESTRING((1 2,3 4))", + "LINESTRING(1 2,3 4)"); + + do_geom_test("MULTIPOLYGON(((1 2,3 4,5 6,1 2)))", + "POLYGON((1 2,3 4,5 6,1 2))"); + + do_geom_test("MULTIPOLYGON(((1 2,3 4,5 6,1 2),(7 8,9 10,11 12,7 8)))", + "POLYGON((1 2,3 4,5 6,1 2),(7 8,9 10,11 12,7 8))"); + + + /* A real MULTI */ + do_geom_test("MULTIPOINT(1 2,3 4)", + "MULTIPOINT(1 2,3 4)"); + + do_geom_test("MULTILINESTRING((1 2,3 4),(5 6,7 8))", + "MULTILINESTRING((1 2,3 4),(5 6,7 8))"); + + do_geom_test("MULTIPOLYGON(((1 2,3 4,5 6,1 2)),((7 8,9 10,11 12,7 8)))", + "MULTIPOLYGON(((1 2,3 4,5 6,1 2)),((7 8,9 10,11 12,7 8)))"); + + do_geom_test("MULTIPOLYGON(((1 2,3 4,5 6,1 2),(7 8,9 10,11 12,7 8)),((13 14,15 16,17 18,13 14)))", + "MULTIPOLYGON(((1 2,3 4,5 6,1 2),(7 8,9 10,11 12,7 8)),((13 14,15 16,17 18,13 14)))"); + + + /* A Collection */ + do_geom_test("GEOMETRYCOLLECTION(POINT(1 2),LINESTRING(3 4,5 6))", + "GEOMETRYCOLLECTION(POINT(1 2),LINESTRING(3 4,5 6))"); + + + /* SRID */ + do_geom_test("SRID=4326;GEOMETRYCOLLECTION EMPTY", + "SRID=4326;GEOMETRYCOLLECTION EMPTY"); + + /* See http://trac.osgeo.org/postgis/ticket/2129 */ + do_geom_test("SRID=4326;GEOMETRYCOLLECTION(MULTIPOINT(0 0))", + "SRID=4326;POINT(0 0)"); + + /* See http://trac.osgeo.org/postgis/ticket/2129 */ + do_geom_test("SRID=4326;GEOMETRYCOLLECTION(MULTIPOINT(0 0, 1 2))", + "SRID=4326;MULTIPOINT(0 0,1 2)"); + + /* See http://trac.osgeo.org/postgis/ticket/2129 */ + do_geom_test("SRID=4326;GEOMETRYCOLLECTION(POINT(0 0),LINESTRING(0 0,10 0))", + "SRID=4326;GEOMETRYCOLLECTION(POINT(0 0),LINESTRING(0 0,10 0))"); + + do_geom_test("SRID=4326;POINT(1 2)", + "SRID=4326;POINT(1 2)"); + + do_geom_test("SRID=4326;MULTIPOINT(1 2)", + "SRID=4326;POINT(1 2)"); + + do_geom_test("SRID=4326;MULTIPOINT(1 2,3 4)", + "SRID=4326;MULTIPOINT(1 2,3 4)"); + + do_geom_test("SRID=4326;MULTILINESTRING((1 2,3 4))", + "SRID=4326;LINESTRING(1 2,3 4)"); + + do_geom_test("SRID=4326;MULTILINESTRING((1 2,3 4),(5 6,7 8))", + "SRID=4326;MULTILINESTRING((1 2,3 4),(5 6,7 8))"); + + /* 3D and 4D */ + do_geom_test("POINT(1 2 3)", + "POINT(1 2 3)"); + + do_geom_test("POINTM(1 2 3)", + "POINTM(1 2 3)"); + + do_geom_test("POINT(1 2 3 4)", + "POINT(1 2 3 4)"); +} + +/* +** Used by test harness to register the tests in this file. +*/ +void homogenize_suite_setup(void); +void homogenize_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("homogenize", NULL, NULL); + PG_ADD_TEST(suite, test_coll_point); + PG_ADD_TEST(suite, test_coll_line); + PG_ADD_TEST(suite, test_coll_poly); + PG_ADD_TEST(suite, test_coll_coll); + PG_ADD_TEST(suite, test_geom); + PG_ADD_TEST(suite, test_coll_curve); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_in_encoded_polyline.c b/mgist-postgis/liblwgeom/cunit/cu_in_encoded_polyline.c new file mode 100644 index 0000000..597493c --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_in_encoded_polyline.c @@ -0,0 +1,65 @@ +/********************************************************************** +* +* PostGIS - Spatial Types for PostgreSQL +* http://postgis.net +* +* Copyright 2014 Kashif Rasul and +* Shoaib Burq +* +* This is free software; you can redistribute and/or modify it under +* the terms of the GNU General Public Licence. See the COPYING file. +* +**********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +static void do_encoded_polyline_test(char * in, int precision, char * out) +{ + LWGEOM *g; + char * h; + size_t size; + + g = lwgeom_from_encoded_polyline(in, precision); + h = lwgeom_to_wkt(g, WKT_EXTENDED, 15, &size); + + if (strcmp(h, out)) + fprintf(stderr, "\nIn: %s\nOut: %s\nTheo: %s\n", in, h, out); + + CU_ASSERT_STRING_EQUAL(h, out); + + lwgeom_free(g); + if ( h ) lwfree(h); +} + +static void in_encoded_polyline_test_geoms(void) +{ + do_encoded_polyline_test( + "_p~iF~ps|U_ulLnnqC_mqNvxq`@", + 5, + "SRID=4326;LINESTRING(-120.2 38.5,-120.95 40.7,-126.453 43.252)"); +} + +static void in_encoded_polyline_test_precision(void) +{ + do_encoded_polyline_test( + "o}~~|AdshNoSsBgd@eGoBlm@wKhj@~@?", + 6, + "SRID=4326;LINESTRING(-0.250691 49.283048,-0.250633 49.283376,-0.250502 49.283972,-0.251245 49.284028,-0.251938 49.284232,-0.251938 49.2842)"); +} + +/* +** Used by test harness to register the tests in this file. +*/ +void in_encoded_polyline_suite_setup(void); +void in_encoded_polyline_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("encoded_polyline_input", NULL, NULL); + PG_ADD_TEST(suite, in_encoded_polyline_test_geoms); + PG_ADD_TEST(suite, in_encoded_polyline_test_precision); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_in_geojson.c b/mgist-postgis/liblwgeom/cunit/cu_in_geojson.c new file mode 100644 index 0000000..439cf9b --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_in_geojson.c @@ -0,0 +1,248 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright 2013 Sandro Santilli + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +static void do_geojson_test(const char * exp, char * in, char * exp_srs) +{ + LWGEOM *g; + char * h = NULL; + char * srs = NULL; + size_t size; + + g = lwgeom_from_geojson(in, &srs); + if ( ! g ) + { + fprintf(stderr, "\nIn: %s\nExp: %s\nObt: %s\n", in, exp, cu_error_msg); + CU_ASSERT(g != NULL); + return; + } + + h = lwgeom_to_wkt(g, WKT_EXTENDED, 15, &size); + + if (strcmp(h, exp)) + { + fprintf(stderr, "\nIn: %s\nExp: %s\nObt: %s\n", in, exp, h); + CU_ASSERT_STRING_EQUAL(h, exp); + } + else + { + CU_ASSERT_STRING_EQUAL(h, exp); + } + + if ( exp_srs ) + { + if ( ! srs ) + { + fprintf(stderr, "\nIn: %s\nExp: %s\nObt: (null)\n", in, exp_srs); + CU_ASSERT_EQUAL(srs, exp_srs); + } + else if (strcmp(srs, exp_srs)) + { + fprintf(stderr, "\nIn: %s\nExp: %s\nObt: %s\n", in, exp_srs, srs); + CU_ASSERT_STRING_EQUAL(srs, exp_srs); + } + } + else if ( srs ) + { + fprintf(stderr, "\nIn: %s\nExp: (null)\nObt: %s\n", in, srs); + CU_ASSERT_EQUAL(srs, exp_srs); + } + + lwgeom_free(g); + if ( h ) lwfree(h); + if ( srs ) lwfree(srs); +} + +static void in_geojson_test_srid(void) +{ + /* Linestring */ + do_geojson_test( + "LINESTRING(0 1,2 3,4 5)", + "{\"type\":\"LineString\",\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}},\"coordinates\":[[0,1],[2,3],[4,5]]}", + "EPSG:4326"); + + /* Polygon */ + do_geojson_test( + "POLYGON((0 1,2 3,4 5,0 1))", + "{\"type\":\"Polygon\",\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}},\"coordinates\":[[[0,1],[2,3],[4,5],[0,1]]]}", + "EPSG:4326"); + + /* Polygon - with internal ring */ + do_geojson_test( + "POLYGON((0 1,2 3,4 5,0 1),(6 7,8 9,10 11,6 7))", + "{\"type\":\"Polygon\",\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}},\"coordinates\":[[[0,1],[2,3],[4,5],[0,1]],[[6,7],[8,9],[10,11],[6,7]]]}", + "EPSG:4326"); + + /* Multiline */ + do_geojson_test( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "{\"type\":\"MultiLineString\",\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}},\"coordinates\":[[[0,1],[2,3],[4,5]],[[6,7],[8,9],[10,11]]]}", + "EPSG:4326"); + + /* MultiPolygon */ + do_geojson_test( + "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "{\"type\":\"MultiPolygon\",\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}},\"coordinates\":[[[[0,1],[2,3],[4,5],[0,1]]],[[[6,7],[8,9],[10,11],[6,7]]]]}", + "EPSG:4326"); + + /* Empty GeometryCollection */ + do_geojson_test( + "GEOMETRYCOLLECTION EMPTY", + "{\"type\":\"GeometryCollection\",\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}},\"geometries\":[]}", + "EPSG:4326"); +} + +static void in_geojson_test_bbox(void) +{ + /* Linestring */ + do_geojson_test( + "LINESTRING(0 1,2 3,4 5)", + "{\"type\":\"LineString\",\"bbox\":[0,1,4,5],\"coordinates\":[[0,1],[2,3],[4,5]]}", + NULL); + + /* Polygon */ + do_geojson_test( + "POLYGON((0 1,2 3,4 5,0 1))", + "{\"type\":\"Polygon\",\"bbox\":[0,1,4,5],\"coordinates\":[[[0,1],[2,3],[4,5],[0,1]]]}", + NULL); + + /* Polygon - with internal ring */ + do_geojson_test( + "POLYGON((0 1,2 3,4 5,0 1),(6 7,8 9,10 11,6 7))", + "{\"type\":\"Polygon\",\"bbox\":[0,1,4,5],\"coordinates\":[[[0,1],[2,3],[4,5],[0,1]],[[6,7],[8,9],[10,11],[6,7]]]}", + NULL); + + /* Multiline */ + do_geojson_test( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "{\"type\":\"MultiLineString\",\"bbox\":[0,1,10,11],\"coordinates\":[[[0,1],[2,3],[4,5]],[[6,7],[8,9],[10,11]]]}", + NULL); + + /* MultiPolygon */ + do_geojson_test( + "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "{\"type\":\"MultiPolygon\",\"bbox\":[0,1,10,11],\"coordinates\":[[[[0,1],[2,3],[4,5],[0,1]]],[[[6,7],[8,9],[10,11],[6,7]]]]}", + NULL); + + /* GeometryCollection */ + do_geojson_test( + "GEOMETRYCOLLECTION(LINESTRING(0 1,-1 3),LINESTRING(2 3,4 5))", + "{\"type\":\"GeometryCollection\",\"bbox\":[-1,1,4,5],\"geometries\":[{\"type\":\"LineString\",\"coordinates\":[[0,1],[-1,3]]},{\"type\":\"LineString\",\"coordinates\":[[2,3],[4,5]]}]}", + NULL); + + /* Empty GeometryCollection */ + do_geojson_test( + "GEOMETRYCOLLECTION EMPTY", + "{\"type\":\"GeometryCollection\",\"geometries\":[]}", + NULL); +} + +static void in_geojson_test_geoms(void) +{ + /* Linestring */ + do_geojson_test( + "LINESTRING(0 1,2 3,4 5)", + "{\"type\":\"LineString\",\"coordinates\":[[0,1],[2,3],[4,5]]}", + NULL); + + /* Polygon */ + do_geojson_test( + "POLYGON((0 1,2 3,4 5,0 1))", + "{\"type\":\"Polygon\",\"coordinates\":[[[0,1],[2,3],[4,5],[0,1]]]}", + NULL); + + /* Polygon - with internal ring */ + do_geojson_test( + "POLYGON((0 1,2 3,4 5,0 1),(6 7,8 9,10 11,6 7))", + "{\"type\":\"Polygon\",\"coordinates\":[[[0,1],[2,3],[4,5],[0,1]],[[6,7],[8,9],[10,11],[6,7]]]}", + NULL); + + /* Multiline */ + do_geojson_test( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "{\"type\":\"MultiLineString\",\"coordinates\":[[[0,1],[2,3],[4,5]],[[6,7],[8,9],[10,11]]]}", + NULL); + + /* MultiPolygon */ + do_geojson_test( + "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[0,1],[2,3],[4,5],[0,1]]],[[[6,7],[8,9],[10,11],[6,7]]]]}", + NULL); + + /* MultiPolygon with internal rings */ + /* See http://trac.osgeo.org/postgis/ticket/2216 */ + do_geojson_test( + "MULTIPOLYGON(((4 0,0 -4,-4 0,0 4,4 0),(2 0,0 2,-2 0,0 -2,2 0)),((24 0,20 -4,16 0,20 4,24 0),(22 0,20 2,18 0,20 -2,22 0)),((44 0,40 -4,36 0,40 4,44 0),(42 0,40 2,38 0,40 -2,42 0)))", + "{'type':'MultiPolygon','coordinates':[[[[4,0],[0,-4],[-4,0],[0,4],[4,0]],[[2,0],[0,2],[-2,0],[0,-2],[2,0]]],[[[24,0],[20,-4],[16,0],[20,4],[24,0]],[[22,0],[20,2],[18,0],[20,-2],[22,0]]],[[[44,0],[40,-4],[36,0],[40,4],[44,0]],[[42,0],[40,2],[38,0],[40,-2],[42,0]]]]}", + NULL); + + /* GeometryCollection */ + do_geojson_test( + "GEOMETRYCOLLECTION(POINT(0 1),LINESTRING(2 3,4 5))", + "{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Point\",\"coordinates\":[0,1]},{\"type\":\"LineString\",\"coordinates\":[[2,3],[4,5]]}]}", + NULL); + + /* Empty GeometryCollection */ + do_geojson_test( + "GEOMETRYCOLLECTION EMPTY", + "{\"type\":\"GeometryCollection\",\"geometries\":[]}", + NULL); + + /* Empty Point */ + do_geojson_test( + "POINT EMPTY", + "{\"type\":\"Point\",\"coordinates\":[]}", + NULL); + + /* Empty LineString */ + do_geojson_test( + "LINESTRING EMPTY", + "{\"type\":\"LineString\",\"coordinates\":[]}", + NULL); + + /* Empty Polygon */ + do_geojson_test( + "POLYGON EMPTY", + "{\"type\":\"Polygon\",\"coordinates\":[]}", + NULL); + + /* Empty MultiPoint */ + do_geojson_test( + "MULTIPOINT EMPTY", + "{\"type\":\"MultiPoint\",\"coordinates\":[]}", + NULL); + + /* Empty MultiPolygon */ + do_geojson_test( + "MULTIPOLYGON EMPTY", + "{\"type\":\"MultiPolygon\",\"coordinates\":[]}", + NULL); +} + +/* +** Used by test harness to register the tests in this file. +*/ +void in_geojson_suite_setup(void); +void in_geojson_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("geojson_input", NULL, NULL); + PG_ADD_TEST(suite, in_geojson_test_srid); + PG_ADD_TEST(suite, in_geojson_test_bbox); + PG_ADD_TEST(suite, in_geojson_test_geoms); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_in_twkb.c b/mgist-postgis/liblwgeom/cunit/cu_in_twkb.c new file mode 100644 index 0000000..6dcc39a --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_in_twkb.c @@ -0,0 +1,242 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2010 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +/* +** Global variable to hold TWKB strings +*/ +static char *hex_a; +static char *hex_b; +static uint8_t precision = 0; +static uint8_t variant = 0; + +/* +** The suite initialization function. +** Create any re-used objects. +*/ +static int init_twkb_in_suite(void) +{ + hex_a = NULL; + hex_b = NULL; + return 0; +} + +/* +** The suite cleanup function. +** Frees any global objects. +*/ +static int clean_twkb_in_suite(void) +{ + if (hex_a) free(hex_a); + if (hex_b) free(hex_b); + hex_a = NULL; + hex_b = NULL; + return 0; +} + + +static void cu_twkb_in(char *wkt) +{ + LWGEOM_PARSER_RESULT pr; + LWGEOM *g_a, *g_b; + /* int i; char *hex; */ + + /* Turn WKT into geom */ + lwgeom_parse_wkt(&pr, wkt, LW_PARSER_CHECK_NONE); + if ( pr.errcode ) + { + printf("ERROR: %s\n", pr.message); + printf("POSITION: %d\n", pr.errlocation); + exit(0); + } + + /* Get the geom */ + g_a = pr.geom; + + /* Turn geom into TWKB */ + lwvarlena_t *twkb_a = lwgeom_to_twkb(g_a, variant, precision, precision, precision); + + // printf("\n Size: %ld\n", twkb_size_a); + + /* Turn TWKB back into geom */ + g_b = lwgeom_from_twkb((uint8_t *)twkb_a->data, LWSIZE_GET(twkb_a->size) - LWVARHDRSZ, LW_PARSER_CHECK_NONE); + + // printf("\n Org: %s\n 1st: %s\n 2nd: %s\n", wkt, lwgeom_to_ewkt(g_a), lwgeom_to_ewkt(g_b)); + + /* Turn geom to TWKB again */ + lwvarlena_t *twkb_b = lwgeom_to_twkb(g_b, variant, precision, precision, precision); + + /* Turn TWKB into hex for comparisons */ + if ( hex_a ) lwfree(hex_a); + if ( hex_b ) lwfree(hex_b); + hex_a = hexbytes_from_bytes((uint8_t *)twkb_a->data, LWSIZE_GET(twkb_a->size) - LWVARHDRSZ); + hex_b = hexbytes_from_bytes((uint8_t *)twkb_b->data, LWSIZE_GET(twkb_b->size) - LWVARHDRSZ); + + /* Clean up */ + lwfree(twkb_a); + lwfree(twkb_b); + lwgeom_parser_result_free(&pr); + lwgeom_free(g_b); +} + +static void test_twkb_in_point(void) +{ + cu_twkb_in("POINT(0 0 0 0)"); +// printf("old: %s\nnew: %s\n",hex_a, hex_b); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_twkb_in("POINT(1 1)"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_twkb_in("POINT EMPTY"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); +} + +static void test_twkb_in_linestring(void) +{ + cu_twkb_in("LINESTRING(0 0,1 1)"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_twkb_in("LINESTRING(0 0 1,1 1 2,2 2 3)"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_twkb_in("LINESTRING EMPTY"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); +} + +static void test_twkb_in_polygon(void) +{ + cu_twkb_in("POLYGON((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_twkb_in("POLYGON((0 0 0 1,0 1 0 2,1 1 0 3,1 0 0 4,0 0 0 5))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_twkb_in("POLYGON((0 0 0 1,0 1 0 2,1 1 0 3,1 0 0 4,0 0 0 5))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_twkb_in("POLYGON EMPTY"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); +} + +static void test_twkb_in_multipoint(void) +{ + cu_twkb_in("MULTIPOINT Z EMPTY"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_twkb_in("MULTIPOINT(1 2, EMPTY, 5 6)"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + // printf(" 1st: %s\n 2nd: %s\n", hex_a, hex_b); + + cu_twkb_in("MULTIPOINT(0 0 0,0 1 0,1 1 0,1 0 0,0 0 1)"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_twkb_in("MULTIPOINT(1 2 3, 1 2 3, 4 5 6, -3 -4 -5, -10 -5 -1)"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); +} + +static void test_twkb_in_multilinestring(void) +{ + cu_twkb_in("MULTILINESTRING((0 0,0 1),(1 1, 10 10))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_twkb_in("MULTILINESTRING((0 0,0 1),EMPTY,(1 1, 10 10))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_twkb_in("MULTILINESTRING((0 0 200000,0 1 10),(1 100000000 23, 10 10 45))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + // printf(" 1st: %s\n 2nd: %s\n", hex_a, hex_b); + + cu_twkb_in("MULTILINESTRING EMPTY"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); +} + +static void test_twkb_in_multipolygon(void) +{ + cu_twkb_in("MULTIPOLYGON(((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),((-1 -1 0,-1 2 0,2 2 0,2 -1 0,-1 -1 0),(0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + //printf("old: %s\nnew: %s\n",hex_a, hex_b); + + cu_twkb_in("MULTIPOLYGON EMPTY"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + //printf("old: %s\nnew: %s\n",hex_a, hex_b); +} + +static void test_twkb_in_collection(void) +{ + cu_twkb_in("GEOMETRYCOLLECTION(MULTIPOLYGON(((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0))),POLYGON((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),POINT(1 1 1),LINESTRING(0 0 0, 1 1 1))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_twkb_in("GEOMETRYCOLLECTION(POLYGON((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),POINT(1 1 1))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_twkb_in("GEOMETRYCOLLECTION EMPTY"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_twkb_in("GEOMETRYCOLLECTION(POINT(1 2 3), LINESTRING EMPTY, POINT(4 5 6))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_twkb_in("GEOMETRYCOLLECTION(POINT(1 2 3), POINT EMPTY, POINT(4 5 6))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); +} + +/* +** PRECISION TESTS HERE: We ALTER THE 'precision' GLOBAL +*/ + +static void test_twkb_in_precision(void) +{ + /* Try these cases at several different precisions */ + for ( precision = 1; precision <= 6; precision++ ) + { + cu_twkb_in("MULTILINESTRING((0 0,0 1),EMPTY,(1 1, 10 10))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_twkb_in("MULTIPOLYGON(((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),((-1 -1 0,-1 2 0,2 2 0,2 -1 0,-1 -1 0),(0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_twkb_in("GEOMETRYCOLLECTION(POLYGON((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),POINT(1 1 1))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_twkb_in("MULTILINESTRING((0 0 200000,0 1 10),(1 100000000 23, 10 10 45))"); + // printf("old: %s\nnew: %s\n",hex_a, hex_b); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + } + + /* Go back to default precision */ + precision = 0; +} + + + +/* +** Used by test harness to register the tests in this file. +*/ +void twkb_in_suite_setup(void); +void twkb_in_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("twkb_input", init_twkb_in_suite, clean_twkb_in_suite); + PG_ADD_TEST(suite, test_twkb_in_point); + PG_ADD_TEST(suite, test_twkb_in_linestring); + PG_ADD_TEST(suite, test_twkb_in_polygon); + PG_ADD_TEST(suite, test_twkb_in_multipoint); + PG_ADD_TEST(suite, test_twkb_in_multilinestring); + PG_ADD_TEST(suite, test_twkb_in_multipolygon); + PG_ADD_TEST(suite, test_twkb_in_collection); + PG_ADD_TEST(suite, test_twkb_in_precision); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_in_wkb.c b/mgist-postgis/liblwgeom/cunit/cu_in_wkb.c new file mode 100644 index 0000000..56cb1f0 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_in_wkb.c @@ -0,0 +1,303 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2010 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +/* +** Global variable to hold WKB strings +*/ +static char *hex_a; +static char *hex_b; + +/* +** The suite initialization function. +** Create any re-used objects. +*/ +static int init_wkb_in_suite(void) +{ + hex_a = NULL; + hex_b = NULL; + return 0; +} + +/* +** The suite cleanup function. +** Frees any global objects. +*/ +static int clean_wkb_in_suite(void) +{ + if (hex_a) free(hex_a); + if (hex_b) free(hex_b); + hex_a = NULL; + hex_b = NULL; + return 0; +} + +static void cu_wkb_malformed_in(char *hex) +{ + LWGEOM *g = lwgeom_from_hexwkb(hex, LW_PARSER_CHECK_ALL); + if (g) { + char *outhex = lwgeom_to_hexwkb_buffer(g, 0); + printf("cu_wkb_malformed_in input: %s\n", hex); + printf("cu_wkb_malformed_in output: %s\n", outhex); + lwfree(outhex); + } + CU_ASSERT( g == NULL ); +} + +static void cu_wkb_in(char *wkt) +{ + LWGEOM_PARSER_RESULT pr; + LWGEOM *g_a, *g_b; + lwvarlena_t *wkb_a, *wkb_b; + /* int i; char *hex; */ + + if ( hex_a ) free(hex_a); + if ( hex_b ) free(hex_b); + + /* Turn WKT into geom */ + lwgeom_parse_wkt(&pr, wkt, LW_PARSER_CHECK_NONE); + if ( pr.errcode ) + { + printf("ERROR: %s\n", pr.message); + printf("POSITION: %d\n", pr.errlocation); + exit(0); + } + + /* Get the geom */ + g_a = pr.geom; + + /* Turn geom into WKB */ + wkb_a = lwgeom_to_wkb_varlena(g_a, WKB_NDR | WKB_EXTENDED); + + /* Turn WKB back into geom */ + g_b = lwgeom_from_wkb((uint8_t *)wkb_a->data, LWSIZE_GET(wkb_a->size) - LWVARHDRSZ, LW_PARSER_CHECK_NONE); + + /* Turn geom to WKB again */ + wkb_b = lwgeom_to_wkb_varlena(g_b, WKB_NDR | WKB_EXTENDED); + + /* Turn geoms into WKB for comparisons */ + hex_a = hexbytes_from_bytes((uint8_t *)wkb_a->data, LWSIZE_GET(wkb_a->size) - LWVARHDRSZ); + hex_b = hexbytes_from_bytes((uint8_t *)wkb_b->data, LWSIZE_GET(wkb_b->size) - LWVARHDRSZ); + + /* Clean up */ + lwfree(wkb_a); + lwfree(wkb_b); + lwgeom_parser_result_free(&pr); + lwgeom_free(g_b); +} + +static void test_wkb_in_point(void) +{ + cu_wkb_in("POINT(0 0 0 0)"); +// printf("old: %s\nnew: %s\n",hex_a, hex_b); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_wkb_in("SRID=4;POINTM(1 1 1)"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_wkb_in("POINT EMPTY"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_wkb_in("SRID=4326;POINT EMPTY"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_wkb_in("POINT Z EMPTY"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_wkb_in("POINT M EMPTY"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_wkb_in("POINT ZM EMPTY"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + +} + +static void test_wkb_in_linestring(void) +{ + cu_wkb_in("LINESTRING(0 0,1 1)"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_wkb_in("LINESTRING(0 0 1,1 1 2,2 2 3)"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); +} + +static void test_wkb_in_polygon(void) +{ + cu_wkb_in("SRID=4;POLYGON((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_wkb_in("SRID=14;POLYGON((0 0 0 1,0 1 0 2,1 1 0 3,1 0 0 4,0 0 0 5))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_wkb_in("SRID=4;POLYGON((0 0 0 1,0 1 0 2,1 1 0 3,1 0 0 4,0 0 0 5))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_wkb_in("POLYGON EMPTY"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); +} + +static void test_wkb_in_multipoint(void) +{ + cu_wkb_in("SRID=4;MULTIPOINT(0 0 0,0 1 0,1 1 0,1 0 0,0 0 1)"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_wkb_in("MULTIPOINT(0 0 0, 0.26794919243112270647255365849413 1 3)"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); +} + +static void test_wkb_in_multilinestring(void) {} + +static void test_wkb_in_multipolygon(void) +{ + cu_wkb_in("SRID=14;MULTIPOLYGON(((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),((-1 -1 0,-1 2 0,2 2 0,2 -1 0,-1 -1 0),(0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + //printf("old: %s\nnew: %s\n",hex_a, hex_b); +} + +static void test_wkb_in_collection(void) +{ + cu_wkb_in("SRID=14;GEOMETRYCOLLECTION(POLYGON((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),POINT(1 1 1))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_wkb_in("GEOMETRYCOLLECTION EMPTY"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_wkb_in("SRID=14;GEOMETRYCOLLECTION(MULTIPOLYGON(((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0))),POLYGON((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),POINT(1 1 1),LINESTRING(0 0 0, 1 1 1))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + +} + +static void test_wkb_in_circularstring(void) +{ + cu_wkb_in("CIRCULARSTRING(0 -2,-2 0,0 2,2 0,0 -2)"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_wkb_in("CIRCULARSTRING(-5 0 0 4, 0 5 1 3, 5 0 2 2, 10 -5 3 1, 15 0 4 0)"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); + + cu_wkb_in("SRID=43;CIRCULARSTRING(-5 0 0 4, 0 5 1 3, 5 0 2 2, 10 -5 3 1, 15 0 4 0)"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); +} + +static void test_wkb_in_compoundcurve(void) +{ + cu_wkb_in("COMPOUNDCURVE(CIRCULARSTRING(0 0 0, 0.26794919243112270647255365849413 1 3, 0.5857864376269049511983112757903 1.4142135623730950488016887242097 1),(0.5857864376269049511983112757903 1.4142135623730950488016887242097 1,2 0 0,0 0 0))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); +} + +static void test_wkb_in_curvpolygon(void) +{ + cu_wkb_in("CURVEPOLYGON(CIRCULARSTRING(-2 0 0 0,-1 -1 1 2,0 0 2 4,1 -1 3 6,2 0 4 8,0 2 2 4,-2 0 0 0),(-1 0 1 2,0 0.5 2 4,1 0 3 6,0 1 3 4,-1 0 1 2))"); + CU_ASSERT_STRING_EQUAL(hex_a, hex_b); +} + +static void test_wkb_in_multicurve(void) {} + +static void test_wkb_in_multisurface(void) {} + +static void test_wkb_in_malformed(void) +{ + /* OSSFUXX */ + cu_wkb_malformed_in("0000000008200000002020202020202020"); + + /* See http://trac.osgeo.org/postgis/ticket/1445 */ + cu_wkb_malformed_in("01060000400200000001040000400100000001010000400000000000000000000000000000000000000000000000000101000040000000000000F03F000000000000F03F000000000000F03F"); + cu_wkb_malformed_in("01050000400200000001040000400100000001010000400000000000000000000000000000000000000000000000000101000040000000000000F03F000000000000F03F000000000000F03F"); + cu_wkb_malformed_in("01040000400200000001040000400100000001010000400000000000000000000000000000000000000000000000000101000040000000000000F03F000000000000F03F000000000000F03F"); + cu_wkb_malformed_in("01030000400200000001040000400100000001010000400000000000000000000000000000000000000000000000000101000040000000000000F03F000000000000F03F000000000000F03F"); + + /* See http://trac.osgeo.org/postgis/ticket/168 */ + cu_wkb_malformed_in("01060000C00100000001030000C00100000003000000E3D9107E234F5041A3DB66BC97A30F4122ACEF440DAF9440FFFFFFFFFFFFEFFFE3D9107E234F5041A3DB66BC97A30F4122ACEF440DAF9440FFFFFFFFFFFFEFFFE3D9107E234F5041A3DB66BC97A30F4122ACEF440DAF9440FFFFFFFFFFFFEFFF"); + + /* Oracle "WKB" */ + cu_wkb_malformed_in("00000f424200000007000000020000000240b92ab16861e92940d6c3e0f5be5d9e40ba1a50623d0bfa40d6ef6f2b020c4a000f42410000000340ba1a50623d0bfa40d6ef6f2b020c4a40ba1ebb14cce52440d6f048fed27bec40ba22f374aba38740d6f1323d6c7219000000020000000240ba22f374aba38740d6f1323d6c721940ba5725e34330d740d6fd1dc28f5c29000f42410000002740ba5725e34330d740d6fd1dc28f5c2940ba5b26dbc90e1940d6fe04a63c03cf40ba5f1ced80a17b40d6feee8726d04e40ba64fab16b244a40d700545391e30840ba6aba5e24788540d701c1db1e9f2740ba705be892932540d703384e2d19da40ba75d91676640a40d704b74bc6a7f040ba7b3120b0397b40d7063fe6e24e3d40ba805df3a57eaa40d707d1a9fbe76d40ba80dc43d054f140d707f92b656dd940ba815a1c9b413a40d70820c497742540ba86048c041b5440d7098df370651840ba8a8666559f6f40d70b03332f017540ba929a863f35bf40d70ddd2c513fcd40ba9a0c08205ff240d710d24dd2f1aa40ba9e1c9f51a16040d7129a3226dd1140baa1f53f6c269a40d71469cabc515540baa59752e7123040d7163fbd0bd2f440baa905e34330d740d7181bd70a3d7140baac425b1082b440d719fcc009c75240baaf50623d0bfa40d71be27ef9db2340bab157404ac14d40d71d3785fe998c40bab34a3d5fdcdf40d71e8e666234a840bab66a43c834e340d721170a535ffd40bab94e977c88e840d723a4188f42ff40babb5fc577e64340d7259a5666c23440babd53f7be121f40d727926e978d5040babf2cd708467540d7298a75344b5340bac0ef1a8ef77f40d72b83c6a7ef9e40bac29c6faca73a40d72d7c9a4a17e740bac4399988d2a240d72f76459d990340bac5c8b018b9dc40d7316efed2743140bac74e146a1a5040d733683126e97940bac8cbda53946e40d735601daf2bd440baca4666559f6f40d737583126e97940bacab55b9b068340d737ec8d0113b440bacb249b951c5c40d73880e55c0fcb40bacdc490bfe33240d73ac7bdc376b240bad210623d0bfa40d73ce46a7ac81d000000020000000240bad210623d0bfa40d73ce46a7ac81d40badf86a7ded6bb40d742325e353f7d000f42410000000540badf86a7ded6bb40d742325e353f7d40bae9151fb3633540d74738abbc084240bae8f89363f57340d74cc8f5be5d9e40bae7c450ce91b940d74ec51a03a44340bae7d374aba38740d750c70a3d70a4000000020000000340bae7d374aba38740d750c70a3d70a440bafb1c28e4fb9840d75bedb228dc9840bb24d3b634dad340d76c4126e54717"); + +} + +static void +test_wkb_fuzz(void) +{ + /* OSS-FUZZ https://trac.osgeo.org/postgis/ticket/4534 */ + uint8_t wkb[36] = {000, 000, 000, 000, 015, 000, 000, 000, 003, 000, 200, 000, 000, 010, 000, 000, 000, 000, + 000, 000, 000, 000, 010, 000, 000, 000, 000, 000, 000, 000, 000, 010, 000, 000, 000, 000}; + LWGEOM *g = lwgeom_from_wkb(wkb, 36, LW_PARSER_CHECK_NONE); + lwgeom_free(g); + + /* OSS-FUZZ https://trac.osgeo.org/postgis/ticket/4536 */ + uint8_t wkb2[319] = { + 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, + 001, 001, 001, 001, 001, 012, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, + 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 051, 001, 001, + 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, + 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 000, 115, 001, 001, 001, 001, + 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 000, 001, 001, 001, 001, 001, 001, 001, + 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, + 001, 001, 001, 001, 001, 001, 000, 000, 000, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, + 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 002, + 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, + 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 207, 001, + 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, + 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 000, 000, 000, 000, 000, + 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 000, 001, 001, + 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, + 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001, 001}; + g = lwgeom_from_wkb(wkb2, 319, LW_PARSER_CHECK_NONE); + lwgeom_free(g); + + /* OSS-FUZZ: https://trac.osgeo.org/postgis/ticket/4535 */ + uint8_t wkb3[9] = {0x01, 0x03, 0x00, 0x00, 0x10, 0x8d, 0x55, 0xf3, 0xff}; + g = lwgeom_from_wkb(wkb3, 9, LW_PARSER_CHECK_NONE); + lwgeom_free(g); + + /* OSS-FUZZ: https://trac.osgeo.org/postgis/ticket/4544 */ + uint8_t wkb4[22] = {0x01, 0x0f, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x11, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00}; + g = lwgeom_from_wkb(wkb4, 22, LW_PARSER_CHECK_NONE); + lwgeom_free(g); + + /* OSS-FUZZ: https://trac.osgeo.org/postgis/ticket/4621 */ + uint32_t big_size = 20000000; + uint8_t *wkb5 = lwalloc(big_size); + memset(wkb5, 0x01, big_size); + g = lwgeom_from_wkb(wkb5, big_size, LW_PARSER_CHECK_NONE); + lwgeom_free(g); + lwfree(wkb5); +} + +/* +** Used by test harness to register the tests in this file. +*/ +void wkb_in_suite_setup(void); +void wkb_in_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("wkb_input", init_wkb_in_suite, clean_wkb_in_suite); + PG_ADD_TEST(suite, test_wkb_in_point); + PG_ADD_TEST(suite, test_wkb_in_linestring); + PG_ADD_TEST(suite, test_wkb_in_polygon); + PG_ADD_TEST(suite, test_wkb_in_multipoint); + PG_ADD_TEST(suite, test_wkb_in_multilinestring); + PG_ADD_TEST(suite, test_wkb_in_multipolygon); + PG_ADD_TEST(suite, test_wkb_in_collection); + PG_ADD_TEST(suite, test_wkb_in_circularstring); + PG_ADD_TEST(suite, test_wkb_in_compoundcurve); + PG_ADD_TEST(suite, test_wkb_in_curvpolygon); + PG_ADD_TEST(suite, test_wkb_in_multicurve); + PG_ADD_TEST(suite, test_wkb_in_multisurface); + PG_ADD_TEST(suite, test_wkb_in_malformed); + PG_ADD_TEST(suite, test_wkb_fuzz); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_in_wkt.c b/mgist-postgis/liblwgeom/cunit/cu_in_wkt.c new file mode 100644 index 0000000..dc25093 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_in_wkt.c @@ -0,0 +1,448 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2010 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +/* +** Globals used by tests +*/ +char *s; +char *r; + +/* +** The suite initialization function. +** Create any re-used objects. +*/ +static int init_wkt_in_suite(void) +{ + return 0; +} + +/* +** The suite cleanup function. +** Frees any global objects. +*/ +static int clean_wkt_in_suite(void) +{ + return 0; +} + +/* +* Return a char* that results from taking the input +* WKT, creating an LWGEOM, then writing it back out +* as an output WKT using the supplied variant. +* If there is an error, return that. +*/ +static char* cu_wkt_in(char *wkt, uint8_t variant) +{ + LWGEOM_PARSER_RESULT p; + int rv = 0; + char *s = 0; + + rv = lwgeom_parse_wkt(&p, wkt, 0); + if (p.errcode) + { + CU_ASSERT_EQUAL(rv, LW_FAILURE); + CU_ASSERT(!p.geom); + return strdup(p.message); + } + CU_ASSERT_EQUAL(rv, LW_SUCCESS); + s = lwgeom_to_wkt(p.geom, variant, 8, NULL); + lwgeom_parser_result_free(&p); + return s; +} + +#include +static void cu_strtolower(char* str) +{ + size_t i; + for (i = 0; i < strlen(str); ++i) { + str[i] = tolower(str[i]); + } +} + +static void test_wkt_in_point(void) +{ + s = "POINT(1 2) foobar"; + r = cu_wkt_in(s, WKT_SFSQL); + CU_ASSERT_STRING_EQUAL("parse error - invalid geometry", r); + lwfree(r); + + s = "POINT(1e700 0)"; + r = cu_wkt_in(s, WKT_SFSQL); + CU_TEST ( ! strcmp(r, "POINT(inf 0)") || ! strcmp(r, "POINT(1.#INF 0)") || ! strcmp(r, "POINT(Infinity 0)") ); + lwfree(r); + + s = "POINT(0 0)"; + r = cu_wkt_in(s, WKT_SFSQL); + CU_ASSERT_STRING_EQUAL(r,s); + lwfree(r); + + s = "POINT EMPTY"; + r = cu_wkt_in(s, WKT_SFSQL); + CU_ASSERT_STRING_EQUAL(r,s); + lwfree(r); + + s = "POINT M EMPTY"; + r = cu_wkt_in(s, WKT_ISO); + CU_ASSERT_STRING_EQUAL(r,s); + lwfree(r); + + s = "point(nan 10)"; + r = cu_wkt_in(s, WKT_ISO); + cu_strtolower(r); + CU_ASSERT_STRING_EQUAL(r,s); + lwfree(r); + + // printf("\nIN: %s\nOUT: %s\n",s,r); +} + +static void test_wkt_in_linestring(void) +{ + s = "LINESTRING EMPTY"; + r = cu_wkt_in(s, WKT_SFSQL); + CU_ASSERT_STRING_EQUAL(r,s); + lwfree(r); + + s = "LINESTRING(0 0,1 1)"; + r = cu_wkt_in(s, WKT_SFSQL); + CU_ASSERT_STRING_EQUAL(r,s); + lwfree(r); + + s = "LINESTRING(0 0 0,1 1 1)"; + r = cu_wkt_in(s, WKT_EXTENDED); + CU_ASSERT_STRING_EQUAL(r,s); + lwfree(r); + + s = "LINESTRING M (0 0 0,1 1 1)"; + r = cu_wkt_in(s, WKT_ISO); + CU_ASSERT_STRING_EQUAL(r,s); + lwfree(r); + + s = "LINESTRING ZM (0 0 0 1,1 1 1 1,2 2 2 2,0.141231 4 5 4)"; + r = cu_wkt_in(s, WKT_ISO); + CU_ASSERT_STRING_EQUAL(r,s); + lwfree(r); + + s = "LINESTRINGM(0 0 0,1 1 1)"; + r = cu_wkt_in(s, WKT_EXTENDED); + CU_ASSERT_STRING_EQUAL(r,s); + lwfree(r); + + s = "LINESTRING ZM EMPTY"; + r = cu_wkt_in(s, WKT_ISO); + CU_ASSERT_STRING_EQUAL(r,s); + lwfree(r); + + s = "LINESTRING Z (0 0 0 1, 0 1 0 1)"; + r = cu_wkt_in(s, WKT_EXTENDED); + CU_ASSERT_STRING_EQUAL(r,"can not mix dimensionality in a geometry"); + //printf("\nIN: %s\nOUT: %s\n",s,r); + lwfree(r); + +} + +static void test_wkt_in_polygon(void) +{ + s = "POLYGON((0 0,0 1,1 1,0 0))"; + r = cu_wkt_in(s, WKT_SFSQL); + CU_ASSERT_STRING_EQUAL(r,s); + lwfree(r); + + s = "POLYGON Z ((0 0,0 10,10 10,10 0,0 0),(1 1 1,1 2 1,2 2 1,2 1 1,1 1 1))"; + r = cu_wkt_in(s, WKT_SFSQL); + CU_ASSERT_STRING_EQUAL(r,"can not mix dimensionality in a geometry"); + lwfree(r); + + s = "POLYGON Z ((0 0,0 10,10 10,10 0,0 0),(1 1,1 2,2 2,2 1,1 1))"; + r = cu_wkt_in(s, WKT_SFSQL); + CU_ASSERT_STRING_EQUAL(r,"can not mix dimensionality in a geometry"); + //printf("\nIN: %s\nOUT: %s\n",s,r); + lwfree(r); +} + +static void test_wkt_in_multipoint(void) +{ + /**I'm remarking this out since it fails on windows because windows returns + MULTIPOINT(-1 -2 -3,5.4 6.6 7.77,-5.4 -6.6 -7.77,1000000 1e-006 -1000000,-1.3e-006 -1.4e-005 0) **/ + /** @todo TODO: Paul put back in if you care after you do replace mumbo jumbo to replace the extra 0s in Windows + */ + // s = "MULTIPOINT(-1 -2 -3,5.4 6.6 7.77,-5.4 -6.6 -7.77,1000000 1e-06 -1000000,-1.3e-06 -1.4e-05 0)"; + // r = cu_wkt_in(s, WKT_EXTENDED); + // CU_ASSERT_STRING_EQUAL(r,s); + // printf("\nIN: %s\nOUT: %s\n",s,r); + // lwfree(r); + + s = "MULTIPOINT(0 0)"; + r = cu_wkt_in(s, WKT_SFSQL); + CU_ASSERT_STRING_EQUAL(r,s); + //printf("\nIN: %s\nOUT: %s\n",s,r); + lwfree(r); + + s = "MULTIPOINT(0 0,1 1)"; + r = cu_wkt_in(s, WKT_SFSQL); + CU_ASSERT_STRING_EQUAL(r,s); + //printf("\nIN: %s\nOUT: %s\n",s,r); + lwfree(r); +} + +static void test_wkt_in_multilinestring(void) +{ + s = "MULTILINESTRING((0 0,1 1),(1 1,2 2),(3 3,3 3,3 3,2 2,2 1))"; + r = cu_wkt_in(s, WKT_SFSQL); + CU_ASSERT_STRING_EQUAL(r,s); + //printf("\nIN: %s\nOUT: %s\n",s,r); + lwfree(r); + +} + +static void test_wkt_in_multipolygon(void) +{ + s = "MULTIPOLYGON(((0 0,0 1,1 1,0 0)))"; + r = cu_wkt_in(s, WKT_SFSQL); + CU_ASSERT_STRING_EQUAL(r,s); + //printf("\nIN: %s\nOUT: %s\n",s,r); + lwfree(r); + + s = "MULTIPOLYGON(((0 0,0 10,10 10,0 0),(1 1,1 2,2 2,1 1)),((-10 -10,-10 -5,-5 -5,-10 -10)))"; + r = cu_wkt_in(s, WKT_SFSQL); + CU_ASSERT_STRING_EQUAL(r,s); + //printf("\nIN: %s\nOUT: %s\n",s,r); + lwfree(r); + + s = "SRID=4;MULTIPOLYGON(((0 0,0 1,1 1,0 0)))"; + r = cu_wkt_in(s, WKT_EXTENDED); + CU_ASSERT_STRING_EQUAL(r,s); + //printf("\nIN: %s\nOUT: %s\n",s,r); + lwfree(r); + +} + +static void test_wkt_in_collection(void) +{ + s = "SRID=5;GEOMETRYCOLLECTION(POINT(0 0),LINESTRING(1 0,0 0),CIRCULARSTRING(0 0,0 1,1 1,0 1,2 2))"; + r = cu_wkt_in(s, WKT_EXTENDED); + //printf("\nIN: %s\nOUT: %s\n",s,r); + CU_ASSERT_STRING_EQUAL(r,s); + lwfree(r); + + s = "GEOMETRYCOLLECTION(POINT(0 0),POINT EMPTY,LINESTRING(1 0,0 0),POLYGON EMPTY,CIRCULARSTRING(0 0,0 1,1 1,0 1,2 2))"; + r = cu_wkt_in(s, WKT_SFSQL); + //printf("\nIN: %s\nOUT: %s\n",s,r); + CU_ASSERT_STRING_EQUAL(r,s); + lwfree(r); + + s = "GEOMETRYCOLLECTION Z (POINT Z (0 0 0))"; + r = cu_wkt_in(s, WKT_ISO); + //printf("\nIN: %s\nOUT: %s\n",s,r); + CU_ASSERT_STRING_EQUAL(r,s); + lwfree(r); + + s = "GEOMETRYCOLLECTION M (MULTILINESTRING M ((0 0 5,2 0 5),(1 1 5,2 2 5)))"; + r = cu_wkt_in(s, WKT_ISO); + //printf("\nIN: %s\nOUT: %s\n",s,r); + CU_ASSERT_STRING_EQUAL(r,s); + lwfree(r); + + /* See http://trac.osgeo.org/postgis/ticket/1455#comment:3 */ + s = "GEOMETRYCOLLECTION Z (MULTILINESTRING Z ((0 0 5,2 0 5),(1 1 5,2 2 5)))"; + r = cu_wkt_in(s, WKT_ISO); + //printf("\nIN: %s\nOUT: %s\n",s,r); + CU_ASSERT_STRING_EQUAL(r,s); + lwfree(r); +} + +static void test_wkt_in_circularstring(void) +{ + s = "CIRCULARSTRING(0 0,0 1,1 1,0 1,2 2)"; + r = cu_wkt_in(s, WKT_SFSQL); + //printf("\nIN: %s\nOUT: %s\n",s,r); + CU_ASSERT_STRING_EQUAL(r,s); + lwfree(r); +} + +static void test_wkt_in_compoundcurve(void) +{ + s = "SRID=4326;COMPOUNDCURVEM(CIRCULARSTRINGM(0 0 2,1 1 2,1 0 2),(1 0 2,0 1 2))"; + r = cu_wkt_in(s, WKT_EXTENDED); + CU_ASSERT_STRING_EQUAL(r,s); + //printf("\nIN: %s\nOUT: %s\n",s,r); + lwfree(r); + + s = "COMPOUNDCURVE Z (CIRCULARSTRING Z (0 0 0,0 1 0,1 1 0,0 0 0,2 2 0),(2 2 0,0 0 1,1 1 1,2 2 1))"; + r = cu_wkt_in(s, WKT_ISO); + CU_ASSERT_STRING_EQUAL(r,s); + //printf("\nIN: %s\nOUT: %s\n",s,r); + lwfree(r); +} + +static void test_wkt_in_curvpolygon(void) +{ + s = "CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0,0 1,1 1,2 2,0 0),(0 0,1 1,2 2)),CIRCULARSTRING(0 0,0 1,1 1,0 0,2 2),(0 0,1 1,2 1))"; + r = cu_wkt_in(s, WKT_ISO); + CU_ASSERT_STRING_EQUAL(r,s); + //printf("\nIN: %s\nOUT: %s\n",s,r); + lwfree(r); +} + +static void test_wkt_in_multicurve(void) +{ + s = "SRID=4326;MULTICURVE(COMPOUNDCURVE(CIRCULARSTRING(0 0,1 1,1 0),(1 0,0 1)))"; + r = cu_wkt_in(s, WKT_EXTENDED); + CU_ASSERT_STRING_EQUAL(r,s); + //printf("\nIN: %s\nOUT: %s\n",s,r); + lwfree(r); +} + +static void test_wkt_in_multisurface(void) +{ + s = "SRID=4326;MULTICURVE(COMPOUNDCURVE(CIRCULARSTRING(0 0,1 1,1 0),(1 0,0 1)))"; + r = cu_wkt_in(s, WKT_EXTENDED); + CU_ASSERT_STRING_EQUAL(r,s); + //printf("\nIN: %s\nOUT: %s\n",s,r); + lwfree(r); +} + +static void test_wkt_in_tin(void) +{ + s = "TIN(((0 1 2,3 4 5,6 7 8,0 1 2)),((0 1 2,3 4 5,6 7 8,9 10 11,0 1 2)))"; + r = cu_wkt_in(s, WKT_EXTENDED); + CU_ASSERT_STRING_EQUAL(r,"triangle must have exactly 4 points"); + //printf("\nIN: %s\nOUT: %s\n",s,r); + lwfree(r); +} + +static void test_wkt_in_polyhedralsurface(void) +{ + s = "POLYHEDRALSURFACE(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))"; + r = cu_wkt_in(s, WKT_EXTENDED); + CU_ASSERT_STRING_EQUAL(r,s); + //printf("\nIN: %s\nOUT: %s\n",s,r); + lwfree(r); + + s = "POLYHEDRALSURFACE Z (((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))"; + r = cu_wkt_in(s, WKT_ISO); + CU_ASSERT_STRING_EQUAL(r,s); + //printf("\nIN: %s\nOUT: %s\n",s,r); + lwfree(r); + + s = "POLYHEDRALSURFACE(((0 1 2,3 4 5,6 7,0 1 2)))"; + r = cu_wkt_in(s, WKT_ISO); + CU_ASSERT_STRING_EQUAL(r,"can not mix dimensionality in a geometry"); + //printf("\nIN: %s\nOUT: %s\n",s,r); + lwfree(r); + +} + +static void test_wkt_in_errlocation(void) +{ + LWGEOM_PARSER_RESULT p; + int rv = 0; + char *wkt = 0; + + wkt = "LINESTRING((0 0 0,1 1)"; + lwgeom_parser_result_init(&p); + rv = lwgeom_parse_wkt(&p, wkt, LW_PARSER_CHECK_ALL); + CU_ASSERT_EQUAL( rv, LW_FAILURE ); + CU_ASSERT((12 - p.errlocation) < 1.5); + +// printf("errlocation %d\n", p.errlocation); +// printf("message %s\n", p.message); + + lwgeom_parser_result_free(&p); + +} + +static void test_wkt_double(void) +{ + LWGEOM_PARSER_RESULT p; + int rv = 0; + char *wkt = 0; + + wkt = "LINESTRING(1.1.1, 2.2.2)"; + lwgeom_parser_result_init(&p); + rv = lwgeom_parse_wkt(&p, wkt, LW_PARSER_CHECK_ALL); + CU_ASSERT( LW_FAILURE == rv ); + CU_ASSERT( p.errcode ); + CU_ASSERT( ! p.geom ); + lwgeom_parser_result_free(&p); + + wkt = "LINESTRING(1.1 .1, 2.2 .2)"; + lwgeom_parser_result_init(&p); + rv = lwgeom_parse_wkt(&p, wkt, LW_PARSER_CHECK_ALL); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + lwgeom_parser_result_free(&p); + + wkt = "LINESTRING( 1.1 .1 , 2.2 .2 )"; + lwgeom_parser_result_init(&p); + rv = lwgeom_parse_wkt(&p, wkt, LW_PARSER_CHECK_ALL); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + lwgeom_parser_result_free(&p); + + wkt = "LINESTRING(\n1.1\n.1,\n2.2\n.2\n)"; + lwgeom_parser_result_init(&p); + rv = lwgeom_parse_wkt(&p, wkt, LW_PARSER_CHECK_ALL); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + lwgeom_parser_result_free(&p); + + wkt = "LINESTRING(1.1\t.1\t,\t2.2\t.2\t)"; + lwgeom_parser_result_init(&p); + rv = lwgeom_parse_wkt(&p, wkt, LW_PARSER_CHECK_ALL); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + lwgeom_parser_result_free(&p); +} + +static void test_wkt_leak(void) +{ + /* OSS-FUZZ: https://trac.osgeo.org/postgis/ticket/4537 */ + char *wkt = "TINEMPTY,"; + char *err = cu_wkt_in(wkt, WKT_EXTENDED); + CU_ASSERT_STRING_EQUAL(err, "parse error - invalid geometry"); + lwfree(err); + + /* OSS-FUZZ: https://trac.osgeo.org/postgis/ticket/4545 */ + wkt = "GEOMeTRYCOLLECTION(POLYHEDRALSURFACEEMPTY "; + err = cu_wkt_in(wkt, WKT_EXTENDED); + CU_ASSERT_STRING_EQUAL(err, "parse error - invalid geometry"); + lwfree(err); +} + +/* +** Used by test harness to register the tests in this file. +*/ +void wkt_in_suite_setup(void); +void wkt_in_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("wkt_input", init_wkt_in_suite, clean_wkt_in_suite); + PG_ADD_TEST(suite, test_wkt_in_point); + PG_ADD_TEST(suite, test_wkt_in_linestring); + PG_ADD_TEST(suite, test_wkt_in_polygon); + PG_ADD_TEST(suite, test_wkt_in_multipoint); + PG_ADD_TEST(suite, test_wkt_in_multilinestring); + PG_ADD_TEST(suite, test_wkt_in_multipolygon); + PG_ADD_TEST(suite, test_wkt_in_collection); + PG_ADD_TEST(suite, test_wkt_in_circularstring); + PG_ADD_TEST(suite, test_wkt_in_compoundcurve); + PG_ADD_TEST(suite, test_wkt_in_curvpolygon); + PG_ADD_TEST(suite, test_wkt_in_multicurve); + PG_ADD_TEST(suite, test_wkt_in_multisurface); + PG_ADD_TEST(suite, test_wkt_in_tin); + PG_ADD_TEST(suite, test_wkt_in_polyhedralsurface); + PG_ADD_TEST(suite, test_wkt_in_errlocation); + PG_ADD_TEST(suite, test_wkt_double); + PG_ADD_TEST(suite, test_wkt_leak); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_iterator.c b/mgist-postgis/liblwgeom/cunit/cu_iterator.c new file mode 100644 index 0000000..7311ab0 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_iterator.c @@ -0,0 +1,265 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2015 Daniel Baston + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include "CUnit/Basic.h" +#include "cu_tester.h" + +#include "../liblwgeom_internal.h" + +char* inputs[] = +{ + "POINT (17 253)", + "POINT Z (17 253 018)", + "TRIANGLE ((0 0, 10 0, 10 10, 0 0))", + "LINESTRING (17 253, -44 28, 33 11, 26 44)", + "LINESTRING M (17 253 0, -44 28 1, 33 11 2, 26 44 3)", + "POLYGON((26426 65078,26531 65242,26075 65136,26096 65427,26426 65078))", + "MULTIPOINT ((1 1), (1 1))", + "MULTILINESTRING Z ((1 1 0, 2 2 0), (3 3 1, 4 4 1))", + "MULTIPOLYGON (((0 0, 10 0, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1)), ((20 20, 20 30, 30 30, 20 20)))", + "POINT EMPTY", + "LINESTRING M EMPTY", + "POLYGON Z EMPTY", + "GEOMETRYCOLLECTION EMPTY", + "GEOMETRYCOLLECTION (MULTIPOINT ((14 80), (22 12)))", + "GEOMETRYCOLLECTION (POINT (3 7), LINESTRING (0 0, 14 3), GEOMETRYCOLLECTION(POINT (2 8)))", + "GEOMETRYCOLLECTION (POINT (3 7), GEOMETRYCOLLECTION(MULTIPOINT ((2 8))))", + "GEOMETRYCOLLECTION (POINT (3 7), GEOMETRYCOLLECTION(LINESTRING (2 8, 4 3), POLYGON EMPTY, MULTIPOINT ((2 8), (17 3), EMPTY)))", + "CIRCULARSTRING(0 0,2 0, 2 1, 2 3, 4 3)", + "COMPOUNDCURVE(CIRCULARSTRING(0 0,2 0, 2 1, 2 3, 4 3),(4 3, 4 5, 1 4, 0 0))", + "MULTICURVE((0 0, 5 5),CIRCULARSTRING(4 0, 4 4, 8 4))", + "CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0,2 0, 2 1, 2 3, 4 3),(4 3, 4 5, 1 4, 0 0)), LINESTRING (0.1 0.1, 0.3 0.1, 0.3 0.3, 0.1 0.1) )", + "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(0 0, 4 0, 4 4, 0 4, 0 0),(1 1, 3 3, 3 1, 1 1)),((10 10, 14 12, 11 10, 10 10),(11 11, 11.5 11, 11 11.5, 11 11)))", + "POLYHEDRALSURFACE( ((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)), ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)), ((1 1 0, 1 1 1, 1 0 1, 1 0 0, 1 1 0)), ((0 1 0, 0 1 1, 1 1 1, 1 1 0, 0 1 0)), ((0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1)) )", + "TIN(((80 130,50 160,80 70,80 130)),((50 160,10 190,10 70,50 160)), ((80 70,50 160,10 70,80 70)),((120 160,120 190,50 160,120 160)), ((120 190,10 190,50 160,120 190)))" +}; + +static uint32_t +count_points_using_iterator(LWGEOM* g) +{ + POINT4D p; + uint32_t count = 0; + LWPOINTITERATOR* it = lwpointiterator_create(g); + + while (lwpointiterator_has_next(it)) + { + CU_ASSERT_TRUE(lwpointiterator_next(it, &p)); + count++; + } + + lwpointiterator_destroy(it); + + return count; +} + +static void +test_point_count(void) +{ + char* types_visited = lwalloc(NUMTYPES * sizeof(char)); + memset(types_visited, LW_FALSE, NUMTYPES * sizeof(char)); + + uint32_t i; + for (i = 0; i < sizeof(inputs)/sizeof(char*); i++) + { + LWGEOM* input = lwgeom_from_wkt(inputs[i], LW_PARSER_CHECK_NONE); + types_visited[lwgeom_get_type(input)] = LW_TRUE; + + uint32_t itercount = count_points_using_iterator(input); + + CU_ASSERT_EQUAL(lwgeom_count_vertices(input), itercount); + + lwgeom_free(input); + } + + /* Assert that every valid LWGEOM type has been tested */ + for (i = 1; i < NUMTYPES; i++) + { + CU_ASSERT_TRUE(types_visited[i]); + } + + lwfree(types_visited); +} + +static void +test_cannot_modify_read_only(void) +{ + LWGEOM* input = lwgeom_from_wkt(inputs[0], LW_PARSER_CHECK_NONE); + LWPOINTITERATOR* it = lwpointiterator_create(input); + + POINT4D p; + p.x = 3.2; + p.y = 4.8; + + CU_ASSERT_EQUAL(LW_FAILURE, lwpointiterator_modify_next(it, &p)); + + lwgeom_free(input); + lwpointiterator_destroy(it); +} + +static void +test_modification(void) +{ + uint32_t i; + uint32_t j = 0; + + for (i = 0; i < sizeof(inputs)/sizeof(char*); i++) + { + LWGEOM* input = lwgeom_from_wkt(inputs[i], LW_PARSER_CHECK_NONE); + LWPOINTITERATOR* it1 = lwpointiterator_create_rw(input); + LWPOINTITERATOR* it2 = lwpointiterator_create(input);; + + while (lwpointiterator_has_next(it1)) + { + /* Make up a coordinate, assign it to the next spot in it1, + * read it from it2 to verify that it was assigned correctly. */ + POINT4D p1, p2; + p1.x = sqrt(j++); + p1.y = sqrt(j++); + p1.z = sqrt(j++); + p1.m = sqrt(j++); + + CU_ASSERT_TRUE(lwpointiterator_modify_next(it1, &p1)); + CU_ASSERT_TRUE(lwpointiterator_next(it2, &p2)); + + CU_ASSERT_EQUAL(p1.x, p2.x); + CU_ASSERT_EQUAL(p1.y, p2.y); + + if (lwgeom_has_z(input)) + CU_ASSERT_EQUAL(p1.z, p2.z); + + if (lwgeom_has_m(input)) + CU_ASSERT_EQUAL(p1.m, p2.m); + } + + lwgeom_free(input); + + lwpointiterator_destroy(it1); + lwpointiterator_destroy(it2); + } +} + +static void +test_no_memory_leaked_when_iterator_is_partially_used(void) +{ + LWGEOM* g = lwgeom_from_wkt("GEOMETRYCOLLECTION (POINT (3 7), GEOMETRYCOLLECTION(LINESTRING (2 8, 4 3), POLYGON EMPTY, MULTIPOINT ((2 8), (17 3), EMPTY)))", LW_PARSER_CHECK_NONE); + + LWPOINTITERATOR* it = lwpointiterator_create(g); + lwpointiterator_next(it, NULL); + lwpointiterator_next(it, NULL); + + lwpointiterator_destroy(it); + lwgeom_free(g); +} + +static void +test_mixed_rw_access(void) +{ + uint32_t i = 0; + LWGEOM* g = lwgeom_from_wkt("GEOMETRYCOLLECTION (POINT (3 7), GEOMETRYCOLLECTION(LINESTRING (2 8, 4 3), POLYGON EMPTY, MULTIPOINT ((2 8), (17 3), EMPTY)))", LW_PARSER_CHECK_NONE); + LWPOINTITERATOR* it1 = lwpointiterator_create_rw(g); + LWPOINTITERATOR* it2 = lwpointiterator_create(g); + + /* Flip the coordinates of the 3rd point */ + while(lwpointiterator_has_next(it1)) + { + if (i == 2) + { + POINT4D p; + double tmp; + + lwpointiterator_peek(it1, &p); + tmp = p.x; + p.x = p.y; + p.y = tmp; + + lwpointiterator_modify_next(it1, &p); + } + else + { + lwpointiterator_next(it1, NULL); + } + i++; + } + CU_ASSERT_EQUAL(5, i); /* Every point was visited */ + lwpointiterator_destroy(it1); + + /* Verify that the points are as expected */ + POINT2D points[] = + { + { .x = 3, .y = 7 }, + { .x = 2, .y = 8 }, + { .x = 3, .y = 4 }, + { .x = 2, .y = 8 }, + { .x = 17, .y = 3} + }; + + for (i = 0; lwpointiterator_has_next(it2); i++) + { + POINT4D p; + + lwpointiterator_next(it2, &p); + + CU_ASSERT_EQUAL(p.x, points[i].x); + CU_ASSERT_EQUAL(p.y, points[i].y); + } + + lwpointiterator_destroy(it2); + lwgeom_free(g); +} + +static void +test_ordering(void) +{ + uint32_t i = 0; + LWGEOM* g = lwgeom_from_wkt("GEOMETRYCOLLECTION (POLYGON ((0 0, 0 10, 10 10, 0 10, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1)), MULTIPOINT((4 4), (3 3)))", LW_PARSER_CHECK_NONE); + + POINT2D points[] = { {.x = 0, .y = 0}, + {.x = 0, .y = 10}, + {.x = 10, .y = 10}, + {.x = 0, .y = 10}, + {.x = 0, .y = 0}, + {.x = 1, .y = 1}, + {.x = 1, .y = 2}, + {.x = 2, .y = 2}, + {.x = 2, .y = 1}, + {.x = 1, .y = 1}, + {.x = 4, .y = 4}, + {.x = 3, .y = 3} + }; + + LWPOINTITERATOR* it = lwpointiterator_create(g); + POINT4D p; + + for (i = 0; lwpointiterator_has_next(it); i++) + { + CU_ASSERT_EQUAL(LW_SUCCESS, lwpointiterator_next(it, &p)); + CU_ASSERT_EQUAL(p.x, points[i].x); + CU_ASSERT_EQUAL(p.y, points[i].y); + } + + lwpointiterator_destroy(it); + lwgeom_free(g); +} + +/* +** Used by test harness to register the tests in this file. +*/ +void iterator_suite_setup(void); +void iterator_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("iterator", NULL, NULL); + PG_ADD_TEST(suite, test_point_count); + PG_ADD_TEST(suite, test_ordering); + PG_ADD_TEST(suite, test_modification); + PG_ADD_TEST(suite, test_mixed_rw_access); + PG_ADD_TEST(suite, test_cannot_modify_read_only); + PG_ADD_TEST(suite, test_no_memory_leaked_when_iterator_is_partially_used); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_lwstroke.c b/mgist-postgis/liblwgeom/cunit/cu_lwstroke.c new file mode 100644 index 0000000..e703594 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_lwstroke.c @@ -0,0 +1,530 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright (C) 2017 Sandro Santilli + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include /* for M_PI */ +#include "CUnit/Basic.h" +#include "CUnit/CUnit.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +/* #define SKIP_TEST_RETAIN_ANGLE 1 */ + + +static LWGEOM* lwgeom_from_text(const char *str) +{ + LWGEOM_PARSER_RESULT r; + if( LW_FAILURE == lwgeom_parse_wkt(&r, (char*)str, LW_PARSER_CHECK_NONE) ) + return NULL; + return r.geom; +} + +static char* lwgeom_to_text(const LWGEOM *geom, int prec) +{ + gridspec grid; + LWGEOM *gridded; + char *wkt; + + memset(&grid, 0, sizeof(gridspec)); + grid.xsize = prec; + grid.ysize = prec; + gridded = lwgeom_grid(geom, &grid); + + wkt = lwgeom_to_wkt(gridded, WKT_ISO, 15, NULL); + lwgeom_free(gridded); + return wkt; +} + +static void test_lwcurve_linearize(void) +{ + LWGEOM *in; + LWGEOM *out, *out2; + char *str; + int toltype; + + /*********************************************************** + * + * Segments per quadrant tolerance type + * + ***********************************************************/ + + toltype = LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD; + + /* 2 quadrants arc (180 degrees, PI radians) */ + in = lwgeom_from_text("CIRCULARSTRING(0 0,100 100,200 0)"); + /* 2 segment per quadrant */ + out = lwcurve_linearize(in, 2, toltype, 0); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,30 70,100 100,170 70,200 0)"); + lwfree(str); + lwgeom_free(out); + + /* 3 segment per quadrant */ + out = lwcurve_linearize(in, 3, toltype, 0); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,14 50,50 86,100 100,150 86,186 50,200 0)"); + lwfree(str); + lwgeom_free(out); + /* 3.5 segment per quadrant (invalid) */ + cu_error_msg_reset(); + out = lwcurve_linearize(in, 3.5, toltype, 0); + CU_ASSERT( out == NULL ); + ASSERT_STRING_EQUAL(cu_error_msg, "lwarc_linearize: segments per quadrant must be an integer value, got 3.5"); + lwgeom_free(out); + /* -2 segment per quadrant (invalid) */ + cu_error_msg_reset(); + out = lwcurve_linearize(in, -2, toltype, 0); + CU_ASSERT( out == NULL ); + ASSERT_STRING_EQUAL(cu_error_msg, "lwarc_linearize: segments per quadrant must be at least 1, got -2"); + lwgeom_free(out); + /* 0 segment per quadrant (invalid) */ + cu_error_msg_reset(); + out = lwcurve_linearize(in, 0, toltype, 0); + CU_ASSERT( out == NULL ); + ASSERT_STRING_EQUAL(cu_error_msg, "lwarc_linearize: segments per quadrant must be at least 1, got 0"); + lwgeom_free(out); + lwgeom_free(in); + + /* 1.5 quadrants arc (145 degrees - PI*3/4 radians ) */ + in = lwgeom_from_text("CIRCULARSTRING(29.2893218813453 70.7106781186548,100 100,200 0)"); + /* 2 segment per quadrant */ + out = lwcurve_linearize(in, 2, toltype, 0); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(30 70,100 100,170 70,200 0)"); + lwfree(str); + lwgeom_free(out); + /* 3 segment per quadrant - non-symmetric */ + out = lwcurve_linearize(in, 3, toltype, 0); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(30 70,74 96,126 96,170 70,196 26,200 0)"); + lwfree(str); + lwgeom_free(out); + + /* 3 segment per quadrant - symmetric */ + out = lwcurve_linearize(in, 3, toltype, LW_LINEARIZE_FLAG_SYMMETRIC); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(30 70,70 96,116 98,158 80,190 46,200 0)"); + lwfree(str); + lwgeom_free(out); + +#ifndef SKIP_TEST_RETAIN_ANGLE + /* 3 segment per quadrant - symmetric/retain_angle */ + out = lwcurve_linearize(in, 3, toltype, + LW_LINEARIZE_FLAG_SYMMETRIC | + LW_LINEARIZE_FLAG_RETAIN_ANGLE + ); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(30 70,40 80,86 100,138 92,180 60,200 14,200 0)"); + lwfree(str); + lwgeom_free(out); +#endif /* SKIP_TEST_RETAIN_ANGLE */ + + lwgeom_free(in); + + /* 10 segments per quadrant (circular) */ + in = lwgeom_from_text("CIRCULARSTRING(0 0,1 0,0 0)"); + // out = lwcurve_linearize(in, 10, toltype, 0); + out = lwcurve_linearize(in, 10, toltype, 0); + // printf("OUT: %s\n", lwgeom_to_wkt(out, WKT_EXTENDED, 5, NULL)); + ASSERT_INT_EQUAL(10*4, lwgeom_count_vertices(out)); + lwgeom_free(out); + lwgeom_free(in); + + /*********************************************************** + * + * Maximum deviation tolerance type + * + ***********************************************************/ + + toltype = LW_LINEARIZE_TOLERANCE_TYPE_MAX_DEVIATION; + + in = lwgeom_from_text("CIRCULARSTRING(0 0,100 100,200 0)"); + + /* Maximum of 10 units of difference, asymmetric */ + out = lwcurve_linearize(in, 10, toltype, 0); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,38 78,124 98,190 42,200 0)"); + lwfree(str); + lwgeom_free(out); + /* Maximum of 0 units of difference (invalid) */ + cu_error_msg_reset(); + out = lwcurve_linearize(in, 0, toltype, 0); + CU_ASSERT( out == NULL ); + ASSERT_STRING_EQUAL(cu_error_msg, "lwarc_linearize: max deviation must be bigger than 0, got 0"); + /* Maximum of -2 units of difference (invalid) */ + cu_error_msg_reset(); + out = lwcurve_linearize(in, -2, toltype, 0); + CU_ASSERT( out == NULL ); + ASSERT_STRING_EQUAL(cu_error_msg, "lwarc_linearize: max deviation must be bigger than 0, got -2"); + /* Maximum of 10 units of difference, symmetric */ + out = lwcurve_linearize(in, 10, toltype, LW_LINEARIZE_FLAG_SYMMETRIC); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,30 70,100 100,170 70,200 0)"); + lwfree(str); + lwgeom_free(out); + /* Maximum of 20 units of difference, asymmetric */ + out = lwcurve_linearize(in, 20, toltype, 0); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,72 96,184 54,200 0)"); + lwfree(str); + lwgeom_free(out); + /* Maximum of 20 units of difference, symmetric */ + out = lwcurve_linearize(in, 20, toltype, LW_LINEARIZE_FLAG_SYMMETRIC); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,50 86,150 86,200 0)"); + lwfree(str); + lwgeom_free(out); + +#ifndef SKIP_TEST_RETAIN_ANGLE + /* Maximum of 20 units of difference, symmetric/retain angle */ + out = lwcurve_linearize(in, 20, toltype, + LW_LINEARIZE_FLAG_SYMMETRIC | + LW_LINEARIZE_FLAG_RETAIN_ANGLE + ); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,4 28,100 100,196 28,200 0)"); + lwfree(str); + lwgeom_free(out); +#endif /* SKIP_TEST_RETAIN_ANGLE */ + + lwgeom_free(in); + + /* + * Clockwise ~90 degrees south-west to north-west quadrants + * starting at ~22 degrees west of vertical line + * + * See https://trac.osgeo.org/postgis/ticket/3772 + */ + toltype = LW_LINEARIZE_TOLERANCE_TYPE_MAX_DEVIATION; + in = lwgeom_from_text("CIRCULARSTRING(71.96 -65.64,22.2 -18.52,20 50)"); + + /* 4 units of max deviation - symmetric */ + out = lwcurve_linearize(in, 4, toltype, LW_LINEARIZE_FLAG_SYMMETRIC); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(72 -66,34 -38,16 4,20 50)"); + lwfree(str); + lwgeom_free(out); + lwgeom_free(in); + + /* + * Clockwise ~90 degrees north-west to south-west quadrants + * starting at ~22 degrees west of vertical line + * + * See https://trac.osgeo.org/postgis/ticket/3772 + */ + toltype = LW_LINEARIZE_TOLERANCE_TYPE_MAX_DEVIATION; + in = lwgeom_from_text("CIRCULARSTRING(20 50,22.2 -18.52,71.96 -65.64)"); + + /* 4 units of max deviation - symmetric */ + out = lwcurve_linearize(in, 4, toltype, LW_LINEARIZE_FLAG_SYMMETRIC); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(20 50,16 4,34 -38,72 -66)"); + lwfree(str); + lwgeom_free(out); + + /* max deviation bigger than twice the radius + * we really only want to make sure NOT to enter + * an infinite loop here. + * See https://trac.osgeo.org/postgis/ticket/4031 + */ + out = lwcurve_linearize(in, 500, toltype, LW_LINEARIZE_FLAG_SYMMETRIC); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(20 50,22 -18,72 -66)"); + lwfree(str); + lwgeom_free(out); + + lwgeom_free(in); + + /* + * ROBUSTNESS: big radius, small tolerance + * See https://trac.osgeo.org/postgis/ticket/4058 + * NOTE: we are really only interested in not entering + * an infinite loop here + */ + toltype = LW_LINEARIZE_TOLERANCE_TYPE_MAX_DEVIATION; + in = lwgeom_from_text("CIRCULARSTRING(" + "2696000.553 1125699.831999999936670, " + "2695950.552000000141561 1125749.833000000100583, " + "2695865.195999999996275 1125835.189000)"); + out = lwcurve_linearize(in, 0.0001, toltype, 0); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(2696000 1125700,2695932 1125768,2695866 1125836)"); + lwfree(str); + lwgeom_free(out); + out = lwcurve_linearize(in, 0.0001, toltype, LW_LINEARIZE_FLAG_SYMMETRIC); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(2696000 1125700,2695932 1125768,2695866 1125836)"); + lwfree(str); + lwgeom_free(out); +#ifndef SKIP_TEST_RETAIN_ANGLE + out = lwcurve_linearize(in, 0.0001, toltype, + LW_LINEARIZE_FLAG_SYMMETRIC | + LW_LINEARIZE_FLAG_RETAIN_ANGLE + ); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(2696000 1125700,2695932 1125768,2695866 1125836)"); + lwfree(str); + lwgeom_free(out); +#endif /* SKIP_TEST_RETAIN_ANGLE */ + lwgeom_free(in); + + /*********************************************************** + * + * Maximum angle tolerance type + * + ***********************************************************/ + + toltype = LW_LINEARIZE_TOLERANCE_TYPE_MAX_ANGLE; + + in = lwgeom_from_text("CIRCULARSTRING(0 0,100 100,200 0)"); + + /* Maximum of 45 degrees per segment, asymmetric */ + out = lwcurve_linearize(in, M_PI / 4.0, toltype, 0); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,30 70,100 100,170 70,200 0)"); + lwfree(str); + lwgeom_free(out); + /* Maximum of 0 degrees per segment (invalid) */ + cu_error_msg_reset(); + out = lwcurve_linearize(in, 0, toltype, 0); + CU_ASSERT( out == NULL ); + ASSERT_STRING_EQUAL(cu_error_msg, "lwarc_linearize: max angle must be bigger than 0, got 0"); + /* Maximum of -2 degrees per segment (invalid) */ + cu_error_msg_reset(); + out = lwcurve_linearize(in, -2, toltype, 0); + CU_ASSERT( out == NULL ); + ASSERT_STRING_EQUAL(cu_error_msg, "lwarc_linearize: max angle must be bigger than 0, got -2"); + /* Maximum of 360 degrees per segment, just return minimum of two segments... */ + cu_error_msg_reset(); + out = lwcurve_linearize(in, M_PI*4, toltype, 0); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,100 100,200 0)"); + lwfree(str); + lwgeom_free(out); + /* Maximum of 70 degrees per segment, asymmetric */ + out = lwcurve_linearize(in, 70 * M_PI / 180, toltype, 0); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,66 94,176 64,200 0)"); + lwfree(str); + lwgeom_free(out); + /* Maximum of 70 degrees per segment, symmetric */ + out = lwcurve_linearize(in, 70 * M_PI / 180, toltype, LW_LINEARIZE_FLAG_SYMMETRIC); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,50 86,150 86,200 0)"); + lwfree(str); + lwgeom_free(out); + +#ifndef SKIP_TEST_RETAIN_ANGLE + /* Maximum of 70 degrees, symmetric/retain angle */ + out = lwcurve_linearize(in, 70 * M_PI / 180, toltype, + LW_LINEARIZE_FLAG_SYMMETRIC | + LW_LINEARIZE_FLAG_RETAIN_ANGLE + ); + str = lwgeom_to_text(out, 2); + ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,6 34,100 100,194 34,200 0)"); + lwfree(str); + lwgeom_free(out); +#endif /* SKIP_TEST_RETAIN_ANGLE */ + + lwgeom_free(in); + + /*********************************************************** + * + * Direction neutrality + * + ***********************************************************/ + + in = lwgeom_from_text("CIRCULARSTRING(71.96 -65.64,22.2 -18.52,20 50)"); + out = lwcurve_linearize(in, M_PI/4.0, + LW_LINEARIZE_TOLERANCE_TYPE_MAX_ANGLE, + LW_LINEARIZE_FLAG_SYMMETRIC); + lwgeom_reverse_in_place(in); + out2 = lwcurve_linearize(in, M_PI/4.0, + LW_LINEARIZE_TOLERANCE_TYPE_MAX_ANGLE, + LW_LINEARIZE_FLAG_SYMMETRIC); + lwgeom_reverse_in_place(out2); + if ( ! lwgeom_same(out, out2) ) + { + fprintf(stderr, "linearization is not direction neutral:\n"); + str = lwgeom_to_wkt(out, WKT_ISO, 18, NULL); + fprintf(stderr, "OUT1: %s\n", str); + lwfree(str); + str = lwgeom_to_wkt(out2, WKT_ISO, 18, NULL); + fprintf(stderr, "OUT2: %s\n", str); + lwfree(str); + } + CU_ASSERT( lwgeom_same(out, out2) ); + lwgeom_free(out2); + lwgeom_free(out); + lwgeom_free(in); +} + +static void test_unstroke() +{ + LWGEOM *in, *out; + char *str; + + /* It would be nice if this example returned two arcs (it's the intersection of two circles) + but it looks like the intersection itself is too sloppy in generating the derived point + to accurately reconstruct the circles. + in = lwgeom_from_text("POLYGON((0.5 0,0.471177920604846 -0.292635483024192,0.38581929876693 -0.574025148547634,0.247204418453818 -0.833355349529403,0.0606601717798223 -1.06066017177982,-5.44089437167602e-17 -1.11044268820754,-0.0606601717798188 -1.06066017177982,-0.247204418453816 -0.833355349529406,-0.385819298766929 -0.574025148547639,-0.471177920604845 -0.292635483024197,-0.5 -4.84663372329776e-15,-0.471177920604847 0.292635483024187,-0.385819298766932 0.57402514854763,-0.247204418453821 0.833355349529398,-0.0606601717798256 1.06066017177982,3.45538806345173e-16 1.11044268820754,0.0606601717798183 1.06066017177982,0.247204418453816 0.833355349529407,0.385819298766929 0.574025148547638,0.471177920604845 0.292635483024196,0.5 0))"); + out = lwgeom_unstroke(in); + str = lwgeom_to_wkt(out, WKT_ISO, 8, NULL); + printf("%s\n", str); + ASSERT_STRING_EQUAL(str, "CIRCULARSTRING(-1 0,0 1,0 -1)"); + lwgeom_free(in); + lwgeom_free(out); + lwfree(str); + */ + + in = lwgeom_from_text("CIRCULARSTRING(-1 0,0 1,0 -1)"); + out = lwgeom_stroke(in,8); + lwgeom_free(in); + in = out; + out = lwgeom_unstroke(in); + str = lwgeom_to_wkt(out, WKT_ISO, 8, NULL); + // printf("%s\n", str); + ASSERT_STRING_EQUAL(str, "CIRCULARSTRING(-1 0,0.70710678 0.70710678,0 -1)"); + lwgeom_free(in); + lwgeom_free(out); + lwfree(str); + + in = lwgeom_from_text("COMPOUNDCURVE(CIRCULARSTRING(-1 0,0 1,0 -1),(0 -1,-1 -1))"); + out = lwgeom_stroke(in,8); + lwgeom_free(in); + in = out; + out = lwgeom_unstroke(in); + str = lwgeom_to_wkt(out, WKT_ISO, 8, NULL); + // printf("%s\n", str); + ASSERT_STRING_EQUAL(str, "COMPOUNDCURVE(CIRCULARSTRING(-1 0,0.70710678 0.70710678,0 -1),(0 -1,-1 -1))"); + lwgeom_free(in); + lwgeom_free(out); + lwfree(str); + + in = lwgeom_from_text("COMPOUNDCURVE((-3 -3,-1 0),CIRCULARSTRING(-1 0,0 1,0 -1),(0 -1,0 -1.5,0 -2),CIRCULARSTRING(0 -2,-1 -3,1 -3),(1 -3,5 5))"); + out = lwgeom_stroke(in,8); + lwgeom_free(in); + in = out; + out = lwgeom_unstroke(in); + str = lwgeom_to_wkt(out, WKT_ISO, 8, NULL); + // printf("%s\n", str); + ASSERT_STRING_EQUAL( + str, + "COMPOUNDCURVE((-3 -3,-1 0),CIRCULARSTRING(-1 0,0.70710678 " + "0.70710678,0 -1),(0 -1,0 -1.5,0 -2),CIRCULARSTRING(0 " + "-2,-0.70710678 -3.70710678,1 -3),(1 -3,5 5))"); + lwgeom_free(in); + lwgeom_free(out); + lwfree(str); + + in = lwgeom_from_text("COMPOUNDCURVE(CIRCULARSTRING(-1 0,0 1,0 -1),CIRCULARSTRING(0 -1,-1 -2,1 -2))"); + out = lwgeom_stroke(in,8); + lwgeom_free(in); + in = out; + out = lwgeom_unstroke(in); + str = lwgeom_to_wkt(out, WKT_ISO, 8, NULL); + // printf("%s\n", str); + ASSERT_STRING_EQUAL( + str, + "COMPOUNDCURVE(CIRCULARSTRING(-1 0,0.70710678 0.70710678,0 " + "-1),CIRCULARSTRING(0 -1,-0.70710678 -2.70710678,1 -2))"); + lwgeom_free(in); + lwgeom_free(out); + lwfree(str); + + in = lwgeom_from_text("COMPOUNDCURVE((0 0, 1 1), CIRCULARSTRING(1 1, 2 2, 3 1), (3 1, 4 4))"); + out = lwgeom_stroke(in,8); + lwgeom_free(in); + in = out; + out = lwgeom_unstroke(in); + str = lwgeom_to_wkt(out, WKT_ISO, 8, NULL); + ASSERT_STRING_EQUAL(str, "COMPOUNDCURVE((0 0,1 1),CIRCULARSTRING(1 1,2 2,3 1),(3 1,4 4))"); + lwgeom_free(in); + lwgeom_free(out); + // printf("%s\n", str); + lwfree(str); + + // See http://trac.osgeo.org/postgis/ticket/2425 + // and http://trac.osgeo.org/postgis/ticket/2420 + in = lwgeom_from_text("LINESTRING(0 0,10 0,10 10,0 10,0 0)"); + out = lwgeom_unstroke(in); + str = lwgeom_to_wkt(out, WKT_ISO, 8, NULL); + ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,10 0,10 10,0 10,0 0)"); + lwgeom_free(in); + lwgeom_free(out); + lwfree(str); + + in = lwgeom_from_text("LINESTRING(10 10,0 10,0 0,10 0)"); + out = lwgeom_unstroke(in); + str = lwgeom_to_wkt(out, WKT_ISO, 8, NULL); + ASSERT_STRING_EQUAL(str, "LINESTRING(10 10,0 10,0 0,10 0)"); + // printf("%s\n", str); + lwgeom_free(in); + lwgeom_free(out); + lwfree(str); + + in = lwgeom_from_text("LINESTRING(0 0,10 0,10 10,0 10)"); + out = lwgeom_unstroke(in); + str = lwgeom_to_wkt(out, WKT_ISO, 8, NULL); + ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,10 0,10 10,0 10)"); + // printf("%s\n", str); + lwgeom_free(in); + lwgeom_free(out); + lwfree(str); + + // See http://trac.osgeo.org/postgis/ticket/2412 + in = lwgeom_from_text("LINESTRING(0 0, 1 1)"); + out = lwgeom_unstroke(in); + str = lwgeom_to_wkt(out, WKT_ISO, 8, NULL); + // printf("%s\n", str); + ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,1 1)"); + lwgeom_free(in); + lwgeom_free(out); + lwfree(str); + + in = lwgeom_from_text("GEOMETRYCOLLECTION(LINESTRING(10 10,10 11),LINESTRING(10 11,11 11),LINESTRING(11 11,10 10))"); + out = lwgeom_stroke(in,8); + lwgeom_free(in); + in = out; + out = lwgeom_unstroke(in); + str = lwgeom_to_wkt(out, WKT_ISO, 8, NULL); + ASSERT_STRING_EQUAL(str, "GEOMETRYCOLLECTION(LINESTRING(10 10,10 11),LINESTRING(10 11,11 11),LINESTRING(11 11,10 10))"); + lwgeom_free(in); + lwgeom_free(out); + lwfree(str); + + in = lwgeom_from_text("GEOMETRYCOLLECTION(LINESTRING(4 4,4 8),CIRCULARSTRING(4 8,6 10,8 8),LINESTRING(8 8,8 4))"); + out = lwgeom_stroke(in,8); + lwgeom_free(in); + in = out; + out = lwgeom_unstroke(in); + str = lwgeom_to_wkt(out, WKT_ISO, 8, NULL); + // printf("%s\n", str); + ASSERT_STRING_EQUAL(str, "GEOMETRYCOLLECTION(LINESTRING(4 4,4 8),CIRCULARSTRING(4 8,6 10,8 8),LINESTRING(8 8,8 4))"); + lwgeom_free(in); + lwgeom_free(out); + lwfree(str); +} + +/* +** Used by the test harness to register the tests in this file. +*/ +void lwstroke_suite_setup(void); +void lwstroke_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("lwstroke", NULL, NULL); + PG_ADD_TEST(suite, test_lwcurve_linearize); + PG_ADD_TEST(suite, test_unstroke); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_measures.c b/mgist-postgis/liblwgeom/cunit/cu_measures.c new file mode 100644 index 0000000..610b2a5 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_measures.c @@ -0,0 +1,1567 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright (C) 2015 Sandro Santilli + * Copyright (C) 2009 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" +#include "measures.h" +#include "measures3d.h" +#include "lwtree.h" + +static LWGEOM* lwgeom_from_text(const char *str) +{ + LWGEOM_PARSER_RESULT r; + if( LW_FAILURE == lwgeom_parse_wkt(&r, (char*)str, LW_PARSER_CHECK_NONE) ) + return NULL; + return r.geom; +} + +#define DIST2DTEST(str1, str2, res, accepted_error) \ + do_test_mindistance_tolerance(str1, str2, res, __LINE__, lwgeom_mindistance2d_tolerance, accepted_error);\ + do_test_mindistance_tolerance(str2, str1, res, __LINE__, lwgeom_mindistance2d_tolerance, accepted_error) +#define DIST3DTEST(str1, str2, res, accepted_error) \ + do_test_mindistance_tolerance(str1, str2, res, __LINE__, lwgeom_mindistance3d_tolerance, accepted_error);\ + do_test_mindistance_tolerance(str2, str1, res, __LINE__, lwgeom_mindistance3d_tolerance, accepted_error) + +static void +do_test_mindistance_tolerance(char *in1, + char *in2, + double expected_res, + int line, + double (*distancef)(const LWGEOM *, const LWGEOM *, double), + double accepted_error) +{ + LWGEOM *lw1; + LWGEOM *lw2; + double distance; + char *msg1 = "test_mindistance2d_tolerance failed (got %g expected %g) at line %d\n"; + char *msg2 = "\n\ndo_test_mindistance2d_tolerance: NULL lwgeom generated from WKT\n %s\n\n"; + + lw1 = lwgeom_from_wkt(in1, LW_PARSER_CHECK_NONE); + lw2 = lwgeom_from_wkt(in2, LW_PARSER_CHECK_NONE); + + if ( ! lw1 ) + { + printf(msg2, in1); + exit(1); + } + if ( ! lw2 ) + { + printf(msg2, in2); + exit(1); + } + + FLAGS_SET_SOLID(lw1->flags, 1); + FLAGS_SET_SOLID(lw2->flags, 1); + + distance = distancef(lw1, lw2, 0.0); + lwgeom_free(lw1); + lwgeom_free(lw2); + if ( fabs(distance - expected_res) > accepted_error ) + { + printf(msg1, distance, expected_res, line); + CU_FAIL(); + } + else + { + CU_PASS(); + } + +} + +static void test_mindistance2d_tolerance(void) +{ + double default_accepted_error = 0.00001; + double zero_accepted_error = 0.0; + + + /* + ** Simple case. + */ + DIST2DTEST("POINT(0 0)", "MULTIPOINT(0 1.5,0 2,0 2.5)", 1.5, default_accepted_error); + + /* + ** Point vs Geometry Collection. + */ + DIST2DTEST("POINT(0 0)", "GEOMETRYCOLLECTION(POINT(3 4))", 5.0, default_accepted_error); + + /* + ** Point vs Geometry Collection Collection. + */ + DIST2DTEST("POINT(0 0)", "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(3 4)))", 5.0, default_accepted_error); + + /* + ** Point vs Geometry Collection Collection Collection. + */ + DIST2DTEST("POINT(0 0)", "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(3 4))))", 5.0, default_accepted_error); + + /* + ** Point vs Geometry Collection Collection Collection Multipoint. + */ + DIST2DTEST("POINT(0 0)", "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(MULTIPOINT(3 4))))", 5.0, default_accepted_error); + + /* + ** Geometry Collection vs Geometry Collection + */ + DIST2DTEST("GEOMETRYCOLLECTION(POINT(0 0))", "GEOMETRYCOLLECTION(POINT(3 4))", 5.0, default_accepted_error); + + /* + ** Geometry Collection Collection vs Geometry Collection Collection + */ + DIST2DTEST("GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(0 0)))", "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(3 4)))", 5.0, default_accepted_error); + + /* + ** Geometry Collection Collection Multipoint vs Geometry Collection Collection Multipoint + */ + DIST2DTEST("GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(MULTIPOINT(0 0)))", "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(MULTIPOINT(3 4)))", 5.0, default_accepted_error); + + /* + ** Linestring vs its start point + */ + DIST2DTEST("LINESTRING(-2 0, -0.2 0)", "POINT(-2 0)", 0, zero_accepted_error); + + /* + ** Linestring vs its end point + */ + DIST2DTEST("LINESTRING(-0.2 0, -2 0)", "POINT(-2 0)", 0, zero_accepted_error); + + /* + ** Linestring vs its start point (tricky number, see #1459) + */ + DIST2DTEST("LINESTRING(-1e-8 0, -0.2 0)", "POINT(-1e-8 0)", 0, zero_accepted_error); + + /* + ** Linestring vs its end point (tricky number, see #1459) + */ + DIST2DTEST("LINESTRING(-0.2 0, -1e-8 0)", "POINT(-1e-8 0)", 0, zero_accepted_error); + + /* + * Circular string and point + */ + DIST2DTEST("CIRCULARSTRING(-1 0, 0 1, 1 0)", "POINT(0 0)", 1, default_accepted_error); + DIST2DTEST("CIRCULARSTRING(-3 0, -2 0, -1 0, 0 1, 1 0)", "POINT(0 0)", 1, default_accepted_error); + + /* + * Circular string and Circular string + */ + DIST2DTEST("CIRCULARSTRING(-1 0, 0 1, 1 0)", "CIRCULARSTRING(0 0, 1 -1, 2 0)", 1, default_accepted_error); + + /* + * CurvePolygon and Point + */ + static char *cs1 = "CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(1 6, 6 1, 9 7),(9 7, 3 13, 1 6)),COMPOUNDCURVE((3 6, 5 4, 7 4, 7 6),CIRCULARSTRING(7 6,5 8,3 6)))"; + DIST2DTEST(cs1, "POINT(3 14)", 1, default_accepted_error); + DIST2DTEST(cs1, "POINT(3 8)", 0, default_accepted_error); + DIST2DTEST(cs1, "POINT(6 5)", 1, default_accepted_error); + DIST2DTEST(cs1, "POINT(6 4)", 0, default_accepted_error); + + /* + * CurvePolygon and Linestring + */ + DIST2DTEST(cs1, "LINESTRING(0 0, 50 0)", 0.917484, default_accepted_error); + DIST2DTEST(cs1, "LINESTRING(6 0, 10 7)", 0, default_accepted_error); + DIST2DTEST(cs1, "LINESTRING(4 4, 4 8)", 0, default_accepted_error); + DIST2DTEST(cs1, "LINESTRING(4 7, 5 6, 6 7)", 0.585786, default_accepted_error); + DIST2DTEST(cs1, "LINESTRING(10 0, 10 2, 10 0)", 1.52913, default_accepted_error); + + /* + * CurvePolygon and Polygon + */ + DIST2DTEST(cs1, "POLYGON((10 4, 10 8, 13 8, 13 4, 10 4))", 0.58415, default_accepted_error); + DIST2DTEST(cs1, "POLYGON((9 4, 9 8, 12 8, 12 4, 9 4))", 0, default_accepted_error); + DIST2DTEST(cs1, "POLYGON((1 4, 1 8, 4 8, 4 4, 1 4))", 0, default_accepted_error); + + /* + * CurvePolygon and CurvePolygon + */ + DIST2DTEST(cs1, "CURVEPOLYGON(CIRCULARSTRING(-1 4, 0 5, 1 4, 0 3, -1 4))", 0.0475666, default_accepted_error); + DIST2DTEST(cs1, "CURVEPOLYGON(CIRCULARSTRING(1 4, 2 5, 3 4, 2 3, 1 4))", 0.0, default_accepted_error); + + /* + * MultiSurface and CurvePolygon + */ + static char *cs2 = "MULTISURFACE(POLYGON((0 0,0 4,4 4,4 0,0 0)),CURVEPOLYGON(CIRCULARSTRING(8 2,10 4,12 2,10 0,8 2)))"; + DIST2DTEST(cs2, "CURVEPOLYGON(CIRCULARSTRING(5 2,6 3,7 2,6 1,5 2))", 1, default_accepted_error); + DIST2DTEST(cs2, "CURVEPOLYGON(CIRCULARSTRING(4 2,5 3,6 2,5 1,4 2))", 0, default_accepted_error); + DIST2DTEST(cs2, "CURVEPOLYGON(CIRCULARSTRING(5 3,6 2,5 1,4 2,5 3))", 0, default_accepted_error); + DIST2DTEST(cs2, "CURVEPOLYGON(CIRCULARSTRING(4.5 3,5.5 2,4.5 1,3.5 2,4.5 3))", 0, default_accepted_error); + DIST2DTEST(cs2, "CURVEPOLYGON(CIRCULARSTRING(5.5 3,6.5 2,5.5 1,4.5 2,5.5 3))", 0.5, default_accepted_error); + DIST2DTEST(cs2, "CURVEPOLYGON(CIRCULARSTRING(10 3,11 2,10 1,9 2,10 3))", 0, default_accepted_error); + DIST2DTEST(cs2, "CURVEPOLYGON(CIRCULARSTRING(2 3,3 2,2 1,1 2,2 3))", 0, default_accepted_error); + DIST2DTEST(cs2, "CURVEPOLYGON(CIRCULARSTRING(5 7,6 8,7 7,6 6,5 7))", 2.60555, default_accepted_error); + + /* + * MultiCurve and Linestring + */ + DIST2DTEST("LINESTRING(0.5 1,0.5 3)", "MULTICURVE(CIRCULARSTRING(2 3,3 2,2 1,1 2,2 3),(0 0, 0 5))", 0.5, default_accepted_error); + + /* + ** "Fast path" case + */ + DIST2DTEST("LINESTRING(10 0,11 1,12 2,13 3,14 4,15 5,16 6)", + "LINESTRING(1 1.5,2 3,3 4.5,4 6,5 7.5,6 9)", + 8.3205, default_accepted_error); + + /* + ** Ticket 4326 + */ + DIST2DTEST( + "CURVEPOLYGON(CIRCULARSTRING(7874821 8715927,8907663 8715927,8844683 7750316,7937800 7750316,7874821 8715927))", + "POINT(5433865 8243495)", 2271704.2698450615, default_accepted_error); +} + +static void +test_mindistance3d_tolerance(void) +{ + double default_accepted_error = 0.00001; + double zero_accepted_error = 0.0; + /* 2D [Z=0] should work just the same */ + DIST3DTEST("POINT(0 0 0)", "MULTIPOINT(0 1.5 0, 0 2 0, 0 2.5 0)", 1.5, default_accepted_error); + DIST3DTEST("POINT(0 0 0)", "MULTIPOINT(0 1.5 0, 0 2 0, 0 2.5 0)", 1.5, default_accepted_error); + DIST3DTEST("POINT(0 0 0)", "GEOMETRYCOLLECTION(POINT(3 4 0))", 5.0, default_accepted_error); + DIST3DTEST("POINT(0 0 0)", "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(3 4 0)))", 5.0, default_accepted_error); + DIST3DTEST("POINT(0 0 0)", "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(3 4 0))))", 5.0, default_accepted_error); + DIST3DTEST("POINT(0 0)", "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(MULTIPOINT(3 4))))", 5.0, default_accepted_error); + DIST3DTEST("GEOMETRYCOLLECTION(POINT(0 0 0))", "GEOMETRYCOLLECTION(POINT(3 4 0))", 5.0, default_accepted_error); + DIST3DTEST("GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(0 0 0)))", + "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(3 4 0)))", + 5.0, default_accepted_error); + DIST3DTEST("GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(MULTIPOINT(0 0 0)))", + "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(MULTIPOINT(3 4 0)))", + 5.0, default_accepted_error); + DIST3DTEST("LINESTRING(-2 0 0, -0.2 0 0)", "POINT(-2 0 0)", 0, zero_accepted_error); + DIST3DTEST("LINESTRING(-0.2 0 0, -2 0 0)", "POINT(-2 0 0)", 0, zero_accepted_error); + DIST3DTEST("LINESTRING(-1e-8 0 0, -0.2 0 0)", "POINT(-1e-8 0 0)", 0, zero_accepted_error); + DIST3DTEST("LINESTRING(-0.2 0 0, -1e-8 0 0)", "POINT(-1e-8 0 0)", 0, zero_accepted_error); + + /* Tests around intersections */ + DIST3DTEST("LINESTRING(1 0 0 , 2 0 0)", "POLYGON((1 1 0, 2 1 0, 2 2 0, 1 2 0, 1 1 0))", 1.0, default_accepted_error); + DIST3DTEST("LINESTRING(1 1 1 , 2 1 0)", "POLYGON((1 1 0, 2 1 0, 2 2 0, 1 2 0, 1 1 0))", 0.0, zero_accepted_error); + DIST3DTEST("LINESTRING(1 1 1 , 2 1 1)", "POLYGON((1 1 0, 2 1 0, 2 2 0, 1 2 0, 1 1 0))", 1.0, default_accepted_error); + + /* Same but triangles */ + DIST3DTEST("LINESTRING(1 0 0 , 2 0 0)", "TRIANGLE((1 1 0, 2 1 0, 1 2 0, 1 1 0))", 1.0, default_accepted_error); + DIST3DTEST("LINESTRING(1 1 1 , 2 1 0)", "TRIANGLE((1 1 0, 2 1 0, 1 2 0, 1 1 0))", 0.0, zero_accepted_error); + DIST3DTEST("LINESTRING(1 1 1 , 2 1 1)", "TRIANGLE((1 1 0, 2 1 0, 1 2 0, 1 1 0))", 1.0, default_accepted_error); + + /* Triangle to triangle*/ + DIST3DTEST("TRIANGLE((-1 1 0, -2 1 0, -1 2 0, -1 1 0))", "TRIANGLE((1 1 0, 2 1 0, 1 2 0, 1 1 0))", 2.0, default_accepted_error); + + /* Line in polygon */ + DIST3DTEST("LINESTRING(1 1 1 , 2 2 2)", "POLYGON((0 0 0, 2 2 2, 3 3 1, 0 0 0))", 0.0, zero_accepted_error); + + /* Line has a point in the same plane as the polygon but isn't the closest*/ + DIST3DTEST("LINESTRING(-10000 -10000 0, 0 0 1)", "POLYGON((0 0 0, 1 0 0, 1 1 0, 0 1 0, 0 0 0))", 1, default_accepted_error); + + /* This is an invalid polygon since it defines just a line */ + DIST3DTEST("LINESTRING(1 1 1, 2 2 2)", "POLYGON((0 0 0, 2 2 2, 3 3 3, 0 0 0))", 0, zero_accepted_error); + DIST3DTEST("TRIANGLE((1 1 1, 2 2 2, 3 3 3, 1 1 1))", "POLYGON((0 0 0, 2 2 2, 3 3 3, 0 0 0))", 0, zero_accepted_error); + DIST3DTEST("POLYGON((0 0 0, 2 2 2, 3 3 3, 0 0 0))", "TRIANGLE((1 1 1, 2 2 2, 3 3 3, 1 1 1))", 0, zero_accepted_error); + DIST3DTEST("TRIANGLE((0 0 0, 2 2 2, 3 3 3, 0 0 0))", "LINESTRING(1 1 1, 2 2 2)", 0, zero_accepted_error); + + /* A box in a box: two solids, one inside another */ + DIST3DTEST( + "POLYHEDRALSURFACE Z (((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),((0 0 1,1 0 1,1 1 1,0 1 1,0 0 1)),((0 0 0,0 0 1,0 1 1,0 1 0,0 0 0)),((1 0 0,1 1 0,1 1 1,1 0 1,1 0 0)),((0 0 0,1 0 0,1 0 1,0 0 1,0 0 0)),((0 1 0,0 1 1,1 1 1,1 1 0,0 1 0)))", + "POLYHEDRALSURFACE Z (((-1 -1 -1,-1 2 -1,2 2 -1,2 -1 -1,-1 -1 -1)),((-1 -1 2,2 -1 2,2 2 2,-1 2 2,-1 -1 2)),((-1 -1 -1,-1 -1 2,-1 2 2,-1 2 -1,-1 -1 -1)),((2 -1 -1,2 2 -1,2 2 2,2 -1 2,2 -1 -1)),((-1 -1 -1,2 -1 -1,2 -1 2,-1 -1 2,-1 -1 -1)),((-1 2 -1,-1 2 2,2 2 2,2 2 -1,-1 2 -1)))", + 0, zero_accepted_error); + + /* A box in a box with a hat: two solids, one inside another, Z ray up is hitting hat */ + DIST3DTEST( + "POLYHEDRALSURFACE Z (((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),((0 0 1,1 0 1,1 1 1,0 1 1,0 0 1)),((0 0 0,0 0 1,0 1 1,0 1 0,0 0 0)),((1 0 0,1 1 0,1 1 1,1 0 1,1 0 0)),((0 0 0,1 0 0,1 0 1,0 0 1,0 0 0)),((0 1 0,0 1 1,1 1 1,1 1 0,0 1 0)))", + "POLYHEDRALSURFACE Z (((0 0 2,0 1 2,-1 2 2,0 0 2)),((0 1 2,0 0 2,0 1 4,0 1 2)),((-1 2 2,0 1 2,1 1 2,-1 2 2)),((0 0 2,-1 2 2,-1 -1 2,0 0 2)),((0 1 4,0 0 2,0 0 4,0 1 4)),((0 1 2,0 1 4,1 1 4,0 1 2)),((1 1 2,0 1 2,1 1 4,1 1 2)),((-1 2 2,1 1 2,2 2 2,-1 2 2)),((-1 -1 2,-1 2 2,-1 -1 -1,-1 -1 2)),((0 0 2,-1 -1 2,1 0 2,0 0 2)),((0 0 4,0 0 2,1 0 2,0 0 4)),((0 1 4,0 0 4,1 0 4,0 1 4)),((1 1 4,0 1 4,1 0 4,1 1 4)),((1 1 2,1 1 4,1 0 4,1 1 2)),((2 2 2,1 1 2,2 -1 2,2 2 2)),((-1 2 2,2 2 2,-1 2 -1,-1 2 2)),((-1 -1 -1,-1 2 2,-1 2 -1,-1 -1 -1)),((-1 -1 2,-1 -1 -1,2 -1 -1,-1 -1 2)),((1 0 2,-1 -1 2,2 -1 2,1 0 2)),((0 0 4,1 0 2,1 0 4,0 0 4)),((1 1 2,1 0 4,1 0 2,1 1 2)),((2 -1 2,1 1 2,1 0 2,2 -1 2)),((2 2 2,2 -1 2,2 2 -1,2 2 2)),((-1 2 -1,2 2 2,2 2 -1,-1 2 -1)),((-1 -1 -1,-1 2 -1,2 2 -1,-1 -1 -1)),((2 -1 -1,-1 -1 -1,2 2 -1,2 -1 -1)),((-1 -1 2,2 -1 -1,2 -1 2,-1 -1 2)),((2 2 -1,2 -1 2,2 -1 -1,2 2 -1)))", + 0, zero_accepted_error); + + /* Same but as TIN */ + DIST3DTEST( + "TIN Z (((0 0 2,0 1 2,-1 2 2,0 0 2)),((0 1 2,0 0 2,0 1 4,0 1 2)),((-1 2 2,0 1 2,1 1 2,-1 2 2)),((0 0 2,-1 2 2,-1 -1 2,0 0 2)),((0 1 4,0 0 2,0 0 4,0 1 4)),((0 1 2,0 1 4,1 1 4,0 1 2)),((1 1 2,0 1 2,1 1 4,1 1 2)),((-1 2 2,1 1 2,2 2 2,-1 2 2)),((-1 -1 2,-1 2 2,-1 -1 -1,-1 -1 2)),((0 0 2,-1 -1 2,1 0 2,0 0 2)),((0 0 4,0 0 2,1 0 2,0 0 4)),((0 1 4,0 0 4,1 0 4,0 1 4)),((1 1 4,0 1 4,1 0 4,1 1 4)),((1 1 2,1 1 4,1 0 4,1 1 2)),((2 2 2,1 1 2,2 -1 2,2 2 2)),((-1 2 2,2 2 2,-1 2 -1,-1 2 2)),((-1 -1 -1,-1 2 2,-1 2 -1,-1 -1 -1)),((-1 -1 2,-1 -1 -1,2 -1 -1,-1 -1 2)),((1 0 2,-1 -1 2,2 -1 2,1 0 2)),((0 0 4,1 0 2,1 0 4,0 0 4)),((1 1 2,1 0 4,1 0 2,1 1 2)),((2 -1 2,1 1 2,1 0 2,2 -1 2)),((2 2 2,2 -1 2,2 2 -1,2 2 2)),((-1 2 -1,2 2 2,2 2 -1,-1 2 -1)),((-1 -1 -1,-1 2 -1,2 2 -1,-1 -1 -1)),((2 -1 -1,-1 -1 -1,2 2 -1,2 -1 -1)),((-1 -1 2,2 -1 -1,2 -1 2,-1 -1 2)),((2 2 -1,2 -1 2,2 -1 -1,2 2 -1)))", + "POLYHEDRALSURFACE Z (((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),((0 0 1,1 0 1,1 1 1,0 1 1,0 0 1)),((0 0 0,0 0 1,0 1 1,0 1 0,0 0 0)),((1 0 0,1 1 0,1 1 1,1 0 1,1 0 0)),((0 0 0,1 0 0,1 0 1,0 0 1,0 0 0)),((0 1 0,0 1 1,1 1 1,1 1 0,0 1 0)))", + 0, zero_accepted_error); + + /* Same but both are TIN */ + DIST3DTEST( + "TIN Z (((0 0 2,0 1 2,-1 2 2,0 0 2)),((0 1 2,0 0 2,0 1 4,0 1 2)),((-1 2 2,0 1 2,1 1 2,-1 2 2)),((0 0 2,-1 2 2,-1 -1 2,0 0 2)),((0 1 4,0 0 2,0 0 4,0 1 4)),((0 1 2,0 1 4,1 1 4,0 1 2)),((1 1 2,0 1 2,1 1 4,1 1 2)),((-1 2 2,1 1 2,2 2 2,-1 2 2)),((-1 -1 2,-1 2 2,-1 -1 -1,-1 -1 2)),((0 0 2,-1 -1 2,1 0 2,0 0 2)),((0 0 4,0 0 2,1 0 2,0 0 4)),((0 1 4,0 0 4,1 0 4,0 1 4)),((1 1 4,0 1 4,1 0 4,1 1 4)),((1 1 2,1 1 4,1 0 4,1 1 2)),((2 2 2,1 1 2,2 -1 2,2 2 2)),((-1 2 2,2 2 2,-1 2 -1,-1 2 2)),((-1 -1 -1,-1 2 2,-1 2 -1,-1 -1 -1)),((-1 -1 2,-1 -1 -1,2 -1 -1,-1 -1 2)),((1 0 2,-1 -1 2,2 -1 2,1 0 2)),((0 0 4,1 0 2,1 0 4,0 0 4)),((1 1 2,1 0 4,1 0 2,1 1 2)),((2 -1 2,1 1 2,1 0 2,2 -1 2)),((2 2 2,2 -1 2,2 2 -1,2 2 2)),((-1 2 -1,2 2 2,2 2 -1,-1 2 -1)),((-1 -1 -1,-1 2 -1,2 2 -1,-1 -1 -1)),((2 -1 -1,-1 -1 -1,2 2 -1,2 -1 -1)),((-1 -1 2,2 -1 -1,2 -1 2,-1 -1 2)),((2 2 -1,2 -1 2,2 -1 -1,2 2 -1)))", + "TIN Z (((0 0 0,0 1 0,1 1 0,0 0 0)),((1 0 0,0 0 0,1 1 0,1 0 0)),((0 1 1,1 0 1,1 1 1,0 1 1)),((0 1 1,0 0 1,1 0 1,0 1 1)),((0 0 0,0 0 1,0 1 1,0 0 0)),((0 1 0,0 0 0,0 1 1,0 1 0)),((1 0 1,1 1 0,1 1 1,1 0 1)),((1 0 1,1 0 0,1 1 0,1 0 1)),((0 0 1,1 0 0,1 0 1,0 0 1)),((0 0 1,0 0 0,1 0 0,0 0 1)),((0 1 0,0 1 1,1 1 1,0 1 0)),((1 1 0,0 1 0,1 1 1,1 1 0)))", + 0, zero_accepted_error); + + /* Point inside TIN */ + DIST3DTEST( + "TIN Z (((0 0 2,0 1 2,-1 2 2,0 0 2)),((0 1 2,0 0 2,0 1 4,0 1 2)),((-1 2 2,0 1 2,1 1 2,-1 2 2)),((0 0 2,-1 2 2,-1 -1 2,0 0 2)),((0 1 4,0 0 2,0 0 4,0 1 4)),((0 1 2,0 1 4,1 1 4,0 1 2)),((1 1 2,0 1 2,1 1 4,1 1 2)),((-1 2 2,1 1 2,2 2 2,-1 2 2)),((-1 -1 2,-1 2 2,-1 -1 -1,-1 -1 2)),((0 0 2,-1 -1 2,1 0 2,0 0 2)),((0 0 4,0 0 2,1 0 2,0 0 4)),((0 1 4,0 0 4,1 0 4,0 1 4)),((1 1 4,0 1 4,1 0 4,1 1 4)),((1 1 2,1 1 4,1 0 4,1 1 2)),((2 2 2,1 1 2,2 -1 2,2 2 2)),((-1 2 2,2 2 2,-1 2 -1,-1 2 2)),((-1 -1 -1,-1 2 2,-1 2 -1,-1 -1 -1)),((-1 -1 2,-1 -1 -1,2 -1 -1,-1 -1 2)),((1 0 2,-1 -1 2,2 -1 2,1 0 2)),((0 0 4,1 0 2,1 0 4,0 0 4)),((1 1 2,1 0 4,1 0 2,1 1 2)),((2 -1 2,1 1 2,1 0 2,2 -1 2)),((2 2 2,2 -1 2,2 2 -1,2 2 2)),((-1 2 -1,2 2 2,2 2 -1,-1 2 -1)),((-1 -1 -1,-1 2 -1,2 2 -1,-1 -1 -1)),((2 -1 -1,-1 -1 -1,2 2 -1,2 -1 -1)),((-1 -1 2,2 -1 -1,2 -1 2,-1 -1 2)),((2 2 -1,2 -1 2,2 -1 -1,2 2 -1)))", + "POINT(0 0 0)", + 0, zero_accepted_error); + + /* A point hits vertical Z edge */ + DIST3DTEST( + "POLYHEDRALSURFACE Z (((0 -1 1,-1 -1 1,-1 -1 -1,0 -1 -1,1 -1 -1,0 -1 2,0 -1 1)),((0 1 1,0 1 2,1 1 -1,0 1 -1,-1 1 -1,-1 1 1,0 1 1)),((0 -1 1,0 1 1,-1 1 1,-1 -1 1,0 -1 1)),((-1 -1 1,-1 1 1,-1 1 -1,-1 -1 -1,-1 -1 1)),((-1 -1 -1,-1 1 -1,0 1 -1,0 -1 -1,-1 -1 -1)),((0 -1 -1,0 1 -1,1 1 -1,1 -1 -1,0 -1 -1)),((1 -1 -1,1 1 -1,0 1 2,0 -1 2,1 -1 -1)),((0 -1 2,0 1 2,0 1 1,0 -1 1,0 -1 2)))", + "POINT(0 0 0)", + 0, zero_accepted_error); + + /* A point in the middle of a hole of extruded polygon */ + DIST3DTEST( + "POLYHEDRALSURFACE Z (((-3 -3 0,-3 3 0,3 3 0,3 -3 0,-3 -3 0),(-1 -1 0,1 -1 0,1 1 0,-1 1 0,-1 -1 0)),((-3 -3 3,3 -3 3,3 3 3,-3 3 3,-3 -3 3),(-1 -1 3,-1 1 3,1 1 3,1 -1 3,-1 -1 3)),((-3 -3 0,-3 -3 3,-3 3 3,-3 3 0,-3 -3 0)),((-3 3 0,-3 3 3,3 3 3,3 3 0,-3 3 0)),((3 3 0,3 3 3,3 -3 3,3 -3 0,3 3 0)),((3 -3 0,3 -3 3,-3 -3 3,-3 -3 0,3 -3 0)),((-1 -1 0,-1 -1 3,1 -1 3,1 -1 0,-1 -1 0)),((1 -1 0,1 -1 3,1 1 3,1 1 0,1 -1 0)),((1 1 0,1 1 3,-1 1 3,-1 1 0,1 1 0)),((-1 1 0,-1 1 3,-1 -1 3,-1 -1 0,-1 1 0)))", + "POINT(0 0 1)", + 1, zero_accepted_error); + + /* A point at the face of a hole of extruded polygon */ + DIST3DTEST( + "POLYHEDRALSURFACE Z (((-3 -3 0,-3 3 0,3 3 0,3 -3 0,-3 -3 0),(-1 -1 0,1 -1 0,1 1 0,-1 1 0,-1 -1 0)),((-3 -3 3,3 -3 3,3 3 3,-3 3 3,-3 -3 3),(-1 -1 3,-1 1 3,1 1 3,1 -1 3,-1 -1 3)),((-3 -3 0,-3 -3 3,-3 3 3,-3 3 0,-3 -3 0)),((-3 3 0,-3 3 3,3 3 3,3 3 0,-3 3 0)),((3 3 0,3 3 3,3 -3 3,3 -3 0,3 3 0)),((3 -3 0,3 -3 3,-3 -3 3,-3 -3 0,3 -3 0)),((-1 -1 0,-1 -1 3,1 -1 3,1 -1 0,-1 -1 0)),((1 -1 0,1 -1 3,1 1 3,1 1 0,1 -1 0)),((1 1 0,1 1 3,-1 1 3,-1 1 0,1 1 0)),((-1 1 0,-1 1 3,-1 -1 3,-1 -1 0,-1 1 0)))", + "POINT(1 1 2)", + 0, zero_accepted_error); + + /* A point at the face of a hole of extruded polygon */ + DIST3DTEST( + "LINESTRING Z (-27974.1264 -110211.5032 148.9768,-27975.4229 -110210.9441 149.0093)", + "LINESTRING Z (-27995.4183 -110201.8041 149.3354,-27975.4229 -110210.9441 149.0093)", + 0, zero_accepted_error); +} + +static int tree_pt(RECT_NODE *tree, double x, double y) +{ + POINT2D pt; + pt.x = x; pt.y = y; + return rect_tree_contains_point(tree, &pt); +} + +static void test_rect_tree_contains_point(void) +{ + LWGEOM *poly; + RECT_NODE* tree; + + /********************************************************************** + * curvepolygon + */ + poly = lwgeom_from_wkt("CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0,1 5,0 10),(0 10,10 10,10 0, 0 0)),COMPOUNDCURVE(CIRCULARSTRING(3 7,5 8,7 7),(7 7,7 3,3 3, 3 7)))", LW_PARSER_CHECK_NONE); + tree = rect_tree_from_lwgeom(poly); + // char *wkt = rect_tree_to_wkt(tree); + // printf("%s\n", wkt); + // lwfree(wkt); + // return; + + /* in hole, within arc stroke */ + CU_ASSERT_EQUAL(tree_pt(tree, 5, 7.5), 0); + /* inside */ + CU_ASSERT_EQUAL(tree_pt(tree, 8, 9), 1); + /* outside */ + CU_ASSERT_EQUAL(tree_pt(tree, -1, 5), 0); + /* outside */ + CU_ASSERT_EQUAL(tree_pt(tree, -1, 7.5), 0); + /* outside, within arc stroke */ + CU_ASSERT_EQUAL(tree_pt(tree, 0.2, 7.5), 0); + /* inside, within arc stroke */ + CU_ASSERT_EQUAL(tree_pt(tree, 0.5, 0.5), 1); + /* inside, crossing arc stroke */ + CU_ASSERT_EQUAL(tree_pt(tree, 2, 7.5), 1); + /* touching hole corner */ + CU_ASSERT_EQUAL(tree_pt(tree, 7, 7), 1); + + rect_tree_free(tree); + lwgeom_free(poly); + + + /********************************************************************** + * polygon with hole and concavities + */ + poly = lwgeom_from_wkt("POLYGON((0 0,0 10,10 10,10 0,9 0,9 9,8 6,8 0,2 0,2 9,1 6,1 0,0 0),(4 4,4 6,6 6,6 4,4 4))", LW_PARSER_CHECK_NONE); + tree = rect_tree_from_lwgeom(poly); + + /* inside, many grazings */ + CU_ASSERT_EQUAL(tree_pt(tree, 3, 6), 1); + /* inside */ + CU_ASSERT_EQUAL(tree_pt(tree, 3, 5.5), 1); + /* outside */ + CU_ASSERT_EQUAL(tree_pt(tree, -3, 5.5), 0); + /* touching interior ring */ + CU_ASSERT_EQUAL(tree_pt(tree, 4, 4), 1); + CU_ASSERT_EQUAL(tree_pt(tree, 6, 6), 1); + /* touching interior ring */ + CU_ASSERT_EQUAL(tree_pt(tree, 4.5, 4), 1); + /* touching exterior ring */ + CU_ASSERT_EQUAL(tree_pt(tree, 8, 0), 1); + CU_ASSERT_EQUAL(tree_pt(tree, 9, 0), 1); + CU_ASSERT_EQUAL(tree_pt(tree, 10, 1), 1); + CU_ASSERT_EQUAL(tree_pt(tree, 9.5, 1), 1); + CU_ASSERT_EQUAL(tree_pt(tree, 0, 10), 1); + /* touching grazing spike */ + CU_ASSERT_EQUAL(tree_pt(tree, 1, 6), 1); + /* outide, many grazings */ + CU_ASSERT_EQUAL(tree_pt(tree, -1, 6), 0); + /* within hole */ + CU_ASSERT_EQUAL(tree_pt(tree, 5, 5), 0); + /* within */ + CU_ASSERT_EQUAL(tree_pt(tree, 0.5, 4), 1); + CU_ASSERT_EQUAL(tree_pt(tree, 0.5, 6), 1); + CU_ASSERT_EQUAL(tree_pt(tree, 0.5, 9), 1); + + rect_tree_free(tree); + lwgeom_free(poly); + + /********************************************************************** + * square + */ + poly = lwgeom_from_wkt("POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))", LW_PARSER_CHECK_NONE); + tree = rect_tree_from_lwgeom(poly); + + /* inside square */ + CU_ASSERT_EQUAL(tree_pt(tree, 0.5, 0.5), 1); + /* outside square */ + CU_ASSERT_EQUAL(tree_pt(tree, 1.5, 0.5), 0); + /* outside square grazing some edges */ + CU_ASSERT_EQUAL(tree_pt(tree, -1, 1), 0); + /* inside square on corner */ + CU_ASSERT_EQUAL(tree_pt(tree, 1, 1), 1); + /* inside square on top edge */ + CU_ASSERT_EQUAL(tree_pt(tree, 0.5, 1), 1); + /* inside square on side edge */ + CU_ASSERT_EQUAL(tree_pt(tree, 1, 0.5), 1); + + rect_tree_free(tree); + lwgeom_free(poly); + + /* ziggy zaggy horizontal saw tooth polygon */ + poly = lwgeom_from_wkt("POLYGON((0 0, 1 3, 2 0, 3 3, 4 0, 4 5, 0 5, 0 0))", LW_PARSER_CHECK_NONE); + tree = rect_tree_from_lwgeom(poly); + + /* not in, left side */ + CU_ASSERT_EQUAL(tree_pt(tree, -0.5, 0.5), 0); + /* not in, right side */ + CU_ASSERT_EQUAL(tree_pt(tree, 3, 1), 0); + /* inside */ + CU_ASSERT_EQUAL(tree_pt(tree, 2, 1), 1); + /* on left border */ + CU_ASSERT_EQUAL(tree_pt(tree, 0, 1), 1); + /* on left border, grazing */ + CU_ASSERT_EQUAL(tree_pt(tree, 0, 3), 1); + /* on right border */ + CU_ASSERT_EQUAL(tree_pt(tree, 4, 0), 1); + /* on tooth concave */ + CU_ASSERT_EQUAL(tree_pt(tree, 3, 3), 1); + /* on tooth convex */ + CU_ASSERT_EQUAL(tree_pt(tree, 2, 0), 1); + + rect_tree_free(tree); + lwgeom_free(poly); + + /********************************************************************** + * ziggy zaggy vertical saw tooth polygon + */ + poly = lwgeom_from_wkt("POLYGON((0 0, 3 1, 0 2, 3 3, 0 4, 3 5, 0 6, 5 6, 5 0, 0 0))", LW_PARSER_CHECK_NONE); + tree = rect_tree_from_lwgeom(poly); + + /* not in, left side */ + CU_ASSERT_EQUAL(tree_pt(tree, -0.5, 3.5), 0); + /* not in, right side */ + CU_ASSERT_EQUAL(tree_pt(tree, 6.0, 2.2), 0); + /* inside */ + CU_ASSERT_EQUAL(tree_pt(tree, 3, 2), 1); + /* on bottom border */ + CU_ASSERT_EQUAL(tree_pt(tree, 1, 0), 1); + /* on top border */ + CU_ASSERT_EQUAL(tree_pt(tree, 3, 6), 1); + /* on tooth concave */ + CU_ASSERT_EQUAL(tree_pt(tree, 3, 1), 1); + /* on tooth convex */ + CU_ASSERT_EQUAL(tree_pt(tree, 0, 2), 1); + /* on tooth convex */ + CU_ASSERT_EQUAL(tree_pt(tree, 0, 6), 1); + + rect_tree_free(tree); + lwgeom_free(poly); + +} + +static int tree_inter(const char *wkt1, const char *wkt2) +{ + LWGEOM *g1 = lwgeom_from_wkt(wkt1, LW_PARSER_CHECK_NONE); + LWGEOM *g2 = lwgeom_from_wkt(wkt2, LW_PARSER_CHECK_NONE); + RECT_NODE *t1 = rect_tree_from_lwgeom(g1); + RECT_NODE *t2 = rect_tree_from_lwgeom(g2); + int result = rect_tree_intersects_tree(t1, t2); + rect_tree_free(t1); + rect_tree_free(t2); + lwgeom_free(g1); + lwgeom_free(g2); + return result; +} + +static void test_rect_tree_intersects_tree(void) +{ + /* total overlap, A == B */ + CU_ASSERT_EQUAL(tree_inter( + "POLYGON((0 0, 3 1, 0 2, 3 3, 0 4, 3 5, 0 6, 5 6, 5 0, 0 0))", + "POLYGON((0 0, 3 1, 0 2, 3 3, 0 4, 3 5, 0 6, 5 6, 5 0, 0 0))"), + LW_TRUE + ); + + /* hiding between the tines of the comb */ + CU_ASSERT_EQUAL(tree_inter( + "POLYGON((0 0, 3 1, 0 2, 3 3, 0 4, 3 5, 0 6, 5 6, 5 0, 0 0))", + "POLYGON((0.3 0.7, 0.3 0.8, 0.4 0.8, 0.4 0.7, 0.3 0.7))"), + LW_FALSE + ); + + /* between the tines, but with a corner overlapping */ + CU_ASSERT_EQUAL(tree_inter( + "POLYGON((0 0, 3 1, 0 2, 3 3, 0 4, 3 5, 0 6, 5 6, 5 0, 0 0))", + "POLYGON((0.3 0.7, 0.3 0.8, 0.4 0.8, 1.3 0.3, 0.3 0.7))"), + LW_TRUE + ); + + /* Just touching the top left corner of the comb */ + CU_ASSERT_EQUAL(tree_inter( + "POLYGON((0 0, 3 1, 0 2, 3 3, 0 4, 3 5, 0 6, 5 6, 5 0, 0 0))", + "POLYGON((-1 5, 0 5, 0 7, -1 7, -1 5))"), + LW_TRUE + ); + + /* Contained, complex */ + CU_ASSERT_EQUAL(tree_inter( + "POLYGON((0 0, 3 1, 0 2, 3 3, 0 4, 3 5, 0 6, 5 6, 5 0, 0 0))", + "GEOMETRYCOLLECTION(MULTILINESTRING((1 2, 3 2)),POINT(1 2))"), + LW_TRUE + ); + + /* Touching, complex */ + CU_ASSERT_EQUAL(tree_inter( + "POLYGON((0 0, 3 1, 0 2, 3 3, 0 4, 3 5, 0 6, 5 6, 5 0, 0 0))", + "GEOMETRYCOLLECTION(MULTILINESTRING((6 3, 8 4)),POINT(5 3))"), + LW_TRUE + ); + + /* Not Touching, complex */ + CU_ASSERT_EQUAL(tree_inter( + "POLYGON((0 0, 3 1, 0 2, 3 3, 0 4, 3 5, 0 6, 5 6, 5 0, 0 0))", + "GEOMETRYCOLLECTION(MULTILINESTRING((6 3, 8 4)),POINT(1 3.5))"), + LW_FALSE + ); + + /* Crossing, complex */ + CU_ASSERT_EQUAL(tree_inter( + "POLYGON((0 0, 3 1, 0 2, 3 3, 0 4, 3 5, 0 6, 5 6, 5 0, 0 0))", + "GEOMETRYCOLLECTION(MULTILINESTRING((1.5 4.1, 1.6 2)),POINT(1 3.5))"), + LW_TRUE + ); +} + + +static double +test_rect_tree_distance_tree_case(const char *wkt1, const char *wkt2) +{ + LWGEOM *lw1 = lwgeom_from_wkt(wkt1, LW_PARSER_CHECK_NONE); + LWGEOM *lw2 = lwgeom_from_wkt(wkt2, LW_PARSER_CHECK_NONE); + RECT_NODE *n1 = rect_tree_from_lwgeom(lw1); + RECT_NODE *n2 = rect_tree_from_lwgeom(lw2); + + // rect_tree_printf(n1, 0); + // rect_tree_printf(n2, 0); + // + // printf("%s\n", rect_tree_to_wkt(n1)); + // printf("%s\n", rect_tree_to_wkt(n2)); + + double dist = rect_tree_distance_tree(n1, n2, 0.0); + // printf("%g\n", dist); + rect_tree_free(n1); + rect_tree_free(n2); + lwgeom_free(lw1); + lwgeom_free(lw2); + return dist; +} + +#define TDT(w1, w2, d) CU_ASSERT_DOUBLE_EQUAL(test_rect_tree_distance_tree_case(w1, w2), d, 0.00001); + +static void +test_rect_tree_distance_tree(void) +{ + const char *wkt; + + wkt = "MULTIPOLYGON(((-123.35702791281 48.4232302445918,-123.35689654493 48.4237265810249,-123.354053908057 48.4234039978588,-123.35417179975 48.4229151379279,-123.354369811539 48.4220987102936,-123.355779071731 48.4222571534228,-123.357238860904 48.4224209369449,-123.35702791281 48.4232302445918)))"; + TDT(wkt, "MULTIPOLYGON(((-123.353452578038 48.4259519079838,-123.35072012771 48.4256699150083,-123.347337809991 48.4254740864963,-123.347469111645 48.4245757659326,-123.349409235923 48.4246224093429,-123.349966167324 48.4246562342604,-123.353650661317 48.4250703224683,-123.353452578038 48.4259519079838)))", 0.0017144228293396); + + wkt = "POINT(0 0)"; + TDT(wkt, "MULTIPOINT(0 1.5,0 2,0 2.5)", 1.5); + TDT(wkt, "GEOMETRYCOLLECTION(POINT(3 4))", 5.0); + TDT(wkt, "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(3 4)))", 5.0); + TDT(wkt, "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(POINT(3 4))))", 5.0); + TDT(wkt, "GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(GEOMETRYCOLLECTION(MULTIPOINT(3 4))))", 5.0); + + TDT("LINESTRING(-2 0, -0.2 0)", "POINT(-2 0)", 0); + TDT("LINESTRING(-0.2 0, -2 0)", "POINT(-2 0)", 0); + TDT("LINESTRING(-1e-8 0, -0.2 0)", "POINT(-1e-8 0)", 0); + TDT("LINESTRING(-0.2 0, -1e-8 0)", "POINT(-1e-8 0)", 0); + + wkt = "CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(1 6, 6 1, 9 7),(9 7, 3 13, 1 6)),COMPOUNDCURVE((3 6, 5 4, 7 4, 7 6),CIRCULARSTRING(7 6,5 8,3 6)))"; + TDT(wkt, "POINT(3 14)", 1); + TDT(wkt, "POINT(3 8)", 0); + TDT(wkt, "POINT(6 5)", 1); + TDT(wkt, "POINT(6 4)", 0); + + wkt = "MULTISURFACE(POLYGON((0 0,0 4,4 4,4 0,0 0)),CURVEPOLYGON(CIRCULARSTRING(8 2,10 4,12 2,10 0,8 2)))"; + TDT(wkt, "CURVEPOLYGON(CIRCULARSTRING(5 7,6 8,7 7,6 6,5 7))", 2.60555); + TDT(wkt, "CURVEPOLYGON(CIRCULARSTRING(5 2,6 3,7 2,6 1,5 2))", 1); + TDT(wkt, "CURVEPOLYGON(CIRCULARSTRING(4 2,5 3,6 2,5 1,4 2))", 0); + TDT(wkt, "CURVEPOLYGON(CIRCULARSTRING(5 3,6 2,5 1,4 2,5 3))", 0); + TDT(wkt, "CURVEPOLYGON(CIRCULARSTRING(4.5 3,5.5 2,4.5 1,3.5 2,4.5 3))", 0); + TDT(wkt, "CURVEPOLYGON(CIRCULARSTRING(5.5 3,6.5 2,5.5 1,4.5 2,5.5 3))", 0.5); + TDT(wkt, "CURVEPOLYGON(CIRCULARSTRING(10 3,11 2,10 1,9 2,10 3))", 0); + TDT(wkt, "CURVEPOLYGON(CIRCULARSTRING(2 3,3 2,2 1,1 2,2 3))", 0); + + wkt = "CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0,5 0,0 0)))"; + TDT(wkt, "POINT(3 0)", 0.0); + TDT(wkt, "POINT(5 0)", 0.0); + TDT(wkt, "POINT(7 0)", 2.0); + TDT(wkt, "POINT(2.5 3.5)", 1.0); + + wkt = "POINT(0 0)"; + TDT(wkt, "POINT(0 1)", 1.0); + TDT(wkt, "POINT(1 0)", 1.0); + + wkt = "LINESTRING(0 0,1 0)"; + TDT(wkt, "LINESTRING(1 0,1 1)", 0.0); + TDT(wkt, "LINESTRING(0 1,1 1)", 1.0); + + wkt = "POLYGON((0 0,0 1,1 1,1 0,0 0))"; + TDT(wkt, "POINT(2 2)", sqrt(2)); + TDT(wkt, "POINT(0.5 0.5)", 0); + TDT(wkt, "POINT(1 1)", 0); + + wkt = "POLYGON((0 0,0 10,10 10,10 0,0 0), (4 4,4 6,6 6,6 4,4 4))"; + TDT(wkt, "POINT(5 5)", 1); + TDT(wkt, "POLYGON((5 5,5 5.5,5.5 5.5,5.5 5, 5 5))", 0.5); +} + + +static void +test_lwgeom_segmentize2d(void) +{ + LWGEOM *linein = lwgeom_from_wkt("LINESTRING(0 0,10 0)", LW_PARSER_CHECK_NONE); + LWGEOM *lineout = lwgeom_segmentize2d(linein, 5); + char *strout = lwgeom_to_ewkt(lineout); + ASSERT_STRING_EQUAL(strout, "LINESTRING(0 0,5 0,10 0)"); + lwfree(strout); + lwgeom_free(linein); + lwgeom_free(lineout); + + /* test that segmentize is proportional - request every 6, get every 5 */ + linein = lwgeom_from_wkt("LINESTRING(0 0, 20 0)", LW_PARSER_CHECK_NONE); + lineout = lwgeom_segmentize2d(linein, 6); + strout = lwgeom_to_ewkt(lineout); + ASSERT_STRING_EQUAL(strout, "LINESTRING(0 0,5 0,10 0,15 0,20 0)"); + lwfree(strout); + lwgeom_free(linein); + lwgeom_free(lineout); + + /* test too many segments */ + linein = lwgeom_from_wkt("LINESTRING(0 0,10 0)", LW_PARSER_CHECK_NONE); + lineout = lwgeom_segmentize2d(linein, 1e-100); + CU_ASSERT_EQUAL(lineout, NULL); + lwgeom_free(linein); + + /* test interruption */ + + linein = lwgeom_from_wkt("LINESTRING(0 0,10 0)", LW_PARSER_CHECK_NONE); + lwgeom_request_interrupt(); + lineout = lwgeom_segmentize2d(linein, INT32_MAX); + CU_ASSERT_EQUAL(lineout, NULL); + lwgeom_free(linein); + + linein = lwgeom_from_wkt("MULTILINESTRING((0 0,10 0),(20 0, 30 0))", LW_PARSER_CHECK_NONE); + lwgeom_request_interrupt(); + lineout = lwgeom_segmentize2d(linein, INT32_MAX); + CU_ASSERT_EQUAL(lineout, NULL); + lwgeom_free(linein); + + linein = lwgeom_from_wkt( + "MULTIPOLYGON(((0 0,20 0,20 20,0 20,0 0),(2 2,2 4,4 4,4 2,2 2),(6 6,6 8,8 8,8 6,6 6)),((40 0,40 20,60 20,60 0,40 0),(42 2,42 4,44 4,44 2,42 2)))" + , LW_PARSER_CHECK_NONE); + lwgeom_request_interrupt(); + lineout = lwgeom_segmentize2d(linein, INT32_MAX); + CU_ASSERT_EQUAL(lineout, NULL); + lwgeom_free(linein); + + linein = lwgeom_from_wkt( + "GEOMETRYCOLLECTION(MULTIPOLYGON(((0 0,20 0,20 20,0 20,0 0),(2 2,2 4,4 4,4 2,2 2),(6 6,6 8,8 8,8 6,6 6)),((40 0,40 20,60 20,60 0,40 0),(42 2,42 4,44 4,44 2,42 2))),MULTILINESTRING((0 0,10 0),(20 0, 30 0)),MULTIPOINT(0 0, 3 4))" + , LW_PARSER_CHECK_NONE); + CU_ASSERT_FATAL(linein != NULL); + lwgeom_request_interrupt(); + lineout = lwgeom_segmentize2d(linein, INT32_MAX); + CU_ASSERT_EQUAL(lineout, NULL); + lwgeom_free(linein); + + linein = lwgeom_from_wkt("LINESTRING(20 0, 30 0)", LW_PARSER_CHECK_NONE); + CU_ASSERT_FATAL(linein != NULL); + /* NOT INTERRUPTED */ + lineout = lwgeom_segmentize2d(linein, 5); + CU_ASSERT_NOT_EQUAL_FATAL(lineout, NULL); + strout = lwgeom_to_ewkt(lineout); + ASSERT_STRING_EQUAL(strout, "LINESTRING(20 0,25 0,30 0)"); + lwfree(strout); + lwgeom_free(linein); + lwgeom_free(lineout); +} + +static void +test_lwgeom_locate_along(void) +{ + LWGEOM *geom = NULL; + LWGEOM *out = NULL; + double measure = 105.0; + char *str; + + /* ST_Locatealong(ST_GeomFromText('MULTILINESTRING M ((1 2 3, 5 4 5), (50 50 1, 60 60 200))'), 105) */ + geom = lwgeom_from_wkt("MULTILINESTRING M ((1 2 3, 5 4 5), (50 50 1, 60 60 200))", LW_PARSER_CHECK_NONE); + out = lwgeom_locate_along(geom, measure, 0.0); + str = lwgeom_to_wkt(out, WKT_ISO, 6, NULL); + lwgeom_free(geom); + lwgeom_free(out); + ASSERT_STRING_EQUAL(str, "MULTIPOINT M ((55.226131 55.226131 105))"); + lwfree(str); + + /* ST_Locatealong(ST_GeomFromText('MULTILINESTRING M ((1 2 3, 5 4 5), (50 50 1, 60 60 200))'), 105) */ + geom = lwgeom_from_wkt("MULTILINESTRING M ((1 2 3, 3 4 2, 9 4 3), (1 2 3, 5 4 5), (50 50 1, 60 60 200))", LW_PARSER_CHECK_NONE); + out = lwgeom_locate_along(geom, measure, 0.0); + str = lwgeom_to_wkt(out, WKT_ISO, 6, NULL); + lwgeom_free(geom); + lwgeom_free(out); + ASSERT_STRING_EQUAL(str, "MULTIPOINT M ((55.226131 55.226131 105))"); + lwfree(str); +} + +static void +test_lw_dist2d_pt_arc(void) +{ + /* int lw_dist2d_pt_arc(const POINT2D* P, const POINT2D* A1, const POINT2D* A2, const POINT2D* A3, DISTPTS* dl) */ + DISTPTS dl; + POINT2D P, A1, A2, A3; + int rv; + + + /* Point within unit semicircle, 0.5 units from arc */ + A1.x = -1; A1.y = 0; + A2.x = 0 ; A2.y = 1; + A3.x = 1 ; A3.y = 0; + P.x = 0 ; P.y = 0.5; + + lw_dist2d_distpts_init(&dl, DIST_MIN); + rv = lw_dist2d_pt_arc(&P, &A1, &A2, &A3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 0.5, 0.000001); + + /* Point outside unit semicircle, 0.5 units from arc */ + P.x = 0 ; P.y = 1.5; + lw_dist2d_distpts_init(&dl, DIST_MIN); + rv = lw_dist2d_pt_arc(&P, &A1, &A2, &A3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 0.5, 0.000001); + + /* Point outside unit semicircle, sqrt(2) units from arc end point*/ + P.x = 0 ; P.y = -1; + lw_dist2d_distpts_init(&dl, DIST_MIN); + rv = lw_dist2d_pt_arc(&P, &A1, &A2, &A3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, sqrt(2.0), 0.000001); + + /* Point outside unit semicircle, sqrt(2)-1 units from arc end point*/ + P.x = 1 ; P.y = 1; + lw_dist2d_distpts_init(&dl, DIST_MIN); + rv = lw_dist2d_pt_arc(&P, &A1, &A2, &A3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, sqrt(2.0)-1, 0.000001); + + /* Point on unit semicircle midpoint */ + P.x = 0 ; P.y = 1; + lw_dist2d_distpts_init(&dl, DIST_MIN); + rv = lw_dist2d_pt_arc(&P, &A1, &A2, &A3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 0, 0.000001); + + /* Point on unit semicircle endpoint */ + P.x = 1 ; P.y = 0; + lw_dist2d_distpts_init(&dl, DIST_MIN); + rv = lw_dist2d_pt_arc(&P, &A1, &A2, &A3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 0, 0.000001); + + /* Point on semicircle center */ + P.x = 0 ; P.y = 0; + lw_dist2d_distpts_init(&dl, DIST_MIN); + rv = lw_dist2d_pt_arc(&P, &A1, &A2, &A3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 1, 0.000001); + + /* Point inside closed circle */ + P.x = 0 ; P.y = 0.5; + A2.x = 1; A2.y = 0; + A3 = A1; + lw_dist2d_distpts_init(&dl, DIST_MIN); + rv = lw_dist2d_pt_arc(&P, &A1, &A2, &A3, &dl); + //printf("distance %g\n", dl.distance); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 0.5, 0.000001); +} + +static void +test_lw_dist2d_seg_arc(void) +{ + /* int lw_dist2d_seg_arc(const POINT2D *A1, const POINT2D *A2, const POINT2D *B1, const POINT2D *B2, const POINT2D *B3, DISTPTS *dl) */ + + DISTPTS dl; + POINT2D A1, A2, B1, B2, B3; + int rv; + + /* Unit semicircle */ + B1.x = -1; B1.y = 0; + B2.x = 0 ; B2.y = 1; + B3.x = 1 ; B3.y = 0; + + /* Edge above the unit semicircle */ + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -2; A1.y = 2; + A2.x = 2 ; A2.y = 2; + rv = lw_dist2d_seg_arc(&A1, &A2, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 1, 0.000001); + + /* Edge to the right of the unit semicircle */ + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = 2; A1.y = -2; + A2.x = 2; A2.y = 2; + rv = lw_dist2d_seg_arc(&A1, &A2, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 1, 0.000001); + + /* Edge to the left of the unit semicircle */ + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -2; A1.y = -2; + A2.x = -2; A2.y = 2; + rv = lw_dist2d_seg_arc(&A1, &A2, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 1, 0.000001); + + /* Edge within the unit semicircle */ + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = 0; A1.y = 0; + A2.x = 0; A2.y = 0.5; + rv = lw_dist2d_seg_arc(&A1, &A2, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 0.5, 0.000001); + + /* Edge grazing the unit semicircle */ + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -2; A1.y = 1; + A2.x = 2; A2.y = 1; + rv = lw_dist2d_seg_arc(&A1, &A2, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 0., 0.000001); + + /* Line grazing the unit semicircle, but edge not */ + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = 1; A1.y = 1; + A2.x = 2; A2.y = 1; + rv = lw_dist2d_seg_arc(&A1, &A2, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, sqrt(2.0)-1, 0.000001); + + /* Edge intersecting the unit semicircle */ + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = 0; A1.y = 0; + A2.x = 2; A2.y = 2; + rv = lw_dist2d_seg_arc(&A1, &A2, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 0, 0.000001); + + /* Line intersecting the unit semicircle, but edge not */ + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -1; A1.y = 1; + A2.x = -2; A2.y = 2; + rv = lw_dist2d_seg_arc(&A1, &A2, &B1, &B2, &B3, &dl); + //printf("distance %g\n", dl.distance); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, sqrt(2.0)-1, 0.000001); +} + +static void +test_lw_dist2d_arc_arc(void) +{ + /* lw_dist2d_arc_arc(const POINT2D *A1, const POINT2D *A2, const POINT2D *A3, + const POINT2D *B1, const POINT2D *B2, const POINT2D *B3, + DISTPTS *dl) */ + DISTPTS dl; + POINT2D A1, A2, A3, B1, B2, B3; + int rv; + + /* Ticket #4326 */ + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -1.0; A1.y = 4.0; + A2.x = 0.0; A2.y = 5.0; + A3.x = 1.0; A3.y = 4.0; + B1.x = 1.0; B1.y = 6.0; + B2.x = 6.0; B2.y = 1.0; + B3.x = 9.0; B3.y = 7.0; + rv = lw_dist2d_arc_arc(&A1, &A2, &A3, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 0.0475666, 0.000001); + + /* Unit semicircle at 0,0 */ + B1.x = -1; B1.y = 0; + B2.x = 0 ; B2.y = 1; + B3.x = 1 ; B3.y = 0; + + /* Arc above the unit semicircle */ + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -1; A1.y = 3; + A2.x = 0 ; A2.y = 2; + A3.x = 1 ; A3.y = 3; + rv = lw_dist2d_arc_arc(&A1, &A2, &A3, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 1, 0.000001); + + /* Arc grazes the unit semicircle */ + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -1; A1.y = 2; + A2.x = 0 ; A2.y = 1; + A3.x = 1 ; A3.y = 2; + rv = lw_dist2d_arc_arc(&A1, &A2, &A3, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 0, 0.000001); + + /* Circles intersect, but arcs do not */ + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -1; A1.y = 1; + A2.x = 0; A2.y = 2; + A3.x = 1; A3.y = 1; + rv = lw_dist2d_arc_arc(&A1, &A2, &A3, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, sqrt(2)-1, 0.000001); + + /* Circles and arcs intersect */ + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -1; A1.y = 1; + A2.x = 0; A2.y = 0; + A3.x = 1; A3.y = 1; + rv = lw_dist2d_arc_arc(&A1, &A2, &A3, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 0, 0.000001); + + /* Concentric: and fully parallel */ + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -2.0; A1.y = 0.0; + A2.x = 0.0; A2.y = 2.0; + A3.x = 2.0; A3.y = 0.0; + rv = lw_dist2d_arc_arc(&A1, &A2, &A3, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 1.0, 0.000001); + + /* Concentric: with A fully included in B's range */ + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -0.5 / sqrt(2.0); A1.y = 0.5 / sqrt(2.0); + A2.x = 0.0; A2.y = 0.5; + A3.x = 0.5 / sqrt(2.0); A3.y = 0.5 / sqrt(2.0); + rv = lw_dist2d_arc_arc(&A1, &A2, &A3, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 0.5, 0.000001); + + /* Concentric: with A partially included in B's range */ + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -0.5 / sqrt(2.0); A1.y = -0.5 / sqrt(2.0); + A2.x = -0.5; A2.y = 0.0; + A3.x = -0.5 / sqrt(2.0); A3.y = 0.5 / sqrt(2.0); + rv = lw_dist2d_arc_arc(&A1, &A2, &A3, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 0.5, 0.000001); + + /* Concentric: with A and B without parallel segments */ + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -0.5 / sqrt(2.0); A1.y = -0.5 / sqrt(2.0); + A2.x = 0.0; A2.y = -0.5; + A3.x = 0.5 / sqrt(2.0); A3.y = -0.5 / sqrt(2.0); + rv = lw_dist2d_arc_arc(&A1, &A2, &A3, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 0.736813, 0.000001); + + /* Concentric: Arcs are the same */ + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -1.0; A1.y = 0.0; + A2.x = 0.0; A2.y = 1.0; + A3.x = 1.0; A3.y = 0.0; + rv = lw_dist2d_arc_arc(&A1, &A2, &A3, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 0.0, 0.000001); + + /* Check different orientations */ + B1.x = -10.0; B1.y = 0.0; + B2.x = 0.0 ; B2.y = 10.0; + B3.x = 10.0 ; B3.y = 0.0; + + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -22.0; A1.y = 0.0; + A2.x = -17.0; A2.y = -5.0; + A3.x = -12.0; A3.y = 0.0; + rv = lw_dist2d_arc_arc(&A1, &A2, &A3, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 2.0, 0.000001); + + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -19.0; A1.y = 0.0; + A2.x = -14.0; A2.y = -5.0; + A3.x = - 9.0; A3.y = 0.0; + rv = lw_dist2d_arc_arc(&A1, &A2, &A3, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 1.0, 0.000001); + + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -9.0; A1.y = 0.0; + A2.x = -4.0; A2.y = -5.0; + A3.x = 1.0; A3.y = 0.0; + rv = lw_dist2d_arc_arc(&A1, &A2, &A3, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 1.0, 0.000001); + + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -1.0; A1.y = 0.0; + A2.x = 4.0; A2.y = -5.0; + A3.x = 9.0; A3.y = 0.0; + rv = lw_dist2d_arc_arc(&A1, &A2, &A3, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 1.0, 0.000001); + + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = 1.0; A1.y = 0.0; + A2.x = 6.0; A2.y = -5.0; + A3.x = 11.0; A3.y = 0.0; + rv = lw_dist2d_arc_arc(&A1, &A2, &A3, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 1.0, 0.000001); + + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = 11.0; A1.y = 0.0; + A2.x = 16.0; A2.y = -5.0; + A3.x = 21.0; A3.y = 0.0; + rv = lw_dist2d_arc_arc(&A1, &A2, &A3, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 1.0, 0.000001); + + + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -15.0; A1.y = -6.0; + A2.x = -10.0; A2.y = -1.0; + A3.x = - 5.0; A3.y = -6.0; + rv = lw_dist2d_arc_arc(&A1, &A2, &A3, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 1.0, 0.000001); + + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -5.0; A1.y = 0.0; + A2.x = 0.0; A2.y = 5.0; + A3.x = 5.0; A3.y = 0.0; + rv = lw_dist2d_arc_arc(&A1, &A2, &A3, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 5.0, 0.000001); + + lw_dist2d_distpts_init(&dl, DIST_MIN); + A1.x = -5.0; A1.y = 0.0; + A2.x = 0.0; A2.y = -5.0; + A3.x = 5.0; A3.y = 0.0; + rv = lw_dist2d_arc_arc(&A1, &A2, &A3, &B1, &B2, &B3, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 5.0, 0.000001); + +} + +static void +test_lw_arc_length(void) +{ +/* double lw_arc_length(const POINT2D *A1, const POINT2D *A2, const POINT2D *A3) */ + + POINT2D A1, A2, A3; + double d; + + /* Unit semicircle at 0,0 */ + A1.x = -1; A1.y = 0; + A2.x = 0 ; A2.y = 1; + A3.x = 1 ; A3.y = 0; + + /* Arc above the unit semicircle */ + d = lw_arc_length(&A1, &A2, &A3); + CU_ASSERT_DOUBLE_EQUAL(d, M_PI, 0.000001); + d = lw_arc_length(&A3, &A2, &A1); + CU_ASSERT_DOUBLE_EQUAL(d, M_PI, 0.000001); + + /* Unit semicircle at 0,0 */ + A1.x = 0; A1.y = 1; + A2.x = 1; A2.y = 0; + A3.x = 0; A3.y = -1; + + /* Arc to right of the unit semicircle */ + d = lw_arc_length(&A1, &A2, &A3); + CU_ASSERT_DOUBLE_EQUAL(d, M_PI, 0.000001); + d = lw_arc_length(&A3, &A2, &A1); + CU_ASSERT_DOUBLE_EQUAL(d, M_PI, 0.000001); + + /* Unit 3/4 circle at 0,0 */ + A1.x = -1; A1.y = 0; + A2.x = 1; A2.y = 0; + A3.x = 0; A3.y = -1; + + /* Arc to right of the unit semicircle */ + d = lw_arc_length(&A1, &A2, &A3); + CU_ASSERT_DOUBLE_EQUAL(d, 3*M_PI_2, 0.000001); + d = lw_arc_length(&A3, &A2, &A1); + CU_ASSERT_DOUBLE_EQUAL(d, 3*M_PI_2, 0.000001); +} + +static void +test_lw_dist2d_pt_ptarrayarc(void) +{ + /* lw_dist2d_pt_ptarrayarc(const POINT2D *p, const POINTARRAY *pa, DISTPTS *dl) */ + DISTPTS dl; + int rv; + LWLINE *lwline; + POINT2D P; + + /* Unit semi-circle above X axis */ + lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(-1 0, 0 1, 1 0)")); + + /* Point at origin */ + P.x = P.y = 0; + lw_dist2d_distpts_init(&dl, DIST_MIN); + rv = lw_dist2d_pt_ptarrayarc(&P, lwline->points, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 1, 0.000001); + + /* Point above arc on Y axis */ + P.y = 2; + lw_dist2d_distpts_init(&dl, DIST_MIN); + rv = lw_dist2d_pt_ptarrayarc(&P, lwline->points, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 1, 0.000001); + + /* Point 45 degrees off arc, 2 radii from center */ + P.y = P.x = 2 * cos(M_PI_4); + lw_dist2d_distpts_init(&dl, DIST_MIN); + rv = lw_dist2d_pt_ptarrayarc(&P, lwline->points, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 1, 0.000001); + + /* Four unit semi-circles surrounding the 2x2 box around origin */ + lwline_free(lwline); + lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(-1 -1, -2 0, -1 1, 0 2, 1 1, 2 0, 1 -1, 0 -2, -1 -1)")); + + /* Point at origin */ + P.x = P.y = 0; + lw_dist2d_distpts_init(&dl, DIST_MIN); + rv = lw_dist2d_pt_ptarrayarc(&P, lwline->points, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, sqrt(2.0), 0.000001); + + /* Point on box edge */ + P.x = -1; P.y = 0; + lw_dist2d_distpts_init(&dl, DIST_MIN); + rv = lw_dist2d_pt_ptarrayarc(&P, lwline->points, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 1, 0.000001); + + /* Point within a semicircle lobe */ + P.x = -1.5; P.y = 0; + lw_dist2d_distpts_init(&dl, DIST_MIN); + rv = lw_dist2d_pt_ptarrayarc(&P, lwline->points, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 0.5, 0.000001); + + /* Point outside a semicircle lobe */ + P.x = -2.5; P.y = 0; + lw_dist2d_distpts_init(&dl, DIST_MIN); + rv = lw_dist2d_pt_ptarrayarc(&P, lwline->points, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 0.5, 0.000001); + + /* Point outside a semicircle lobe */ + P.y = -2.5; P.x = 0; + lw_dist2d_distpts_init(&dl, DIST_MIN); + rv = lw_dist2d_pt_ptarrayarc(&P, lwline->points, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 0.5, 0.000001); + + /* Point outside a semicircle lobe */ + P.y = 2; P.x = 1; + lw_dist2d_distpts_init(&dl, DIST_MIN); + rv = lw_dist2d_pt_ptarrayarc(&P, lwline->points, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, sqrt(2.0)-1.0, 0.000001); + + /* Clean up */ + lwline_free(lwline); +} + +static void +test_lw_dist2d_ptarray_ptarrayarc(void) +{ + /* int lw_dist2d_ptarray_ptarrayarc(const POINTARRAY *pa, const POINTARRAY *pb, DISTPTS *dl) */ + DISTPTS dl; + int rv; + LWLINE *lwline1; + LWLINE *lwline2; + + /* Unit semi-circle above X axis */ + lwline1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(-1 0, 0 1, 1 0)")); + + /* Line above top of semi-circle */ + lwline2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(-2 2, -1 2, 1 2, 2 2)")); + lw_dist2d_distpts_init(&dl, DIST_MIN); + rv = lw_dist2d_ptarray_ptarrayarc(lwline2->points, lwline1->points, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 1, 0.000001); + + /* Reversed arguments, should fail */ + lw_dist2d_distpts_init(&dl, DIST_MIN); + cu_error_msg_reset(); + rv = lw_dist2d_ptarray_ptarrayarc(lwline1->points, lwline2->points, &dl); + //printf("%s\n", cu_error_msg); + CU_ASSERT_EQUAL( rv, LW_FAILURE ); + ASSERT_STRING_EQUAL( + cu_error_msg, + "lw_dist2d_ptarray_ptarrayarc called with non-arc input"); + + lwline_free(lwline2); + + /* Line along side of semi-circle */ + lwline2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(-2 -3, -2 -2, -2 2, -2 3)")); + lw_dist2d_distpts_init(&dl, DIST_MIN); + rv = lw_dist2d_ptarray_ptarrayarc(lwline2->points, lwline1->points, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 1, 0.000001); + + /* Four unit semi-circles surrounding the 2x2 box around origin */ + lwline_free(lwline1); + lwline_free(lwline2); + lwline1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(-1 -1, -2 0, -1 1, 0 2, 1 1, 2 0, 1 -1, 0 -2, -1 -1)")); + lwline2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(-2.5 -3, -2.5 -2, -2.5 2, -2.5 3)")); + rv = lw_dist2d_ptarray_ptarrayarc(lwline2->points, lwline1->points, &dl); + CU_ASSERT_EQUAL( rv, LW_SUCCESS ); + CU_ASSERT_DOUBLE_EQUAL(dl.distance, 0.5, 0.000001); + + lwline_free(lwline2); + lwline_free(lwline1); +} + +static void +test_lwgeom_tcpa(void) +{ + LWGEOM *g1, *g2; + double m, dist; + + /* Invalid input, lack of dimensions */ + + g1 = lwgeom_from_wkt("LINESTRING M(0 0 1, 0 0 1)", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("LINESTRING (0 0 2, 0 0 5)", LW_PARSER_CHECK_NONE); + m = lwgeom_tcpa(g1, g2, NULL); + lwgeom_free(g1); + lwgeom_free(g2); + ASSERT_DOUBLE_EQUAL(m, -1.0); + ASSERT_STRING_EQUAL( + cu_error_msg, + "Both input geometries must have a measure dimension"); + + /* Invalid input, not linestrings */ + + g1 = lwgeom_from_wkt("LINESTRING M(0 0 1, 0 0 1)", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("POINT M (0 0 2)", LW_PARSER_CHECK_NONE); + m = lwgeom_tcpa(g1, g2, NULL); + lwgeom_free(g1); + lwgeom_free(g2); + ASSERT_DOUBLE_EQUAL(m, -1.0); + ASSERT_STRING_EQUAL(cu_error_msg, + "Both input geometries must be linestrings"); + + /* Invalid input, too short linestring */ + + g1 = lwgeom_from_wkt("LINESTRING M(0 0 1, 10 0 10)", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("LINESTRING M(2 0 1)", LW_PARSER_CHECK_NONE); + dist = -77; + m = lwgeom_tcpa(g1, g2, &dist); + lwgeom_free(g1); + lwgeom_free(g2); + ASSERT_DOUBLE_EQUAL(dist, -77.0); /* not touched */ + ASSERT_DOUBLE_EQUAL(m, -1.0); + ASSERT_STRING_EQUAL( + cu_error_msg, + "Both input lines must have at least 2 points" /* should be accepted + ? */ + + ); + + /* Invalid input, empty linestring */ + + g1 = lwgeom_from_wkt("LINESTRING M(0 0 1, 10 0 10)", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("LINESTRING M EMPTY", LW_PARSER_CHECK_NONE); + m = lwgeom_tcpa(g1, g2, NULL); + lwgeom_free(g1); + lwgeom_free(g2); + ASSERT_DOUBLE_EQUAL(m, -1.0); + ASSERT_STRING_EQUAL(cu_error_msg, + "Both input lines must have at least 2 points" + + ); + + /* Timeranges do not overlap */ + + g1 = lwgeom_from_wkt("LINESTRING M(0 0 1, 0 0 1)", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("LINESTRING M(0 0 2, 0 0 5)", LW_PARSER_CHECK_NONE); + m = lwgeom_tcpa(g1, g2, NULL); + lwgeom_free(g1); + lwgeom_free(g2); + ASSERT_DOUBLE_EQUAL(m, -2.0); /* means timeranges do not overlap */ + + /* One of the tracks is still, the other passes to that point */ + + g1 = lwgeom_from_wkt("LINESTRING M(0 0 1, 0 0 1)", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("LINESTRING M(0 0 1, 0 0 5)", LW_PARSER_CHECK_NONE); + dist = -1; + m = lwgeom_tcpa(g1, g2, &dist); + ASSERT_DOUBLE_EQUAL(m, 1.0); + ASSERT_DOUBLE_EQUAL(dist, 0.0); + CU_ASSERT( lwgeom_cpa_within(g1, g2, 0.0) == LW_TRUE ); + lwgeom_free(g1); + lwgeom_free(g2); + + /* One of the tracks is still, the other passes at 10 meters from point */ + + g1 = lwgeom_from_wkt("LINESTRING M(0 0 1, 0 0 5)", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("LINESTRING M(-10 10 1, 10 10 5)", LW_PARSER_CHECK_NONE); + dist = -1; + m = lwgeom_tcpa(g1, g2, &dist); + ASSERT_DOUBLE_EQUAL(m, 3.0); + ASSERT_DOUBLE_EQUAL(dist, 10.0); + CU_ASSERT( lwgeom_cpa_within(g1, g2, 11.0) == LW_TRUE ); + CU_ASSERT( lwgeom_cpa_within(g1, g2, 10.0) == LW_TRUE ); + CU_ASSERT( lwgeom_cpa_within(g1, g2, 9.0) == LW_FALSE ); + lwgeom_free(g1); + lwgeom_free(g2); + + /* Equal tracks, 2d */ + + g1 = lwgeom_from_wkt("LINESTRING M(0 0 10, 10 0 20)", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("LINESTRING M(0 0 10, 10 0 20)", LW_PARSER_CHECK_NONE); + dist = -1; + m = lwgeom_tcpa(g1, g2, &dist); + lwgeom_free(g1); + lwgeom_free(g2); + ASSERT_DOUBLE_EQUAL(m, 10.0); + ASSERT_DOUBLE_EQUAL(dist, 0.0); + + /* Reversed tracks, 2d */ + + g1 = lwgeom_from_wkt("LINESTRING M(0 0 10, 10 0 20)", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("LINESTRING M(10 0 10, 0 0 20)", LW_PARSER_CHECK_NONE); + dist = -1; + m = lwgeom_tcpa(g1, g2, &dist); + lwgeom_free(g1); + lwgeom_free(g2); + ASSERT_DOUBLE_EQUAL(m, 15.0); + ASSERT_DOUBLE_EQUAL(dist, 0.0); + + /* Parallel tracks, same speed, 2d */ + + g1 = lwgeom_from_wkt("LINESTRING M(2 0 10, 12 0 20)", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("LINESTRING M(13 0 10, 23 0 20)", LW_PARSER_CHECK_NONE); + dist = -1; + m = lwgeom_tcpa(g1, g2, &dist); + lwgeom_free(g1); + lwgeom_free(g2); + ASSERT_DOUBLE_EQUAL(m, 10.0); + ASSERT_DOUBLE_EQUAL(dist, 11.0); + + /* Parallel tracks, different speed (g2 gets closer as time passes), 2d */ + + g1 = lwgeom_from_wkt("LINESTRING M(4 0 10, 10 0 20)", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("LINESTRING M(2 0 10, 9 0 20)", LW_PARSER_CHECK_NONE); + dist = -1; + m = lwgeom_tcpa(g1, g2, &dist); + lwgeom_free(g1); + lwgeom_free(g2); + ASSERT_DOUBLE_EQUAL(m, 20.0); + ASSERT_DOUBLE_EQUAL(dist, 1.0); + + /* Parallel tracks, different speed (g2 left behind as time passes), 2d */ + + g1 = lwgeom_from_wkt("LINESTRING M(4 0 10, 10 0 20)", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("LINESTRING M(2 0 10, 6 0 20)", LW_PARSER_CHECK_NONE); + dist = -1; + m = lwgeom_tcpa(g1, g2, &dist); + lwgeom_free(g1); + lwgeom_free(g2); + ASSERT_DOUBLE_EQUAL(m, 10.0); + ASSERT_DOUBLE_EQUAL(dist, 2.0); + + /* Tracks, colliding, 2d */ + + g1 = lwgeom_from_wkt("LINESTRING M(0 0 0, 10 0 10)", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("LINESTRING M(5 -8 0, 5 8 10)", LW_PARSER_CHECK_NONE); + dist = -1; + m = lwgeom_tcpa(g1, g2, &dist); + lwgeom_free(g1); + lwgeom_free(g2); + ASSERT_DOUBLE_EQUAL(m, 5.0); + ASSERT_DOUBLE_EQUAL(dist, 0.0); + + /* Tracks crossing, NOT colliding, 2d */ + + g1 = lwgeom_from_wkt("LINESTRING M(0 0 0, 10 0 10)", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("LINESTRING M(8 -5 0, 8 5 10)", LW_PARSER_CHECK_NONE); + dist = -1; + m = lwgeom_tcpa(g1, g2, &dist); + lwgeom_free(g1); + lwgeom_free(g2); + ASSERT_DOUBLE_EQUAL(m, 6.5); + ASSERT_DOUBLE_EQUAL(rint(dist*100), 212.0); + + /* Same origin, different direction, 2d */ + + g1 = lwgeom_from_wkt("LINESTRING M(0 0 1, 10 0 10)", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("LINESTRING M(0 0 1, -100 0 10)", LW_PARSER_CHECK_NONE); + dist = -1; + m = lwgeom_tcpa(g1, g2, &dist); + lwgeom_free(g1); + lwgeom_free(g2); + ASSERT_DOUBLE_EQUAL(m, 1.0); + ASSERT_DOUBLE_EQUAL(dist, 0.0); + + /* Same ending, different direction, 2d */ + + g1 = lwgeom_from_wkt("LINESTRING M(10 0 1, 0 0 10)", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("LINESTRING M(0 -100 1, 0 0 10)", LW_PARSER_CHECK_NONE); + dist = -1; + m = lwgeom_tcpa(g1, g2, &dist); + lwgeom_free(g1); + lwgeom_free(g2); + ASSERT_DOUBLE_EQUAL(m, 10.0); + ASSERT_DOUBLE_EQUAL(dist, 0.0); + + /* Converging tracks, 3d */ + + g1 = lwgeom_from_wkt("LINESTRING ZM(0 0 0 10, 10 0 0 20)", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("LINESTRING ZM(0 0 8 10, 10 0 5 20)", LW_PARSER_CHECK_NONE); + dist = -1; + m = lwgeom_tcpa(g1, g2, &dist); + lwgeom_free(g1); + lwgeom_free(g2); + ASSERT_DOUBLE_EQUAL(m, 20.0); + ASSERT_DOUBLE_EQUAL(dist, 5.0); + + /* G1 stops at t=1 until t=4 to let G2 pass by, then continues */ + /* G2 passes at 1 meter from G1 t=3 */ + + g1 = lwgeom_from_wkt("LINESTRING M(0 0 0, 0 1 1, 0 1 4, 0 10 13)", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("LINESTRING M(-10 2 0, 0 2 3, 12 2 13)", LW_PARSER_CHECK_NONE); + dist = -1; + m = lwgeom_tcpa(g1, g2, &dist); + lwgeom_free(g1); + lwgeom_free(g2); + ASSERT_DOUBLE_EQUAL(m, 3.0); + ASSERT_DOUBLE_EQUAL(dist, 1.0); + + /* Test for https://trac.osgeo.org/postgis/ticket/3136 */ + + g1 = lwgeom_from_wkt("LINESTRING M (0 0 1432291464,2 0 1432291536) ", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("LINESTRING M (0 0 1432291464,1 0 1432291466.25,2 0 1432291500)", LW_PARSER_CHECK_NONE); + dist = -1; + m = lwgeom_tcpa(g1, g2, &dist); + lwgeom_free(g1); + lwgeom_free(g2); + ASSERT_DOUBLE_EQUAL(m, 1432291464.0); + ASSERT_DOUBLE_EQUAL(dist, 0.0); + + /* Tracks share a single point in time */ + + g1 = lwgeom_from_wkt("LINESTRINGM(0 0 0, 1 0 2)", LW_PARSER_CHECK_NONE); + g2 = lwgeom_from_wkt("LINESTRINGM(0 0 2, 1 0 3)", LW_PARSER_CHECK_NONE); + dist = -1; + m = lwgeom_tcpa(g1, g2, &dist); + lwgeom_free(g1); + lwgeom_free(g2); + ASSERT_DOUBLE_EQUAL(m, 2.0); + ASSERT_DOUBLE_EQUAL(dist, 1.0); + +} + +static void +test_lwgeom_is_trajectory(void) +{ + LWGEOM *g; + int ret; + + g = lwgeom_from_wkt("POINT M(0 0 1)", LW_PARSER_CHECK_NONE); + ret = lwgeom_is_trajectory(g); + lwgeom_free(g); + ASSERT_INT_EQUAL(ret, LW_FALSE); /* not a linestring */ + + g = lwgeom_from_wkt("LINESTRING Z(0 0 1, 0 0 1)", LW_PARSER_CHECK_NONE); + ret = lwgeom_is_trajectory(g); + lwgeom_free(g); + ASSERT_INT_EQUAL(ret, LW_FALSE); /* no measure */ + + g = lwgeom_from_wkt("LINESTRING M(0 0 1, 0 0 1)", LW_PARSER_CHECK_NONE); + ret = lwgeom_is_trajectory(g); + lwgeom_free(g); + ASSERT_INT_EQUAL(ret, LW_FALSE); /* same measure in two points */ + + g = lwgeom_from_wkt("LINESTRING M(0 0 1, 0 0 0)", LW_PARSER_CHECK_NONE); + ret = lwgeom_is_trajectory(g); + lwgeom_free(g); + ASSERT_INT_EQUAL(ret, LW_FALSE); /* backward measure */ + + g = lwgeom_from_wkt("LINESTRING M(0 0 1, 1 0 3, 2 2 2)", LW_PARSER_CHECK_NONE); + ret = lwgeom_is_trajectory(g); + lwgeom_free(g); + ASSERT_INT_EQUAL(ret, LW_FALSE); /* backward measure */ + + g = lwgeom_from_wkt("LINESTRING M(0 0 1, 0 0 2)", LW_PARSER_CHECK_NONE); + ret = lwgeom_is_trajectory(g); + lwgeom_free(g); + ASSERT_INT_EQUAL(ret, LW_TRUE); /* ok (still) */ + + g = lwgeom_from_wkt("LINESTRING M EMPTY", LW_PARSER_CHECK_NONE); + ret = lwgeom_is_trajectory(g); + lwgeom_free(g); + ASSERT_INT_EQUAL(ret, LW_TRUE); /* ok (empty) */ + + g = lwgeom_from_wkt("LINESTRING M (0 0 1)", LW_PARSER_CHECK_NONE); + ret = lwgeom_is_trajectory(g); + lwgeom_free(g); + ASSERT_INT_EQUAL(ret, LW_TRUE); /* ok (corner case) */ +} + +/* +** Used by test harness to register the tests in this file. +*/ +void measures_suite_setup(void); +void measures_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("measures", NULL, NULL); + PG_ADD_TEST(suite, test_mindistance2d_tolerance); + PG_ADD_TEST(suite, test_mindistance3d_tolerance); + PG_ADD_TEST(suite, test_rect_tree_contains_point); + PG_ADD_TEST(suite, test_rect_tree_intersects_tree); + PG_ADD_TEST(suite, test_lwgeom_segmentize2d); + PG_ADD_TEST(suite, test_lwgeom_locate_along); + PG_ADD_TEST(suite, test_lw_dist2d_pt_arc); + PG_ADD_TEST(suite, test_lw_dist2d_seg_arc); + PG_ADD_TEST(suite, test_lw_dist2d_arc_arc); + PG_ADD_TEST(suite, test_lw_arc_length); + PG_ADD_TEST(suite, test_lw_dist2d_pt_ptarrayarc); + PG_ADD_TEST(suite, test_lw_dist2d_ptarray_ptarrayarc); + PG_ADD_TEST(suite, test_lwgeom_tcpa); + PG_ADD_TEST(suite, test_lwgeom_is_trajectory); + PG_ADD_TEST(suite, test_rect_tree_distance_tree); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_minimum_bounding_circle.c b/mgist-postgis/liblwgeom/cunit/cu_minimum_bounding_circle.c new file mode 100644 index 0000000..663572c --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_minimum_bounding_circle.c @@ -0,0 +1,93 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2015 Daniel Baston + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include "CUnit/Basic.h" +#include "cu_tester.h" +#include "../liblwgeom_internal.h" + +static void mbc_test(LWGEOM* g) +{ + LWBOUNDINGCIRCLE* result = lwgeom_calculate_mbc(g); + CU_ASSERT_TRUE(result != NULL); + + LWPOINTITERATOR* it = lwpointiterator_create(g); + + POINT2D p; + POINT4D p4; + char *msg1 = "mbc_test failed (got %.12f should be less than radius %.12f) \n"; + while (lwpointiterator_next(it, &p4)) + { + p.x = p4.x; + p.y = p4.y; + + /* We need to store the distance in a variable before the assert so that + * it is rounded from its 80-bit representation (on x86) down to 64 bits. + * */ + volatile double d = distance2d_pt_pt(result->center, &p); + if ( (d - result->radius) > 0.0000001 ) + { + printf(msg1, d, result->radius); + CU_FAIL(); + } + else + { + CU_PASS(); + } + } + + lwboundingcircle_destroy(result); + lwpointiterator_destroy(it); +} + +static void basic_test(void) +{ + uint32_t i; + + char* inputs[] = + { + "POLYGON((26426 65078,26531 65242,26075 65136,26096 65427,26426 65078))", + "POINT (17 253)", + "TRIANGLE ((0 0, 10 0, 10 10, 0 0))", + "LINESTRING (17 253, -44 28, 33 11, 26 44)", + "MULTIPOINT ((0 0), (0 0), (0 0), (0 0))", + "POLYGON ((0 0, 1 0, 1 1, 0 1, 0 0))", + "LINESTRING (-48546889 37039202, -37039202 -48546889)" + }; + + for (i = 0; i < sizeof(inputs)/sizeof(LWGEOM*); i++) + { + LWGEOM* input = lwgeom_from_wkt(inputs[i], LW_PARSER_CHECK_NONE); + mbc_test(input); + lwgeom_free(input); + } + +} + +static void test_empty(void) +{ + LWGEOM* input = lwgeom_from_wkt("POINT EMPTY", LW_PARSER_CHECK_NONE); + + LWBOUNDINGCIRCLE* result = lwgeom_calculate_mbc(input); + CU_ASSERT_TRUE(result == NULL); + + lwgeom_free(input); +} + +/* + ** Used by test harness to register the tests in this file. + */ +void minimum_bounding_circle_suite_setup(void); +void minimum_bounding_circle_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("minimum_bounding_circle", NULL, NULL); + PG_ADD_TEST(suite, basic_test); + PG_ADD_TEST(suite, test_empty); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_misc.c b/mgist-postgis/liblwgeom/cunit/cu_misc.c new file mode 100644 index 0000000..ccd0e4b --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_misc.c @@ -0,0 +1,359 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2008 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "optionlist.h" +#include "stringlist.h" +#include "cu_tester.h" + + +static void test_misc_simplify(void) +{ + LWGEOM *geom; + LWGEOM *geom2d; + char *wkt_out; + + geom = lwgeom_from_wkt("LINESTRING(0 0,0 10,0 51,50 20,30 20,7 32)", LW_PARSER_CHECK_NONE); + geom2d = lwgeom_simplify(geom, 2, LW_FALSE); + wkt_out = lwgeom_to_ewkt(geom2d); + CU_ASSERT_STRING_EQUAL("LINESTRING(0 0,0 51,50 20,30 20,7 32)",wkt_out); + lwgeom_free(geom); + lwgeom_free(geom2d); + lwfree(wkt_out); + + geom = lwgeom_from_wkt("MULTILINESTRING((0 0,0 10,0 51,50 20,30 20,7 32))", LW_PARSER_CHECK_NONE); + geom2d = lwgeom_simplify(geom, 2, LW_FALSE); + wkt_out = lwgeom_to_ewkt(geom2d); + CU_ASSERT_STRING_EQUAL("MULTILINESTRING((0 0,0 51,50 20,30 20,7 32))",wkt_out); + lwgeom_free(geom); + lwgeom_free(geom2d); + lwfree(wkt_out); + + geom = lwgeom_from_wkt("POLYGON((0 0,1 1,1 3,0 4,-2 3,-1 1,0 0))", LW_PARSER_CHECK_NONE); + geom2d = lwgeom_simplify(geom, 1, LW_FALSE); + wkt_out = lwgeom_to_ewkt(geom2d); + CU_ASSERT_STRING_EQUAL("POLYGON((0 0,0 4,-2 3,0 0))", wkt_out); + lwgeom_free(geom); + lwgeom_free(geom2d); + lwfree(wkt_out); +} + +static void test_misc_startpoint(void) +{ + LWGEOM *geom; + POINT4D p = {0}; + + geom = lwgeom_from_wkt("POINT(1 2)", LW_PARSER_CHECK_NONE); + CU_ASSERT(lwgeom_startpoint(geom, &p) == LW_SUCCESS); + CU_ASSERT_EQUAL(p.x, 1); + CU_ASSERT_EQUAL(p.y, 2); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("LINESTRING(10 20, 30 40)", LW_PARSER_CHECK_NONE); + CU_ASSERT(lwgeom_startpoint(geom, &p) == LW_SUCCESS); + CU_ASSERT_EQUAL(p.x, 10); + CU_ASSERT_EQUAL(p.y, 20); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("POLYGON((1 2, 3 4, 5 6, 1 2))", LW_PARSER_CHECK_NONE); + CU_ASSERT(lwgeom_startpoint(geom, &p) == LW_SUCCESS); + CU_ASSERT_EQUAL(p.x, 1); + CU_ASSERT_EQUAL(p.y, 2); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("GEOMETRYCOLLECTION(LINESTRING(100 200, 300 400), POINT(10 20))", LW_PARSER_CHECK_NONE); + CU_ASSERT(lwgeom_startpoint(geom, &p) == LW_SUCCESS); + CU_ASSERT_EQUAL(p.x, 100); + CU_ASSERT_EQUAL(p.y, 200); + lwgeom_free(geom); +} + +static void test_misc_count_vertices(void) +{ + LWGEOM *geom; + int count; + + geom = lwgeom_from_wkt("GEOMETRYCOLLECTION(POINT(0 0),LINESTRING(0 0,1 1),POLYGON((0 0,0 1,1 0,0 0)),CIRCULARSTRING(0 0,0 1,1 1),CURVEPOLYGON(CIRCULARSTRING(0 0,0 1,1 1)))", LW_PARSER_CHECK_NONE); + count = lwgeom_count_vertices(geom); + CU_ASSERT_EQUAL(count,13); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("GEOMETRYCOLLECTION(CIRCULARSTRING(0 0,0 1,1 1),POINT(0 0),CURVEPOLYGON(CIRCULARSTRING(0 0,0 1,1 1,1 0,0 0)))", LW_PARSER_CHECK_NONE); + count = lwgeom_count_vertices(geom); + CU_ASSERT_EQUAL(count,9); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("CURVEPOLYGON((0 0,1 0,0 1,0 0),CIRCULARSTRING(0 0,1 0,1 1,1 0,0 0))", LW_PARSER_CHECK_NONE); + count = lwgeom_count_vertices(geom); + CU_ASSERT_EQUAL(count,9); + lwgeom_free(geom); + + + geom = lwgeom_from_wkt("POLYGON((0 0,1 0,0 1,0 0))", LW_PARSER_CHECK_NONE); + count = lwgeom_count_vertices(geom); + CU_ASSERT_EQUAL(count,4); + lwgeom_free(geom); + + geom = lwgeom_from_wkt("CURVEPOLYGON((0 0,1 0,0 1,0 0),CIRCULARSTRING(0 0,1 0,1 1,1 0,0 0))", LW_PARSER_CHECK_NONE); + count = lwgeom_count_vertices(geom); + CU_ASSERT_EQUAL(count,9); + lwgeom_free(geom); +} + +static void test_misc_area(void) +{ + LWGEOM *geom; + double area; + + geom = lwgeom_from_wkt("LINESTRING EMPTY", LW_PARSER_CHECK_ALL); + area = lwgeom_area(geom); + CU_ASSERT_DOUBLE_EQUAL(area, 0.0, 0.0001); + lwgeom_free(geom); +} + +static void test_misc_wkb(void) +{ + static char *wkb = "010A0000000200000001080000000700000000000000000000C00000000000000000000000000000F0BF000000000000F0BF00000000000000000000000000000000000000000000F03F000000000000F0BF000000000000004000000000000000000000000000000000000000000000004000000000000000C00000000000000000010200000005000000000000000000F0BF00000000000000000000000000000000000000000000E03F000000000000F03F00000000000000000000000000000000000000000000F03F000000000000F0BF0000000000000000"; + LWGEOM *geom = lwgeom_from_hexwkb(wkb, LW_PARSER_CHECK_ALL); + char *str = lwgeom_to_wkt(geom, WKB_ISO, 8, 0); + CU_ASSERT_STRING_EQUAL(str, "CURVEPOLYGON(CIRCULARSTRING(-2 0,-1 -1,0 0,1 -1,2 0,0 2,-2 0),(-1 0,0 0.5,1 0,0 1,-1 0))"); + lwfree(str); + lwgeom_free(geom); + +} + + +static void test_grid(void) +{ + gridspec grid; + static char *wkt = "MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0)))"; + LWGEOM *geom = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_ALL); + LWGEOM *geomgrid; + char *str; + + grid.ipx = grid.ipy = 0; + grid.xsize = grid.ysize = 20; + + geomgrid = lwgeom_grid(geom, &grid); + str = lwgeom_to_ewkt(geomgrid); + CU_ASSERT_STRING_EQUAL(str, "MULTIPOLYGON EMPTY"); + lwfree(str); + lwgeom_free(geom); + lwgeom_free(geomgrid); +} + +static void do_grid_test(const char *wkt_in, const char *wkt_out, double size) +{ + char *wkt_result, *wkt_norm; + gridspec grid; + LWGEOM *g = lwgeom_from_wkt(wkt_in, LW_PARSER_CHECK_ALL); + LWGEOM *go = lwgeom_from_wkt(wkt_out, LW_PARSER_CHECK_ALL); + wkt_norm = lwgeom_to_ewkt(go); + memset(&grid, 0, sizeof(gridspec)); + grid.xsize = grid.ysize = grid.zsize = grid.msize = size; + lwgeom_grid_in_place(g, &grid); + wkt_result = lwgeom_to_ewkt(g); + // printf("%s ==%ld==> %s == %s\n", wkt_in, lround(size), wkt_result, wkt_out); + CU_ASSERT_STRING_EQUAL(wkt_result, wkt_norm); + lwfree(wkt_result); + lwfree(wkt_norm); + lwgeom_free(g); + lwgeom_free(go); +} + +static void test_grid_in_place(void) +{ + do_grid_test( + "POINT ZM (5.1423999999 5.1423999999 5.1423999999 5.1423999999)", + "POINT(5.1424 5.1424 5.1424 5.1424)", + 0.0001 + ); + do_grid_test( + "MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0)))", + "MULTIPOLYGON EMPTY", + 20 + ); + do_grid_test( + "MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0)))", + "MULTIPOLYGON(((0 0,10 0,10 10, 0 10,0 0)))", + 1 + ); + do_grid_test( + "LINESTRING(0 0,1 1, 2 2, 3 3, 4 4, 5 5)", + "LINESTRING(0 0,2 2,4 4)", + 2 + ); + do_grid_test( + "MULTIPOINT(0 0,1 1, 2 2, 3 3, 4 4, 5 5)", + /* This preserves current behaviour, but is probably not right */ + "MULTIPOINT(0 0,0 0,2 2,4 4,4 4,4 4)", + 2 + ); + do_grid_test( + "MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0),(4 4, 4 5, 5 5, 5 4, 4 4)))", + "MULTIPOLYGON(((0 0,10 0,10 10, 0 10,0 0)))", + 2 + ); + do_grid_test( + "MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0),(4 4, 4 5, 5 5, 5 4, 4 4)))", + "MULTIPOLYGON EMPTY", + 20 + ); + do_grid_test( + "POINT Z (5 5 5)", + "POINT(0 0 0)", + 20 + ); +} + +static void test_clone(void) +{ + static char *wkt = "GEOMETRYCOLLECTION(MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0))),POINT(1 1),LINESTRING(2 3,4 5))"; + LWGEOM *geom1 = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_ALL); + LWGEOM *geom2; + + /* Free in "backwards" order */ + geom2 = lwgeom_clone(geom1); + lwgeom_free(geom1); + lwgeom_free(geom2); + + /* Free in "forewards" order */ + geom1 = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_ALL); + geom2 = lwgeom_clone(geom1); + lwgeom_free(geom2); + lwgeom_free(geom1); +} + +static void test_lwmpoint_from_lwgeom(void) +{ + /* This cast is so ugly, we only want to do it once. And not even that. */ + LWGEOM* (*to_points)(LWGEOM*) = (LWGEOM* (*)(LWGEOM*)) &lwmpoint_from_lwgeom; + + do_fn_test(to_points, "MULTIPOLYGON (EMPTY)", "MULTIPOINT EMPTY"); + do_fn_test(to_points, "POINT (30 10)", "MULTIPOINT ((30 10))"); + do_fn_test(to_points, "LINESTRING Z (30 10 4,10 30 5,40 40 6)", "MULTIPOINT Z (30 10 4,10 30 5, 40 40 6)"); + do_fn_test(to_points, "POLYGON((35 10,45 45,15 40,10 20,35 10),(20 30,35 35,30 20,20 30))", "MULTIPOINT(35 10,45 45,15 40,10 20,35 10,20 30,35 35,30 20,20 30)"); + do_fn_test(to_points, "MULTIPOINT M (10 40 1,40 30 2,20 20 3,30 10 4)", "MULTIPOINT M (10 40 1,40 30 2,20 20 3,30 10 4)"); + do_fn_test(to_points, "COMPOUNDCURVE(CIRCULARSTRING(0 0,2 0, 2 1, 2 3, 4 3),(4 3, 4 5, 1 4, 0 0))", "MULTIPOINT(0 0, 2 0, 2 1, 2 3, 4 3, 4 3, 4 5, 1 4, 0 0)"); + do_fn_test(to_points, "TIN(((80 130,50 160,80 70,80 130)),((50 160,10 190,10 70,50 160)))", "MULTIPOINT (80 130, 50 160, 80 70, 80 130, 50 160, 10 190, 10 70, 50 160)"); +} + +static void test_gbox_serialized_size(void) +{ + lwflags_t flags = lwflags(0, 0, 0); + CU_ASSERT_EQUAL(gbox_serialized_size(flags),16); + FLAGS_SET_BBOX(flags, 1); + CU_ASSERT_EQUAL(gbox_serialized_size(flags),16); + FLAGS_SET_Z(flags, 1); + CU_ASSERT_EQUAL(gbox_serialized_size(flags),24); + FLAGS_SET_M(flags, 1); + CU_ASSERT_EQUAL(gbox_serialized_size(flags),32); + FLAGS_SET_GEODETIC(flags, 1); + CU_ASSERT_EQUAL(gbox_serialized_size(flags),24); +} + +static void test_optionlist(void) +{ + size_t sz; + const char* value; + // zero out all the pointers so our list ends up null-terminated + char *olist[OPTION_LIST_SIZE]; + char input[128]; + memset(olist, 0, sizeof(olist)); + // input string needs to be writeable because we are inserting nulls + strcpy(input, "key1=value1 key2=value2 "); + option_list_parse(input, olist); + + value = option_list_search(olist, "key1"); + // printf("value: %s\n", value); + CU_ASSERT_STRING_EQUAL("value1", value); + value = option_list_search(olist, "key2"); + CU_ASSERT_STRING_EQUAL("value2", value); + value = option_list_search(olist, "key3"); + CU_ASSERT_EQUAL(NULL, value); + + sz = option_list_length(olist); + CU_ASSERT_EQUAL(4, sz); + + memset(olist, 0, sizeof(olist)); + strcpy(input, " "); + option_list_parse(input, olist); + value = option_list_search(olist, "key1"); + CU_ASSERT_EQUAL(NULL, value); + + memset(olist, 0, sizeof(olist)); + strcpy(input, " key3= "); + option_list_parse(input, olist); + sz = option_list_length(olist); + CU_ASSERT_EQUAL(2, sz); + + strcpy(input, " key1=value1 key2='value2 value3' "); + memset(olist, 0, sizeof(olist)); + option_list_gdal_parse(input, olist); + sz = option_list_length(olist); + CU_ASSERT_EQUAL(2, sz); + CU_ASSERT_STRING_EQUAL("key1=value1", olist[0]); + CU_ASSERT_STRING_EQUAL("key2='value2 value3'", olist[1]); +} + + +static void test_stringlist(void) +{ + stringlist_t s; + stringlist_init(&s); + + CU_ASSERT_EQUAL(stringlist_length(&s), 0); + stringlist_add_string_nosort(&s, "first string"); + stringlist_add_string_nosort(&s, "second string"); + stringlist_add_string_nosort(&s, "third string"); + CU_ASSERT_EQUAL(stringlist_length(&s), 3); + CU_ASSERT_STRING_EQUAL(stringlist_get(&s, 0), "first string"); + stringlist_add_string_nosort(&s, "an initial string"); + stringlist_sort(&s); + CU_ASSERT_STRING_EQUAL(stringlist_get(&s, 0), "an initial string"); + CU_ASSERT_STRING_EQUAL(stringlist_find(&s, "third string"), "third string"); + CU_ASSERT_EQUAL(stringlist_find(&s, "nothing_matches"), NULL); + stringlist_add_string_nosort(&s, "fourth string"); + stringlist_add_string_nosort(&s, "fifth string"); + stringlist_add_string_nosort(&s, "sixth string"); + stringlist_add_string_nosort(&s, "seventh string"); + stringlist_add_string_nosort(&s, "eighth string"); + stringlist_sort(&s); + CU_ASSERT_STRING_EQUAL(stringlist_find(&s, "fifth string"), "fifth string"); + stringlist_release(&s); +} + + +/* +** Used by the test harness to register the tests in this file. +*/ +void misc_suite_setup(void); +void misc_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("miscellaneous", NULL, NULL); + PG_ADD_TEST(suite, test_misc_simplify); + PG_ADD_TEST(suite, test_misc_startpoint); + PG_ADD_TEST(suite, test_misc_count_vertices); + PG_ADD_TEST(suite, test_misc_area); + PG_ADD_TEST(suite, test_misc_wkb); + PG_ADD_TEST(suite, test_grid); + PG_ADD_TEST(suite, test_grid_in_place); + PG_ADD_TEST(suite, test_clone); + PG_ADD_TEST(suite, test_lwmpoint_from_lwgeom); + PG_ADD_TEST(suite, test_gbox_serialized_size); + PG_ADD_TEST(suite, test_optionlist); + PG_ADD_TEST(suite, test_stringlist); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_node.c b/mgist-postgis/liblwgeom/cunit/cu_node.c new file mode 100644 index 0000000..b97f1f3 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_node.c @@ -0,0 +1,77 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright (C) 2011 Sandro Santilli + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include "CUnit/Basic.h" +#include "cu_tester.h" + +#include "liblwgeom.h" +#include "liblwgeom_internal.h" +#include "../lwgeom_geos.h" + +static void test_lwgeom_node(void) +{ + LWGEOM *in, *out; + const char *wkt; + char *tmp; + + /* Because i don't trust that much prior tests... ;) */ + cu_error_msg_reset(); + + wkt = "LINESTRING(0 0,5 5, 10 0)"; + in = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_NONE); + out = lwgeom_node(in); + /* printf("%s\n", lwgeom_to_ewkt(out)); */ + CU_ASSERT(lwgeom_same(in, out)); + lwgeom_free(out); lwgeom_free(in); + + wkt = "MULTILINESTRING((0 0,0 5),(10 0, -10 5))"; + in = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_NONE); + out = lwgeom_node(in); + tmp = lwgeom_to_ewkt(out); + CU_ASSERT_STRING_EQUAL("MULTILINESTRING((0 2.5,-10 5),(0 0,0 2.5),(0 2.5,0 5),(10 0,0 2.5))", tmp) + lwfree(tmp); lwgeom_free(out); lwgeom_free(in); + + wkt = "MULTILINESTRING((0 0,5 5,10 0, 11 0, 20 0),(10 0, 12 0, 22 0))"; + in = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_NONE); + out = lwgeom_node(in); + tmp = lwgeom_to_ewkt(out); + /* printf("%s\n", tmp); */ + CU_ASSERT_STRING_EQUAL("MULTILINESTRING((0 0,5 5,10 0),(10 0,11 0,12 0,20 0),(20 0,22 0))", tmp); + lwfree(tmp); lwgeom_free(out); lwgeom_free(in); + + wkt = "MULTILINESTRING((0 0,5 5,10 0, 11 0, 20 0),(22 0, 12 0, 10 0),(0 5, 5 0))"; + in = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_NONE); + out = lwgeom_node(in); + tmp = lwgeom_to_ewkt(out); + /* printf("%s\n", tmp); */ + CU_ASSERT_STRING_EQUAL( +"MULTILINESTRING((0 0,2.5 2.5),(0 5,2.5 2.5),(2.5 2.5,5 5,10 0),(10 0,11 0,12 0,20 0),(20 0,22 0),(2.5 2.5,5 0))", + tmp); + lwfree(tmp); lwgeom_free(out); lwgeom_free(in); +} + +static int +clean_geos_node_suite(void) +{ + finishGEOS(); + return 0; +} + +/* +** Used by test harness to register the tests in this file. +*/ +void node_suite_setup(void); +void node_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("noding", NULL, clean_geos_node_suite); + PG_ADD_TEST(suite, test_lwgeom_node); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_out_encoded_polyline.c b/mgist-postgis/liblwgeom/cunit/cu_out_encoded_polyline.c new file mode 100644 index 0000000..608b0ae --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_out_encoded_polyline.c @@ -0,0 +1,98 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright 2014 Kashif Rasul and + * Shoaib Burq + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +static void +do_encoded_polyline_test(char* in, int precision, char* out) +{ + LWGEOM *g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwvarlena_t *v = lwgeom_to_encoded_polyline(g, precision); + + ASSERT_VARLENA_EQUAL(v, out); + + lwgeom_free(g); + lwfree(v); +} + +static void +out_encoded_polyline_test_geoms(void) +{ + /* Magic Linestring */ + do_encoded_polyline_test( + "SRID=4326;LINESTRING(33.6729 38.7071,33.6692 38.701," + "33.6673 38.6972,33.6626 38.6871)", + 5, + "k~fkFsvolEbe@bVvVzJb~@j\\"); + + /* Linestring */ + do_encoded_polyline_test( + "LINESTRING(-120.2 38.5,-120.95 40.7,-126.453 43.252)", + 5, + "_p~iF~ps|U_ulLnnqC_mqNvxq`@"); + do_encoded_polyline_test("LINESTRING EMPTY", 5, ""); + + /* MultiPoint */ + do_encoded_polyline_test( + "MULTIPOINT(-120.2 38.5,-120.95 40.7)", 5, "_p~iF~ps|U_ulLnnqC"); + do_encoded_polyline_test("MULTIPOINT EMPTY", 5, ""); +} + +static void +out_encoded_polyline_test_srid(void) +{ + + /* SRID - with PointArray */ + do_encoded_polyline_test( + "SRID=4326;LINESTRING(0 1,2 3)", 5, "_ibE?_seK_seK"); + + /* wrong SRID */ + do_encoded_polyline_test( + "SRID=4327;LINESTRING(0 1,2 3)", 5, "_ibE?_seK_seK"); +} + +static void +out_encoded_polyline_test_precision(void) +{ + + /* Linestring */ + do_encoded_polyline_test( + "LINESTRING(-0.250691 49.283048, -0.250633 49.283376," + "-0.250502 49.283972, -0.251245 49.284028, -0.251938 " + "49.284232, -0.251938 49.2842)", + 6, + "o}~~|AdshNoSsBgd@eGoBlm@wKhj@~@?"); + + /* MultiPoint */ + do_encoded_polyline_test( + "MULTIPOINT(-120.2 38.5,-120.95 40.7)", 3, "gejAnwiFohCzm@"); +} + +/* +** Used by test harness to register the tests in this file. +*/ +void out_encoded_polyline_suite_setup(void); +void +out_encoded_polyline_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("encoded_polyline_output", NULL, NULL); + PG_ADD_TEST(suite, out_encoded_polyline_test_geoms); + PG_ADD_TEST(suite, out_encoded_polyline_test_srid); + PG_ADD_TEST(suite, out_encoded_polyline_test_precision); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_out_geojson.c b/mgist-postgis/liblwgeom/cunit/cu_out_geojson.c new file mode 100644 index 0000000..bbfae2a --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_out_geojson.c @@ -0,0 +1,287 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2010 Olivier Courtin + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +static void do_geojson_test(char * in, char * out, char * srs, int precision, int has_bbox) +{ + LWGEOM *g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwvarlena_t *v = lwgeom_to_geojson(g, srs, precision, has_bbox); + + ASSERT_VARLENA_EQUAL(v, out); + + lwgeom_free(g); + lwfree(v); +} + + +static void do_geojson_unsupported(char * in, char * out) +{ + LWGEOM *g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwvarlena_t *v = lwgeom_to_geojson(g, NULL, 0, 0); + + ASSERT_STRING_EQUAL(out, cu_error_msg); + cu_error_msg_reset(); + + lwfree(v); + lwgeom_free(g); +} + + +static void out_geojson_test_precision(void) +{ + /* 0 precision, i.e a round */ + do_geojson_test( + "POINT(1.1111111111111 1.1111111111111)", + "{\"type\":\"Point\",\"coordinates\":[1,1]}", + NULL, 0, 0); + + /* 3 digits precision */ + do_geojson_test( + "POINT(1.1111111111111 1.1111111111111)", + "{\"type\":\"Point\",\"coordinates\":[1.111,1.111]}", + NULL, 3, 0); + + /* 9 digits precision */ + do_geojson_test( + "POINT(1.2345678901234 1.2345678901234)", + "{\"type\":\"Point\",\"coordinates\":[1.23456789,1.23456789]}", + NULL, 9, 0); + + /* huge data */ + do_geojson_test( + "POINT(1E300 -1E300)", + "{\"type\":\"Point\",\"coordinates\":[1e+300,-1e+300]}", + NULL, 0, 0); + + /* huge precision, see http://trac.osgeo.org/postgis/ticket/2052 */ + do_geojson_test( + "POINT(1 2)", + "{\"type\":\"Point\",\"coordinates\":[1,2]}", + NULL, 100, 0); + + /* double precision, see http://trac.osgeo.org/postgis/ticket/2051 */ + do_geojson_test( + "POINT(59.99 -59.99)", + "{\"type\":\"Point\",\"coordinates\":[59.99,-59.99]}", + NULL, 15, 0); + + /* small numbers */ + do_geojson_test("POINT(1E-300 -2E-200)", "{\"type\":\"Point\",\"coordinates\":[1e-300,-2e-200]}", NULL, 300, 0); +} + + +static void out_geojson_test_dims(void) +{ + /* 3D */ + do_geojson_test( + "POINT(0 1 2)", + "{\"type\":\"Point\",\"coordinates\":[0,1,2]}", + NULL, 0, 0); + + /* 3DM */ + do_geojson_test( + "POINTM(0 1 2)", + "{\"type\":\"Point\",\"coordinates\":[0,1]}", + NULL, 0, 0); + + /* 4D */ + do_geojson_test( + "POINT(0 1 2 3)", + "{\"type\":\"Point\",\"coordinates\":[0,1,2]}", + NULL, 0, 0); +} + + +static void out_geojson_test_srid(void) +{ + /* Linestring */ + do_geojson_test( + "LINESTRING(0 1,2 3,4 5)", + "{\"type\":\"LineString\",\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}},\"coordinates\":[[0,1],[2,3],[4,5]]}", + "EPSG:4326", 0, 0); + + /* Polygon */ + do_geojson_test( + "POLYGON((0 1,2 3,4 5,0 1))", + "{\"type\":\"Polygon\",\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}},\"coordinates\":[[[0,1],[2,3],[4,5],[0,1]]]}", + "EPSG:4326", 0, 0); + + /* Polygon - with internal ring */ + do_geojson_test( + "POLYGON((0 1,2 3,4 5,0 1),(6 7,8 9,10 11,6 7))", + "{\"type\":\"Polygon\",\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}},\"coordinates\":[[[0,1],[2,3],[4,5],[0,1]],[[6,7],[8,9],[10,11],[6,7]]]}", + "EPSG:4326", 0, 0); + + /* Multiline */ + do_geojson_test( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "{\"type\":\"MultiLineString\",\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}},\"coordinates\":[[[0,1],[2,3],[4,5]],[[6,7],[8,9],[10,11]]]}", + "EPSG:4326", 0, 0); + + /* MultiPolygon */ + do_geojson_test( + "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "{\"type\":\"MultiPolygon\",\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}},\"coordinates\":[[[[0,1],[2,3],[4,5],[0,1]]],[[[6,7],[8,9],[10,11],[6,7]]]]}", + "EPSG:4326", 0, 0); + + /* GeometryCollection */ + do_geojson_test( + "GEOMETRYCOLLECTION(POINT(0 1),LINESTRING(2 3,4 5))", + "{\"type\":\"GeometryCollection\",\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}},\"geometries\":[{\"type\":\"Point\",\"coordinates\":[0,1]},{\"type\":\"LineString\",\"coordinates\":[[2,3],[4,5]]}]}", + "EPSG:4326", 0, 0); + + /* Empty GeometryCollection */ + do_geojson_test( + "GEOMETRYCOLLECTION EMPTY", + "{\"type\":\"GeometryCollection\",\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}},\"geometries\":[]}", + "EPSG:4326", 0, 0); +} + +static void out_geojson_test_bbox(void) +{ + /* Linestring */ + do_geojson_test( + "LINESTRING(0 1,2 3,4 5)", + "{\"type\":\"LineString\",\"bbox\":[0,1,4,5],\"coordinates\":[[0,1],[2,3],[4,5]]}", + NULL, 0, 1); + + /* Polygon */ + do_geojson_test( + "POLYGON((0 1,2 3,4 5,0 1))", + "{\"type\":\"Polygon\",\"bbox\":[0,1,4,5],\"coordinates\":[[[0,1],[2,3],[4,5],[0,1]]]}", + NULL, 0, 1); + + /* Polygon - with internal ring */ + do_geojson_test( + "POLYGON((0 1,2 3,4 5,0 1),(6 7,8 9,10 11,6 7))", + "{\"type\":\"Polygon\",\"bbox\":[0,1,4,5],\"coordinates\":[[[0,1],[2,3],[4,5],[0,1]],[[6,7],[8,9],[10,11],[6,7]]]}", + NULL, 0, 1); + + /* Multiline */ + do_geojson_test( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "{\"type\":\"MultiLineString\",\"bbox\":[0,1,10,11],\"coordinates\":[[[0,1],[2,3],[4,5]],[[6,7],[8,9],[10,11]]]}", + NULL, 0, 1); + + /* MultiPolygon */ + do_geojson_test( + "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "{\"type\":\"MultiPolygon\",\"bbox\":[0,1,10,11],\"coordinates\":[[[[0,1],[2,3],[4,5],[0,1]]],[[[6,7],[8,9],[10,11],[6,7]]]]}", + NULL, 0, 1); + + /* GeometryCollection */ + do_geojson_test( + "GEOMETRYCOLLECTION(LINESTRING(0 1,-1 3),LINESTRING(2 3,4 5))", + "{\"type\":\"GeometryCollection\",\"bbox\":[-1,1,4,5],\"geometries\":[{\"type\":\"LineString\",\"coordinates\":[[0,1],[-1,3]]},{\"type\":\"LineString\",\"coordinates\":[[2,3],[4,5]]}]}", + NULL, 0, 1); + + /* Empty GeometryCollection */ + do_geojson_test( + "GEOMETRYCOLLECTION EMPTY", + "{\"type\":\"GeometryCollection\",\"geometries\":[]}", + NULL, 0, 1); +} + +static void out_geojson_test_geoms(void) +{ + /* Linestring */ + do_geojson_test( + "LINESTRING(0 1,2 3,4 5)", + "{\"type\":\"LineString\",\"coordinates\":[[0,1],[2,3],[4,5]]}", + NULL, 0, 0); + + /* Polygon */ + do_geojson_test( + "POLYGON((0 1,2 3,4 5,0 1))", + "{\"type\":\"Polygon\",\"coordinates\":[[[0,1],[2,3],[4,5],[0,1]]]}", + NULL, 0, 0); + + /* Polygon - with internal ring */ + do_geojson_test( + "POLYGON((0 1,2 3,4 5,0 1),(6 7,8 9,10 11,6 7))", + "{\"type\":\"Polygon\",\"coordinates\":[[[0,1],[2,3],[4,5],[0,1]],[[6,7],[8,9],[10,11],[6,7]]]}", + NULL, 0, 0); + + /* Multiline */ + do_geojson_test( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "{\"type\":\"MultiLineString\",\"coordinates\":[[[0,1],[2,3],[4,5]],[[6,7],[8,9],[10,11]]]}", + NULL, 0, 0); + + /* MultiPolygon */ + do_geojson_test( + "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "{\"type\":\"MultiPolygon\",\"coordinates\":[[[[0,1],[2,3],[4,5],[0,1]]],[[[6,7],[8,9],[10,11],[6,7]]]]}", + NULL, 0, 0); + + /* GeometryCollection */ + do_geojson_test( + "GEOMETRYCOLLECTION(POINT(0 1),LINESTRING(2 3,4 5))", + "{\"type\":\"GeometryCollection\",\"geometries\":[{\"type\":\"Point\",\"coordinates\":[0,1]},{\"type\":\"LineString\",\"coordinates\":[[2,3],[4,5]]}]}", + NULL, 0, 0); + + /* Empty GeometryCollection */ + do_geojson_test( + "GEOMETRYCOLLECTION EMPTY", + "{\"type\":\"GeometryCollection\",\"geometries\":[]}", + NULL, 0, 0); + + /* Nested GeometryCollection */ + do_geojson_unsupported( + "GEOMETRYCOLLECTION(POINT(0 1),GEOMETRYCOLLECTION(LINESTRING(2 3,4 5)))", + "GeoJson: geometry not supported."); + + /* CircularString */ + do_geojson_unsupported( + "CIRCULARSTRING(-2 0,0 2,2 0,0 2,2 4)", + "lwgeom_to_geojson: 'CircularString' geometry type not supported"); + + /* CompoundCurve */ + do_geojson_unsupported( + "COMPOUNDCURVE(CIRCULARSTRING(0 0,1 1,1 0),(1 0,0 1))", + "lwgeom_to_geojson: 'CompoundCurve' geometry type not supported"); + + /* CurvePolygon */ + do_geojson_unsupported( + "CURVEPOLYGON(CIRCULARSTRING(-2 0,-1 -1,0 0,1 -1,2 0,0 2,-2 0),(-1 0,0 0.5,1 0,0 1,-1 0))", + "lwgeom_to_geojson: 'CurvePolygon' geometry type not supported"); + + /* MultiCurve */ + do_geojson_unsupported( + "MULTICURVE((5 5,3 5,3 3,0 3),CIRCULARSTRING(0 0,2 1,2 2))", + "lwgeom_to_geojson: 'MultiCurve' geometry type not supported"); + + /* MultiSurface */ + do_geojson_unsupported( + "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(-2 0,-1 -1,0 0,1 -1,2 0,0 2,-2 0),(-1 0,0 0.5,1 0,0 1,-1 0)),((7 8,10 10,6 14,4 11,7 8)))", + "lwgeom_to_geojson: 'MultiSurface' geometry type not supported"); +} + +/* +** Used by test harness to register the tests in this file. +*/ +void out_geojson_suite_setup(void); +void out_geojson_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("geojson_output", NULL, NULL); + PG_ADD_TEST(suite, out_geojson_test_precision); + PG_ADD_TEST(suite, out_geojson_test_dims); + PG_ADD_TEST(suite, out_geojson_test_srid); + PG_ADD_TEST(suite, out_geojson_test_bbox); + PG_ADD_TEST(suite, out_geojson_test_geoms); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_out_gml.c b/mgist-postgis/liblwgeom/cunit/cu_out_gml.c new file mode 100644 index 0000000..fdd1631 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_out_gml.c @@ -0,0 +1,1032 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright 2010 Olivier Courtin + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +static void do_gml2_test(char * in, char * out, char * srs, int precision) +{ + LWGEOM *g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwvarlena_t *v = lwgeom_to_gml2(g, srs, precision, "gml:"); + + ASSERT_VARLENA_EQUAL(v, out); + + lwgeom_free(g); + lwfree(v); +} + +static void do_gml2_test_prefix(char * in, char * out, char * srs, int precision, const char *prefix) +{ + LWGEOM *g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwvarlena_t *v = lwgeom_to_gml2(g, srs, precision, prefix); + + ASSERT_VARLENA_EQUAL(v, out); + + lwgeom_free(g); + lwfree(v); +} + +static void do_gml3_test_opts(char * in, char * out, char * srs, int precision, int opts) +{ + LWGEOM *g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwvarlena_t *v = lwgeom_to_gml3(g, srs, precision, opts, "gml:", NULL); + + ASSERT_VARLENA_EQUAL(v, out); + + lwgeom_free(g); + lwfree(v); +} + +static void do_gml3_test(char * in, char * out, char * srs, int precision, int is_geodetic) +{ + int opts = LW_GML_IS_DIMS; + if ( is_geodetic ) opts |= LW_GML_IS_DEGREE; + + LWGEOM *g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwvarlena_t *v = lwgeom_to_gml3(g, srs, precision, opts, "gml:", NULL); + + ASSERT_VARLENA_EQUAL(v, out); + + lwgeom_free(g); + lwfree(v); +} + +static void do_gml3_test_prefix(char * in, char * out, char * srs, int precision, int is_geodetic, const char *prefix) +{ + int opts = LW_GML_IS_DIMS; + if ( is_geodetic ) opts |= LW_GML_IS_DEGREE; + + LWGEOM *g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwvarlena_t *v = lwgeom_to_gml3(g, srs, precision, opts, prefix, NULL); + + ASSERT_VARLENA_EQUAL(v, out); + + lwgeom_free(g); + lwfree(v); +} + +static void do_gml3_test_nodims(char * in, char * out, char * srs, int precision, int is_geodetic, int is_dims, const char *prefix) +{ + int opts = 0; + if ( is_geodetic ) opts |= LW_GML_IS_DEGREE; + if ( is_dims ) opts |= LW_GML_IS_DIMS; + + LWGEOM *g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwvarlena_t *v = lwgeom_to_gml3(g, srs, precision, opts, prefix, NULL); + + ASSERT_VARLENA_EQUAL(v, out); + + lwgeom_free(g); + lwfree(v); +} + +static void do_gml2_unsupported(char * in, char * out) +{ + LWGEOM *g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwvarlena_t *v = lwgeom_to_gml2(g, NULL, 0, ""); + + ASSERT_STRING_EQUAL(out, cu_error_msg); + cu_error_msg_reset(); + + lwfree(v); + lwgeom_free(g); +} + +static void do_gml2_extent_test(char * in, char * out, char * srs, + double precision, char * prefix) +{ + LWGEOM *g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwvarlena_t *v = lwgeom_extent_to_gml2(g, srs, precision, prefix); + if (!v) + { + ASSERT_STRING_EQUAL(in, cu_error_msg); + } + else + { + ASSERT_VARLENA_EQUAL(v, out); + } + + cu_error_msg_reset(); + lwfree(v); + lwgeom_free(g); +} + +static void do_gml3_extent_test(char * in, char * out, char * srs, + double precision, int opts, char* prefix) +{ + LWGEOM *g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwvarlena_t *v = lwgeom_extent_to_gml3(g, srs, precision, opts, prefix); + + ASSERT_VARLENA_EQUAL(v, out); + + lwgeom_free(g); + lwfree(v); +} + +static void out_gml_test_precision(void) +{ + /* GML2 - 0 precision, i.e a round */ + do_gml2_test( + "POINT(1.1111111111111 1.1111111111111)", + "1,1", + NULL, 0); + + /* GML3 - 0 precision, i.e a round */ + do_gml3_test( + "POINT(1.1111111111111 1.1111111111111)", + "1 1", + NULL, 0, 0); + + + /* GML2 - 3 digits precision */ + do_gml2_test( + "POINT(1.1111111111111 1.1111111111111)", + "1.111,1.111", + NULL, 3); + + /* GML3 - 3 digits precision */ + do_gml3_test( + "POINT(1.1111111111111 1.1111111111111)", + "1.111 1.111", + NULL, 3, 0); + + + /* GML2 - 9 digits precision */ + do_gml2_test( + "POINT(1.2345678901234 1.2345678901234)", + "1.23456789,1.23456789", + NULL, 9); + + /* GML3 - 9 digits precision */ + do_gml3_test( + "POINT(1.2345678901234 1.2345678901234)", + "1.23456789 1.23456789", + NULL, 9, 0); + + + /* GML2 - huge data */ + do_gml2_test( + "POINT(1E300 -1E300)", + "1e+300,-1e+300", + NULL, 0); + + /* GML3 - huge data */ + do_gml3_test( + "POINT(1E300 -1E300)", + "1e+300 -1e+300", + NULL, 0, 0); +} + +static void out_gml_test_srid(void) +{ + /* GML2 - Point with SRID */ + do_gml2_test( + "POINT(0 1)", + "0,1", + "EPSG:4326", 0); + + /* GML3 - Point with SRID */ + do_gml3_test( + "POINT(0 1)", + "0 1", + "EPSG:4326", 0, 0); + + + /* GML2 - Linestring with SRID */ + do_gml2_test( + "LINESTRING(0 1,2 3,4 5)", + "0,1 2,3 4,5", + "EPSG:4326", 0); + + /* GML3 - Linestring with SRID */ + do_gml3_test( + "LINESTRING(0 1,2 3,4 5)", + "0 1 2 3 4 5", + "EPSG:4326", 0, 0); + + /* GML3 - Linestring with SRID and short tag*/ + do_gml3_test_opts( + "LINESTRING(0 1,2 3,4 5)", + "0 1 2 3 4 5", + "EPSG:4326", 0, LW_GML_SHORTLINE); + + + /* GML2 Polygon with SRID */ + do_gml2_test( + "POLYGON((0 1,2 3,4 5,0 1))", + "0,1 2,3 4,5 0,1", + "EPSG:4326", 0); + + /* GML3 Polygon with SRID */ + do_gml3_test( + "POLYGON((0 1,2 3,4 5,0 1))", + "0 1 2 3 4 5 0 1", + "EPSG:4326", 0, 0); + + + /* GML2 MultiPoint with SRID */ + do_gml2_test( + "MULTIPOINT(0 1,2 3)", + "0,12,3", + "EPSG:4326", 0); + + /* GML3 MultiPoint with SRID */ + do_gml3_test( + "MULTIPOINT(0 1,2 3)", + "0 12 3", + "EPSG:4326", 0, 0); + + + /* GML2 Multiline with SRID */ + do_gml2_test( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "0,1 2,3 4,56,7 8,9 10,11", + "EPSG:4326", 0); + + + /* GML3 Multiline with SRID */ + do_gml3_test( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "0 1 2 3 4 56 7 8 9 10 11", + "EPSG:4326", 0, 0); + + /* GML3 Multiline with SRID and LineString tag */ + do_gml3_test_opts( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "0 1 2 3 4 56 7 8 9 10 11", + "EPSG:4326", 0, LW_GML_SHORTLINE); + + + /* GML2 MultiPolygon with SRID */ + do_gml2_test( + "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "0,1 2,3 4,5 0,16,7 8,9 10,11 6,7", + "EPSG:4326", 0); + + /* GML3 MultiPolygon with SRID */ + do_gml3_test( + "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "0 1 2 3 4 5 0 16 7 8 9 10 11 6 7", + "EPSG:4326", 0, 0); + + /* GML3 PolyhedralSurface with SRID */ + do_gml3_test( + "POLYHEDRALSURFACE(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "0 1 2 3 4 5 0 16 7 8 9 10 11 6 7", + "EPSG:4326", 0, 0); + + /* GML3 Tin with SRID */ + do_gml3_test( + "TIN(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "0 1 2 3 4 5 0 16 7 8 9 10 11 6 7", + "EPSG:4326", 0, 0); + + + /* GML2 GeometryCollection with SRID */ + do_gml2_test( + "GEOMETRYCOLLECTION(POINT(0 1),LINESTRING(2 3,4 5))", + "0,12,3 4,5", + "EPSG:4326", 0); + + /* GML3 GeometryCollection with SRID */ + do_gml3_test( + "GEOMETRYCOLLECTION(POINT(0 1),LINESTRING(2 3,4 5))", + "0 12 3 4 5", + "EPSG:4326", 0, 0); +} + + +static void out_gml_test_geodetic(void) +{ + /* GML3 - Geodetic Point */ + do_gml3_test( + "POINT(0 1)", + "1 0", + "urn:ogc:def:crs:EPSG::4326", 0, 1); + + /* GML3 - 3D Geodetic Point */ + do_gml3_test( + "POINT(0 1 2)", + "1 0 2", + "urn:ogc:def:crs:EPSG::4326", 0, 1); +} + + +static void out_gml_test_dims(void) +{ + /* GML2 - 3D */ + do_gml2_test( + "POINT(0 1 2)", + "0,1,2", + NULL, 0); + + /* GML3 - 3D */ + do_gml3_test( + "POINT(0 1 2)", + "0 1 2", + NULL, 0, 0); + + + /* GML2 - 3DM */ + do_gml2_test( + "POINTM(0 1 2)", + "0,1", + NULL, 0); + + /* GML3 - 3DM */ + do_gml3_test( + "POINTM(0 1 2)", + "0 1", + NULL, 0, 0); + + + /* GML2 - 4D */ + do_gml2_test( + "POINT(0 1 2 3)", + "0,1,2", + NULL, 0); + + /* GML3 - 4D */ + do_gml3_test( + "POINT(0 1 2 3)", + "0 1 2", + NULL, 0, 0); +} + + +static void out_gml_test_geoms(void) +{ + /* GML2 - Linestring */ + do_gml2_test( + "LINESTRING(0 1,2 3,4 5)", + "0,1 2,3 4,5", + NULL, 0); + + /* GML3 - Linestring */ + do_gml3_test( + "LINESTRING(0 1,2 3,4 5)", + "0 1 2 3 4 5", + NULL, 0, 0); + + + /* GML2 Polygon */ + do_gml2_test( + "POLYGON((0 1,2 3,4 5,0 1))", + "0,1 2,3 4,5 0,1", + NULL, 0); + + /* GML3 Polygon */ + do_gml3_test( + "POLYGON((0 1,2 3,4 5,0 1))", + "0 1 2 3 4 5 0 1", + NULL, 0, 0); + + /* GML2 Polygon - with internal ring */ + do_gml2_test( + "POLYGON((0 1,2 3,4 5,0 1),(6 7,8 9,10 11,6 7))", + "0,1 2,3 4,5 0,16,7 8,9 10,11 6,7", + NULL, 0); + + /* GML3 Polygon - with internal ring */ + do_gml3_test( + "POLYGON((0 1,2 3,4 5,0 1),(6 7,8 9,10 11,6 7))", + "0 1 2 3 4 5 0 16 7 8 9 10 11 6 7", + NULL, 0, 0); + + + /* GML3 Triangle */ + do_gml3_test( + "TRIANGLE((0 1,2 3,4 5,0 1))", + "0 1 2 3 4 5 0 1", + NULL, 0, 0); + + + /* GML2 MultiPoint */ + do_gml2_test( + "MULTIPOINT(0 1,2 3)", + "0,12,3", + NULL, 0); + + /* GML3 MultiPoint */ + do_gml3_test( + "MULTIPOINT(0 1,2 3)", + "0 12 3", + NULL, 0, 0); + + + /* GML2 Multiline */ + do_gml2_test( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "0,1 2,3 4,56,7 8,9 10,11", + NULL, 0); + + /* GML3 Multiline */ + do_gml3_test( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "0 1 2 3 4 56 7 8 9 10 11", + NULL, 0, 0); + + + /* GML2 MultiPolygon */ + do_gml2_test( + "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "0,1 2,3 4,5 0,16,7 8,9 10,11 6,7", + NULL, 0); + + /* GML3 MultiPolygon */ + do_gml3_test( + "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "0 1 2 3 4 5 0 16 7 8 9 10 11 6 7", + NULL, 0, 0); + + + /* GML2 - GeometryCollection */ + do_gml2_test( + "GEOMETRYCOLLECTION(POINT(0 1),LINESTRING(2 3,4 5))", + "0,12,3 4,5", + NULL, 0); + + /* GML3 - GeometryCollection */ + do_gml3_test( + "GEOMETRYCOLLECTION(POINT(0 1),LINESTRING(2 3,4 5))", + "0 12 3 4 5", + NULL, 0, 0); + + + /* GML2 - Nested GeometryCollection */ + do_gml2_test( + "GEOMETRYCOLLECTION(POINT(0 1),GEOMETRYCOLLECTION(LINESTRING(2 3,4 5)))", + "0,12,3 4,5", + NULL, 0); + + /* GML3 - Nested GeometryCollection */ + do_gml3_test( + "GEOMETRYCOLLECTION(POINT(0 1),GEOMETRYCOLLECTION(LINESTRING(2 3,4 5)))", + "0 12 3 4 5", + NULL, 0, 0); + + /* GML2 - CircularString */ + do_gml2_unsupported( + "CIRCULARSTRING(-2 0,0 2,2 0,0 2,2 4)", + "lwgeom_to_gml2: 'CircularString' geometry type not supported"); + /* GML3 - CircularString */ + do_gml3_test( + "CIRCULARSTRING(-2 0,0 2,2 0,0 2,2 4)", + "-2 0 0 2 2 0 0 2 2 4", + NULL, 0, 0 ); + + /* GML2 - CompoundCurve */ + do_gml2_unsupported( + "COMPOUNDCURVE(CIRCULARSTRING(0 0,1 1,1 0),(1 0,0 1))", + "lwgeom_to_gml2: 'CompoundCurve' geometry type not supported"); + /* GML3 - CompoundCurve */ + + do_gml3_test( + "COMPOUNDCURVE(CIRCULARSTRING(0 0,1 1,1 0),(1 0,0 1))", + "0 0 1 1 1 01 0 0 1", + NULL, 0, 0 ); + + /* GML2 - CurvePolygon */ + do_gml2_unsupported( + "CURVEPOLYGON(CIRCULARSTRING(-2 0,-1 -1,0 0,1 -1,2 0,0 2,-2 0),(-1 0,0 0.5,1 0,0 1,-1 0))", + "lwgeom_to_gml2: 'CurvePolygon' geometry type not supported"); + + /* GML3 - CurvePolygon */ + do_gml3_test( + "CURVEPOLYGON(CIRCULARSTRING(-2 0,-1 -1,0 0,1 -1,2 0,0 2,-2 0),(-1 0,0 0.5,1 0,0 1,-1 0))", + "-2 0 -1 -1 0 0 1 -1 2 0 0 2 -2 0-1 0 0 0.5 1 0 0 1 -1 0", + NULL, 1, 0 ); + do_gml3_test( + "CURVEPOLYGON(COMPOUNDCURVE((763650.600000001 189057.100000001,7636.35 189045.199999999, 763650.548999999 189057.844000001,763650.600000001 189057.100000001)))", + "763650.6 189057.1 7636.35 189045.2 763650.549 189057.844 763650.6 189057.1", + NULL, 7, 0 ); + + /* GML2 - MultiCurve */ + do_gml2_unsupported( + "MULTICURVE((5 5,3 5,3 3,0 3),CIRCULARSTRING(0 0,2 1,2 2))", + "lwgeom_to_gml2: 'MultiCurve' geometry type not supported"); + + /* GML3 - MultiCurve */ + do_gml3_test( + "MULTICURVE((5 5,3 5,3 3,0 3),CIRCULARSTRING(0 0,2 1,2 2))", + "5 5 3 5 3 3 0 30 0 2 1 2 2", + NULL, 0, 0 ); + + /* GML2 - MultiSurface */ + do_gml2_unsupported( + "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(-2 0,-1 -1,0 0,1 -1,2 0,0 2,-2 0),(-1 0,0 0.5,1 0,0 1,-1 0)),((7 8,10 10,6 14,4 11,7 8)))", + "lwgeom_to_gml2: 'MultiSurface' geometry type not supported"); + + /* GML3 - MultiSurface */ + do_gml3_test( + "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(-2 0,-1 -1,0 0,1 -1,2 0,0 2,-2 0),(-1 0,0 0.5,1 0,0 1,-1 0)),((7 8,10 10,6 14,4 11,7 8)))", + "-2 0 -1 -1 0 0 1 -1 2 0 0 2 -2 0-1 0 0 0.5 1 0 0 1 -1 07 8 10 10 6 14 4 11 7 8", + NULL, 1, 0 ); + + /* GML2 - PolyhedralSurface */ + do_gml2_unsupported( + "POLYHEDRALSURFACE(((0 1,2 3,4 5,0 1)))", + "Cannot convert PolyhedralSurface to GML2. Try ST_AsGML(3, ) to generate GML3."); + + /* GML2 - Tin */ + do_gml2_unsupported( + "TIN(((0 1,2 3,4 5,0 1)))", + "Cannot convert Tin to GML2. Try ST_AsGML(3, ) to generate GML3."); +} + +static void out_gml_test_geoms_prefix(void) +{ + /* GML2 - Linestring */ + do_gml2_test_prefix( + "LINESTRING(0 1,2 3,4 5)", + "0,1 2,3 4,5", + NULL, 0, "custom:"); + + /* GML3 - Linestring */ + do_gml3_test_prefix( + "LINESTRING(0 1,2 3,4 5)", + "0 1 2 3 4 5", + NULL, 0, 0, "custom:"); + + + /* GML2 Polygon */ + do_gml2_test_prefix( + "POLYGON((0 1,2 3,4 5,0 1))", + "0,1 2,3 4,5 0,1", + NULL, 0, "custom:"); + + /* GML3 Polygon */ + do_gml3_test_prefix( + "POLYGON((0 1,2 3,4 5,0 1))", + "0 1 2 3 4 5 0 1", + NULL, 0, 0, "custom:"); + + + /* GML2 Polygon - with internal ring */ + do_gml2_test_prefix( + "POLYGON((0 1,2 3,4 5,0 1),(6 7,8 9,10 11,6 7))", + "0,1 2,3 4,5 0,16,7 8,9 10,11 6,7", + NULL, 0, "custom:"); + + /* GML3 Polygon - with internal ring */ + do_gml3_test_prefix( + "POLYGON((0 1,2 3,4 5,0 1),(6 7,8 9,10 11,6 7))", + "0 1 2 3 4 5 0 16 7 8 9 10 11 6 7", + NULL, 0, 0, "custom:"); + + /* GML3 Triangle */ + do_gml3_test_prefix( + "TRIANGLE((0 1,2 3,4 5,0 1))", + "0 1 2 3 4 5 0 1", + NULL, 0, 0, "custom:"); + + + /* GML2 MultiPoint */ + do_gml2_test_prefix( + "MULTIPOINT(0 1,2 3)", + "0,12,3", + NULL, 0, "custom:"); + + /* GML3 MultiPoint */ + do_gml3_test_prefix( + "MULTIPOINT(0 1,2 3)", + "0 12 3", + NULL, 0, 0, "custom:"); + + + /* GML2 Multiline */ + do_gml2_test_prefix( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "0,1 2,3 4,56,7 8,9 10,11", + NULL, 0, "custom:"); + + /* GML3 Multiline */ + do_gml3_test_prefix( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "0 1 2 3 4 56 7 8 9 10 11", + NULL, 0, 0, "custom:"); + + + /* GML2 MultiPolygon */ + do_gml2_test_prefix( + "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "0,1 2,3 4,5 0,16,7 8,9 10,11 6,7", + NULL, 0, "custom:"); + + /* GML3 MultiPolygon */ + do_gml3_test_prefix( + "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "0 1 2 3 4 5 0 16 7 8 9 10 11 6 7", + NULL, 0, 0, "custom:"); + + /* GML3 PolyhedralSurface */ + do_gml3_test_prefix( + "POLYHEDRALSURFACE(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "0 1 2 3 4 5 0 16 7 8 9 10 11 6 7", + NULL, 0, 0, "custom:"); + + /* GML3 Tin */ + do_gml3_test_prefix( + "TIN(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "0 1 2 3 4 5 0 16 7 8 9 10 11 6 7", + NULL, 0, 0, "custom:"); + + /* GML2 - GeometryCollection */ + do_gml2_test_prefix( + "GEOMETRYCOLLECTION(POINT(0 1),LINESTRING(2 3,4 5))", + "0,12,3 4,5", + NULL, 0, "custom:"); + + /* GML3 - GeometryCollection */ + do_gml3_test_prefix( + "GEOMETRYCOLLECTION(POINT(0 1),LINESTRING(2 3,4 5))", + "0 12 3 4 5", + NULL, 0, 0, "custom:"); + + /* GML2 - Nested GeometryCollection */ + do_gml2_test_prefix( + "GEOMETRYCOLLECTION(POINT(0 1),GEOMETRYCOLLECTION(LINESTRING(2 3,4 5)))", + "0,12,3 4,5", + NULL, 0, "custom:"); + + /* GML3 - Nested GeometryCollection */ + do_gml3_test_prefix( + "GEOMETRYCOLLECTION(POINT(0 1),GEOMETRYCOLLECTION(LINESTRING(2 3,4 5)))", + "0 12 3 4 5", + NULL, 0, 0, "custom:"); + + /*------------- empty prefixes below ------------------------ */ + + /* GML2 - Linestring */ + do_gml2_test_prefix( + "LINESTRING(0 1,2 3,4 5)", + "0,1 2,3 4,5", + NULL, 0, ""); + + /* GML3 - Linestring */ + do_gml3_test_prefix( + "LINESTRING(0 1,2 3,4 5)", + "0 1 2 3 4 5", + NULL, 0, 0, ""); + + + /* GML2 Polygon */ + do_gml2_test_prefix( + "POLYGON((0 1,2 3,4 5,0 1))", + "0,1 2,3 4,5 0,1", + NULL, 0, ""); + + /* GML3 Polygon */ + do_gml3_test_prefix( + "POLYGON((0 1,2 3,4 5,0 1))", + "0 1 2 3 4 5 0 1", + NULL, 0, 0, ""); + + + /* GML2 Polygon - with internal ring */ + do_gml2_test_prefix( + "POLYGON((0 1,2 3,4 5,0 1),(6 7,8 9,10 11,6 7))", + "0,1 2,3 4,5 0,16,7 8,9 10,11 6,7", + NULL, 0, ""); + + /* GML3 Polygon - with internal ring */ + do_gml3_test_prefix( + "POLYGON((0 1,2 3,4 5,0 1),(6 7,8 9,10 11,6 7))", + "0 1 2 3 4 5 0 16 7 8 9 10 11 6 7", + NULL, 0, 0, ""); + + /* GML3 Triangle */ + do_gml3_test_prefix( + "TRIANGLE((0 1,2 3,4 5,0 1))", + "0 1 2 3 4 5 0 1", + NULL, 0, 0, ""); + + + /* GML2 MultiPoint */ + do_gml2_test_prefix( + "MULTIPOINT(0 1,2 3)", + "0,12,3", + NULL, 0, ""); + + /* GML3 MultiPoint */ + do_gml3_test_prefix( + "MULTIPOINT(0 1,2 3)", + "0 12 3", + NULL, 0, 0, ""); + + + /* GML2 Multiline */ + do_gml2_test_prefix( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "0,1 2,3 4,56,7 8,9 10,11", + NULL, 0, ""); + + /* GML3 Multiline */ + do_gml3_test_prefix( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "0 1 2 3 4 56 7 8 9 10 11", + NULL, 0, 0, ""); + + + /* GML2 MultiPolygon */ + do_gml2_test_prefix( + "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "0,1 2,3 4,5 0,16,7 8,9 10,11 6,7", + NULL, 0, ""); + + /* GML3 MultiPolygon */ + do_gml3_test_prefix( + "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "0 1 2 3 4 5 0 16 7 8 9 10 11 6 7", + NULL, 0, 0, ""); + + /* GML3 PolyhedralSurface */ + do_gml3_test_prefix( + "POLYHEDRALSURFACE(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "0 1 2 3 4 5 0 16 7 8 9 10 11 6 7", + NULL, 0, 0, ""); + + /* GML3 PolyhedralSurface */ + do_gml3_test_prefix( + "TIN(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "0 1 2 3 4 5 0 16 7 8 9 10 11 6 7", + NULL, 0, 0, ""); + + /* GML2 - GeometryCollection */ + do_gml2_test_prefix( + "GEOMETRYCOLLECTION(POINT(0 1),LINESTRING(2 3,4 5))", + "0,12,3 4,5", + NULL, 0, ""); + + /* GML3 - GeometryCollection */ + do_gml3_test_prefix( + "GEOMETRYCOLLECTION(POINT(0 1),LINESTRING(2 3,4 5))", + "0 12 3 4 5", + NULL, 0, 0, ""); + + /* GML2 - Nested GeometryCollection */ + do_gml2_test_prefix( + "GEOMETRYCOLLECTION(POINT(0 1),GEOMETRYCOLLECTION(LINESTRING(2 3,4 5)))", + "0,12,3 4,5", + NULL, 0, ""); + + /* GML3 - Nested GeometryCollection */ + do_gml3_test_prefix( + "GEOMETRYCOLLECTION(POINT(0 1),GEOMETRYCOLLECTION(LINESTRING(2 3,4 5)))", + "0 12 3 4 5", + NULL, 0, 0, ""); + + + +} + + +static void out_gml_test_geoms_nodims(void) +{ + /* GML3 - Linestring */ + do_gml3_test_nodims( + "LINESTRING(0 1,2 3,4 5)", + "0 1 2 3 4 5", + NULL, 0, 0, 0, ""); + + + /* GML3 Polygon */ + do_gml3_test_nodims( + "POLYGON((0 1,2 3,4 5,0 1))", + "0 1 2 3 4 5 0 1", + NULL, 0, 0, 0, ""); + + + /* GML3 Polygon - with internal ring */ + do_gml3_test_nodims( + "POLYGON((0 1,2 3,4 5,0 1),(6 7,8 9,10 11,6 7))", + "0 1 2 3 4 5 0 16 7 8 9 10 11 6 7", + NULL, 0, 0, 0, ""); + + /* GML3 Triangle */ + do_gml3_test_nodims( + "TRIANGLE((0 1,2 3,4 5,0 1))", + "0 1 2 3 4 5 0 1", + NULL, 0, 0, 0, ""); + + + /* GML3 MultiPoint */ + do_gml3_test_nodims( + "MULTIPOINT(0 1,2 3)", + "0 12 3", + NULL, 0, 0, 0, ""); + + + /* GML3 Multiline */ + do_gml3_test_nodims( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "0 1 2 3 4 56 7 8 9 10 11", + NULL, 0, 0, 0, ""); + + + /* GML3 MultiPolygon */ + do_gml3_test_nodims( + "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "0 1 2 3 4 5 0 16 7 8 9 10 11 6 7", + NULL, 0, 0, 0, ""); + + /* GML3 PolyhedralSurface */ + do_gml3_test_nodims( + "POLYHEDRALSURFACE(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "0 1 2 3 4 5 0 16 7 8 9 10 11 6 7", + NULL, 0, 0, 0, ""); + + /* GML3 Tin */ + do_gml3_test_nodims( + "TIN(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "0 1 2 3 4 5 0 16 7 8 9 10 11 6 7", + NULL, 0, 0, 0, ""); + + /* GML3 - GeometryCollection */ + do_gml3_test_nodims( + "GEOMETRYCOLLECTION(POINT(0 1),LINESTRING(2 3,4 5))", + "0 12 3 4 5", + NULL, 0, 0, 0, ""); + + /* GML3 - Nested GeometryCollection */ + do_gml3_test_nodims( + "GEOMETRYCOLLECTION(POINT(0 1),GEOMETRYCOLLECTION(LINESTRING(2 3,4 5)))", + "0 12 3 4 5", + NULL, 0, 0, 0, ""); +} + +static void out_gml2_extent(void) +{ + /* GML2: Point */ + do_gml2_extent_test( + "POINT(-15 60)", + "-15,60 -15,60", + NULL, 15, ""); + do_gml2_extent_test( + "POINT(-15 60)", + "-15,60 -15,60", + NULL, 15, "gml:"); + do_gml2_extent_test( + "POINT(-15 60)", + "-15,60 -15,60", + "urn:ogc:def:crs:EPSG::4326", 15, ""); + + /* GML2: Multipoint */ + do_gml2_extent_test( + "MULTIPOINT(2 3, -5 -6)", + "-5,-6 2,3", + NULL, 15, ""); + + /* GML2: Linestring */ + do_gml2_extent_test( + "LINESTRING(0 1,2 3,4 5)", + "0,1 4,5", + NULL, 15, ""); + + /* GML2: MultiLinestring */ + do_gml2_extent_test( + "MULTILINESTRING((0 1,2 3),(4 5, 10 6))", + "0,1 10,6", + NULL, 15, ""); + + /* GML2: Polygon */ + do_gml2_extent_test( + "POLYGON((1 7,7 14, 14 7, 1 7))", + "1,7 14,14", + NULL, 15, ""); + + /* GML2: MultiPolygon */ + do_gml2_extent_test( + "MULTIPOLYGON(((1 7,7 14, 14 7, 1 7)),((-4 -6, -15 3, 0 0, -4 -6)))", + "-15,-6 14,14", + NULL, 15, ""); + + /* GML2: MultiSurface */ + do_gml2_extent_test( + "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(-2 0,-1 -1,0 0,1 -1,2 0,0 2,-2 0),(-1 0,0 0.5,1 0,0 1,-1 0)),((7 8,10 10,6 14,4 11,7 8)))", + "-2,-1 10,14", + NULL, 15, ""); + + /* GML2: empty */ + do_gml2_extent_test( + "GEOMETRYCOLLECTION EMPTY", + "", + NULL, 15, ""); + + /* GML2: empty with srsName */ + do_gml2_extent_test( + "GEOMETRYCOLLECTION EMPTY", + "", + "urn:ogc:def:crs:EPSG::4326", 15, ""); + +} + +static void out_gml3_extent(void) +{ + /* GML3: Point */ + do_gml3_extent_test( + "POINT(-15 60)", + "-15 60-15 60", + NULL, 15, 0, ""); + do_gml3_extent_test( + "POINT(-15 60)", + "-15 60-15 60", + NULL, 15, 0, "gml:"); + do_gml3_extent_test( + "POINT(-15 60)", + "-15 60-15 60", + "urn:ogc:def:crs:EPSG::4326", 15, 0, ""); + + /* GML3: Multipoint */ + do_gml3_extent_test( + "MULTIPOINT(2 3, -5 -6)", + "-5 -62 3", + NULL, 15, 0, ""); + + /* GML3: Linestring */ + do_gml3_extent_test( + "LINESTRING(0 1,2 3,4 5)", + "0 14 5", + NULL, 15, 0, ""); + + /* GML3: MultiLinestring */ + do_gml3_extent_test( + "MULTILINESTRING((0 1,2 3),(4 5, 10 6))", + "0 110 6", + NULL, 15, 0, ""); + do_gml3_extent_test( + "MULTILINESTRING((0 1,2 3),(4 5, 10 6))", + "1 06 10", + NULL, 15, LW_GML_IS_DEGREE, ""); + do_gml3_extent_test( + "MULTILINESTRING((0 1,2 3),(4 5, 10 6))", + "1 06 10", + NULL, 15, LW_GML_IS_DEGREE|LW_GML_IS_DIMS, ""); + do_gml3_extent_test( + "MULTILINESTRING((0 1 10,2 3 30),(4 5 50, 10 6 -70))", + "1 0 -706 10 50", + NULL, 15, LW_GML_IS_DEGREE|LW_GML_IS_DIMS, ""); + + /* GML3: Polygon */ + do_gml3_extent_test( + "POLYGON((1 7,7 14, 14 7, 1 7))", + "1 714 14", + NULL, 15, 0, ""); + + /* GML3: MultiPolygon */ + do_gml3_extent_test( + "MULTIPOLYGON(((1 7,7 14, 14 7, 1 7)),((-4 -6, -15 3, 0 0, -4 -6)))", + "-15 -614 14", + NULL, 15, 0, ""); + + /* GML3: MultiSurface */ + do_gml3_extent_test( + "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(-2 0,-1 -1,0 0,1 -1,2 0,0 2,-2 0),(-1 0,0 0.5,1 0,0 1,-1 0)),((7 8,10 10,6 14,4 11,7 8)))", + "-2 -110 14", + NULL, 15, 0, ""); + + /* GML3: empty */ + do_gml3_extent_test( + "GEOMETRYCOLLECTION EMPTY", + "", + NULL, 15, 0, ""); + + /* GML3: empty with srsName */ + do_gml3_extent_test( + "GEOMETRYCOLLECTION EMPTY", + "", + "urn:ogc:def:crs:EPSG::4326", 15, 0, ""); + +} + +/* +** Used by test harness to register the tests in this file. +*/ +void out_gml_suite_setup(void); +void out_gml_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("gml_output", NULL, NULL); + PG_ADD_TEST(suite, out_gml_test_precision); + PG_ADD_TEST(suite, out_gml_test_srid); + PG_ADD_TEST(suite, out_gml_test_dims); + PG_ADD_TEST(suite, out_gml_test_geodetic); + PG_ADD_TEST(suite, out_gml_test_geoms); + PG_ADD_TEST(suite, out_gml_test_geoms_prefix); + PG_ADD_TEST(suite, out_gml_test_geoms_nodims); + PG_ADD_TEST(suite, out_gml2_extent); + PG_ADD_TEST(suite, out_gml3_extent); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_out_kml.c b/mgist-postgis/liblwgeom/cunit/cu_out_kml.c new file mode 100644 index 0000000..d886ddd --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_out_kml.c @@ -0,0 +1,226 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2010 Olivier Courtin + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +static void do_kml_test(char * in, char * out, int precision) +{ + LWGEOM *g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwvarlena_t *v = lwgeom_to_kml2(g, precision, ""); + + ASSERT_VARLENA_EQUAL(v, out); + + lwgeom_free(g); + lwfree(v); +} + + +static void do_kml_unsupported(char * in, char * out) +{ + LWGEOM *g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwvarlena_t *v = lwgeom_to_kml2(g, 0, ""); + + ASSERT_STRING_EQUAL(cu_error_msg, out); + cu_error_msg_reset(); + + lwfree(v); + lwgeom_free(g); +} + + +static void do_kml_test_prefix(char * in, char * out, int precision, const char *prefix) +{ + LWGEOM *g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwvarlena_t *v = lwgeom_to_kml2(g, precision, prefix); + + ASSERT_VARLENA_EQUAL(v, out); + + lwgeom_free(g); + lwfree(v); +} + + +static void out_kml_test_precision(void) +{ + /* 0 precision, i.e a round */ + do_kml_test( + "POINT(1.1111111111111 1.1111111111111)", + "1,1", + 0); + + /* 3 digits precision */ + do_kml_test( + "POINT(1.1111111111111 1.1111111111111)", + "1.111,1.111", + 3); + + /* 9 digits precision */ + do_kml_test( + "POINT(1.2345678901234 1.2345678901234)", + "1.23456789,1.23456789", + 8); + + /* huge data */ + do_kml_test( + "POINT(1E300 -1E300)", + "1e+300,-1e+300", + 0); +} + + +static void out_kml_test_dims(void) +{ + /* 3D */ + do_kml_test( + "POINT(0 1 2)", + "0,1,2", + 0); + + /* 3DM */ + do_kml_test( + "POINTM(0 1 2)", + "0,1", + 0); + + /* 4D */ + do_kml_test( + "POINT(0 1 2 3)", + "0,1,2", + 0); +} + + +static void out_kml_test_geoms(void) +{ + /* Linestring */ + do_kml_test( + "LINESTRING(0 1,2 3,4 5)", + "0,1 2,3 4,5", + 0); + + /* Polygon */ + do_kml_test( + "POLYGON((0 1,2 3,4 5,0 1))", + "0,1 2,3 4,5 0,1", + 0); + + /* Polygon - with internal ring */ + do_kml_test( + "POLYGON((0 1,2 3,4 5,0 1),(6 7,8 9,10 11,6 7))", + "0,1 2,3 4,5 0,16,7 8,9 10,11 6,7", + 0); + + /* MultiPoint */ + do_kml_test( + "MULTIPOINT(0 1,2 3)", + "0,12,3", + 0); + + /* MultiLine */ + do_kml_test( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "0,1 2,3 4,56,7 8,9 10,11", + 0); + + /* MultiPolygon */ + do_kml_test( + "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "0,1 2,3 4,5 0,16,7 8,9 10,11 6,7", + 0); + + /* GeometryCollection */ + do_kml_unsupported( + "GEOMETRYCOLLECTION(POINT(0 1))", + "lwgeom_to_kml2: 'GeometryCollection' geometry type not supported"); + + /* CircularString */ + do_kml_unsupported( + "CIRCULARSTRING(-2 0,0 2,2 0,0 2,2 4)", + "lwgeom_to_kml2: 'CircularString' geometry type not supported"); + + /* CompoundCurve */ + do_kml_unsupported( + "COMPOUNDCURVE(CIRCULARSTRING(0 0,1 1,1 0),(1 0,0 1))", + "lwgeom_to_kml2: 'CompoundCurve' geometry type not supported"); + + /* CurvePolygon */ + do_kml_unsupported( + "CURVEPOLYGON(CIRCULARSTRING(-2 0,-1 -1,0 0,1 -1,2 0,0 2,-2 0),(-1 0,0 0.5,1 0,0 1,-1 0))", + "lwgeom_to_kml2: 'CurvePolygon' geometry type not supported"); + + /* MultiCurve */ + do_kml_unsupported( + "MULTICURVE((5 5,3 5,3 3,0 3),CIRCULARSTRING(0 0,2 1,2 2))", + "lwgeom_to_kml2: 'MultiCurve' geometry type not supported"); + + /* MultiSurface */ + do_kml_unsupported( + "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(-2 0,-1 -1,0 0,1 -1,2 0,0 2,-2 0),(-1 0,0 0.5,1 0,0 1,-1 0)),((7 8,10 10,6 14,4 11,7 8)))", + "lwgeom_to_kml2: 'MultiSurface' geometry type not supported"); +} + +static void out_kml_test_prefix(void) +{ + /* Linestring */ + do_kml_test_prefix( + "LINESTRING(0 1,2 3,4 5)", + "0,1 2,3 4,5", + 0, "kml:"); + + /* Polygon */ + do_kml_test_prefix( + "POLYGON((0 1,2 3,4 5,0 1))", + "0,1 2,3 4,5 0,1", + 0, "kml:"); + + /* Polygon - with internal ring */ + do_kml_test_prefix( + "POLYGON((0 1,2 3,4 5,0 1),(6 7,8 9,10 11,6 7))", + "0,1 2,3 4,5 0,16,7 8,9 10,11 6,7", + 0, "kml:"); + + /* MultiPoint */ + do_kml_test_prefix( + "MULTIPOINT(0 1,2 3)", + "0,12,3", + 0, "kml:"); + + /* MultiLine */ + do_kml_test_prefix( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "0,1 2,3 4,56,7 8,9 10,11", + 0, "kml:"); + + /* MultiPolygon */ + do_kml_test_prefix( + "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "0,1 2,3 4,5 0,16,7 8,9 10,11 6,7", + 0, "kml:"); + +} +/* +** Used by test harness to register the tests in this file. +*/ +void out_kml_suite_setup(void); +void out_kml_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("kml_output", NULL, NULL); + PG_ADD_TEST(suite, out_kml_test_precision); + PG_ADD_TEST(suite, out_kml_test_dims); + PG_ADD_TEST(suite, out_kml_test_geoms); + PG_ADD_TEST(suite, out_kml_test_prefix); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_out_svg.c b/mgist-postgis/liblwgeom/cunit/cu_out_svg.c new file mode 100644 index 0000000..2c2a945 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_out_svg.c @@ -0,0 +1,321 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2010 Olivier Courtin + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +static void do_svg_test(char * in, char * out, int precision, int relative) +{ + LWGEOM *g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwvarlena_t *v = lwgeom_to_svg(g, precision, relative); + + ASSERT_VARLENA_EQUAL(v, out); + + lwgeom_free(g); + lwfree(v); +} + + +static void do_svg_unsupported(char * in, char * out) +{ + LWGEOM *g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwvarlena_t *v = lwgeom_to_svg(g, 0, 0); + + ASSERT_STRING_EQUAL(cu_error_msg, out); + cu_error_msg_reset(); + + lwgeom_free(g); + lwfree(v); +} + + +static void out_svg_test_precision(void) +{ + /* 0 precision, i.e a round - with Circle point */ + do_svg_test( + "POINT(1.1111111111111 1.1111111111111)", + "cx=\"1\" cy=\"-1\"", + 0, 0); + + /* 0 precision, i.e a round - with Point */ + do_svg_test( + "POINT(1.1111111111111 1.1111111111111)", + "x=\"1\" y=\"-1\"", + 0, 1); + + /* 0 precision, i.e a round - with PointArray */ + do_svg_test( + "LINESTRING(1.1111111111111 1.1111111111111,1.1111111111111 1.1111111111111)", + "M 1 -1 L 1 -1", + 0, 0); + + /* 0 precision, i.e a round - with relative PointArray */ + do_svg_test( + "LINESTRING(1.1111111111111 1.1111111111111,1.1111111111111 1.1111111111111)", + "M 1 -1 l 0 0", + 0, 1); + + + /* 9 digits precision - with Circle point */ + do_svg_test( + "POINT(1.2345678901234 1.2345678901234)", + "cx=\"1.23456789\" cy=\"-1.23456789\"", + 9, 0); + + /* 9 digits precision - with Point */ + do_svg_test( + "POINT(1.2345678901234 1.2345678901234)", + "x=\"1.23456789\" y=\"-1.23456789\"", + 9, 1); + + /* 9 digits precision - with PointArray */ + do_svg_test( + "LINESTRING(1.2345678901234 1.2345678901234,2.3456789012345 2.3456789012345)", + "M 1.23456789 -1.23456789 L 2.345678901 -2.345678901", + 9, 0); + + /* 9 digits precision - with relative PointArray */ + do_svg_test( + "LINESTRING(1.2345678901234 1.2345678901234,2.3456789012345 2.3456789012345)", + "M 1.23456789 -1.23456789 l 1.111111011 -1.111111011", + 9, 1); + + + /* huge data - with Circle point */ + do_svg_test( + "POINT(1E300 -1E300)", + "cx=\"1e+300\" cy=\"1e+300\"", + 0, 0); + + /* huge data - with Point */ + do_svg_test( + "POINT(1E300 -1E300)", + "x=\"1e+300\" y=\"1e+300\"", + 0, 1); + + /* huge data - with PointArray */ + do_svg_test( + "LINESTRING(1E300 -1E300,1E301 -1E301)", + "M 1e+300 1e+300 L 1e+301 1e+301", + 0, 0); + + /* huge data - with relative PointArray */ + do_svg_test("LINESTRING(1E300 -1E300,1E301 -1E301)", "M 1e+300 1e+300 l 9e+300 9e+300", 0, 1); +} + + +static void out_svg_test_dims(void) +{ + /* 4D - with Circle point */ + do_svg_test( + "POINT(0 1 2 3)", + "cx=\"0\" cy=\"-1\"", + 0, 0); + + /* 4D - with Point */ + do_svg_test( + "POINT(0 1 2 3)", + "x=\"0\" y=\"-1\"", + 0, 1); + + /* 4D - with PointArray */ + do_svg_test( + "LINESTRING(0 1 2 3,4 5 6 7)", + "M 0 -1 L 4 -5", + 0, 0); + + /* 4D - with relative PointArray */ + do_svg_test( + "LINESTRING(0 1 2 3,4 5 6 7)", + "M 0 -1 l 4 -4", + 0, 1); +} + + +static void out_svg_test_geoms(void) +{ + /* Linestring */ + do_svg_test( + "LINESTRING(0 1,2 3,4 5)", + "M 0 -1 L 2 -3 4 -5", + 0, 0); + + /* Polygon */ + do_svg_test( + "POLYGON((0 1,2 3,4 5,0 1))", + "M 0 -1 L 2 -3 4 -5 Z", + 0, 0); + + /* Polygon - with internal ring */ + do_svg_test( + "POLYGON((0 1,2 3,4 5,0 1),(6 7,8 9,10 11,6 7))", + "M 0 -1 L 2 -3 4 -5 Z M 6 -7 L 8 -9 10 -11 Z", + 0, 0); + + /* MultiPoint */ + do_svg_test( + "MULTIPOINT(0 1,2 3)", + "cx=\"0\" cy=\"-1\",cx=\"2\" cy=\"-3\"", + 0, 0); + + /* MultiLine */ + do_svg_test( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "M 0 -1 L 2 -3 4 -5 M 6 -7 L 8 -9 10 -11", + 0, 0); + + /* MultiPolygon */ + do_svg_test( + "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "M 0 -1 L 2 -3 4 -5 Z M 6 -7 L 8 -9 10 -11 Z", + 0, 0); + + /* GeometryCollection */ + do_svg_test( + "GEOMETRYCOLLECTION(POINT(0 1),LINESTRING(2 3,4 5))", + "cx=\"0\" cy=\"-1\";M 2 -3 L 4 -5", + 0, 0); + + /* CircularString */ + do_svg_test( + "CIRCULARSTRING(-2 0,0 2,2 0,0 2,2 4)", + "M -2 0 A 2 2 0 0 1 2 0 A 2 2 0 0 1 2 -4", + 0, 0); + + /* : Circle */ + do_svg_test( + "CIRCULARSTRING(4 2,-2 2,4 2)", + "M 1 -2 m 3 0 a 3 3 0 1 0 -6 0 a 3 3 0 1 0 6 0", + 0, 0); + + /* CompoundCurve */ + do_svg_test( + "COMPOUNDCURVE(CIRCULARSTRING(0 0,1 1,1 0),(1 0,0 1))", + "M 0 0 A 1 1 0 1 1 1 0 L 0 -1", 0, 0); + + /* MultiCurve */ + do_svg_test( + "MULTICURVE((5 5,3 5,3 3,0 3),CIRCULARSTRING(0 0,2 1,2 2))", + "M 5 -5 L 3 -5 3 -3 0 -3 M 0 0 A 2 2 0 0 0 2 -2", 0, 0); + + /* CurvePolygon */ + do_svg_test( + "CURVEPOLYGON(CIRCULARSTRING(-2 0,-1 -1,0 0,1 -1,2 0,0 2,-2 0),(-1 0,0 0.5,1 0,0 1,-1 0))", + "M -2 0 A 1 1 0 0 0 0 0 A 1 1 0 0 0 2 0 A 2 2 0 0 0 -2 0 Z M -1 0 L 0 0 1 0 0 -1 -1 0 Z", 0, 0); + + /* MultiSurface */ + do_svg_test( + "MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(-2 0,-1 -1,0 0,1 -1,2 0,0 2,-2 0),(-1 0,0 0.5,1 0,0 1,-1 0)),((7 8,10 10,6 14,4 11,7 8)))", + "M -2 0 A 1 1 0 0 0 0 0 A 1 1 0 0 0 2 0 A 2 2 0 0 0 -2 0 Z M -1 0 L 0 0 1 0 0 -1 -1 0 Z M 7 -8 L 10 -10 6 -14 4 -11 Z", 0, 0); + + /* Empty GeometryCollection */ + do_svg_test( + "GEOMETRYCOLLECTION EMPTY", + "", + 0, 0); + + /* Nested GeometryCollection */ + do_svg_unsupported( + "GEOMETRYCOLLECTION(POINT(0 1),GEOMETRYCOLLECTION(LINESTRING(2 3,4 5)))", + "assvg_geom_buf: 'GeometryCollection' geometry type not supported."); + +} + +static void out_svg_test_relative(void) +{ + /* Linestring */ + do_svg_test( + "LINESTRING(0 1,2 3,4 5)", + "M 0 -1 l 2 -2 2 -2", + 0, 1); + + /* Polygon */ + do_svg_test( + "POLYGON((0 1,2 3,4 5,0 1))", + "M 0 -1 l 2 -2 2 -2 z", + 0, 1); + + /* Polygon - with internal ring */ + do_svg_test( + "POLYGON((0 1,2 3,4 5,0 1),(6 7,8 9,10 11,6 7))", + "M 0 -1 l 2 -2 2 -2 z M 6 -7 l 2 -2 2 -2 z", + 0, 1); + + /* MultiPoint */ + do_svg_test( + "MULTIPOINT(0 1,2 3)", + "x=\"0\" y=\"-1\",x=\"2\" y=\"-3\"", + 0, 1); + + /* MultiLine */ + do_svg_test( + "MULTILINESTRING((0 1,2 3,4 5),(6 7,8 9,10 11))", + "M 0 -1 l 2 -2 2 -2 M 6 -7 l 2 -2 2 -2", + 0, 1); + + /* MultiPolygon */ + do_svg_test( + "MULTIPOLYGON(((0 1,2 3,4 5,0 1)),((6 7,8 9,10 11,6 7)))", + "M 0 -1 l 2 -2 2 -2 z M 6 -7 l 2 -2 2 -2 z", + 0, 1); + + /* GeometryCollection */ + do_svg_test( + "GEOMETRYCOLLECTION(POINT(0 1),LINESTRING(2 3,4 5))", + "x=\"0\" y=\"-1\";M 2 -3 l 2 -2", + 0, 1); +} + +static void out_svg_test_srid(void) +{ + /* SRID - with Circle point */ + do_svg_test( + "SRID=4326;POINT(0 1)", + "cx=\"0\" cy=\"-1\"", + 0, 0); + + /* SRID - with Point */ + do_svg_test( + "SRID=4326;POINT(0 1)", + "x=\"0\" y=\"-1\"", + 0, 1); + + /* SRID - with PointArray */ + do_svg_test( + "SRID=4326;LINESTRING(0 1,2 3)", + "M 0 -1 L 2 -3", + 0, 0); + + /* SRID - with relative PointArray */ + do_svg_test( + "SRID=4326;LINESTRING(0 1,2 3)", + "M 0 -1 l 2 -2", + 0, 1); +} + +/* +** Used by test harness to register the tests in this file. +*/ +void out_svg_suite_setup(void); +void out_svg_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("svg_output", NULL, NULL); + PG_ADD_TEST(suite, out_svg_test_precision); + PG_ADD_TEST(suite, out_svg_test_dims); + PG_ADD_TEST(suite, out_svg_test_relative); + PG_ADD_TEST(suite, out_svg_test_geoms); + PG_ADD_TEST(suite, out_svg_test_srid); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_out_twkb.c b/mgist-postgis/liblwgeom/cunit/cu_out_twkb.c new file mode 100644 index 0000000..5c2602e --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_out_twkb.c @@ -0,0 +1,288 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2014 Nicklas Avén + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + + +/* +** Global variable to hold hex TWKB strings +*/ +static char *s; +static char *w; + +/* +** The suite initialization function. +** Create any re-used objects. +*/ +static int init_twkb_out_suite(void) +{ + s = NULL; + w = NULL; + return 0; +} + +/* +** The suite cleanup function. +** Frees any global objects. +*/ +static int clean_twkb_out_suite(void) +{ + if (s) free(s); + if (w) free(w); + s = NULL; + w = NULL; + return 0; +} + + +/* +** Creating an input TWKB from a wkt string +*/ +static void cu_twkb(char *wkt, int8_t prec_xy, int8_t prec_z, int8_t prec_m, uint8_t variant) +{ + LWGEOM *g = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_NONE); + if ( ! g ) lwnotice("input wkt is invalid: %s", wkt); + lwvarlena_t *twkb = lwgeom_to_twkb(g, variant, prec_xy, prec_z, prec_m); + lwgeom_free(g); + if ( s ) free(s); + s = hexbytes_from_bytes((uint8_t *)twkb->data, LWSIZE_GET(twkb->size) - LWVARHDRSZ); + free(twkb); +} + + +/* +** Creating an input TWKB from a wkt string +*/ +static void cu_twkb_idlist(char *wkt, int64_t *idlist, int8_t prec_xy, int8_t prec_z, int8_t prec_m, uint8_t variant) +{ + LWGEOM *g = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_NONE); + LWGEOM *g_b; + if ( ! g ) lwnotice("input wkt is invalid: %s", wkt); + lwvarlena_t *twkb = lwgeom_to_twkb_with_idlist(g, idlist, variant, prec_xy, prec_z, prec_m); + lwgeom_free(g); + if ( s ) free(s); + if ( w ) free(w); + s = hexbytes_from_bytes((uint8_t *)twkb->data, LWSIZE_GET(twkb->size) - LWVARHDRSZ); + g_b = lwgeom_from_twkb((uint8_t *)twkb->data, LWSIZE_GET(twkb->size) - LWVARHDRSZ, LW_PARSER_CHECK_NONE); + w = lwgeom_to_ewkt(g_b); + lwgeom_free(g_b); + free(twkb); +} + + + +static void test_twkb_out_point(void) +{ + + cu_twkb("POINT EMPTY", 0, 0, 0, 0); + CU_ASSERT_STRING_EQUAL(s,"0110"); + + cu_twkb("POINT(0 0)", 0, 0, 0, 0); + CU_ASSERT_STRING_EQUAL(s,"01000000"); + + cu_twkb("POINT(0 0 0 0)", 0, 0, 0, 0); + CU_ASSERT_STRING_EQUAL(s,"01080300000000"); + + /* Point with bounding box */ + cu_twkb("POINT(0 0)", 0, 0, 0, TWKB_BBOX); + CU_ASSERT_STRING_EQUAL(s,"0101000000000000"); + // printf("TWKB: %s\n",s); + + /* Adding a size paramters to X/Y */ + cu_twkb("POINT(0 0)", 0, 0, 0, TWKB_SIZE); + CU_ASSERT_STRING_EQUAL(s,"0102020000"); + + /* Adding a size paramters to X/Y/M */ + cu_twkb("POINTM(0 0 0)", 0, 0, 0, TWKB_SIZE); + CU_ASSERT_STRING_EQUAL(s,"010A0203000000"); + + /* Adding a size paramters to X/Y/Z/M */ + cu_twkb("POINT(0 0 0 0)", 0, 0, 0, TWKB_SIZE); + CU_ASSERT_STRING_EQUAL(s,"010A030400000000"); + + /* Since the third dimension is Z it shall get a precision of 1 decimal (third argument) */ + cu_twkb("POINTZ(1 1 1)", 0,1,2, 0); + CU_ASSERT_STRING_EQUAL(s,"010845020214"); + + /* Since the third dimension is M it shall get a precision of 2 decimals (fourth argument) */ + cu_twkb("POINTM(1 1 1)", 0,1,2, 0); + // printf("\n%s\n", s); + CU_ASSERT_STRING_EQUAL(s,"0108460202C801"); +} + +static void test_twkb_out_linestring(void) +{ + + cu_twkb("LINESTRING(0 0,1 1)", 0, 0, 0, 0); + CU_ASSERT_STRING_EQUAL(s,"02000200000202"); + // printf("TWKB: %s\n",s); + + cu_twkb("LINESTRING(0 0 1,1 1 2,2 2 3)", 0, 0, 0, 0); + CU_ASSERT_STRING_EQUAL(s,"02080103000002020202020202"); + // printf("TWKB: %s\n",s); + + /* Line with bounding box */ + cu_twkb("LINESTRING(0 0,1 1,2 2)", 0, 0, 0, TWKB_BBOX); + CU_ASSERT_STRING_EQUAL(s,"02010004000403000002020202"); + // printf("TWKB: %s\n",s); + + cu_twkb("LINESTRING EMPTY", 0, 0, 0, 0); + CU_ASSERT_STRING_EQUAL(s,"0210"); + // printf("TWKB: %s\n",s); +} + +static void test_twkb_out_polygon(void) +{ + cu_twkb("SRID=4;POLYGON((0 0 0, 0 1 0,1 1 0,1 0 0, 0 0 0))", 0, 0, 0, 0); + CU_ASSERT_STRING_EQUAL(s,"0308010105000000000200020000000100010000"); + // printf("TWKB: %s\n",s); + + cu_twkb("SRID=14;POLYGON((0 0 0 1, 0 1 0 2,1 1 0 3,1 0 0 4, 0 0 0 5))", 0, 0, 0, 0); + CU_ASSERT_STRING_EQUAL(s,"03080301050000000200020002020000020001000201000002"); + // printf("TWKB: %s\n",s); + + cu_twkb("POLYGON EMPTY", 0, 0, 0, 0); + CU_ASSERT_STRING_EQUAL(s,"0310"); + // printf("TWKB: %s\n",s); +} + +static void test_twkb_out_multipoint(void) +{ + cu_twkb("MULTIPOINT(0 0 0, 0 1 0,1 1 0,1 0 0, 0 0 0)", 0, 0, 0, 0); + CU_ASSERT_STRING_EQUAL(s,"04080105000000000200020000000100010000"); + + cu_twkb("MULTIPOINT(0 0 0, 0.26794919243112270647255365849413 1 3)",7 ,7 , 0, 0); + //printf("WKB: %s",s); + CU_ASSERT_STRING_EQUAL(s,"E4081D02000000888BC70280DAC409808ECE1C"); +// printf("TWKB: %s\n",s); +} + +static void test_twkb_out_multilinestring(void) {} + +static void test_twkb_out_multipolygon(void) +{ + cu_twkb("MULTIPOLYGON(((0 0 0, 0 1 0,1 1 0,1 0 0, 0 0 0)),((-1 -1 0,-1 2 0,2 2 0,2 -1 0,-1 -1 0),(0 0 0, 0 1 0,1 1 0,1 0 0, 0 0 0)))", 0, 0, 0, 0); + CU_ASSERT_STRING_EQUAL(s,"060801020105000000000200020000000100010000020501010000060006000000050005000005020200000200020000000100010000"); +} + +static void test_twkb_out_collection(void) +{ + cu_twkb("GEOMETRYCOLLECTION(LINESTRING(1 1, 2 2), LINESTRING(3 3, 4 4), LINESTRING(5 5, 6 6))", 0, 0, 0, 0); + // printf("TWKB: %s\n",s); + CU_ASSERT_STRING_EQUAL(s,"07000302000202020202020002060602020200020A0A0202"); + + cu_twkb("GEOMETRYCOLLECTION(POLYGON((0 0 0, 0 1 0,1 1 0,1 0 0, 0 0 0)),POINT(1 1 1))", 0, 0, 0, 0); + // printf("TWKB: %s\n",s); + CU_ASSERT_STRING_EQUAL(s,"070801020308010105000000000200020000000100010000010801020202"); + + cu_twkb("GEOMETRYCOLLECTION EMPTY", 0, 0, 0, 0); + CU_ASSERT_STRING_EQUAL(s,"0710"); +} + +static void test_twkb_out_idlist(void) +{ + int64_t idlist[2]; + + idlist[0] = 2; + idlist[1] = 4; + cu_twkb_idlist("MULTIPOINT(1 1, 0 0)",idlist, 0, 0, 0, 0); + // printf("TWKB: %s\n",s); + // printf("WKT: %s\n",w); + CU_ASSERT_STRING_EQUAL(s,"040402040802020101"); + CU_ASSERT_STRING_EQUAL(w,"MULTIPOINT(1 1,0 0)"); + + /* + 04 06 multipoint, size/idlist + 07 size 7 bytes + 02 two geometries + 0408 idlist (2, 4) + 0202 first point @ 1,1 + 0101 second point offset -1,-1 + */ + idlist[0] = 2; + idlist[1] = 4; + cu_twkb_idlist("MULTIPOINT(1 1, 0 0)",idlist, 0, 0, 0, TWKB_SIZE); + // printf("TWKB: %s\n",s); + // printf("WKT: %s\n",w); + CU_ASSERT_STRING_EQUAL(s,"04060702040802020101"); + CU_ASSERT_STRING_EQUAL(w,"MULTIPOINT(1 1,0 0)"); + + /* + 04 07 multipoint, bbox/size/idlist + 0B size 11 bytes + 00020002 bbox x(0,1), y(0,1) + 02 two geometries + 0408 idlist (2,4) + 0202 first point @ 1,1 + 0101 seconds point offset -1,-1 + */ + idlist[0] = 2; + idlist[1] = 4; + cu_twkb_idlist("MULTIPOINT(1 1, 0 0)",idlist, 0, 0, 0, TWKB_SIZE | TWKB_BBOX); + // printf("TWKB: %s\n",s); + // printf("WKT: %s\n",w); + CU_ASSERT_STRING_EQUAL(s,"04070B0002000202040802020101"); + CU_ASSERT_STRING_EQUAL(w,"MULTIPOINT(1 1,0 0)"); + + /* + 0704 geometrycollection, idlist + 02 two geometries + 0408 idlist (2,4) + 01000202 first point (type, meta, x, y) + 01000000 second point (type, meta, x, y) + */ + idlist[0] = 2; + idlist[1] = 4; + cu_twkb_idlist("GEOMETRYCOLLECTION(POINT(1 1),POINT(0 0))",idlist, 0, 0, 0, 0); + // printf("TWKB: %s\n",s); + CU_ASSERT_STRING_EQUAL(s,"07040204080100020201000000"); + CU_ASSERT_STRING_EQUAL(w,"GEOMETRYCOLLECTION(POINT(1 1),POINT(0 0))"); + + /* + 0706 geometrycollection, size/idlist + 0D size, 13 bytes + 02 two geometries + 0408 idlist (2,4) + 0102020202 first point (type, meta, size, x, y) + 0102020000 second point (type, meta, size, x, y) + */ + idlist[0] = 2; + idlist[1] = 4; + cu_twkb_idlist("GEOMETRYCOLLECTION(POINT(1 1),POINT(0 0))",idlist, 0, 0, 0, TWKB_SIZE); + // printf("TWKB: %s\n",s); + CU_ASSERT_STRING_EQUAL(s,"07060D02040801020202020102020000"); + CU_ASSERT_STRING_EQUAL(w,"GEOMETRYCOLLECTION(POINT(1 1),POINT(0 0))"); + +} + + +/* +** Used by test harness to register the tests in this file. +*/ +void twkb_out_suite_setup(void); +void twkb_out_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("twkb_output", init_twkb_out_suite, clean_twkb_out_suite); + PG_ADD_TEST(suite, test_twkb_out_point); + PG_ADD_TEST(suite, test_twkb_out_linestring); + PG_ADD_TEST(suite, test_twkb_out_polygon); + PG_ADD_TEST(suite, test_twkb_out_multipoint); + PG_ADD_TEST(suite, test_twkb_out_multilinestring); + PG_ADD_TEST(suite, test_twkb_out_multipolygon); + PG_ADD_TEST(suite, test_twkb_out_collection); + PG_ADD_TEST(suite, test_twkb_out_idlist); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_out_wkb.c b/mgist-postgis/liblwgeom/cunit/cu_out_wkb.c new file mode 100644 index 0000000..236c53a --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_out_wkb.c @@ -0,0 +1,230 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2010 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +/* +** Global variable to hold hex WKB strings +*/ +static char *s; + +/* +** The suite initialization function. +** Create any re-used objects. +*/ +static int init_wkb_out_suite(void) +{ + s = NULL; + return 0; +} + +/* +** The suite cleanup function. +** Frees any global objects. +*/ +static int clean_wkb_out_suite(void) +{ + if (s) free(s); + s = NULL; + return 0; +} + +/* +** Creating an input from a hexwkb +*/ +static void cu_wkb_from_hexwkb(char *hexwkb) +{ + LWGEOM *g = lwgeom_from_hexwkb(hexwkb, LW_PARSER_CHECK_NONE); + if ( s ) free(s); + s = (char *)lwgeom_to_wkb_buffer(g, WKB_HEX | WKB_XDR | WKB_EXTENDED); + lwgeom_free(g); +} + +/* +** Creating an input WKB from a wkb string +*/ +static void cu_wkb(char *wkt) +{ + LWGEOM *g = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_NONE); + if ( s ) free(s); + s = (char *)lwgeom_to_wkb_buffer(g, WKB_HEX | WKB_XDR | WKB_EXTENDED); + lwgeom_free(g); +} + +static void cu_wkb_empty_point_check(char *hex) +{ + LWGEOM *g; + g = lwgeom_from_hexwkb(hex, LW_PARSER_CHECK_NONE); + CU_ASSERT(g != NULL); + CU_ASSERT(lwgeom_is_empty(g)); + CU_ASSERT(g->type == POINTTYPE); + lwgeom_free(g); +} + +static void test_wkb_out_point(void) +{ + cu_wkb("POINT(0 0 0 0)"); + CU_ASSERT_STRING_EQUAL(s,"00C00000010000000000000000000000000000000000000000000000000000000000000000"); + + cu_wkb("SRID=4;POINTM(1 1 1)"); + CU_ASSERT_STRING_EQUAL(s,"0060000001000000043FF00000000000003FF00000000000003FF0000000000000"); + + cu_wkb("POINT EMPTY"); + cu_wkb_empty_point_check(s); + + cu_wkb("SRID=4326;POINT EMPTY"); + cu_wkb_empty_point_check(s); + + cu_wkb("POINT Z EMPTY"); + cu_wkb_empty_point_check(s); + + cu_wkb("POINT M EMPTY"); + cu_wkb_empty_point_check(s); + + cu_wkb("POINT ZM EMPTY"); + cu_wkb_empty_point_check(s); +} + +static void test_wkb_out_linestring(void) +{ + cu_wkb("LINESTRING(0 0,1 1)"); + CU_ASSERT_STRING_EQUAL(s,"000000000200000002000000000000000000000000000000003FF00000000000003FF0000000000000"); + + cu_wkb("LINESTRING(0 0 1,1 1 2,2 2 3)"); + CU_ASSERT_STRING_EQUAL(s,"008000000200000003000000000000000000000000000000003FF00000000000003FF00000000000003FF00000000000004000000000000000400000000000000040000000000000004008000000000000"); + + cu_wkb("LINESTRING EMPTY"); + CU_ASSERT_STRING_EQUAL(s,"000000000200000000"); +} + +static void test_wkb_out_polygon(void) +{ + cu_wkb("SRID=4;POLYGON((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0))"); + CU_ASSERT_STRING_EQUAL(s,"00A000000300000004000000010000000500000000000000000000000000000000000000000000000000000000000000003FF000000000000000000000000000003FF00000000000003FF000000000000000000000000000003FF000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + + cu_wkb("SRID=14;POLYGON((0 0 0 1,0 1 0 2,1 1 0 3,1 0 0 4,0 0 0 5))"); + CU_ASSERT_STRING_EQUAL(s,"00E00000030000000E00000001000000050000000000000000000000000000000000000000000000003FF000000000000000000000000000003FF0000000000000000000000000000040000000000000003FF00000000000003FF0000000000000000000000000000040080000000000003FF00000000000000000000000000000000000000000000040100000000000000000000000000000000000000000000000000000000000004014000000000000"); + + cu_wkb("SRID=4;POLYGON((0 0 0 1,0 1 0 2,1 1 0 3,1 0 0 4,0 0 0 5))"); + CU_ASSERT_STRING_EQUAL(s,"00E00000030000000400000001000000050000000000000000000000000000000000000000000000003FF000000000000000000000000000003FF0000000000000000000000000000040000000000000003FF00000000000003FF0000000000000000000000000000040080000000000003FF00000000000000000000000000000000000000000000040100000000000000000000000000000000000000000000000000000000000004014000000000000"); + + cu_wkb("POLYGON EMPTY"); + CU_ASSERT_STRING_EQUAL(s,"000000000300000000"); + + /* + * POLYGON with EMPTY shell + * See http://http://trac.osgeo.org/postgis/ticket/937 + */ + cu_wkb_from_hexwkb("01030000000100000000000000"); + CU_ASSERT_STRING_EQUAL(s,"000000000300000000"); +} + +static void test_wkb_out_multipoint(void) +{ + cu_wkb("SRID=4;MULTIPOINT(0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)"); + CU_ASSERT_STRING_EQUAL(s,"00A000000400000004000000050080000001000000000000000000000000000000000000000000000000008000000100000000000000003FF0000000000000000000000000000000800000013FF00000000000003FF0000000000000000000000000000000800000013FF0000000000000000000000000000000000000000000000080000001000000000000000000000000000000000000000000000000"); + + cu_wkb("MULTIPOINT(0 0 0, 0.26794919243112270647255365849413 1 3)"); + CU_ASSERT_STRING_EQUAL(s,"008000000400000002008000000100000000000000000000000000000000000000000000000000800000013FD126145E9ECD563FF00000000000004008000000000000"); + + cu_wkb("MULTIPOINT EMPTY"); + CU_ASSERT_STRING_EQUAL(s,"000000000400000000"); +} + +static void test_wkb_out_multilinestring(void) {} + +static void test_wkb_out_multipolygon(void) +{ + cu_wkb("SRID=14;MULTIPOLYGON(((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),((-1 -1 0,-1 2 0,2 2 0,2 -1 0,-1 -1 0),(0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)))"); + CU_ASSERT_STRING_EQUAL(s,"00A00000060000000E000000020080000003000000010000000500000000000000000000000000000000000000000000000000000000000000003FF000000000000000000000000000003FF00000000000003FF000000000000000000000000000003FF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000030000000200000005BFF0000000000000BFF00000000000000000000000000000BFF0000000000000400000000000000000000000000000004000000000000000400000000000000000000000000000004000000000000000BFF00000000000000000000000000000BFF0000000000000BFF000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000003FF000000000000000000000000000003FF00000000000003FF000000000000000000000000000003FF000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + + cu_wkb("MULTIPOLYGON EMPTY"); + CU_ASSERT_STRING_EQUAL(s,"000000000600000000"); +} + +static void test_wkb_out_collection(void) +{ + cu_wkb("SRID=14;GEOMETRYCOLLECTION(POLYGON((0 0 0,0 1 0,1 1 0,1 0 0,0 0 0)),POINT(1 1 1))"); + CU_ASSERT_STRING_EQUAL(s,"00A00000070000000E000000020080000003000000010000000500000000000000000000000000000000000000000000000000000000000000003FF000000000000000000000000000003FF00000000000003FF000000000000000000000000000003FF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000013FF00000000000003FF00000000000003FF0000000000000"); + + cu_wkb("GEOMETRYCOLLECTION EMPTY"); + CU_ASSERT_STRING_EQUAL(s,"000000000700000000"); + + cu_wkb("GEOMETRYCOLLECTION(LINESTRING EMPTY)"); + CU_ASSERT_STRING_EQUAL(s,"000000000700000001000000000200000000"); + + cu_wkb("GEOMETRYCOLLECTION(LINESTRING EMPTY, MULTILINESTRING(EMPTY,EMPTY))"); + // printf("%s\n",s ); + CU_ASSERT_STRING_EQUAL(s,"000000000700000002000000000200000000000000000500000002000000000200000000000000000200000000"); +} + +static void test_wkb_out_circularstring(void) +{ + cu_wkb("CIRCULARSTRING(0 -2,-2 0,0 2,2 0,0 -2)"); + CU_ASSERT_STRING_EQUAL(s,"0000000008000000050000000000000000C000000000000000C000000000000000000000000000000000000000000000004000000000000000400000000000000000000000000000000000000000000000C000000000000000"); + + cu_wkb("CIRCULARSTRING(-5 0 0 4, 0 5 1 3, 5 0 2 2, 10 -5 3 1, 15 0 4 0)"); + CU_ASSERT_STRING_EQUAL(s,"00C000000800000005C014000000000000000000000000000000000000000000004010000000000000000000000000000040140000000000003FF0000000000000400800000000000040140000000000000000000000000000400000000000000040000000000000004024000000000000C01400000000000040080000000000003FF0000000000000402E000000000000000000000000000040100000000000000000000000000000"); + + cu_wkb("SRID=43;CIRCULARSTRING(-5 0 0 4, 0 5 1 3, 5 0 2 2, 10 -5 3 1, 15 0 4 0)"); + CU_ASSERT_STRING_EQUAL(s,"00E00000080000002B00000005C014000000000000000000000000000000000000000000004010000000000000000000000000000040140000000000003FF0000000000000400800000000000040140000000000000000000000000000400000000000000040000000000000004024000000000000C01400000000000040080000000000003FF0000000000000402E000000000000000000000000000040100000000000000000000000000000"); +} + +static void test_wkb_out_compoundcurve(void) +{ + cu_wkb("COMPOUNDCURVE(CIRCULARSTRING(0 0 0, 0.26794919243112270647255365849413 1 3, 0.5857864376269049511983112757903 1.4142135623730950488016887242097 1),(0.5857864376269049511983112757903 1.4142135623730950488016887242097 1,2 0 0,0 0 0))"); + CU_ASSERT_STRING_EQUAL(s,"0080000009000000020080000008000000030000000000000000000000000000000000000000000000003FD126145E9ECD563FF000000000000040080000000000003FE2BEC3330188673FF6A09E667F3BCD3FF00000000000000080000002000000033FE2BEC3330188673FF6A09E667F3BCD3FF0000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); +} + +static void test_wkb_out_curvpolygon(void) +{ + cu_wkb("CURVEPOLYGON(CIRCULARSTRING(-2 0 0 0,-1 -1 1 2,0 0 2 4,1 -1 3 6,2 0 4 8,0 2 2 4,-2 0 0 0),(-1 0 1 2,0 0.5 2 4,1 0 3 6,0 1 3 4,-1 0 1 2))"); + CU_ASSERT_STRING_EQUAL(s,"00C000000A0000000200C000000800000007C000000000000000000000000000000000000000000000000000000000000000BFF0000000000000BFF00000000000003FF0000000000000400000000000000000000000000000000000000000000000400000000000000040100000000000003FF0000000000000BFF00000000000004008000000000000401800000000000040000000000000000000000000000000401000000000000040200000000000000000000000000000400000000000000040000000000000004010000000000000C00000000000000000000000000000000000000000000000000000000000000000C000000200000005BFF000000000000000000000000000003FF0000000000000400000000000000000000000000000003FE0000000000000400000000000000040100000000000003FF000000000000000000000000000004008000000000000401800000000000000000000000000003FF000000000000040080000000000004010000000000000BFF000000000000000000000000000003FF00000000000004000000000000000"); +} + +static void test_wkb_out_multicurve(void) {} + +static void test_wkb_out_multisurface(void) {} + +static void test_wkb_out_polyhedralsurface(void) +{ +// cu_wkb("POLYHEDRALSURFACE(((0 0 0 0,0 0 1 0,0 1 0 2,0 0 0 0)),((0 0 0 0,0 1 0 0,1 0 0 4,0 0 0 0)),((0 0 0 0,1 0 0 0,0 0 1 6,0 0 0 0)),((1 0 0 0,0 1 0 0,0 0 1 0,1 0 0 0)))"); +// CU_ASSERT_STRING_EQUAL(s, t); +// printf("\nnew: %s\nold: %s\n",s,t); +} + +/* +** Used by test harness to register the tests in this file. +*/ +void wkb_out_suite_setup(void); +void wkb_out_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("wkb_output", init_wkb_out_suite, clean_wkb_out_suite); + PG_ADD_TEST(suite, test_wkb_out_point); + PG_ADD_TEST(suite, test_wkb_out_linestring); + PG_ADD_TEST(suite, test_wkb_out_polygon); + PG_ADD_TEST(suite, test_wkb_out_multipoint); + PG_ADD_TEST(suite, test_wkb_out_multilinestring); + PG_ADD_TEST(suite, test_wkb_out_multipolygon); + PG_ADD_TEST(suite, test_wkb_out_collection); + PG_ADD_TEST(suite, test_wkb_out_circularstring); + PG_ADD_TEST(suite, test_wkb_out_compoundcurve); + PG_ADD_TEST(suite, test_wkb_out_curvpolygon); + PG_ADD_TEST(suite, test_wkb_out_multicurve); + PG_ADD_TEST(suite, test_wkb_out_multisurface); + PG_ADD_TEST(suite, test_wkb_out_polyhedralsurface); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_out_wkt.c b/mgist-postgis/liblwgeom/cunit/cu_out_wkt.c new file mode 100644 index 0000000..5dd4bc5 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_out_wkt.c @@ -0,0 +1,235 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2010 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +/* +** Global variable to hold WKT strings +*/ +static char *s = NULL; + +/* +** The suite initialization function. +** Create any re-used objects. +*/ +static int init_wkt_out_suite(void) +{ + s = NULL; + return 0; +} + +/* +** The suite cleanup function. +** Frees any global objects. +*/ +static int clean_wkt_out_suite(void) +{ + if ( s ) free(s); + s = NULL; + return 0; +} + +static char* cu_wkt(char *wkt, uint8_t variant) +{ + LWGEOM *g = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_NONE); + if ( s ) free(s); + if ( ! g ) + { + printf("error converting '%s' to lwgeom\n", wkt); + exit(0); + } + s = lwgeom_to_wkt(g, variant, 8, NULL); + lwgeom_free(g); + return s; +} + +static void test_wkt_out_point(void) +{ + CU_ASSERT_STRING_EQUAL(cu_wkt("POINT(0.1111 0.1111 0.1111 0)",WKT_ISO), "POINT ZM (0.1111 0.1111 0.1111 0)"); + CU_ASSERT_STRING_EQUAL(cu_wkt("POINT(0 0 0 0)",WKT_EXTENDED), "POINT(0 0 0 0)"); + CU_ASSERT_STRING_EQUAL(cu_wkt("POINT(0 0 0 0)",WKT_SFSQL), "POINT(0 0)"); + + CU_ASSERT_STRING_EQUAL(cu_wkt("POINTM(0 0 0)",WKT_ISO), "POINT M (0 0 0)"); + CU_ASSERT_STRING_EQUAL(cu_wkt("POINTM(0 0 0)",WKT_EXTENDED), "POINTM(0 0 0)"); + CU_ASSERT_STRING_EQUAL(cu_wkt("POINTM(0 0 0)",WKT_SFSQL), "POINT(0 0)"); + + CU_ASSERT_STRING_EQUAL(cu_wkt("POINT(100 100)",WKT_ISO), "POINT(100 100)"); + CU_ASSERT_STRING_EQUAL(cu_wkt("POINT(100 100)",WKT_EXTENDED), "POINT(100 100)"); + CU_ASSERT_STRING_EQUAL(cu_wkt("POINT(100 100)",WKT_SFSQL), "POINT(100 100)"); + + CU_ASSERT_STRING_EQUAL(cu_wkt("POINT(100.1 100 12 12)",WKT_ISO), "POINT ZM (100.1 100 12 12)"); + CU_ASSERT_STRING_EQUAL(cu_wkt("POINT(100.1 100 12 12)",WKT_EXTENDED), "POINT(100.1 100 12 12)"); + CU_ASSERT_STRING_EQUAL(cu_wkt("POINT(100.1 100 12 12)",WKT_SFSQL), "POINT(100.1 100)"); + + CU_ASSERT_STRING_EQUAL(cu_wkt("SRID=100;POINT(100.1 100 12 12)",WKT_SFSQL), "POINT(100.1 100)"); + CU_ASSERT_STRING_EQUAL(cu_wkt("SRID=100;POINT(100.1 100 12 12)",WKT_EXTENDED), "SRID=100;POINT(100.1 100 12 12)"); + + /* Test big numbers */ + ASSERT_STRING_EQUAL(cu_wkt("POINT(-123456789012345.12345678 -1234567890123458.12345678)", WKT_ISO), + "POINT(-123456789012345.12 -1.23456789e+15)"); + ASSERT_STRING_EQUAL( + cu_wkt( + "POINT( " + "9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 " + "0.000000000000000000000000000001)", + WKT_ISO), + "POINT(1e+100 1e-30)"); +} + +static void test_wkt_out_linestring(void) +{ + CU_ASSERT_STRING_EQUAL(cu_wkt("LINESTRING(1 2 3 4,5 6 7 8)",WKT_ISO), "LINESTRING ZM (1 2 3 4,5 6 7 8)"); + CU_ASSERT_STRING_EQUAL(cu_wkt("LINESTRING(1 2 3,5 6 7)",WKT_ISO), "LINESTRING Z (1 2 3,5 6 7)"); + CU_ASSERT_STRING_EQUAL(cu_wkt("LINESTRINGM(1 2 3,5 6 7)",WKT_ISO), "LINESTRING M (1 2 3,5 6 7)"); +} + +static void test_wkt_out_polygon(void) +{ + CU_ASSERT_STRING_EQUAL( + cu_wkt("POLYGON((100 100 2, 100 200 2, 200 200 2, 200 100 2, 100 100 2))",WKT_ISO), + "POLYGON Z ((100 100 2,100 200 2,200 200 2,200 100 2,100 100 2))" + ); + CU_ASSERT_STRING_EQUAL( + cu_wkt("POLYGON((100 100 2, 100 200 2, 200 200 2, 200 100 2, 100 100 2))",WKT_EXTENDED), + "POLYGON((100 100 2,100 200 2,200 200 2,200 100 2,100 100 2))" + ); +} +static void test_wkt_out_multipoint(void) +{ + CU_ASSERT_STRING_EQUAL(cu_wkt("MULTIPOINT(1 2 3 4,5 6 7 8)",WKT_ISO), "MULTIPOINT ZM ((1 2 3 4),(5 6 7 8))"); + CU_ASSERT_STRING_EQUAL(cu_wkt("MULTIPOINT(1 2 3,5 6 7)",WKT_ISO), "MULTIPOINT Z ((1 2 3),(5 6 7))"); + CU_ASSERT_STRING_EQUAL(cu_wkt("MULTIPOINTM(1 2 3,5 6 7)",WKT_ISO), "MULTIPOINT M ((1 2 3),(5 6 7))"); + +} + +static void test_wkt_out_multilinestring(void) +{ + CU_ASSERT_STRING_EQUAL( + cu_wkt("MULTILINESTRING((1 2 3 4,5 6 7 8))",WKT_ISO), + "MULTILINESTRING ZM ((1 2 3 4,5 6 7 8))" + ); + CU_ASSERT_STRING_EQUAL( + cu_wkt("MULTILINESTRING((1 2 3,5 6 7))",WKT_ISO), + "MULTILINESTRING Z ((1 2 3,5 6 7))" + ); + CU_ASSERT_STRING_EQUAL( + cu_wkt("MULTILINESTRINGM((1 2 3,5 6 7))",WKT_ISO), + "MULTILINESTRING M ((1 2 3,5 6 7))" + ); +} + +static void test_wkt_out_multipolygon(void) +{ + CU_ASSERT_STRING_EQUAL( + cu_wkt("MULTIPOLYGON(((100 100 2, 100 200 2, 200 200 2, 200 100 2, 100 100 2)))",WKT_ISO), + "MULTIPOLYGON Z (((100 100 2,100 200 2,200 200 2,200 100 2,100 100 2)))" + ); + CU_ASSERT_STRING_EQUAL( + cu_wkt("MULTIPOLYGON(((100 100 2, 100 200 2, 200 200 2, 200 100 2, 100 100 2)))",WKT_EXTENDED), + "MULTIPOLYGON(((100 100 2,100 200 2,200 200 2,200 100 2,100 100 2)))" + ); +} + +static void test_wkt_out_collection(void) +{ + //printf("%s\n",cu_wkt("GEOMETRYCOLLECTION(MULTIPOLYGON(((100 100 2, 100 200 2, 200 200 2, 200 100 2, 100 100 2))),MULTIPOINT(.5 .5 .5,1 1 1),CURVEPOLYGON((.8 .8 .8,.8 .8 .8,.8 .8 .8)))",WKT_ISO)); + CU_ASSERT_STRING_EQUAL( + cu_wkt("GEOMETRYCOLLECTION(POLYGON((100 100 2, 100 200 2, 200 200 2, 200 100 2, 100 100 2)),POINT(.5 .5 .5),CIRCULARSTRING(.8 .8 .8,.8 .8 .8,.8 .8 .8))",WKT_ISO), + "GEOMETRYCOLLECTION Z (POLYGON Z ((100 100 2,100 200 2,200 200 2,200 100 2,100 100 2)),POINT Z (0.5 0.5 0.5),CIRCULARSTRING Z (0.8 0.8 0.8,0.8 0.8 0.8,0.8 0.8 0.8))" + ); + CU_ASSERT_STRING_EQUAL( + cu_wkt("GEOMETRYCOLLECTION(MULTIPOLYGON(((100 100 2, 100 200 2, 200 200 2, 200 100 2, 100 100 2))),MULTIPOINT(.5 .5 .5,1 1 1),CURVEPOLYGON((.8 .8 .8,.8 .8 .8,.8 .8 .8)))",WKT_ISO), + "GEOMETRYCOLLECTION Z (MULTIPOLYGON Z (((100 100 2,100 200 2,200 200 2,200 100 2,100 100 2))),MULTIPOINT Z ((0.5 0.5 0.5),(1 1 1)),CURVEPOLYGON Z ((0.8 0.8 0.8,0.8 0.8 0.8,0.8 0.8 0.8)))" + ); + + /* See http://trac.osgeo.org/postgis/ticket/724 */ + CU_ASSERT_STRING_EQUAL( + cu_wkt("GEOMETRYCOLLECTIONM(MULTIPOINTM(0 0 0), POINTM(1 1 1))", WKT_EXTENDED), + "GEOMETRYCOLLECTIONM(MULTIPOINTM(0 0 0),POINTM(1 1 1))" + ); +} + +static void test_wkt_out_circularstring(void) +{ + CU_ASSERT_STRING_EQUAL( + cu_wkt("CIRCULARSTRING(1 2 3 4,4 5 6 7,7 8 9 0)",WKT_ISO), + "CIRCULARSTRING ZM (1 2 3 4,4 5 6 7,7 8 9 0)" + ); + CU_ASSERT_STRING_EQUAL( + cu_wkt("CIRCULARSTRING(1 2 3 4,4 5 6 7,7 8 9 0)",WKT_EXTENDED), + "CIRCULARSTRING(1 2 3 4,4 5 6 7,7 8 9 0)" + ); + //printf("%s\n",cu_wkt("GEOMETRYCOLLECTION(MULTIPOLYGON(((100 100 2, 100 200 2, 200 200 2, 200 100 2, 100 100 2))),MULTIPOINT(.5 .5 .5,1 1 1),CURVEPOLYGON((.8 .8 .8,.8 .8 .8,.8 .8 .8)))",WKT_ISO)); +} + +static void test_wkt_out_compoundcurve(void) +{ + CU_ASSERT_STRING_EQUAL( + cu_wkt("COMPOUNDCURVE((1 2 3 4,4 5 6 7,7 8 9 0),CIRCULARSTRING(7 8 9 0,4 3 2 1,1 2 3 4,4 5 6 7,7 8 9 0))",WKT_ISO), + "COMPOUNDCURVE ZM ((1 2 3 4,4 5 6 7,7 8 9 0),CIRCULARSTRING ZM (7 8 9 0,4 3 2 1,1 2 3 4,4 5 6 7,7 8 9 0))" + ); +} + +static void test_wkt_out_curvpolygon(void) +{ + CU_ASSERT_STRING_EQUAL( + cu_wkt("CURVEPOLYGON((1 2 3 4,4 5 6 7,7 8 9 0),CIRCULARSTRING(7 8 9 0,1 2 1 1,1 2 3 4,4 5 6 7,7 8 9 0))",WKT_ISO), + "CURVEPOLYGON ZM ((1 2 3 4,4 5 6 7,7 8 9 0),CIRCULARSTRING ZM (7 8 9 0,1 2 1 1,1 2 3 4,4 5 6 7,7 8 9 0))" + ); +} + +static void test_wkt_out_multicurve(void) +{ + CU_ASSERT_STRING_EQUAL( + cu_wkt("MULTICURVE((1 2 3 4,4 5 6 7,7 8 9 0),CIRCULARSTRING(1 2 3 4,4 5 6 7,7 8 9 0))",WKT_ISO), + "MULTICURVE ZM ((1 2 3 4,4 5 6 7,7 8 9 0),CIRCULARSTRING ZM (1 2 3 4,4 5 6 7,7 8 9 0))" + ); + CU_ASSERT_STRING_EQUAL( + cu_wkt("MULTICURVE(COMPOUNDCURVE((1 2 3 4,4 5 6 7,7 8 9 0),CIRCULARSTRING(7 8 9 0,8 9 0 0,1 2 3 4,4 5 6 7,7 8 9 0)),(1 2 3 4,4 5 6 7,7 8 9 0),CIRCULARSTRING(1 2 3 4,4 5 6 7,7 8 9 0))",WKT_ISO), + "MULTICURVE ZM (COMPOUNDCURVE ZM ((1 2 3 4,4 5 6 7,7 8 9 0),CIRCULARSTRING ZM (7 8 9 0,8 9 0 0,1 2 3 4,4 5 6 7,7 8 9 0)),(1 2 3 4,4 5 6 7,7 8 9 0),CIRCULARSTRING ZM (1 2 3 4,4 5 6 7,7 8 9 0))" + ); +} + +static void test_wkt_out_multisurface(void) +{ + CU_ASSERT_STRING_EQUAL( + cu_wkt("MULTISURFACE(((1 2 3 4,4 5 6 7,7 8 9 0)),CURVEPOLYGON((1 2 3 4,4 5 6 7,7 8 9 0)))",WKT_ISO), + "MULTISURFACE ZM (((1 2 3 4,4 5 6 7,7 8 9 0)),CURVEPOLYGON ZM ((1 2 3 4,4 5 6 7,7 8 9 0)))" + ); + +} + +/* +** Used by test harness to register the tests in this file. +*/ +void wkt_out_suite_setup(void); +void wkt_out_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("wkt_output", init_wkt_out_suite, clean_wkt_out_suite); + PG_ADD_TEST(suite, test_wkt_out_point); + PG_ADD_TEST(suite, test_wkt_out_linestring); + PG_ADD_TEST(suite, test_wkt_out_polygon); + PG_ADD_TEST(suite, test_wkt_out_multipoint); + PG_ADD_TEST(suite, test_wkt_out_multilinestring); + PG_ADD_TEST(suite, test_wkt_out_multipolygon); + PG_ADD_TEST(suite, test_wkt_out_collection); + PG_ADD_TEST(suite, test_wkt_out_circularstring); + PG_ADD_TEST(suite, test_wkt_out_compoundcurve); + PG_ADD_TEST(suite, test_wkt_out_curvpolygon); + PG_ADD_TEST(suite, test_wkt_out_multicurve); + PG_ADD_TEST(suite, test_wkt_out_multisurface); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_out_x3d.c b/mgist-postgis/liblwgeom/cunit/cu_out_x3d.c new file mode 100644 index 0000000..8a0ffa1 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_out_x3d.c @@ -0,0 +1,175 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2011-2016 Regina Obe + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +static void do_x3d3_test(char * in, char * out, int precision, int option) +{ + LWGEOM *g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwvarlena_t *v = lwgeom_to_x3d3(g, precision, option, ""); + + ASSERT_VARLENA_EQUAL(v, out); + + lwgeom_free(g); + lwfree(v); +} + + +static void do_x3d3_unsupported(char * in, char * out) +{ + LWGEOM *g = lwgeom_from_wkt(in, LW_PARSER_CHECK_NONE); + lwvarlena_t *v = lwgeom_to_x3d3(g, 0, 0, ""); + + ASSERT_STRING_EQUAL(out, cu_error_msg); + cu_error_msg_reset(); + + lwfree(v); + lwgeom_free(g); +} + + +static void out_x3d3_test_precision(void) +{ + /* 0 precision, i.e a round */ + do_x3d3_test( + "POINT(1.1111111111111 1.1111111111111 2.11111111111111)", + "1 1 2", + 0, 0); + + /* 3 digits precision */ + do_x3d3_test( + "POINT(1.1111111111111 1.1111111111111 2.11111111111111)", + "1.111 1.111 2.111", + 3, 0); + + /* 9 digits precision */ + do_x3d3_test( + "POINT(1.2345678901234 1.2345678901234 4.123456789001)", + "1.23456789 1.23456789 4.123456789", + 9, 0); + + /* huge data */ + do_x3d3_test("POINT(1E300 -105E-153 4E300)", "1e+300 -1e-151 4e+300", 0, 0); +} + + +static void out_x3d3_test_geoms(void) +{ + /* Linestring */ + do_x3d3_test( + "LINESTRING(0 1 5,2 3 6,4 5 7)", + "", + 0, 0); + + /* Polygon **/ + do_x3d3_test( + "POLYGON((15 10 3,13.536 6.464 3,10 5 3,6.464 6.464 3,5 10 3,6.464 13.536 3,10 15 3,13.536 13.536 3,15 10 3))", + "", + 3, 0); + + /* TODO: Polygon - with internal ring - the answer is clearly wrong */ + /** do_x3d3_test( + "POLYGON((0 1 3,2 3 3,4 5 3,0 1 3),(6 7 3,8 9 3,10 11 3,6 7 3))", + "", + NULL, 0); **/ + + /* 2D MultiPoint */ + do_x3d3_test( + "MULTIPOINT(0 1,2 3,4 5)", + "", + 0, 0); + + /* 3D MultiPoint */ + do_x3d3_test( + "MULTIPOINT Z(0 1 1,2 3 4,4 5 5)", + "", + 0, 0); + /* 3D Multiline */ + do_x3d3_test( + "MULTILINESTRING Z((0 1 1,2 3 4,4 5 5),(6 7 5,8 9 8,10 11 5))", + "", + 0, 0); + + /* MultiPolygon */ + do_x3d3_test( + "MULTIPOLYGON(((0 1 1,2 3 1,4 5 1,0 1 1)),((6 7 1,8 9 1,10 11 1,6 7 1)))", + "", + 0, 0); + + /* PolyhedralSurface */ + do_x3d3_test( + "POLYHEDRALSURFACE( ((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)), ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)), ((1 1 0, 1 1 1, 1 0 1, 1 0 0, 1 1 0)), ((0 1 0, 0 1 1, 1 1 1, 1 1 0, 0 1 0)), ((0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1)) )", + "", + 0, 0); + + /* TODO: returns garbage at moment correctly implement GeometryCollection -- */ + /** do_x3d3_test( + "GEOMETRYCOLLECTION(POINT(0 1 3),LINESTRING(2 3 3,4 5 3))", + "", + NULL, 0); **/ + + /* TODO: Implement Empty GeometryCollection correctly or throw a not-implemented */ + /** do_x3d3_test( + "GEOMETRYCOLLECTION EMPTY", + "", + NULL, 0); **/ + + /* CircularString */ + do_x3d3_unsupported( + "CIRCULARSTRING(-2 0 1,0 2 1,2 0 1,0 2 1,2 4 1)", + "lwgeom_to_x3d3: 'CircularString' geometry type not supported"); + + /* CompoundCurve */ + do_x3d3_unsupported( + "COMPOUNDCURVE(CIRCULARSTRING(0 0 1,1 1 1,1 0 1),(1 0 1,0 1 1))", + "lwgeom_to_x3d3: 'CompoundCurve' geometry type not supported"); + +} + +static void out_x3d3_test_option(void) +{ + /* 0 precision, flip coordinates*/ + do_x3d3_test( + "POINT(3.1111111111111 1.1111111111111 2.11111111111111)", + "1 3 2", + 0, 1); + + /* geocoordinate long,lat*/ + do_x3d3_test( + "SRID=4326;POLYGON((15 10 3,13.536 6.464 3,10 5 3,6.464 6.464 3,5 10 3,6.464 13.536 3,10 15 3,13.536 13.536 3,15 10 3))", + "", + 3, 2); + + /* geocoordinate lat long*/ + do_x3d3_test( + "SRID=4326;POLYGON((15 10 3,13.536 6.464 3,10 5 3,6.464 6.464 3,5 10 3,6.464 13.536 3,10 15 3,13.536 13.536 3,15 10 3))", + "", + 3, 3); +} + + +/* +** Used by test harness to register the tests in this file. +*/ +void out_x3d_suite_setup(void); +void out_x3d_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("x3d_output", NULL, NULL); + PG_ADD_TEST(suite, out_x3d3_test_precision); + PG_ADD_TEST(suite, out_x3d3_test_geoms); + PG_ADD_TEST(suite, out_x3d3_test_option); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_print.c b/mgist-postgis/liblwgeom/cunit/cu_print.c new file mode 100644 index 0000000..7274b61 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_print.c @@ -0,0 +1,579 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2008 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +static void +test_lwpoint_to_latlon_assert_format(char *point_wkt, const char *format, const char *expected) +{ + LWPOINT * test_point = (LWPOINT*)lwgeom_from_wkt(point_wkt, LW_PARSER_CHECK_NONE); + int num_old_failures, num_new_failures; + char * actual; + cu_error_msg_reset(); + actual = lwpoint_to_latlon(test_point, format); + if (0 != strlen(cu_error_msg)) + { + printf("\nAssert failed:\n\tFormat [%s] generated an error: %s\n", format, cu_error_msg); + CU_FAIL(); + } + num_old_failures = CU_get_number_of_failures(); + CU_ASSERT_STRING_EQUAL(actual, expected); + num_new_failures = CU_get_number_of_failures(); + if (num_new_failures > num_old_failures) + { + printf("\nAssert failed:\n\t%s\t(actual)\n\t%s\t(expected)\n", actual, expected); + } + lwfree(actual); + lwpoint_free(test_point); +} +static void +test_lwpoint_to_latlon_assert_error(char *point_wkt, const char *format) +{ + LWPOINT * test_point = (LWPOINT*)lwgeom_from_wkt(point_wkt, LW_PARSER_CHECK_NONE); + cu_error_msg_reset(); + char* tmp = lwpoint_to_latlon(test_point, format); + lwfree(tmp); + if (0 == strlen(cu_error_msg)) + { + printf("\nAssert failed:\n\tFormat [%s] did not generate an error.\n", format); + CU_FAIL(); + } + else + { + cu_error_msg_reset(); + } + lwpoint_free(test_point); +} + +/* +** Test points around the globe using the default format. Null and empty string both mean use the default. +*/ +static void +test_lwpoint_to_latlon_default_format(void) +{ + test_lwpoint_to_latlon_assert_format("POINT(0 0)", + NULL, + "0\xC2\xB0" + "0'0.000\"N 0\xC2\xB0" + "0'0.000\"E"); + test_lwpoint_to_latlon_assert_format("POINT(45.4545 12.34567)", + "", + "12\xC2\xB0" + "20'44.412\"N 45\xC2\xB0" + "27'16.200\"E"); + test_lwpoint_to_latlon_assert_format("POINT(180 90)", + NULL, + "90\xC2\xB0" + "0'0.000\"N 180\xC2\xB0" + "0'0.000\"E"); + test_lwpoint_to_latlon_assert_format("POINT(181 91)", + "", + "89\xC2\xB0" + "0'0.000\"N 1\xC2\xB0" + "0'0.000\"E"); + test_lwpoint_to_latlon_assert_format("POINT(180.0001 90.0001)", + NULL, + "89\xC2\xB0" + "59'59.640\"N 0\xC2\xB0" + "0'0.360\"E"); + test_lwpoint_to_latlon_assert_format("POINT(45.4545 -12.34567)", + "", + "12\xC2\xB0" + "20'44.412\"S 45\xC2\xB0" + "27'16.200\"E"); + test_lwpoint_to_latlon_assert_format("POINT(180 -90)", + NULL, + "90\xC2\xB0" + "0'0.000\"S 180\xC2\xB0" + "0'0.000\"E"); + test_lwpoint_to_latlon_assert_format("POINT(181 -91)", + "", + "89\xC2\xB0" + "0'0.000\"S 1\xC2\xB0" + "0'0.000\"E"); + test_lwpoint_to_latlon_assert_format("POINT(180.0001 -90.0001)", + NULL, + "89\xC2\xB0" + "59'59.640\"S 0\xC2\xB0" + "0'0.360\"E"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 12.34567)", + "", + "12\xC2\xB0" + "20'44.412\"N 45\xC2\xB0" + "27'16.200\"W"); + test_lwpoint_to_latlon_assert_format("POINT(-180 90)", + NULL, + "90\xC2\xB0" + "0'0.000\"N 180\xC2\xB0" + "0'0.000\"W"); + test_lwpoint_to_latlon_assert_format("POINT(-181 91)", + "", + "89\xC2\xB0" + "0'0.000\"N 1\xC2\xB0" + "0'0.000\"W"); + test_lwpoint_to_latlon_assert_format("POINT(-180.0001 90.0001)", + NULL, + "89\xC2\xB0" + "59'59.640\"N 0\xC2\xB0" + "0'0.360\"W"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", + "", + "12\xC2\xB0" + "20'44.412\"S 45\xC2\xB0" + "27'16.200\"W"); + test_lwpoint_to_latlon_assert_format("POINT(-180 -90)", + NULL, + "90\xC2\xB0" + "0'0.000\"S 180\xC2\xB0" + "0'0.000\"W"); + test_lwpoint_to_latlon_assert_format("POINT(-181 -91)", + "", + "89\xC2\xB0" + "0'0.000\"S 1\xC2\xB0" + "0'0.000\"W"); + test_lwpoint_to_latlon_assert_format("POINT(-180.0001 -90.0001)", + NULL, + "89\xC2\xB0" + "59'59.640\"S 0\xC2\xB0" + "0'0.360\"W"); + test_lwpoint_to_latlon_assert_format("POINT(-2348982391.123456 -238749827.34879)", + "", + "12\xC2\xB0" + "39'4.356\"N 31\xC2\xB0" + "7'24.442\"W"); + /* See https://trac.osgeo.org/postgis/ticket/3688 */ + test_lwpoint_to_latlon_assert_format("POINT (76.6 -76.6)", + NULL, + "76\xC2\xB0" + "36'0.000\"S 76\xC2\xB0" + "36'0.000\"E"); +} + +/* + * Test all possible combinations of the orders of the parameters. + */ +static void +test_lwpoint_to_latlon_format_orders(void) +{ + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "C DD MM SS", "S 12 20 44 W 45 27 16"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "C DD SS MM", "S 12 44 20 W 45 16 27"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "C MM DD SS", "S 20 12 44 W 27 45 16"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "C MM SS DD", "S 20 44 12 W 27 16 45"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "C SS DD MM", "S 44 12 20 W 16 45 27"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "C SS MM DD", "S 44 20 12 W 16 27 45"); + + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "DD C MM SS", "12 S 20 44 45 W 27 16"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "DD C SS MM", "12 S 44 20 45 W 16 27"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "MM C DD SS", "20 S 12 44 27 W 45 16"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "MM C SS DD", "20 S 44 12 27 W 16 45"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "SS C DD MM", "44 S 12 20 16 W 45 27"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "SS C MM DD", "44 S 20 12 16 W 27 45"); + + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "DD MM C SS", "12 20 S 44 45 27 W 16"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "DD SS C MM", "12 44 S 20 45 16 W 27"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "MM DD C SS", "20 12 S 44 27 45 W 16"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "MM SS C DD", "20 44 S 12 27 16 W 45"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "SS DD C MM", "44 12 S 20 16 45 W 27"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "SS MM C DD", "44 20 S 12 16 27 W 45"); + + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "DD MM SS C", "12 20 44 S 45 27 16 W"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "DD SS MM C", "12 44 20 S 45 16 27 W"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "MM DD SS C", "20 12 44 S 27 45 16 W"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "MM SS DD C", "20 44 12 S 27 16 45 W"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "SS DD MM C", "44 12 20 S 16 45 27 W"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "SS MM DD C", "44 20 12 S 16 27 45 W"); +} + +/* + * Test with and without the optional parameters. + */ +static void +test_lwpoint_to_latlon_optional_format(void) +{ + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "DD.DDD", "-12.346 -45.455"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "DD.DDD C", "12.346 S 45.455 W"); + test_lwpoint_to_latlon_assert_format( + "POINT(-45.4545 -12.34567)", "DD.DDD MM.MMM", "-12.000 20.740 -45.000 27.270"); + test_lwpoint_to_latlon_assert_format( + "POINT(-45.4545 -12.34567)", "DD.DDD MM.MMM C", "12.000 20.740 S 45.000 27.270 W"); + test_lwpoint_to_latlon_assert_format( + "POINT(-45.4545 -12.34567)", "DD.DDD MM.MMM SS.SSS", "-12.000 20.000 44.412 -45.000 27.000 16.200"); + test_lwpoint_to_latlon_assert_format( + "POINT(-45.4545 -12.34567)", "DD.DDD MM.MMM SS.SSS C", "12.000 20.000 44.412 S 45.000 27.000 16.200 W"); +} + +static void +test_lwpoint_to_latlon_oddball_formats(void) +{ + test_lwpoint_to_latlon_assert_format( + "POINT(-45.4545 -12.34567)", "DD.DDDMM.MMMSS.SSSC", "12.00020.00044.412S 45.00027.00016.200W"); + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "DDMM.MMM", "-1220.740 -4527.270"); + /* "##." will be printed as "##" */ + test_lwpoint_to_latlon_assert_format("POINT(-45.4545 -12.34567)", "DD.MM.MMM", "-1220.740 -4527.270"); +} + +/* + * Test using formats that should produce errors. + */ +static void +test_lwpoint_to_latlon_bad_formats(void) +{ + test_lwpoint_to_latlon_assert_error("POINT(1.23456 7.89012)", "DD.DDD SS.SSS"); + test_lwpoint_to_latlon_assert_error("POINT(1.23456 7.89012)", "MM.MMM SS.SSS"); + test_lwpoint_to_latlon_assert_error("POINT(1.23456 7.89012)", "DD.DDD SS.SSS DD"); + test_lwpoint_to_latlon_assert_error("POINT(1.23456 7.89012)", "DD MM SS MM"); + test_lwpoint_to_latlon_assert_error("POINT(1.23456 7.89012)", "DD MM SS SS"); + test_lwpoint_to_latlon_assert_error("POINT(1.23456 7.89012)", "C DD.DDD C"); + test_lwpoint_to_latlon_assert_error("POINT(1.23456 7.89012)", + "C \xC2" + "DD.DDD"); + test_lwpoint_to_latlon_assert_error("POINT(1.23456 7.89012)", "C DD.DDD \xC2"); + test_lwpoint_to_latlon_assert_error("POINT(1.23456 7.89012)", + "C DD\x80" + "MM "); + test_lwpoint_to_latlon_assert_error("POINT(1.23456 7.89012)", + "C DD \xFF" + "MM"); + test_lwpoint_to_latlon_assert_error("POINT(1.23456 7.89012)", + "C DD \xB0" + "MM"); + test_lwpoint_to_latlon_assert_error( + "POINT(1.23456 7.89012)", + "DD.DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"); + test_lwpoint_to_latlon_assert_error( + "POINT(1.23456 7.89012)", + "DD.DDD jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj"); +} + +char result[OUT_DOUBLE_BUFFER_SIZE] = {0}; + +#define assert_lwprint_equal(d, precision, expected) \ + lwprint_double((d), (precision), result); \ + ASSERT_STRING_EQUAL(result, (expected)); + +static void +test_lwprint(void) +{ + static const int precision_start = -1; /* Check for negatives */ + static const int precision_end = OUT_MAX_DIGITS + 2; /* Ask for more digits than available in all cases */ + + /* Negative zero should be printed as 0 */ + for (int i = precision_start; i < precision_end; i++) + assert_lwprint_equal(-0, i, "0"); + + /* 2 = 0x4000000000000000 + * -2 = 0xC000000000000000 + * Both with exact representation, so we should never see extra digits + */ + for (int i = precision_start; i < precision_end; i++) + { + assert_lwprint_equal(2.0, i, "2"); + assert_lwprint_equal(-2.0, i, "-2"); + } + + /* 0.3 doesn't have an exact representation but it's the shortest representation for 0x3FD3333333333333 + * 2.99999999999999988897769753748E-1 = 0x3FD3333333333333 + */ + assert_lwprint_equal(0.3, 0, "0"); + for (int i = 1; i < precision_end; i++) + { + assert_lwprint_equal(0.3, i, "0.3"); + assert_lwprint_equal(2.99999999999999988897769753748E-1, i, "0.3"); + } + + /* 2.5 has an exact representation (0x4004000000000000) */ + assert_lwprint_equal(2.5, 0, "2"); + for (int i = 1; i < precision_end; i++) + { + assert_lwprint_equal(2.5, i, "2.5"); + } + + /* Test trailing zeros and rounding + * 0.0000000298023223876953125 == 0x3E60000000000000 == 2.98023223876953125E-8 + */ + assert_lwprint_equal(0.0000000298023223876953125, -1, "0"); + assert_lwprint_equal(0.0000000298023223876953125, 0, "0"); + assert_lwprint_equal(0.0000000298023223876953125, 1, "0"); + assert_lwprint_equal(0.0000000298023223876953125, 2, "0"); + assert_lwprint_equal(0.0000000298023223876953125, 3, "0"); + assert_lwprint_equal(0.0000000298023223876953125, 4, "0"); + assert_lwprint_equal(0.0000000298023223876953125, 5, "0"); + assert_lwprint_equal(0.0000000298023223876953125, 6, "0"); + assert_lwprint_equal(0.0000000298023223876953125, 7, "0"); + assert_lwprint_equal(0.0000000298023223876953125, 8, "0.00000003"); + assert_lwprint_equal(0.0000000298023223876953125, 9, "0.00000003"); + assert_lwprint_equal(0.0000000298023223876953125, 10, "0.0000000298"); + assert_lwprint_equal(0.0000000298023223876953125, 11, "0.0000000298"); + assert_lwprint_equal(0.0000000298023223876953125, 12, "0.000000029802"); + assert_lwprint_equal(0.0000000298023223876953125, 13, "0.0000000298023"); + assert_lwprint_equal(0.0000000298023223876953125, 14, "0.00000002980232"); + assert_lwprint_equal(0.0000000298023223876953125, 15, "0.000000029802322"); + assert_lwprint_equal(0.0000000298023223876953125, 16, "0.0000000298023224"); + assert_lwprint_equal(0.0000000298023223876953125, 17, "0.00000002980232239"); + assert_lwprint_equal(0.0000000298023223876953125, 18, "0.000000029802322388"); + assert_lwprint_equal(0.0000000298023223876953125, 19, "0.0000000298023223877"); + assert_lwprint_equal(0.0000000298023223876953125, 20, "0.0000000298023223877"); + assert_lwprint_equal(0.0000000298023223876953125, 21, "0.000000029802322387695"); + assert_lwprint_equal(0.0000000298023223876953125, 22, "0.0000000298023223876953"); + assert_lwprint_equal(0.0000000298023223876953125, 23, "0.00000002980232238769531"); + assert_lwprint_equal(0.0000000298023223876953125, 24, "0.000000029802322387695312"); + assert_lwprint_equal(0.0000000298023223876953125, 40, "0.000000029802322387695312"); + + /* Negative 0 after rounding should be printed as 0 */ + assert_lwprint_equal(-0.0005, 0, "0"); + assert_lwprint_equal(-0.0005, 1, "0"); + assert_lwprint_equal(-0.0005, 2, "0"); + assert_lwprint_equal(-0.0005, 3, "0"); + assert_lwprint_equal(-0.0005, 4, "-0.0005"); + + /* Rounding on the first decimal digit */ + assert_lwprint_equal(-2.5, 0, "-2"); + assert_lwprint_equal(-1.5, 0, "-2"); + assert_lwprint_equal(-0.99, 0, "-1"); + assert_lwprint_equal(-0.5, 0, "0"); + assert_lwprint_equal(-0.01, 0, "0"); + assert_lwprint_equal(0.5, 0, "0"); + assert_lwprint_equal(0.99, 0, "1"); + assert_lwprint_equal(1.5, 0, "2"); + assert_lwprint_equal(2.5, 0, "2"); + + /* Check rounding */ + assert_lwprint_equal(0.035, 2, "0.04"); + assert_lwprint_equal(0.045, 2, "0.04"); + assert_lwprint_equal(0.04500000000000001, 2, "0.05"); + assert_lwprint_equal(0.077, 2, "0.08"); + assert_lwprint_equal(0.087, 2, "0.09"); + assert_lwprint_equal(1.05, 1, "1"); + assert_lwprint_equal(1.005, 2, "1"); + assert_lwprint_equal(2.05, 1, "2"); + assert_lwprint_equal(2.005, 2, "2"); + + for (int i = 0; i < 15; i++) + assert_lwprint_equal(-0.99999999999999988898, i, "-1"); + for (int i = 15; i < 20; i++) + assert_lwprint_equal(-0.99999999999999988898, i, "-0.9999999999999999"); + + for (int i = 0; i < 16; i++) + assert_lwprint_equal(0.99999999999999977796, i, "1"); + for (int i = 16; i < 20; i++) + assert_lwprint_equal(0.99999999999999977796, i, "0.9999999999999998"); + + assert_lwprint_equal(0.0999999999999999916733273153113, 0, "0"); + assert_lwprint_equal(-0.0999999999999999916733273153113, 0, "0"); + for (int i = 1; i < 15; i++) + { + assert_lwprint_equal(0.0999999999999999916733273153113, i, "0.1"); + assert_lwprint_equal(-0.0999999999999999916733273153113, i, "-0.1"); + } + + assert_lwprint_equal(0.00999999999999999847, 0, "0"); + assert_lwprint_equal(-0.00999999999999999847, 0, "0"); + assert_lwprint_equal(0.00999999999999999847, 1, "0"); + assert_lwprint_equal(-0.00999999999999999847, 1, "0"); + for (int i = 2; i < 15; i++) + { + assert_lwprint_equal(0.00999999999999999847, i, "0.01"); + assert_lwprint_equal(-0.00999999999999999847, i, "-0.01"); + } + + /* Test regression changes (output that changed in the tests vs 3.0) */ + /* There is at most 17 significative digits */ + assert_lwprint_equal(-123456789012345.12345678, 20, "-123456789012345.12"); + assert_lwprint_equal(123456789012345.12345678, 20, "123456789012345.12"); + + /* Precision is respected (as number of decimal digits) */ + assert_lwprint_equal(92115.51207431706, 12, "92115.51207431706"); + assert_lwprint_equal(463412.82600000006, 12, "463412.82600000006"); + assert_lwprint_equal(463462.2069374289, 12, "463462.2069374289"); + assert_lwprint_equal(-115.17281600000001, OUT_DEFAULT_DECIMAL_DIGITS, "-115.17281600000001"); + assert_lwprint_equal(-115.17281600000001, 12, "-115.172816"); + assert_lwprint_equal(36.11464599999999, OUT_DEFAULT_DECIMAL_DIGITS, "36.11464599999999"); + assert_lwprint_equal(36.11464599999999, 12, "36.114646"); + assert_lwprint_equal(400000, 0, "400000"); + assert_lwprint_equal(400000, 12, "400000"); + assert_lwprint_equal(400000, 20, "400000"); + assert_lwprint_equal(5.0833333333333330372738600999582558870316, 15, "5.083333333333333"); + assert_lwprint_equal(1.4142135623730951, 15, "1.414213562373095"); + assert_lwprint_equal(143.62025166838282, 15, "143.62025166838282"); + assert_lwprint_equal(-30.037497356076827, 15, "-30.037497356076827"); + assert_lwprint_equal(142.92857147299705, 15, "142.92857147299705"); + assert_lwprint_equal(-32.75101196874403, 15, "-32.75101196874403"); + + /* Note about this: + * 149.57565307617187 == 0x4062B26BC0000000 + * 149.57565307617188 == 0x4062B26BC0000000 + * + * 0x4062B26BC0000000 == 149.575653076171875 + * Both if we consider "round to nearest, ties to even" (which is what we use) + * or "round to nearest, ties away from zero": + * 75 => 80 => 8 for the last digit + * + * It acts the same way in PostgreSQL: + # Select '149.57565307617187'::float8, '149.575653076171875'::float8, '149.57565307617188'::float8; + float8 | float8 | float8 + --------------------+--------------------+-------------------- + 149.57565307617188 | 149.57565307617188 | 149.57565307617188 + (1 row) + */ + assert_lwprint_equal(149.57565307617187, 15, "149.57565307617188"); + assert_lwprint_equal(-149.57565307617187, 15, "-149.57565307617188"); + + /* Shortest representation is used */ + assert_lwprint_equal(7000109.9999999990686774253845214843750000000000, 8, "7000110"); + assert_lwprint_equal(7000109.9999999990686774253845214843750000000000, 12, "7000109.999999999"); + + /* SnapToGrid by itself is not enough to limit output decimals */ + const double d = 526355.92112222222; + const double gridsize = 0.00001; + const double gridded = rint(d / gridsize) * gridsize; /* Formula from ptarray_grid_in_place */ + assert_lwprint_equal(gridded, 15, "526355.9211200001"); + assert_lwprint_equal(gridded, 5, "526355.92112"); + + /* Test the change towards scientific notation */ + assert_lwprint_equal(nextafter(OUT_MAX_DOUBLE, 0), OUT_MAX_DIGITS, "999999999999999.9"); + assert_lwprint_equal(nextafter(-OUT_MAX_DOUBLE, 0), OUT_MAX_DIGITS, "-999999999999999.9"); + assert_lwprint_equal(OUT_MAX_DOUBLE, OUT_MAX_DIGITS, "1e+15"); + assert_lwprint_equal(-OUT_MAX_DOUBLE, OUT_MAX_DIGITS, "-1e+15"); + assert_lwprint_equal(nextafter(OUT_MAX_DOUBLE, INFINITY), OUT_MAX_DIGITS, "1.0000000000000001e+15"); + assert_lwprint_equal(nextafter(-OUT_MAX_DOUBLE, -INFINITY), OUT_MAX_DIGITS, "-1.0000000000000001e+15"); + + assert_lwprint_equal(nextafter(OUT_MIN_DOUBLE, 0), OUT_MAX_DIGITS, "9.999999999999999e-9"); + assert_lwprint_equal(nextafter(-OUT_MIN_DOUBLE, 0), OUT_MAX_DIGITS, "-9.999999999999999e-9"); + assert_lwprint_equal(OUT_MIN_DOUBLE, OUT_MAX_DIGITS, "1e-8"); + assert_lwprint_equal(-OUT_MIN_DOUBLE, OUT_MAX_DIGITS, "-1e-8"); + assert_lwprint_equal(nextafter(OUT_MIN_DOUBLE, INFINITY), OUT_MAX_DIGITS, "0.000000010000000000000002"); + assert_lwprint_equal(nextafter(-OUT_MIN_DOUBLE, -INFINITY), OUT_MAX_DIGITS, "-0.000000010000000000000002"); + + + /* Big numbers that use scientific notation respect the precision parameter */ + assert_lwprint_equal(9e+300, 0, "9e+300"); + assert_lwprint_equal(9e+300, 15, "9e+300"); + assert_lwprint_equal(-9e+300, 0, "-9e+300"); + assert_lwprint_equal(-9e+300, 15, "-9e+300"); + assert_lwprint_equal(9.000000000000001e+300, 0, "9e+300"); + assert_lwprint_equal(9.000000000000001e+300, 15, "9.000000000000001e+300"); + assert_lwprint_equal(-9.000000000000001e+300, 0, "-9e+300"); + assert_lwprint_equal(-9.000000000000001e+300, 15, "-9.000000000000001e+300"); + + assert_lwprint_equal(6917529027641081856.0, 17, "6.917529027641082e+18"); + assert_lwprint_equal(6917529027641081856.0, 16, "6.917529027641082e+18"); + assert_lwprint_equal(6917529027641081856.0, 15, "6.917529027641082e+18"); + assert_lwprint_equal(6917529027641081856.0, 14, "6.91752902764108e+18"); + assert_lwprint_equal(6917529027641081856.0, 13, "6.9175290276411e+18"); + assert_lwprint_equal(6917529027641081856.0, 12, "6.917529027641e+18"); + assert_lwprint_equal(6917529027641081856.0, 11, "6.91752902764e+18"); + assert_lwprint_equal(6917529027641081856.0, 10, "6.9175290276e+18"); + assert_lwprint_equal(6917529027641081856.0, 9, "6.917529028e+18"); + assert_lwprint_equal(6917529027641081856.0, 8, "6.91752903e+18"); + assert_lwprint_equal(6917529027641081856.0, 7, "6.917529e+18"); + assert_lwprint_equal(6917529027641081856.0, 6, "6.917529e+18"); + assert_lwprint_equal(6917529027641081856.0, 5, "6.91753e+18"); + assert_lwprint_equal(6917529027641081856.0, 4, "6.9175e+18"); + assert_lwprint_equal(6917529027641081856.0, 3, "6.918e+18"); + assert_lwprint_equal(6917529027641081856.0, 2, "6.92e+18"); + assert_lwprint_equal(6917529027641081856.0, 1, "6.9e+18"); + assert_lwprint_equal(6917529027641081856.0, 0, "7e+18"); + + /* Test special values (+-inf, NaNs) */ + for (int i = precision_start; i < precision_end; i++) + { + assert_lwprint_equal(NAN, i, "NaN"); + assert_lwprint_equal(INFINITY, i, "Infinity"); + assert_lwprint_equal(-INFINITY, i, "-Infinity"); + } + + /* Extremes */ + assert_lwprint_equal(2.2250738585072014e-308, OUT_MAX_DIGITS, "2.2250738585072014e-308"); + assert_lwprint_equal(1.7976931348623157e+308, OUT_MAX_DIGITS, "1.7976931348623157e+308"); /* Max */ + assert_lwprint_equal(2.9802322387695312E-8, OUT_MAX_DIGITS, "0.000000029802322387695312"); /* Trailing zeros */ +} + +/* Macro to test rountrip of lwprint_double when using enough precision digits (OUT_MAX_DIGITS) */ +#define assert_lwprint_roundtrip(d) \ + { \ + char s[OUT_DOUBLE_BUFFER_SIZE] = {0}; \ + lwprint_double(d, OUT_MAX_DIGITS, s); \ + ASSERT_DOUBLE_EQUAL(atof(s), d); \ + } + +static void +test_lwprint_roundtrip(void) +{ + /* Test roundtrip with the first value outside the range that's always printed as zero */ + assert_lwprint_roundtrip(nextafter(FP_TOLERANCE, 1)); + assert_lwprint_roundtrip(nextafter(-FP_TOLERANCE, -1)); + + /* Test roundtrip in around the switch to scientific notation */ + assert_lwprint_roundtrip(nextafter(OUT_MAX_DOUBLE, 0)); + assert_lwprint_roundtrip(nextafter(-OUT_MAX_DOUBLE, 0)); + assert_lwprint_roundtrip(OUT_MAX_DOUBLE); + assert_lwprint_roundtrip(OUT_MAX_DOUBLE); + assert_lwprint_roundtrip(nextafter(OUT_MAX_DOUBLE, INFINITY)); + assert_lwprint_roundtrip(nextafter(-OUT_MAX_DOUBLE, -INFINITY)); + + /* Test some numbers */ + assert_lwprint_roundtrip(0); + assert_lwprint_roundtrip(0.0000000298023223876953125); + assert_lwprint_roundtrip(0.3); + assert_lwprint_roundtrip(0.5); + assert_lwprint_roundtrip(7000109.9999999990686774253845214843750000000000); + assert_lwprint_roundtrip(6917529027641081856.0); + assert_lwprint_roundtrip(9e+300); + assert_lwprint_roundtrip(-9e+300); + assert_lwprint_roundtrip(9.000000000000001e+300); + assert_lwprint_roundtrip(-9.000000000000001e+300); + + /* Even if we write the **same** number differently as the (compiler) input the roundtrip is guaranteed */ + assert_lwprint_roundtrip(149.57565307617187); + assert_lwprint_roundtrip(149.57565307617188); + assert_lwprint_roundtrip(-149.57565307617187); + assert_lwprint_roundtrip(-149.57565307617188); + + /* Extremes */ + assert_lwprint_roundtrip(2.2250738585072014e-308); /* We normalize small numbers to 0 */ + assert_lwprint_roundtrip(1.7976931348623157e+308); + assert_lwprint_roundtrip(2.9802322387695312E-8); + + /* Special cases */ + assert_lwprint_roundtrip(-0); /* -0 is considered equal to 0 */ + + /* Disabled because Windows / MinGW doesn't like them (#4735) + * assert_lwprint_roundtrip(INFINITY); + * assert_lwprint_roundtrip(-INFINITY); + */ + + /* nan is never equal to nan + * assert_lwprint_roundtrip(NAN); + */ +} + +/* +** Callback used by the test harness to register the tests in this file. +*/ +void print_suite_setup(void); +void print_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("printing", NULL, NULL); + PG_ADD_TEST(suite, test_lwpoint_to_latlon_default_format); + PG_ADD_TEST(suite, test_lwpoint_to_latlon_format_orders); + PG_ADD_TEST(suite, test_lwpoint_to_latlon_optional_format); + PG_ADD_TEST(suite, test_lwpoint_to_latlon_oddball_formats); + PG_ADD_TEST(suite, test_lwpoint_to_latlon_bad_formats); + PG_ADD_TEST(suite, test_lwprint); + PG_ADD_TEST(suite, test_lwprint_roundtrip); +} + diff --git a/mgist-postgis/liblwgeom/cunit/cu_ptarray.c b/mgist-postgis/liblwgeom/cunit/cu_ptarray.c new file mode 100644 index 0000000..3b6cec8 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_ptarray.c @@ -0,0 +1,856 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright (C) 2011 Sandro Santilli + * Copyright (C) 2008 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" +#include "CUnit/CUnit.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + + +static LWGEOM* lwgeom_from_text(const char *str) +{ + LWGEOM_PARSER_RESULT r; + if( LW_FAILURE == lwgeom_parse_wkt(&r, (char*)str, LW_PARSER_CHECK_NONE) ) + return NULL; + return r.geom; +} + +static char* lwgeom_to_text(const LWGEOM *geom) +{ + return lwgeom_to_wkt(geom, WKT_ISO, 8, NULL); +} + +static void test_ptarray_append_point(void) +{ + LWLINE *line; + char *wkt; + POINT4D p; + + line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0,1 1)")); + p.x = 1; + p.y = 1; + ptarray_append_point(line->points, &p, LW_TRUE); + wkt = lwgeom_to_text(lwline_as_lwgeom(line)); + ASSERT_STRING_EQUAL(wkt,"LINESTRING(0 0,1 1,1 1)"); + lwfree(wkt); + + ptarray_append_point(line->points, &p, LW_FALSE); + wkt = lwgeom_to_text(lwline_as_lwgeom(line)); + ASSERT_STRING_EQUAL(wkt,"LINESTRING(0 0,1 1,1 1)"); + lwfree(wkt); + + lwline_free(line); +} + +static void test_ptarray_insert_point(void) +{ + LWLINE *line; + char *wkt; + POINT4D p; + + line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING EMPTY")); + p.x = 1; + p.y = 1; + ptarray_insert_point(line->points, &p, 0); + wkt = lwgeom_to_text(lwline_as_lwgeom(line)); + ASSERT_STRING_EQUAL(wkt,"LINESTRING(1 1)"); + lwfree(wkt); + + p.x = 2; + p.y = 20; + ptarray_insert_point(line->points, &p, 0); + wkt = lwgeom_to_text(lwline_as_lwgeom(line)); + ASSERT_STRING_EQUAL(wkt,"LINESTRING(2 20,1 1)"); + lwfree(wkt); + + p.x = 3; + p.y = 30; + ptarray_insert_point(line->points, &p, 1); + wkt = lwgeom_to_text(lwline_as_lwgeom(line)); + ASSERT_STRING_EQUAL(wkt,"LINESTRING(2 20,3 30,1 1)"); + lwfree(wkt); + + p.x = 4; + p.y = 40; + ptarray_insert_point(line->points, &p, 0); + wkt = lwgeom_to_text(lwline_as_lwgeom(line)); + ASSERT_STRING_EQUAL(wkt,"LINESTRING(4 40,2 20,3 30,1 1)"); + lwfree(wkt); + + p.x = 5; + p.y = 50; + ptarray_insert_point(line->points, &p, 4); + wkt = lwgeom_to_text(lwline_as_lwgeom(line)); + ASSERT_STRING_EQUAL(wkt,"LINESTRING(4 40,2 20,3 30,1 1,5 50)"); + lwfree(wkt); + + lwline_free(line); +} + +static void test_ptarray_append_ptarray(void) +{ + LWLINE *line1, *line2; + int ret; + char *wkt; + + /* Empty first line */ + line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING EMPTY")); + line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0,0 10,5 5)")); + ret = ptarray_append_ptarray(line1->points, line2->points, -1); + CU_ASSERT(ret == LW_SUCCESS); + wkt = lwgeom_to_text(lwline_as_lwgeom(line1)); + ASSERT_STRING_EQUAL(wkt, "LINESTRING(0 0,0 10,5 5)"); + lwfree(wkt); + lwline_free(line2); + lwline_free(line1); + + /* Empty second line */ + line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0, 5 5, 6 3)")); + line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING EMPTY")); + ret = ptarray_append_ptarray(line1->points, line2->points, -1); + CU_ASSERT(ret == LW_SUCCESS); + wkt = lwgeom_to_text(lwline_as_lwgeom(line1)); + ASSERT_STRING_EQUAL(wkt, "LINESTRING(0 0,5 5,6 3)"); + lwfree(wkt); + lwline_free(line2); + lwline_free(line1); + + /* Both lines empty */ + line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING EMPTY")); + line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING EMPTY")); + ret = ptarray_append_ptarray(line1->points, line2->points, -1); + CU_ASSERT(ret == LW_SUCCESS); + wkt = lwgeom_to_text(lwline_as_lwgeom(line1)); + ASSERT_STRING_EQUAL(wkt, "LINESTRING EMPTY"); + lwfree(wkt); + lwline_free(line2); + lwline_free(line1); + + /* Sane sewing */ + line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(10 4, 0 0,5 7)")); + line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(5 7,12 43, 42 15)")); + ret = ptarray_append_ptarray(line1->points, line2->points, 0); + CU_ASSERT(ret == LW_SUCCESS); + wkt = lwgeom_to_text(lwline_as_lwgeom(line1)); + ASSERT_STRING_EQUAL(wkt, "LINESTRING(10 4,0 0,5 7,12 43,42 15)"); + lwfree(wkt); + lwline_free(line2); + lwline_free(line1); + + /* Untolerated sewing */ + line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(10 4, 0 0,5 7)")); + line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(5.5 7,12 43, 42 15)")); + ret = ptarray_append_ptarray(line1->points, line2->points, 0); + CU_ASSERT(ret == LW_FAILURE); + lwline_free(line2); + lwline_free(line1); + + /* Tolerated sewing */ + line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(10 4, 0 0,5 7)")); + line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(5.5 7,12 43, 42 15)")); + ret = ptarray_append_ptarray(line1->points, line2->points, .7); + CU_ASSERT(ret == LW_SUCCESS); + wkt = lwgeom_to_text(lwline_as_lwgeom(line1)); + ASSERT_STRING_EQUAL(wkt, "LINESTRING(10 4,0 0,5 7,5.5 7,12 43,42 15)"); + lwfree(wkt); + lwline_free(line2); + lwline_free(line1); + + /* Check user input trust (creates non-simple line */ + line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0,0 10)")); + line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0,0 10)")); + ret = ptarray_append_ptarray(line1->points, line2->points, -1); + CU_ASSERT(ret == LW_SUCCESS); + wkt = lwgeom_to_text(lwline_as_lwgeom(line1)); + ASSERT_STRING_EQUAL(wkt, "LINESTRING(0 0,0 10,0 0,0 10)"); + lwfree(wkt); + lwline_free(line2); + lwline_free(line1); + + /* Mixed dimensionality is not allowed */ + line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 10 0, 10 0 0)")); + line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(10 0,11 0)")); + ret = ptarray_append_ptarray(line1->points, line2->points, -1); + CU_ASSERT(ret == LW_FAILURE); + lwline_free(line2); + lwline_free(line1); + + /* Appending a read-only pointarray is allowed */ + line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 10, 10 0)")); + line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(10 0,11 0)")); + FLAGS_SET_READONLY(line2->points->flags, 1); + ret = ptarray_append_ptarray(line1->points, line2->points, -1); + CU_ASSERT(ret == LW_SUCCESS); + wkt = lwgeom_to_text(lwline_as_lwgeom(line1)); + ASSERT_STRING_EQUAL(wkt, "LINESTRING(0 10,10 0,11 0)"); + lwfree(wkt); + FLAGS_SET_READONLY(line2->points->flags, 0); /* for lwline_free */ + lwline_free(line2); + lwline_free(line1); + + /* Appending to a read-only pointarray is forbidden */ + line1 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 10, 10 0)")); + line2 = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(10 0,11 0)")); + FLAGS_SET_READONLY(line1->points->flags, 1); + ret = ptarray_append_ptarray(line1->points, line2->points, -1); + CU_ASSERT(ret == LW_FAILURE); + lwline_free(line2); + FLAGS_SET_READONLY(line1->points->flags, 0); /* for lwline_free */ + lwline_free(line1); + +} + +static void test_ptarray_locate_point(void) +{ + LWLINE *line; + double loc, dist; + POINT4D p, l; + + line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 3,20 4)")); + + p = getPoint4d(line->points, 0); + loc = ptarray_locate_point(line->points, &p, &dist, &l); + CU_ASSERT_EQUAL(loc, 0); + CU_ASSERT_EQUAL(dist, 0.0); + + p = getPoint4d(line->points, 1); + loc = ptarray_locate_point(line->points, &p, &dist, &l); + CU_ASSERT_EQUAL(loc, 1); + CU_ASSERT_EQUAL(dist, 0.0); + + p.x = 21; p.y = 4; + loc = ptarray_locate_point(line->points, &p, &dist, NULL); + CU_ASSERT_EQUAL(loc, 1); + CU_ASSERT_EQUAL(dist, 1.0); + + p.x = 0; p.y = 2; + loc = ptarray_locate_point(line->points, &p, &dist, &l); + CU_ASSERT_EQUAL(loc, 0); + CU_ASSERT_EQUAL(dist, 1.0); + + lwline_free(line); + line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0,20 0,40 0)")); + + p.x = 20; p.y = 0; + loc = ptarray_locate_point(line->points, &p, &dist, &l); + CU_ASSERT_EQUAL(loc, 0.5); + CU_ASSERT_EQUAL(dist, 0.0); + + lwline_free(line); + line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(-40 0,0 0,20 0,40 0)")); + + p.x = 20; p.y = 0; + loc = ptarray_locate_point(line->points, &p, &dist, &l); + CU_ASSERT_EQUAL(loc, 0.75); + CU_ASSERT_EQUAL(dist, 0.0); + + lwline_free(line); + line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING M (0 0 0, 10 0 20)")); + + p.x = 5; p.y = 0; + loc = ptarray_locate_point(line->points, &p, &dist, &l); + CU_ASSERT_EQUAL(loc, 0.5); + CU_ASSERT_EQUAL(dist, 0.0); + CU_ASSERT_EQUAL(l.m, 10.0); + + lwline_free(line); + +} + +static void test_ptarray_isccw(void) +{ + LWLINE *line; + LWPOLY* poly; + int ccw; + + /* clockwise rectangle */ + line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0,0 10,10 10,10 0, 0 0)")); + ccw = ptarray_isccw(line->points); + CU_ASSERT_EQUAL(ccw, 0); + lwline_free(line); + + /* clockwise triangle */ + line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 3,20 4,20 3, 0 3)")); + ccw = ptarray_isccw(line->points); + CU_ASSERT_EQUAL(ccw, 0); + lwline_free(line); + + /* counterclockwise triangle */ + line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 3,20 3,20 4, 0 3)")); + ccw = ptarray_isccw(line->points); + CU_ASSERT_EQUAL(ccw, 1); + lwline_free(line); + + /* counterclockwise narrow ring (see ticket #1302) */ + line = lwgeom_as_lwline(lwgeom_from_hexwkb("01020000000500000000917E9BA468294100917E9B8AEA284137894120A4682941C976BE9F8AEA2841B39ABE1FA46829415ACCC29F8AEA2841C976BE1FA4682941C976BE9F8AEA284100917E9BA468294100917E9B8AEA2841", LW_PARSER_CHECK_NONE)); + ccw = ptarray_isccw(line->points); + CU_ASSERT_EQUAL(ccw, 1); + lwline_free(line); + + /* clockwise narrow ring (see ticket #1302) */ + line = lwgeom_as_lwline(lwgeom_from_hexwkb("01020000000500000000917E9BA468294100917E9B8AEA2841C976BE1FA4682941C976BE9F8AEA2841B39ABE1FA46829415ACCC29F8AEA284137894120A4682941C976BE9F8AEA284100917E9BA468294100917E9B8AEA2841", LW_PARSER_CHECK_NONE)); + ccw = ptarray_isccw(line->points); + CU_ASSERT_EQUAL(ccw, 0); + lwline_free(line); + + /* Clockwise narrow ring (see ticket #1302) */ + poly = lwgeom_as_lwpoly(lwgeom_from_hexwkb("0103000000010000000500000000917E9BA468294100917E9B8AEA2841C976BE1FA4682941C976BE9F8AEA2841B39ABE1FA46829415ACCC29F8AEA284137894120A4682941C976BE9F8AEA284100917E9BA468294100917E9B8AEA2841", LW_PARSER_CHECK_NONE)); + ccw = ptarray_isccw(poly->rings[0]); + CU_ASSERT_EQUAL(ccw, 0); + lwpoly_free(poly); +} + +static void test_ptarray_signed_area() +{ + LWLINE *line; + double area; + + /* parallelogram */ + line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0,1 1, 2 1, 1 0, 0 0)")); + area = ptarray_signed_area(line->points); + ASSERT_DOUBLE_EQUAL_TOLERANCE(area, 1.0, 0.0000001); + lwline_free(line); + + /* square */ + line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0,0 2, 2 2, 2 0, 0 0)")); + area = ptarray_signed_area(line->points); + ASSERT_DOUBLE_EQUAL_TOLERANCE(area, 4.0, 0.0000001); + lwline_free(line); + + /* square backwares*/ + line = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0,2 0, 2 2, 0 2, 0 0)")); + area = ptarray_signed_area(line->points); + //printf("%g\n",area); + ASSERT_DOUBLE_EQUAL_TOLERANCE(area, -4.0, 0.0000001); + lwline_free(line); + +} + +static void test_ptarray_contains_point() +{ +/* int ptarray_contains_point(const POINTARRAY *pa, const POINT2D *pt, int *winding_number) */ + + LWLINE *lwline; + POINTARRAY *pa; + POINT2D pt; + int rv; + + lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0, 0 1, 1 1, 1 0, 0 0)")); + pa = lwline->points; + + /* Point in middle of square */ + pt.x = 0.5; + pt.y = 0.5; + rv = ptarray_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_INSIDE); + + /* Point on left edge of square */ + pt.x = 0; + pt.y = 0.5; + rv = ptarray_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_BOUNDARY); + + /* Point on top edge of square */ + pt.x = 0.5; + pt.y = 1; + rv = ptarray_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_BOUNDARY); + + /* Point on bottom left corner of square */ + pt.x = 0; + pt.y = 0; + rv = ptarray_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_BOUNDARY); + + /* Point on top left corner of square */ + pt.x = 0; + pt.y = 1; + rv = ptarray_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_BOUNDARY); + + /* Point outside top left corner of square */ + pt.x = -0.1; + pt.y = 1; + rv = ptarray_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_OUTSIDE); + + /* Point outside top left corner of square */ + pt.x = 0; + pt.y = 1.1; + rv = ptarray_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_OUTSIDE); + + /* Point outside left side of square */ + pt.x = -0.2; + pt.y = 0.5; + rv = ptarray_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_OUTSIDE); + + lwline_free(lwline); + lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0, 1 1, 2 0, 0 0)")); + pa = lwline->points; + + /* Point outside grazing top of triangle */ + pt.x = 0; + pt.y = 1; + rv = ptarray_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_OUTSIDE); + + lwline_free(lwline); + lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(0 0, 0 4, 1 4, 2 2, 3 4, 4 4, 4 0, 0 0)")); + pa = lwline->points; + + /* Point outside grazing top of triangle */ + pt.x = 1; + pt.y = 2; + rv = ptarray_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_INSIDE); + + /* Point outside grazing top of triangle */ + pt.x = 3; + pt.y = 2; + rv = ptarray_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_INSIDE); + + lwline_free(lwline); +} + +static void test_ptarrayarc_contains_point() +{ + /* int ptarrayarc_contains_point(const POINTARRAY *pa, const POINT2D *pt) */ + + LWLINE *lwline; + POINTARRAY *pa; + POINT2D pt; + int rv; + + /*** Collection of semi-circles surrounding unit square ***/ + lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(-1 -1, -2 0, -1 1, 0 2, 1 1, 2 0, 1 -1, 0 -2, -1 -1)")); + pa = lwline->points; + + /* Point in middle of square */ + pt.x = 0; + pt.y = 0; + rv = ptarrayarc_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_INSIDE); + + /* Point in left lobe */ + pt.x = -1.1; + pt.y = 0.1; + rv = ptarrayarc_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_INSIDE); + + /* Point on boundary of left lobe */ + pt.x = -1; + pt.y = 0; + rv = ptarrayarc_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_INSIDE); + + /* Point on boundary vertex */ + pt.x = -1; + pt.y = 1; + rv = ptarrayarc_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_BOUNDARY); + + /* Point outside */ + pt.x = -1.5; + pt.y = 1.5; + rv = ptarrayarc_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_OUTSIDE); + + /*** Two-edge ring made up of semi-circles (really, a circle) ***/ + lwline_free(lwline); + lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(-1 0, 0 1, 1 0, 0 -1, -1 0)")); + pa = lwline->points; + + /* Point outside */ + pt.x = -1.5; + pt.y = 1.5; + rv = ptarrayarc_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_OUTSIDE); + + /* Point more outside */ + pt.x = 2.5; + pt.y = 1.5; + rv = ptarrayarc_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_OUTSIDE); + + /* Point more outside */ + pt.x = 2.5; + pt.y = 2.5; + rv = ptarrayarc_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_OUTSIDE); + + /* Point inside at middle */ + pt.x = 0; + pt.y = 0; + rv = ptarrayarc_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_INSIDE); + + /* Point inside offset from middle */ + pt.x = 0.01; + pt.y = 0.01; + rv = ptarrayarc_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_INSIDE); + + /* Point on edge vertex */ + pt.x = 0; + pt.y = 1; + rv = ptarrayarc_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_BOUNDARY); + + /*** Two-edge ring, closed ***/ + lwline_free(lwline); + lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(1 6, 6 1, 9 7, 6 10, 1 6)")); + pa = lwline->points; + + /* Point to left of ring */ + pt.x = 20; + pt.y = 4; + rv = ptarrayarc_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_OUTSIDE); + + /*** One-edge ring, closed circle ***/ + lwline_free(lwline); + lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(-1 0, 1 0, -1 0)")); + pa = lwline->points; + + /* Point inside */ + pt.x = 0; + pt.y = 0; + rv = ptarrayarc_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_INSIDE); + + /* Point outside */ + pt.x = 0; + pt.y = 2; + rv = ptarrayarc_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_OUTSIDE); + + /* Point on boundary */ + pt.x = 0; + pt.y = 1; + rv = ptarrayarc_contains_point(pa, &pt); + CU_ASSERT_EQUAL(rv, LW_BOUNDARY); + + /*** Overshort ring ***/ + lwline_free(lwline); + lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(-1 0, 1 0)")); + pa = lwline->points; + cu_error_msg_reset(); + rv = ptarrayarc_contains_point(pa, &pt); + //printf("%s\n", cu_error_msg); + ASSERT_STRING_EQUAL("ptarrayarc_contains_point called with even number of points", cu_error_msg); + + /*** Unclosed ring ***/ + lwline_free(lwline); + lwline = lwgeom_as_lwline(lwgeom_from_text("LINESTRING(-1 0, 1 0, 2 0)")); + pa = lwline->points; + cu_error_msg_reset(); + rv = ptarrayarc_contains_point(pa, &pt); + ASSERT_STRING_EQUAL("ptarrayarc_contains_point called on unclosed ring", cu_error_msg); + + lwline_free(lwline); +} + +static void test_ptarray_scale() +{ + LWLINE *line; + POINTARRAY *pa; + POINT4D factor; + const char *wkt; + char *wktout; + + wkt = "LINESTRING ZM (0 1 2 3,1 2 3 0,-2 -3 0 -1,-3 0 -1 -2)"; + line = lwgeom_as_lwline(lwgeom_from_text(wkt)); + pa = line->points; + + factor.x = factor.y = factor.z = factor.m = 1; + ptarray_scale(pa, &factor); + wktout = lwgeom_to_text(lwline_as_lwgeom(line)); + ASSERT_STRING_EQUAL(wktout, wkt); + lwfree(wktout); + + factor.x = 2; + wkt = "LINESTRING ZM (0 1 2 3,2 2 3 0,-4 -3 0 -1,-6 0 -1 -2)"; + ptarray_scale(pa, &factor); + wktout = lwgeom_to_text(lwline_as_lwgeom(line)); + ASSERT_STRING_EQUAL(wktout, wkt); + lwfree(wktout); + + factor.x = 1; factor.y = 3; + wkt = "LINESTRING ZM (0 3 2 3,2 6 3 0,-4 -9 0 -1,-6 0 -1 -2)"; + ptarray_scale(pa, &factor); + wktout = lwgeom_to_text(lwline_as_lwgeom(line)); + ASSERT_STRING_EQUAL(wktout, wkt); + lwfree(wktout); + + factor.x = 1; factor.y = 1; factor.z = -2; + wkt = "LINESTRING ZM (0 3 -4 3,2 6 -6 0,-4 -9 0 -1,-6 0 2 -2)"; + ptarray_scale(pa, &factor); + wktout = lwgeom_to_text(lwline_as_lwgeom(line)); + ASSERT_STRING_EQUAL(wktout, wkt); + lwfree(wktout); + + factor.x = 1; factor.y = 1; factor.z = 1; factor.m = 2; + wkt = "LINESTRING ZM (0 3 -4 6,2 6 -6 0,-4 -9 0 -2,-6 0 2 -4)"; + ptarray_scale(pa, &factor); + wktout = lwgeom_to_text(lwline_as_lwgeom(line)); + ASSERT_STRING_EQUAL(wktout, wkt); + lwfree(wktout); + + lwline_free(line); +} + +static void test_ptarray_scroll() +{ + LWLINE *line; + POINTARRAY *pa; + POINT4D scroll; + const char *wkt; + char *wktout; + int rv; + + wkt = "LINESTRING ZM (1 1 1 1,2 2 2 2,3 3 3 3,4 4 4 4,1 1 1 1)"; + line = lwgeom_as_lwline(lwgeom_from_text(wkt)); + pa = line->points; + + scroll.x = scroll.y = scroll.z = scroll.m = 2; + rv = ptarray_scroll_in_place(pa, &scroll); + CU_ASSERT_EQUAL(rv, LW_SUCCESS); + wktout = lwgeom_to_text(lwline_as_lwgeom(line)); + wkt = "LINESTRING ZM (2 2 2 2,3 3 3 3,4 4 4 4,1 1 1 1,2 2 2 2)"; + ASSERT_STRING_EQUAL(wktout, wkt); + lwfree(wktout); + + scroll.x = scroll.y = scroll.z = scroll.m = 1; + rv = ptarray_scroll_in_place(pa, &scroll); + CU_ASSERT_EQUAL(rv, LW_SUCCESS); + wktout = lwgeom_to_text(lwline_as_lwgeom(line)); + wkt = "LINESTRING ZM (1 1 1 1,2 2 2 2,3 3 3 3,4 4 4 4,1 1 1 1)"; + ASSERT_STRING_EQUAL(wktout, wkt); + lwfree(wktout); + + scroll.x = scroll.y = scroll.z = scroll.m = 9; + rv = ptarray_scroll_in_place(pa, &scroll); + CU_ASSERT_EQUAL(rv, LW_FAILURE); + ASSERT_STRING_EQUAL(cu_error_msg, "ptarray_scroll_in_place: input POINTARRAY does not contain the given point"); + + lwline_free(line); +} + +static void test_ptarray_closest_vertex_2d() +{ + LWLINE *line; + POINTARRAY *pa; + double dist; + POINT2D qp; + const char *wkt; + int rv; + + wkt = "LINESTRING (0 0 0, 1 0 0, 2 0 0, 3 0 10)"; + line = lwgeom_as_lwline(lwgeom_from_text(wkt)); + pa = line->points; + + qp.x = qp.y = 0; + rv = ptarray_closest_vertex_2d(pa, &qp, &dist); + ASSERT_INT_EQUAL(rv, 0); + ASSERT_DOUBLE_EQUAL(dist, 0); + + qp.x = qp.y = 1; + rv = ptarray_closest_vertex_2d(pa, &qp, &dist); + ASSERT_INT_EQUAL(rv, 1); + ASSERT_DOUBLE_EQUAL(dist, 1); + + qp.x = 5; qp.y = 0; + rv = ptarray_closest_vertex_2d(pa, &qp, &dist); + ASSERT_INT_EQUAL(rv, 3); + ASSERT_DOUBLE_EQUAL(dist, 2); + + + lwline_free(line); +} + +static void test_ptarray_closest_segment_2d() +{ + LWLINE *line; + POINTARRAY *pa; + double dist; + POINT2D qp; + const char *wkt; + int rv; + + wkt = "LINESTRING (0 0 0, 1 0 0, 2 0 0, 3 0 10)"; + line = lwgeom_as_lwline(lwgeom_from_text(wkt)); + pa = line->points; + + qp.x = qp.y = 0; + rv = ptarray_closest_segment_2d(pa, &qp, &dist); + ASSERT_INT_EQUAL(rv, 0); + ASSERT_DOUBLE_EQUAL(dist, 0); + + qp.x = 1; + rv = ptarray_closest_segment_2d(pa, &qp, &dist); + ASSERT_INT_EQUAL(rv, 0); + ASSERT_DOUBLE_EQUAL(dist, 0); + + qp.y = 1; + rv = ptarray_closest_segment_2d(pa, &qp, &dist); + ASSERT_INT_EQUAL(rv, 0); + ASSERT_DOUBLE_EQUAL(dist, 1); + + qp.x = 5; qp.y = 0; + rv = ptarray_closest_segment_2d(pa, &qp, &dist); + ASSERT_INT_EQUAL(rv, 2); + ASSERT_DOUBLE_EQUAL(dist, 2); + + + lwline_free(line); + + /* See https://trac.osgeo.org/postgis/ticket/4990 */ + /* Test modified to give more stable results */ + wkt = "LINESTRING(4 31,7 31,7 34,4 34,4 31)"; + line = lwgeom_as_lwline(lwgeom_from_text(wkt)); + pa = line->points; + qp.x = 7.1; qp.y = 31.1; + rv = ptarray_closest_segment_2d(pa, &qp, &dist); + ASSERT_INT_EQUAL(rv, 1); + lwline_free(line); +} + +static void test_ptarray_closest_point_on_segment(void) +{ + POINT4D s0, s1, qp, cp; + + s0.x = s0.y = 0; s0.z = 10; s0.m = 20; + s1.x = 0; s1.y = 10; s1.z = 0; s1.m = 10; + + /* Closest is bottom point */ + + qp.x = -0.1; qp.y = 0; + closest_point_on_segment(&qp, &s0, &s1, &cp); + ASSERT_DOUBLE_EQUAL(cp.x, 0); + ASSERT_DOUBLE_EQUAL(cp.y, 0); + ASSERT_DOUBLE_EQUAL(cp.z, 10); + ASSERT_DOUBLE_EQUAL(cp.m, 20); + + qp.x = 0.1; qp.y = 0; + closest_point_on_segment(&qp, &s0, &s1, &cp); + ASSERT_DOUBLE_EQUAL(cp.x, 0); + ASSERT_DOUBLE_EQUAL(cp.y, 0); + ASSERT_DOUBLE_EQUAL(cp.z, 10); + ASSERT_DOUBLE_EQUAL(cp.m, 20); + + qp.x = 0; qp.y = -0.1; + closest_point_on_segment(&qp, &s0, &s1, &cp); + ASSERT_DOUBLE_EQUAL(cp.x, 0); + ASSERT_DOUBLE_EQUAL(cp.y, 0); + ASSERT_DOUBLE_EQUAL(cp.z, 10); + ASSERT_DOUBLE_EQUAL(cp.m, 20); + + /* Closest is top point */ + + qp.x = 0; qp.y = 10.1; + closest_point_on_segment(&qp, &s0, &s1, &cp); + ASSERT_DOUBLE_EQUAL(cp.x, 0); + ASSERT_DOUBLE_EQUAL(cp.y, 10); + ASSERT_DOUBLE_EQUAL(cp.z, 0); + ASSERT_DOUBLE_EQUAL(cp.m, 10); + + qp.x = 0.1; qp.y = 10; + closest_point_on_segment(&qp, &s0, &s1, &cp); + ASSERT_DOUBLE_EQUAL(cp.x, 0); + ASSERT_DOUBLE_EQUAL(cp.y, 10); + ASSERT_DOUBLE_EQUAL(cp.z, 0); + ASSERT_DOUBLE_EQUAL(cp.m, 10); + + qp.x = -0.1; qp.y = 10; + closest_point_on_segment(&qp, &s0, &s1, &cp); + ASSERT_DOUBLE_EQUAL(cp.x, 0); + ASSERT_DOUBLE_EQUAL(cp.y, 10); + ASSERT_DOUBLE_EQUAL(cp.z, 0); + ASSERT_DOUBLE_EQUAL(cp.m, 10); + + /* Closest is mid point */ + + qp.x = 0.1; qp.y = 5; + closest_point_on_segment(&qp, &s0, &s1, &cp); + ASSERT_DOUBLE_EQUAL(cp.x, 0); + ASSERT_DOUBLE_EQUAL(cp.y, 5); + ASSERT_DOUBLE_EQUAL(cp.z, 5); + ASSERT_DOUBLE_EQUAL(cp.m, 15); + + qp.x = -0.1; qp.y = 5; + closest_point_on_segment(&qp, &s0, &s1, &cp); + ASSERT_DOUBLE_EQUAL(cp.x, 0); + ASSERT_DOUBLE_EQUAL(cp.y, 5); + ASSERT_DOUBLE_EQUAL(cp.z, 5); + ASSERT_DOUBLE_EQUAL(cp.m, 15); + + qp.x = 0.1; qp.y = 2; + closest_point_on_segment(&qp, &s0, &s1, &cp); + ASSERT_DOUBLE_EQUAL(cp.x, 0); + ASSERT_DOUBLE_EQUAL(cp.y, 2); + ASSERT_DOUBLE_EQUAL(cp.z, 8); + ASSERT_DOUBLE_EQUAL(cp.m, 18); + + qp.x = -0.1; qp.y = 2; + closest_point_on_segment(&qp, &s0, &s1, &cp); + ASSERT_DOUBLE_EQUAL(cp.x, 0); + ASSERT_DOUBLE_EQUAL(cp.y, 2); + ASSERT_DOUBLE_EQUAL(cp.z, 8); + ASSERT_DOUBLE_EQUAL(cp.m, 18); + + qp.x = 0.1; qp.y = 8; + closest_point_on_segment(&qp, &s0, &s1, &cp); + ASSERT_DOUBLE_EQUAL(cp.x, 0); + ASSERT_DOUBLE_EQUAL(cp.y, 8); + ASSERT_DOUBLE_EQUAL_TOLERANCE(cp.z, 2, 1e-5); + ASSERT_DOUBLE_EQUAL(cp.m, 12); + + qp.x = -0.1; qp.y = 8; + closest_point_on_segment(&qp, &s0, &s1, &cp); + ASSERT_DOUBLE_EQUAL(cp.x, 0); + ASSERT_DOUBLE_EQUAL(cp.y, 8); + ASSERT_DOUBLE_EQUAL_TOLERANCE(cp.z, 2, 1e-5); + ASSERT_DOUBLE_EQUAL(cp.m, 12); + + +} + + +/* +** Used by the test harness to register the tests in this file. +*/ +void ptarray_suite_setup(void); +void ptarray_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("ptarray", NULL, NULL); + PG_ADD_TEST(suite, test_ptarray_append_point); + PG_ADD_TEST(suite, test_ptarray_append_ptarray); + PG_ADD_TEST(suite, test_ptarray_locate_point); + PG_ADD_TEST(suite, test_ptarray_isccw); + PG_ADD_TEST(suite, test_ptarray_signed_area); + PG_ADD_TEST(suite, test_ptarray_insert_point); + PG_ADD_TEST(suite, test_ptarray_contains_point); + PG_ADD_TEST(suite, test_ptarrayarc_contains_point); + PG_ADD_TEST(suite, test_ptarray_scale); + PG_ADD_TEST(suite, test_ptarray_scroll); + PG_ADD_TEST(suite, test_ptarray_closest_vertex_2d); + PG_ADD_TEST(suite, test_ptarray_closest_segment_2d); + PG_ADD_TEST(suite, test_ptarray_closest_point_on_segment); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_sfcgal.c b/mgist-postgis/liblwgeom/cunit/cu_sfcgal.c new file mode 100644 index 0000000..0cba173 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_sfcgal.c @@ -0,0 +1,99 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "cu_tester.h" +#include "liblwgeom.h" + +extern LWGEOM *lwgeom_sfcgal_noop(const LWGEOM *geom_in); + +static void +test_sfcgal_noop(void) +{ + size_t i; + + char *ewkt[] = { + "POINT(0 0.2)", + "LINESTRING(-1 -1,-1 2.5,2 2,2 -1)", + "TRIANGLE((0 0,-1 1,0 -1,0 0))", + "MULTIPOINT(0.9 0.9,0.9 0.9,0.9 0.9,0.9 0.9,0.9 0.9,0.9 0.9)", + "SRID=1;MULTILINESTRING((-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1))", + "SRID=1;MULTILINESTRING((-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1))", + "POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0))", + "SRID=4326;POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0))", + "SRID=4326;POLYGON((-1 -1 1,-1 2.5 1,2 2 2,2 -1 2,-1 -1 2),(0 0 1,0 1 1,1 1 1,1 0 2,0 0 2))", + "SRID=4326;POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5))", + "SRID=100000;POLYGON((-1 -1 3,-1 2.5 3,2 2 3,2 -1 3,-1 -1 3),(0 0 3,0 1 3,1 1 3,1 0 3,0 0 3),(-0.5 -0.5 3,-0.5 -0.4 3,-0.4 -0.4 3,-0.4 -0.5 3,-0.5 -0.5 3))", + "SRID=4326;MULTIPOLYGON(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)),((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)))", + "SRID=4326;GEOMETRYCOLLECTION(POINT(0 1),POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0)),MULTIPOLYGON(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5))))", + "POLYHEDRALSURFACE(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)),((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)))", + "POLYHEDRALSURFACE(((-1 -1 1,-1 2.5 1,2 2 1,2 -1 1,-1 -1 1),(0 0 1,0 1 1,1 1 1,1 0 1,0 0 1),(-0.5 -0.5 1,-0.5 -0.4 1,-0.4 -0.4 1,-0.4 -0.5 1,-0.5 -0.5 1)),((-1 -1 1,-1 2.5 1,2 2 1,2 -1 1,-1 -1 1),(0 0 1,0 1 1,1 1 1,1 0 1,0 0 1),(-0.5 -0.5 1,-0.5 -0.4 1,-0.4 -0.4 1,-0.4 -0.5 1,-0.5 -0.5 1)))", + "TIN(((0 0,0 -1,-1 1,0 0)),((0 0,1 0,0 -1,0 0)))", + }; + + char *expected_ewkt[] = { + "POINT(0 0.2)", + "LINESTRING(-1 -1,-1 2.5,2 2,2 -1)", + "TRIANGLE((0 0,-1 1,0 -1,0 0))", + "MULTIPOINT(0.9 0.9,0.9 0.9,0.9 0.9,0.9 0.9,0.9 0.9,0.9 0.9)", + "SRID=1;MULTILINESTRING((-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1))", + "SRID=1;MULTILINESTRING((-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1),(-1 -1,-1 2.5,2 2,2 -1))", + "POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0))", + "SRID=4326;POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0))", + "SRID=4326;POLYGON((-1 -1 1,-1 2.5 1,2 2 2,2 -1 2,-1 -1 2),(0 0 1,0 1 1,1 1 1,1 0 2,0 0 2))", + "SRID=4326;POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5))", + "SRID=100000;POLYGON((-1 -1 3,-1 2.5 3,2 2 3,2 -1 3,-1 -1 3),(0 0 3,0 1 3,1 1 3,1 0 3,0 0 3),(-0.5 -0.5 3,-0.5 -0.4 3,-0.4 -0.4 3,-0.4 -0.5 3,-0.5 -0.5 3))", + "SRID=4326;MULTIPOLYGON(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)),((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)))", + "SRID=4326;GEOMETRYCOLLECTION(POINT(0 1),POLYGON((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0)),MULTIPOLYGON(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5))))", + "POLYHEDRALSURFACE(((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)),((-1 -1,-1 2.5,2 2,2 -1,-1 -1),(0 0,0 1,1 1,1 0,0 0),(-0.5 -0.5,-0.5 -0.4,-0.4 -0.4,-0.4 -0.5,-0.5 -0.5)))", + "POLYHEDRALSURFACE(((-1 -1 1,-1 2.5 1,2 2 1,2 -1 1,-1 -1 1),(0 0 1,0 1 1,1 1 1,1 0 1,0 0 1),(-0.5 -0.5 1,-0.5 -0.4 1,-0.4 -0.4 1,-0.4 -0.5 1,-0.5 -0.5 1)),((-1 -1 1,-1 2.5 1,2 2 1,2 -1 1,-1 -1 1),(0 0 1,0 1 1,1 1 1,1 0 1,0 0 1),(-0.5 -0.5 1,-0.5 -0.4 1,-0.4 -0.4 1,-0.4 -0.5 1,-0.5 -0.5 1)))", + "TIN(((0 0,0 -1,-1 1,0 0)),((0 0,1 0,0 -1,0 0)))", + }; + + for (i = 0; i < (sizeof ewkt / sizeof(char *)); i++) + { + LWGEOM *geom_in, *geom_out; + char *in_ewkt; + char *out_ewkt; + + in_ewkt = ewkt[i]; + geom_in = lwgeom_from_wkt(in_ewkt, LW_PARSER_CHECK_NONE); + geom_out = lwgeom_sfcgal_noop(geom_in); + if (!geom_out) + { + fprintf(stderr, "\nNull return from lwgeom_sfcgal_noop with wkt: %s\n", in_ewkt); + lwgeom_free(geom_in); + continue; + } + out_ewkt = lwgeom_to_ewkt(geom_out); + if (strcmp(expected_ewkt[i], out_ewkt)) + fprintf(stderr, "\nExp: %s\nObt: %s\n", expected_ewkt[i], out_ewkt); + CU_ASSERT_STRING_EQUAL(expected_ewkt[i], out_ewkt); + lwfree(out_ewkt); + lwgeom_free(geom_out); + lwgeom_free(geom_in); + } +} + +/* +** Used by test harness to register the tests in this file. +*/ +void sfcgal_suite_setup(void); +void +sfcgal_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("sfcgal", NULL, NULL); + PG_ADD_TEST(suite, test_sfcgal_noop); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_split.c b/mgist-postgis/liblwgeom/cunit/cu_split.c new file mode 100644 index 0000000..edbf00a --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_split.c @@ -0,0 +1,292 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright (C) 2011-2015 Sandro Santilli + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include "CUnit/Basic.h" +#include "cu_tester.h" + +#include "liblwgeom.h" +#include "liblwgeom_internal.h" +#include "../lwgeom_geos.h" + +static void test_lwline_split_by_point_to(void) +{ + LWLINE *line; + LWPOINT *point; + LWMLINE *coll; + int ret; + + /* Because i don't trust that much prior tests... ;) */ + cu_error_msg_reset(); + + coll = lwmline_construct_empty(SRID_UNKNOWN, 0, 0); + CU_ASSERT_EQUAL(coll->ngeoms, 0); + + line = lwgeom_as_lwline(lwgeom_from_wkt("LINESTRING(0 0,5 5, 10 0)", + LW_PARSER_CHECK_NONE)); + CU_ASSERT(line != NULL); + + point = lwgeom_as_lwpoint(lwgeom_from_wkt( + "POINT(0 0)", + LW_PARSER_CHECK_NONE)); + ret = lwline_split_by_point_to(line, point, coll); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_EQUAL(coll->ngeoms, 0); + lwpoint_free(point); + + point = lwgeom_as_lwpoint(lwgeom_from_wkt( + "POINT(10 0)", + LW_PARSER_CHECK_NONE)); + ret = lwline_split_by_point_to(line, point, coll); + CU_ASSERT_EQUAL(ret, 1); + CU_ASSERT_EQUAL(coll->ngeoms, 0); + lwpoint_free(point); + + point = lwgeom_as_lwpoint(lwgeom_from_wkt( + "POINT(5 0)", + LW_PARSER_CHECK_NONE)); + ret = lwline_split_by_point_to(line, point, coll); + CU_ASSERT_EQUAL(ret, 0); + CU_ASSERT_EQUAL(coll->ngeoms, 0); + lwpoint_free(point); + + point = lwgeom_as_lwpoint(lwgeom_from_wkt( + "POINT(5 5)", + LW_PARSER_CHECK_NONE)); + ret = lwline_split_by_point_to(line, point, coll); + CU_ASSERT_EQUAL(ret, 2); + CU_ASSERT_EQUAL(coll->ngeoms, 2); + lwpoint_free(point); + + point = lwgeom_as_lwpoint(lwgeom_from_wkt( + "POINT(2 2)", + LW_PARSER_CHECK_NONE)); + ret = lwline_split_by_point_to(line, point, coll); + CU_ASSERT_EQUAL(ret, 2); + CU_ASSERT_EQUAL(coll->ngeoms, 4); + lwpoint_free(point); + + lwline_free(line); + + line = lwgeom_as_lwline(lwgeom_from_wkt("LINESTRING EMPTY", + LW_PARSER_CHECK_NONE)); + CU_ASSERT(line != NULL); + point = lwgeom_as_lwpoint(lwgeom_from_wkt( + "POINT(0 0)", LW_PARSER_CHECK_NONE)); + CU_ASSERT(point != NULL); + ret = lwline_split_by_point_to(line, point, coll); + CU_ASSERT_EQUAL(ret, 0); /* the point is not on the line */ + lwpoint_free(point); + lwline_free(line); + + lwcollection_free((LWCOLLECTION*)coll); +} + +static void test_lwgeom_split(void) +{ + LWGEOM *geom, *blade, *ret, *tmp1, *tmp2; + char *wkt, *in_wkt; + + geom = lwgeom_from_wkt("MULTILINESTRING((-5 -2,0 0),(0 0,10 10))", LW_PARSER_CHECK_NONE); + CU_ASSERT(geom != NULL); + blade = lwgeom_from_wkt("POINT(0 0)", LW_PARSER_CHECK_NONE); + CU_ASSERT(blade != NULL); + ret = lwgeom_split(geom, blade); + CU_ASSERT(ret != NULL); + wkt = lwgeom_to_ewkt(ret); + in_wkt = "GEOMETRYCOLLECTION(LINESTRING(-5 -2,0 0),LINESTRING(0 0,10 10))"; + ASSERT_STRING_EQUAL(wkt, in_wkt); + lwfree(wkt); + lwgeom_free(ret); + lwgeom_free(geom); + lwgeom_free(blade); + + /* See #1311 */ + geom = lwgeom_from_wkt("LINESTRING(0 0,10 0,20 4,0 3)", LW_PARSER_CHECK_NONE); + CU_ASSERT(geom != NULL); + blade = lwgeom_from_wkt("POINT(10 0)", LW_PARSER_CHECK_NONE); + ret = lwgeom_split(geom, blade); + CU_ASSERT(ret != NULL); + wkt = lwgeom_to_ewkt(ret); + in_wkt = "GEOMETRYCOLLECTION(LINESTRING(0 0,10 0),LINESTRING(10 0,20 4,0 3))"; + ASSERT_STRING_EQUAL(wkt, in_wkt); + lwfree(wkt); + lwgeom_free(ret); + lwgeom_free(geom); + lwgeom_free(blade); + + /* See #2528 (1) -- memory leak test, needs valgrind to check */ + geom = lwgeom_from_wkt("SRID=1;LINESTRING(0 1,10 1)", LW_PARSER_CHECK_NONE); + CU_ASSERT(geom != NULL); + blade = lwgeom_from_wkt("LINESTRING(7 0,7 3)", LW_PARSER_CHECK_NONE); + ret = lwgeom_split(geom, blade); + CU_ASSERT(ret != NULL); + wkt = lwgeom_to_ewkt(ret); + in_wkt = "SRID=1;GEOMETRYCOLLECTION(LINESTRING(0 1,7 1),LINESTRING(7 1,10 1))"; + ASSERT_STRING_EQUAL(wkt, in_wkt); + lwfree(wkt); + lwgeom_free(ret); + lwgeom_free(geom); + lwgeom_free(blade); + + /* See #2528 (2) -- memory leak test, needs valgrind to check */ + geom = lwgeom_from_wkt("SRID=1;POLYGON((0 1, 10 1, 10 10, 0 10, 0 1))", LW_PARSER_CHECK_NONE); + CU_ASSERT(geom != NULL); + blade = lwgeom_from_wkt("LINESTRING(7 0,7 20)", LW_PARSER_CHECK_NONE); + tmp1 = lwgeom_split(geom, blade); + ret = lwgeom_normalize(tmp1); + lwgeom_free(tmp1); + CU_ASSERT(ret != NULL); + wkt = lwgeom_to_ewkt(ret); + tmp1 = lwgeom_from_wkt( + "SRID=1;GEOMETRYCOLLECTION(POLYGON((7 1,0 1,0 10,7 10,7 1)),POLYGON((7 10,10 10,10 1,7 1,7 10)))", + LW_PARSER_CHECK_NONE); + tmp2 = lwgeom_normalize(tmp1); + in_wkt = lwgeom_to_ewkt(tmp2); + ASSERT_STRING_EQUAL(wkt, in_wkt); + lwfree(wkt); + lwfree(in_wkt); + lwgeom_free(tmp1); + lwgeom_free(tmp2); + lwgeom_free(ret); + lwgeom_free(geom); + lwgeom_free(blade); + + /* Split line by multiline */ + geom = lwgeom_from_wkt("LINESTRING(0 0, 10 0)", LW_PARSER_CHECK_NONE); + CU_ASSERT_FATAL(geom != NULL); + blade = lwgeom_from_wkt("MULTILINESTRING((1 1,1 -1),(2 1,2 -1,3 -1,3 1))", LW_PARSER_CHECK_NONE); + ret = lwgeom_split(geom, blade); + if (!ret) + printf("%s", cu_error_msg); + CU_ASSERT_FATAL(ret != NULL); + wkt = lwgeom_to_ewkt(ret); + CU_ASSERT_FATAL(wkt != NULL); + in_wkt = "GEOMETRYCOLLECTION(LINESTRING(0 0,1 0),LINESTRING(1 0,2 0),LINESTRING(2 0,3 0),LINESTRING(3 0,10 0))"; + ASSERT_STRING_EQUAL(wkt, in_wkt); + lwfree(wkt); + lwgeom_free(ret); + lwgeom_free(geom); + lwgeom_free(blade); + + /* Split line by polygon (boundary) */ + geom = lwgeom_from_wkt("LINESTRING(0 0, 10 0)", LW_PARSER_CHECK_NONE); + CU_ASSERT_FATAL(geom != NULL); + blade = lwgeom_from_wkt("POLYGON((1 -2,1 1,2 1,2 -1,3 -1,3 1,11 1,11 -2,1 -2))", LW_PARSER_CHECK_NONE); + ret = lwgeom_split(geom, blade); + if (!ret) + printf("%s", cu_error_msg); + CU_ASSERT_FATAL(ret != NULL); + wkt = lwgeom_to_ewkt(ret); + CU_ASSERT_FATAL(wkt != NULL); + in_wkt = "GEOMETRYCOLLECTION(LINESTRING(0 0,1 0),LINESTRING(1 0,2 0),LINESTRING(2 0,3 0),LINESTRING(3 0,10 0))"; + ASSERT_STRING_EQUAL(wkt, in_wkt); + lwfree(wkt); + lwgeom_free(ret); + lwgeom_free(geom); + lwgeom_free(blade); + + /* Split line by EMPTY polygon (boundary) */ + geom = lwgeom_from_wkt("LINESTRING(0 0, 10 0)", LW_PARSER_CHECK_NONE); + CU_ASSERT_FATAL(geom != NULL); + blade = lwgeom_from_wkt("POLYGON EMPTY", LW_PARSER_CHECK_NONE); + ret = lwgeom_split(geom, blade); + if (!ret) + printf("%s", cu_error_msg); + CU_ASSERT_FATAL(ret != NULL); + wkt = lwgeom_to_ewkt(ret); + CU_ASSERT_FATAL(wkt != NULL); + in_wkt = "GEOMETRYCOLLECTION(LINESTRING(0 0,10 0))"; + ASSERT_STRING_EQUAL(wkt, in_wkt); + lwfree(wkt); + lwgeom_free(ret); + lwgeom_free(geom); + lwgeom_free(blade); + + /* Split line by multipolygon (boundary) */ + geom = lwgeom_from_wkt("LINESTRING(0 0, 10 0)", LW_PARSER_CHECK_NONE); + CU_ASSERT_FATAL(geom != NULL); + blade = lwgeom_from_wkt("MULTIPOLYGON(((1 -1,1 1,2 1,2 -1,1 -1)),((3 -1,3 1,11 1,11 -1,3 -1)))", + LW_PARSER_CHECK_NONE); + ret = lwgeom_split(geom, blade); + if (!ret) + printf("%s", cu_error_msg); + CU_ASSERT_FATAL(ret != NULL); + wkt = lwgeom_to_ewkt(ret); + CU_ASSERT_FATAL(wkt != NULL); + in_wkt = "GEOMETRYCOLLECTION(LINESTRING(0 0,1 0),LINESTRING(1 0,2 0),LINESTRING(2 0,3 0),LINESTRING(3 0,10 0))"; + ASSERT_STRING_EQUAL(wkt, in_wkt); + lwfree(wkt); + lwgeom_free(ret); + lwgeom_free(geom); + lwgeom_free(blade); + + /* Split line by multipoint */ + geom = lwgeom_from_wkt("LINESTRING(0 0, 10 0)", LW_PARSER_CHECK_NONE); + CU_ASSERT_FATAL(geom != NULL); + blade = lwgeom_from_wkt("MULTIPOINT(2 0,8 0,4 0)", LW_PARSER_CHECK_NONE); + ret = lwgeom_split(geom, blade); + if (!ret) + printf("%s", cu_error_msg); + CU_ASSERT_FATAL(ret != NULL); + wkt = lwgeom_to_ewkt(ret); + CU_ASSERT_FATAL(wkt != NULL); + in_wkt = "GEOMETRYCOLLECTION(LINESTRING(8 0,10 0),LINESTRING(0 0,2 0),LINESTRING(4 0,8 0),LINESTRING(2 0,4 0))"; + ASSERT_STRING_EQUAL(wkt, in_wkt); + lwfree(wkt); + lwgeom_free(ret); + lwgeom_free(geom); + lwgeom_free(blade); + + /* See #3401 -- robustness issue */ + geom = lwgeom_from_wkt("LINESTRING(-180 0,0 0)", LW_PARSER_CHECK_NONE); + CU_ASSERT(geom != NULL); + blade = lwgeom_from_wkt("POINT(-20 0)", LW_PARSER_CHECK_NONE); + ret = lwgeom_split(geom, blade); + CU_ASSERT(ret != NULL); + { + LWCOLLECTION *split = lwgeom_as_lwcollection(ret); + LWLINE *l1, *l2; + POINT2D pt; + CU_ASSERT(split != NULL); + l1 = lwgeom_as_lwline(split->geoms[0]); + CU_ASSERT(l1 != NULL); + getPoint2d_p(l1->points, 1, &pt); + ASSERT_DOUBLE_EQUAL(pt.x, -20); + ASSERT_DOUBLE_EQUAL(pt.y, 0); + l2 = lwgeom_as_lwline(split->geoms[1]); + CU_ASSERT(l2 != NULL); + getPoint2d_p(l2->points, 0, &pt); + ASSERT_DOUBLE_EQUAL(pt.x, -20); + ASSERT_DOUBLE_EQUAL(pt.y, 0); + } + lwgeom_free(ret); + lwgeom_free(geom); + lwgeom_free(blade); +} + +static int +clean_geos_split_suite(void) +{ + finishGEOS(); + return 0; +} + +/* +** Used by test harness to register the tests in this file. +*/ +void split_suite_setup(void); +void split_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("split", NULL, clean_geos_split_suite); + PG_ADD_TEST(suite, test_lwline_split_by_point_to); + PG_ADD_TEST(suite, test_lwgeom_split); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_stringbuffer.c b/mgist-postgis/liblwgeom/cunit/cu_stringbuffer.c new file mode 100644 index 0000000..b647942 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_stringbuffer.c @@ -0,0 +1,62 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright 2012 Sandro Santilli + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "stringbuffer.h" +#include "cu_tester.h" + + +static void test_stringbuffer_append(void) +{ + stringbuffer_t *sb; + const char *str; + + sb = stringbuffer_create_with_size(2); + stringbuffer_append(sb, "hello world"); + str = stringbuffer_getstring(sb); + + CU_ASSERT_STRING_EQUAL("hello world", str); + + stringbuffer_destroy(sb); +} + +static void test_stringbuffer_aprintf(void) +{ + stringbuffer_t *sb; + const char *str; + + sb = stringbuffer_create_with_size(2); + stringbuffer_aprintf(sb, "hello %dth world", 14); + str = stringbuffer_getstring(sb); + + CU_ASSERT_STRING_EQUAL("hello 14th world", str); + + stringbuffer_destroy(sb); +} + + +/* TODO: add more... */ + +/* +** Used by the test harness to register the tests in this file. +*/ +void stringbuffer_suite_setup(void); +void stringbuffer_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("stringbuffer", NULL, NULL); + PG_ADD_TEST(suite, test_stringbuffer_append); + PG_ADD_TEST(suite, test_stringbuffer_aprintf); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_surface.c b/mgist-postgis/liblwgeom/cunit/cu_surface.c new file mode 100644 index 0000000..ce9db52 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_surface.c @@ -0,0 +1,411 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2010-2012 Olivier Courtin + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include "cu_surface.h" + +void triangle_parse(void) +{ + LWGEOM *geom; + GSERIALIZED *g; + char *tmp; + + cu_error_msg_reset(); /* Because i don't trust that much prior tests... ;) */ + + /* 2 dims */ + geom = lwgeom_from_wkt("TRIANGLE((0 1,2 3,4 5,0 1))", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(strlen(cu_error_msg), 0); + CU_ASSERT_EQUAL(geom->type, TRIANGLETYPE); + tmp = lwgeom_to_ewkt(geom); + CU_ASSERT_STRING_EQUAL("TRIANGLE((0 1,2 3,4 5,0 1))", tmp); + lwfree(tmp); + lwgeom_free(geom); + /* 3DM */ + geom = lwgeom_from_wkt("TRIANGLEM((0 1 2,3 4 5,6 7 8,0 1 2))", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(strlen(cu_error_msg), 0); + CU_ASSERT_EQUAL(geom->type, TRIANGLETYPE); + tmp = lwgeom_to_ewkt(geom); + CU_ASSERT_STRING_EQUAL("TRIANGLEM((0 1 2,3 4 5,6 7 8,0 1 2))", tmp); + lwfree(tmp); + lwgeom_free(geom); + + /* ERROR: a missing Z values */ + geom = lwgeom_from_wkt("TRIANGLE((0 1 2,3 4 5,6 7,0 1 2))", LW_PARSER_CHECK_NONE); + CU_ASSERT_STRING_EQUAL("can not mix dimensionality in a geometry", cu_error_msg); + cu_error_msg_reset(); + lwgeom_free(geom); + + /* ERROR: non closed rings */ + geom = lwgeom_from_wkt("TRIANGLE((0 1 2,3 4 5,6 7 8,0 0 2))", LW_PARSER_CHECK_NONE); + CU_ASSERT_STRING_EQUAL("geometry contains non-closed rings", cu_error_msg); + cu_error_msg_reset(); + lwgeom_free(geom); + + /* ERROR: non closed face in Z dim */ + geom = lwgeom_from_wkt("TRIANGLE((0 1 2,3 4 5,6 7 8,0 1 3))", LW_PARSER_CHECK_NONE); + CU_ASSERT_STRING_EQUAL("geometry contains non-closed rings", cu_error_msg); + cu_error_msg_reset(); + lwgeom_free(geom); + + /* ERROR: non closed face in Z dim, with a 4D geom */ + geom = lwgeom_from_wkt("TRIANGLE((0 1 2 3,4 5 6 7,8 9 10 11,0 1 3 3))", LW_PARSER_CHECK_NONE); + CU_ASSERT_STRING_EQUAL("geometry contains non-closed rings", cu_error_msg); + cu_error_msg_reset(); + lwgeom_free(geom); + + /* ERROR: only 3 points in a face */ + geom = lwgeom_from_wkt("TRIANGLE((0 1 2,3 4 5,0 1 2))", LW_PARSER_CHECK_NONE); + CU_ASSERT_STRING_EQUAL("triangle must have exactly 4 points", cu_error_msg); + cu_error_msg_reset(); + lwgeom_free(geom); + + /* ERROR: more than 4 points in a face */ + geom = lwgeom_from_wkt("TRIANGLE((0 1 2,3 4 5,6 7 8,9 10 11,0 1 2))", LW_PARSER_CHECK_NONE); + CU_ASSERT_STRING_EQUAL("triangle must have exactly 4 points", cu_error_msg); + cu_error_msg_reset(); + lwgeom_free(geom); + + /* ERROR: no interior rings allowed */ + geom = lwgeom_from_wkt("TRIANGLE((0 1 2,3 4 5,6 7 8,0 1 2),(9 10 11,12 13 14,15 16 17,9 10 11)", LW_PARSER_CHECK_NONE); + CU_ASSERT_STRING_EQUAL("parse error - invalid geometry", cu_error_msg); + cu_error_msg_reset(); + lwgeom_free(geom); + + /* EMPTY face */ + geom = lwgeom_from_wkt("TRIANGLE EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(strlen(cu_error_msg), 0); + CU_ASSERT_EQUAL(geom->type, TRIANGLETYPE); + tmp = lwgeom_to_wkt(geom, LW_PARSER_CHECK_NONE, 0, 0); + CU_ASSERT_STRING_EQUAL("TRIANGLE EMPTY", tmp); + lwfree(tmp); + lwgeom_free(geom); + + /* explicit SRID */ + geom = lwgeom_from_wkt("SRID=4326;TRIANGLE((0 1 2,3 4 5,6 7 8,0 1 2))", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(strlen(cu_error_msg), 0); + CU_ASSERT_EQUAL(geom->type, TRIANGLETYPE); + CU_ASSERT_EQUAL(geom->srid, 4326); + tmp = lwgeom_to_ewkt(geom); + CU_ASSERT_STRING_EQUAL("SRID=4326;TRIANGLE((0 1 2,3 4 5,6 7 8,0 1 2))", tmp); + lwfree(tmp); + lwgeom_free(geom); + + /* geography support */ + geom = lwgeom_from_wkt("TRIANGLE((0 1 2,3 4 5,6 7 8,0 1 2))", LW_PARSER_CHECK_NONE); + g = gserialized_from_lwgeom(geom, 0); + CU_ASSERT_EQUAL(gserialized_get_type(g), TRIANGLETYPE); + lwgeom_free(geom); + lwfree(g); +} + + +void tin_parse(void) +{ + LWGEOM *geom; + GSERIALIZED *g; + char *tmp; + + cu_error_msg_reset(); /* Because i don't trust that much prior tests... ;) */ + + /* empty */ + geom = lwgeom_from_wkt("TIN EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(strlen(cu_error_msg), 0); + CU_ASSERT_EQUAL(geom->type, TINTYPE); + tmp = lwgeom_to_ewkt(geom); + CU_ASSERT_STRING_EQUAL("TIN EMPTY", tmp); + lwfree(tmp); + lwgeom_free(geom); + + /* 2 dims */ + geom = lwgeom_from_wkt("TIN(((0 1,2 3,4 5,0 1)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(strlen(cu_error_msg), 0); + CU_ASSERT_EQUAL(geom->type, TINTYPE); + tmp = lwgeom_to_ewkt(geom); + CU_ASSERT_STRING_EQUAL("TIN(((0 1,2 3,4 5,0 1)))", tmp); + lwfree(tmp); + lwgeom_free(geom); + + /* 3DM */ + geom = lwgeom_from_wkt("TINM(((0 1 2,3 4 5,6 7 8,0 1 2)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(strlen(cu_error_msg), 0); + CU_ASSERT_EQUAL(geom->type, TINTYPE); + tmp = lwgeom_to_ewkt(geom); + CU_ASSERT_STRING_EQUAL("TINM(((0 1 2,3 4 5,6 7 8,0 1 2)))", tmp); + lwfree(tmp); + lwgeom_free(geom); + + /* ERROR: a missing Z values */ + geom = lwgeom_from_wkt("TIN(((0 1 2,3 4 5,6 7,0 1 2)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_STRING_EQUAL("can not mix dimensionality in a geometry", cu_error_msg); + cu_error_msg_reset(); + lwgeom_free(geom); + + /* ERROR: non closed rings */ + geom = lwgeom_from_wkt("TIN(((0 1 2,3 4 5,6 7 8,0 0 2)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_STRING_EQUAL("geometry contains non-closed rings", cu_error_msg); + cu_error_msg_reset(); + lwgeom_free(geom); + + /* ERROR: non closed face in Z dim */ + geom = lwgeom_from_wkt("TIN(((0 1 2,3 4 5,6 7 8,0 1 3)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_STRING_EQUAL("geometry contains non-closed rings", cu_error_msg); + cu_error_msg_reset(); + lwgeom_free(geom); + + /* ERROR: non closed face in Z dim, with a 4D geom */ + geom = lwgeom_from_wkt("TIN(((0 1 2 3,4 5 6 7,8 9 10 11,0 1 3 3)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_STRING_EQUAL("geometry contains non-closed rings", cu_error_msg); + cu_error_msg_reset(); + lwgeom_free(geom); + + /* ERROR: only 3 points in a face */ + geom = lwgeom_from_wkt("TIN(((0 1 2,3 4 5,0 1 2)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_STRING_EQUAL("triangle must have exactly 4 points", cu_error_msg); + cu_error_msg_reset(); + lwgeom_free(geom); + + /* ERROR: more than 3 points in a face */ + geom = lwgeom_from_wkt("TIN(((0 1 2,3 4 5,6 7 8,9 10 11,0 1 2)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_STRING_EQUAL("triangle must have exactly 4 points", cu_error_msg); + cu_error_msg_reset(); + lwgeom_free(geom); + + /* ERROR: use ring for triangle */ + geom = lwgeom_from_wkt("TIN(((0 1 2,3 4 5,6 7 8,0 1 2),(9 10 11,12 13 14,15 16 17,9 10 11)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_STRING_EQUAL("parse error - invalid geometry", cu_error_msg); + cu_error_msg_reset(); + lwgeom_free(geom); + + /* EMPTY face */ + geom = lwgeom_from_wkt("TIN EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(strlen(cu_error_msg), 0); + CU_ASSERT_EQUAL(geom->type, TINTYPE); + tmp = lwgeom_to_ewkt(geom); + CU_ASSERT_STRING_EQUAL("TIN EMPTY", tmp); + lwfree(tmp); + lwgeom_free(geom); + + /* A simple tetrahedron */ + geom = lwgeom_from_wkt("TIN(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(strlen(cu_error_msg), 0); + CU_ASSERT_EQUAL(geom->type, TINTYPE); + CU_ASSERT_EQUAL(geom->srid, SRID_UNKNOWN); + tmp = lwgeom_to_ewkt(geom); + CU_ASSERT_STRING_EQUAL("TIN(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))", tmp); + lwfree(tmp); + lwgeom_free(geom); + + /* A 4D tetrahedron */ + geom = lwgeom_from_wkt("TIN(((0 0 0 0,0 0 1 0,0 1 0 2,0 0 0 0)),((0 0 0 0,0 1 0 0,1 0 0 4,0 0 0 0)),((0 0 0 0,1 0 0 0,0 0 1 6,0 0 0 0)),((1 0 0 0,0 1 0 0,0 0 1 0,1 0 0 0)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(strlen(cu_error_msg), 0); + CU_ASSERT_EQUAL(geom->type, TINTYPE); + CU_ASSERT_EQUAL(FLAGS_GET_M(geom->flags), 1); + CU_ASSERT_EQUAL(geom->srid, SRID_UNKNOWN); + tmp = lwgeom_to_ewkt(geom); + CU_ASSERT_STRING_EQUAL("TIN(((0 0 0 0,0 0 1 0,0 1 0 2,0 0 0 0)),((0 0 0 0,0 1 0 0,1 0 0 4,0 0 0 0)),((0 0 0 0,1 0 0 0,0 0 1 6,0 0 0 0)),((1 0 0 0,0 1 0 0,0 0 1 0,1 0 0 0)))", tmp); + lwfree(tmp); + lwgeom_free(geom); + + /* explicit SRID */ + geom = lwgeom_from_wkt("SRID=4326;TIN(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(strlen(cu_error_msg), 0); + CU_ASSERT_EQUAL(geom->type, TINTYPE); + CU_ASSERT_EQUAL(geom->srid, 4326); + tmp = lwgeom_to_ewkt(geom); + CU_ASSERT_STRING_EQUAL("SRID=4326;TIN(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))", tmp); + lwfree(tmp); + lwgeom_free(geom); + + /* geography support */ + geom = lwgeom_from_wkt("TIN(((0 1 2,3 4 5,6 7 8,0 1 2)))", LW_PARSER_CHECK_NONE); + g = gserialized_from_lwgeom(geom, 0); + CU_ASSERT_EQUAL(gserialized_get_type(g), TINTYPE); + lwgeom_free(geom); + lwfree(g); +} + + +void polyhedralsurface_parse(void) +{ + LWGEOM *geom; + GSERIALIZED *g; + char *tmp; + + cu_error_msg_reset(); /* Because i don't trust that much prior tests... ;) */ + + /* 2 dims */ + geom = lwgeom_from_wkt("POLYHEDRALSURFACE(((0 1,2 3,4 5,0 1)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(strlen(cu_error_msg), 0); + CU_ASSERT_EQUAL(geom->type, POLYHEDRALSURFACETYPE); + tmp = lwgeom_to_hexwkb_buffer(geom, WKB_NDR | WKB_EXTENDED); + CU_ASSERT_STRING_EQUAL("010F00000001000000010300000001000000040000000000000000000000000000000000F03F00000000000000400000000000000840000000000000104000000000000014400000000000000000000000000000F03F", tmp); + lwfree(tmp); + tmp = lwgeom_to_ewkt(geom); + CU_ASSERT_STRING_EQUAL("POLYHEDRALSURFACE(((0 1,2 3,4 5,0 1)))", tmp); + lwfree(tmp); + lwgeom_free(geom); + + /* 3DM */ + geom = lwgeom_from_wkt("POLYHEDRALSURFACEM(((0 1 2,3 4 5,6 7 8,0 1 2)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(strlen(cu_error_msg), 0); + CU_ASSERT_EQUAL(geom->type, POLYHEDRALSURFACETYPE); + tmp = lwgeom_to_ewkt(geom); + CU_ASSERT_STRING_EQUAL("POLYHEDRALSURFACEM(((0 1 2,3 4 5,6 7 8,0 1 2)))", tmp); + lwfree(tmp); + tmp = lwgeom_to_hexwkb_buffer(geom, WKB_NDR | WKB_EXTENDED); + CU_ASSERT_STRING_EQUAL("010F00004001000000010300004001000000040000000000000000000000000000000000F03F000000000000004000000000000008400000000000001040000000000000144000000000000018400000000000001C4000000000000020400000000000000000000000000000F03F0000000000000040", tmp); + lwfree(tmp); + lwgeom_free(geom); + + /* ERROR: a missing Z values */ + geom = lwgeom_from_wkt("POLYHEDRALSURFACE(((0 1 2,3 4 5,6 7,0 1 2)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_STRING_EQUAL("can not mix dimensionality in a geometry", cu_error_msg); + cu_error_msg_reset(); + lwgeom_free(geom); + + /* 1 face with 1 interior ring */ + geom = lwgeom_from_wkt("POLYHEDRALSURFACE(((0 1 2,3 4 5,6 7 8,0 1 2),(9 10 11,12 13 14,15 16 17,9 10 11)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(strlen(cu_error_msg), 0); + CU_ASSERT_EQUAL(geom->type, POLYHEDRALSURFACETYPE); + tmp = lwgeom_to_ewkt(geom); + CU_ASSERT_STRING_EQUAL("POLYHEDRALSURFACE(((0 1 2,3 4 5,6 7 8,0 1 2),(9 10 11,12 13 14,15 16 17,9 10 11)))", tmp); + lwfree(tmp); + tmp = lwgeom_to_hexwkb_buffer(geom, WKB_NDR | WKB_EXTENDED); + CU_ASSERT_STRING_EQUAL("010F00008001000000010300008002000000040000000000000000000000000000000000F03F000000000000004000000000000008400000000000001040000000000000144000000000000018400000000000001C4000000000000020400000000000000000000000000000F03F00000000000000400400000000000000000022400000000000002440000000000000264000000000000028400000000000002A400000000000002C400000000000002E4000000000000030400000000000003140000000000000224000000000000024400000000000002640", tmp); + lwfree(tmp); + lwgeom_free(geom); + + /* ERROR: non closed rings */ + geom = lwgeom_from_wkt("POLYHEDRALSURFACE(((0 1 2,3 4 5,6 7 8,0 0 2)))", LW_PARSER_CHECK_ALL); + CU_ASSERT_STRING_EQUAL("geometry contains non-closed rings", cu_error_msg); + cu_error_msg_reset(); + lwgeom_free(geom); + + /* ERROR: non closed face in Z dim */ + geom = lwgeom_from_wkt("POLYHEDRALSURFACE(((0 1 2,3 4 5,6 7 8,0 1 3)))", LW_PARSER_CHECK_ALL); + CU_ASSERT_STRING_EQUAL("geometry contains non-closed rings", cu_error_msg); + cu_error_msg_reset(); + lwgeom_free(geom); + + /* ERROR: non closed face in Z dim, with a 4D geom */ + geom = lwgeom_from_wkt("POLYHEDRALSURFACE(((0 1 2 3,4 5 6 7,8 9 10 11,0 1 3 3)))", LW_PARSER_CHECK_ALL); + CU_ASSERT_STRING_EQUAL("geometry contains non-closed rings", cu_error_msg); + cu_error_msg_reset(); + lwgeom_free(geom); + + /* ERROR: only 3 points in a face */ + geom = lwgeom_from_wkt("POLYHEDRALSURFACE(((0 1 2,3 4 5,0 1 2)))", LW_PARSER_CHECK_ALL); + CU_ASSERT_STRING_EQUAL("geometry requires more points", cu_error_msg); + cu_error_msg_reset(); + lwgeom_free(geom); + + /* EMPTY face */ + geom = lwgeom_from_wkt("POLYHEDRALSURFACE EMPTY", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(strlen(cu_error_msg), 0); + CU_ASSERT_EQUAL(geom->type, POLYHEDRALSURFACETYPE); + tmp = (char *)lwgeom_to_hexwkb_buffer(geom, WKB_HEX | WKB_ISO | WKB_NDR); + CU_ASSERT_STRING_EQUAL("010F00000000000000", tmp); + lwfree(tmp); + tmp = lwgeom_to_ewkt(geom); + CU_ASSERT_STRING_EQUAL("POLYHEDRALSURFACE EMPTY", tmp); + lwfree(tmp); + lwgeom_free(geom); + + /* A simple tetrahedron */ + geom = lwgeom_from_wkt("POLYHEDRALSURFACE(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(strlen(cu_error_msg), 0); + CU_ASSERT_EQUAL(geom->type, POLYHEDRALSURFACETYPE); + CU_ASSERT_EQUAL(geom->srid, SRID_UNKNOWN); + tmp = lwgeom_to_ewkt(geom); + CU_ASSERT_STRING_EQUAL("POLYHEDRALSURFACE(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))", tmp); + lwfree(tmp); + tmp = lwgeom_to_hexwkb_buffer(geom, WKB_NDR | WKB_EXTENDED); + CU_ASSERT_STRING_EQUAL("010F000080040000000103000080010000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F0000000000000000000000000000000000000000000000000000000000000000010300008001000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F0000000000000000000000000000000000000000000000000000000000000000000000000000000001030000800100000004000000000000000000000000000000000000000000000000000000000000000000F03F0000000000000000000000000000000000000000000000000000000000000000000000000000F03F00000000000000000000000000000000000000000000000001030000800100000004000000000000000000F03F000000000000000000000000000000000000000000000000000000000000F03F000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F00000000000000000000000000000000", tmp); + lwfree(tmp); + lwgeom_free(geom); + + /* A 4D tetrahedron */ + geom = lwgeom_from_wkt("POLYHEDRALSURFACE(((0 0 0 0,0 0 1 0,0 1 0 2,0 0 0 0)),((0 0 0 0,0 1 0 0,1 0 0 4,0 0 0 0)),((0 0 0 0,1 0 0 0,0 0 1 6,0 0 0 0)),((1 0 0 0,0 1 0 0,0 0 1 0,1 0 0 0)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(strlen(cu_error_msg), 0); + CU_ASSERT_EQUAL(geom->type, POLYHEDRALSURFACETYPE); + CU_ASSERT_EQUAL(FLAGS_GET_M(geom->flags), 1); + CU_ASSERT_EQUAL(geom->srid, SRID_UNKNOWN); + tmp = lwgeom_to_ewkt(geom); + CU_ASSERT_STRING_EQUAL("POLYHEDRALSURFACE(((0 0 0 0,0 0 1 0,0 1 0 2,0 0 0 0)),((0 0 0 0,0 1 0 0,1 0 0 4,0 0 0 0)),((0 0 0 0,1 0 0 0,0 0 1 6,0 0 0 0)),((1 0 0 0,0 1 0 0,0 0 1 0,1 0 0 0)))", tmp); + lwfree(tmp); + tmp = lwgeom_to_hexwkb_buffer(geom, WKB_NDR | WKB_EXTENDED); + CU_ASSERT_STRING_EQUAL("010F0000C00400000001030000C00100000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F00000000000000000000000000000000000000000000F03F00000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000001030000C0010000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F00000000000000000000000000000000000000000000F03F000000000000000000000000000000000000000000001040000000000000000000000000000000000000000000000000000000000000000001030000C001000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F0000000000001840000000000000000000000000000000000000000000000000000000000000000001030000C00100000004000000000000000000F03F0000000000000000000000000000000000000000000000000000000000000000000000000000F03F0000000000000000000000000000000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F000000000000000000000000000000000000000000000000", tmp); + lwfree(tmp); + lwgeom_free(geom); + + + /* explicit SRID */ + geom = lwgeom_from_wkt("SRID=4326;POLYHEDRALSURFACE(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))", LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(strlen(cu_error_msg), 0); + CU_ASSERT_EQUAL(geom->type, POLYHEDRALSURFACETYPE); + CU_ASSERT_EQUAL(geom->srid, 4326); + tmp = lwgeom_to_ewkt(geom); + CU_ASSERT_STRING_EQUAL("SRID=4326;POLYHEDRALSURFACE(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))", tmp); + lwfree(tmp); + tmp = lwgeom_to_hexwkb_buffer(geom, WKB_NDR | WKB_EXTENDED); + CU_ASSERT_STRING_EQUAL("010F0000A0E6100000040000000103000080010000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F0000000000000000000000000000000000000000000000000000000000000000010300008001000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000F03F0000000000000000000000000000F03F0000000000000000000000000000000000000000000000000000000000000000000000000000000001030000800100000004000000000000000000000000000000000000000000000000000000000000000000F03F0000000000000000000000000000000000000000000000000000000000000000000000000000F03F00000000000000000000000000000000000000000000000001030000800100000004000000000000000000F03F000000000000000000000000000000000000000000000000000000000000F03F000000000000000000000000000000000000000000000000000000000000F03F000000000000F03F00000000000000000000000000000000", tmp); + lwfree(tmp); + lwgeom_free(geom); + + + /* geography support */ + geom = lwgeom_from_wkt("POLYHEDRALSURFACE(((0 1 2,3 4 5,6 7 8,0 1 2)))", LW_PARSER_CHECK_NONE); + g = gserialized_from_lwgeom(geom, 0); + CU_ASSERT_EQUAL(gserialized_get_type(g), POLYHEDRALSURFACETYPE); + lwgeom_free(geom); + lwfree(g); +} + + +static void +check_dimension(char *ewkt, int dim) +{ + LWGEOM *geom; + + geom = lwgeom_from_wkt(ewkt, LW_PARSER_CHECK_NONE); + CU_ASSERT_EQUAL(strlen(cu_error_msg), 0); + CU_ASSERT_EQUAL(lwgeom_dimensionality(geom), dim); + lwgeom_free(geom); +} + +void +surface_dimension(void) +{ + /* 2D */ + check_dimension("POLYHEDRALSURFACE(((0 0,0 1,1 1,0 0)))", 2); + check_dimension("TIN(((0 0,0 1,1 1,0 0)))", 2); + + /* 3D single face */ + check_dimension("POLYHEDRALSURFACE(((0 0 0,0 0 1,0 1 0,0 0 0)))", 2); + check_dimension("TIN(((0 0 0,0 0 1,0 1 0,0 0 0)))", 2); + + /* Tetrahedron */ + check_dimension("POLYHEDRALSURFACE(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))", 3); + check_dimension("TIN(((0 0 0,0 0 1,0 1 0,0 0 0)),((0 0 0,0 1 0,1 0 0,0 0 0)),((0 0 0,1 0 0,0 0 1,0 0 0)),((1 0 0,0 1 0,0 0 1,1 0 0)))", 3); +} + + +/* +** Used by test harness to register the tests in this file. +*/ +void surface_suite_setup(void); +void surface_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("surface", NULL, NULL); + PG_ADD_TEST(suite, triangle_parse); + PG_ADD_TEST(suite, tin_parse); + PG_ADD_TEST(suite, polyhedralsurface_parse); + PG_ADD_TEST(suite, surface_dimension); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_surface.h b/mgist-postgis/liblwgeom/cunit/cu_surface.h new file mode 100644 index 0000000..011b4ce --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_surface.h @@ -0,0 +1,24 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2010 Olivier Courtin + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "cu_tester.h" + +/* Test functions */ +void triangle_parse(void); +void tin_parse(void); +void polyhedralsurface_parse(void); +void surface_dimension(void); diff --git a/mgist-postgis/liblwgeom/cunit/cu_tester.c b/mgist-postgis/liblwgeom/cunit/cu_tester.c new file mode 100644 index 0000000..2aa1f6a --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_tester.c @@ -0,0 +1,330 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2008 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include "CUnit/Basic.h" +#include "liblwgeom_internal.h" +#include "cu_tester.h" +#include "../postgis_config.h" + +char cu_error_msg[MAX_CUNIT_ERROR_LENGTH + 1] = {0}; + +/* Internal funcs */ +static void +cu_errorreporter(const char *fmt, va_list ap); + +static void +cu_noticereporter(const char *fmt, va_list ap); + +static void +cu_debuglogger(int level, const char *fmt, va_list ap); + + +/* ADD YOUR SUITE SETUP FUNCTION HERE (1 of 2) */ +extern void print_suite_setup(); +extern void algorithms_suite_setup(); +extern void boundary_suite_setup(); +extern void buildarea_suite_setup(); +extern void clean_suite_setup(); +extern void clip_by_rect_suite_setup(); +extern void force_dims_suite_setup(void); +extern void force_sfs_suite_setup(void); +extern void geodetic_suite_setup(void); +extern void geos_suite_setup(void); +extern void geos_cluster_suite_setup(void); +extern void unionfind_suite_setup(void); +extern void homogenize_suite_setup(void); +extern void in_encoded_polyline_suite_setup(void); +extern void in_geojson_suite_setup(void); +extern void iterator_suite_setup(void); +extern void twkb_in_suite_setup(void); +extern void gserialized1_suite_setup(void); +extern void gserialized2_suite_setup(void); +extern void lwstroke_suite_setup(void); +extern void measures_suite_setup(void); +extern void effectivearea_suite_setup(void); +extern void chaikin_suite_setup(void); +extern void filterm_suite_setup(void); +extern void minimum_bounding_circle_suite_setup(void); +extern void misc_suite_setup(void); +extern void node_suite_setup(void); +extern void out_encoded_polyline_suite_setup(void); +extern void out_geojson_suite_setup(void); +extern void out_gml_suite_setup(void); +extern void out_kml_suite_setup(void); +extern void out_svg_suite_setup(void); +extern void twkb_out_suite_setup(void); +extern void out_x3d_suite_setup(void); +extern void ptarray_suite_setup(void); +#if HAVE_SFCGAL +extern void sfcgal_suite_setup(void); +#endif +extern void split_suite_setup(void); +extern void stringbuffer_suite_setup(void); +extern void tree_suite_setup(void); +extern void triangulate_suite_setup(void); +extern void varint_suite_setup(void); +extern void wkt_out_suite_setup(void); +extern void wkb_out_suite_setup(void); +extern void surface_suite_setup(void); +extern void wkb_in_suite_setup(void); +extern void wkt_in_suite_setup(void); +extern void wrapx_suite_setup(void); + + +/* AND ADD YOUR SUITE SETUP FUNCTION HERE (2 of 2) */ +PG_SuiteSetup setupfuncs[] = {algorithms_suite_setup, + boundary_suite_setup, + buildarea_suite_setup, + clean_suite_setup, + clip_by_rect_suite_setup, + force_dims_suite_setup, + force_sfs_suite_setup, + geodetic_suite_setup, + geos_suite_setup, + geos_cluster_suite_setup, + unionfind_suite_setup, + homogenize_suite_setup, + in_encoded_polyline_suite_setup, +#if HAVE_LIBJSON + in_geojson_suite_setup, +#endif + iterator_suite_setup, + twkb_in_suite_setup, + gserialized1_suite_setup, + gserialized2_suite_setup, + lwstroke_suite_setup, + measures_suite_setup, + effectivearea_suite_setup, + chaikin_suite_setup, + filterm_suite_setup, + minimum_bounding_circle_suite_setup, + misc_suite_setup, + node_suite_setup, + out_encoded_polyline_suite_setup, + out_geojson_suite_setup, + out_gml_suite_setup, + out_kml_suite_setup, + out_svg_suite_setup, + out_x3d_suite_setup, + ptarray_suite_setup, + print_suite_setup, +#if HAVE_SFCGAL + sfcgal_suite_setup, +#endif + split_suite_setup, + stringbuffer_suite_setup, + surface_suite_setup, + tree_suite_setup, + triangulate_suite_setup, + twkb_out_suite_setup, + varint_suite_setup, + wkb_in_suite_setup, + wkb_out_suite_setup, + wkt_in_suite_setup, + wkt_out_suite_setup, + wrapx_suite_setup, + NULL}; + + +#define MAX_CUNIT_MSG_LENGTH 256 + +/* +** The main() function for setting up and running the tests. +** Returns a CUE_SUCCESS on successful running, another +** CUnit error code on failure. +*/ +int main(int argc, char *argv[]) +{ + int index; + char *suite_name; + CU_pSuite suite_to_run; + char *test_name; + CU_pTest test_to_run = NULL; + CU_ErrorCode errCode = 0; + CU_pTestRegistry registry; + int num_run; + int num_failed; + PG_SuiteSetup *setupfunc = setupfuncs; + + /* Install the custom error handler */ + lwgeom_set_handlers(0, 0, 0, cu_errorreporter, cu_noticereporter); + lwgeom_set_debuglogger(cu_debuglogger); + + /* Initialize the CUnit test registry */ + if (CUE_SUCCESS != CU_initialize_registry()) + { + errCode = CU_get_error(); + printf(" Error attempting to initialize registry: %d. See CUError.h for error code list.\n", errCode); + return errCode; + } + + /* Register all the test suites. */ + while ( *setupfunc ) + { + (*setupfunc)(); + setupfunc++; + } + + /* Run all tests using the CUnit Basic interface */ + CU_basic_set_mode(CU_BRM_VERBOSE); + if (argc <= 1) + { + errCode = CU_basic_run_tests(); + } + else + { + /* NOTE: The cunit functions used here (CU_get_registry, CU_get_suite_by_name, and CU_get_test_by_name) are + * listed with the following warning: "Internal CUnit system functions. Should not be routinely called by users." + * However, there didn't seem to be any other way to get tests by name, so we're calling them. */ + registry = CU_get_registry(); + for (index = 1; index < argc; index++) + { + suite_name = argv[index]; + test_name = NULL; + suite_to_run = CU_get_suite_by_name(suite_name, registry); + if (NULL == suite_to_run) + { + /* See if it's a test name instead of a suite name. */ + suite_to_run = registry->pSuite; + while (suite_to_run != NULL) + { + test_to_run = CU_get_test_by_name(suite_name, suite_to_run); + if (test_to_run != NULL) + { + /* It was a test name. */ + test_name = suite_name; + suite_name = suite_to_run->pName; + break; + } + suite_to_run = suite_to_run->pNext; + } + } + if (suite_to_run == NULL) + { + printf("\n'%s' does not appear to be either a suite name or a test name.\n\n", suite_name); + } + else + { + if (test_name != NULL && test_to_run != NULL) + { + /* Run only this test. */ + printf("\nRunning test '%s' in suite '%s'.\n", test_name, suite_name); + /* This should be CU_basic_run_test, but that method is broken, see: + * https://sourceforge.net/tracker/?func=detail&aid=2851925&group_id=32992&atid=407088 + * This one doesn't output anything for success, so we have to do it manually. */ + errCode = CU_run_test(suite_to_run, test_to_run); + if (errCode != CUE_SUCCESS) + { + printf(" Error attempting to run tests: %d. See CUError.h for error code list.\n", errCode); + } + else + { + num_run = CU_get_number_of_asserts(); + num_failed = CU_get_number_of_failures(); + printf("\n %s - asserts - %3d passed, %3d failed, %3d total.\n\n", + (0 == num_failed ? "PASSED" : "FAILED"), (num_run - num_failed), num_failed, num_run); + } + } + else + { + /* Run all the tests in the suite. */ + printf("\nRunning all tests in suite '%s'.\n", suite_name); + /* This should be CU_basic_run_suite, but that method is broken, see: + * https://sourceforge.net/tracker/?func=detail&aid=2851925&group_id=32992&atid=407088 + * This one doesn't output anything for success, so we have to do it manually. */ + errCode = CU_run_suite(suite_to_run); + if (errCode != CUE_SUCCESS) + { + printf(" Error attempting to run tests: %d. See CUError.h for error code list.\n", errCode); + } + else + { + num_run = CU_get_number_of_tests_run(); + num_failed = CU_get_number_of_tests_failed(); + printf("\n %s - tests - %3d passed, %3d failed, %3d total.\n", + (0 == num_failed ? "PASSED" : "FAILED"), (num_run - num_failed), num_failed, num_run); + num_run = CU_get_number_of_asserts(); + num_failed = CU_get_number_of_failures(); + printf(" - asserts - %3d passed, %3d failed, %3d total.\n\n", + (num_run - num_failed), num_failed, num_run); + } + } + } + } + /* Presumably if the CU_basic_run_[test|suite] functions worked, we wouldn't have to do this. */ + CU_basic_show_failures(CU_get_failure_list()); + printf("\n\n"); /* basic_show_failures leaves off line breaks. */ + } + num_failed = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failed; +} +/** + * CUnit error handler + * Log message in a global var instead of printing in stderr + * + * CAUTION: Not stop execution on lwerror case !!! + */ +static void +cu_errorreporter(const char *fmt, va_list ap) +{ + vsnprintf (cu_error_msg, MAX_CUNIT_MSG_LENGTH, fmt, ap); + cu_error_msg[MAX_CUNIT_MSG_LENGTH]='\0'; + /*fprintf(stderr, "ERROR: %s\n", cu_error_msg);*/ +} + +static void +cu_noticereporter(const char *fmt, va_list ap) +{ + char buf[MAX_CUNIT_MSG_LENGTH+1]; + vsnprintf (buf, MAX_CUNIT_MSG_LENGTH, fmt, ap); + buf[MAX_CUNIT_MSG_LENGTH]='\0'; + fprintf(stderr, "NOTICE: %s\n", buf); +} + +static void +cu_debuglogger(int level, const char *fmt, va_list ap) +{ + char buf[MAX_CUNIT_MSG_LENGTH+1]; + vsnprintf (buf, MAX_CUNIT_MSG_LENGTH, fmt, ap); + buf[MAX_CUNIT_MSG_LENGTH]='\0'; + fprintf(stderr, "DEBUG%d: %s\n", level, buf); +} + +void +cu_error_msg_reset() +{ + memset(cu_error_msg, '\0', MAX_CUNIT_ERROR_LENGTH); +} + +/* Utility functions for testing */ + +/* do_transformation_test + * - reads input_wkt and expected_wkt + * - asserts output of transfn(input) = expected + * - cleans up + */ +void +do_fn_test(LWGEOM* (*transfn)(LWGEOM*), char *input_wkt, char *expected_wkt) +{ + LWGEOM* input = lwgeom_from_wkt(input_wkt, LW_PARSER_CHECK_NONE); + LWGEOM* expected = lwgeom_from_wkt(expected_wkt, LW_PARSER_CHECK_NONE); + LWGEOM* observed = transfn(input); + + ASSERT_LWGEOM_EQUAL(observed, expected); + + lwgeom_free(input); + lwgeom_free(expected); + lwgeom_free(observed); +} + diff --git a/mgist-postgis/liblwgeom/cunit/cu_tester.h b/mgist-postgis/liblwgeom/cunit/cu_tester.h new file mode 100644 index 0000000..24a0354 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_tester.h @@ -0,0 +1,154 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright (C) 2009 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#ifndef _CU_TESTER_H +#define _CU_TESTER_H 1 + +#include "liblwgeom.h" +#include + +#define MAX_CUNIT_ERROR_LENGTH 512 + +#define PG_ADD_TEST(suite, testfunc) CU_add_test(suite, #testfunc, testfunc) + +/* Contains the most recent error message generated by lwerror. */ +extern char cu_error_msg[]; + +/* Resets cu_error_msg back to blank. */ +void cu_error_msg_reset(void); + +/* Our internal callback to register Suites with the main tester */ +typedef void (*PG_SuiteSetup)(void); + +#define ASSERT_DOUBLE_EQUAL(o,e) do { \ + if ( o != e ) \ + fprintf(stderr, "[%s:%d]\n Expected: %g\n Obtained: %g\n", __FILE__, __LINE__, (double)(e), (o)); \ + CU_ASSERT_EQUAL(o,(double)e); \ +} while (0); + +#define ASSERT_DOUBLE_EQUAL_TOLERANCE(o,e,t) do { \ + if ( fabs(o-e) > t ) \ + fprintf(stderr, "[%s:%d]\n Expected: %g\n Obtained: %g\n Difference: %.15g\n Tolerated: %.15g\n", __FILE__, __LINE__, (double)(e), (o), fabs((o)-(e)), (t)); \ + CU_ASSERT_DOUBLE_EQUAL(o,e,t); \ +} while (0); + +#define ASSERT_INT_EQUAL(o,e) do { \ + if ( o != e ) \ + fprintf(stderr, "[%s:%d]\n Expected: %d\n Obtained: %d\n", __FILE__, __LINE__, (e), (o)); \ + CU_ASSERT_EQUAL(o,e); \ +} while (0); + +#define ASSERT_NORMALIZED_GEOM_SAME(gobt, gexp) \ + do \ + { \ + char *obt, *exp; \ + LWGEOM *ngobt, *ngexp; \ + ngobt = lwgeom_normalize(gobt); \ + ngexp = lwgeom_normalize(gexp); \ + if (!lwgeom_same((ngobt), (ngexp))) \ + { \ + obt = lwgeom_to_wkt((ngobt), WKT_ISO, 8, NULL); \ + exp = lwgeom_to_wkt((ngexp), WKT_ISO, 8, NULL); \ + fprintf(stderr, "[%s:%d]\n Expected: %s\n Obtained: %s\n", __FILE__, __LINE__, exp, obt); \ + free(obt); \ + free(exp); \ + lwgeom_free(ngobt); \ + lwgeom_free(ngexp); \ + CU_ASSERT(0); \ + } \ + else \ + { \ + lwgeom_free(ngobt); \ + lwgeom_free(ngexp); \ + CU_ASSERT(1); \ + } \ + } while (0) + +static inline void +assert_string_equal_impl(const char *obtained, const char *expected, const char *file, int line) +{ + CU_BOOL error = (!obtained && expected) || (obtained && !expected) || (strcmp(obtained, expected) != 0); + char *msg = NULL; + if (error) + { + msg = lwalloc(60 + (obtained ? strlen(obtained) : 4) + (expected ? strlen(expected) : 4)); + sprintf(msg, + "ASSERT_STRING_EQUAL\n\t* Expected: %s\n\t* Obtained: %s", + expected ? expected : "NULL", + obtained ? obtained : "NULL"); + } + CU_assertImplementation(!error, line, msg, file, NULL, CU_FALSE); + if (msg) + lwfree(msg); +} + +#define ASSERT_STRING_EQUAL(o, e) assert_string_equal_impl(o, e, __FILE__, __LINE__) + +#define ASSERT_VARLENA_EQUAL(v, s) \ + do \ + { \ + if (strncmp(v->data, s, LWSIZE_GET(v->size) - LWVARHDRSZ) != 0) \ + { \ + fprintf( \ + stderr, "[%s:%d]\n Expected: %s\n Obtained: %s\n", __FILE__, __LINE__, (s), (v->data)); \ + CU_FAIL(); \ + } \ + else \ + CU_PASS(); \ + } while (0); + +#define ASSERT_LWGEOM_EQUAL(o, e) do { \ + if ( !lwgeom_same(o, e) ) { \ + char* wkt_o = lwgeom_to_ewkt(o); \ + char* wkt_e = lwgeom_to_ewkt(e); \ + fprintf(stderr, "[%s:%d]\n Expected: %s\n Obtained: %s\n", __FILE__, __LINE__, (wkt_o), (wkt_e)); \ + lwfree(wkt_o); \ + lwfree(wkt_e); \ + } \ + CU_ASSERT_TRUE(lwgeom_same(o, e)); \ +} while(0); + +#define ASSERT_INTARRAY_EQUAL(o, e, n) do { \ + size_t i = 0; \ + for (i = 0; i < n; i++) { \ + if (o[i] != e[i]) { \ + fprintf(stderr, "[%s:%d]", __FILE__, __LINE__); \ + fprintf(stderr, "\nExpected: ["); \ + for (i = 0; i < n; i++) \ + fprintf(stderr, " %d", e[i]); \ + fprintf(stderr, " ]\nObtained: ["); \ + for (i = 0; i < n; i++) \ + fprintf(stderr, " %d", o[i]); \ + fprintf(stderr, " ]\n"); \ + CU_FAIL(); \ + break; \ + } \ + } \ + CU_PASS(); \ +} while(0); + +#define ASSERT_POINT2D_EQUAL(o, e, eps) do { \ + CU_ASSERT_DOUBLE_EQUAL(o.x, e.x, eps); \ + CU_ASSERT_DOUBLE_EQUAL(o.y, e.y, eps); \ +} while(0); + +#define ASSERT_POINT4D_EQUAL(o, e, eps) do { \ + CU_ASSERT_DOUBLE_EQUAL(o.x, e.x, eps); \ + CU_ASSERT_DOUBLE_EQUAL(o.y, e.y, eps); \ + CU_ASSERT_DOUBLE_EQUAL(o.z, e.z, eps); \ + CU_ASSERT_DOUBLE_EQUAL(o.m, e.m, eps); \ +} while(0); + +/* Utility functions */ +void do_fn_test(LWGEOM* (*transfn)(LWGEOM*), char *input_wkt, char *expected_wkt); + +#endif /* _CU_TESTER_H */ diff --git a/mgist-postgis/liblwgeom/cunit/cu_tree.c b/mgist-postgis/liblwgeom/cunit/cu_tree.c new file mode 100644 index 0000000..ce46c1c --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_tree.c @@ -0,0 +1,434 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright 2011 Sandro Santilli + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" + +#include "liblwgeom_internal.h" +#include "lwgeodetic.h" +#include "lwgeodetic_tree.h" +#include "cu_tester.h" + + +static void test_tree_circ_create(void) +{ + LWLINE *g; + CIRC_NODE *c; + /* Line with 4 edges */ + g = lwgeom_as_lwline(lwgeom_from_wkt("LINESTRING(0 88,0 89,0 90,180 89,180 88)", LW_PARSER_CHECK_NONE)); + c = circ_tree_new(g->points); + //circ_tree_print(c, 0); + + if ( CIRC_NODE_SIZE > 4 ) + { + CU_ASSERT(c->num_nodes == 4); + } + else + { + CU_ASSERT(c->num_nodes == ( 4 % CIRC_NODE_SIZE ? 1 : 0 ) + 4 / CIRC_NODE_SIZE); + } + + circ_tree_free(c); + lwline_free(g); +} + + +static void test_tree_circ_pip(void) +{ + LWLINE *g; + CIRC_NODE *c; + POINT2D pt, pt_outside; + int rv, on_boundary; + + pt.x = 0.0; + pt.y = 0.0; + pt_outside.x = -2.0; + pt_outside.y = 0.0; + + /* Point in square */ + g = lwgeom_as_lwline(lwgeom_from_wkt("LINESTRING(-1 -1,1 -1,1 1,-1 1,-1 -1)", LW_PARSER_CHECK_NONE)); + c = circ_tree_new(g->points); + rv = circ_tree_contains_point(c, &pt, &pt_outside, 0, &on_boundary); + CU_ASSERT_EQUAL(rv, 1); + + /* Point on other side of square */ + pt.x = 2.0; + pt.y = 0.0; + rv = circ_tree_contains_point(c, &pt, &pt_outside, 0, &on_boundary); + CU_ASSERT_EQUAL(rv, 0); + + /* Clean and do new shape */ + circ_tree_free(c); + lwline_free(g); + + /* Point in square, stab passing through vertex */ + pt.x = 0.0; + pt.y = 0.0; + g = lwgeom_as_lwline(lwgeom_from_wkt("LINESTRING(-1 -1,0 -1,1 -1,1 0,1 1,0 1,-1 1,-1 0,-1 -1)", LW_PARSER_CHECK_NONE)); + c = circ_tree_new(g->points); + //circ_tree_print(c, 0); + rv = circ_tree_contains_point(c, &pt, &pt_outside, 0, &on_boundary); + CU_ASSERT_EQUAL(rv, 1); + + /* Point on other side of square, stab passing through vertex */ + pt.x = 2.0; + pt.y = 0.0; + rv = circ_tree_contains_point(c, &pt, &pt_outside, 0, &on_boundary); + CU_ASSERT_EQUAL(rv, 0); + + /* Clean and do new shape */ + circ_tree_free(c); + lwline_free(g); + + /* Point outside "w" thing, stab passing through vertexes and grazing pointy thing */ + pt.x = 2.0; + pt.y = 0.0; + g = lwgeom_as_lwline(lwgeom_from_wkt("LINESTRING(-1 -1,0 -1,1 -1,1 0,1 1,0 0,-1 1,-1 0,-1 -1)", LW_PARSER_CHECK_NONE)); + c = circ_tree_new(g->points); + //circ_tree_print(c, 0); + rv = circ_tree_contains_point(c, &pt, &pt_outside, 0, &on_boundary); + //printf("rv %d\n", rv); + CU_ASSERT_EQUAL(rv, 0); + + /* Point inside "w" thing, stab passing through vertexes and grazing pointy thing */ + pt.x = 0.8; + pt.y = 0.0; + rv = circ_tree_contains_point(c, &pt, &pt_outside, 0, &on_boundary); + //printf("rv %d\n", rv); + CU_ASSERT_EQUAL(rv, 1); + + /* Clean and do new shape */ + circ_tree_free(c); + lwline_free(g); + +} + +static void test_tree_circ_pip2(void) +{ + LWGEOM* g; + LWPOLY* p; + LWPOINT* lwpt; + int rv_classic, rv_tree, on_boundary; + POINT2D pt, pt_outside; + GBOX gbox; + CIRC_NODE *c; + + g = lwgeom_from_wkt("POLYGON((0 0,0 1,1 1,1 0,0 0))", LW_PARSER_CHECK_NONE); + p = lwgeom_as_lwpoly(g); + + pt.x = 0.2; + pt.y = 0.1; + lwgeom_calculate_gbox_geodetic(g, &gbox); + gbox_pt_outside(&gbox, &pt_outside); + c = circ_tree_new(p->rings[0]); + //circ_tree_print(c, 0); + rv_classic = lwpoly_covers_point2d(p, &pt); + rv_tree = circ_tree_contains_point(c, &pt, &pt_outside, 0, &on_boundary); + CU_ASSERT_EQUAL(rv_tree, rv_classic); + circ_tree_free(c); + lwgeom_free(g); + + g = lwgeom_from_hexwkb("0103000020E6100000010000004700000000000000621119C000000040C70C4B40000000E0CC6C18C0000000A026FF4A4000000040438519C000000000E8F44A40000000000F5318C00000004024C84A4000000060F9E518C0000000A027AD4A40000000805E0D18C0000000C0F5784A4000000040539718C000000080815E4A40000000C026FE19C0000000C0502D4A4000000060127019C000000040EA164A40000000003BFD1BC0000000609E234A4000000080D9011CC000000060B9114A4000000040C8501EC0000000C0D50C4A40000000C05F2C20C000000040C9E749400000006008D820C000000080D6F0494000000080139F20C000000060F3DE4940000000A0B16421C0000000C059C94940000000808FA223C0000000C007B949400000000041E722C000000080C3DC4940000000808F4224C0000000405DCE494000000060752923C000000040A9EF4940000000005CAD24C0000000C036E4494000000040F88624C00000008078FE494000000060558523C00000006025134A40000000403AED24C00000000011174A40000000A05E7D23C0000000E0A41F4A4000000040F0AD23C0000000809A304A4000000040A64E23C000000040C9474A40000000C0FCA221C0000000C030554A40000000805EDD23C0000000E010474A4000000040BFF822C00000008078664A4000000080C98F22C000000040E2914A40000000C036E021C00000002024924A4000000080D9E121C000000000D0A14A4000000040533723C000000040B99D4A40000000204B1E23C0000000C0CCB04A4000000000625A24C0000000A071B44A40000000004A5F24C0000000806FC64A40000000E0DD6523C00000006088CC4A400000004012D023C0000000001AE14A40000000806E1F23C0000000400BEE4A40000000E0A2E123C0000000C017EF4A4000000060449423C00000004003F94A40000000C0DC0624C0000000A0ED1B4B40000000A0803F24C0000000005D0C4B4000000040753924C000000080701D4B400000002021F320C00000000001234B4000000000C65221C000000080792D4B40000000406D6020C0000000001A514B4000000040BF9821C0000000A00E594B400000000031B520C0000000C0726B4B400000002019EA20C000000020977F4B400000000002ED1FC0000000E0B49B4B400000000084CC1EC0000000602D8C4B4000000020BB2A1FC000000060239B4B4000000040AE871EC0000000A0FDA14B400000008077771EC0000000C0E5864B40000000C0AABA1EC000000000B7794B40000000C03EC91DC0000000E020874B4000000000A4301EC0000000C0C49B4B4000000000B5811DC0000000A0A3B04B400000004095BD1BC000000020869E4B400000004091021DC00000004009894B40000000409D361EC000000080A2614B40000000809FB41FC0000000A0AB594B40000000C046021FC0000000C0164C4B40000000C0EC5020C0000000E05A384B4000000040DF3C1EC0000000803F104B4000000000B4221DC0000000C0CD0F4B40000000C0261E1CC00000006067354B4000000080E17A1AC000000080C3044B4000000000621119C000000040C70C4B40", LW_PARSER_CHECK_NONE); + p = lwgeom_as_lwpoly(g); + lwpt = (LWPOINT*)lwgeom_from_hexwkb("0101000020E610000057B89C28FEB320C09C8102CB3B2B4A40", LW_PARSER_CHECK_NONE); + lwpoint_getPoint2d_p(lwpt, &pt); + lwgeom_calculate_gbox_geodetic(g, &gbox); + gbox_pt_outside(&gbox, &pt_outside); + //printf("OUTSIDE POINT(%g %g)\n", pt_outside.x, pt_outside.y); + c = circ_tree_new(p->rings[0]); + rv_classic = lwpoly_covers_point2d(p, &pt); + rv_tree = circ_tree_contains_point(c, &pt, &pt_outside, 0, &on_boundary); + CU_ASSERT_EQUAL(rv_tree, rv_classic); + circ_tree_free(c); + lwpoint_free(lwpt); + lwgeom_free(g); + + g = lwgeom_from_hexwkb("0103000020E610000001000000CF0100000000000000004EC03943F5FFFF7F56400000000000003E403943F5FFFF7F56400000000000003E401842CEFBFF7F56400000000000003E402F849CF7FF7F56400000000000003E4047C66AF3FF7F56400000000000003E405F0839EFFF7F56400000000000003E40774A07EBFF7F56400000000000003E408F8CD5E6FF7F56400000000000003E40A6CEA3E2FF7F56400000000000003E40D65240DAFF7F56400000000000003E4006D7DCD1FF7F56400000000000003E40355B79C9FF7F56400000000000003E407D21E4BCFF7F56400000000000003E40DC291DACFF7F56400000000000003E403B32569BFF7F56400000000000003E40CABE2B82FF7F56400000000000003E40594B0169FF7F56400000000000003E40309E4143FF7F56400000000000003E401E335019FF7F56400000000000003E403C4CFBE6FE7F56400000000000003E40B96DDFA3FE7F56400000000000003E407E552E54FE7F56400000000000003E40A245B6F3FD7F56400000000000003E403D80457EFD7F56400000000000003E407E8978EBFC7F56400000000000003E407FA31D37FC7F56400000000000003E405510035DFB7F56400000000000003E404A969350FA7F56400000000000003E40BC3D0801F97F56400000000000003E40C3482F6AF77F56400000000000003E40D5011077F57F56400000000000003E406CB3B112F37F56400000000000003E402C2CB81FF07F56400000000000003E40A4F8F884EC7F56400000000000003E40C5AD8218E87F56400000000000003E40C1A6CEA3E27F56400000000000003E40A1BAB9F8DB7F56400000000000003E40401361C3D37F56400000000000003E40639813B4C97F56400000000000003E408D429259BD7F56400000000000003E40B854A52DAE7F56400000000000003E406F9EEA909B7F56400000000000003E403FC6DCB5847F56400000000000003E408FC536A9687F56400000000000003E402975C938467F56400000000000003E403F8D7BF31B7F56400000000000003E40F4311F10E87E56400000000000003E40C1FBAA5CA87E56400000000000003E40D2FF722D5A7E56400000000000003E4009A7052FFA7D56400000000000003E403332C85D847D56400000000000003E40B35F77BAF37C56400000000000003E40253ACB2C427C56400000000000003E40FA7B293C687B56400000000000003E407D3D5FB35C7A56400000000000003E40E3A25A44147956400000000000003E40A0504F1F817756400000000000003E4062855B3E927556400000000000003E40B62BF4C1327356400000000000003E40280D350A497056400000000000003E4061DC0DA2B56C56400000000000003E40B81E85EB516856400000000000003E403237DF88EE6256400000000000003E4041EE224C515C56400000000000003E409EE925C6325456400000000000003E400B2593533B4A56400000000000003E4089CF9D60FF3D56400000000000003E40F04A92E7FA2E56400000000000003E40AC3594DA8B1C56400000000000003E40C9022670EB0556400000000000003E4069A510C825EA55400000000000003E4033DC80CF0FC855400000000000003E40FF907EFB3A9E55400000000000003E404203B16CE66A55400000000000003E40DA39CD02ED2B55400000000000003E4070404B57B0DE54400000000000003E4000000000008054403333333333B33D4000000000008054406666666666663D4000000000008054409999999999193D400000000000805440CCCCCCCCCCCC3C4000000000008054400000000000803C4000000000008054403333333333333C4000000000008054406666666666E63B4000000000008054409999999999993B400000000000805440CCCCCCCCCC4C3B4000000000008054400000000000003B4000000000008054403333333333B33A4000000000008054406666666666663A4000000000008054409999999999193A400000000000805440CCCCCCCCCCCC3940000000000080544000000000008039400000000000805440333333333333394000000000008054406666666666E63840000000000080544099999999999938400000000000805440CCCCCCCCCC4C38400000000000805440000000000000384000000000008054403333333333B3374000000000008054406666666666663740000000000080544099999999991937400000000000805440CDCCCCCCCCCC3640000000000080544000000000008036400000000000805440333333333333364000000000008054406666666666E63540000000000080544099999999999935400000000000805440CDCCCCCCCC4C35400000000000805440000000000000354000000000008054403333333333B3344000000000008054406666666666663440000000000080544099999999991934400000000000805440CDCCCCCCCCCC3340000000000080544000000000008033400000000000805440333333333333334000000000008054406666666666E63240000000000080544099999999999932400000000000805440CDCCCCCCCC4C32400000000000805440000000000000324000000000008054403333333333B3314000000000008054406666666666663140000000000080544099999999991931400000000000805440CDCCCCCCCCCC304000000000008054400000000000803040000000000080544033333333333330400000000000805440CCCCCCCCCCCC2F4000000000008054403333333333332F4000000000008054409999999999992E4000000000008054400000000000002E4000000000008054406666666666662D400000000000805440CCCCCCCCCCCC2C4000000000008054403333333333332C4000000000008054409999999999992B4000000000008054400000000000002B4000000000008054406666666666662A400000000000805440CCCCCCCCCCCC2940000000000080544033333333333329400000000000805440999999999999284000000000008054400000000000002840000000000080544066666666666627400000000000805440CDCCCCCCCCCC2640000000000080544033333333333326400000000000805440999999999999254000000000008054400000000000002540000000000080544066666666666624400000000000805440CDCCCCCCCCCC2340000000000080544033333333333323400000000000805440999999999999224000000000008054400000000000002240000000000080544066666666666621400000000000805440CDCCCCCCCCCC20400000000000805440333333333333204000000000008054403333333333331F4000000000008054400000000000001E400000000000805440CCCCCCCCCCCC1C4000000000008054409999999999991B4000000000008054406666666666661A4000000000008054403333333333331940000000000080544000000000000018400000000000805440CDCCCCCCCCCC1640000000000080544099999999999915400000000000805440666666666666144000000000008054403333333333331340000000000080544000000000000012400000000000805440CDCCCCCCCCCC104000000000008054403333333333330F400000000000805440CCCCCCCCCCCC0C4000000000008054406666666666660A400000000000805440000000000000084000000000008054409999999999990540000000000080544033333333333303400000000000805440CDCCCCCCCCCC00400000000000805440CCCCCCCCCCCCFC3F0000000000805440000000000000F83F0000000000805440333333333333F33F0000000000805440CCCCCCCCCCCCEC3F0000000000805440333333333333E33F0000000000805440333333333333D33F00000000008054400000000000000000000000000080544000000000000000002174D0251C7C54400000000000000000F96871C6307854400000000000000000E6E61BD13D745440000000000000000019726C3D437054400000000000000000F0129CFA406C54400000000000000000CCD1E3F7366854400000000000000000F374AE28256454400000000000000000ACC266800B6054400000000000000000700514EAE95B544000000000000000006EC1525DC057544000000000000000001C412AC58E5354400000000000000000AB083719554F5440000000000000000091628044134B544000000000000000001615713AC94654400000000000000000992842EA7642544000000000000000007AA52C431C3E5440000000000000000000529B38B939544000000000000000008B36C7B94D3554400000000000000000795BE9B5D9305440000000000000000029C93A1C5D2C54400000000000000000E54526E0D72754400000000000000000211CB3EC49235440000000000000000027124C35B31E5440000000000000000055302AA9131A544000000000000000000A7F86376B1554400000000000000000BE4868CBB91054400000000000000000A1116C5CFF0B54400000000000000000406667D13B0754400000000000000000E50CC51D6F0254400000000000000000ED0DBE3099FD53400000000000000000D0B359F5B9F853400000000000000000D6C4025FD1F353400000000000000000768BC058DFEE534000000000000000000E10CCD1E3E953400000000000000000FF5A5EB9DEE453400000000000000000A774B0FECFDF534000000000000000006665FB90B7DA53400000000000000000CBB9145795D55340000000000000000005F6984869D053400000000000000000BCE82B4833CB534000000000000000001E166A4DF3C553400000000000000000A3C85A43A9C053400000000000000000DA8CD31055BB53400000000000000000F3E670ADF6B5534000000000000000007C6308008EB053400000000000000000EC4CA1F31AAB534000000000000000008B69A67B9DA553400000000000000000E945ED7E15A0534000000000000000004CA8E0F0829A53400000000000000000431D56B8E5945340000000000000000045EF54C03D8F534000000000000000009BE447FC8A8953400000000000000000D2890453CD83534000000000000000004BE7C3B3047E5340000000000000000093895B053178534000000000000000000B79043752725340000000000000000012BEF737686C5340000000000000000036E50AEF726653400000000000000000EF3845477260534000000000000000009CC1DF2F665A53400000000000000000CC0BB08F4E5453400000000000000000DE1FEF552B4E53400000000000000000618A7269FC4753400000000000000000B45373B9C141534000000000000000006708C72C7B3B53400000000000000000D9B0A6B228355340000000000000000098D9E731CA2E53400000000000000000340F60915F2853400000000000000000F4177AC4E821534000000000000000007EC2D9AD651B534000000000000000003317B83CD61453400000000000000000A0A2EA573A0E5340000000000000000056F146E6910753400000000000000000B20B06D7DC0053400000000000000000457EFD101BFA524000000000000000008593347F4CF352400000000000000000191A4F0471EC5240000000000000000049D8B79388E552400000000000000000BA9C121093DE52400000000000000000E6B1666490D75240000000000000000059A4897780D0524000000000000000008CBE823463C9524000000000000000000D8D278238C2524000000000000000006B9C4D4700BB524000000000000000001D37FC6EBAB352400000000000000000B3E908E066AC52400000000000000000A4FE7A8505A5524000000000000000009544F641969D52400000000000000000FF05820019965240000000000000000070CFF3A78D8E52400000000000000000772D211FF486524000000000000000008B6A11514C7F52400000000000000000545568209677524000000000000000005F7AFB73D16F524000000000000000002424D236FE675240000000000000000033DFC14F1C6052400000000000000000317A6EA12B5852400000000000000000963FDF162C505240000000000000000008FEB7921D48524000000000000000000000000000405240999999999999C9BF0000000000405240999999999999D9BF0000000000405240333333333333E3BF0000000000405240999999999999E9BF0000000000405240000000000000F0BF0000000000405240333333333333F3BF0000000000405240666666666666F6BF0000000000405240999999999999F9BF0000000000405240CCCCCCCCCCCCFCBF000000000040524000000000000000C0000000000040524099999999999901C0000000000040524033333333333303C00000000000405240CDCCCCCCCCCC04C0000000000040524066666666666606C0000000000040524000000000000008C0000000000040524099999999999909C000000000004052403333333333330BC00000000000405240CCCCCCCCCCCC0CC000000000004052406666666666660EC0000000000040524000000000000010C00000000000405240CDCCCCCCCCCC10C0000000000040524099999999999911C0000000000040524066666666666612C0000000000040524033333333333313C0000000000040524000000000000014C00000000000405240CDCCCCCCCCCC14C0000000000040524099999999999915C0000000000040524066666666666616C0000000000040524033333333333317C0000000000040524000000000000018C00000000000405240CCCCCCCCCCCC18C0000000000040524099999999999919C000000000004052406666666666661AC000000000004052403333333333331BC000000000004052400000000000001CC00000000000405240CCCCCCCCCCCC1CC000000000004052409999999999991DC000000000004052406666666666661EC000000000004052403333333333331FC0000000000040524000000000000020C0000000000040524066666666666620C00000000000405240CDCCCCCCCCCC20C0000000000040524033333333333321C0000000000040524099999999999921C0000000000040524000000000000022C0000000000040524066666666666622C00000000000405240CDCCCCCCCCCC22C0000000000040524033333333333323C0000000000040524099999999999923C0000000000040524000000000000024C0000000000040524066666666666624C00000000000405240CDCCCCCCCCCC24C0000000000040524033333333333325C0000000000040524099999999999925C0000000000040524000000000000026C0000000000040524066666666666626C00000000000405240CDCCCCCCCCCC26C0000000000040524033333333333327C0000000000040524099999999999927C0000000000040524000000000000028C0000000000040524066666666666628C00000000000405240CCCCCCCCCCCC28C0000000000040524033333333333329C0000000000040524099999999999929C000000000004052400000000000002AC000000000004052406666666666662AC00000000000405240CCCCCCCCCCCC2AC000000000004052403333333333332BC000000000004052409999999999992BC000000000004052400000000000002CC000000000004052406666666666662CC00000000000405240CCCCCCCCCCCC2CC000000000004052403333333333332DC000000000004052409999999999992DC000000000004052400000000000002EC000000000004052406666666666662EC00000000000405240CCCCCCCCCCCC2EC000000000004052403333333333332FC000000000004052409999999999992FC0000000000040524000000000000030C0000000000040524033333333333330C0000000000040524066666666666630C0000000000040524099999999999930C00000000000405240CDCCCCCCCCCC30C0000000000040524000000000000031C0000000000040524033333333333331C0000000000040524066666666666631C0000000000040524099999999999931C00000000000405240CDCCCCCCCCCC31C0000000000040524000000000000032C0000000000040524033333333333332C0000000000040524066666666666632C0000000000040524099999999999932C00000000000405240CDCCCCCCCCCC32C0000000000040524000000000000033C0000000000040524033333333333333C0000000000040524066666666666633C0000000000040524099999999999933C00000000000405240CDCCCCCCCCCC33C0000000000040524000000000000034C0000000000040524000000000000034C0000000000080514000000000008043C00000000000C04F4000000000008045C00000000000404D4030116F9D7F8B45C00000000000404D408FA67A32FF9645C00000000000404D40BFB7E9CF7EA245C00000000000404D401E4DF564FEAD45C00000000000404D404E5E64027EB945C00000000000404D40AEF36F97FDC445C00000000000404D40DD04DF347DD045C00000000000404D403D9AEAC9FCDB45C00000000000404D406DAB59677CE745C00000000000404D40CC4065FCFBF245C00000000000404D40FC51D4997BFE45C00000000000404D405BE7DF2EFB0946C00000000000404D408BF84ECC7A1546C00000000000404D40EB8D5A61FA2046C00000000000404D401B9FC9FE792C46C00000000000404D407A34D593F93746C00000000000404D40AA454431794346C00000000000404D40DA56B3CEF84E46C00000000000404D4039ECBE63785A46C00000000000404D4069FD2D01F86546C00000000000404D40C8923996777146C00000000000404D40F8A3A833F77C46C00000000000404D405839B4C8768846C00000000000404D40884A2366F69346C00000000000404D40E7DF2EFB759F46C00000000000404D4017F19D98F5AA46C00000000000404D407686A92D75B646C00000000000404D40A69718CBF4C146C00000000000404D40062D246074CD46C00000000000404D40353E93FDF3D846C00000000000404D4095D39E9273E446C00000000000404D40C5E40D30F3EF46C00000000000404D40247A19C572FB46C00000000000404D40548B8862F20647C00000000000404D40B42094F7711247C00000000000404D40E3310395F11D47C00000000000404D4013437232712947C00000000000404D4073D87DC7F03447C00000000000404D40A2E9EC64704047C00000000000404D40027FF8F9EF4B47C00000000000404D40329067976F5747C00000000000404D409125732CEF6247C00000000000404D40C136E2C96E6E47C00000000000404D4021CCED5EEE7947C00000000000404D4050DD5CFC6D8547C00000000000404D40B0726891ED9047C00000000000404D40E083D72E6D9C47C00000000000404D403F19E3C3ECA747C00000000000404D406F2A52616CB347C00000000000404D40CEBF5DF6EBBE47C00000000000404D40FED0CC936BCA47C00000000000404D405E66D828EBD547C00000000000404D408E7747C66AE147C00000000000404D40BD88B663EAEC47C00000000000404D401D1EC2F869F847C00000000000404D404D2F3196E90348C00000000000404D40ACC43C2B690F48C00000000000404D40DCD5ABC8E81A48C00000000000404D403B6BB75D682648C00000000000404D406B7C26FBE73148C00000000000404D40CB113290673D48C00000000000404D40FB22A12DE74848C00000000000404D405AB8ACC2665448C00000000000404D408AC91B60E65F48C00000000000404D40E95E27F5656B48C00000000000404D4019709692E57648C00000000000404D407905A227658248C00000000000404D40A81611C5E48D48C00000000000404D4008AC1C5A649948C00000000000404D4038BD8BF7E3A448C00000000000404D4068CEFA9463B048C00000000000404D40C763062AE3BB48C00000000000404D40F77475C762C748C00000000000404D40560A815CE2D248C00000000000404D40861BF0F961DE48C00000000000404D40E6B0FB8EE1E948C00000000000404D4015C26A2C61F548C00000000000404D4000000000000049C00000000000404D400000000000E04CC0000000000040504000000000000053C000000000000053400000000000C052C000000000008053400000000000004EC000000000008054400000000000004EC03943F5FFFF7F5640", LW_PARSER_CHECK_NONE); + p = lwgeom_as_lwpoly(g); + lwpt = (LWPOINT*)lwgeom_from_hexwkb("0101000020E610000031B1F9B836A046C03C889D2974124E40", LW_PARSER_CHECK_NONE); + lwpoint_getPoint2d_p(lwpt, &pt); + lwgeom_calculate_gbox_geodetic(g, &gbox); + gbox_pt_outside(&gbox, &pt_outside); + //printf("OUTSIDE POINT(%g %g)\n", pt_outside.x, pt_outside.y); + c = circ_tree_new(p->rings[0]); + rv_classic = lwpoly_covers_point2d(p, &pt); + rv_tree = circ_tree_contains_point(c, &pt, &pt_outside, 0, &on_boundary); + CU_ASSERT_EQUAL(rv_tree, rv_classic); + circ_tree_free(c); + lwpoint_free(lwpt); + lwgeom_free(g); + +} + + +static void test_tree_circ_distance(void) +{ + LWGEOM *lwg1, *lwg2; + CIRC_NODE *c1, *c2; + SPHEROID s; + double d1, d2, d3, d4, e1, e2; + double threshold = 0.0; + + spheroid_init(&s, 1.0, 1.0); + + /* Ticket #4223 */ + /* tall skinny rectangle */ + lwg1 = lwgeom_from_wkt("POLYGON((58.5112113206308 0,58.5112113200772 0.000901937525203378,58.511300910044 0.000901937636668872,58.5113009105976 0,58.5112113206308 0))", LW_PARSER_CHECK_NONE); + /* square box 5m to left */ + lwg2 = lwgeom_from_wkt("POLYGON((58.5111665256017 0.000270581240841207,58.5111665255629 0.000360774987788249,58.5110769356128 0.000360774943200728,58.5110769356515 0.000270581207400566,58.5111665256017 0.000270581240841207))", LW_PARSER_CHECK_NONE); + c1 = lwgeom_calculate_circ_tree(lwg1); + c2 = lwgeom_calculate_circ_tree(lwg2); + d1 = circ_tree_distance_tree(c1, c2, &s, threshold); + d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold); + e1 = d1 * WGS84_RADIUS; + e2 = d2 * WGS84_RADIUS; + // printf("d1 = %g d2 = %g\n", d1, d2); + // printf("e1 = %g e2 = %g\n", e1, e2); + // printf("polygon a\n"); + // circ_tree_print(c1, 0); + // printf("polygon b\n"); + // circ_tree_print(c2, 0); + circ_tree_free(c1); + circ_tree_free(c2); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + CU_ASSERT_DOUBLE_EQUAL(e1, e2, 0.0001); + + + /* Ticket #1958 */ + lwg1 = lwgeom_from_wkt("LINESTRING(22.88333 41.96667,21.32667 42.13667)", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("POLYGON((22.94472 41.34667,22.87528 41.99028,22.87389 41.98472,22.87472 41.98333,22.94472 41.34667))", LW_PARSER_CHECK_NONE); + c1 = lwgeom_calculate_circ_tree(lwg1); + c2 = lwgeom_calculate_circ_tree(lwg2); + d1 = circ_tree_distance_tree(c1, c2, &s, threshold); + d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold); +// printf("d1 = %g d2 = %g\n", d1 * WGS84_RADIUS, d2 * WGS84_RADIUS); +// printf("line\n"); +// circ_tree_print(c1, 0); +// printf("poly\n"); +// circ_tree_print(c2, 0); + circ_tree_free(c1); + circ_tree_free(c2); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + CU_ASSERT_DOUBLE_EQUAL(d1, d2, 0.0000001); + + /* Ticket #1951 */ + lwg1 = lwgeom_from_wkt("LINESTRING(0 0, 0 0)", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("POINT(0.1 0.1)", LW_PARSER_CHECK_NONE); + c1 = lwgeom_calculate_circ_tree(lwg1); + c2 = lwgeom_calculate_circ_tree(lwg2); + d1 = circ_tree_distance_tree(c1, c2, &s, threshold); + d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold); + circ_tree_free(c1); + circ_tree_free(c2); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + CU_ASSERT_DOUBLE_EQUAL(d1, d2, 0.00001); + + lwg1 = lwgeom_from_wkt("LINESTRING(-1 -1,0 -1,1 -1,1 0,1 1,0 0,-1 1,-1 0,-1 -1)", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("POINT(-2 0)", LW_PARSER_CHECK_NONE); + c1 = lwgeom_calculate_circ_tree(lwg1); + c2 = lwgeom_calculate_circ_tree(lwg2); + d1 = circ_tree_distance_tree(c1, c2, &s, threshold); + d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold); + circ_tree_free(c1); + circ_tree_free(c2); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + CU_ASSERT_DOUBLE_EQUAL(d1, d2, 0.00001); + + lwg1 = lwgeom_from_wkt("LINESTRING(-1 -1,0 -1,1 -1,1 0,1 1,0 0,-1 1,-1 0,-1 -1)", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("POINT(2 2)", LW_PARSER_CHECK_NONE); + c1 = lwgeom_calculate_circ_tree(lwg1); + c2 = lwgeom_calculate_circ_tree(lwg2); + d1 = circ_tree_distance_tree(c1, c2, &s, threshold); + d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold); + circ_tree_free(c1); + circ_tree_free(c2); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + CU_ASSERT_DOUBLE_EQUAL(d1, d2, 0.00001); + + lwg1 = lwgeom_from_wkt("LINESTRING(-1 -1,0 -1,1 -1,1 0,1 1,0 0,-1 1,-1 0,-1 -1)", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("POINT(1 1)", LW_PARSER_CHECK_NONE); + c1 = lwgeom_calculate_circ_tree(lwg1); + c2 = lwgeom_calculate_circ_tree(lwg2); + d1 = circ_tree_distance_tree(c1, c2, &s, threshold); + d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold); + circ_tree_free(c1); + circ_tree_free(c2); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + CU_ASSERT_DOUBLE_EQUAL(d1, d2, 0.00001); + + lwg1 = lwgeom_from_wkt("LINESTRING(-1 -1,0 -1,1 -1,1 0,1 1,0 0,-1 1,-1 0,-1 -1)", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("POINT(1 0.5)", LW_PARSER_CHECK_NONE); + c1 = lwgeom_calculate_circ_tree(lwg1); + c2 = lwgeom_calculate_circ_tree(lwg2); + d1 = circ_tree_distance_tree(c1, c2, &s, threshold); + d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold); +// printf("distance_tree %g\n", distance_tree); +// printf("distance_geom %g\n", distance_geom); +// circ_tree_print(cline, 0); +// circ_tree_print(cpoint, 0); + circ_tree_free(c1); + circ_tree_free(c2); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + CU_ASSERT_DOUBLE_EQUAL(d1, d2, 0.00001); + + + /* Ticket #2351 */ + lwg1 = lwgeom_from_wkt("LINESTRING(149.386990599235 -26.3567415843982,149.386990599247 -26.3567415843965)", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("POINT(149.386990599235 -26.3567415843982)", LW_PARSER_CHECK_NONE); + c1 = lwgeom_calculate_circ_tree(lwg1); + c2 = lwgeom_calculate_circ_tree(lwg2); + d1 = circ_tree_distance_tree(c1, c2, &s, threshold); + d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold); +// printf("d1 = %g d2 = %g\n", d1 * WGS84_RADIUS, d2 * WGS84_RADIUS); +// printf("line\n"); +// circ_tree_print(c1, 0); +// printf("point\n"); +// circ_tree_print(c2, 0); + circ_tree_free(c1); + circ_tree_free(c2); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + CU_ASSERT_DOUBLE_EQUAL(d1, d2, 0.0000001); + + /* Ticket #2634 */ + lwg1 = lwgeom_from_wkt("MULTIPOINT (-10 40,-10 65,10 40,10 65,30 40,30 65,50 40,50 65)", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("POLYGON((-9.1111111 40,-9.14954053919354 39.6098193559677,-9.26335203497743 39.2346331352698,-9.44817187539491 38.8888595339608,-9.6968975376269 38.5857864376269,-9.99997063396079 38.3370607753949,-10.3457442352698 38.1522409349774,-10.7209304559677 38.0384294391935,-11.1111111 38,-11.5012917440323 38.0384294391935,-11.8764779647302 38.1522409349774,-12.2222515660392 38.3370607753949,-12.5253246623731 38.5857864376269,-12.7740503246051 38.8888595339608,-12.9588701650226 39.2346331352698,-13.0726816608065 39.6098193559677,-13.1111111 40,-13.0726816608065 40.3901806440322,-12.9588701650226 40.7653668647302,-12.7740503246051 41.1111404660392,-12.5253246623731 41.4142135623731,-12.2222515660392 41.6629392246051,-11.8764779647302 41.8477590650226,-11.5012917440323 41.9615705608065,-11.1111111 42,-10.7209304559678 41.9615705608065,-10.3457442352698 41.8477590650226,-9.9999706339608 41.6629392246051,-9.69689753762691 41.4142135623731,-9.44817187539491 41.1111404660392,-9.26335203497743 40.7653668647302,-9.14954053919354 40.3901806440323,-9.1111111 40))", LW_PARSER_CHECK_NONE); + c1 = lwgeom_calculate_circ_tree(lwg1); + c2 = lwgeom_calculate_circ_tree(lwg2); + d1 = circ_tree_distance_tree(c1, c2, &s, threshold); + d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold); +// printf("d1 = %g d2 = %g\n", d1 * WGS84_RADIUS, d2 * WGS84_RADIUS); +// printf("multipoint\n"); +// circ_tree_print(c1, 0); +// printf("polygon\n"); +// circ_tree_print(c2, 0); + circ_tree_free(c1); + circ_tree_free(c2); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + CU_ASSERT_DOUBLE_EQUAL(d1, d2, 0.0000001); + + /* Ticket #2634 */ + lwg1 = lwgeom_from_wkt("MULTIPOINT Z (-10 40 1,-10 65 1,10 40 1,10 65 1,30 40 1,30 65 1,50 40 1,50 65 1,-10 40 2,-10 65 2,10 40 2,10 65 2,30 40 2,30 65 2,50 40 2,50 65 2,-10 40 3,-10 65 3,10 40 3,10 65 3,30 40 3,30 65 3,50 40 3,50 65 3)", LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_wkt("MULTIPOLYGON(((-9.1111111 40,-9.14954053919354 39.6098193559677,-9.26335203497743 39.2346331352698,-9.44817187539491 38.8888595339608,-9.6968975376269 38.5857864376269,-9.99997063396079 38.3370607753949,-10.3457442352698 38.1522409349774,-10.7209304559677 38.0384294391935,-11.1111111 38,-11.5012917440323 38.0384294391935,-11.8764779647302 38.1522409349774,-12.2222515660392 38.3370607753949,-12.5253246623731 38.5857864376269,-12.7740503246051 38.8888595339608,-12.9588701650226 39.2346331352698,-13.0726816608065 39.6098193559677,-13.1111111 40,-13.0726816608065 40.3901806440322,-12.9588701650226 40.7653668647302,-12.7740503246051 41.1111404660392,-12.5253246623731 41.4142135623731,-12.2222515660392 41.6629392246051,-11.8764779647302 41.8477590650226,-11.5012917440323 41.9615705608065,-11.1111111 42,-10.7209304559678 41.9615705608065,-10.3457442352698 41.8477590650226,-9.9999706339608 41.6629392246051,-9.69689753762691 41.4142135623731,-9.44817187539491 41.1111404660392,-9.26335203497743 40.7653668647302,-9.14954053919354 40.3901806440323,-9.1111111 40)))", LW_PARSER_CHECK_NONE); + c1 = lwgeom_calculate_circ_tree(lwg1); + c2 = lwgeom_calculate_circ_tree(lwg2); +// printf("\n"); +// circ_tree_print(c1, 0); +// printf("\n"); +// circ_tree_print(c2, 0); +// printf("\n"); + d1 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold); + d2 = circ_tree_distance_tree(c1, c2, &s, threshold); + d3 = circ_tree_distance_tree(c1, c2, &s, threshold); + d4 = circ_tree_distance_tree(c1, c2, &s, threshold); +// printf("\n d1-no-tree %20.20g\n d2 %20.20g\n d3 %20.20g\n d4 %20.20g\n", d1, d2, d3, d4); + circ_tree_free(c1); + circ_tree_free(c2); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + CU_ASSERT_DOUBLE_EQUAL(d1, d2, 0.00000001); + CU_ASSERT_DOUBLE_EQUAL(d1, d3, 0.00000001); + CU_ASSERT_DOUBLE_EQUAL(d1, d4, 0.00000001); + + + /* Ticket #4290 */ + const char *wkb1 = "0103000020E610000001000000EB010000560E7951F83853C0986D8DC811AE42404601F3C2F93853C028F9A6C80FAE42400EF46097FB3853C07B5CA9550FAE4240CC9BF16EFD3853C0F0D49BDC0FAE42400662BD42FF3853C03DF79F370FAE4240FC814915013953C0669AAA2E0EAE42401B6D54A7023953C0372CBC910CAE4240A451EF59043953C0336BCB570BAE42409C75E90B063953C08A26DAEB09AE42401554B6DF073953C04131DD4609AE4240F900D2D2093953C006A1DFA008AE424074706C850B3953C09198EB6607AE42401D7798580D3953C06B9AF28F06AE4240D75FC42B0F3953C0B5E9F6B805AE42409732F0FE103953C06600FBE104AE4240A0475DD3123953C0198EFA6E04AE4240B9805AC8143953C0AA9EF25E04AE42407E92B8BC163953C0E888EC1C04AE4240DA1615B1183953C0FBB7E8DA03AE4240184CE1841A3953C0B87CE93503AE42402370AD581C3953C07508EA9002AE4240B1483C301E3953C0247BD81703AE42405ECEE5321F3953C02740AB9905AE424008B91F18203953C0691E78B208AE42402245F374213953C07DE45A050AAE42408017844C233953C0E4A0488C0AAE4240005FE240253953C08116434A0AAE424029093F35273953C011DD3A080AAE4240DA0AAC09293953C0FBAA379509AE42403CA208FE2A3953C041F22E5309AE424033BED4D12C3953C090742DAE08AE4240E30392C52E3953C080D5283A08AE424020960D7A303953C080A2279607AE424068FB69AC323953C0F3911E2007AE4240A3059541343953C0F3B51D7D06AE4240B2CF6015363953C0FD161BD805AE424027B38BE8373953C079E51C0105AE424051CB55BC393953C06CE1195C04AE4240397D808F3B3953C08DC4188503AE424014BD5A433D3953C09BC41AAF02AE4240F98D76363F3953C0A52D160902AE4240A0E9410A413953C04739126401AE4240BA535A3B433953C0339C0D8A00AE4240000C95B0443953C0F58710B6FFAD4240C73EBF83463953C010170EDFFEAD4240EDDCF774483953C03D5111A3FDAD42400C5120484A3953C06C780ECCFCAD4240B8F7B7FA4B3953C0012C1392FBAD424011D740CD4D3953C046821489FAAD4240AF9BC99F4F3953C055A01580F9AD4240443CF372513953C0FBDC11A9F8AD4240D6CE1C46533953C0F95A10D2F7AD42405CB64419553953C026330CFBF6AD42402417CDEB563953C0C56F0CF2F5AD42400055B4BD583953C032A40EB7F4AD4240191E4B705A3953C078B9117DF3AD42401FF250025C3953C024881CE0F1AD4240131655945D3953C0813A2743F0AD424094704A465F3953C0C4F62BD7EEAD4240321FD218613953C0EDFA2ACEEDAD424017AB59EB623953C0EA4C27C5ECAD42403B24E1BD643953C0A6E025BCEBAD424081351871663953C0DC6D25B4EAAD4240EA11AE23683953C0DBFB267AE9AD4240743A35F6693953C0C5EE2471E8AD424020615DC96B3953C083FD1D9AE7AD424054EAF27B6D3953C06CF21E60E6AD42408606BC4F6F3953C0D36115BBE5AD4240183BA2E3703953C0AE0615B4E4AD4240B2EA1A98723953C048570C10E4AD42402E54006A743953C0583A0BD5E2AD4240C559951C763953C008380B9BE1AD4240F49F290D783953C038060D2DE0AD424025BF2B9F793953C0BD5C1290DEAD424002A610717B3953C0A7681055DDAD4240B1FE03237D3953C09E4B14E9DBAD42405E6798D57E3953C0805013AFDAAD42407DC73C68803953C033B41544D9AD42409343ED18823953C0BE721D74D7AD4240E7634DAA833953C066EA28A5D5AD4240DF7CCDFB843953C000F23874D3AD424085E94B4D863953C0F9634B43D1AD4240D6D3E85E873953C0017D67B0CEAD424065B12733883953C008167D83CCAD42406A881464893953C0B39497EFC9AD42409C16F1B48A3953C0B2DDAB8CC7AD42404B7ACD058C3953C0930AC029C5AD42402AFF4A578D3953C011E6D1F8C2AD42402D3037888E3953C0E201EC64C0AD42403D5DB4D98F3953C0B12DFB33BEAD42406D1950EB903953C0C8A216A1BBAD42400D889BDD913953C00C17310FB9AD4240E773B570923953C0D675591CB6AD4240F9CED003933953C0C0487F29B3AD4240B6B2CA14943953C028BE9C64B0AD42406EB34928953953C04056AC67AEAD42405F902479963953C0C7A5BF04ACAD4240522FAFAA973953C02B70D4A2A9AD4240BA954C7E983953C0A04EF043A7AD42404764A48E993953C0C59C0F4DA4AD4240B2ABEE809A3953C0228F29BBA1AD4240A10049539B3953C0FEB449F89EAD424080AAA1259C3953C0FEDC69359CAD4240321C4DD99C3953C0755889A599AD424030BB05AB9D3953C07396ABB096AD42409B990F5E9E3953C0A237CDEE93AD4240D40FD50F9F3953C0E64AF3C890AD4240324B90A39F3953C0B17616088EAD424032895817A03953C000883D168BAD4240B4FFB4ABA03953C0FD735E8788AD4240D4CD0F40A13953C08FED7CF885AD424060BB77F2A13953C0439EA00483AD42400D96DFA4A23953C053C1C61080AD424023602897A33953C0BC2CE07E7DAD4240A1AACF88A43953C09E3AFEBA7AAD4240068DD83BA53953C0C1901FF977AD42402556E1EEA53953C03DDF403775AD4240AB60AA62A63953C0A8B7674572AD42405D96D397A63953C0F0F491556FAD42400A935E8EA63953C0238CBF676CAD4240C9B6B671A63953C029C16D3E6BAD4240C2815AF3A53953C04C461B6269AD4240F7E145ABA53953C02EB84E7666AD4240878F2FA1A53953C0FE817E5663AD4240D6525AD6A53953C0F3B1A86660AD424046A3214AA63953C03E10D2745DAD4240E367EABDA63953C095E4F8825AAD42408BD54090A73953C0957B18C057AD4240353D5923A83953C036913DCD54AD4240AC72AFF5A83953C045165D0A52AD4240A942F8E7A93953C0261A76784FAD424012D10E7BAA3953C09C279B854CAD4240FBAE644DAB3953C06F8EBAC249AD424037CDCAFFAB3953C027D2DDCE46AD42405D2D4354AC3953C07AB608DE43AD42409529C7FFAC3953C075B49F9D41AD4240C07BAD14AD3953C0F90522123FAD42405A18380BAD3953C0369C4F243CAD424045502101AD3953C035E2810439AD42401DA0E774AD3953C08793A81236AD42408EAEEE27AE3953C05D89C95033AD4240401206BBAE3953C0C263EE5D30AD4240E30A7E0FAF3953C0EEBE166D2DAD4240C2C293A2AF3953C0E2173E7A2AAD4240C45CA935B03953C0BCF1628727AD4240B0F7AFE8B03953C09EC983C524AD42401864C57BB13953C01098A8D221AD4240D1AF99CCB23953C05FA5B96F1FAD4240026AF15DB43953C0C7DDBDA01DAD424016EAE8EFB53953C017C3BF031CAD4240A85E74A2B73953C022B6B8C91AAD42407E110D35B93953C06694B55E19AD42401F4264C6BA3953C0C19FBB8F17AD4240C39EC837BC3953C0A42AC38F15AD424003B05C49BD3953C0753BD9FC12AD42407E24443CBE3953C0EB50F19C10AD42404E4949EFBE3953C055140FDB0DAD4240AC5C9CC1BF3953C078722D180BAD424096DC3EB3C03953C0C2DF495408AD4240BA3E9385C13953C0491A689105AD4240FD8A2697C23953C00F4B80FE02AD4240070823C6C33953C0DB329DD4FFAC42407B0EC979C43953C02BFBBA44FDAC42400D54C96AC53953C05F5AD94EFAAC42407A97CD1DC63953C00249F98CF7AC42409C3EF190C63953C06D782169F4AC42406D7417C6C63953C00E514B79F1AC42407394DC39C73953C021BD7387EEAC4240A380EFCCC73953C0D4C49794EBAC42408BC6429FC83953C010A4B5D1E8AC4240ED2FB5F0C93953C04C15C0A0E6AC4240AC823622CB3953C0BE91D33EE4AC42401756FC54CC3953C0F384DD40E2AC424072E300C7CD3953C0B034E172E0AC42403982D95ACF3953C0176DD76BDFAC4240656AF22DD13953C022BAC494DEAC424097FB45BFD23953C0CAEAC5C5DCAC4240CEA409F2D33953C085CED1C7DAAC424024871C44D53953C04F11D9C8D8AC424052FB5A74D63953C00718F002D6AC4240E0CF4C47D73953C0B20A0B72D3AC424095902C78D83953C065371DDED0AC4240BFDB200ADA3953C00A731B41CFAC4240648B95DCDB3953C0B73C0C38CEAC42406550ADAFDD3953C0060DF860CDAC42408C03D462DF3953C0107EEA58CCAC42406D1C35D4E03953C01BA1EE58CAAC42407F610325E23953C007BCFBF5C7AC424070453AB8E33953C06584F4BCC6AC4240C7C9BE6AE53953C00685E882C5AC4240712F343DE73953C01F6BD579C4AC4240143C4B10E93953C05F57C2A2C3AC4240C35902E4EA3953C01462AAFDC2AC424049F3BAB7EC3953C004279258C2AC42407834158CEE3953C0D4FC74E5C1AC4240F8B5CD5FF03953C090C95E40C1AC42409158E432F23953C000204869C0AC42401B1368E5F33953C0FB803A2FBFAC42407AB0EB97F53953C081B12CF5BDAC4240B3D5A36BF73953C07C291350BDAC4240D68DB83EF93953C0ACB2FB78BCAC424097124734FB3953C0FBE9D39ABCAC424018E6A6F2FB3953C08955BA5CBDAC424051AB0417FC3953C054999EEBBEAC4240ACDE61A4FA3953C00A82A187C0AC42406D51DEF1F83953C005E5AFC1C1AC424031F8EB5FF73953C009D9B45EC3AC42400C11FBCDF53953C03111BCFBC4AC4240DF71083CF43953C033B3C098C6AC4240AC24268AF23953C045A9C904C8AC424067D6E016F13953C09862D06EC9AC424099E43286EF3953C07703D06FCBAC4240FF1B92D5ED3953C07C8ED13FCDAC42403D7F6104EC3953C0477CDEACCEAC4240EDD2B8CFEA3953C0752BE014D0AC424026866B00E93953C0B322E117D2AC42403B26C78DE73953C02740E2B3D3AC42404D3B241BE63953C0FEA7E54FD5AC4240F4E8D189E43953C0ACFAE31ED7AC42401C5ADFF7E23953C00147E9BBD8AC4240E400FA45E13953C09E61F027DAAC4240301B1694DF3953C047B9F993DBAC4240851C2302DE3953C0D109FC30DDAC4240317E1F90DC3953C02CE4F9FEDEAC424032055F1FDB3953C021ADF030E1AC42400B3F0D8ED93953C0694FF0FFE2AC4240E8D4AA1CD83953C06185EBFFE4AC4240583B48ABD63953C0301FE4FFE6AC42407272F419D53953C0E85AE3CEE8AC4240BC0AD2E7D33953C08758D5FEEAAC42408D904FB6D23953C00112C560EDAC4240117DCE84D13953C0B9A7B4C2EFAC424032B64B53D03953C03733A424F2AC4240A343D901CF3953C03AD89755F4AC424089BC57D0CD3953C0712687B7F6AC4240CA6EE37ECC3953C066A27AE8F8AC42403019516DCB3953C0791D637BFBAC42408A147D5ACA3953C074DF54AAFDAC42408A942D4AC93953C0054A36A100AD4240C7F7B9E7C73953C08FDE2D6004AD42403B4F27CEC63953C0274EF85907AD4240AF45811AC63953C075A0DAE909AD4240EB958F09C53953C0FD75C0AE0CAD424048D14755C43953C05D67A70C0FAD42408667F744C33953C063E68A0312AD4240A3DE5453C23953C0B1A16EC714AD424018496080C13953C0A018555817AD424070E1788DC03953C00B373DB819AD42402DCE965CBF3953C09D38294C1CAD4240FA99024BBE3953C007D410DF1EAD4240A4257D19BD3953C03EEFFE4021AD4240389A9AE8BB3953C0B2AEEAD423AD4240B6D714B7BA3953C0059CD83626AD42401C0C80A5B93953C0CEE7BFC928AD42402A42DCB3B83953C0B012A38D2BAD4240258D9A83B73953C088CC89532EAD42403F25A3B0B63953C0E3C26FE430AD4240CA891ABDB53953C019E25B1233AD42403E3776CBB43953C079D23ED635AD4240009C6F18B43953C045261E9838AD42407DE76865B33953C06F72FD593BAD4240B42162B2B23953C0E130DF1B3EAD4240BE634C1FB23953C081F2B70E41AD4240D60A358CB13953C0B135930144AD4240455E1019B13953C08E676A2547AD4240CCB305A4B03953C0EF4848B349AD4240F6ABEF10B03953C0057223A64CAD424025C8279DAF3953C067DDFC974FAD4240D89CAF48AF3953C0CB80D48852AD4240985D37F4AE3953C05DA8A97955AD4240BBE80EBFAE3953C023847F6958AD4240099C966AAE3953C07B22575A5BAD42401D70CEF6AD3953C0BB81304C5EAD42405BC00783AD3953C0F4D0093E61AD42405C4F0BD5AC3953C026ADCF8F65AD424083E84261AC3953C00101A98168AD4240D5FD7BEDAB3953C0DA4482736BAD42400DE66198AB3953C0560B5C326EAD42403516FBE5AA3953C0FCC3382671AD42402B09E452AA3953C0553F111974AD424052491BDFA93953C04881EA0A77AD42405FA50F6AA93953C083A8CA9879AD4240DBB6E9F6A83953C0F2259FBC7CAD424059D5D063A83953C098167AAF7FAD4240F55C09F0A73953C0F13D53A182AD42409746407CA73953C0CB6E2C9385AD424087AD28E9A63953C06ECB048688AD4240E0725F75A63953C0F4F4DD778BAD4240B44EA6E1A53953C089F9BA388EAD4240DD863B6DA53953C0F45296F890AD4240794332BAA43953C0D47977BA93AD4240E26B2A07A43953C05712567C96AD424003CD1074A33953C0ACD6306F99AD4240563707C1A23953C00A6E0F319CAD4240849CBDCEA13953C03E80F3C29EAD42406A5165FCA03953C027A3D385A1AD4240FEFF6929A03953C01779B816A4AD42404869C1379F3953C0902D9ADAA6AD4240E0AF18469E3953C0F0D37B9EA9AD4240D2EECC539D3953C0A9296230ACAD42406E9282619C3953C071EA45C2AEAD4240991D386F9B3953C0F1162C54B1AD424086649D5D9A3953C06C5811E7B3AD4240A3AC526B993953C09366F778B6AD4240EE904B3C983953C07501D5A2B9AD424039DFBD48973953C0325CBFD0BBAD4240AA002075963953C0D7E9A52FBEAD4240DAC4D482953953C02BBD8BC1C0AD42409E608990943953C053086F53C3AD42408F39FC5E933953C0D3135AB5C5AD4240F0F0614D923953C0EFBE3E48C8AD42402AAD243B913953C0561428A9CAAD4240B1159709903953C0BDDF120BCDAD42409D89F41F8E3953C02157F49ED0AD4240DD1885AE8C3953C04248E69ED2AD4240A90A075D8B3953C021DDD6CFD4AD42402740870B8A3953C079E8C400D7AD42401E22AABA883953C09E95B063D9AD42407056DB49873953C039D29F95DBAD4240DA6ECBD7853953C0C2C39563DDAD42408B0F5B66843953C0DEF08663DFAD4240530EF9D4823953C0AD177E32E1AD4240E62AE762813953C095AB7300E3AD4240012436B27F3953C054DE6BD0E4AD42408ABC823F7E3953C0625B636CE6AD42403E73E16E7C3953C066BD5E0BE8AD42401565EBFA7A3953C06CCD5C43E9AD4240E1BD6428793953C0AC67634CEAAD4240F5F3DD55773953C0C44F6755EBAD424055E1B582753953C0DA326D2CECAD42403B22EEAE733953C08F7D77D1ECAD424001EEC5DB713953C033697FA8EDAD42409CC23E09703953C0AE6F82B1EEAD42409C3B75356E3953C0B91C8C56EFAD42406CC54C626C3953C08F5E932DF0AD4240C48B76706A3953C08CE99637F1AD424006E24BDB683953C099879DDAF1AD4240E1CAC508673953C01D6B9FE3F2AD4240E2133E36653953C01D9DA3ECF3AD424048E46544633953C06748A6F6F4AD4240959C7EB0613953C0B33CA5FDF5AD4240A28455DD5F3953C0C0FAAAD4F6AD42405C478B095E3953C0E3B0B279F7AD4240D9FD61365C3953C09A83B550F8AD4240FE3DDB635A3953C0A15AB859F9AD4240CE9C42B1583953C0DFD9B293FAAD42408473ABFE563953C0D595AFCDFBAD42409AD3222C553953C0F9D8B1D6FCAD424009718B79533953C03C30AE10FEAD4240631F04A7513953C04780AD19FFAD424070F66AF44F3953C0927FA95300AE4240F16DE3214E3953C02E63A85C01AE42409D3D5A4F4C3953C0511BA76502AE424086FAD07C4A3953C02D15A86E03AE424029A1A6A9483953C04C8BA84504AE42403014BED7463953C05D6CA48005AE4240A1B844E5443953C0CCCAA75806AE42405759B950433953C0F8CFA52D07AE4240A2FE7F9D413953C062CFA43508AE4240049BA5E93F3953C07E51A30B09AE4240D9D2CCB93D3953C03CB0A0490AAE424002DCD4453C3953C0DFB498810BAE4240028599923A3953C0BD7C94890CAE4240638100E0383953C045DA8DC30DAE4240B98BC62C373953C0D7AD8BCB0EAE42405623ED3A353953C07D6088D50FAE4240078D1087333953C0358A85AB10AE4240747735D3313953C0F0EF848111AE4240FD4F79DF2F3953C078EF89F511AE42401CD1AC0B2E3953C0F5D28B9A12AE42400741E0372C3953C0707D8D3F13AE42400A5106462A3953C0FD3F8B4914AE4240B75ADA72283953C019D0872015AE42402B8F0D9F263953C051CC88C515AE42403977E1CB243953C0F464879C16AE424070CEB6F8223953C0523E837317AE4240F1ED1A46213953C012037AAD18AE42406B3B21941F3953C094606E191AAE4240A6EFE5E01D3953C0A6E166211BAE4240F9C8FA0E1C3953C0A09E5D5C1CAE4240B37CB07B1A3953C03FFE52951DAE42406D8864E8183953C0E14048CE1EAE42402D401B17173953C0BCB5393B20AE424051917E64153953C027272F7521AE4240130184B2133953C0923222E122AE4240CAA2E8FF113953C07636171B24AE424066F8DE6D103953C004C104B825AE4240B777E2BB0E3953C0324CF72327AE4240EA87D8290D3953C03E81E4C028AE424044AC3C770B3953C018CBD8FA29AE4240F6EB30E5093953C02EB7C5972BAE42408F0AD633083953C0ABDAB2352DAE424002FA2AA1063953C02606A4A02EAE42404ED11E0F053953C0AE73903D30AE4240BB12147D033953C074AB7CDA31AE4240EB3BA8EB013953C0DB9F66A933AE424039CB3D5A003953C0C95E507835AE42406EA6D1C8FE3853C0AD013A4737AE4240E355C636FD3853C0B69525E438AE424068D4C8C2FB3853C01581171C3AAE42403E445C31FA3853C05CAC00EB3BAE4240311AF19FF83853C035A2E9B93DAE424010B2E30DF73853C0FAA1D4563FAE42400FB4D77BF53853C0006CBFF340AE4240ED8B6AEAF33853C0FEF3A7C242AE4240DF7DDF98F23853C0100E89F344AE42406A161408F13853C03A196FF446AE42400894D897EF3853C0150A4E5849AE4240C6140C45EE3853C0508B35254BAE424044DA7EF3EC3853C0573816564DAE42408A6D0382EB3853C07F06F9554FAE4240652F95F0E93853C07E8CE02451AE4240BF9B689EE83853C0F26DC52353AE4240D910FA0CE73853C012A9ACF254AE424046087E9BE53853C0F4EA8EF256AE424007586FCBE33853C08F0F76C358AE4240BD93E279E23853C06FCD55F45AAE4240FED0A447E13853C0065F35245DAE424055620854E03853C0291E17525FAE424007307B02DF3853C0BC91F68261AE424051D17DD1DD3853C0BA1ECF1664AE424083A630BFDC3853C0FD1EAC7766AE42402981D4CCDB3853C0B2D7860969AE42400C7127BBDA3853C0DD175F9C6BAE42409A08CBC8D93853C030B2392E6EAE4240F7B01DB7D83853C0EECF11C170AE42406A3C70A5D73853C05755EC5373AE42400C9BC293D63853C0834EC4E675AE4240883C2562D53853C0A7CAA04878AE4240E892C66FD43853C0EDA678DA7AAE42404EEA283ED33853C0F0F9543C7DAE4240368E890CD23853C09C42319E7FAE424030F38BDBD03853C0D9440B3282AE4240DF71DDC9CF3853C09AC9E2C484AE424098FB2E7ACE3853C00EFBB78B87AE4240808E3F67CD3853C0CB8F98BA89AE424017254174CC3853C00009721A8CAE4240436C3263CB3853C0EF9949DF8EAE4240057FBDCCC93853C0ACED431E8FAE424091FA48EBC83853C0207A62318DAE42406547459BC93853C00C6D9A7589AE42403DFF116DCA3853C01975C78086AE4240C714705FCB3853C0733EF0EE83AE424008E42D13CC3853C06956165F81AE42406690DC24CD3853C083133FCC7EAE424023A4BC95CE3853C0DA29619A7CAE42405ABD4BE7CF3853C0961783697AAE4240D8F7BC78D13853C051319E9A78AE42401D3FDDEAD23853C0EF21B9CC76AE424023BB7A1CD43853C0F6D6DC6A74AE4240981CE8EED43853C0392707A871AE424093F044E1D53853C054D72C166FAE4240EACEF2B4D63853C0AA5050B76CAE4240D0495E87D73853C0EF8B7AF469AE424026A6A8DAD73853C072ADAD9F66AE4240C0F551B1D73853C056C2DE8063AE4240C3526E6CD73853C0C891FB8E61AE424023C21A02D83853C0E9E71C645FAE4240D2669AF7D83853C0693535CC5DAE4240EE32DAE8DA3853C0D77446905CAE42405C718A7BDC3853C04FB759255BAE424010B966ECDD3853C0C2747AF358AE4240899E13C0DE3853C085849D9456AE42405B9A7F92DF3853C0A24AC7D153AE4240BD371805E03853C0C338FA7B50AE4240E8A511FBDF3853C0DC4D2B5C4DAE42408987EC6EE03853C0E66A576A4AAE424015975641E13853C0732881A747AE4240071EB333E23853C09513A61545AE42402167AE26E33853C06AD9C8B542AE42400D9A28D9E33853C0C8ABF4C13FAE42406423438CE43853C0C1521E003DAE42406E935D3FE53853C00FF2473E3AAE4240125D76F2E53853C06396717C37AE424015C98003E73853C008BF9AB734AE42406E519E37E83853C0FBDFB11D33AE424055715AE8E93853C08D74CA4D31AE4240E6CC853AEB3853C0BA81E54E2FAE4240C97F518DEC3853C0DB4DFE812DAE42405BADDDDEED3853C096B31D512BAE4240C39418D3EE3853C0B07E3B5529AE42400AD53307F03853C0D2974FBB27AE4240C3950F3AF13853C00ACA6CBD25AE42408AA8B9CFF13853C022328D9223AE4240B1B39243F23853C0F7D3B8A020AE42400F123C55F33853C0BC8ADC0D1EAE424095D59447F43853C051EB027C1BAE4240F05D0FB9F53853C036D51C7C19AE4240FC4948EBF63853C075CB3B4C17AE4240CEB8609EF73853C05AA0648A14AE4240560E7951F83853C0986D8DC811AE4240"; + const char *wkb2 = "0103000020E610000001000000080000002CAFE119EF2F53C02D6D4097DFBE42400050FBE7F32F53C05A08B32BD9BE42402ED0C3E9E52F53C0132FFFAE9DBE424048885052FE2F53C0DE5D06256BBE42409534CFEF163053C07D170F98ADBE42402E72095F303053C025ECD50CEABE424059CE3A7C103053C0235393C114BF42402CAFE119EF2F53C02D6D4097DFBE4240"; + spheroid_init(&s, WGS84_MAJOR_AXIS, WGS84_MINOR_AXIS); + lwg1 = lwgeom_from_hexwkb(wkb1, LW_PARSER_CHECK_NONE); + lwg2 = lwgeom_from_hexwkb(wkb2, LW_PARSER_CHECK_NONE); + c1 = lwgeom_calculate_circ_tree(lwg1); + c2 = lwgeom_calculate_circ_tree(lwg2); + d1 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold); + d2 = circ_tree_distance_tree(c1, c2, &s, threshold); + circ_tree_free(c1); + circ_tree_free(c2); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + CU_ASSERT_DOUBLE_EQUAL(d1, 18358.866, 0.01); + CU_ASSERT_DOUBLE_EQUAL(d2, 18358.866, 0.01); +} + +static void test_tree_circ_distance_threshold(void) +{ + SPHEROID s; + int i, j; + int step = 10; + + const char *txt_poly1 = "0103000020E6100000010000000B0000000AA2F068F47651C0F7893DEB70B8454007ABD4C6D57651C000FB650799B84540C21AA2645A7651C011C24BA84AB8454089A9A325E87751C03314EB5453B74540AF9ED96BF57751C0BF9818F889B74540E936A498B47751C0690C87D1C5B74540F5386204DC7751C02FCA658F1AB8454077B65F7B657751C012C586EE37B845408C1862C5977751C00F17E41674B84540D4012F57357751C0AD3BC67E99B845400AA2F068F47651C0F7893DEB70B84540"; + const char *txt_poly2 = "0103000020E610000003000000B5000000E0D13F40187451C01A09009164BB4540771C07D8FA7351C04DBBEDB634BB45400D27D627A47351C070625C9386BB4540D691981FAD7351C02F382CCDFABA4540AA38FA85B97351C03AD3A8271CBB4540D211F0CBBE7351C027F11A8FD9BA4540839AE427EB7351C0BF97C6BBA3BA4540748967A4D47351C00C0037C970BA45408F182A0CF07351C0C050808070BA45405063F06DD27351C0681018D037BA4540E509D5C2EE7351C0B9429E0231BA4540EA39B20CCC7351C018FBB70F22BA454072DD47C8ED7351C04C992D5E0BBA4540934BDE09CE7351C04B17895ACFB94540F7AA9F1EF77351C04DE737D4DCB945400D217458DF7351C022324164B0B945408A2F53C4087451C0A7EC0972B1B9454091A02DF3FC7351C0077F0D388AB94540D9EA0152187451C02316A57280B9454023E37795F07351C09314E67D36B945408DD1866BFC7351C06A0A537214B945405F77D635DE7351C01C7B3C251EB945404785C336F27351C07D767383F1B8454043664E41C77351C0F499D9F3C2B84540DA5CD86FDC7351C0ECA943B295B845403A60B1A2AF7351C063B81C61AFB8454041469468AA7351C0B0727EE16AB845405ADF4611937351C0AA5D2634A3B84540D1BB958D917351C0086C6F1450B845408BBECFF4787351C008F3F4958EB845407F025FE0737351C0506C9F8057B845405A49EDDE5E7351C0AB88DA1B84B845402AAE2CB6767351C07ECC7AEB33B845406C3BCEFF557351C0D93C09506DB8454032C3D6B8577351C0CF4410B115B84540C47F5B30337351C08DCEBB6F7DB845400DC58DB4127351C0D68016845EB845409E0DC353E17251C05E161FDE94B84540D94A5CCDF87251C0D4A79022C3B745404B789041A97151C0D366245447B64540E229E50C7A7151C04864DEC059B645403CF020E75F7151C0C2341E664FB745404F6388C1807151C0BE100BDA2FB7454099DB3D97877151C0853918DE6CB74540080D2824137151C017385A8090B74540A380F14EE97051C0A517F62259B74540C9B0B8E1F47051C019014EF43DB7454075AB809D997051C023033EFC28B745401FCC6CFD3C7051C01778CC51B1B74540A5AFFD2A5E7051C08B7644232EB8454075947311FD7051C0EB4B7B9F09B8454007A084455D7151C065E676C6A7B845401CB4DA891A7151C096D19999D0B84540F9D410BC3E7151C08C3797C11CB945400CA63027017151C0640A96032BBA4540B07FB7D8897151C0B632805859BA454078D01A1FDD7151C0202711EE70B945405F3A6E8E627251C0932D32FE34BA4540B532341D277251C0DFA18E0392BA4540CD8E813B147251C0FEBBA2346EBA4540421F7F16F87151C0DEB93B76A3BA45403B2350A3D37151C0330AF75F99BA454077512147AC7151C0A628D5F117BB454011C9CE77507151C04CC4E1E705BB4540E6889885EE7051C0C45D89CDA8BA4540575D1B37B07051C0F883C0A0D7BA45408A0FB14B977051C01D328086A9BA454033A7D2839B7051C0EEC463DD22BA4540AFDC6C64DC7051C013983A6CE3B94540CC1E39AEE17051C076E753DD7BB94540F6E0B3BECA7051C070E459395BB94540E636719D447051C0CB5C78F2F8B845402D3B6C3AC76F51C08AFD07346AB945404080D345386F51C02EFB7C9465B94540B5BB47ED506F51C0B5742F4FC3B84540F4DAAD32326F51C03AE0604DCEB8454079D03A00286F51C0BFE416D692B84540E7EEF142456F51C026483D9219B84540556E7F645D6F51C06BA1A86620B845406BD12F44536F51C0F976E08663B84540D7BFD8077B6F51C0C500FD8E63B8454004DD9EC4816F51C0441D30FBDBB745403EE733710C6F51C05E6524F44EB74540BEEA42C14A6E51C0367C93EEDEB54540C84C8F841D6E51C0F035740AD0B54540F0B3122E316E51C09158039618B44540F5E629F8706E51C02FD3E2F726B44540533862949E6E51C015FA5534BBB34540C649D8EDEF6E51C02B5E6D6F80B345405D7B912E616F51C0DF4BA76B0EB2454072578CCFB76F51C0A22CCF6690B1454015A5C130647051C0F4EFF7540FB14540F1ACD1FA9D7051C0329CCCA94CB14540374504840E7151C04726DFC9B5B04540C4B031D7D97151C0FC7C331392B04540DC234455E77151C00A9F4A04B9B04540D1505DECCD7151C0D9351517EAB04540CDD86B5EF87151C004CB649085B1454099BE7DDB797151C037D42330B4B245400EA292D32C7151C0820E1DD8B4B24540ACE77E61257151C0EE15BF9BEBB245407AA945D2867151C0666E8B0F01B345404EE1C915D37151C04B6DD883B3B24540C58BDF35247251C0D3FB74C050B445403C06C143697251C0603A1C0D59B44540D2F9E6E6B07251C0BC16F1CCC7B44540E47B911C8D7251C0A95F88542AB5454047BC0FA6237251C031E8A2FB38B54540CAB1FB9FF87151C03B2BF41EF7B445404EBF18E8017251C09C164886A8B4454050CD38EBC67151C014E9B12709B54540B4C7E42DD37151C0FB5B1E864CB54540BECC9D27167251C0B37B1B12AEB545400643207D967251C048C2DBC1ACB5454039070923977251C0E6D50A2602B64540DDD41A1C2E7351C06156AFD91EB645402FAEBC26AD7251C06DB4F5FA28B64540E6498EEFD87251C06214C5327CB645400C5E024B2E7351C0CABD9E93B9B6454001F550AB447351C0FBAFC57D96B645405AE881CF3D7351C01917460EC7B64540695D4AA6607351C00D689CE284B64540C6299985517351C076C84855D3B645406625BA34747351C0E739BF479CB64540386A02DD607351C0D6534476E1B645400B69DB9F997351C0B8AC5ACD90B645400E3CF2EEAC7351C05979D2F9A1B645400FD99F71887351C03916C9ACFAB64540CA154DF19B7351C0CC4ECC370AB745403B8F39FFC07351C0311B27F4ACB6454008ED94DBD07351C069128CC9B9B64540BDEC1CCDAA7351C0FA1AAC0617B74540A99C51C1C07351C07759CE3A2AB7454073FCF177E67351C04A84EAA2CDB6454055CDCB6ED17351C0CB6C7B6438B74540DDD31435F87351C0D4D993891FB74540811059D30D7451C077753AD162B74540F18AF9E46B7451C0FD21E09762B74540F253FA4F547451C067E3343FACB74540F0BCC760697451C0E0AE673BDBB74540FA1616AB497451C0D50836ABF0B74540F41845176E7451C0950B89B7FCB74540A16098ED3D7451C0D4E3E2D80BB84540B9AFBD7B727451C019895C361DB84540163C14EB717451C013C465C245B845402645A98E427451C049F426523FB84540C47E7169707451C0958552ED5DB84540652934E8467451C0D6A4B9DC6EB84540762DC58E717451C0F00F358EA7B8454052BD47144E7451C0CE25A2A9B7B84540FE28AFF55E7451C0EC20702D0BB94540DC47DC52777451C0DB556D5401B945407C974EEE617451C0DF8FB3BF25B94540F486D9A77E7451C0C20621B41AB94540432A3030707451C08AB2EDBC39B945409B0D7D28167551C0BB2B0598F9B84540E9C3E5D1067551C0617DD7107BB94540ADD4816DC57451C052E51C9E7FB9454083A85A09D37451C04AA52729AAB94540E8B74A52A57451C0054656858DB94540C1F58C11C07451C073982E6DC8B945406639F493967451C09D31BA6A90B9454023779107AB7451C026DD7171DFB945404CC088A46D7451C00324607A78B94540B4263081827451C0AF743013AAB94540A1FEA7A25A7451C030846A958DB945403B797701697451C00240A15CE1B945405EBCC1C4497451C0AF0092E9BCB945408A96E72B6A7451C0A85FF698F9B9454048855507427451C06852C7B9F0B94540CFBB01EA5C7451C0DC68FBB715BA4540D42EA3CF3C7451C0D2A6746A0BBA4540510413EB577451C0EE883A7D2CBA4540DC8C76E73B7451C0808E628128BA4540904E729D4F7451C09335C2C358BA4540A9018BEC347451C0017C60D031BA4540508873A32E7451C03F4C9200BDBA45400FEFB0B0567451C00274F969CCBA45402E2B3AC7317451C09C029279CBBA4540C6748AB7417451C060364B9C0EBB4540E0D13F40187451C01A09009164BB4540080000006DABE362BF6F51C03B3EAB0286B84540F2EBCFEEAC6F51C008D8984EA3B84540FF2B5394BB6F51C0B2F08C42CBB84540FDF9E71CFC6F51C07C7C0453C8B845403ABCCFEBCF6F51C0D477C34AC2B845402D81685DC76F51C071D8F5EE94B84540FB229C95E86F51C0BD8E7FF7AEB845406DABE362BF6F51C03B3EAB0286B8454008000000DB185B503E7051C066B728BCD3B245400AC83B92357051C04BA0208DA3B34540D7FCFFA31D7051C0CF7C254CD6B345400E29CDB9907051C012EB7D78CCB34540806300429F7051C0B0D493C19CB34540786CA34E6C7051C093B0BB4060B345407D77C1F9657051C0497CC614C3B24540DB185B503E7051C066B728BCD3B24540"; + const char *polys[2]; + static int npolys = 2; + + polys[0] = txt_poly1; + polys[1] = txt_poly2; + + // spheroid_init(&s, WGS84_RADIUS, WGS84_RADIUS); + spheroid_init(&s, WGS84_MAJOR_AXIS, WGS84_MINOR_AXIS); + + for ( j = 0; j < npolys; j++ ) + { + LWGEOM *lwg1 = lwgeom_from_hexwkb(polys[j], LW_PARSER_CHECK_NONE); + LWGEOM *lwg2 = lwgeom_from_wkt("POINT(-69.83262 43.43636)", LW_PARSER_CHECK_NONE); + + CIRC_NODE *c1 = lwgeom_calculate_circ_tree(lwg1); + CIRC_NODE *c2 = lwgeom_calculate_circ_tree(lwg2); + + for ( i = 50; i < 1500 / step; i++ ) + { + double d1, d2; + double d = lwgeom_distance_spheroid(lwg1, lwg2, &s, 0); + double threshold = step * i; + d1 = circ_tree_distance_tree(c1, c2, &s, threshold); + d2 = lwgeom_distance_spheroid(lwg1, lwg2, &s, threshold); + if (threshold > d && (d1 > threshold || d2 > threshold)) + { + printf("polygon #%d\n" + "threshold = %g\n" + "true distance = %g\n" + "circ_tree_distance = %g\n" + "lwgeom_distance_spheroid = %g\n", j, threshold, d, d1, d2); + CU_FAIL_FATAL(); + } + } + + circ_tree_free(c1); + circ_tree_free(c2); + lwgeom_free(lwg1); + lwgeom_free(lwg2); + } + +} + +/* +** Used by test harness to register the tests in this file. +*/ +void tree_suite_setup(void); +void tree_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("spatial_trees", NULL, NULL); + PG_ADD_TEST(suite, test_tree_circ_create); + PG_ADD_TEST(suite, test_tree_circ_pip); + PG_ADD_TEST(suite, test_tree_circ_pip2); + PG_ADD_TEST(suite, test_tree_circ_distance); + PG_ADD_TEST(suite, test_tree_circ_distance_threshold); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_triangulate.c b/mgist-postgis/liblwgeom/cunit/cu_triangulate.c new file mode 100644 index 0000000..45b8398 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_triangulate.c @@ -0,0 +1,125 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright (C) 2015 Daniel Baston + * Copyright (C) 2012 Sandro Santilli + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include "CUnit/Basic.h" +#include "cu_tester.h" + +#include "liblwgeom_internal.h" +#include "../lwgeom_geos.h" + +static void +test_lwgeom_delaunay_triangulation(void) +{ + LWGEOM *in, *tmp, *out; + char *wkt, *exp_wkt; + + /* Because i don't trust that much prior tests... ;) */ + cu_error_msg_reset(); + + in = lwgeom_from_wkt("MULTIPOINT(10 0, 20 0, 5 5)", LW_PARSER_CHECK_NONE); + + tmp = lwgeom_delaunay_triangulation(in, 0, 0); + lwgeom_free(in); + out = lwgeom_normalize(tmp); + lwgeom_free(tmp); + + wkt = lwgeom_to_ewkt(out); + lwgeom_free(out); + + exp_wkt = "GEOMETRYCOLLECTION(POLYGON((5 5,20 0,10 0,5 5)))"; + if (strcmp(wkt, exp_wkt)) + fprintf(stderr, "\nExp: %s\nObt: %s\n", exp_wkt, wkt); + + CU_ASSERT_STRING_EQUAL(wkt, exp_wkt); + lwfree(wkt); +} + +static void +test_lwgeom_voronoi_diagram(void) +{ + LWGEOM *in = lwgeom_from_wkt("MULTIPOINT(4 4, 5 5, 6 6)", LW_PARSER_CHECK_NONE); + + LWGEOM *out_boundaries = lwgeom_voronoi_diagram(in, NULL, 0, 0); + LWGEOM *out_lines = lwgeom_voronoi_diagram(in, NULL, 0, 1); + + /* For boundaries we get a generic LWCOLLECTION */ + CU_ASSERT_EQUAL(COLLECTIONTYPE, lwgeom_get_type(out_boundaries)); + /* For lines we get a MULTILINETYPE */ + CU_ASSERT_EQUAL(MULTILINETYPE, lwgeom_get_type(out_lines)); + + lwgeom_free(in); + lwgeom_free(out_boundaries); + lwgeom_free(out_lines); +} + +static void +test_lwgeom_voronoi_diagram_custom_envelope(void) +{ + LWGEOM *in = lwgeom_from_wkt("MULTIPOINT(4 4, 5 5, 6 6)", LW_PARSER_CHECK_NONE); + LWGEOM *for_extent = lwgeom_from_wkt("LINESTRING (-10 -10, 10 10)", LW_PARSER_CHECK_NONE); + const GBOX *clipping_extent = lwgeom_get_bbox(for_extent); + + LWGEOM *out = lwgeom_voronoi_diagram(in, clipping_extent, 0, 0); + const GBOX *output_extent = lwgeom_get_bbox(out); + + CU_ASSERT_TRUE(gbox_same(clipping_extent, output_extent)); + + lwgeom_free(in); + lwgeom_free(for_extent); + lwgeom_free(out); +} + +static void +assert_empty_diagram(char *wkt, double tolerance) +{ + LWGEOM *in = lwgeom_from_wkt(wkt, LW_PARSER_CHECK_NONE); + LWGEOM *out = lwgeom_voronoi_diagram(in, NULL, tolerance, 0); + + CU_ASSERT_TRUE(lwgeom_is_collection(out)); + CU_ASSERT_EQUAL(COLLECTIONTYPE, lwgeom_get_type(out)); + + lwgeom_free(in); + lwgeom_free(out); +} + +static void +test_lwgeom_voronoi_diagram_expected_empty(void) +{ + assert_empty_diagram("POLYGON EMPTY", 0); + assert_empty_diagram("POINT (1 2)", 0); + + /* This one produces an empty diagram because our two unqiue points + * collapse onto one after considering the tolerance. */ + assert_empty_diagram("MULTIPOINT (0 0, 0 0.00001)", 0.001); +} + +static int +clean_geos_triangulate_suite(void) +{ + finishGEOS(); + return 0; +} + +/* +** Used by test harness to register the tests in this file. +*/ +void triangulate_suite_setup(void); +void +triangulate_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("triangulate", NULL, clean_geos_triangulate_suite); + PG_ADD_TEST(suite, test_lwgeom_delaunay_triangulation); + PG_ADD_TEST(suite, test_lwgeom_voronoi_diagram); + PG_ADD_TEST(suite, test_lwgeom_voronoi_diagram_expected_empty); + PG_ADD_TEST(suite, test_lwgeom_voronoi_diagram_custom_envelope); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_unionfind.c b/mgist-postgis/liblwgeom/cunit/cu_unionfind.c new file mode 100644 index 0000000..2231298 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_unionfind.c @@ -0,0 +1,174 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright 2015 Daniel Baston + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include "CUnit/Basic.h" + +#include "../lwunionfind.h" +#include "cu_tester.h" + +static void test_unionfind_create(void) +{ + UNIONFIND *uf = UF_create(10); + + uint32_t expected_initial_ids[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + uint32_t expected_initial_sizes[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + + ASSERT_INT_EQUAL(uf->N, 10); + ASSERT_INT_EQUAL(uf->num_clusters, 10); + ASSERT_INTARRAY_EQUAL(uf->clusters, expected_initial_ids, 10); + ASSERT_INTARRAY_EQUAL(uf->cluster_sizes, expected_initial_sizes, 10); + + UF_destroy(uf); +} + +static void test_unionfind_union(void) +{ + UNIONFIND *uf = UF_create(10); + + UF_union(uf, 0, 7); /* both have size = 1, so 7 becomes 0 */ + UF_union(uf, 3, 2); /* both have size = 1, so 3 becomes 2 */ + UF_union(uf, 8, 7); /* add 8 (smaller) to 0-7 (larger) */ + UF_union(uf, 1, 2); /* add 1 (smaller) to 2-3 (larger) */ + + uint32_t expected_final_ids[] = { 0, 2, 2, 2, 4, 5, 6, 0, 0, 9 }; + uint32_t expected_final_sizes[] = { 3, 0, 3, 0, 1, 1, 1, 0, 0, 1 }; + + ASSERT_INT_EQUAL(uf->N, 10); + ASSERT_INT_EQUAL(uf->num_clusters, 6); + ASSERT_INTARRAY_EQUAL(uf->clusters, expected_final_ids, 10); + ASSERT_INTARRAY_EQUAL(uf->cluster_sizes, expected_final_sizes, 10); + + UF_destroy(uf); +} + +static void test_unionfind_ordered_by_cluster(void) +{ + uint32_t final_clusters[] = { 0, 2, 2, 2, 4, 5, 6, 0, 0, 2 }; + uint32_t final_sizes[] = { 3, 0, 4, 0, 1, 1, 1, 0, 0, 0 }; + + /* Manually create UF at desired final state */ + UNIONFIND uf = + { + .N = 10, + .num_clusters = 5, + .clusters = final_clusters, + .cluster_sizes = final_sizes + }; + + uint32_t* ids_by_cluster = UF_ordered_by_cluster(&uf); + + char encountered_cluster[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + uint32_t i; + for (i = 0; i < uf.N; i++) + { + uint32_t c = final_clusters[ids_by_cluster[i]]; + if (!encountered_cluster[c]) + { + encountered_cluster[c] = 1; + } + else + { + /* If we've seen an element of this cluster before, then the + * current cluster must be the same as the previous cluster. */ + uint32_t c_prev = final_clusters[ids_by_cluster[i-1]]; + CU_ASSERT_EQUAL(c, c_prev); + } + } + lwfree(ids_by_cluster); +} + +static void test_unionfind_path_compression(void) +{ + UNIONFIND* uf = UF_create(5); + uint32_t i; + + uf->clusters[1] = 0; + uf->clusters[2] = 1; + uf->clusters[3] = 2; + uf->clusters[4] = 3; + + /* Calling "find" on a leaf should attach all nodes between the root and the + * leaf directly to the root. */ + uint32_t root = UF_find(uf, 4); + for (i = 0; i < uf->N; i++) + { + /* Verify that all cluster references have been updated to point + * directly to the root. */ + CU_ASSERT_EQUAL(root, uf->clusters[i]); + } + + UF_destroy(uf); +} + +static void test_unionfind_collapse_cluster_ids(void) +{ + UNIONFIND* uf = UF_create(10); + + uf->clusters[0] = 8; + uf->clusters[1] = 5; + uf->clusters[2] = 5; + uf->clusters[3] = 5; + uf->clusters[4] = 7; + uf->clusters[5] = 5; + uf->clusters[6] = 8; + uf->clusters[7] = 7; + uf->clusters[8] = 8; + uf->clusters[9] = 7; + + uf->cluster_sizes[0] = 3; + uf->cluster_sizes[1] = 4; + uf->cluster_sizes[2] = 4; + uf->cluster_sizes[3] = 4; + uf->cluster_sizes[4] = 3; + uf->cluster_sizes[5] = 4; + uf->cluster_sizes[6] = 3; + uf->cluster_sizes[7] = 3; + uf->cluster_sizes[8] = 3; + uf->cluster_sizes[9] = 3; + + /* 5 -> 0 + * 7 -> 1 + * 8 -> 2 + */ + uint32_t expected_collapsed_ids[] = { 2, 0, 0, 0, 1, 0, 2, 1, 2, 1 }; + uint32_t* collapsed_ids = UF_get_collapsed_cluster_ids(uf, NULL); + + ASSERT_INTARRAY_EQUAL(collapsed_ids, expected_collapsed_ids, 10); + + lwfree(collapsed_ids); + + char is_in_cluster[] = { 0, 1, 1, 1, 0, 1, 0, 0, 0, 0 }; + uint32_t expected_collapsed_ids2[] = { 8, 0, 0, 0, 7, 0, 8, 7, 8, 7 }; + + collapsed_ids = UF_get_collapsed_cluster_ids(uf, is_in_cluster); + uint32_t i; + for (i = 0; i < uf->N; i++) + { + if (is_in_cluster[i]) + ASSERT_INT_EQUAL(expected_collapsed_ids2[i], collapsed_ids[i]); + } + + lwfree(collapsed_ids); + UF_destroy(uf); +} + +void unionfind_suite_setup(void); +void unionfind_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("clustering_unionfind", NULL, NULL); + PG_ADD_TEST(suite, test_unionfind_create); + PG_ADD_TEST(suite, test_unionfind_union); + PG_ADD_TEST(suite, test_unionfind_ordered_by_cluster); + PG_ADD_TEST(suite, test_unionfind_path_compression); + PG_ADD_TEST(suite, test_unionfind_collapse_cluster_ids); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_varint.c b/mgist-postgis/liblwgeom/cunit/cu_varint.c new file mode 100644 index 0000000..069f0c3 --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_varint.c @@ -0,0 +1,257 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright (C) 2013 Nicklas Avén + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include "CUnit/Basic.h" +#include "CUnit/CUnit.h" +#include "liblwgeom_internal.h" +#include "varint.h" +#include "cu_tester.h" + + + +// size_t varint_u32_encode_buf(uint32_t val, uint8_t *buf); +// size_t varint_s32_encode_buf(int32_t val, uint8_t *buf); +// size_t varint_u64_encode_buf(uint64_t val, uint8_t *buf); +// size_t varint_s64_encode_buf(int64_t val, uint8_t *buf); +// int64_t varint_s64_decode(const uint8_t *the_start, const uint8_t *the_end, size_t *size); +// uint64_t varint_u64_decode(const uint8_t *the_start, const uint8_t *the_end, size_t *size); +// +// size_t varint_size(const uint8_t *the_start, const uint8_t *the_end); +// + +static void do_test_u32_varint(uint32_t nr, int expected_size, char* expected_res) +{ + int size; + char *hex; + uint8_t buf[16]; + + size = varint_u32_encode_buf(nr, buf); + if ( size != expected_size ) + printf("Expected: %d\nObtained: %d\n", expected_size, size); + + CU_ASSERT_EQUAL(size, expected_size); + + hex = hexbytes_from_bytes(buf, size); + ASSERT_STRING_EQUAL(hex, expected_res); + lwfree(hex); +} + +static void do_test_s32_varint(int32_t nr,int expected_size, char* expected_res) +{ + uint8_t buf[16]; + int size; + char *hex; + + size = varint_s32_encode_buf(nr, buf); + if ( size != expected_size ) + { + printf("Expected: %d\nObtained: %d\n", expected_size, size); + } + CU_ASSERT_EQUAL(size,expected_size); + + hex = hexbytes_from_bytes(buf, size); + ASSERT_STRING_EQUAL(hex, expected_res); + lwfree(hex); +} + +static void do_test_u64_varint(uint64_t nr,int expected_size, char* expected_res) +{ + uint8_t buf[16]; + int size; + char *hex; + + size = varint_u64_encode_buf(nr, buf); + if ( size != expected_size ) + { + printf("Expected: %d\nObtained: %d\n", expected_size, size); + } + CU_ASSERT_EQUAL(size,expected_size); + + hex = hexbytes_from_bytes(buf,size); + ASSERT_STRING_EQUAL(hex, expected_res); + lwfree(hex); +} + +static void do_test_s64_varint(int64_t nr,int expected_size, char* expected_res) +{ + uint8_t buf[16]; + int size; + char *hex; + + size = varint_s64_encode_buf(nr, buf); + if ( size != expected_size ) + { + printf("Expected: %d\nObtained: %d\n", expected_size, size); + } + CU_ASSERT_EQUAL(size,expected_size); + + hex = hexbytes_from_bytes(buf,size); + ASSERT_STRING_EQUAL(hex, expected_res); + lwfree(hex); +} + +static void test_varint(void) +{ + + do_test_u64_varint(1, 1, "01"); + do_test_u64_varint(300, 2, "AC02"); + do_test_u64_varint(150, 2, "9601"); + do_test_u64_varint(240, 2, "F001"); + do_test_u64_varint(0x4000, 3, "808001"); + /* + 0100:0000 0000:0000 - input (0x4000) + 1000:0000 1000:0000 0000:0001 - output (0x808001) + 000:0000 000:0000 000:0001 - chop + 000:0001 000:0000 000:0000 - swap + 0:0000 0100:0000 0000:0000 - concat = input + */ + do_test_u64_varint(2147483647, 5, "FFFFFFFF07"); + /* + 0111:1111 1111:1111 1111:1111 1111:1111 - input (0x7FFFFFFF) + 1111:1111 1111:1111 1111:1111 1111:1111 0000:0111 - output(0xFFFFFFFF07) + 111:1111 111:1111 111:1111 111:1111 000:0111 - chop + 000:0111 111:1111 111:1111 111:1111 111:1111 - swap + 0111:1111 1111:1111 1111:1111 1111:1111 - concat = input + | | | | + 2^32 2^16 2^8 2^0 + */ + do_test_s64_varint(1, 1, "02"); + do_test_s64_varint(-1, 1, "01"); + do_test_s64_varint(-2, 1, "03"); + + do_test_u32_varint(2147483647, 5, "FFFFFFFF07"); + /* + 0111:1111 1111:1111 1111:1111 1111:1111 - input (7fffffff) + 1111:1111 1111:1111 1111:1111 1111:1111 0000:0111 - output (ffffff07) + 111:1111 111:1111 111:1111 111:1111 000:0111 - chop + 000:0111 111:1111 111:1111 111:1111 111:1111 - swap + 0111:1111 1111:1111 1111:1111 1111:1111 - concat = input + | | | | + 2^32 2^16 2^8 2^0 + */ + do_test_s32_varint(2147483647, 5, "FEFFFFFF0F"); + /* + 0111:1111 1111:1111 1111:1111 1111:1111 - input (7fffffff) + 1111:1110 1111:1111 1111:1111 1111:1111 0000:1111 - output(feffffff0f) + 1111:1111 1111:1111 1111:1111 1111:1111 0000:0111 - zigzag (ffffff07) + 111:1111 111:1111 111:1111 111:1111 000:0111 - chop + 000:0111 111:1111 111:1111 111:1111 111:1111 - swap + 0111:1111 1111:1111 1111:1111 1111:1111 - concat = input + | | | | + 2^32 2^16 2^8 2^0 + */ + do_test_s32_varint(-2147483648, 5, "FFFFFFFF0F"); + + do_test_s32_varint(1, 1, "02"); + /* + 0000:0001 - input (01) + 0000:0010 - A: input << 1 + 0000:0000 - B: input >> 31 + 0000:0010 - zigzag (A xor B) == output + */ + + do_test_s32_varint(-1, 1, "01"); + /* + 1111:1111 ... 1111:1111 - input (FFFFFFFF) + 1111:1111 ... 1111:1110 - A: input << 1 + 1111:1111 ... 1111:1111 - B: input >> 31 + 0000:0000 ... 0000:0001 - zigzag (A xor B) == output + */ + + +} + + +static void do_test_u64_roundtrip(uint64_t i64_in) +{ + uint8_t buffer[16]; + uint64_t i64_out; + size_t size_in, size_out; + size_in = varint_u64_encode_buf(i64_in, buffer); + i64_out = varint_u64_decode(buffer, buffer + size_in, &size_out); + CU_ASSERT_EQUAL(i64_in, i64_out); + CU_ASSERT_EQUAL(size_in, size_out); +} + +static void do_test_s64_roundtrip(int64_t i64_in) +{ + uint8_t buffer[16]; + int64_t i64_out; + size_t size_in, size_out; + size_in = varint_s64_encode_buf(i64_in, buffer); + i64_out = varint_s64_decode(buffer, buffer + size_in, &size_out); + CU_ASSERT_EQUAL(i64_in, i64_out); + CU_ASSERT_EQUAL(size_in, size_out); +} + +static void test_varint_roundtrip(void) +{ + + do_test_u64_roundtrip(0xFFFFFFFFFFFFFFFF); + + int i; + for ( i = 0; i < 1024; i += 63 ) + { + do_test_u64_roundtrip(i); + do_test_s64_roundtrip(i); + do_test_s64_roundtrip(-1*i); + } +} + +static void test_zigzag(void) +{ + int64_t a; + int32_t b; + int i; + + for ( i = 1; i < 1024; i += 31 ) + { + a = b = i; + CU_ASSERT_EQUAL(a, unzigzag64(zigzag64(a))); + CU_ASSERT_EQUAL(b, unzigzag32(zigzag32(b))); + + a = b = -1 * i; + CU_ASSERT_EQUAL(a, unzigzag64(zigzag64(a))); + CU_ASSERT_EQUAL(b, unzigzag32(zigzag32(b))); + } + + //8 + CU_ASSERT_EQUAL(-INT8_MAX, unzigzag8(zigzag8(-INT8_MAX))); + CU_ASSERT_EQUAL(INT8_MAX, unzigzag8(zigzag8(INT8_MAX))); + CU_ASSERT_EQUAL(0, unzigzag8(zigzag8(0))); + + //32 + CU_ASSERT_EQUAL(-INT32_MAX, unzigzag32(zigzag32(-INT32_MAX))); + CU_ASSERT_EQUAL(INT32_MAX, unzigzag32(zigzag32(INT32_MAX))); + CU_ASSERT_EQUAL(0, unzigzag32(zigzag32(0))); + + //64 + CU_ASSERT_EQUAL(-INT64_MAX, unzigzag64(zigzag64(-INT64_MAX))); + CU_ASSERT_EQUAL(INT64_MAX, unzigzag64(zigzag64(INT64_MAX))); + CU_ASSERT_EQUAL(0, unzigzag64(zigzag64(0))); +} + + +/* +** Used by the test harness to register the tests in this file. +*/ +void varint_suite_setup(void); +void varint_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("varint", NULL, NULL); + PG_ADD_TEST(suite, test_zigzag); + PG_ADD_TEST(suite, test_varint); + PG_ADD_TEST(suite, test_varint_roundtrip); +} diff --git a/mgist-postgis/liblwgeom/cunit/cu_wrapx.c b/mgist-postgis/liblwgeom/cunit/cu_wrapx.c new file mode 100644 index 0000000..a5ca09a --- /dev/null +++ b/mgist-postgis/liblwgeom/cunit/cu_wrapx.c @@ -0,0 +1,198 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright (C) 2016 Sandro Santilli + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include "CUnit/Basic.h" +#include "cu_tester.h" + +#include "liblwgeom.h" +#include "liblwgeom_internal.h" +#include "../lwgeom_geos.h" + +static void test_lwgeom_wrapx(void) +{ + LWGEOM *geom, *ret, *tmp, *tmp2; + char *exp_wkt, *obt_wkt; + + geom = lwgeom_from_wkt( + "POLYGON EMPTY", + LW_PARSER_CHECK_NONE); + CU_ASSERT_FATAL(geom != NULL); + ret = lwgeom_wrapx(geom, 0, 20); + CU_ASSERT_FATAL(ret != NULL); + obt_wkt = lwgeom_to_ewkt(ret); + exp_wkt = "POLYGON EMPTY"; + ASSERT_STRING_EQUAL(obt_wkt, exp_wkt); + lwfree(obt_wkt); + lwgeom_free(ret); + lwgeom_free(geom); + + geom = lwgeom_from_wkt( + "POINT(0 0)", + LW_PARSER_CHECK_NONE); + CU_ASSERT_FATAL(geom != NULL); + ret = lwgeom_wrapx(geom, 2, 10); + CU_ASSERT_FATAL(ret != NULL); + obt_wkt = lwgeom_to_ewkt(ret); + exp_wkt = "POINT(10 0)"; + ASSERT_STRING_EQUAL(obt_wkt, exp_wkt); + lwfree(obt_wkt); + lwgeom_free(ret); + lwgeom_free(geom); + + geom = lwgeom_from_wkt( + "POINT(0 0)", + LW_PARSER_CHECK_NONE); + CU_ASSERT_FATAL(geom != NULL); + ret = lwgeom_wrapx(geom, 0, 20); + CU_ASSERT_FATAL(ret != NULL); + obt_wkt = lwgeom_to_ewkt(ret); + exp_wkt = "POINT(0 0)"; + ASSERT_STRING_EQUAL(obt_wkt, exp_wkt); + lwfree(obt_wkt); + lwgeom_free(ret); + lwgeom_free(geom); + + geom = lwgeom_from_wkt( + "POINT(0 0)", + LW_PARSER_CHECK_NONE); + CU_ASSERT_FATAL(geom != NULL); + ret = lwgeom_wrapx(geom, 0, -20); + CU_ASSERT_FATAL(ret != NULL); + obt_wkt = lwgeom_to_ewkt(ret); + exp_wkt = "POINT(0 0)"; + ASSERT_STRING_EQUAL(obt_wkt, exp_wkt); + lwfree(obt_wkt); + lwgeom_free(ret); + lwgeom_free(geom); + + geom = lwgeom_from_wkt( + "LINESTRING(0 0,10 0)", + LW_PARSER_CHECK_NONE); + CU_ASSERT_FATAL(geom != NULL); + tmp = lwgeom_wrapx(geom, 8, -10); + ret = lwgeom_normalize(tmp); + lwgeom_free(tmp); + CU_ASSERT_FATAL(ret != NULL); + obt_wkt = lwgeom_to_ewkt(ret); + tmp = lwgeom_from_wkt( + "MULTILINESTRING((0 0,8 0),(-2 0,0 0))", + LW_PARSER_CHECK_NONE); + tmp2 = lwgeom_normalize(tmp); + lwgeom_free(tmp); + exp_wkt = lwgeom_to_ewkt(tmp2); + lwgeom_free(tmp2); + ASSERT_STRING_EQUAL(obt_wkt, exp_wkt); + lwfree(obt_wkt); + lwfree(exp_wkt); + lwgeom_free(ret); + lwgeom_free(geom); + + geom = lwgeom_from_wkt( + "MULTILINESTRING((-5 -2,0 0),(0 0,10 10))", + LW_PARSER_CHECK_NONE); + CU_ASSERT_FATAL(geom != NULL); + tmp = lwgeom_wrapx(geom, 0, 20); + ret = lwgeom_normalize(tmp); + lwgeom_free(tmp); + CU_ASSERT_FATAL(ret != NULL); + obt_wkt = lwgeom_to_ewkt(ret); + tmp = lwgeom_from_wkt("MULTILINESTRING((15 -2,20 0),(0 0,10 10))", LW_PARSER_CHECK_NONE); + tmp2 = lwgeom_normalize(tmp); + lwgeom_free(tmp); + exp_wkt = lwgeom_to_ewkt(tmp2); + lwgeom_free(tmp2); + ASSERT_STRING_EQUAL(obt_wkt, exp_wkt); + lwfree(obt_wkt); + lwfree(exp_wkt); + lwgeom_free(ret); + lwgeom_free(geom); + + geom = lwgeom_from_wkt( + "MULTIPOLYGON(" + " ((0 0,10 0,10 10,0 10,0 0),(2 2,4 2,4 4,2 4,2 2))," + " ((0 11,10 11,10 21,0 21,0 11),(2 13,4 13,4 15,2 15,2 13))" + ")", + LW_PARSER_CHECK_NONE); + CU_ASSERT_FATAL(geom != NULL); + tmp = lwgeom_wrapx(geom, 2, 20); + ret = lwgeom_normalize(tmp); + lwgeom_free(tmp); + CU_ASSERT_FATAL(ret != NULL); + obt_wkt = lwgeom_to_ewkt(ret); + tmp = lwgeom_from_wkt("GEOMETRYCOLLECTION(" + "MULTIPOLYGON(" + "((22 0,20 0,20 10,22 10,22 4,22 2,22 0))," + "((2 10,10 10,10 0,2 0,2 2,4 2,4 4,2 4,2 10))" + ")," + "MULTIPOLYGON(" + "((22 11,20 11,20 21,22 21,22 15,22 13,22 11))," + "((2 21,10 21,10 11,2 11,2 13,4 13,4 15,2 15,2 21))" + ")" + ")", + LW_PARSER_CHECK_NONE); + tmp2 = lwgeom_normalize(tmp); + lwgeom_free(tmp); + exp_wkt = lwgeom_to_ewkt(tmp2); + lwgeom_free(tmp2); + ASSERT_STRING_EQUAL(obt_wkt, exp_wkt); + lwfree(obt_wkt); + lwfree(exp_wkt); + lwgeom_free(ret); + lwgeom_free(geom); + + geom = lwgeom_from_wkt( + "GEOMETRYCOLLECTION(" + " MULTILINESTRING((-5 -2,0 0),(0 0,10 10))," + " POINT(-5 0)," + " POLYGON EMPTY" + ")", + LW_PARSER_CHECK_NONE); + CU_ASSERT_FATAL(geom != NULL); + tmp = lwgeom_wrapx(geom, 0, 20); + ret = lwgeom_normalize(tmp); + lwgeom_free(tmp); + CU_ASSERT_FATAL(ret != NULL); + obt_wkt = lwgeom_to_ewkt(ret); + tmp = lwgeom_from_wkt( + "GEOMETRYCOLLECTION(" + "MULTILINESTRING((15 -2,20 0),(0 0,10 10))," + "POINT(15 0)," + "POLYGON EMPTY" + ")", + LW_PARSER_CHECK_NONE); + tmp2 = lwgeom_normalize(tmp); + lwgeom_free(tmp); + exp_wkt = lwgeom_to_ewkt(tmp2); + lwgeom_free(tmp2); + ASSERT_STRING_EQUAL(obt_wkt, exp_wkt); + lwfree(obt_wkt); + lwfree(exp_wkt); + lwgeom_free(ret); + lwgeom_free(geom); +} + +static int +clean_geos_wrapx_suite(void) +{ + finishGEOS(); + return 0; +} + +/* +** Used by test harness to register the tests in this file. +*/ +void wrapx_suite_setup(void); +void wrapx_suite_setup(void) +{ + CU_pSuite suite = CU_add_suite("wrapx", NULL, clean_geos_wrapx_suite); + PG_ADD_TEST(suite, test_lwgeom_wrapx); +} diff --git a/mgist-postgis/liblwgeom/effectivearea.c b/mgist-postgis/liblwgeom/effectivearea.c new file mode 100644 index 0000000..e985921 --- /dev/null +++ b/mgist-postgis/liblwgeom/effectivearea.c @@ -0,0 +1,561 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2014 Nicklas Avén + * + **********************************************************************/ + + + #include "effectivearea.h" + + +EFFECTIVE_AREAS* +initiate_effectivearea(const POINTARRAY *inpts) +{ + LWDEBUG(2, "Entered initiate_effectivearea"); + EFFECTIVE_AREAS *ea; + ea=lwalloc(sizeof(EFFECTIVE_AREAS)); + ea->initial_arealist = lwalloc(inpts->npoints*sizeof(areanode)); + ea->res_arealist = lwalloc(inpts->npoints*sizeof(double)); + ea->inpts=inpts; + return ea; +} + + +void destroy_effectivearea(EFFECTIVE_AREAS *ea) +{ + lwfree(ea->initial_arealist); + lwfree(ea->res_arealist); + lwfree(ea); +} + + +static MINHEAP +initiate_minheap(int npoints) +{ + MINHEAP tree; + tree.key_array = lwalloc(npoints*sizeof(void*)); + tree.maxSize=npoints; + tree.usedSize=0; + return tree; +} + + +static void +destroy_minheap(MINHEAP tree) +{ + lwfree(tree.key_array); +} + + +/** + +Calculate the area of a triangle in 2d +*/ +static double triarea2d(const double *P1, const double *P2, const double *P3) +{ + return fabs(0.5*((P1[0]-P2[0])*(P3[1]-P2[1])-(P1[1]-P2[1])*(P3[0]-P2[0]))); +} + +/** + +Calculate the area of a triangle in 3d space +*/ +static double triarea3d(const double *P1, const double *P2, const double *P3) +{ + LWDEBUG(2, "Entered triarea3d"); + double ax,bx,ay,by,az,bz,cx,cy,cz, area; + + ax=P1[0]-P2[0]; + bx=P3[0]-P2[0]; + ay=P1[1]-P2[1]; + by=P3[1]-P2[1]; + az=P1[2]-P2[2]; + bz=P3[2]-P2[2]; + + cx = ay*bz - az*by; + cy = az*bx - ax*bz; + cz = ax*by - ay*bx; + + area = fabs(0.5*(sqrt(cx*cx+cy*cy+cz*cz))); + return area; +} + +/** + +We create the minheap by ordering the minheap array by the areas in the areanode structs that the minheap keys refer to +*/ +static int cmpfunc (const void * a, const void * b) +{ + double v1 = (*(areanode**)a)->area; + double v2 = (*(areanode**)b)->area; + /*qsort gives unpredictable results when comparing identical values. + If two values is the same we force returning the last point in the point array. + That way we get the same ordering on different machines and platforms*/ + if (v1==v2) + return (*(areanode**)a)-(*(areanode**)b); + else + return (v1 > v2) ? 1 : ((v1 < v2) ? -1 : 0); +} + + +/** + +Sift Down +*/ +static void down(MINHEAP *tree,areanode *arealist,int parent) +{ + LWDEBUG(2, "Entered down"); + areanode **treearray=tree->key_array; + int left=parent*2+1; + int right = left +1; + void *tmp; + int swap=parent; + double leftarea=0; + double rightarea=0; + + double parentarea=((areanode*) treearray[parent])->area; + + if(leftusedSize) + { + leftarea=((areanode*) treearray[left])->area; + if(parentarea>leftarea) + swap=left; + } + if(rightusedSize) + { + rightarea=((areanode*) treearray[right])->area; + if(rightareaparent) + { + /*ok, we have to swap something*/ + tmp=treearray[parent]; + treearray[parent]=treearray[swap]; + /*Update reference*/ + ((areanode*) treearray[parent])->treeindex=parent; + treearray[swap]=tmp; + /*Update reference*/ + ((areanode*) treearray[swap])->treeindex=swap; + if(swapusedSize) + down(tree,arealist,swap); + } + return; +} + + +/** + +Sift Up +*/ +static void up(MINHEAP *tree, __attribute__((__unused__)) areanode *e,int c) +{ + LWDEBUG(2, "Entered up"); + void *tmp; + + areanode **treearray=tree->key_array; + + int parent=floor((c-1)/2); + + while(((areanode*) treearray[c])->area<((areanode*) treearray[parent])->area) + { + /*ok, we have to swap*/ + tmp=treearray[parent]; + treearray[parent]=treearray[c]; + /*Update reference*/ + ((areanode*) treearray[parent])->treeindex=parent; + treearray[c]=tmp; + /*Update reference*/ + ((areanode*) treearray[c])->treeindex=c; + c=parent; + parent=floor((c-1)/2); + } + return; +} + + +/** + +Get a reference to the point with the smallest effective area from the root of the min heap +*/ +static areanode* minheap_pop(MINHEAP *tree,areanode *arealist ) +{ + LWDEBUG(2, "Entered minheap_pop"); + areanode *res = tree->key_array[0]; + + /*put last value first*/ + tree->key_array[0]=tree->key_array[(tree->usedSize)-1]; + ((areanode*) tree->key_array[0])->treeindex=0; + + tree->usedSize--; + down(tree,arealist,0); + return res; +} + + +/** + +The member of the minheap at index idx is changed. Update the tree and make restore the heap property +*/ +static void minheap_update(MINHEAP *tree,areanode *arealist , int idx) +{ + areanode **treearray=tree->key_array; + int parent=floor((idx-1)/2); + + if(((areanode*) treearray[idx])->area<((areanode*) treearray[parent])->area) + up(tree,arealist,idx); + else + down(tree,arealist,idx); + return; +} + +/** + +To get the effective area, we have to check what area a point results in when all smaller areas are eliminated +*/ +static void tune_areas(EFFECTIVE_AREAS *ea, int avoid_collaps, int set_area, double trshld) +{ + LWDEBUG(2, "Entered tune_areas"); + const double *P1; + const double *P2; + const double *P3; + double area; + int go_on=1; + double check_order_min_area = 0; + + int npoints=ea->inpts->npoints; + int i; + int current, before_current, after_current; + + MINHEAP tree = initiate_minheap(npoints); + + int is3d = FLAGS_GET_Z(ea->inpts->flags); + + + /*Add all keys (index in initial_arealist) into minheap array*/ + for (i=0;iinitial_arealist+i; + LWDEBUGF(2, "add nr %d, with area %lf, and %lf",i,ea->initial_arealist[i].area, tree.key_array[i]->area ); + } + tree.usedSize=npoints; + + /*order the keys by area, small to big*/ + qsort(tree.key_array, npoints, sizeof(void*), cmpfunc); + + /*We have to put references to our tree in our point-list*/ + for (i=0;itreeindex=i; + LWDEBUGF(4,"Check ordering qsort gives, area=%lf and belong to point %d",((areanode*) tree.key_array[i])->area, tree.key_array[i]-ea->initial_arealist); + } + /*Ok, now we have a minHeap, just need to keep it*/ + + /*for (i=0;iinitial_arealist)-ea->initial_arealist; + + /*We have found the smallest area. That is the resulting effective area for the "current" point*/ + if (ires_arealist[current]=ea->initial_arealist[current].area; + else + ea->res_arealist[current]=FLT_MAX; + + if(ea->res_arealist[current]res_arealist[current],check_order_min_area); + + check_order_min_area=ea->res_arealist[current]; + + /*The found smallest area point is now regarded as eliminated and we have to recalculate the area the adjacent (ignoring earlier eliminated points) points gives*/ + + /*FInd point before and after*/ + before_current=ea->initial_arealist[current].prev; + after_current=ea->initial_arealist[current].next; + + P2= (double*)getPoint_internal(ea->inpts, before_current); + P3= (double*)getPoint_internal(ea->inpts, after_current); + + /*Check if point before current point is the first in the point array. */ + if(before_current>0) + { + + P1= (double*)getPoint_internal(ea->inpts, ea->initial_arealist[before_current].prev); + if(is3d) + area=triarea3d(P1, P2, P3); + else + area=triarea2d(P1, P2, P3); + + ea->initial_arealist[before_current].area = FP_MAX(area,ea->res_arealist[current]); + minheap_update(&tree, ea->initial_arealist, ea->initial_arealist[before_current].treeindex); + } + if(after_currentinpts, ea->initial_arealist[after_current].next); + + + if(is3d) + area=triarea3d(P1, P2, P3); + else + area=triarea2d(P1, P2, P3); + + + ea->initial_arealist[after_current].area = FP_MAX(area,ea->res_arealist[current]); + minheap_update(&tree, ea->initial_arealist, ea->initial_arealist[after_current].treeindex); + } + + /*rearrange the nodes so the eliminated point will be ingored on the next run*/ + ea->initial_arealist[before_current].next = ea->initial_arealist[current].next; + ea->initial_arealist[after_current].prev = ea->initial_arealist[current].prev; + + /*Check if we are finished*/ + if((!set_area && ea->res_arealist[current]>=trshld) || (ea->initial_arealist[0].next==(npoints-1))) + go_on=0; + + i++; + }; + destroy_minheap(tree); + return; +} + + +/** + +We calculate the effective area for the first time +*/ +void ptarray_calc_areas(EFFECTIVE_AREAS *ea, int avoid_collaps, int set_area, double trshld) +{ + LWDEBUG(2, "Entered ptarray_calc_areas"); + int i; + int npoints=ea->inpts->npoints; + int is3d = FLAGS_GET_Z(ea->inpts->flags); + double area; + + const double *P1; + const double *P2; + const double *P3; + + P1 = (double*)getPoint_internal(ea->inpts, 0); + P2 = (double*)getPoint_internal(ea->inpts, 1); + + /*The first and last point shall always have the maximum effective area. We use float max to not make trouble for bbox*/ + ea->initial_arealist[0].area=ea->initial_arealist[npoints-1].area=FLT_MAX; + ea->res_arealist[0]=ea->res_arealist[npoints-1]=FLT_MAX; + + ea->initial_arealist[0].next=1; + ea->initial_arealist[0].prev=0; + + for (i=1;i<(npoints)-1;i++) + { + ea->initial_arealist[i].next=i+1; + ea->initial_arealist[i].prev=i-1; + P3 = (double*)getPoint_internal(ea->inpts, i+1); + + if(is3d) + area=triarea3d(P1, P2, P3); + else + area=triarea2d(P1, P2, P3); + + LWDEBUGF(4,"Write area %lf to point %d on address %p",area,i,&(ea->initial_arealist[i].area)); + ea->initial_arealist[i].area=area; + P1=P2; + P2=P3; + + } + ea->initial_arealist[npoints-1].next=npoints-1; + ea->initial_arealist[npoints-1].prev=npoints-2; + + for (i=1;i<(npoints)-1;i++) + { + ea->res_arealist[i]=FLT_MAX; + } + + tune_areas(ea,avoid_collaps,set_area, trshld); + return ; +} + + + +static POINTARRAY * ptarray_set_effective_area(POINTARRAY *inpts,int avoid_collaps,int set_area, double trshld) +{ + LWDEBUG(2, "Entered ptarray_set_effective_area"); + uint32_t p; + POINT4D pt; + EFFECTIVE_AREAS *ea; + POINTARRAY *opts; + int set_m; + if(set_area) + set_m=1; + else + set_m=FLAGS_GET_M(inpts->flags); + ea=initiate_effectivearea(inpts); + + opts = ptarray_construct_empty(FLAGS_GET_Z(inpts->flags), set_m, inpts->npoints); + + ptarray_calc_areas(ea,avoid_collaps,set_area,trshld); + + if(set_area) + { + /*Only return points with an effective area above the threshold*/ + for (p=0;pinpts->npoints;p++) + { + if(ea->res_arealist[p]>=trshld) + { + pt=getPoint4d(ea->inpts, p); + pt.m=ea->res_arealist[p]; + ptarray_append_point(opts, &pt, LW_TRUE); + } + } + } + else + { + /*Only return points with an effective area above the threshold*/ + for (p=0;pinpts->npoints;p++) + { + if(ea->res_arealist[p]>=trshld) + { + pt=getPoint4d(ea->inpts, p); + ptarray_append_point(opts, &pt, LW_TRUE); + } + } + } + destroy_effectivearea(ea); + + return opts; + +} + +static LWLINE* lwline_set_effective_area(const LWLINE *iline,int set_area, double trshld) +{ + LWDEBUG(2, "Entered lwline_set_effective_area"); + + /* Skip empty case or too small to simplify */ + if( lwline_is_empty(iline) || iline->points->npoints<3) + return lwline_clone(iline); + + int set_m; + if(set_area) + set_m=1; + else + set_m=FLAGS_GET_M(iline->flags); + + LWLINE *oline = lwline_construct_empty(iline->srid, FLAGS_GET_Z(iline->flags), set_m); + + + + oline = lwline_construct(iline->srid, NULL, ptarray_set_effective_area(iline->points,2,set_area,trshld)); + + oline->type = iline->type; + return oline; + +} + + +static LWPOLY* lwpoly_set_effective_area(const LWPOLY *ipoly,int set_area, double trshld) +{ + LWDEBUG(2, "Entered lwpoly_set_effective_area"); + uint32_t i; + int set_m; + int avoid_collapse=4; + if(set_area) + set_m=1; + else + set_m=FLAGS_GET_M(ipoly->flags); + LWPOLY *opoly = lwpoly_construct_empty(ipoly->srid, FLAGS_GET_Z(ipoly->flags), set_m); + + if( lwpoly_is_empty(ipoly) ) + return opoly; /* should we return NULL instead ? */ + + for (i = 0; i < ipoly->nrings; i++) + { + POINTARRAY *pa = ptarray_set_effective_area(ipoly->rings[i],avoid_collapse,set_area,trshld); + /* Add ring to simplified polygon */ + if(pa->npoints>=4) + { + if( lwpoly_add_ring(opoly,pa ) == LW_FAILURE ) + return NULL; + } + /*Inner rings we allow to collapse and then we remove them*/ + avoid_collapse=0; + } + + + opoly->type = ipoly->type; + + if( lwpoly_is_empty(opoly)) + return NULL; + + return opoly; + +} + + +static LWCOLLECTION* lwcollection_set_effective_area(const LWCOLLECTION *igeom,int set_area, double trshld) +{ + LWDEBUG(2, "Entered lwcollection_set_effective_area"); + uint32_t i; + int set_m; + if(set_area) + set_m=1; + else + set_m=FLAGS_GET_M(igeom->flags); + LWCOLLECTION *out = lwcollection_construct_empty(igeom->type, igeom->srid, FLAGS_GET_Z(igeom->flags), set_m); + + if( lwcollection_is_empty(igeom) ) + return out; /* should we return NULL instead ? */ + + for( i = 0; i < igeom->ngeoms; i++ ) + { + LWGEOM *ngeom = lwgeom_set_effective_area(igeom->geoms[i],set_area,trshld); + if ( ngeom ) out = lwcollection_add_lwgeom(out, ngeom); + } + + return out; +} + + +LWGEOM* lwgeom_set_effective_area(const LWGEOM *igeom,int set_area, double trshld) +{ + LWDEBUG(2, "Entered lwgeom_set_effective_area"); + switch (igeom->type) + { + case POINTTYPE: + case MULTIPOINTTYPE: + return lwgeom_clone(igeom); + case LINETYPE: + return (LWGEOM*)lwline_set_effective_area((LWLINE*)igeom,set_area, trshld); + case POLYGONTYPE: + return (LWGEOM*)lwpoly_set_effective_area((LWPOLY*)igeom,set_area, trshld); + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + return (LWGEOM*)lwcollection_set_effective_area((LWCOLLECTION *)igeom,set_area, trshld); + default: + lwerror("lwgeom_simplify: unsupported geometry type: %s",lwtype_name(igeom->type)); + } + return NULL; +} + diff --git a/mgist-postgis/liblwgeom/effectivearea.h b/mgist-postgis/liblwgeom/effectivearea.h new file mode 100644 index 0000000..01d023f --- /dev/null +++ b/mgist-postgis/liblwgeom/effectivearea.h @@ -0,0 +1,78 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2014 Nicklas Avén + * + **********************************************************************/ + + #ifndef _EFFECTIVEAREA_H +#define _EFFECTIVEAREA_H 1 + + + #include "liblwgeom_internal.h" + #include "lwgeom_log.h" + + +/** + +This structure is placed in an array with one member per point. +It has links into the minheap rtee and keeps track of eliminated points +*/ +typedef struct +{ + double area; + int treeindex; + int prev; + int next; +} areanode; + + +/** + +This structure holds a minheap tree that is used to keep track of what points that has the smallest effective area. +When eliminating points the neighbor points has its effective area affected and the minheap helps to resort efficient. +*/ +typedef struct +{ + int maxSize; + int usedSize; + areanode **key_array; +} MINHEAP; + + +/** + +Structure to hold pointarray and its arealist +*/ +typedef struct +{ + const POINTARRAY *inpts; + areanode *initial_arealist; + double *res_arealist; +} EFFECTIVE_AREAS; + + +EFFECTIVE_AREAS* initiate_effectivearea(const POINTARRAY *inpts); + +void destroy_effectivearea(EFFECTIVE_AREAS *ea); + +void ptarray_calc_areas(EFFECTIVE_AREAS *ea,int avoid_collaps, int set_area, double trshld); + +#endif /* _EFFECTIVEAREA_H */ diff --git a/mgist-postgis/liblwgeom/gbox.c b/mgist-postgis/liblwgeom/gbox.c new file mode 100644 index 0000000..265c692 --- /dev/null +++ b/mgist-postgis/liblwgeom/gbox.c @@ -0,0 +1,848 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2009 Paul Ramsey + * + **********************************************************************/ + +#include "liblwgeom_internal.h" +#include "lwgeodetic.h" +#include "lwgeom_log.h" +#include +#include + + +GBOX* gbox_new(lwflags_t flags) +{ + GBOX *g = (GBOX*)lwalloc(sizeof(GBOX)); + gbox_init(g); + g->flags = flags; + return g; +} + +void gbox_init(GBOX *gbox) +{ + memset(gbox, 0, sizeof(GBOX)); +} + +GBOX* gbox_clone(const GBOX *gbox) +{ + GBOX *g = lwalloc(sizeof(GBOX)); + memcpy(g, gbox, sizeof(GBOX)); + return g; +} + +/* TODO to be removed */ +BOX3D* box3d_from_gbox(const GBOX *gbox) +{ + BOX3D *b; + assert(gbox); + + b = lwalloc(sizeof(BOX3D)); + + b->xmin = gbox->xmin; + b->xmax = gbox->xmax; + b->ymin = gbox->ymin; + b->ymax = gbox->ymax; + + if ( FLAGS_GET_Z(gbox->flags) ) + { + b->zmin = gbox->zmin; + b->zmax = gbox->zmax; + } + else + { + b->zmin = b->zmax = 0.0; + } + + b->srid = SRID_UNKNOWN; + return b; +} + +/* TODO to be removed */ +GBOX* box3d_to_gbox(const BOX3D *b3d) +{ + GBOX *b; + assert(b3d); + + b = lwalloc(sizeof(GBOX)); + + b->xmin = b3d->xmin; + b->xmax = b3d->xmax; + b->ymin = b3d->ymin; + b->ymax = b3d->ymax; + b->zmin = b3d->zmin; + b->zmax = b3d->zmax; + + return b; +} + +void gbox_expand(GBOX *g, double d) +{ + g->xmin -= d; + g->xmax += d; + g->ymin -= d; + g->ymax += d; + if (FLAGS_GET_Z(g->flags) || FLAGS_GET_GEODETIC(g->flags)) + { + g->zmin -= d; + g->zmax += d; + } + if (FLAGS_GET_M(g->flags)) + { + g->mmin -= d; + g->mmax += d; + } +} + +void gbox_expand_xyzm(GBOX *g, double dx, double dy, double dz, double dm) +{ + g->xmin -= dx; + g->xmax += dx; + g->ymin -= dy; + g->ymax += dy; + + if (FLAGS_GET_Z(g->flags)) + { + g->zmin -= dz; + g->zmax += dz; + } + + if (FLAGS_GET_M(g->flags)) + { + g->mmin -= dm; + g->mmax += dm; + } +} + +int gbox_union(const GBOX *g1, const GBOX *g2, GBOX *gout) +{ + if ( ( ! g1 ) && ( ! g2 ) ) + return LW_FALSE; + else if (!g1) + { + memcpy(gout, g2, sizeof(GBOX)); + return LW_TRUE; + } + else if (!g2) + { + memcpy(gout, g1, sizeof(GBOX)); + return LW_TRUE; + } + + gout->flags = g1->flags; + + gout->xmin = FP_MIN(g1->xmin, g2->xmin); + gout->xmax = FP_MAX(g1->xmax, g2->xmax); + + gout->ymin = FP_MIN(g1->ymin, g2->ymin); + gout->ymax = FP_MAX(g1->ymax, g2->ymax); + + gout->zmin = FP_MIN(g1->zmin, g2->zmin); + gout->zmax = FP_MAX(g1->zmax, g2->zmax); + + return LW_TRUE; +} + +int gbox_same(const GBOX *g1, const GBOX *g2) +{ + if (FLAGS_GET_ZM(g1->flags) != FLAGS_GET_ZM(g2->flags)) + return LW_FALSE; + + if (!gbox_same_2d(g1, g2)) return LW_FALSE; + + if (FLAGS_GET_Z(g1->flags) && (g1->zmin != g2->zmin || g1->zmax != g2->zmax)) + return LW_FALSE; + if (FLAGS_GET_M(g1->flags) && (g1->mmin != g2->mmin || g1->mmax != g2->mmax)) + return LW_FALSE; + + return LW_TRUE; +} + +int gbox_same_2d(const GBOX *g1, const GBOX *g2) +{ + if (g1->xmin == g2->xmin && g1->ymin == g2->ymin && + g1->xmax == g2->xmax && g1->ymax == g2->ymax) + return LW_TRUE; + return LW_FALSE; +} + +int gbox_same_2d_float(const GBOX *g1, const GBOX *g2) +{ + if ((g1->xmax == g2->xmax || next_float_up(g1->xmax) == next_float_up(g2->xmax)) && + (g1->ymax == g2->ymax || next_float_up(g1->ymax) == next_float_up(g2->ymax)) && + (g1->xmin == g2->xmin || next_float_down(g1->xmin) == next_float_down(g1->xmin)) && + (g1->ymin == g2->ymin || next_float_down(g2->ymin) == next_float_down(g2->ymin))) + return LW_TRUE; + return LW_FALSE; +} + +int gbox_is_valid(const GBOX *gbox) +{ + /* X */ + if ( ! isfinite(gbox->xmin) || isnan(gbox->xmin) || + ! isfinite(gbox->xmax) || isnan(gbox->xmax) ) + return LW_FALSE; + + /* Y */ + if ( ! isfinite(gbox->ymin) || isnan(gbox->ymin) || + ! isfinite(gbox->ymax) || isnan(gbox->ymax) ) + return LW_FALSE; + + /* Z */ + if ( FLAGS_GET_GEODETIC(gbox->flags) || FLAGS_GET_Z(gbox->flags) ) + { + if ( ! isfinite(gbox->zmin) || isnan(gbox->zmin) || + ! isfinite(gbox->zmax) || isnan(gbox->zmax) ) + return LW_FALSE; + } + + /* M */ + if ( FLAGS_GET_M(gbox->flags) ) + { + if ( ! isfinite(gbox->mmin) || isnan(gbox->mmin) || + ! isfinite(gbox->mmax) || isnan(gbox->mmax) ) + return LW_FALSE; + } + + return LW_TRUE; +} + +int gbox_merge_point3d(const POINT3D *p, GBOX *gbox) +{ + if ( gbox->xmin > p->x ) gbox->xmin = p->x; + if ( gbox->ymin > p->y ) gbox->ymin = p->y; + if ( gbox->zmin > p->z ) gbox->zmin = p->z; + if ( gbox->xmax < p->x ) gbox->xmax = p->x; + if ( gbox->ymax < p->y ) gbox->ymax = p->y; + if ( gbox->zmax < p->z ) gbox->zmax = p->z; + return LW_SUCCESS; +} + +int gbox_init_point3d(const POINT3D *p, GBOX *gbox) +{ + gbox->xmin = gbox->xmax = p->x; + gbox->ymin = gbox->ymax = p->y; + gbox->zmin = gbox->zmax = p->z; + return LW_SUCCESS; +} + +int gbox_contains_point3d(const GBOX *gbox, const POINT3D *pt) +{ + if ( gbox->xmin > pt->x || gbox->ymin > pt->y || gbox->zmin > pt->z || + gbox->xmax < pt->x || gbox->ymax < pt->y || gbox->zmax < pt->z ) + { + return LW_FALSE; + } + return LW_TRUE; +} + +int gbox_merge(const GBOX *new_box, GBOX *merge_box) +{ + assert(merge_box); + + if ( FLAGS_GET_ZM(merge_box->flags) != FLAGS_GET_ZM(new_box->flags) ) + return LW_FAILURE; + + if ( new_box->xmin < merge_box->xmin) merge_box->xmin = new_box->xmin; + if ( new_box->ymin < merge_box->ymin) merge_box->ymin = new_box->ymin; + if ( new_box->xmax > merge_box->xmax) merge_box->xmax = new_box->xmax; + if ( new_box->ymax > merge_box->ymax) merge_box->ymax = new_box->ymax; + + if ( FLAGS_GET_Z(merge_box->flags) || FLAGS_GET_GEODETIC(merge_box->flags) ) + { + if ( new_box->zmin < merge_box->zmin) merge_box->zmin = new_box->zmin; + if ( new_box->zmax > merge_box->zmax) merge_box->zmax = new_box->zmax; + } + if ( FLAGS_GET_M(merge_box->flags) ) + { + if ( new_box->mmin < merge_box->mmin) merge_box->mmin = new_box->mmin; + if ( new_box->mmax > merge_box->mmax) merge_box->mmax = new_box->mmax; + } + + return LW_SUCCESS; +} + +int gbox_overlaps(const GBOX *g1, const GBOX *g2) +{ + + /* Make sure our boxes are consistent */ + if ( FLAGS_GET_GEODETIC(g1->flags) != FLAGS_GET_GEODETIC(g2->flags) ) + lwerror("gbox_overlaps: cannot compare geodetic and non-geodetic boxes"); + + /* Check X/Y first */ + if ( g1->xmax < g2->xmin || g1->ymax < g2->ymin || + g1->xmin > g2->xmax || g1->ymin > g2->ymax ) + return LW_FALSE; + + /* Deal with the geodetic case special: we only compare the geodetic boxes (x/y/z) */ + /* Never the M dimension */ + if ( FLAGS_GET_GEODETIC(g1->flags) && FLAGS_GET_GEODETIC(g2->flags) ) + { + if ( g1->zmax < g2->zmin || g1->zmin > g2->zmax ) + return LW_FALSE; + else + return LW_TRUE; + } + + /* If both geodetic or both have Z, check Z */ + if ( FLAGS_GET_Z(g1->flags) && FLAGS_GET_Z(g2->flags) ) + { + if ( g1->zmax < g2->zmin || g1->zmin > g2->zmax ) + return LW_FALSE; + } + + /* If both have M, check M */ + if ( FLAGS_GET_M(g1->flags) && FLAGS_GET_M(g2->flags) ) + { + if ( g1->mmax < g2->mmin || g1->mmin > g2->mmax ) + return LW_FALSE; + } + + return LW_TRUE; +} + +int +gbox_overlaps_2d(const GBOX *g1, const GBOX *g2) +{ + + /* Make sure our boxes are consistent */ + if ( FLAGS_GET_GEODETIC(g1->flags) != FLAGS_GET_GEODETIC(g2->flags) ) + lwerror("gbox_overlaps: cannot compare geodetic and non-geodetic boxes"); + + /* Check X/Y first */ + if ( g1->xmax < g2->xmin || g1->ymax < g2->ymin || + g1->xmin > g2->xmax || g1->ymin > g2->ymax ) + return LW_FALSE; + + return LW_TRUE; +} + +int +gbox_contains_2d(const GBOX *g1, const GBOX *g2) +{ + if ( ( g2->xmin < g1->xmin ) || ( g2->xmax > g1->xmax ) || + ( g2->ymin < g1->ymin ) || ( g2->ymax > g1->ymax ) ) + { + return LW_FALSE; + } + return LW_TRUE; +} + +int +gbox_contains_point2d(const GBOX *g, const POINT2D *p) +{ + if ( ( g->xmin <= p->x ) && ( g->xmax >= p->x ) && + ( g->ymin <= p->y ) && ( g->ymax >= p->y ) ) + { + return LW_TRUE; + } + return LW_FALSE; +} + +/** +* Warning, this function is only good for x/y/z boxes, used +* in unit testing of geodetic box generation. +*/ +GBOX* gbox_from_string(const char *str) +{ + const char *ptr = str; + char *nextptr; + char *gbox_start = strstr(str, "GBOX(("); + GBOX *gbox = gbox_new(lwflags(0,0,1)); + if ( ! gbox_start ) return NULL; /* No header found */ + ptr += 6; + gbox->xmin = strtod(ptr, &nextptr); + if ( ptr == nextptr ) return NULL; /* No double found */ + ptr = nextptr + 1; + gbox->ymin = strtod(ptr, &nextptr); + if ( ptr == nextptr ) return NULL; /* No double found */ + ptr = nextptr + 1; + gbox->zmin = strtod(ptr, &nextptr); + if ( ptr == nextptr ) return NULL; /* No double found */ + ptr = nextptr + 3; + gbox->xmax = strtod(ptr, &nextptr); + if ( ptr == nextptr ) return NULL; /* No double found */ + ptr = nextptr + 1; + gbox->ymax = strtod(ptr, &nextptr); + if ( ptr == nextptr ) return NULL; /* No double found */ + ptr = nextptr + 1; + gbox->zmax = strtod(ptr, &nextptr); + if ( ptr == nextptr ) return NULL; /* No double found */ + return gbox; +} + +char* gbox_to_string(const GBOX *gbox) +{ + const size_t sz = 138; + char *str = NULL; + + if ( ! gbox ) + return lwstrdup("NULL POINTER"); + + str = (char*)lwalloc(sz); + + if ( FLAGS_GET_GEODETIC(gbox->flags) ) + { + snprintf(str, sz, "GBOX((%.8g,%.8g,%.8g),(%.8g,%.8g,%.8g))", gbox->xmin, gbox->ymin, gbox->zmin, gbox->xmax, gbox->ymax, gbox->zmax); + return str; + } + if ( FLAGS_GET_Z(gbox->flags) && FLAGS_GET_M(gbox->flags) ) + { + snprintf(str, sz, "GBOX((%.8g,%.8g,%.8g,%.8g),(%.8g,%.8g,%.8g,%.8g))", gbox->xmin, gbox->ymin, gbox->zmin, gbox->mmin, gbox->xmax, gbox->ymax, gbox->zmax, gbox->mmax); + return str; + } + if ( FLAGS_GET_Z(gbox->flags) ) + { + snprintf(str, sz, "GBOX((%.8g,%.8g,%.8g),(%.8g,%.8g,%.8g))", gbox->xmin, gbox->ymin, gbox->zmin, gbox->xmax, gbox->ymax, gbox->zmax); + return str; + } + if ( FLAGS_GET_M(gbox->flags) ) + { + snprintf(str, sz, "GBOX((%.8g,%.8g,%.8g),(%.8g,%.8g,%.8g))", gbox->xmin, gbox->ymin, gbox->mmin, gbox->xmax, gbox->ymax, gbox->mmax); + return str; + } + snprintf(str, sz, "GBOX((%.8g,%.8g),(%.8g,%.8g))", gbox->xmin, gbox->ymin, gbox->xmax, gbox->ymax); + return str; +} + +GBOX* gbox_copy(const GBOX *box) +{ + GBOX *copy = (GBOX*)lwalloc(sizeof(GBOX)); + memcpy(copy, box, sizeof(GBOX)); + return copy; +} + +void gbox_duplicate(const GBOX *original, GBOX *duplicate) +{ + assert(duplicate); + assert(original); + memcpy(duplicate, original, sizeof(GBOX)); +} + +size_t gbox_serialized_size(lwflags_t flags) +{ + if (FLAGS_GET_GEODETIC(flags)) + return 6 * sizeof(float); + else + return 2 * FLAGS_NDIMS(flags) * sizeof(float); +} + + +/* ******************************************************************************** +** Compute cartesian bounding GBOX boxes from LWGEOM. +*/ + +int lw_arc_calculate_gbox_cartesian_2d(const POINT2D *A1, const POINT2D *A2, const POINT2D *A3, GBOX *gbox) +{ + POINT2D xmin, ymin, xmax, ymax; + POINT2D C; + int A2_side; + double radius_A; + + LWDEBUG(2, "lw_arc_calculate_gbox_cartesian_2d called."); + + radius_A = lw_arc_center(A1, A2, A3, &C); + + /* Negative radius signals straight line, p1/p2/p3 are collinear */ + if (radius_A < 0.0) + { + gbox->xmin = FP_MIN(A1->x, A3->x); + gbox->ymin = FP_MIN(A1->y, A3->y); + gbox->xmax = FP_MAX(A1->x, A3->x); + gbox->ymax = FP_MAX(A1->y, A3->y); + return LW_SUCCESS; + } + + /* Matched start/end points imply circle */ + if ( A1->x == A3->x && A1->y == A3->y ) + { + gbox->xmin = C.x - radius_A; + gbox->ymin = C.y - radius_A; + gbox->xmax = C.x + radius_A; + gbox->ymax = C.y + radius_A; + return LW_SUCCESS; + } + + /* First approximation, bounds of start/end points */ + gbox->xmin = FP_MIN(A1->x, A3->x); + gbox->ymin = FP_MIN(A1->y, A3->y); + gbox->xmax = FP_MAX(A1->x, A3->x); + gbox->ymax = FP_MAX(A1->y, A3->y); + + /* Create points for the possible extrema */ + xmin.x = C.x - radius_A; + xmin.y = C.y; + ymin.x = C.x; + ymin.y = C.y - radius_A; + xmax.x = C.x + radius_A; + xmax.y = C.y; + ymax.x = C.x; + ymax.y = C.y + radius_A; + + /* Divide the circle into two parts, one on each side of a line + joining p1 and p3. The circle extrema on the same side of that line + as p2 is on, are also the extrema of the bbox. */ + + A2_side = lw_segment_side(A1, A3, A2); + + if ( A2_side == lw_segment_side(A1, A3, &xmin) ) + gbox->xmin = xmin.x; + + if ( A2_side == lw_segment_side(A1, A3, &ymin) ) + gbox->ymin = ymin.y; + + if ( A2_side == lw_segment_side(A1, A3, &xmax) ) + gbox->xmax = xmax.x; + + if ( A2_side == lw_segment_side(A1, A3, &ymax) ) + gbox->ymax = ymax.y; + + return LW_SUCCESS; +} + + +static int lw_arc_calculate_gbox_cartesian(const POINT4D *p1, const POINT4D *p2, const POINT4D *p3, GBOX *gbox) +{ + int rv; + + LWDEBUG(2, "lw_arc_calculate_gbox_cartesian called."); + + rv = lw_arc_calculate_gbox_cartesian_2d((POINT2D*)p1, (POINT2D*)p2, (POINT2D*)p3, gbox); + gbox->zmin = FP_MIN(p1->z, p3->z); + gbox->mmin = FP_MIN(p1->m, p3->m); + gbox->zmax = FP_MAX(p1->z, p3->z); + gbox->mmax = FP_MAX(p1->m, p3->m); + return rv; +} + +static void +ptarray_calculate_gbox_cartesian_2d(const POINTARRAY *pa, GBOX *gbox) +{ + const POINT2D *p = getPoint2d_cp(pa, 0); + + gbox->xmax = gbox->xmin = p->x; + gbox->ymax = gbox->ymin = p->y; + + for (uint32_t i = 1; i < pa->npoints; i++) + { + p = getPoint2d_cp(pa, i); + gbox->xmin = FP_MIN(gbox->xmin, p->x); + gbox->xmax = FP_MAX(gbox->xmax, p->x); + gbox->ymin = FP_MIN(gbox->ymin, p->y); + gbox->ymax = FP_MAX(gbox->ymax, p->y); + } +} + +/* Works with X/Y/Z. Needs to be adjusted after if X/Y/M was required */ +static void +ptarray_calculate_gbox_cartesian_3d(const POINTARRAY *pa, GBOX *gbox) +{ + const POINT3D *p = getPoint3d_cp(pa, 0); + + gbox->xmax = gbox->xmin = p->x; + gbox->ymax = gbox->ymin = p->y; + gbox->zmax = gbox->zmin = p->z; + + for (uint32_t i = 1; i < pa->npoints; i++) + { + p = getPoint3d_cp(pa, i); + gbox->xmin = FP_MIN(gbox->xmin, p->x); + gbox->xmax = FP_MAX(gbox->xmax, p->x); + gbox->ymin = FP_MIN(gbox->ymin, p->y); + gbox->ymax = FP_MAX(gbox->ymax, p->y); + gbox->zmin = FP_MIN(gbox->zmin, p->z); + gbox->zmax = FP_MAX(gbox->zmax, p->z); + } +} + +static void +ptarray_calculate_gbox_cartesian_4d(const POINTARRAY *pa, GBOX *gbox) +{ + const POINT4D *p = getPoint4d_cp(pa, 0); + + gbox->xmax = gbox->xmin = p->x; + gbox->ymax = gbox->ymin = p->y; + gbox->zmax = gbox->zmin = p->z; + gbox->mmax = gbox->mmin = p->m; + + for (uint32_t i = 1; i < pa->npoints; i++) + { + p = getPoint4d_cp(pa, i); + gbox->xmin = FP_MIN(gbox->xmin, p->x); + gbox->xmax = FP_MAX(gbox->xmax, p->x); + gbox->ymin = FP_MIN(gbox->ymin, p->y); + gbox->ymax = FP_MAX(gbox->ymax, p->y); + gbox->zmin = FP_MIN(gbox->zmin, p->z); + gbox->zmax = FP_MAX(gbox->zmax, p->z); + gbox->mmin = FP_MIN(gbox->mmin, p->m); + gbox->mmax = FP_MAX(gbox->mmax, p->m); + } +} + +int +ptarray_calculate_gbox_cartesian(const POINTARRAY *pa, GBOX *gbox) +{ + if (!pa || pa->npoints == 0) + return LW_FAILURE; + if (!gbox) + return LW_FAILURE; + + int has_z = FLAGS_GET_Z(pa->flags); + int has_m = FLAGS_GET_M(pa->flags); + gbox->flags = lwflags(has_z, has_m, 0); + LWDEBUGF(4, "ptarray_calculate_gbox Z: %d M: %d", has_z, has_m); + int coordinates = 2 + has_z + has_m; + + switch (coordinates) + { + case 2: + { + ptarray_calculate_gbox_cartesian_2d(pa, gbox); + break; + } + case 3: + { + if (has_z) + { + ptarray_calculate_gbox_cartesian_3d(pa, gbox); + } + else + { + double zmin = gbox->zmin; + double zmax = gbox->zmax; + ptarray_calculate_gbox_cartesian_3d(pa, gbox); + gbox->mmin = gbox->zmin; + gbox->mmax = gbox->zmax; + gbox->zmin = zmin; + gbox->zmax = zmax; + } + break; + } + default: + { + ptarray_calculate_gbox_cartesian_4d(pa, gbox); + break; + } + } + return LW_SUCCESS; +} + +static int lwcircstring_calculate_gbox_cartesian(LWCIRCSTRING *curve, GBOX *gbox) +{ + GBOX tmp = {0}; + POINT4D p1, p2, p3; + uint32_t i; + + if (!curve) return LW_FAILURE; + if (curve->points->npoints < 3) return LW_FAILURE; + + tmp.flags = + lwflags(FLAGS_GET_Z(curve->flags), FLAGS_GET_M(curve->flags), 0); + + /* Initialize */ + gbox->xmin = gbox->ymin = gbox->zmin = gbox->mmin = FLT_MAX; + gbox->xmax = gbox->ymax = gbox->zmax = gbox->mmax = -1*FLT_MAX; + + for ( i = 2; i < curve->points->npoints; i += 2 ) + { + getPoint4d_p(curve->points, i-2, &p1); + getPoint4d_p(curve->points, i-1, &p2); + getPoint4d_p(curve->points, i, &p3); + + if (lw_arc_calculate_gbox_cartesian(&p1, &p2, &p3, &tmp) == LW_FAILURE) + continue; + + gbox_merge(&tmp, gbox); + } + + return LW_SUCCESS; +} + +static int lwpoint_calculate_gbox_cartesian(LWPOINT *point, GBOX *gbox) +{ + if ( ! point ) return LW_FAILURE; + return ptarray_calculate_gbox_cartesian( point->point, gbox ); +} + +static int lwline_calculate_gbox_cartesian(LWLINE *line, GBOX *gbox) +{ + if ( ! line ) return LW_FAILURE; + return ptarray_calculate_gbox_cartesian( line->points, gbox ); +} + +static int lwtriangle_calculate_gbox_cartesian(LWTRIANGLE *triangle, GBOX *gbox) +{ + if ( ! triangle ) return LW_FAILURE; + return ptarray_calculate_gbox_cartesian( triangle->points, gbox ); +} + +static int lwpoly_calculate_gbox_cartesian(LWPOLY *poly, GBOX *gbox) +{ + if ( ! poly ) return LW_FAILURE; + if ( poly->nrings == 0 ) return LW_FAILURE; + /* Just need to check outer ring */ + return ptarray_calculate_gbox_cartesian( poly->rings[0], gbox ); +} + +static int lwcollection_calculate_gbox_cartesian(LWCOLLECTION *coll, GBOX *gbox) +{ + GBOX subbox = {0}; + uint32_t i; + int result = LW_FAILURE; + int first = LW_TRUE; + assert(coll); + if ( (coll->ngeoms == 0) || !gbox) + return LW_FAILURE; + + subbox.flags = coll->flags; + + for ( i = 0; i < coll->ngeoms; i++ ) + { + if ( lwgeom_calculate_gbox_cartesian((LWGEOM*)(coll->geoms[i]), &subbox) == LW_SUCCESS ) + { + /* Keep a copy of the sub-bounding box for later + if ( coll->geoms[i]->bbox ) + lwfree(coll->geoms[i]->bbox); + coll->geoms[i]->bbox = gbox_copy(&subbox); */ + if ( first ) + { + gbox_duplicate(&subbox, gbox); + first = LW_FALSE; + } + else + { + gbox_merge(&subbox, gbox); + } + result = LW_SUCCESS; + } + } + return result; +} + +int lwgeom_calculate_gbox_cartesian(const LWGEOM *lwgeom, GBOX *gbox) +{ + if ( ! lwgeom ) return LW_FAILURE; + LWDEBUGF(4, "lwgeom_calculate_gbox got type (%d) - %s", lwgeom->type, lwtype_name(lwgeom->type)); + + switch (lwgeom->type) + { + case POINTTYPE: + return lwpoint_calculate_gbox_cartesian((LWPOINT *)lwgeom, gbox); + case LINETYPE: + return lwline_calculate_gbox_cartesian((LWLINE *)lwgeom, gbox); + case CIRCSTRINGTYPE: + return lwcircstring_calculate_gbox_cartesian((LWCIRCSTRING *)lwgeom, gbox); + case POLYGONTYPE: + return lwpoly_calculate_gbox_cartesian((LWPOLY *)lwgeom, gbox); + case TRIANGLETYPE: + return lwtriangle_calculate_gbox_cartesian((LWTRIANGLE *)lwgeom, gbox); + case COMPOUNDTYPE: + case CURVEPOLYTYPE: + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTICURVETYPE: + case MULTIPOLYGONTYPE: + case MULTISURFACETYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + return lwcollection_calculate_gbox_cartesian((LWCOLLECTION *)lwgeom, gbox); + } + /* Never get here, please. */ + lwerror("unsupported type (%d) - %s", lwgeom->type, lwtype_name(lwgeom->type)); + return LW_FAILURE; +} + +void gbox_float_round(GBOX *gbox) +{ + gbox->xmin = next_float_down(gbox->xmin); + gbox->xmax = next_float_up(gbox->xmax); + + gbox->ymin = next_float_down(gbox->ymin); + gbox->ymax = next_float_up(gbox->ymax); + + if ( FLAGS_GET_M(gbox->flags) ) + { + gbox->mmin = next_float_down(gbox->mmin); + gbox->mmax = next_float_up(gbox->mmax); + } + + if ( FLAGS_GET_Z(gbox->flags) ) + { + gbox->zmin = next_float_down(gbox->zmin); + gbox->zmax = next_float_up(gbox->zmax); + } +} + +uint64_t +gbox_get_sortable_hash(const GBOX *g, const int32_t srid) +{ + union floatuint { + uint32_t u; + float f; + }; + + union floatuint x, y; + + /* + * Since in theory the bitwise representation of an IEEE + * float is sortable (exponents come before mantissa, etc) + * we just copy the bits directly into an int and then + * interleave those ints. + */ + if (FLAGS_GET_GEODETIC(g->flags)) + { + GEOGRAPHIC_POINT gpt; + POINT3D p; + p.x = (g->xmax + g->xmin) / 2.0; + p.y = (g->ymax + g->ymin) / 2.0; + p.z = (g->zmax + g->zmin) / 2.0; + normalize(&p); + cart2geog(&p, &gpt); + /* We know range for geography, so build the curve taking it into account */ + x.f = 1.5 + gpt.lon / 512.0; + y.f = 1.5 + gpt.lat / 256.0; + } + else + { + x.f = (g->xmax + g->xmin) / 2; + y.f = (g->ymax + g->ymin) / 2; + /* + * Tweak for popular SRID values: push floating point values into 1..2 range, + * a region where exponent is constant and thus Hilbert curve + * doesn't have compression artifact when X or Y value is close to 0. + * If someone has out of bounds value it will still expose the arifact but not crash. + * TODO: reconsider when we will have machinery to properly get bounds by SRID. + */ + if (srid == 3857 || srid == 3395) + { + x.f = 1.5 + x.f / 67108864.0; + y.f = 1.5 + y.f / 67108864.0; + } + else if (srid == 4326) + { + x.f = 1.5 + x.f / 512.0; + y.f = 1.5 + y.f / 256.0; + } + } + + return uint32_hilbert(y.u, x.u); +} diff --git a/mgist-postgis/liblwgeom/gserialized.c b/mgist-postgis/liblwgeom/gserialized.c new file mode 100644 index 0000000..2badbfd --- /dev/null +++ b/mgist-postgis/liblwgeom/gserialized.c @@ -0,0 +1,427 @@ +#include "liblwgeom_internal.h" +#include "gserialized1.h" +#include "gserialized2.h" + +/* First four bits don't change between v0 and v1 */ +#define GFLAG_Z 0x01 +#define GFLAG_M 0x02 +#define GFLAG_BBOX 0x04 +#define GFLAG_GEODETIC 0x08 +/* v1 and v2 MUST share the same version bits */ +#define GFLAG_VER_0 0x40 +#define GFLAGS_GET_VERSION(gflags) (((gflags) & GFLAG_VER_0)>>6) + +/** +* Read the flags from a #GSERIALIZED and return a standard lwflag +* integer +*/ +lwflags_t gserialized_get_lwflags(const GSERIALIZED *g) +{ + if (GFLAGS_GET_VERSION(g->gflags)) + return gserialized2_get_lwflags(g); + else + return gserialized1_get_lwflags(g); +}; + +/** +* Copy a new bounding box into an existing gserialized. +* If necessary a new #GSERIALIZED will be allocated. Test +* that input != output before freeing input. +*/ +GSERIALIZED *gserialized_set_gbox(GSERIALIZED *g, GBOX *gbox) +{ + if (GFLAGS_GET_VERSION(g->gflags)) + return gserialized2_set_gbox(g, gbox); + else + return gserialized1_set_gbox(g, gbox); +} + +/** +* Return the serialization version +*/ +uint32_t gserialized_get_version(const GSERIALIZED *g) +{ + return GFLAGS_GET_VERSION(g->gflags); +} + + +/** +* Remove the bounding box from a #GSERIALIZED. Returns a freshly +* allocated #GSERIALIZED every time. +*/ +GSERIALIZED* gserialized_drop_gbox(GSERIALIZED *g) +{ + if (GFLAGS_GET_VERSION(g->gflags)) + return gserialized2_drop_gbox(g); + else + return gserialized1_drop_gbox(g); +} + +/** +* Read the box from the #GSERIALIZED or calculate it if necessary. +* Return #LWFAILURE if box cannot be calculated (NULL or EMPTY +* input). +*/ +int gserialized_get_gbox_p(const GSERIALIZED *g, GBOX *gbox) +{ + if (GFLAGS_GET_VERSION(g->gflags)) + return gserialized2_get_gbox_p(g, gbox); + else + return gserialized1_get_gbox_p(g, gbox); +} + +/** +* Read the box from the #GSERIALIZED or return #LWFAILURE if +* box is unavailable. +*/ +int gserialized_fast_gbox_p(const GSERIALIZED *g, GBOX *gbox) +{ + if (GFLAGS_GET_VERSION(g->gflags)) + return gserialized2_fast_gbox_p(g, gbox); + else + return gserialized1_fast_gbox_p(g, gbox); +} + +/** +* Extract the geometry type from the serialized form (it hides in +* the anonymous data area, so this is a handy function). +*/ +uint32_t gserialized_get_type(const GSERIALIZED *g) +{ + if (GFLAGS_GET_VERSION(g->gflags)) + return gserialized2_get_type(g); + else + return gserialized1_get_type(g); +} + +/** +* Returns the size in bytes to read from toast to get the basic +* information from a geometry: GSERIALIZED struct, bbox and type +*/ +uint32_t gserialized_max_header_size(void) +{ + size_t sz1 = gserialized1_max_header_size(); + size_t sz2 = gserialized2_max_header_size(); + return sz1 > sz2 ? sz1 : sz2; +} + +/** +* Returns a hash code for the srid/type/geometry information +* in the GSERIALIZED. Ignores metadata like flags and optional +* boxes, etc. +*/ +int32_t +gserialized_hash(const GSERIALIZED *g) +{ + if (GFLAGS_GET_VERSION(g->gflags)) + return gserialized2_hash(g); + else + return gserialized1_hash(g); +} + +/** +* Extract the SRID from the serialized form (it is packed into +* three bytes so this is a handy function). +*/ +int32_t gserialized_get_srid(const GSERIALIZED *g) +{ + if (GFLAGS_GET_VERSION(g->gflags)) + return gserialized2_get_srid(g); + else + return gserialized1_get_srid(g); +} + +/** +* Write the SRID into the serialized form (it is packed into +* three bytes so this is a handy function). +*/ +void gserialized_set_srid(GSERIALIZED *g, int32_t srid) +{ + if (GFLAGS_GET_VERSION(g->gflags)) + return gserialized2_set_srid(g, srid); + else + return gserialized1_set_srid(g, srid); +} + +/** +* Check if a #GSERIALIZED is empty without deserializing first. +* Only checks if the number of elements of the parent geometry +* is zero, will not catch collections of empty, eg: +* GEOMETRYCOLLECTION(POINT EMPTY) +*/ +int gserialized_is_empty(const GSERIALIZED *g) +{ + if (GFLAGS_GET_VERSION(g->gflags)) + return gserialized2_is_empty(g); + else + return gserialized1_is_empty(g); +} + +/** +* Check if a #GSERIALIZED has a bounding box without deserializing first. +*/ +int gserialized_has_bbox(const GSERIALIZED *g) +{ + if (GFLAGS_GET_VERSION(g->gflags)) + return gserialized2_has_bbox(g); + else + return gserialized1_has_bbox(g); +} + +/** +* Check if a #GSERIALIZED has a Z ordinate. +*/ +int gserialized_has_z(const GSERIALIZED *g) +{ + if (GFLAGS_GET_VERSION(g->gflags)) + return gserialized2_has_z(g); + else + return gserialized1_has_z(g); +} + +/** +* Check if a #GSERIALIZED has an M ordinate. +*/ +int gserialized_has_m(const GSERIALIZED *g) +{ + if (GFLAGS_GET_VERSION(g->gflags)) + return gserialized2_has_m(g); + else + return gserialized1_has_m(g); +} + +/** +* Check if a #GSERIALIZED is a geography. +*/ +int gserialized_is_geodetic(const GSERIALIZED *g) +{ + if (GFLAGS_GET_VERSION(g->gflags)) + return gserialized2_is_geodetic(g); + else + return gserialized1_is_geodetic(g); +} + +/** +* Return the number of dimensions (2, 3, 4) in a geometry +*/ +int gserialized_ndims(const GSERIALIZED *g) +{ + if (GFLAGS_GET_VERSION(g->gflags)) + return gserialized2_ndims(g); + else + return gserialized1_ndims(g); +} + +/** +* Allocate a new #GSERIALIZED from an #LWGEOM. For all non-point types, a bounding +* box will be calculated and embedded in the serialization. The geodetic flag is used +* to control the box calculation (cartesian or geocentric). If set, the size pointer +* will contain the size of the final output, which is useful for setting the PgSQL +* VARSIZE information. +*/ +GSERIALIZED* gserialized_from_lwgeom(LWGEOM *geom, size_t *size) +{ + return gserialized2_from_lwgeom(geom, size); +} + +/** +* Return the memory size a GSERIALIZED will occupy for a given LWGEOM. +*/ +size_t gserialized_from_lwgeom_size(const LWGEOM *geom) +{ + return gserialized2_from_lwgeom_size(geom); +} + +/** +* Allocate a new #LWGEOM from a #GSERIALIZED. The resulting #LWGEOM will have coordinates +* that are double aligned and suitable for direct reading using getPoint2d_cp +*/ +LWGEOM* lwgeom_from_gserialized(const GSERIALIZED *g) +{ + if (GFLAGS_GET_VERSION(g->gflags)) + return lwgeom_from_gserialized2(g); + else + return lwgeom_from_gserialized1(g); +} + + +const float * gserialized_get_float_box_p(const GSERIALIZED *g, size_t *ndims) +{ + if (GFLAGS_GET_VERSION(g->gflags)) + return gserialized2_get_float_box_p(g, ndims); + else + return gserialized1_get_float_box_p(g, ndims); +} + +int +gserialized_peek_first_point(const GSERIALIZED *g, POINT4D *out_point) +{ + if (GFLAGS_GET_VERSION(g->gflags)) + return gserialized2_peek_first_point(g, out_point); + else + return gserialized1_peek_first_point(g, out_point); +} + +/** +* Return -1 if g1 is "less than" g2, 1 if g1 is "greater than" +* g2 and 0 if g1 and g2 are the "same". Equality is evaluated +* with a memcmp and size check. So it is possible that two +* identical objects where one lacks a bounding box could be +* evaluated as non-equal initially. Greater and less than +* are evaluated by calculating a sortable key from the center +* point of the object bounds. +* Because this function might have to handle GSERIALIZED +* objects of either version, we implement it up here at the +* switching layer rather than down lower. +*/ +#define G2FLAG_EXTENDED 0x10 +inline static size_t gserialized_header_size(const GSERIALIZED *g) +{ + size_t sz = 8; /* varsize (4) + srid(3) + flags (1) */ + + if ((GFLAGS_GET_VERSION(g->gflags)) && + (G2FLAG_EXTENDED & g->gflags)) + sz += 8; + + if (GFLAG_BBOX & g->gflags) + { + if (GFLAG_GEODETIC & g->gflags) + { + sz += 6 * sizeof(float); + } + else + { + sz += 4 * sizeof(float) + + ((GFLAG_Z & g->gflags) ? 2*sizeof(float) : 0) + + ((GFLAG_M & g->gflags) ? 2*sizeof(float) : 0); + } + } + + return sz; +} + +inline static int gserialized_cmp_srid(const GSERIALIZED *g1, const GSERIALIZED *g2) +{ + return ( + g1->srid[0] == g2->srid[0] && + g1->srid[1] == g2->srid[1] && + g1->srid[2] == g2->srid[2] + ) ? 0 : 1; +} + +/* ORDER BY hash(g), g::bytea, ST_SRID(g), hasz(g), hasm(g) */ +int gserialized_cmp(const GSERIALIZED *g1, const GSERIALIZED *g2) +{ + GBOX box1 = {0}, box2 = {0}; + uint64_t hash1, hash2; + size_t sz1 = LWSIZE_GET(g1->size); + size_t sz2 = LWSIZE_GET(g2->size); + size_t hsz1 = gserialized_header_size(g1); + size_t hsz2 = gserialized_header_size(g2); + uint8_t *b1 = (uint8_t*)g1 + hsz1; + uint8_t *b2 = (uint8_t*)g2 + hsz2; + size_t bsz1 = sz1 - hsz1; + size_t bsz2 = sz2 - hsz2; + size_t bsz_min = bsz1 < bsz2 ? bsz1 : bsz2; + + /* Equality fast path */ + /* Return equality for perfect equality only */ + int cmp_srid = gserialized_cmp_srid(g1, g2); + int cmp = memcmp(b1, b2, bsz_min); + int g1hasz = gserialized_has_z(g1); + int g1hasm = gserialized_has_m(g1); + int g2hasz = gserialized_has_z(g2); + int g2hasm = gserialized_has_m(g2); + + if (bsz1 == bsz2 && cmp_srid == 0 && cmp == 0 && g1hasz == g2hasz && g1hasm == g2hasm) + return 0; + else + { + int g1_is_empty = (gserialized_get_gbox_p(g1, &box1) == LW_FAILURE); + int g2_is_empty = (gserialized_get_gbox_p(g2, &box2) == LW_FAILURE); + int32_t srid1 = gserialized_get_srid(g1); + int32_t srid2 = gserialized_get_srid(g2); + + /* Empty < Non-empty */ + if (g1_is_empty && !g2_is_empty) + return -1; + + /* Non-empty > Empty */ + if (!g1_is_empty && g2_is_empty) + return 1; + + if (!g1_is_empty && !g2_is_empty) + { + /* Using the boxes, calculate sortable hash key. */ + hash1 = gbox_get_sortable_hash(&box1, srid1); + hash2 = gbox_get_sortable_hash(&box2, srid2); + + if (hash1 > hash2) + return 1; + if (hash1 < hash2) + return -1; + } + + /* Prefix comes before longer one. */ + if (bsz1 != bsz2 && cmp == 0) + { + if (bsz1 < bsz2) + return -1; + return 1; + } + + /* If SRID is not equal, sort on it */ + if (cmp_srid != 0) + return (srid1 > srid2) ? 1 : -1; + + /* ZM flag sort*/ + if (g1hasz != g2hasz) + return (g1hasz > g2hasz) ? 1 : -1; + + if (g1hasm != g2hasm) + return (g1hasm > g2hasm) ? 1 : -1; + + assert(cmp != 0); + return cmp > 0 ? 1 : -1; + } +} + +uint64_t +gserialized_get_sortable_hash(const GSERIALIZED *g) +{ + GBOX box; + int is_empty = (gserialized_get_gbox_p(g, &box) == LW_FAILURE); + + if (is_empty) + return 0; + else + return gbox_get_sortable_hash(&box, gserialized_get_srid(g)); +} + +void gserialized_error_if_srid_mismatch(const GSERIALIZED *g1, const GSERIALIZED *g2, const char *funcname); +void +gserialized_error_if_srid_mismatch(const GSERIALIZED *g1, const GSERIALIZED *g2, const char *funcname) +{ + int32_t srid1 = gserialized_get_srid(g1); + int32_t srid2 = gserialized_get_srid(g2); + if (srid1 != srid2) + lwerror("%s: Operation on mixed SRID geometries (%s, %d) != (%s, %d)", + funcname, + lwtype_name(gserialized1_get_type(g1)), + srid1, + lwtype_name(gserialized_get_type(g2)), + srid2); +} + +void gserialized_error_if_srid_mismatch_reference(const GSERIALIZED *g1, const int32_t srid2, const char *funcname); +void +gserialized_error_if_srid_mismatch_reference(const GSERIALIZED *g1, const int32_t srid2, const char *funcname) +{ + int32_t srid1 = gserialized_get_srid(g1); + if (srid1 != srid2) + lwerror("%s: Operation on mixed SRID geometries %s %d != %d", + funcname, + lwtype_name(gserialized1_get_type(g1)), + srid1, + srid2); +} diff --git a/mgist-postgis/liblwgeom/gserialized.h b/mgist-postgis/liblwgeom/gserialized.h new file mode 100644 index 0000000..6ca6e4f --- /dev/null +++ b/mgist-postgis/liblwgeom/gserialized.h @@ -0,0 +1,172 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2019 Paul Ramsey + * + **********************************************************************/ + +/* +* GSERIALIZED PUBLIC API +*/ + +/** +* Read the flags from a #GSERIALIZED and return a standard lwflag +* integer +*/ +lwflags_t gserialized_get_lwflags(const GSERIALIZED *g); + +/** +* Copy a new bounding box into an existing gserialized. +* If necessary a new #GSERIALIZED will be allocated. Test +* that input != output before freeing input. +*/ +GSERIALIZED *gserialized_set_gbox(GSERIALIZED *g, GBOX *gbox); + +/** +* Remove the bounding box from a #GSERIALIZED. Returns a freshly +* allocated #GSERIALIZED every time. +*/ +GSERIALIZED *gserialized_drop_gbox(GSERIALIZED *g); + +/** +* Read the box from the #GSERIALIZED or calculate it if necessary. +* Return #LWFAILURE if box cannot be calculated (NULL or EMPTY +* input). +*/ +int gserialized_get_gbox_p(const GSERIALIZED *g, GBOX *gbox); + +/** +* Read the box from the #GSERIALIZED or return #LWFAILURE if +* box is unavailable. +*/ +int gserialized_fast_gbox_p(const GSERIALIZED *g, GBOX *gbox); + +/** +* Extract the geometry type from the serialized form (it hides in +* the anonymous data area, so this is a handy function). +*/ +extern uint32_t gserialized_get_type(const GSERIALIZED *g); + +/** +* Returns the size in bytes to read from toast to get the basic +* information from a geometry: GSERIALIZED struct, bbox and type +*/ +extern uint32_t gserialized_max_header_size(void); + +/** +* Returns a hash code for the srid/type/geometry information +* in the GSERIALIZED. Ignores metadata like flags and optional +* boxes, etc. +*/ +extern int32_t gserialized_hash(const GSERIALIZED *g); + +/** +* Extract the SRID from the serialized form (it is packed into +* three bytes so this is a handy function). +*/ +extern int32_t gserialized_get_srid(const GSERIALIZED *g); + +/** +* Write the SRID into the serialized form (it is packed into +* three bytes so this is a handy function). +*/ +extern void gserialized_set_srid(GSERIALIZED *g, int32_t srid); + +/** +* Check if a #GSERIALIZED is empty without deserializing first. +* Only checks if the number of elements of the parent geometry +* is zero, will not catch collections of empty, eg: +* GEOMETRYCOLLECTION(POINT EMPTY) +*/ +extern int gserialized_is_empty(const GSERIALIZED *g); + +/** +* Check if a #GSERIALIZED has a bounding box without deserializing first. +*/ +extern int gserialized_has_bbox(const GSERIALIZED *gser); + +/** +* Check if a #GSERIALIZED has a Z ordinate. +*/ +extern int gserialized_has_z(const GSERIALIZED *gser); + +/** +* Check if a #GSERIALIZED has an M ordinate. +*/ +extern int gserialized_has_m(const GSERIALIZED *gser); + +/** +* Check if a #GSERIALIZED is a geography. +*/ +extern int gserialized_is_geodetic(const GSERIALIZED *gser); + +/** +* Return the number of dimensions (2, 3, 4) in a geometry +*/ +extern int gserialized_ndims(const GSERIALIZED *gser); + +/** +* Return -1 if g1 is "less than" g2, 1 if g1 is "greater than" +* g2 and 0 if g1 and g2 are the "same". Equality is evaluated +* with a memcmp and size check. So it is possible that two +* identical objects where one lacks a bounding box could be +* evaluated as non-equal initially. Greater and less than +* are evaluated by calculating a sortable key from the center +* point of the object bounds. +*/ +extern int gserialized_cmp(const GSERIALIZED *g1, const GSERIALIZED *g2); + +/** +* Allocate a new #GSERIALIZED from an #LWGEOM. For all non-point types, a bounding +* box will be calculated and embedded in the serialization. The geodetic flag is used +* to control the box calculation (cartesian or geocentric). If set, the size pointer +* will contain the size of the final output, which is useful for setting the PgSQL +* VARSIZE information. +*/ +GSERIALIZED *gserialized_from_lwgeom(LWGEOM *geom, size_t *size); + +/** +* Return the memory size a GSERIALIZED will occupy for a given LWGEOM. +*/ +size_t gserialized_from_lwgeom_size(const LWGEOM *geom); + +/** +* Allocate a new #LWGEOM from a #GSERIALIZED. The resulting #LWGEOM will have coordinates +* that are double aligned and suitable for direct reading using getPoint2d_cp +*/ +LWGEOM *lwgeom_from_gserialized(const GSERIALIZED *g); + +/** +* Pull a #GBOX from the header of a #GSERIALIZED, if one is available. If +* it is not, calculate it from the geometry. If that doesn't work (null +* or empty) return LW_FAILURE. +*/ +int gserialized_get_gbox_p(const GSERIALIZED *g, GBOX *box); + +/** +* Pull a #GBOX from the header of a #GSERIALIZED, if one is available. If +* it is not, return LW_FAILURE. +*/ +int gserialized_fast_gbox_p(const GSERIALIZED *g, GBOX *box); + +/** + * Pull the first point values of a #GSERIALIZED. Only works for POINTTYPE + */ +int gserialized_peek_first_point(const GSERIALIZED *g, POINT4D *out_point); diff --git a/mgist-postgis/liblwgeom/gserialized.txt b/mgist-postgis/liblwgeom/gserialized.txt new file mode 100644 index 0000000..d774226 --- /dev/null +++ b/mgist-postgis/liblwgeom/gserialized.txt @@ -0,0 +1,183 @@ + +GSERIALIZED FORM +================= + +The serialized form is a byte array, that holds all the information +stored in an LWGEOM, but in a flat structure, rather than an +in-memory tree. As with all such formats, trade-offs much be +made between size, data alignment, and convenience of access. + +* It is understood that GSERIALIZED is to be used primarily (only) + by PostGIS. Other users of the geometry library (for example, the + loaders and dumpers) will serialize to WKB or EWKB or various text + representations. Therefore, GSERIALIZED includes the uint32 "size" + field at the top used by PostgreSQL for varlena types. +* GSERIALIZED is built to be read and written recursively, so that + nested collections of collections (of ...) are possible without + restrictions on depth. +* GSERIALIZED preserves double alignment of ordinate arrays. This will + allow coordinate access without memcpy. +* GSERIALIZED includes a mandatory SRID, in recognition of the fact + that most production use of PostGIS does actually use an SRID. +* GSERIALIZED places the dimensionality information, the SRID + information and the bounding boxes at the front of the structure, + and all sub-components inherit from those parent values. +* GSERIALIZED retains the idea of optional bounding boxes, so that small + features do not carry the extra storage overhead of a largely redundant + bounding box. + +STRUCTURE +--------- + +Most of the internals of GSERIALIZED are anonymous. The start of the +"data" member can be the type number, or the bounding box, or an extended +flags area. Or some combination of the above. + +typedef struct +{ + uint32 size; /* Use LWSIZE_SET() and LWSIZE_GET() macros to manipulate. */ + uchar srid[3]; /* 21 bits of SRID (and 3 spare bits) */ + uchar flags; /* High priority information */ + uchar data[1]; /* See gserialized.txt */ +} GSERIALIZED; + +SIZE +---- + +The first four bytes in the "size" member are meant to be compatible with +the PostgreSQL VARLENA header. Use SIZE_SET() and SIZE_GET() macros to +manipulate the "size". Be careful not to mis-set the size, or operations +in PostgreSQL will end up scribbling on memory it should not. + +FLAGS (V1) +---------- + +In the v1 serialization we have used up all of our flags except the version +bits. + +* HasZ (0x01) +* HasM (0x02) +* HasBBox (0x04) +* IsGeodetic (0x08) +* ReadOnly (0x10) +* IsSolid (0x20) +* VersionBit1 (0x40) +* VersionBit2 (0x80) + +FLAGS (V2) +---------- + +The v2 serialization recovers the flag space used by less-important flags +and moves those flags to a new optional flag area. + +* HasZ (0x01) +* HasM (0x02) +* HasBBox (0x04) +* IsGeodetic (0x08) +* Reserved (0x10) +* HasExtendedFlags (0x20) +* VersionBit1 (0x40) +* VersionBit2 (0x80) + +Potential uses of the reserved flag: + +* IsLightPoint: signals that the geometry type is point, and that the + double coordinates will begin immediately in the "data" member without + the usual type number and padding bytes. Allows 2D points to be + represented in a minimum of 24 bytes, rather than the current 32. + +OPTIONAL ELEMENTS (V1) +---------------------- + +If the HasBBox flag is set, the serialization contains the optional BBox +section, which is 2*ndims*sizeof(float) in size, and consists of the +bounding box. Since the box consists of pairs of floats, the presence +or absence of the box does not affect the double alignment of the +geometry coordinates. + +* 2D: +* 3D: +* 3DM: +* 4D: + +OPTIONAL ELEMENTS (V2) +---------------------- + +The v2 serialization has a lot more flag space, and therefor a lot of +extra room to flag potential optional elements in the serialization. + +In order to keep the double coordinates in the geometry section +double aligned, optional elements should themselves be multiples of +8 bytes. + +* ExtendedFlags: If the HasExtendedFlags flag is set, then 8 bytes + of extra flag space (uint64_t) are inserted prior to the (optional) BBox +* BBox: See V1 above. + +EXTENDED FLAGS (V2) +------------------- + +The extended flags are not heavily used at present, but can be used +to expand the serialization with options in the future. Extended flags +use 8 bytes of space, to ensure that that the coordinates in the +geometry section (see below) remain double-aligned for direct +memory access. + +* IsSolid (0x01) + +Potential extra uses of extended flags are: + +* IsValidChecked/IsValid: a pair of flags used to cache validity state + in the serialization to make ST_IsValid() checks blindingly fast. +* HasGeometryHash: signals presence of optional hash value that provides + a small identity check that can be used in prepared geometry cache + management to determine of the cache is dirty without requiring + a full read and comparison of the geometry. + +GEOMETRY (V1 & V2) +------------------ + +After the header and optional elements comes the recursively +searchable geometry representations. Each type is double aligned, +so any combination is double aligned, and we start after a double +aligned standard header, so we are golden: + + + /* 0 if empty, 1 otherwise */ +[double] +[double] +[double] + + + /* 0 if empty, N otherwise */ +[double] +... +[double] + + + /* 0 if empty, N otherwise */ + + + /* pad if nrings % 2 > 0 */ +[double] +... +[double] + + + /* 0 if empty, N otherwise */ +[geom] +... +[geom] + + + /* 0 if empty, N otherwise */ +[double] +... +[double] + + + /* 0 if empty, N otherwise */ +[geom] +... +[geom] + diff --git a/mgist-postgis/liblwgeom/gserialized1.c b/mgist-postgis/liblwgeom/gserialized1.c new file mode 100644 index 0000000..1a87ccf --- /dev/null +++ b/mgist-postgis/liblwgeom/gserialized1.c @@ -0,0 +1,1607 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2009 Paul Ramsey + * Copyright 2017 Darafei Praliaskouski + * + **********************************************************************/ + + +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" +#include "lwgeodetic.h" +#include "gserialized1.h" + +#include + +/*********************************************************************** +* GSERIALIZED metadata utility functions. +*/ + +static int gserialized1_read_gbox_p(const GSERIALIZED *g, GBOX *gbox); + + +lwflags_t gserialized1_get_lwflags(const GSERIALIZED *g) +{ + lwflags_t lwflags = 0; + uint8_t gflags = g->gflags; + FLAGS_SET_Z(lwflags, G1FLAGS_GET_Z(gflags)); + FLAGS_SET_M(lwflags, G1FLAGS_GET_M(gflags)); + FLAGS_SET_BBOX(lwflags, G1FLAGS_GET_BBOX(gflags)); + FLAGS_SET_GEODETIC(lwflags, G1FLAGS_GET_GEODETIC(gflags)); + FLAGS_SET_SOLID(lwflags, G1FLAGS_GET_SOLID(gflags)); + return lwflags; +} + +uint8_t lwflags_get_g1flags(lwflags_t lwflags) +{ + uint8_t gflags = 0; + G1FLAGS_SET_Z(gflags, FLAGS_GET_Z(lwflags)); + G1FLAGS_SET_M(gflags, FLAGS_GET_M(lwflags)); + G1FLAGS_SET_BBOX(gflags, FLAGS_GET_BBOX(lwflags)); + G1FLAGS_SET_GEODETIC(gflags, FLAGS_GET_GEODETIC(lwflags)); + G1FLAGS_SET_SOLID(gflags, FLAGS_GET_SOLID(lwflags)); + return gflags; +} + + +static size_t gserialized1_box_size(const GSERIALIZED *g) +{ + if (G1FLAGS_GET_GEODETIC(g->gflags)) + return 6 * sizeof(float); + else + return 2 * G1FLAGS_NDIMS(g->gflags) * sizeof(float); +} + +/* handle missaligned uint32_t data */ +static inline uint32_t gserialized1_get_uint32_t(const uint8_t *loc) +{ + return *((uint32_t*)loc); +} + +uint8_t g1flags(int has_z, int has_m, int is_geodetic) +{ + uint8_t gflags = 0; + if (has_z) + G1FLAGS_SET_Z(gflags, 1); + if (has_m) + G1FLAGS_SET_M(gflags, 1); + if (is_geodetic) + G1FLAGS_SET_GEODETIC(gflags, 1); + return gflags; +} + +int gserialized1_has_bbox(const GSERIALIZED *gser) +{ + return G1FLAGS_GET_BBOX(gser->gflags); +} + +int gserialized1_has_z(const GSERIALIZED *gser) +{ + return G1FLAGS_GET_Z(gser->gflags); +} + +int gserialized1_has_m(const GSERIALIZED *gser) +{ + return G1FLAGS_GET_M(gser->gflags); +} + +int gserialized1_ndims(const GSERIALIZED *gser) +{ + return G1FLAGS_NDIMS(gser->gflags); +} + +int gserialized1_is_geodetic(const GSERIALIZED *gser) +{ + return G1FLAGS_GET_GEODETIC(gser->gflags); +} + +uint32_t gserialized1_max_header_size(void) +{ + /* GSERIALIZED size + max bbox according gbox_serialized_size (XYZM*2) + extended flags + type */ + return offsetof(GSERIALIZED, data) + 8 * sizeof(float) + sizeof(uint32_t); +} + +static uint32_t gserialized1_header_size(const GSERIALIZED *gser) +{ + uint32_t sz = 8; /* varsize (4) + srid(3) + flags (1) */ + + if (gserialized1_has_bbox(gser)) + sz += gserialized1_box_size(gser); + + return sz; +} + +uint32_t gserialized1_get_type(const GSERIALIZED *g) +{ + uint32_t *ptr; + ptr = (uint32_t*)(g->data); + if ( G1FLAGS_GET_BBOX(g->gflags) ) + { + ptr += (gserialized1_box_size(g) / sizeof(uint32_t)); + } + return *ptr; +} + +int32_t gserialized1_get_srid(const GSERIALIZED *s) +{ + int32_t srid = 0; + srid = srid | (s->srid[0] << 16); + srid = srid | (s->srid[1] << 8); + srid = srid | s->srid[2]; + /* Only the first 21 bits are set. Slide up and back to pull + the negative bits down, if we need them. */ + srid = (srid<<11)>>11; + + /* 0 is our internal unknown value. We'll map back and forth here for now */ + if ( srid == 0 ) + return SRID_UNKNOWN; + else + return srid; +} + +void gserialized1_set_srid(GSERIALIZED *s, int32_t srid) +{ + LWDEBUGF(3, "%s called with srid = %d", __func__, srid); + + srid = clamp_srid(srid); + + /* 0 is our internal unknown value. + * We'll map back and forth here for now */ + if ( srid == SRID_UNKNOWN ) + srid = 0; + + s->srid[0] = (srid & 0x001F0000) >> 16; + s->srid[1] = (srid & 0x0000FF00) >> 8; + s->srid[2] = (srid & 0x000000FF); +} + +static size_t gserialized1_is_empty_recurse(const uint8_t *p, int *isempty); +static size_t gserialized1_is_empty_recurse(const uint8_t *p, int *isempty) +{ + int i; + int32_t type, num; + + memcpy(&type, p, 4); + memcpy(&num, p+4, 4); + + if ( lwtype_is_collection(type) ) + { + size_t lz = 8; + for ( i = 0; i < num; i++ ) + { + lz += gserialized1_is_empty_recurse(p+lz, isempty); + if ( ! *isempty ) + return lz; + } + *isempty = LW_TRUE; + return lz; + } + else + { + *isempty = (num == 0 ? LW_TRUE : LW_FALSE); + return 8; + } +} + +int gserialized1_is_empty(const GSERIALIZED *g) +{ + uint8_t *p = (uint8_t*)g; + int isempty = 0; + assert(g); + + p += 8; /* Skip varhdr and srid/flags */ + if(gserialized1_has_bbox(g)) + p += gserialized1_box_size(g); /* Skip the box */ + + gserialized1_is_empty_recurse(p, &isempty); + return isempty; +} + + +/* Prototype for lookup3.c */ +/* key = the key to hash */ +/* length = length of the key */ +/* pc = IN: primary initval, OUT: primary hash */ +/* pb = IN: secondary initval, OUT: secondary hash */ +void hashlittle2(const void *key, size_t length, uint32_t *pc, uint32_t *pb); + +int32_t +gserialized1_hash(const GSERIALIZED *g1) +{ + int32_t hval; + int32_t pb = 0, pc = 0; + /* Point to just the type/coordinate part of buffer */ + size_t hsz1 = gserialized1_header_size(g1); + uint8_t *b1 = (uint8_t*)g1 + hsz1; + /* Calculate size of type/coordinate buffer */ + size_t sz1 = LWSIZE_GET(g1->size); + size_t bsz1 = sz1 - hsz1; + /* Calculate size of srid/type/coordinate buffer */ + int32_t srid = gserialized1_get_srid(g1); + size_t bsz2 = bsz1 + sizeof(int); + uint8_t *b2 = lwalloc(bsz2); + /* Copy srid into front of combined buffer */ + memcpy(b2, &srid, sizeof(int)); + /* Copy type/coordinates into rest of combined buffer */ + memcpy(b2+sizeof(int), b1, bsz1); + /* Hash combined buffer */ + hashlittle2(b2, bsz2, (uint32_t *)&pb, (uint32_t *)&pc); + lwfree(b2); + hval = pb ^ pc; + return hval; +} + +int gserialized1_read_gbox_p(const GSERIALIZED *g, GBOX *gbox) +{ + + /* Null input! */ + if ( ! ( g && gbox ) ) return LW_FAILURE; + + /* Initialize the flags on the box */ + gbox->flags = gserialized1_get_lwflags(g); + + /* Has pre-calculated box */ + if ( G1FLAGS_GET_BBOX(g->gflags) ) + { + int i = 0; + float *fbox = (float*)(g->data); + gbox->xmin = fbox[i++]; + gbox->xmax = fbox[i++]; + gbox->ymin = fbox[i++]; + gbox->ymax = fbox[i++]; + + /* Geodetic? Read next dimension (geocentric Z) and return */ + if ( G1FLAGS_GET_GEODETIC(g->gflags) ) + { + gbox->zmin = fbox[i++]; + gbox->zmax = fbox[i++]; + return LW_SUCCESS; + } + /* Cartesian? Read extra dimensions (if there) and return */ + if ( G1FLAGS_GET_Z(g->gflags) ) + { + gbox->zmin = fbox[i++]; + gbox->zmax = fbox[i++]; + } + if ( G1FLAGS_GET_M(g->gflags) ) + { + gbox->mmin = fbox[i++]; + gbox->mmax = fbox[i++]; + } + return LW_SUCCESS; + } + return LW_FAILURE; +} + +/* +* Populate a bounding box *without* allocating an LWGEOM. Useful +* for some performance purposes. +*/ +int +gserialized1_peek_gbox_p(const GSERIALIZED *g, GBOX *gbox) +{ + uint32_t type = gserialized1_get_type(g); + + /* Peeking doesn't help if you already have a box or are geodetic */ + if ( G1FLAGS_GET_GEODETIC(g->gflags) || G1FLAGS_GET_BBOX(g->gflags) ) + { + return LW_FAILURE; + } + + /* Boxes of points are easy peasy */ + if ( type == POINTTYPE ) + { + int i = 1; /* Start past */ + double *dptr = (double*)(g->data); + + /* Read the empty flag */ + int32_t *iptr = (int32_t *)(g->data); + int isempty = (iptr[1] == 0); + + /* EMPTY point has no box */ + if ( isempty ) return LW_FAILURE; + + gbox->xmin = gbox->xmax = dptr[i++]; + gbox->ymin = gbox->ymax = dptr[i++]; + gbox->flags = gserialized1_get_lwflags(g); + if ( G1FLAGS_GET_Z(g->gflags) ) + { + gbox->zmin = gbox->zmax = dptr[i++]; + } + if ( G1FLAGS_GET_M(g->gflags) ) + { + gbox->mmin = gbox->mmax = dptr[i++]; + } + gbox_float_round(gbox); + return LW_SUCCESS; + } + /* We can calculate the box of a two-point cartesian line trivially */ + else if ( type == LINETYPE ) + { + int ndims = G1FLAGS_NDIMS(g->gflags); + int i = 0; /* Start at */ + double *dptr = (double*)(g->data); + int32_t *iptr = (int32_t *)(g->data); + int npoints = iptr[1]; /* Read the npoints */ + + /* This only works with 2-point lines */ + if ( npoints != 2 ) + return LW_FAILURE; + + /* Advance to X */ + /* Past */ + i++; + gbox->xmin = FP_MIN(dptr[i], dptr[i+ndims]); + gbox->xmax = FP_MAX(dptr[i], dptr[i+ndims]); + + /* Advance to Y */ + i++; + gbox->ymin = FP_MIN(dptr[i], dptr[i+ndims]); + gbox->ymax = FP_MAX(dptr[i], dptr[i+ndims]); + + gbox->flags = gserialized1_get_lwflags(g); + if ( G1FLAGS_GET_Z(g->gflags) ) + { + /* Advance to Z */ + i++; + gbox->zmin = FP_MIN(dptr[i], dptr[i+ndims]); + gbox->zmax = FP_MAX(dptr[i], dptr[i+ndims]); + } + if ( G1FLAGS_GET_M(g->gflags) ) + { + /* Advance to M */ + i++; + gbox->mmin = FP_MIN(dptr[i], dptr[i+ndims]); + gbox->mmax = FP_MAX(dptr[i], dptr[i+ndims]); + } + gbox_float_round(gbox); + return LW_SUCCESS; + } + /* We can also do single-entry multi-points */ + else if ( type == MULTIPOINTTYPE ) + { + int i = 0; /* Start at */ + double *dptr = (double*)(g->data); + int32_t *iptr = (int32_t *)(g->data); + int ngeoms = iptr[1]; /* Read the ngeoms */ + int npoints; + + /* This only works with single-entry multipoints */ + if ( ngeoms != 1 ) + return LW_FAILURE; + + /* Npoints is at */ + npoints = iptr[3]; + + /* The check below is necessary because we can have a MULTIPOINT + * that contains a single, empty POINT (ngeoms = 1, npoints = 0) */ + if ( npoints != 1 ) + return LW_FAILURE; + + /* Move forward two doubles (four ints) */ + /* Past */ + /* Past */ + i += 2; + + /* Read the doubles from the one point */ + gbox->xmin = gbox->xmax = dptr[i++]; + gbox->ymin = gbox->ymax = dptr[i++]; + gbox->flags = gserialized1_get_lwflags(g); + if ( G1FLAGS_GET_Z(g->gflags) ) + { + gbox->zmin = gbox->zmax = dptr[i++]; + } + if ( G1FLAGS_GET_M(g->gflags) ) + { + gbox->mmin = gbox->mmax = dptr[i++]; + } + gbox_float_round(gbox); + return LW_SUCCESS; + } + /* And we can do single-entry multi-lines with two vertices (!!!) */ + else if ( type == MULTILINETYPE ) + { + int ndims = G1FLAGS_NDIMS(g->gflags); + int i = 0; /* Start at */ + double *dptr = (double*)(g->data); + int32_t *iptr = (int32_t *)(g->data); + int ngeoms = iptr[1]; /* Read the ngeoms */ + int npoints; + + /* This only works with 1-line multilines */ + if ( ngeoms != 1 ) + return LW_FAILURE; + + /* Npoints is at */ + npoints = iptr[3]; + + if ( npoints != 2 ) + return LW_FAILURE; + + /* Advance to X */ + /* Move forward two doubles (four ints) */ + /* Past */ + /* Past */ + i += 2; + gbox->xmin = FP_MIN(dptr[i], dptr[i+ndims]); + gbox->xmax = FP_MAX(dptr[i], dptr[i+ndims]); + + /* Advance to Y */ + i++; + gbox->ymin = FP_MIN(dptr[i], dptr[i+ndims]); + gbox->ymax = FP_MAX(dptr[i], dptr[i+ndims]); + + gbox->flags = gserialized1_get_lwflags(g); + if ( G1FLAGS_GET_Z(g->gflags) ) + { + /* Advance to Z */ + i++; + gbox->zmin = FP_MIN(dptr[i], dptr[i+ndims]); + gbox->zmax = FP_MAX(dptr[i], dptr[i+ndims]); + } + if ( G1FLAGS_GET_M(g->gflags) ) + { + /* Advance to M */ + i++; + gbox->mmin = FP_MIN(dptr[i], dptr[i+ndims]); + gbox->mmax = FP_MAX(dptr[i], dptr[i+ndims]); + } + gbox_float_round(gbox); + return LW_SUCCESS; + } + + return LW_FAILURE; +} + +static inline void +gserialized1_copy_point(double *dptr, lwflags_t flags, POINT4D *out_point) +{ + uint8_t dim = 0; + out_point->x = dptr[dim++]; + out_point->y = dptr[dim++]; + + if (G1FLAGS_GET_Z(flags)) + { + out_point->z = dptr[dim++]; + } + if (G1FLAGS_GET_M(flags)) + { + out_point->m = dptr[dim]; + } +} + +int +gserialized1_peek_first_point(const GSERIALIZED *g, POINT4D *out_point) +{ + uint8_t *geometry_start = ((uint8_t *)g->data); + if (gserialized1_has_bbox(g)) + { + geometry_start += gserialized1_box_size(g); + } + + uint32_t isEmpty = (((uint32_t *)geometry_start)[1]) == 0; + if (isEmpty) + { + return LW_FAILURE; + } + + uint32_t type = (((uint32_t *)geometry_start)[0]); + /* Setup double_array_start depending on the geometry type */ + double *double_array_start = NULL; + switch (type) + { + case (POINTTYPE): + /* For points we only need to jump over the type and npoints 32b ints */ + double_array_start = (double *)(geometry_start + 2 * sizeof(uint32_t)); + break; + + default: + lwerror("%s is currently not implemented for type %d", __func__, type); + return LW_FAILURE; + } + + gserialized1_copy_point(double_array_start, g->gflags, out_point); + return LW_SUCCESS; +} + +/** +* Read the bounding box off a serialization and calculate one if +* it is not already there. +*/ +int gserialized1_get_gbox_p(const GSERIALIZED *g, GBOX *box) +{ + /* Try to just read the serialized box. */ + if ( gserialized1_read_gbox_p(g, box) == LW_SUCCESS ) + { + return LW_SUCCESS; + } + /* No box? Try to peek into simpler geometries and */ + /* derive a box without creating an lwgeom */ + else if ( gserialized1_peek_gbox_p(g, box) == LW_SUCCESS ) + { + return LW_SUCCESS; + } + /* Damn! Nothing for it but to create an lwgeom... */ + /* See http://trac.osgeo.org/postgis/ticket/1023 */ + else + { + LWGEOM *lwgeom = lwgeom_from_gserialized(g); + int ret = lwgeom_calculate_gbox(lwgeom, box); + gbox_float_round(box); + lwgeom_free(lwgeom); + return ret; + } +} + +/** +* Read the bounding box off a serialization and fail if +* it is not already there. +*/ +int gserialized1_fast_gbox_p(const GSERIALIZED *g, GBOX *box) +{ + /* Try to just read the serialized box. */ + if ( gserialized1_read_gbox_p(g, box) == LW_SUCCESS ) + { + return LW_SUCCESS; + } + /* No box? Try to peek into simpler geometries and */ + /* derive a box without creating an lwgeom */ + else if ( gserialized1_peek_gbox_p(g, box) == LW_SUCCESS ) + { + return LW_SUCCESS; + } + else + { + return LW_FAILURE; + } +} + + + + +/*********************************************************************** +* Calculate the GSERIALIZED size for an LWGEOM. +*/ + +/* Private functions */ + +static size_t gserialized1_from_any_size(const LWGEOM *geom); /* Local prototype */ + +static size_t gserialized1_from_lwpoint_size(const LWPOINT *point) +{ + size_t size = 4; /* Type number. */ + + assert(point); + + size += 4; /* Number of points (one or zero (empty)). */ + size += point->point->npoints * FLAGS_NDIMS(point->flags) * sizeof(double); + + LWDEBUGF(3, "point size = %d", size); + + return size; +} + +static size_t gserialized1_from_lwline_size(const LWLINE *line) +{ + size_t size = 4; /* Type number. */ + + assert(line); + + size += 4; /* Number of points (zero => empty). */ + size += line->points->npoints * FLAGS_NDIMS(line->flags) * sizeof(double); + + LWDEBUGF(3, "linestring size = %d", size); + + return size; +} + +static size_t gserialized1_from_lwtriangle_size(const LWTRIANGLE *triangle) +{ + size_t size = 4; /* Type number. */ + + assert(triangle); + + size += 4; /* Number of points (zero => empty). */ + size += triangle->points->npoints * FLAGS_NDIMS(triangle->flags) * sizeof(double); + + LWDEBUGF(3, "triangle size = %d", size); + + return size; +} + +static size_t gserialized1_from_lwpoly_size(const LWPOLY *poly) +{ + size_t size = 4; /* Type number. */ + uint32_t i = 0; + + assert(poly); + + size += 4; /* Number of rings (zero => empty). */ + if ( poly->nrings % 2 ) + size += 4; /* Padding to double alignment. */ + + for ( i = 0; i < poly->nrings; i++ ) + { + size += 4; /* Number of points in ring. */ + size += poly->rings[i]->npoints * FLAGS_NDIMS(poly->flags) * sizeof(double); + } + + LWDEBUGF(3, "polygon size = %d", size); + + return size; +} + +static size_t gserialized1_from_lwcircstring_size(const LWCIRCSTRING *curve) +{ + size_t size = 4; /* Type number. */ + + assert(curve); + + size += 4; /* Number of points (zero => empty). */ + size += curve->points->npoints * FLAGS_NDIMS(curve->flags) * sizeof(double); + + LWDEBUGF(3, "circstring size = %d", size); + + return size; +} + +static size_t gserialized1_from_lwcollection_size(const LWCOLLECTION *col) +{ + size_t size = 4; /* Type number. */ + uint32_t i = 0; + + assert(col); + + size += 4; /* Number of sub-geometries (zero => empty). */ + + for ( i = 0; i < col->ngeoms; i++ ) + { + size_t subsize = gserialized1_from_any_size(col->geoms[i]); + size += subsize; + LWDEBUGF(3, "lwcollection subgeom(%d) size = %d", i, subsize); + } + + LWDEBUGF(3, "lwcollection size = %d", size); + + return size; +} + +static size_t gserialized1_from_any_size(const LWGEOM *geom) +{ + LWDEBUGF(2, "Input type: %s", lwtype_name(geom->type)); + + switch (geom->type) + { + case POINTTYPE: + return gserialized1_from_lwpoint_size((LWPOINT *)geom); + case LINETYPE: + return gserialized1_from_lwline_size((LWLINE *)geom); + case POLYGONTYPE: + return gserialized1_from_lwpoly_size((LWPOLY *)geom); + case TRIANGLETYPE: + return gserialized1_from_lwtriangle_size((LWTRIANGLE *)geom); + case CIRCSTRINGTYPE: + return gserialized1_from_lwcircstring_size((LWCIRCSTRING *)geom); + case CURVEPOLYTYPE: + case COMPOUNDTYPE: + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTICURVETYPE: + case MULTIPOLYGONTYPE: + case MULTISURFACETYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + return gserialized1_from_lwcollection_size((LWCOLLECTION *)geom); + default: + lwerror("Unknown geometry type: %d - %s", geom->type, lwtype_name(geom->type)); + return 0; + } +} + +/* Public function */ + +size_t gserialized1_from_lwgeom_size(const LWGEOM *geom) +{ + size_t size = 8; /* Header overhead. */ + assert(geom); + + if (geom->bbox) + size += gbox_serialized_size(geom->flags); + + size += gserialized1_from_any_size(geom); + LWDEBUGF(3, "%s size = %d", __func__, size); + + return size; +} + +/*********************************************************************** +* Serialize an LWGEOM into GSERIALIZED. +*/ + +/* Private functions */ + +static size_t gserialized1_from_lwgeom_any(const LWGEOM *geom, uint8_t *buf); + +static size_t gserialized1_from_lwpoint(const LWPOINT *point, uint8_t *buf) +{ + uint8_t *loc; + int ptsize = ptarray_point_size(point->point); + int type = POINTTYPE; + + assert(point); + assert(buf); + + if ( FLAGS_GET_ZM(point->flags) != FLAGS_GET_ZM(point->point->flags) ) + lwerror("Dimensions mismatch in lwpoint"); + + LWDEBUGF(2, "%s (%p, %p) called", __func__, point, buf); + + loc = buf; + + /* Write in the type. */ + memcpy(loc, &type, sizeof(uint32_t)); + loc += sizeof(uint32_t); + /* Write in the number of points (0 => empty). */ + memcpy(loc, &(point->point->npoints), sizeof(uint32_t)); + loc += sizeof(uint32_t); + + /* Copy in the ordinates. */ + if ( point->point->npoints > 0 ) + { + memcpy(loc, getPoint_internal(point->point, 0), ptsize); + loc += ptsize; + } + + return (size_t)(loc - buf); +} + +static size_t gserialized1_from_lwline(const LWLINE *line, uint8_t *buf) +{ + uint8_t *loc; + int ptsize; + size_t size; + int type = LINETYPE; + + assert(line); + assert(buf); + + LWDEBUGF(2, "%s (%p, %p) called", __func__, line, buf); + + if ( FLAGS_GET_Z(line->flags) != FLAGS_GET_Z(line->points->flags) ) + lwerror("Dimensions mismatch in lwline"); + + ptsize = ptarray_point_size(line->points); + + loc = buf; + + /* Write in the type. */ + memcpy(loc, &type, sizeof(uint32_t)); + loc += sizeof(uint32_t); + + /* Write in the npoints. */ + memcpy(loc, &(line->points->npoints), sizeof(uint32_t)); + loc += sizeof(uint32_t); + + LWDEBUGF(3, "%s added npoints (%d)", __func__, line->points->npoints); + + /* Copy in the ordinates. */ + if ( line->points->npoints > 0 ) + { + size = line->points->npoints * ptsize; + memcpy(loc, getPoint_internal(line->points, 0), size); + loc += size; + } + LWDEBUGF(3, "%s copied serialized_pointlist (%d bytes)", __func__, ptsize * line->points->npoints); + + return (size_t)(loc - buf); +} + +static size_t gserialized1_from_lwpoly(const LWPOLY *poly, uint8_t *buf) +{ + uint32_t i; + uint8_t *loc; + int ptsize; + int type = POLYGONTYPE; + + assert(poly); + assert(buf); + + LWDEBUGF(2, "%s called", __func__); + + ptsize = sizeof(double) * FLAGS_NDIMS(poly->flags); + loc = buf; + + /* Write in the type. */ + memcpy(loc, &type, sizeof(uint32_t)); + loc += sizeof(uint32_t); + + /* Write in the nrings. */ + memcpy(loc, &(poly->nrings), sizeof(uint32_t)); + loc += sizeof(uint32_t); + + /* Write in the npoints per ring. */ + for ( i = 0; i < poly->nrings; i++ ) + { + memcpy(loc, &(poly->rings[i]->npoints), sizeof(uint32_t)); + loc += sizeof(uint32_t); + } + + /* Add in padding if necessary to remain double aligned. */ + if ( poly->nrings % 2 ) + { + memset(loc, 0, sizeof(uint32_t)); + loc += sizeof(uint32_t); + } + + /* Copy in the ordinates. */ + for ( i = 0; i < poly->nrings; i++ ) + { + POINTARRAY *pa = poly->rings[i]; + size_t pasize; + + if ( FLAGS_GET_ZM(poly->flags) != FLAGS_GET_ZM(pa->flags) ) + lwerror("Dimensions mismatch in lwpoly"); + + pasize = pa->npoints * ptsize; + if ( pa->npoints > 0 ) + memcpy(loc, getPoint_internal(pa, 0), pasize); + loc += pasize; + } + return (size_t)(loc - buf); +} + +static size_t gserialized1_from_lwtriangle(const LWTRIANGLE *triangle, uint8_t *buf) +{ + uint8_t *loc; + int ptsize; + size_t size; + int type = TRIANGLETYPE; + + assert(triangle); + assert(buf); + + LWDEBUGF(2, "%s (%p, %p) called", __func__, triangle, buf); + + if ( FLAGS_GET_ZM(triangle->flags) != FLAGS_GET_ZM(triangle->points->flags) ) + lwerror("Dimensions mismatch in lwtriangle"); + + ptsize = ptarray_point_size(triangle->points); + + loc = buf; + + /* Write in the type. */ + memcpy(loc, &type, sizeof(uint32_t)); + loc += sizeof(uint32_t); + + /* Write in the npoints. */ + memcpy(loc, &(triangle->points->npoints), sizeof(uint32_t)); + loc += sizeof(uint32_t); + + LWDEBUGF(3, "%s added npoints (%d)", __func__, triangle->points->npoints); + + /* Copy in the ordinates. */ + if ( triangle->points->npoints > 0 ) + { + size = triangle->points->npoints * ptsize; + memcpy(loc, getPoint_internal(triangle->points, 0), size); + loc += size; + } + LWDEBUGF(3, "%s copied serialized_pointlist (%d bytes)", __func__, ptsize * triangle->points->npoints); + + return (size_t)(loc - buf); +} + +static size_t gserialized1_from_lwcircstring(const LWCIRCSTRING *curve, uint8_t *buf) +{ + uint8_t *loc; + int ptsize; + size_t size; + int type = CIRCSTRINGTYPE; + + assert(curve); + assert(buf); + + if (FLAGS_GET_ZM(curve->flags) != FLAGS_GET_ZM(curve->points->flags)) + lwerror("Dimensions mismatch in lwcircstring"); + + + ptsize = ptarray_point_size(curve->points); + loc = buf; + + /* Write in the type. */ + memcpy(loc, &type, sizeof(uint32_t)); + loc += sizeof(uint32_t); + + /* Write in the npoints. */ + memcpy(loc, &curve->points->npoints, sizeof(uint32_t)); + loc += sizeof(uint32_t); + + /* Copy in the ordinates. */ + if ( curve->points->npoints > 0 ) + { + size = curve->points->npoints * ptsize; + memcpy(loc, getPoint_internal(curve->points, 0), size); + loc += size; + } + + return (size_t)(loc - buf); +} + +static size_t gserialized1_from_lwcollection(const LWCOLLECTION *coll, uint8_t *buf) +{ + size_t subsize = 0; + uint8_t *loc; + uint32_t i; + int type; + + assert(coll); + assert(buf); + + type = coll->type; + loc = buf; + + /* Write in the type. */ + memcpy(loc, &type, sizeof(uint32_t)); + loc += sizeof(uint32_t); + + /* Write in the number of subgeoms. */ + memcpy(loc, &coll->ngeoms, sizeof(uint32_t)); + loc += sizeof(uint32_t); + + /* Serialize subgeoms. */ + for ( i=0; ingeoms; i++ ) + { + if (FLAGS_GET_ZM(coll->flags) != FLAGS_GET_ZM(coll->geoms[i]->flags)) + lwerror("Dimensions mismatch in lwcollection"); + subsize = gserialized1_from_lwgeom_any(coll->geoms[i], loc); + loc += subsize; + } + + return (size_t)(loc - buf); +} + +static size_t gserialized1_from_lwgeom_any(const LWGEOM *geom, uint8_t *buf) +{ + assert(geom); + assert(buf); + + LWDEBUGF(2, "Input type (%d) %s, hasz: %d hasm: %d", + geom->type, lwtype_name(geom->type), + FLAGS_GET_Z(geom->flags), FLAGS_GET_M(geom->flags)); + LWDEBUGF(2, "LWGEOM(%p) uint8_t(%p)", geom, buf); + + switch (geom->type) + { + case POINTTYPE: + return gserialized1_from_lwpoint((LWPOINT *)geom, buf); + case LINETYPE: + return gserialized1_from_lwline((LWLINE *)geom, buf); + case POLYGONTYPE: + return gserialized1_from_lwpoly((LWPOLY *)geom, buf); + case TRIANGLETYPE: + return gserialized1_from_lwtriangle((LWTRIANGLE *)geom, buf); + case CIRCSTRINGTYPE: + return gserialized1_from_lwcircstring((LWCIRCSTRING *)geom, buf); + case CURVEPOLYTYPE: + case COMPOUNDTYPE: + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTICURVETYPE: + case MULTIPOLYGONTYPE: + case MULTISURFACETYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + return gserialized1_from_lwcollection((LWCOLLECTION *)geom, buf); + default: + lwerror("Unknown geometry type: %d - %s", geom->type, lwtype_name(geom->type)); + return 0; + } + return 0; +} + +static size_t gserialized1_from_gbox(const GBOX *gbox, uint8_t *buf) +{ + uint8_t *loc = buf; + float f; + size_t return_size; + + assert(buf); + + f = next_float_down(gbox->xmin); + memcpy(loc, &f, sizeof(float)); + loc += sizeof(float); + + f = next_float_up(gbox->xmax); + memcpy(loc, &f, sizeof(float)); + loc += sizeof(float); + + f = next_float_down(gbox->ymin); + memcpy(loc, &f, sizeof(float)); + loc += sizeof(float); + + f = next_float_up(gbox->ymax); + memcpy(loc, &f, sizeof(float)); + loc += sizeof(float); + + if ( FLAGS_GET_GEODETIC(gbox->flags) ) + { + f = next_float_down(gbox->zmin); + memcpy(loc, &f, sizeof(float)); + loc += sizeof(float); + + f = next_float_up(gbox->zmax); + memcpy(loc, &f, sizeof(float)); + loc += sizeof(float); + + return_size = (size_t)(loc - buf); + LWDEBUGF(4, "returning size %d", return_size); + return return_size; + } + + if ( FLAGS_GET_Z(gbox->flags) ) + { + f = next_float_down(gbox->zmin); + memcpy(loc, &f, sizeof(float)); + loc += sizeof(float); + + f = next_float_up(gbox->zmax); + memcpy(loc, &f, sizeof(float)); + loc += sizeof(float); + + } + + if ( FLAGS_GET_M(gbox->flags) ) + { + f = next_float_down(gbox->mmin); + memcpy(loc, &f, sizeof(float)); + loc += sizeof(float); + + f = next_float_up(gbox->mmax); + memcpy(loc, &f, sizeof(float)); + loc += sizeof(float); + } + return_size = (size_t)(loc - buf); + LWDEBUGF(4, "returning size %d", return_size); + return return_size; +} + +/* Public function */ + +GSERIALIZED* gserialized1_from_lwgeom(LWGEOM *geom, size_t *size) +{ + size_t expected_size = 0; + size_t return_size = 0; + uint8_t *serialized = NULL; + uint8_t *ptr = NULL; + GSERIALIZED *g = NULL; + assert(geom); + + /* + ** See if we need a bounding box, add one if we don't have one. + */ + if ( (! geom->bbox) && lwgeom_needs_bbox(geom) && (!lwgeom_is_empty(geom)) ) + { + lwgeom_add_bbox(geom); + } + + /* + ** Harmonize the flags to the state of the lwgeom + */ + if ( geom->bbox ) + FLAGS_SET_BBOX(geom->flags, 1); + else + FLAGS_SET_BBOX(geom->flags, 0); + + /* Set up the uint8_t buffer into which we are going to write the serialized geometry. */ + expected_size = gserialized1_from_lwgeom_size(geom); + serialized = lwalloc(expected_size); + ptr = serialized; + + /* Move past size, srid and flags. */ + ptr += 8; + + /* Write in the serialized form of the gbox, if necessary. */ + if ( geom->bbox ) + ptr += gserialized1_from_gbox(geom->bbox, ptr); + + /* Write in the serialized form of the geometry. */ + ptr += gserialized1_from_lwgeom_any(geom, ptr); + + /* Calculate size as returned by data processing functions. */ + return_size = ptr - serialized; + + if ( expected_size != return_size ) /* Uh oh! */ + { + lwerror("Return size (%d) not equal to expected size (%d)!", return_size, expected_size); + return NULL; + } + + if ( size ) /* Return the output size to the caller if necessary. */ + *size = return_size; + + g = (GSERIALIZED*)serialized; + + /* + ** We are aping PgSQL code here, PostGIS code should use + ** VARSIZE to set this for real. + */ + g->size = return_size << 2; + + /* Set the SRID! */ + gserialized1_set_srid(g, geom->srid); + + g->gflags = lwflags_get_g1flags(geom->flags); + + return g; +} + +/*********************************************************************** +* De-serialize GSERIALIZED into an LWGEOM. +*/ + +static LWGEOM* lwgeom_from_gserialized1_buffer(uint8_t *data_ptr, lwflags_t lwflags, size_t *size); + +static LWPOINT* lwpoint_from_gserialized1_buffer(uint8_t *data_ptr, lwflags_t lwflags, size_t *size) +{ + uint8_t *start_ptr = data_ptr; + LWPOINT *point; + uint32_t npoints = 0; + + assert(data_ptr); + + point = (LWPOINT*)lwalloc(sizeof(LWPOINT)); + point->srid = SRID_UNKNOWN; /* Default */ + point->bbox = NULL; + point->type = POINTTYPE; + point->flags = lwflags; + + data_ptr += 4; /* Skip past the type. */ + npoints = gserialized1_get_uint32_t(data_ptr); /* Zero => empty geometry */ + data_ptr += 4; /* Skip past the npoints. */ + + if ( npoints > 0 ) + point->point = ptarray_construct_reference_data(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), 1, data_ptr); + else + point->point = ptarray_construct(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), 0); /* Empty point */ + + data_ptr += npoints * FLAGS_NDIMS(lwflags) * sizeof(double); + + if ( size ) + *size = data_ptr - start_ptr; + + return point; +} + +static LWLINE* lwline_from_gserialized1_buffer(uint8_t *data_ptr, lwflags_t lwflags, size_t *size) +{ + uint8_t *start_ptr = data_ptr; + LWLINE *line; + uint32_t npoints = 0; + + assert(data_ptr); + + line = (LWLINE*)lwalloc(sizeof(LWLINE)); + line->srid = SRID_UNKNOWN; /* Default */ + line->bbox = NULL; + line->type = LINETYPE; + line->flags = lwflags; + + data_ptr += 4; /* Skip past the type. */ + npoints = gserialized1_get_uint32_t(data_ptr); /* Zero => empty geometry */ + data_ptr += 4; /* Skip past the npoints. */ + + if ( npoints > 0 ) + line->points = ptarray_construct_reference_data(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), npoints, data_ptr); + + else + line->points = ptarray_construct(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), 0); /* Empty linestring */ + + data_ptr += FLAGS_NDIMS(lwflags) * npoints * sizeof(double); + + if ( size ) + *size = data_ptr - start_ptr; + + return line; +} + +static LWPOLY* lwpoly_from_gserialized1_buffer(uint8_t *data_ptr, lwflags_t lwflags, size_t *size) +{ + uint8_t *start_ptr = data_ptr; + LWPOLY *poly; + uint8_t *ordinate_ptr; + uint32_t nrings = 0; + uint32_t i = 0; + + assert(data_ptr); + + poly = (LWPOLY*)lwalloc(sizeof(LWPOLY)); + poly->srid = SRID_UNKNOWN; /* Default */ + poly->bbox = NULL; + poly->type = POLYGONTYPE; + poly->flags = lwflags; + + data_ptr += 4; /* Skip past the polygontype. */ + nrings = gserialized1_get_uint32_t(data_ptr); /* Zero => empty geometry */ + poly->nrings = nrings; + LWDEBUGF(4, "nrings = %d", nrings); + data_ptr += 4; /* Skip past the nrings. */ + + ordinate_ptr = data_ptr; /* Start the ordinate pointer. */ + if ( nrings > 0) + { + poly->rings = (POINTARRAY**)lwalloc( sizeof(POINTARRAY*) * nrings ); + poly->maxrings = nrings; + ordinate_ptr += nrings * 4; /* Move past all the npoints values. */ + if ( nrings % 2 ) /* If there is padding, move past that too. */ + ordinate_ptr += 4; + } + else /* Empty polygon */ + { + poly->rings = NULL; + poly->maxrings = 0; + } + + for ( i = 0; i < nrings; i++ ) + { + uint32_t npoints = 0; + + /* Read in the number of points. */ + npoints = gserialized1_get_uint32_t(data_ptr); + data_ptr += 4; + + /* Make a point array for the ring, and move the ordinate pointer past the ring ordinates. */ + poly->rings[i] = ptarray_construct_reference_data(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), npoints, ordinate_ptr); + + ordinate_ptr += sizeof(double) * FLAGS_NDIMS(lwflags) * npoints; + } + + if ( size ) + *size = ordinate_ptr - start_ptr; + + return poly; +} + +static LWTRIANGLE* lwtriangle_from_gserialized1_buffer(uint8_t *data_ptr, lwflags_t lwflags, size_t *size) +{ + uint8_t *start_ptr = data_ptr; + LWTRIANGLE *triangle; + uint32_t npoints = 0; + + assert(data_ptr); + + triangle = (LWTRIANGLE*)lwalloc(sizeof(LWTRIANGLE)); + triangle->srid = SRID_UNKNOWN; /* Default */ + triangle->bbox = NULL; + triangle->type = TRIANGLETYPE; + triangle->flags = lwflags; + + data_ptr += 4; /* Skip past the type. */ + npoints = gserialized1_get_uint32_t(data_ptr); /* Zero => empty geometry */ + data_ptr += 4; /* Skip past the npoints. */ + + if ( npoints > 0 ) + triangle->points = ptarray_construct_reference_data(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), npoints, data_ptr); + else + triangle->points = ptarray_construct(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), 0); /* Empty triangle */ + + data_ptr += FLAGS_NDIMS(lwflags) * npoints * sizeof(double); + + if ( size ) + *size = data_ptr - start_ptr; + + return triangle; +} + +static LWCIRCSTRING* lwcircstring_from_gserialized1_buffer(uint8_t *data_ptr, lwflags_t lwflags, size_t *size) +{ + uint8_t *start_ptr = data_ptr; + LWCIRCSTRING *circstring; + uint32_t npoints = 0; + + assert(data_ptr); + + circstring = (LWCIRCSTRING*)lwalloc(sizeof(LWCIRCSTRING)); + circstring->srid = SRID_UNKNOWN; /* Default */ + circstring->bbox = NULL; + circstring->type = CIRCSTRINGTYPE; + circstring->flags = lwflags; + + data_ptr += 4; /* Skip past the circstringtype. */ + npoints = gserialized1_get_uint32_t(data_ptr); /* Zero => empty geometry */ + data_ptr += 4; /* Skip past the npoints. */ + + if ( npoints > 0 ) + circstring->points = ptarray_construct_reference_data(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), npoints, data_ptr); + else + circstring->points = ptarray_construct(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), 0); /* Empty circularstring */ + + data_ptr += FLAGS_NDIMS(lwflags) * npoints * sizeof(double); + + if ( size ) + *size = data_ptr - start_ptr; + + return circstring; +} + +static LWCOLLECTION* lwcollection_from_gserialized1_buffer(uint8_t *data_ptr, lwflags_t lwflags, size_t *size) +{ + uint32_t type; + uint8_t *start_ptr = data_ptr; + LWCOLLECTION *collection; + uint32_t ngeoms = 0; + uint32_t i = 0; + + assert(data_ptr); + + type = gserialized1_get_uint32_t(data_ptr); + data_ptr += 4; /* Skip past the type. */ + + collection = (LWCOLLECTION*)lwalloc(sizeof(LWCOLLECTION)); + collection->srid = SRID_UNKNOWN; /* Default */ + collection->bbox = NULL; + collection->type = type; + collection->flags = lwflags; + + ngeoms = gserialized1_get_uint32_t(data_ptr); + collection->ngeoms = ngeoms; /* Zero => empty geometry */ + data_ptr += 4; /* Skip past the ngeoms. */ + + if ( ngeoms > 0 ) + { + collection->geoms = lwalloc(sizeof(LWGEOM*) * ngeoms); + collection->maxgeoms = ngeoms; + } + else + { + collection->geoms = NULL; + collection->maxgeoms = 0; + } + + /* Sub-geometries are never de-serialized with boxes (#1254) */ + FLAGS_SET_BBOX(lwflags, 0); + + for ( i = 0; i < ngeoms; i++ ) + { + uint32_t subtype = gserialized1_get_uint32_t(data_ptr); + size_t subsize = 0; + + if ( ! lwcollection_allows_subtype(type, subtype) ) + { + lwerror("Invalid subtype (%s) for collection type (%s)", lwtype_name(subtype), lwtype_name(type)); + lwfree(collection); + return NULL; + } + collection->geoms[i] = lwgeom_from_gserialized1_buffer(data_ptr, lwflags, &subsize); + data_ptr += subsize; + } + + if ( size ) + *size = data_ptr - start_ptr; + + return collection; +} + +LWGEOM* lwgeom_from_gserialized1_buffer(uint8_t *data_ptr, lwflags_t lwflags, size_t *g_size) +{ + uint32_t type; + + assert(data_ptr); + + type = gserialized1_get_uint32_t(data_ptr); + + LWDEBUGF(2, "Got type %d (%s), hasz=%d hasm=%d geodetic=%d hasbox=%d", type, lwtype_name(type), + FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), FLAGS_GET_GEODETIC(lwflags), FLAGS_GET_BBOX(lwflags)); + + switch (type) + { + case POINTTYPE: + return (LWGEOM *)lwpoint_from_gserialized1_buffer(data_ptr, lwflags, g_size); + case LINETYPE: + return (LWGEOM *)lwline_from_gserialized1_buffer(data_ptr, lwflags, g_size); + case CIRCSTRINGTYPE: + return (LWGEOM *)lwcircstring_from_gserialized1_buffer(data_ptr, lwflags, g_size); + case POLYGONTYPE: + return (LWGEOM *)lwpoly_from_gserialized1_buffer(data_ptr, lwflags, g_size); + case TRIANGLETYPE: + return (LWGEOM *)lwtriangle_from_gserialized1_buffer(data_ptr, lwflags, g_size); + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COMPOUNDTYPE: + case CURVEPOLYTYPE: + case MULTICURVETYPE: + case MULTISURFACETYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + return (LWGEOM *)lwcollection_from_gserialized1_buffer(data_ptr, lwflags, g_size); + default: + lwerror("Unknown geometry type: %d - %s", type, lwtype_name(type)); + return NULL; + } +} + +LWGEOM* lwgeom_from_gserialized1(const GSERIALIZED *g) +{ + lwflags_t lwflags = 0; + int32_t srid = 0; + uint32_t lwtype = 0; + uint8_t *data_ptr = NULL; + LWGEOM *lwgeom = NULL; + GBOX bbox; + size_t size = 0; + + assert(g); + + srid = gserialized1_get_srid(g); + lwtype = gserialized1_get_type(g); + lwflags = gserialized1_get_lwflags(g); + + LWDEBUGF(4, "Got type %d (%s), srid=%d", lwtype, lwtype_name(lwtype), srid); + + data_ptr = (uint8_t*)g->data; + if (FLAGS_GET_BBOX(lwflags)) + data_ptr += gbox_serialized_size(lwflags); + + lwgeom = lwgeom_from_gserialized1_buffer(data_ptr, lwflags, &size); + + if ( ! lwgeom ) + lwerror("%s: unable create geometry", __func__); /* Ooops! */ + + lwgeom->type = lwtype; + lwgeom->flags = lwflags; + + if ( gserialized1_read_gbox_p(g, &bbox) == LW_SUCCESS ) + { + lwgeom->bbox = gbox_copy(&bbox); + } + else if ( lwgeom_needs_bbox(lwgeom) && (lwgeom_calculate_gbox(lwgeom, &bbox) == LW_SUCCESS) ) + { + lwgeom->bbox = gbox_copy(&bbox); + } + else + { + lwgeom->bbox = NULL; + } + + lwgeom_set_srid(lwgeom, srid); + + return lwgeom; +} + +const float * gserialized1_get_float_box_p(const GSERIALIZED *g, size_t *ndims) +{ + if (ndims) + *ndims = G1FLAGS_NDIMS_BOX(g->gflags); + if (!g) return NULL; + if (!G1FLAGS_GET_BBOX(g->gflags)) return NULL; + return (const float *)(g->data); +} + +/** +* Update the bounding box of a #GSERIALIZED, allocating a fresh one +* if there is not enough space to just write the new box in. +* WARNING if a new object needs to be created, the +* input pointer will have to be freed by the caller! Check +* to see if input == output. Returns null if there's a problem +* like mismatched dimensions. +*/ +GSERIALIZED* gserialized1_set_gbox(GSERIALIZED *g, GBOX *gbox) +{ + + int g_ndims = G1FLAGS_NDIMS_BOX(g->gflags); + int box_ndims = FLAGS_NDIMS_BOX(gbox->flags); + GSERIALIZED *g_out = NULL; + size_t box_size = 2 * g_ndims * sizeof(float); + float *fbox; + int fbox_pos = 0; + + /* The dimensionality of the inputs has to match or we are SOL. */ + if ( g_ndims != box_ndims ) + { + return NULL; + } + + /* Serialized already has room for a box. */ + if (G1FLAGS_GET_BBOX(g->gflags)) + { + g_out = g; + } + /* Serialized has no box. We need to allocate enough space for the old + data plus the box, and leave a gap in the memory segment to write + the new values into. + */ + else + { + size_t varsize_new = LWSIZE_GET(g->size) + box_size; + uint8_t *ptr; + g_out = lwalloc(varsize_new); + /* Copy the head of g into place */ + memcpy(g_out, g, 8); + /* Copy the body of g into place after leaving space for the box */ + ptr = g_out->data; + ptr += box_size; + memcpy(ptr, g->data, LWSIZE_GET(g->size) - 8); + G1FLAGS_SET_BBOX(g_out->gflags, 1); + LWSIZE_SET(g_out->size, varsize_new); + } + + /* Move bounds to nearest float values */ + gbox_float_round(gbox); + /* Now write the float box values into the memory segement */ + fbox = (float*)(g_out->data); + /* Copy in X/Y */ + fbox[fbox_pos++] = gbox->xmin; + fbox[fbox_pos++] = gbox->xmax; + fbox[fbox_pos++] = gbox->ymin; + fbox[fbox_pos++] = gbox->ymax; + /* Optionally copy in higher dims */ + if(gserialized1_has_z(g) || gserialized1_is_geodetic(g)) + { + fbox[fbox_pos++] = gbox->zmin; + fbox[fbox_pos++] = gbox->zmax; + } + if(gserialized1_has_m(g) && ! gserialized1_is_geodetic(g)) + { + fbox[fbox_pos++] = gbox->mmin; + fbox[fbox_pos++] = gbox->mmax; + } + + return g_out; +} + + +/** +* Remove the bounding box from a #GSERIALIZED. Returns a freshly +* allocated #GSERIALIZED every time. +*/ +GSERIALIZED* gserialized1_drop_gbox(GSERIALIZED *g) +{ + int g_ndims = G1FLAGS_NDIMS_BOX(g->gflags); + size_t box_size = 2 * g_ndims * sizeof(float); + size_t g_out_size = LWSIZE_GET(g->size) - box_size; + GSERIALIZED *g_out = lwalloc(g_out_size); + + /* Copy the contents while omitting the box */ + if ( G1FLAGS_GET_BBOX(g->gflags) ) + { + uint8_t *outptr = (uint8_t*)g_out; + uint8_t *inptr = (uint8_t*)g; + /* Copy the header (size+type) of g into place */ + memcpy(outptr, inptr, 8); + outptr += 8; + inptr += 8 + box_size; + /* Copy parts after the box into place */ + memcpy(outptr, inptr, g_out_size - 8); + G1FLAGS_SET_BBOX(g_out->gflags, 0); + LWSIZE_SET(g_out->size, g_out_size); + } + /* No box? Nothing to do but copy and return. */ + else + { + memcpy(g_out, g, g_out_size); + } + + return g_out; +} + diff --git a/mgist-postgis/liblwgeom/gserialized1.h b/mgist-postgis/liblwgeom/gserialized1.h new file mode 100644 index 0000000..e84b975 --- /dev/null +++ b/mgist-postgis/liblwgeom/gserialized1.h @@ -0,0 +1,163 @@ +/** +* Macros for manipulating the 'flags' byte. A uint8_t used as follows: +* VVSRGBMZ +* Version bit, followed by +* Validty, Solid, ReadOnly, Geodetic, HasBBox, HasM and HasZ flags. +*/ +#define G1FLAG_Z 0x01 +#define G1FLAG_M 0x02 +#define G1FLAG_BBOX 0x04 +#define G1FLAG_GEODETIC 0x08 +#define G1FLAG_READONLY 0x10 +#define G1FLAG_SOLID 0x20 +/* VERSION BITS 0x40 */ +/* VERSION BITS 0x80 */ + +#define G1FLAGS_GET_Z(gflags) ((gflags) & G1FLAG_Z) +#define G1FLAGS_GET_M(gflags) (((gflags) & G1FLAG_M)>>1) +#define G1FLAGS_GET_BBOX(gflags) (((gflags) & G1FLAG_BBOX)>>2) +#define G1FLAGS_GET_GEODETIC(gflags) (((gflags) & G1FLAG_GEODETIC)>>3) +#define G1FLAGS_GET_SOLID(gflags) (((gflags) & G1FLAG_SOLID)>>5) + + +#define G1FLAGS_SET_Z(gflags, value) ((gflags) = (value) ? ((gflags) | G1FLAG_Z) : ((gflags) & ~G1FLAG_Z)) +#define G1FLAGS_SET_M(gflags, value) ((gflags) = (value) ? ((gflags) | G1FLAG_M) : ((gflags) & ~G1FLAG_M)) +#define G1FLAGS_SET_BBOX(gflags, value) ((gflags) = (value) ? ((gflags) | G1FLAG_BBOX) : ((gflags) & ~G1FLAG_BBOX)) +#define G1FLAGS_SET_GEODETIC(gflags, value) ((gflags) = (value) ? ((gflags) | G1FLAG_GEODETIC) : ((gflags) & ~G1FLAG_GEODETIC)) +#define G1FLAGS_SET_SOLID(gflags, value) ((gflags) = (value) ? ((gflags) | G1FLAG_SOLID) : ((gflags) & ~G1FLAG_SOLID)) + +#define G1FLAGS_NDIMS(gflags) (2 + G1FLAGS_GET_Z(gflags) + G1FLAGS_GET_M(gflags)) +#define G1FLAGS_GET_ZM(gflags) (G1FLAGS_GET_M(gflags) + G1FLAGS_GET_Z(gflags) * 2) +#define G1FLAGS_NDIMS_BOX(gflags) (G1FLAGS_GET_GEODETIC(gflags) ? 3 : G1FLAGS_NDIMS(gflags)) + +uint8_t g1flags(int has_z, int has_m, int is_geodetic); +uint8_t lwflags_get_g1flags(lwflags_t lwflags); + +/* +* GSERIALIZED PUBLIC API +*/ + +/** +* Read the flags from a #GSERIALIZED and return a standard lwflag +* integer +*/ +lwflags_t gserialized1_get_lwflags(const GSERIALIZED *g); + +/** +* Copy a new bounding box into an existing gserialized. +* If necessary a new #GSERIALIZED will be allocated. Test +* that input != output before freeing input. +*/ +GSERIALIZED *gserialized1_set_gbox(GSERIALIZED *g, GBOX *gbox); + +/** +* Remove the bounding box from a #GSERIALIZED. Returns a freshly +* allocated #GSERIALIZED every time. +*/ +GSERIALIZED* gserialized1_drop_gbox(GSERIALIZED *g); + +/** +* Read the box from the #GSERIALIZED or calculate it if necessary. +* Return #LWFAILURE if box cannot be calculated (NULL or EMPTY +* input). +*/ +int gserialized1_get_gbox_p(const GSERIALIZED *g, GBOX *gbox); + +/** +* Read the box from the #GSERIALIZED or return #LWFAILURE if +* box is unavailable. +*/ +int gserialized1_fast_gbox_p(const GSERIALIZED *g, GBOX *gbox); + +/** +* Extract the geometry type from the serialized form (it hides in +* the anonymous data area, so this is a handy function). +*/ +uint32_t gserialized1_get_type(const GSERIALIZED *g); + +/** +* Returns the size in bytes to read from toast to get the basic +* information from a geometry: GSERIALIZED struct, bbox and type +*/ +uint32_t gserialized1_max_header_size(void); + +/** +* Returns a hash code for the srid/type/geometry information +* in the GSERIALIZED. Ignores metadata like flags and optional +* boxes, etc. +*/ +int32_t gserialized1_hash(const GSERIALIZED *g); + +/** +* Extract the SRID from the serialized form (it is packed into +* three bytes so this is a handy function). +*/ +int32_t gserialized1_get_srid(const GSERIALIZED *g); + +/** +* Write the SRID into the serialized form (it is packed into +* three bytes so this is a handy function). +*/ +void gserialized1_set_srid(GSERIALIZED *g, int32_t srid); + +/** +* Check if a #GSERIALIZED is empty without deserializing first. +* Only checks if the number of elements of the parent geometry +* is zero, will not catch collections of empty, eg: +* GEOMETRYCOLLECTION(POINT EMPTY) +*/ +int gserialized1_is_empty(const GSERIALIZED *g); + +/** +* Check if a #GSERIALIZED has a bounding box without deserializing first. +*/ +int gserialized1_has_bbox(const GSERIALIZED *gser); + +/** +* Check if a #GSERIALIZED has a Z ordinate. +*/ +int gserialized1_has_z(const GSERIALIZED *gser); + +/** +* Check if a #GSERIALIZED has an M ordinate. +*/ +int gserialized1_has_m(const GSERIALIZED *gser); + +/** +* Check if a #GSERIALIZED is a geography. +*/ +int gserialized1_is_geodetic(const GSERIALIZED *gser); + +/** +* Return the number of dimensions (2, 3, 4) in a geometry +*/ +int gserialized1_ndims(const GSERIALIZED *gser); + +/** +* Allocate a new #GSERIALIZED from an #LWGEOM. For all non-point types, a bounding +* box will be calculated and embedded in the serialization. The geodetic flag is used +* to control the box calculation (cartesian or geocentric). If set, the size pointer +* will contain the size of the final output, which is useful for setting the PgSQL +* VARSIZE information. +*/ +GSERIALIZED* gserialized1_from_lwgeom(LWGEOM *geom, size_t *size); + +/** +* Return the memory size a GSERIALIZED will occupy for a given LWGEOM. +*/ +size_t gserialized1_from_lwgeom_size(const LWGEOM *geom); + +/** +* Allocate a new #LWGEOM from a #GSERIALIZED. The resulting #LWGEOM will have coordinates +* that are double aligned and suitable for direct reading using getPoint2d_cp +*/ +LWGEOM* lwgeom_from_gserialized1(const GSERIALIZED *g); + +/** +* Point into the float box area of the serialization +*/ +const float * gserialized1_get_float_box_p(const GSERIALIZED *g, size_t *ndims); + +int gserialized1_peek_gbox_p(const GSERIALIZED *g, GBOX *gbox); + +int gserialized1_peek_first_point(const GSERIALIZED *g, POINT4D *out_point); diff --git a/mgist-postgis/liblwgeom/gserialized2.c b/mgist-postgis/liblwgeom/gserialized2.c new file mode 100644 index 0000000..e9dc77b --- /dev/null +++ b/mgist-postgis/liblwgeom/gserialized2.c @@ -0,0 +1,1668 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2009 Paul Ramsey + * Copyright 2017 Darafei Praliaskouski + * + **********************************************************************/ + +/* +* GSERIALIZED verison 2 includes an optional extended flags uint64_t +* before the optional bounding box. There may be other optional +* components before the data area, but they all must be double +* aligned to that the ordinates remain double aligned. +* +* size Used by PgSQL VARSIZE g->size +* srid +* gflags> 1 byte g->gflags +* [ Optional extended flags (check flags for cue) +* ] +* [ Optional bounding box (check flags for cue) +* Number of dimensions is variable +* and also indicated in the flags +* ] +* ... +* data area +*/ + +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" +#include "lwgeodetic.h" +#include "gserialized2.h" + +#include + +/*********************************************************************** +* GSERIALIZED metadata utility functions. +*/ + +static int gserialized2_read_gbox_p(const GSERIALIZED *g, GBOX *gbox); + + +lwflags_t gserialized2_get_lwflags(const GSERIALIZED *g) +{ + lwflags_t lwflags = 0; + uint8_t gflags = g->gflags; + FLAGS_SET_Z(lwflags, G2FLAGS_GET_Z(gflags)); + FLAGS_SET_M(lwflags, G2FLAGS_GET_M(gflags)); + FLAGS_SET_BBOX(lwflags, G2FLAGS_GET_BBOX(gflags)); + FLAGS_SET_GEODETIC(lwflags, G2FLAGS_GET_GEODETIC(gflags)); + if (G2FLAGS_GET_EXTENDED(gflags)) + { + uint64_t xflags = 0; + memcpy(&xflags, g->data, sizeof(uint64_t)); + FLAGS_SET_SOLID(lwflags, xflags & G2FLAG_X_SOLID); + } + return lwflags; +} + +static int lwflags_uses_extended_flags(lwflags_t lwflags) +{ + lwflags_t core_lwflags = LWFLAG_Z | LWFLAG_M | LWFLAG_BBOX | LWFLAG_GEODETIC; + return (lwflags & (~core_lwflags)) != 0; +} + + +static inline size_t gserialized2_box_size(const GSERIALIZED *g) +{ + if (G2FLAGS_GET_GEODETIC(g->gflags)) + return 6 * sizeof(float); + else + return 2 * G2FLAGS_NDIMS(g->gflags) * sizeof(float); +} + +static inline size_t gserialized2_header_size(const GSERIALIZED *g) +{ + uint32_t sz = 8; /* varsize (4) + srid(3) + flags (1) */ + + if (gserialized2_has_extended(g)) + sz += 8; + + if (gserialized2_has_bbox(g)) + sz += gserialized2_box_size(g); + + return sz; +} + +/* Returns a pointer to the start of the geometry data */ +static inline uint8_t * +gserialized2_get_geometry_p(const GSERIALIZED *g) +{ + uint32_t extra_data_bytes = 0; + if (gserialized2_has_extended(g)) + extra_data_bytes += sizeof(uint64_t); + + if (gserialized2_has_bbox(g)) + extra_data_bytes += gserialized2_box_size(g); + + return ((uint8_t *)g->data) + extra_data_bytes; +} + +uint8_t lwflags_get_g2flags(lwflags_t lwflags) +{ + uint8_t gflags = 0; + G2FLAGS_SET_Z(gflags, FLAGS_GET_Z(lwflags)); + G2FLAGS_SET_M(gflags, FLAGS_GET_M(lwflags)); + G2FLAGS_SET_BBOX(gflags, FLAGS_GET_BBOX(lwflags)); + G2FLAGS_SET_GEODETIC(gflags, FLAGS_GET_GEODETIC(lwflags)); + G2FLAGS_SET_EXTENDED(gflags, lwflags_uses_extended_flags(lwflags)); + G2FLAGS_SET_VERSION(gflags, 1); + return gflags; +} + +/* handle missaligned uint32_t data */ +static inline uint32_t gserialized2_get_uint32_t(const uint8_t *loc) +{ + return *((uint32_t*)loc); +} + +uint8_t g2flags(int has_z, int has_m, int is_geodetic) +{ + uint8_t gflags = 0; + if (has_z) + G2FLAGS_SET_Z(gflags, 1); + if (has_m) + G2FLAGS_SET_M(gflags, 1); + if (is_geodetic) + G2FLAGS_SET_GEODETIC(gflags, 1); + return gflags; +} + +int gserialized2_has_bbox(const GSERIALIZED *g) +{ + return G2FLAGS_GET_BBOX(g->gflags); +} + +int gserialized2_has_extended(const GSERIALIZED *g) +{ + return G2FLAGS_GET_EXTENDED(g->gflags); +} + +int gserialized2_has_z(const GSERIALIZED *g) +{ + return G2FLAGS_GET_Z(g->gflags); +} + +int gserialized2_has_m(const GSERIALIZED *g) +{ + return G2FLAGS_GET_M(g->gflags); +} + +int gserialized2_ndims(const GSERIALIZED *g) +{ + return G2FLAGS_NDIMS(g->gflags); +} + +int gserialized2_is_geodetic(const GSERIALIZED *g) +{ + return G2FLAGS_GET_GEODETIC(g->gflags); +} + +uint32_t gserialized2_max_header_size(void) +{ + /* GSERIALIZED size + max bbox according gbox_serialized_size (XYZM*2) + extended flags + type */ + return offsetof(GSERIALIZED, data) + 8 * sizeof(float) + sizeof(uint64_t) + sizeof(uint32_t); +} + + +uint32_t gserialized2_get_type(const GSERIALIZED *g) +{ + uint8_t *ptr = gserialized2_get_geometry_p(g); + return *((uint32_t*)(ptr)); +} + +int32_t gserialized2_get_srid(const GSERIALIZED *g) +{ + int32_t srid = 0; + srid = srid | (g->srid[0] << 16); + srid = srid | (g->srid[1] << 8); + srid = srid | (g->srid[2]); + /* Only the first 21 bits are set. Slide up and back to pull + the negative bits down, if we need them. */ + srid = (srid<<11)>>11; + + /* 0 is our internal unknown value. We'll map back and forth here for now */ + if (srid == 0) + return SRID_UNKNOWN; + else + return srid; +} + +void gserialized2_set_srid(GSERIALIZED *g, int32_t srid) +{ + LWDEBUGF(3, "%s called with srid = %d", __func__, srid); + + srid = clamp_srid(srid); + + /* 0 is our internal unknown value. + * We'll map back and forth here for now */ + if (srid == SRID_UNKNOWN) + srid = 0; + + g->srid[0] = (srid & 0x001F0000) >> 16; + g->srid[1] = (srid & 0x0000FF00) >> 8; + g->srid[2] = (srid & 0x000000FF); +} + +static size_t gserialized2_is_empty_recurse(const uint8_t *p, int *isempty); +static size_t gserialized2_is_empty_recurse(const uint8_t *p, int *isempty) +{ + int i; + int32_t type, num; + + memcpy(&type, p, 4); + memcpy(&num, p+4, 4); + + if (lwtype_is_collection(type)) + { + size_t lz = 8; + for ( i = 0; i < num; i++ ) + { + lz += gserialized2_is_empty_recurse(p+lz, isempty); + if (!*isempty) + return lz; + } + *isempty = LW_TRUE; + return lz; + } + else + { + *isempty = (num == 0 ? LW_TRUE : LW_FALSE); + return 8; + } +} + +int gserialized2_is_empty(const GSERIALIZED *g) +{ + int isempty = 0; + uint8_t *p = gserialized2_get_geometry_p(g); + gserialized2_is_empty_recurse(p, &isempty); + return isempty; +} + + +/* Prototype for lookup3.c */ +/* key = the key to hash */ +/* length = length of the key */ +/* pc = IN: primary initval, OUT: primary hash */ +/* pb = IN: secondary initval, OUT: secondary hash */ +void hashlittle2(const void *key, size_t length, uint32_t *pc, uint32_t *pb); + +int32_t +gserialized2_hash(const GSERIALIZED *g1) +{ + int32_t hval; + int32_t pb = 0, pc = 0; + /* Point to just the type/coordinate part of buffer */ + size_t hsz1 = gserialized2_header_size(g1); + uint8_t *b1 = (uint8_t *)g1 + hsz1; + /* Calculate size of type/coordinate buffer */ + size_t sz1 = LWSIZE_GET(g1->size); + size_t bsz1 = sz1 - hsz1; + /* Calculate size of srid/type/coordinate buffer */ + int32_t srid = gserialized2_get_srid(g1); + size_t bsz2 = bsz1 + sizeof(int); + uint8_t *b2 = lwalloc(bsz2); + /* Copy srid into front of combined buffer */ + memcpy(b2, &srid, sizeof(int)); + /* Copy type/coordinates into rest of combined buffer */ + memcpy(b2+sizeof(int), b1, bsz1); + /* Hash combined buffer */ + hashlittle2(b2, bsz2, (uint32_t *)&pb, (uint32_t *)&pc); + lwfree(b2); + hval = pb ^ pc; + return hval; +} + + +const float * gserialized2_get_float_box_p(const GSERIALIZED *g, size_t *ndims) +{ + uint8_t *ptr = (uint8_t*)(g->data); + size_t bndims = G2FLAGS_NDIMS_BOX(g->gflags); + + if (ndims) + *ndims = bndims; + + /* Cannot do anything if there's no box */ + if (!(g && gserialized_has_bbox(g))) + return NULL; + + /* Advance past optional extended flags */ + if (gserialized2_has_extended(g)) + ptr += 8; + + return (const float *)(ptr); +} + +int gserialized2_read_gbox_p(const GSERIALIZED *g, GBOX *gbox) +{ + uint8_t gflags = g->gflags; + /* Null input! */ + if (!(g && gbox)) return LW_FAILURE; + + /* Initialize the flags on the box */ + gbox->flags = gserialized2_get_lwflags(g); + + /* Has pre-calculated box */ + if (G2FLAGS_GET_BBOX(gflags)) + { + int i = 0; + const float *fbox = gserialized2_get_float_box_p(g, NULL); + gbox->xmin = fbox[i++]; + gbox->xmax = fbox[i++]; + gbox->ymin = fbox[i++]; + gbox->ymax = fbox[i++]; + + /* Geodetic? Read next dimension (geocentric Z) and return */ + if (G2FLAGS_GET_GEODETIC(gflags)) + { + gbox->zmin = fbox[i++]; + gbox->zmax = fbox[i++]; + return LW_SUCCESS; + } + /* Cartesian? Read extra dimensions (if there) and return */ + if (G2FLAGS_GET_Z(gflags)) + { + gbox->zmin = fbox[i++]; + gbox->zmax = fbox[i++]; + } + if (G2FLAGS_GET_M(gflags)) + { + gbox->mmin = fbox[i++]; + gbox->mmax = fbox[i++]; + } + return LW_SUCCESS; + } + return LW_FAILURE; +} + +/* +* Populate a bounding box *without* allocating an LWGEOM. Useful +* for some performance purposes. +*/ +int +gserialized2_peek_gbox_p(const GSERIALIZED *g, GBOX *gbox) +{ + uint32_t type = gserialized2_get_type(g); + uint8_t *geometry_start = gserialized2_get_geometry_p(g); + double *dptr = (double *)(geometry_start); + int32_t *iptr = (int32_t *)(geometry_start); + + /* Peeking doesn't help if you already have a box or are geodetic */ + if (G2FLAGS_GET_GEODETIC(g->gflags) || G2FLAGS_GET_BBOX(g->gflags)) + { + return LW_FAILURE; + } + + /* Boxes of points are easy peasy */ + if (type == POINTTYPE) + { + int i = 1; /* Start past */ + + /* Read the npoints flag */ + int isempty = (iptr[1] == 0); + + /* EMPTY point has no box */ + if (isempty) return LW_FAILURE; + + gbox->xmin = gbox->xmax = dptr[i++]; + gbox->ymin = gbox->ymax = dptr[i++]; + gbox->flags = gserialized2_get_lwflags(g); + if (G2FLAGS_GET_Z(g->gflags)) + { + gbox->zmin = gbox->zmax = dptr[i++]; + } + if (G2FLAGS_GET_M(g->gflags)) + { + gbox->mmin = gbox->mmax = dptr[i++]; + } + gbox_float_round(gbox); + return LW_SUCCESS; + } + /* We can calculate the box of a two-point cartesian line trivially */ + else if (type == LINETYPE) + { + int ndims = G2FLAGS_NDIMS(g->gflags); + int i = 0; /* Start at */ + int npoints = iptr[1]; /* Read the npoints */ + + /* This only works with 2-point lines */ + if (npoints != 2) + return LW_FAILURE; + + /* Advance to X */ + /* Past */ + i++; + gbox->xmin = FP_MIN(dptr[i], dptr[i+ndims]); + gbox->xmax = FP_MAX(dptr[i], dptr[i+ndims]); + + /* Advance to Y */ + i++; + gbox->ymin = FP_MIN(dptr[i], dptr[i+ndims]); + gbox->ymax = FP_MAX(dptr[i], dptr[i+ndims]); + + gbox->flags = gserialized2_get_lwflags(g); + if (G2FLAGS_GET_Z(g->gflags)) + { + /* Advance to Z */ + i++; + gbox->zmin = FP_MIN(dptr[i], dptr[i+ndims]); + gbox->zmax = FP_MAX(dptr[i], dptr[i+ndims]); + } + if (G2FLAGS_GET_M(g->gflags)) + { + /* Advance to M */ + i++; + gbox->mmin = FP_MIN(dptr[i], dptr[i+ndims]); + gbox->mmax = FP_MAX(dptr[i], dptr[i+ndims]); + } + gbox_float_round(gbox); + return LW_SUCCESS; + } + /* We can also do single-entry multi-points */ + else if (type == MULTIPOINTTYPE) + { + int i = 0; /* Start at */ + int ngeoms = iptr[1]; /* Read the ngeoms */ + int npoints; + + /* This only works with single-entry multipoints */ + if (ngeoms != 1) + return LW_FAILURE; + + /* Npoints is at */ + npoints = iptr[3]; + + /* The check below is necessary because we can have a MULTIPOINT + * that contains a single, empty POINT (ngeoms = 1, npoints = 0) */ + if (npoints != 1) + return LW_FAILURE; + + /* Move forward two doubles (four ints) */ + /* Past */ + /* Past */ + i += 2; + + /* Read the doubles from the one point */ + gbox->xmin = gbox->xmax = dptr[i++]; + gbox->ymin = gbox->ymax = dptr[i++]; + gbox->flags = gserialized2_get_lwflags(g); + if (G2FLAGS_GET_Z(g->gflags)) + { + gbox->zmin = gbox->zmax = dptr[i++]; + } + if (G2FLAGS_GET_M(g->gflags)) + { + gbox->mmin = gbox->mmax = dptr[i++]; + } + gbox_float_round(gbox); + return LW_SUCCESS; + } + /* And we can do single-entry multi-lines with two vertices (!!!) */ + else if (type == MULTILINETYPE) + { + int ndims = G2FLAGS_NDIMS(g->gflags); + int i = 0; /* Start at */ + int ngeoms = iptr[1]; /* Read the ngeoms */ + int npoints; + + /* This only works with 1-line multilines */ + if (ngeoms != 1) + return LW_FAILURE; + + /* Npoints is at */ + npoints = iptr[3]; + + if (npoints != 2) + return LW_FAILURE; + + /* Advance to X */ + /* Move forward two doubles (four ints) */ + /* Past */ + /* Past */ + i += 2; + gbox->xmin = FP_MIN(dptr[i], dptr[i+ndims]); + gbox->xmax = FP_MAX(dptr[i], dptr[i+ndims]); + + /* Advance to Y */ + i++; + gbox->ymin = FP_MIN(dptr[i], dptr[i+ndims]); + gbox->ymax = FP_MAX(dptr[i], dptr[i+ndims]); + + gbox->flags = gserialized2_get_lwflags(g); + if (G2FLAGS_GET_Z(g->gflags)) + { + /* Advance to Z */ + i++; + gbox->zmin = FP_MIN(dptr[i], dptr[i+ndims]); + gbox->zmax = FP_MAX(dptr[i], dptr[i+ndims]); + } + if (G2FLAGS_GET_M(g->gflags)) + { + /* Advance to M */ + i++; + gbox->mmin = FP_MIN(dptr[i], dptr[i+ndims]); + gbox->mmax = FP_MAX(dptr[i], dptr[i+ndims]); + } + gbox_float_round(gbox); + return LW_SUCCESS; + } + + return LW_FAILURE; +} + +static inline void +gserialized2_copy_point(double *dptr, lwflags_t flags, POINT4D *out_point) +{ + uint8_t dim = 0; + out_point->x = dptr[dim++]; + out_point->y = dptr[dim++]; + + if (G2FLAGS_GET_Z(flags)) + { + out_point->z = dptr[dim++]; + } + if (G2FLAGS_GET_M(flags)) + { + out_point->m = dptr[dim]; + } +} + +int +gserialized2_peek_first_point(const GSERIALIZED *g, POINT4D *out_point) +{ + uint8_t *geometry_start = gserialized2_get_geometry_p(g); + + uint32_t isEmpty = (((uint32_t *)geometry_start)[1]) == 0; + if (isEmpty) + { + return LW_FAILURE; + } + + uint32_t type = (((uint32_t *)geometry_start)[0]); + /* Setup double_array_start depending on the geometry type */ + double *double_array_start = NULL; + switch (type) + { + case (POINTTYPE): + /* For points we only need to jump over the type and npoints 32b ints */ + double_array_start = (double *)(geometry_start + 2 * sizeof(uint32_t)); + break; + + default: + lwerror("%s is currently not implemented for type %d", __func__, type); + return LW_FAILURE; + } + + gserialized2_copy_point(double_array_start, g->gflags, out_point); + return LW_SUCCESS; +} + +/** +* Read the bounding box off a serialization and calculate one if +* it is not already there. +*/ +int gserialized2_get_gbox_p(const GSERIALIZED *g, GBOX *box) +{ + /* Try to just read the serialized box. */ + if (gserialized2_read_gbox_p(g, box) == LW_SUCCESS) + { + return LW_SUCCESS; + } + /* No box? Try to peek into simpler geometries and */ + /* derive a box without creating an lwgeom */ + else if (gserialized2_peek_gbox_p(g, box) == LW_SUCCESS) + { + return LW_SUCCESS; + } + /* Damn! Nothing for it but to create an lwgeom... */ + /* See http://trac.osgeo.org/postgis/ticket/1023 */ + else + { + LWGEOM *lwgeom = lwgeom_from_gserialized(g); + int ret = lwgeom_calculate_gbox(lwgeom, box); + gbox_float_round(box); + lwgeom_free(lwgeom); + return ret; + } +} + +/** +* Read the bounding box off a serialization and fail if +* it is not already there. +*/ +int gserialized2_fast_gbox_p(const GSERIALIZED *g, GBOX *box) +{ + /* Try to just read the serialized box. */ + if (gserialized2_read_gbox_p(g, box) == LW_SUCCESS) + { + return LW_SUCCESS; + } + /* No box? Try to peek into simpler geometries and */ + /* derive a box without creating an lwgeom */ + else if (gserialized2_peek_gbox_p(g, box) == LW_SUCCESS) + { + return LW_SUCCESS; + } + else + { + return LW_FAILURE; + } +} + + + + +/*********************************************************************** +* Calculate the GSERIALIZED size for an LWGEOM. +*/ + +/* Private functions */ + +static size_t gserialized2_from_any_size(const LWGEOM *geom); /* Local prototype */ + +static size_t gserialized2_from_lwpoint_size(const LWPOINT *point) +{ + size_t size = 4; /* Type number. */ + + assert(point); + + size += 4; /* Number of points (one or zero (empty)). */ + size += point->point->npoints * FLAGS_NDIMS(point->flags) * sizeof(double); + + LWDEBUGF(3, "point size = %d", size); + + return size; +} + +static size_t gserialized2_from_lwline_size(const LWLINE *line) +{ + size_t size = 4; /* Type number. */ + + assert(line); + + size += 4; /* Number of points (zero => empty). */ + size += line->points->npoints * FLAGS_NDIMS(line->flags) * sizeof(double); + + LWDEBUGF(3, "linestring size = %d", size); + + return size; +} + +static size_t gserialized2_from_lwtriangle_size(const LWTRIANGLE *triangle) +{ + size_t size = 4; /* Type number. */ + + assert(triangle); + + size += 4; /* Number of points (zero => empty). */ + size += triangle->points->npoints * FLAGS_NDIMS(triangle->flags) * sizeof(double); + + LWDEBUGF(3, "triangle size = %d", size); + + return size; +} + +static size_t gserialized2_from_lwpoly_size(const LWPOLY *poly) +{ + size_t size = 4; /* Type number. */ + uint32_t i = 0; + const size_t point_size = FLAGS_NDIMS(poly->flags) * sizeof(double); + + assert(poly); + + size += 4; /* Number of rings (zero => empty). */ + if (poly->nrings % 2) + size += 4; /* Padding to double alignment. */ + + for (i = 0; i < poly->nrings; i++) + { + size += 4; /* Number of points in ring. */ + size += poly->rings[i]->npoints * point_size; + } + + LWDEBUGF(3, "polygon size = %d", size); + + return size; +} + +static size_t gserialized2_from_lwcircstring_size(const LWCIRCSTRING *curve) +{ + size_t size = 4; /* Type number. */ + + assert(curve); + + size += 4; /* Number of points (zero => empty). */ + size += curve->points->npoints * FLAGS_NDIMS(curve->flags) * sizeof(double); + + LWDEBUGF(3, "circstring size = %d", size); + + return size; +} + +static size_t gserialized2_from_lwcollection_size(const LWCOLLECTION *col) +{ + size_t size = 4; /* Type number. */ + uint32_t i = 0; + + assert(col); + + size += 4; /* Number of sub-geometries (zero => empty). */ + + for (i = 0; i < col->ngeoms; i++) + { + size_t subsize = gserialized2_from_any_size(col->geoms[i]); + size += subsize; + LWDEBUGF(3, "lwcollection subgeom(%d) size = %d", i, subsize); + } + + LWDEBUGF(3, "lwcollection size = %d", size); + + return size; +} + +static size_t gserialized2_from_any_size(const LWGEOM *geom) +{ + LWDEBUGF(2, "Input type: %s", lwtype_name(geom->type)); + + switch (geom->type) + { + case POINTTYPE: + return gserialized2_from_lwpoint_size((LWPOINT *)geom); + case LINETYPE: + return gserialized2_from_lwline_size((LWLINE *)geom); + case POLYGONTYPE: + return gserialized2_from_lwpoly_size((LWPOLY *)geom); + case TRIANGLETYPE: + return gserialized2_from_lwtriangle_size((LWTRIANGLE *)geom); + case CIRCSTRINGTYPE: + return gserialized2_from_lwcircstring_size((LWCIRCSTRING *)geom); + case CURVEPOLYTYPE: + case COMPOUNDTYPE: + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTICURVETYPE: + case MULTIPOLYGONTYPE: + case MULTISURFACETYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + return gserialized2_from_lwcollection_size((LWCOLLECTION *)geom); + default: + lwerror("Unknown geometry type: %d - %s", geom->type, lwtype_name(geom->type)); + return 0; + } +} + +/* Public function */ + +size_t gserialized2_from_lwgeom_size(const LWGEOM *geom) +{ + size_t size = 8; /* Header overhead (varsize+flags+srid) */ + assert(geom); + + /* Reserve space for extended flags */ + if (lwflags_uses_extended_flags(geom->flags)) + size += 8; + + /* Reserve space for bounding box */ + if (geom->bbox) + size += gbox_serialized_size(geom->flags); + + size += gserialized2_from_any_size(geom); + LWDEBUGF(3, "%s size = %d", __func__, size); + + return size; +} + +/*********************************************************************** +* Serialize an LWGEOM into GSERIALIZED. +*/ + +/* Private functions */ + +static size_t gserialized2_from_lwgeom_any(const LWGEOM *geom, uint8_t *buf); + +static size_t gserialized2_from_lwpoint(const LWPOINT *point, uint8_t *buf) +{ + uint8_t *loc; + int ptsize = ptarray_point_size(point->point); + int type = POINTTYPE; + + assert(point); + assert(buf); + + if (FLAGS_GET_ZM(point->flags) != FLAGS_GET_ZM(point->point->flags)) + lwerror("Dimensions mismatch in lwpoint"); + + LWDEBUGF(2, "%s (%p, %p) called", __func__, point, buf); + + loc = buf; + + /* Write in the type. */ + memcpy(loc, &type, sizeof(uint32_t)); + loc += sizeof(uint32_t); + /* Write in the number of points (0 => empty). */ + memcpy(loc, &(point->point->npoints), sizeof(uint32_t)); + loc += sizeof(uint32_t); + + /* Copy in the ordinates. */ + if (point->point->npoints > 0) + { + memcpy(loc, getPoint_internal(point->point, 0), ptsize); + loc += ptsize; + } + + return (size_t)(loc - buf); +} + +static size_t gserialized2_from_lwline(const LWLINE *line, uint8_t *buf) +{ + uint8_t *loc; + int ptsize; + size_t size; + int type = LINETYPE; + + assert(line); + assert(buf); + + LWDEBUGF(2, "%s (%p, %p) called", __func__, line, buf); + + if (FLAGS_GET_Z(line->flags) != FLAGS_GET_Z(line->points->flags)) + lwerror("Dimensions mismatch in lwline"); + + ptsize = ptarray_point_size(line->points); + + loc = buf; + + /* Write in the type. */ + memcpy(loc, &type, sizeof(uint32_t)); + loc += sizeof(uint32_t); + + /* Write in the npoints. */ + memcpy(loc, &(line->points->npoints), sizeof(uint32_t)); + loc += sizeof(uint32_t); + + LWDEBUGF(3, "%s added npoints (%d)", __func__, line->points->npoints); + + /* Copy in the ordinates. */ + if (line->points->npoints > 0) + { + size = line->points->npoints * ptsize; + memcpy(loc, getPoint_internal(line->points, 0), size); + loc += size; + } + LWDEBUGF(3, "%s copied serialized_pointlist (%d bytes)", __func__, ptsize * line->points->npoints); + + return (size_t)(loc - buf); +} + +static size_t gserialized2_from_lwpoly(const LWPOLY *poly, uint8_t *buf) +{ + uint32_t i; + uint8_t *loc; + int ptsize; + int type = POLYGONTYPE; + + assert(poly); + assert(buf); + + LWDEBUGF(2, "%s called", __func__); + + ptsize = sizeof(double) * FLAGS_NDIMS(poly->flags); + loc = buf; + + /* Write in the type. */ + memcpy(loc, &type, sizeof(uint32_t)); + loc += sizeof(uint32_t); + + /* Write in the nrings. */ + memcpy(loc, &(poly->nrings), sizeof(uint32_t)); + loc += sizeof(uint32_t); + + /* Write in the npoints per ring. */ + for (i = 0; i < poly->nrings; i++) + { + memcpy(loc, &(poly->rings[i]->npoints), sizeof(uint32_t)); + loc += sizeof(uint32_t); + } + + /* Add in padding if necessary to remain double aligned. */ + if (poly->nrings % 2) + { + memset(loc, 0, sizeof(uint32_t)); + loc += sizeof(uint32_t); + } + + /* Copy in the ordinates. */ + for (i = 0; i < poly->nrings; i++) + { + POINTARRAY *pa = poly->rings[i]; + size_t pasize; + + if (FLAGS_GET_ZM(poly->flags) != FLAGS_GET_ZM(pa->flags)) + lwerror("Dimensions mismatch in lwpoly"); + + pasize = pa->npoints * ptsize; + if ( pa->npoints > 0 ) + memcpy(loc, getPoint_internal(pa, 0), pasize); + loc += pasize; + } + return (size_t)(loc - buf); +} + +static size_t gserialized2_from_lwtriangle(const LWTRIANGLE *triangle, uint8_t *buf) +{ + uint8_t *loc; + int ptsize; + size_t size; + int type = TRIANGLETYPE; + + assert(triangle); + assert(buf); + + LWDEBUGF(2, "%s (%p, %p) called", __func__, triangle, buf); + + if (FLAGS_GET_ZM(triangle->flags) != FLAGS_GET_ZM(triangle->points->flags)) + lwerror("Dimensions mismatch in lwtriangle"); + + ptsize = ptarray_point_size(triangle->points); + + loc = buf; + + /* Write in the type. */ + memcpy(loc, &type, sizeof(uint32_t)); + loc += sizeof(uint32_t); + + /* Write in the npoints. */ + memcpy(loc, &(triangle->points->npoints), sizeof(uint32_t)); + loc += sizeof(uint32_t); + + LWDEBUGF(3, "%s added npoints (%d)", __func__, triangle->points->npoints); + + /* Copy in the ordinates. */ + if (triangle->points->npoints > 0) + { + size = triangle->points->npoints * ptsize; + memcpy(loc, getPoint_internal(triangle->points, 0), size); + loc += size; + } + LWDEBUGF(3, "%s copied serialized_pointlist (%d bytes)", __func__, ptsize * triangle->points->npoints); + + return (size_t)(loc - buf); +} + +static size_t gserialized2_from_lwcircstring(const LWCIRCSTRING *curve, uint8_t *buf) +{ + uint8_t *loc; + int ptsize; + size_t size; + int type = CIRCSTRINGTYPE; + + assert(curve); + assert(buf); + + if (FLAGS_GET_ZM(curve->flags) != FLAGS_GET_ZM(curve->points->flags)) + lwerror("Dimensions mismatch in lwcircstring"); + + + ptsize = ptarray_point_size(curve->points); + loc = buf; + + /* Write in the type. */ + memcpy(loc, &type, sizeof(uint32_t)); + loc += sizeof(uint32_t); + + /* Write in the npoints. */ + memcpy(loc, &curve->points->npoints, sizeof(uint32_t)); + loc += sizeof(uint32_t); + + /* Copy in the ordinates. */ + if (curve->points->npoints > 0) + { + size = curve->points->npoints * ptsize; + memcpy(loc, getPoint_internal(curve->points, 0), size); + loc += size; + } + + return (size_t)(loc - buf); +} + +static size_t gserialized2_from_lwcollection(const LWCOLLECTION *coll, uint8_t *buf) +{ + size_t subsize = 0; + uint8_t *loc; + uint32_t i; + int type; + + assert(coll); + assert(buf); + + type = coll->type; + loc = buf; + + /* Write in the type. */ + memcpy(loc, &type, sizeof(uint32_t)); + loc += sizeof(uint32_t); + + /* Write in the number of subgeoms. */ + memcpy(loc, &coll->ngeoms, sizeof(uint32_t)); + loc += sizeof(uint32_t); + + /* Serialize subgeoms. */ + for (i = 0; i < coll->ngeoms; i++) + { + if (FLAGS_GET_ZM(coll->flags) != FLAGS_GET_ZM(coll->geoms[i]->flags)) + lwerror("Dimensions mismatch in lwcollection"); + subsize = gserialized2_from_lwgeom_any(coll->geoms[i], loc); + loc += subsize; + } + + return (size_t)(loc - buf); +} + +static size_t gserialized2_from_lwgeom_any(const LWGEOM *geom, uint8_t *buf) +{ + assert(geom); + assert(buf); + + LWDEBUGF(2, "Input type (%d) %s, hasz: %d hasm: %d", + geom->type, lwtype_name(geom->type), + FLAGS_GET_Z(geom->flags), FLAGS_GET_M(geom->flags)); + LWDEBUGF(2, "LWGEOM(%p) uint8_t(%p)", geom, buf); + + switch (geom->type) + { + case POINTTYPE: + return gserialized2_from_lwpoint((LWPOINT *)geom, buf); + case LINETYPE: + return gserialized2_from_lwline((LWLINE *)geom, buf); + case POLYGONTYPE: + return gserialized2_from_lwpoly((LWPOLY *)geom, buf); + case TRIANGLETYPE: + return gserialized2_from_lwtriangle((LWTRIANGLE *)geom, buf); + case CIRCSTRINGTYPE: + return gserialized2_from_lwcircstring((LWCIRCSTRING *)geom, buf); + case CURVEPOLYTYPE: + case COMPOUNDTYPE: + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTICURVETYPE: + case MULTIPOLYGONTYPE: + case MULTISURFACETYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + return gserialized2_from_lwcollection((LWCOLLECTION *)geom, buf); + default: + lwerror("Unknown geometry type: %d - %s", geom->type, lwtype_name(geom->type)); + return 0; + } + return 0; +} + +static size_t gserialized2_from_extended_flags(lwflags_t lwflags, uint8_t *buf) +{ + if (lwflags_uses_extended_flags(lwflags)) + { + uint64_t xflags = 0; + if (FLAGS_GET_SOLID(lwflags)) + xflags |= G2FLAG_X_SOLID; + + // G2FLAG_X_CHECKED_VALID + // G2FLAG_X_IS_VALID + // G2FLAG_X_HAS_HASH + + memcpy(buf, &xflags, sizeof(uint64_t)); + return sizeof(uint64_t); + } + return 0; +} + +static size_t gserialized2_from_gbox(const GBOX *gbox, uint8_t *buf) +{ + uint8_t *loc = buf; + float *f; + uint8_t i = 0; + size_t return_size; + + assert(buf); + + f = (float *)buf; + f[i++] = next_float_down(gbox->xmin); + f[i++] = next_float_up(gbox->xmax); + f[i++] = next_float_down(gbox->ymin); + f[i++] = next_float_up(gbox->ymax); + loc += 4 * sizeof(float); + + if (FLAGS_GET_GEODETIC(gbox->flags)) + { + f[i++] = next_float_down(gbox->zmin); + f[i++] = next_float_up(gbox->zmax); + loc += 2 * sizeof(float); + + return_size = (size_t)(loc - buf); + LWDEBUGF(4, "returning size %d", return_size); + return return_size; + } + + if (FLAGS_GET_Z(gbox->flags)) + { + f[i++] = next_float_down(gbox->zmin); + f[i++] = next_float_up(gbox->zmax); + loc += 2 * sizeof(float); + } + + if (FLAGS_GET_M(gbox->flags)) + { + f[i++] = next_float_down(gbox->mmin); + f[i++] = next_float_up(gbox->mmax); + loc += 2 * sizeof(float); + } + return_size = (size_t)(loc - buf); + LWDEBUGF(4, "returning size %d", return_size); + return return_size; +} + +/* Public function */ + +GSERIALIZED* gserialized2_from_lwgeom(LWGEOM *geom, size_t *size) +{ + size_t expected_size = 0; + size_t return_size = 0; + uint8_t *ptr = NULL; + GSERIALIZED *g = NULL; + assert(geom); + + /* + ** See if we need a bounding box, add one if we don't have one. + */ + if ((!geom->bbox) && lwgeom_needs_bbox(geom) && (!lwgeom_is_empty(geom))) + { + lwgeom_add_bbox(geom); + } + + /* + ** Harmonize the flags to the state of the lwgeom + */ + FLAGS_SET_BBOX(geom->flags, (geom->bbox ? 1 : 0)); + + /* Set up the uint8_t buffer into which we are going to write the serialized geometry. */ + expected_size = gserialized2_from_lwgeom_size(geom); + ptr = lwalloc(expected_size); + g = (GSERIALIZED*)(ptr); + + /* Set the SRID! */ + gserialized2_set_srid(g, geom->srid); + /* + ** We are aping PgSQL code here, PostGIS code should use + ** VARSIZE to set this for real. + */ + LWSIZE_SET(g->size, expected_size); + g->gflags = lwflags_get_g2flags(geom->flags); + + /* Move write head past size, srid and flags. */ + ptr += 8; + + /* Write in the extended flags if necessary */ + ptr += gserialized2_from_extended_flags(geom->flags, ptr); + + /* Write in the serialized form of the gbox, if necessary. */ + if (geom->bbox) + ptr += gserialized2_from_gbox(geom->bbox, ptr); + + /* Write in the serialized form of the geometry. */ + ptr += gserialized2_from_lwgeom_any(geom, ptr); + + /* Calculate size as returned by data processing functions. */ + return_size = ptr - (uint8_t*)g; + + assert(expected_size == return_size); + if (size) /* Return the output size to the caller if necessary. */ + *size = return_size; + + return g; +} + +/*********************************************************************** +* De-serialize GSERIALIZED into an LWGEOM. +*/ + +static LWGEOM *lwgeom_from_gserialized2_buffer(uint8_t *data_ptr, lwflags_t lwflags, size_t *size, int32_t srid); + +static LWPOINT * +lwpoint_from_gserialized2_buffer(uint8_t *data_ptr, lwflags_t lwflags, size_t *size, int32_t srid) +{ + uint8_t *start_ptr = data_ptr; + LWPOINT *point; + uint32_t npoints = 0; + + assert(data_ptr); + + point = (LWPOINT*)lwalloc(sizeof(LWPOINT)); + point->srid = srid; + point->bbox = NULL; + point->type = POINTTYPE; + point->flags = lwflags; + + data_ptr += 4; /* Skip past the type. */ + npoints = gserialized2_get_uint32_t(data_ptr); /* Zero => empty geometry */ + data_ptr += 4; /* Skip past the npoints. */ + + if (npoints > 0) + point->point = ptarray_construct_reference_data(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), 1, data_ptr); + else + point->point = ptarray_construct(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), 0); /* Empty point */ + + data_ptr += npoints * FLAGS_NDIMS(lwflags) * sizeof(double); + + if (size) + *size = data_ptr - start_ptr; + + return point; +} + +static LWLINE * +lwline_from_gserialized2_buffer(uint8_t *data_ptr, lwflags_t lwflags, size_t *size, int32_t srid) +{ + uint8_t *start_ptr = data_ptr; + LWLINE *line; + uint32_t npoints = 0; + + assert(data_ptr); + + line = (LWLINE*)lwalloc(sizeof(LWLINE)); + line->srid = srid; + line->bbox = NULL; + line->type = LINETYPE; + line->flags = lwflags; + + data_ptr += 4; /* Skip past the type. */ + npoints = gserialized2_get_uint32_t(data_ptr); /* Zero => empty geometry */ + data_ptr += 4; /* Skip past the npoints. */ + + if (npoints > 0) + line->points = ptarray_construct_reference_data(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), npoints, data_ptr); + + else + line->points = ptarray_construct(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), 0); /* Empty linestring */ + + data_ptr += FLAGS_NDIMS(lwflags) * npoints * sizeof(double); + + if (size) + *size = data_ptr - start_ptr; + + return line; +} + +static LWPOLY * +lwpoly_from_gserialized2_buffer(uint8_t *data_ptr, lwflags_t lwflags, size_t *size, int32_t srid) +{ + uint8_t *start_ptr = data_ptr; + LWPOLY *poly; + uint8_t *ordinate_ptr; + uint32_t nrings = 0; + uint32_t i = 0; + + assert(data_ptr); + + poly = (LWPOLY*)lwalloc(sizeof(LWPOLY)); + poly->srid = srid; + poly->bbox = NULL; + poly->type = POLYGONTYPE; + poly->flags = lwflags; + + data_ptr += 4; /* Skip past the polygontype. */ + nrings = gserialized2_get_uint32_t(data_ptr); /* Zero => empty geometry */ + poly->nrings = nrings; + LWDEBUGF(4, "nrings = %d", nrings); + data_ptr += 4; /* Skip past the nrings. */ + + ordinate_ptr = data_ptr; /* Start the ordinate pointer. */ + if (nrings > 0) + { + poly->rings = (POINTARRAY**)lwalloc( sizeof(POINTARRAY*) * nrings ); + poly->maxrings = nrings; + ordinate_ptr += nrings * 4; /* Move past all the npoints values. */ + if (nrings % 2) /* If there is padding, move past that too. */ + ordinate_ptr += 4; + } + else /* Empty polygon */ + { + poly->rings = NULL; + poly->maxrings = 0; + } + + for (i = 0; i < nrings; i++) + { + uint32_t npoints = 0; + + /* Read in the number of points. */ + npoints = gserialized2_get_uint32_t(data_ptr); + data_ptr += 4; + + /* Make a point array for the ring, and move the ordinate pointer past the ring ordinates. */ + poly->rings[i] = ptarray_construct_reference_data(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), npoints, ordinate_ptr); + + ordinate_ptr += sizeof(double) * FLAGS_NDIMS(lwflags) * npoints; + } + + if (size) + *size = ordinate_ptr - start_ptr; + + return poly; +} + +static LWTRIANGLE * +lwtriangle_from_gserialized2_buffer(uint8_t *data_ptr, lwflags_t lwflags, size_t *size, int32_t srid) +{ + uint8_t *start_ptr = data_ptr; + LWTRIANGLE *triangle; + uint32_t npoints = 0; + + assert(data_ptr); + + triangle = (LWTRIANGLE*)lwalloc(sizeof(LWTRIANGLE)); + triangle->srid = srid; /* Default */ + triangle->bbox = NULL; + triangle->type = TRIANGLETYPE; + triangle->flags = lwflags; + + data_ptr += 4; /* Skip past the type. */ + npoints = gserialized2_get_uint32_t(data_ptr); /* Zero => empty geometry */ + data_ptr += 4; /* Skip past the npoints. */ + + if (npoints > 0) + triangle->points = ptarray_construct_reference_data(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), npoints, data_ptr); + else + triangle->points = ptarray_construct(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), 0); /* Empty triangle */ + + data_ptr += FLAGS_NDIMS(lwflags) * npoints * sizeof(double); + + if (size) + *size = data_ptr - start_ptr; + + return triangle; +} + +static LWCIRCSTRING * +lwcircstring_from_gserialized2_buffer(uint8_t *data_ptr, lwflags_t lwflags, size_t *size, int32_t srid) +{ + uint8_t *start_ptr = data_ptr; + LWCIRCSTRING *circstring; + uint32_t npoints = 0; + + assert(data_ptr); + + circstring = (LWCIRCSTRING*)lwalloc(sizeof(LWCIRCSTRING)); + circstring->srid = srid; + circstring->bbox = NULL; + circstring->type = CIRCSTRINGTYPE; + circstring->flags = lwflags; + + data_ptr += 4; /* Skip past the circstringtype. */ + npoints = gserialized2_get_uint32_t(data_ptr); /* Zero => empty geometry */ + data_ptr += 4; /* Skip past the npoints. */ + + if (npoints > 0) + circstring->points = ptarray_construct_reference_data(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), npoints, data_ptr); + else + circstring->points = ptarray_construct(FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), 0); /* Empty circularstring */ + + data_ptr += FLAGS_NDIMS(lwflags) * npoints * sizeof(double); + + if (size) + *size = data_ptr - start_ptr; + + return circstring; +} + +static LWCOLLECTION * +lwcollection_from_gserialized2_buffer(uint8_t *data_ptr, lwflags_t lwflags, size_t *size, int32_t srid) +{ + uint32_t type; + uint8_t *start_ptr = data_ptr; + LWCOLLECTION *collection; + uint32_t ngeoms = 0; + uint32_t i = 0; + + assert(data_ptr); + + type = gserialized2_get_uint32_t(data_ptr); + data_ptr += 4; /* Skip past the type. */ + + collection = (LWCOLLECTION*)lwalloc(sizeof(LWCOLLECTION)); + collection->srid = srid; + collection->bbox = NULL; + collection->type = type; + collection->flags = lwflags; + + ngeoms = gserialized2_get_uint32_t(data_ptr); + collection->ngeoms = ngeoms; /* Zero => empty geometry */ + data_ptr += 4; /* Skip past the ngeoms. */ + + if (ngeoms > 0) + { + collection->geoms = lwalloc(sizeof(LWGEOM*) * ngeoms); + collection->maxgeoms = ngeoms; + } + else + { + collection->geoms = NULL; + collection->maxgeoms = 0; + } + + /* Sub-geometries are never de-serialized with boxes (#1254) */ + FLAGS_SET_BBOX(lwflags, 0); + + for (i = 0; i < ngeoms; i++) + { + uint32_t subtype = gserialized2_get_uint32_t(data_ptr); + size_t subsize = 0; + + if (!lwcollection_allows_subtype(type, subtype)) + { + lwerror("Invalid subtype (%s) for collection type (%s)", lwtype_name(subtype), lwtype_name(type)); + lwfree(collection); + return NULL; + } + collection->geoms[i] = lwgeom_from_gserialized2_buffer(data_ptr, lwflags, &subsize, srid); + data_ptr += subsize; + } + + if (size) + *size = data_ptr - start_ptr; + + return collection; +} + +LWGEOM * +lwgeom_from_gserialized2_buffer(uint8_t *data_ptr, lwflags_t lwflags, size_t *g_size, int32_t srid) +{ + uint32_t type; + + assert(data_ptr); + + type = gserialized2_get_uint32_t(data_ptr); + + LWDEBUGF(2, "Got type %d (%s), hasz=%d hasm=%d geodetic=%d hasbox=%d", type, lwtype_name(type), + FLAGS_GET_Z(lwflags), FLAGS_GET_M(lwflags), FLAGS_GET_GEODETIC(lwflags), FLAGS_GET_BBOX(lwflags)); + + switch (type) + { + case POINTTYPE: + return (LWGEOM *)lwpoint_from_gserialized2_buffer(data_ptr, lwflags, g_size, srid); + case LINETYPE: + return (LWGEOM *)lwline_from_gserialized2_buffer(data_ptr, lwflags, g_size, srid); + case CIRCSTRINGTYPE: + return (LWGEOM *)lwcircstring_from_gserialized2_buffer(data_ptr, lwflags, g_size, srid); + case POLYGONTYPE: + return (LWGEOM *)lwpoly_from_gserialized2_buffer(data_ptr, lwflags, g_size, srid); + case TRIANGLETYPE: + return (LWGEOM *)lwtriangle_from_gserialized2_buffer(data_ptr, lwflags, g_size, srid); + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COMPOUNDTYPE: + case CURVEPOLYTYPE: + case MULTICURVETYPE: + case MULTISURFACETYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + return (LWGEOM *)lwcollection_from_gserialized2_buffer(data_ptr, lwflags, g_size, srid); + default: + lwerror("Unknown geometry type: %d - %s", type, lwtype_name(type)); + return NULL; + } +} + +LWGEOM* lwgeom_from_gserialized2(const GSERIALIZED *g) +{ + lwflags_t lwflags = 0; + int32_t srid = 0; + uint32_t lwtype = 0; + uint8_t *data_ptr = NULL; + LWGEOM *lwgeom = NULL; + GBOX bbox; + size_t size = 0; + + assert(g); + + srid = gserialized2_get_srid(g); + lwtype = gserialized2_get_type(g); + lwflags = gserialized2_get_lwflags(g); + + LWDEBUGF(4, "Got type %d (%s), srid=%d", lwtype, lwtype_name(lwtype), srid); + + data_ptr = (uint8_t*)g->data; + + /* Skip optional flags */ + if (G2FLAGS_GET_EXTENDED(g->gflags)) + { + data_ptr += sizeof(uint64_t); + } + + /* Skip over optional bounding box */ + if (FLAGS_GET_BBOX(lwflags)) + data_ptr += gbox_serialized_size(lwflags); + + lwgeom = lwgeom_from_gserialized2_buffer(data_ptr, lwflags, &size, srid); + + if (!lwgeom) + lwerror("%s: unable create geometry", __func__); /* Ooops! */ + + lwgeom->type = lwtype; + lwgeom->flags = lwflags; + + if (gserialized2_read_gbox_p(g, &bbox) == LW_SUCCESS) + { + lwgeom->bbox = gbox_copy(&bbox); + } + else if (lwgeom_needs_bbox(lwgeom) && (lwgeom_calculate_gbox(lwgeom, &bbox) == LW_SUCCESS)) + { + lwgeom->bbox = gbox_copy(&bbox); + } + else + { + lwgeom->bbox = NULL; + } + + return lwgeom; +} + +/** +* Update the bounding box of a #GSERIALIZED, allocating a fresh one +* if there is not enough space to just write the new box in. +* WARNING if a new object needs to be created, the +* input pointer will have to be freed by the caller! Check +* to see if input == output. Returns null if there's a problem +* like mismatched dimensions. +*/ +GSERIALIZED* gserialized2_set_gbox(GSERIALIZED *g, GBOX *gbox) +{ + + int g_ndims = G2FLAGS_NDIMS_BOX(g->gflags); + int box_ndims = FLAGS_NDIMS_BOX(gbox->flags); + GSERIALIZED *g_out = NULL; + size_t box_size = 2 * g_ndims * sizeof(float); + float *fbox; + int fbox_pos = 0; + + /* The dimensionality of the inputs has to match or we are SOL. */ + if (g_ndims != box_ndims) + { + return NULL; + } + + /* Serialized already has room for a box. */ + if (G2FLAGS_GET_BBOX(g->gflags)) + { + g_out = g; + } + /* Serialized has no box. We need to allocate enough space for the old + data plus the box, and leave a gap in the memory segment to write + the new values into. + */ + else + { + size_t varsize_in = LWSIZE_GET(g->size); + size_t varsize_out = varsize_in + box_size; + uint8_t *ptr_out, *ptr_in, *ptr; + g_out = lwalloc(varsize_out); + ptr_out = (uint8_t*)g_out; + ptr = ptr_in = (uint8_t*)g; + /* Copy the head of g into place */ + memcpy(ptr_out, ptr_in, 8); ptr_out += 8; ptr_in += 8; + /* Optionally copy extended bit into place */ + if (G2FLAGS_GET_EXTENDED(g->gflags)) + { + memcpy(ptr_out, ptr_in, 8); ptr_out += 8; ptr_in += 8; + } + /* Copy the body of g into place after leaving space for the box */ + ptr_out += box_size; + memcpy(ptr_out, ptr_in, varsize_in - (ptr_in - ptr)); + G2FLAGS_SET_BBOX(g_out->gflags, 1); + LWSIZE_SET(g_out->size, varsize_out); + } + + /* Move bounds to nearest float values */ + gbox_float_round(gbox); + /* Now write the float box values into the memory segement */ + fbox = (float*)(g_out->data); + /* Copy in X/Y */ + fbox[fbox_pos++] = gbox->xmin; + fbox[fbox_pos++] = gbox->xmax; + fbox[fbox_pos++] = gbox->ymin; + fbox[fbox_pos++] = gbox->ymax; + /* Optionally copy in higher dims */ + if(gserialized2_has_z(g) || gserialized2_is_geodetic(g)) + { + fbox[fbox_pos++] = gbox->zmin; + fbox[fbox_pos++] = gbox->zmax; + } + if(gserialized2_has_m(g) && ! gserialized2_is_geodetic(g)) + { + fbox[fbox_pos++] = gbox->mmin; + fbox[fbox_pos++] = gbox->mmax; + } + + return g_out; +} + + +/** +* Remove the bounding box from a #GSERIALIZED. Returns a freshly +* allocated #GSERIALIZED every time. +*/ +GSERIALIZED* gserialized2_drop_gbox(GSERIALIZED *g) +{ + int g_ndims = G2FLAGS_NDIMS_BOX(g->gflags); + size_t box_size = 2 * g_ndims * sizeof(float); + size_t g_out_size = LWSIZE_GET(g->size) - box_size; + GSERIALIZED *g_out = lwalloc(g_out_size); + + /* Copy the contents while omitting the box */ + if (G2FLAGS_GET_BBOX(g->gflags)) + { + uint8_t *outptr = (uint8_t*)g_out; + uint8_t *inptr = (uint8_t*)g; + /* Copy the header (size+type) of g into place */ + memcpy(outptr, inptr, 8); outptr += 8; inptr += 8; + /* Copy extended flags, if there are any */ + if (G2FLAGS_GET_EXTENDED(g->gflags)) + { + memcpy(outptr, inptr, 8); outptr += 8; inptr += 8; + } + /* Advance past box */ + inptr += box_size; + /* Copy parts after the box into place */ + memcpy(outptr, inptr, g_out_size - 8); + G2FLAGS_SET_BBOX(g_out->gflags, 0); + LWSIZE_SET(g_out->size, g_out_size); + } + /* No box? Nothing to do but copy and return. */ + else + { + memcpy(g_out, g, g_out_size); + } + + return g_out; +} diff --git a/mgist-postgis/liblwgeom/gserialized2.h b/mgist-postgis/liblwgeom/gserialized2.h new file mode 100644 index 0000000..c703722 --- /dev/null +++ b/mgist-postgis/liblwgeom/gserialized2.h @@ -0,0 +1,175 @@ +/** +* Macros for manipulating the core 'flags' byte. A uint8_t used as follows: +*/ +#define G2FLAG_Z 0x01 +#define G2FLAG_M 0x02 +#define G2FLAG_BBOX 0x04 +#define G2FLAG_GEODETIC 0x08 +#define G2FLAG_EXTENDED 0x10 +#define G2FLAG_RESERVED1 0x20 /* RESERVED FOR FUTURE USES */ +#define G2FLAG_VER_0 0x40 +#define G2FLAG_RESERVED2 0x80 /* RESERVED FOR FUTURE VERSIONS */ + +/** +* Macros for the extended 'flags' uint64_t. +*/ +#define G2FLAG_X_SOLID 0x00000001 +#define G2FLAG_X_CHECKED_VALID 0x00000002 // To Be Implemented? +#define G2FLAG_X_IS_VALID 0x00000004 // To Be Implemented? +#define G2FLAG_X_HAS_HASH 0x00000008 // To Be Implemented? + +#define G2FLAGS_GET_VERSION(gflags) (((gflags) & G2FLAG_VER_0)>>6) +#define G2FLAGS_GET_Z(gflags) ((gflags) & G2FLAG_Z) +#define G2FLAGS_GET_M(gflags) (((gflags) & G2FLAG_M)>>1) +#define G2FLAGS_GET_BBOX(gflags) (((gflags) & G2FLAG_BBOX)>>2) +#define G2FLAGS_GET_GEODETIC(gflags) (((gflags) & G2FLAG_GEODETIC)>>3) +#define G2FLAGS_GET_EXTENDED(gflags) (((gflags) & G2FLAG_EXTENDED)>>4) +#define G2FLAGS_GET_UNUSED(gflags) (((gflags) & G2FLAG_UNUSED)>>5) + +#define G2FLAGS_SET_Z(gflags, value) ((gflags) = (value) ? ((gflags) | G2FLAG_Z) : ((gflags) & ~G2FLAG_Z)) +#define G2FLAGS_SET_M(gflags, value) ((gflags) = (value) ? ((gflags) | G2FLAG_M) : ((gflags) & ~G2FLAG_M)) +#define G2FLAGS_SET_BBOX(gflags, value) ((gflags) = (value) ? ((gflags) | G2FLAG_BBOX) : ((gflags) & ~G2FLAG_BBOX)) +#define G2FLAGS_SET_GEODETIC(gflags, value) ((gflags) = (value) ? ((gflags) | G2FLAG_GEODETIC) : ((gflags) & ~G2FLAG_GEODETIC)) +#define G2FLAGS_SET_EXTENDED(gflags, value) ((gflags) = (value) ? ((gflags) | G2FLAG_EXTENDED) : ((gflags) & ~G2FLAG_EXTENDED)) +#define G2FLAGS_SET_VERSION(gflags, value) ((gflags) = (value) ? ((gflags) | G2FLAG_VER_0) : ((gflags) & ~G2FLAG_VER_0)) + +#define G2FLAGS_NDIMS(gflags) (2 + G2FLAGS_GET_Z(gflags) + G2FLAGS_GET_M(gflags)) +#define G2FLAGS_GET_ZM(gflags) (G2FLAGS_GET_M(gflags) + G2FLAGS_GET_Z(gflags) * 2) +#define G2FLAGS_NDIMS_BOX(gflags) (G2FLAGS_GET_GEODETIC(gflags) ? 3 : G2FLAGS_NDIMS(gflags)) + +uint8_t g2flags(int has_z, int has_m, int is_geodetic); +uint8_t lwflags_get_g2flags(lwflags_t lwflags); + +/* +* GSERIALIZED PUBLIC API +*/ + +/** +* Read the flags from a #GSERIALIZED and return a standard lwflag +* integer +*/ +lwflags_t gserialized2_get_lwflags(const GSERIALIZED *g); + +/** +* Copy a new bounding box into an existing gserialized. +* If necessary a new #GSERIALIZED will be allocated. Test +* that input != output before freeing input. +*/ +GSERIALIZED *gserialized2_set_gbox(GSERIALIZED *g, GBOX *gbox); + +/** +* Remove the bounding box from a #GSERIALIZED. Returns a freshly +* allocated #GSERIALIZED every time. +*/ +GSERIALIZED* gserialized2_drop_gbox(GSERIALIZED *g); + +/** +* Read the box from the #GSERIALIZED or calculate it if necessary. +* Return #LWFAILURE if box cannot be calculated (NULL or EMPTY +* input). +*/ +int gserialized2_get_gbox_p(const GSERIALIZED *g, GBOX *gbox); + +/** +* Read the box from the #GSERIALIZED or return #LWFAILURE if +* box is unavailable. +*/ +int gserialized2_fast_gbox_p(const GSERIALIZED *g, GBOX *gbox); + +/** +* Extract the geometry type from the serialized form (it hides in +* the anonymous data area, so this is a handy function). +*/ +uint32_t gserialized2_get_type(const GSERIALIZED *g); + +/** +* Returns the size in bytes to read from toast to get the basic +* information from a geometry: GSERIALIZED struct, bbox and type +*/ +uint32_t gserialized2_max_header_size(void); + +/** +* Returns a hash code for the srid/type/geometry information +* in the GSERIALIZED. Ignores metadata like flags and optional +* boxes, etc. +*/ +int32_t gserialized2_hash(const GSERIALIZED *g); + +/** +* Extract the SRID from the serialized form (it is packed into +* three bytes so this is a handy function). +*/ +int32_t gserialized2_get_srid(const GSERIALIZED *g); + +/** +* Write the SRID into the serialized form (it is packed into +* three bytes so this is a handy function). +*/ +void gserialized2_set_srid(GSERIALIZED *g, int32_t srid); + +/** +* Check if a #GSERIALIZED is empty without deserializing first. +* Only checks if the number of elements of the parent geometry +* is zero, will not catch collections of empty, eg: +* GEOMETRYCOLLECTION(POINT EMPTY) +*/ +int gserialized2_is_empty(const GSERIALIZED *g); + +/** +* Check if a #GSERIALIZED has a bounding box without deserializing first. +*/ +int gserialized2_has_bbox(const GSERIALIZED *gser); + +/** +* Check if a #GSERIALIZED has an extended flags section. +*/ +int gserialized2_has_extended(const GSERIALIZED *g); + +/** +* Check if a #GSERIALIZED has a Z ordinate. +*/ +int gserialized2_has_z(const GSERIALIZED *gser); + +/** +* Check if a #GSERIALIZED has an M ordinate. +*/ +int gserialized2_has_m(const GSERIALIZED *gser); + +/** +* Check if a #GSERIALIZED is a geography. +*/ +int gserialized2_is_geodetic(const GSERIALIZED *gser); + +/** +* Return the number of dimensions (2, 3, 4) in a geometry +*/ +int gserialized2_ndims(const GSERIALIZED *gser); + +/** +* Allocate a new #GSERIALIZED from an #LWGEOM. For all non-point types, a bounding +* box will be calculated and embedded in the serialization. The geodetic flag is used +* to control the box calculation (cartesian or geocentric). If set, the size pointer +* will contain the size of the final output, which is useful for setting the PgSQL +* VARSIZE information. +*/ +GSERIALIZED* gserialized2_from_lwgeom(LWGEOM *geom, size_t *size); + +/** +* Return the memory size a GSERIALIZED will occupy for a given LWGEOM. +*/ +size_t gserialized2_from_lwgeom_size(const LWGEOM *geom); + +/** +* Allocate a new #LWGEOM from a #GSERIALIZED. The resulting #LWGEOM will have coordinates +* that are double aligned and suitable for direct reading using getPoint2d_cp +*/ +LWGEOM* lwgeom_from_gserialized2(const GSERIALIZED *g); + +/** +* Point into the float box area of the serialization +*/ +const float * gserialized2_get_float_box_p(const GSERIALIZED *g, size_t *ndims); + +int gserialized2_peek_gbox_p(const GSERIALIZED *g, GBOX *gbox); + +int gserialized2_peek_first_point(const GSERIALIZED *g, POINT4D *out_point); diff --git a/mgist-postgis/liblwgeom/liblwgeom.h b/mgist-postgis/liblwgeom/liblwgeom.h new file mode 100644 index 0000000..37246f4 --- /dev/null +++ b/mgist-postgis/liblwgeom/liblwgeom.h @@ -0,0 +1,2634 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2011 Sandro Santilli + * Copyright 2011 Paul Ramsey + * Copyright 2007-2008 Mark Cave-Ayland + * Copyright 2001-2006 Refractions Research Inc. + * + **********************************************************************/ + + +#ifndef _LIBLWGEOM_H +#define _LIBLWGEOM_H 1 + +/* Manually added to avoid using postgis_config.h*/ +#define POSTGIS_GEOS_VERSION 31002 +#define POSTGIS_DEBUG_LEVEL 0 + +#include +#include +#include +#include + +// #include "../postgis_config.h" + +#include "proj.h" + +/* For PROJ6 we cache several extra values to avoid calls to proj_get_source_crs + * or proj_get_target_crs since those are very costly + */ +typedef struct LWPROJ +{ + PJ* pj; + + /* for pipeline transforms, whether to do a forward or inverse */ + bool pipeline_is_forward; + + /* Source crs is geographic: Used in geography calls (source srid == dst srid) */ + uint8_t source_is_latlong; + /* Source ellipsoid parameters */ + double source_semi_major_metre; + double source_semi_minor_metre; +} LWPROJ; + + + +/* Enable new geodesic functions API */ +#define PROJ_GEODESIC + +/** +* @file liblwgeom.h +* +* This library is the generic geometry handling section of PostGIS. The geometry +* objects, constructors, destructors, and a set of spatial processing functions, +* are implemented here. +* +* The library is designed for use in non-PostGIS applications if necessary. The +* units tests at cunit/cu_tester.c and the loader/dumper programs at +* ../loader/shp2pgsql.c are examples of non-PostGIS applications using liblwgeom. +* +* Programs using this library can install their custom memory managers and error +* handlers by calling the lwgeom_set_handlers() function, otherwise the default +* ones will be used. +*/ + +/** + * liblwgeom versions + */ +#define LIBLWGEOM_VERSION "3.4.2" +#define LIBLWGEOM_VERSION_MAJOR "" +#define LIBLWGEOM_VERSION_MINOR "" +#define LIBLWGEOM_GEOS_VERSION "31002" + +/** Return lwgeom version string (not to be freed) */ +const char* lwgeom_version(void); + +/** +* Return types for functions with status returns. +*/ +#define LW_TRUE 1 +#define LW_FALSE 0 +#define LW_UNKNOWN 2 +#define LW_FAILURE 0 +#define LW_SUCCESS 1 + +/** +* LWTYPE numbers, used internally by PostGIS +*/ +#define POINTTYPE 1 +#define LINETYPE 2 +#define POLYGONTYPE 3 +#define MULTIPOINTTYPE 4 +#define MULTILINETYPE 5 +#define MULTIPOLYGONTYPE 6 +#define COLLECTIONTYPE 7 +#define CIRCSTRINGTYPE 8 +#define COMPOUNDTYPE 9 +#define CURVEPOLYTYPE 10 +#define MULTICURVETYPE 11 +#define MULTISURFACETYPE 12 +#define POLYHEDRALSURFACETYPE 13 +#define TRIANGLETYPE 14 +#define TINTYPE 15 + +#define NUMTYPES 16 + +/** +* Flags applied in EWKB to indicate Z/M dimensions and +* presence/absence of SRID and bounding boxes +*/ +#define WKBZOFFSET 0x80000000 +#define WKBMOFFSET 0x40000000 +#define WKBSRIDFLAG 0x20000000 +#define WKBBBOXFLAG 0x10000000 + +/** Ordinate names */ +typedef enum LWORD_T { + LWORD_X = 0, + LWORD_Y = 1, + LWORD_Z = 2, + LWORD_M = 3 +} LWORD; + +/********************************************************************** +** Spherical radius. +** Moritz, H. (1980). Geodetic Reference System 1980, by resolution of +** the XVII General Assembly of the IUGG in Canberra. +** http://en.wikipedia.org/wiki/Earth_radius +** http://en.wikipedia.org/wiki/World_Geodetic_System +*/ + +#define WGS84_MAJOR_AXIS 6378137.0 +#define WGS84_INVERSE_FLATTENING 298.257223563 +#define WGS84_MINOR_AXIS (WGS84_MAJOR_AXIS - WGS84_MAJOR_AXIS / WGS84_INVERSE_FLATTENING) +#define WGS84_RADIUS ((2.0 * WGS84_MAJOR_AXIS + WGS84_MINOR_AXIS ) / 3.0) +#define WGS84_SRID 4326 + + +/** +* Macros for manipulating the 'flags' byte. A uint8_t used as follows: +* VVSRGBMZ +* Version bit, followed by +* Validty, Solid, ReadOnly, Geodetic, HasBBox, HasM and HasZ flags. +*/ +#define LWFLAG_Z 0x01 +#define LWFLAG_M 0x02 +#define LWFLAG_BBOX 0x04 +#define LWFLAG_GEODETIC 0x08 +#define LWFLAG_READONLY 0x10 +#define LWFLAG_SOLID 0x20 + +#define FLAGS_GET_Z(flags) ((flags) & LWFLAG_Z) +#define FLAGS_GET_M(flags) (((flags) & LWFLAG_M)>>1) +#define FLAGS_GET_BBOX(flags) (((flags) & LWFLAG_BBOX)>>2) +#define FLAGS_GET_GEODETIC(flags) (((flags) & LWFLAG_GEODETIC)>>3) +#define FLAGS_GET_READONLY(flags) (((flags) & LWFLAG_READONLY)>>4) +#define FLAGS_GET_SOLID(flags) (((flags) & LWFLAG_SOLID)>>5) + +#define FLAGS_SET_Z(flags, value) ((flags) = (value) ? ((flags) | LWFLAG_Z) : ((flags) & ~LWFLAG_Z)) +#define FLAGS_SET_M(flags, value) ((flags) = (value) ? ((flags) | LWFLAG_M) : ((flags) & ~LWFLAG_M)) +#define FLAGS_SET_BBOX(flags, value) ((flags) = (value) ? ((flags) | LWFLAG_BBOX) : ((flags) & ~LWFLAG_BBOX)) +#define FLAGS_SET_GEODETIC(flags, value) ((flags) = (value) ? ((flags) | LWFLAG_GEODETIC) : ((flags) & ~LWFLAG_GEODETIC)) +#define FLAGS_SET_READONLY(flags, value) ((flags) = (value) ? ((flags) | LWFLAG_READONLY) : ((flags) & ~LWFLAG_READONLY)) +#define FLAGS_SET_SOLID(flags, value) ((flags) = (value) ? ((flags) | LWFLAG_SOLID) : ((flags) & ~LWFLAG_SOLID)) + +#define FLAGS_NDIMS(flags) (2 + FLAGS_GET_Z(flags) + FLAGS_GET_M(flags)) +#define FLAGS_GET_ZM(flags) (FLAGS_GET_M(flags) + FLAGS_GET_Z(flags) * 2) +#define FLAGS_NDIMS_BOX(flags) (FLAGS_GET_GEODETIC(flags) ? 3 : FLAGS_NDIMS(flags)) + +/** +* Macros for manipulating the 'typemod' int. An int32_t used as follows: +* Plus/minus = Top bit. +* Spare bits = Next 2 bits. +* SRID = Next 21 bits. +* TYPE = Next 6 bits. +* ZM Flags = Bottom 2 bits. +*/ + +#define TYPMOD_GET_SRID(typmod) ((((typmod) & 0x0FFFFF00) - ((typmod) & 0x10000000)) >> 8) +#define TYPMOD_SET_SRID(typmod, srid) ((typmod) = (((typmod) & 0xE00000FF) | ((srid & 0x001FFFFF)<<8))) +#define TYPMOD_GET_TYPE(typmod) ((typmod & 0x000000FC)>>2) +#define TYPMOD_SET_TYPE(typmod, type) ((typmod) = (typmod & 0xFFFFFF03) | ((type & 0x0000003F)<<2)) +#define TYPMOD_GET_Z(typmod) ((typmod & 0x00000002)>>1) +#define TYPMOD_SET_Z(typmod) ((typmod) = typmod | 0x00000002) +#define TYPMOD_GET_M(typmod) (typmod & 0x00000001) +#define TYPMOD_SET_M(typmod) ((typmod) = typmod | 0x00000001) +#define TYPMOD_GET_NDIMS(typmod) (2+TYPMOD_GET_Z(typmod)+TYPMOD_GET_M(typmod)) + +/** +* Maximum allowed SRID value in serialized geometry. +* Currently we are using 21 bits (2097152) of storage for SRID. +*/ +#define SRID_MAXIMUM 999999 + +/** + * Maximum valid SRID value for the user + * We reserve 1000 values for internal use + */ +#define SRID_USER_MAXIMUM 998999 + +/** Unknown SRID value */ +#define SRID_UNKNOWN 0 +#define SRID_IS_UNKNOWN(x) ((int)x<=0) + +/* Invalid SRID value, for internal use */ +#define SRID_INVALID (999999 + 2) + +/* +** EPSG WGS84 geographics, OGC standard default SRS, better be in +** the SPATIAL_REF_SYS table! +*/ +#define SRID_DEFAULT 4326 + +#ifndef __GNUC__ +# define __attribute__(x) +#endif + +/** +* Return a valid SRID from an arbitrary integer +* Raises a notice if what comes out is different from +* what went in. +* Raises an error if SRID value is out of bounds. +*/ +extern int32_t clamp_srid(int32_t srid); + +/** + * Global functions for memory/logging handlers. + */ +typedef void* (*lwallocator)(size_t size); +typedef void* (*lwreallocator)(void *mem, size_t size); +typedef void (*lwfreeor)(void* mem); +typedef void (*lwreporter)(const char* fmt, va_list ap) + __attribute__ (( format(printf, 1, 0) )); +typedef void (*lwdebuglogger)(int level, const char* fmt, va_list ap) + __attribute__ (( format(printf, 2,0) )); + +/** +* Install custom memory management and error handling functions you want your +* application to use. +* @ingroup system +* @todo take a structure ? +*/ +extern void lwgeom_set_handlers(lwallocator allocator, + lwreallocator reallocator, lwfreeor freeor, lwreporter errorreporter, + lwreporter noticereporter); + +extern void lwgeom_set_debuglogger(lwdebuglogger debuglogger); + +/** + * Request interruption of any running code + * + * Safe for use from signal handlers + * + * Interrupted code will (as soon as it finds out + * to be interrupted) cleanup and return as soon as possible. + * + * The return value from interrupted code is undefined, + * it is the caller responsibility to not take it in consideration. + * + */ +extern void lwgeom_request_interrupt(void); + +/** + * Cancel any interruption request + */ +extern void lwgeom_cancel_interrupt(void); + +/** + * Install a callback to be called periodically during + * algorithm execution. Mostly only needed on WIN32 to + * dispatch queued signals. + * + * The callback is invoked before checking for interrupt + * being requested, so you can request interruption from + * the callback, if you want (see lwgeom_request_interrupt). + * + */ +typedef void (lwinterrupt_callback)(); +extern lwinterrupt_callback *lwgeom_register_interrupt_callback(lwinterrupt_callback *); + + +/****************************************************************** +* LWGEOM and GBOX both use LWFLAGS bit mask. +* Serializations (may) use different bit mask schemes. +*/ +typedef uint16_t lwflags_t; + +/****************************************************************** +* LWGEOM varlena equivalent type that contains both the size and +* data(see Postgresql c.h) +*/ +typedef struct lwvarlena_t +{ + uint32_t size; /* Do not touch this field directly! */ + char data[]; /* Data content is here */ +} lwvarlena_t; + +#define LWVARHDRSZ ((int32_t) sizeof(int32_t)) + +/** +* Macro for reading the size from the GSERIALIZED size attribute. +* Cribbed from PgSQL, top 30 bits are size. Use VARSIZE() when working +* internally with PgSQL. See SET_VARSIZE_4B / VARSIZE_4B in +* PGSRC/src/include/postgres.h for details. +*/ +#ifdef WORDS_BIGENDIAN +#define LWSIZE_GET(varsize) ((varsize) & 0x3FFFFFFF) +#define LWSIZE_SET(varsize, len) ((varsize) = ((len) & 0x3FFFFFFF)) +#define IS_BIG_ENDIAN 1 +#else +#define LWSIZE_GET(varsize) (((varsize) >> 2) & 0x3FFFFFFF) +#define LWSIZE_SET(varsize, len) ((varsize) = (((uint32_t)(len)) << 2)) +#define IS_BIG_ENDIAN 0 +#endif + +/******************************************************************/ + +typedef struct { + double afac, bfac, cfac, dfac, efac, ffac, gfac, hfac, ifac, xoff, yoff, zoff; +} AFFINE; + +/******************************************************************/ + +typedef struct +{ + double xmin, ymin, zmin; + double xmax, ymax, zmax; + int32_t srid; +} +BOX3D; + +/****************************************************************** +* GBOX structure. +* We include the flags (information about dimensionality), +* so we don't have to constantly pass them +* into functions that use the GBOX. +*/ +typedef struct +{ + lwflags_t flags; + double xmin; + double xmax; + double ymin; + double ymax; + double zmin; + double zmax; + double mmin; + double mmax; +} GBOX; + + +/****************************************************************** +* SPHEROID +* +* Standard definition of an ellipsoid (what wkt calls a spheroid) +* f = (a-b)/a +* e_sq = (a*a - b*b)/(a*a) +* b = a - fa +*/ +typedef struct +{ + double a; /* semimajor axis */ + double b; /* semiminor axis b = (a - fa) */ + double f; /* flattening f = (a-b)/a */ + double e; /* eccentricity (first) */ + double e_sq; /* eccentricity squared (first) e_sq = (a*a-b*b)/(a*a) */ + double radius; /* spherical average radius = (2*a+b)/3 */ + char name[20]; /* name of ellipse */ +} +SPHEROID; + +/****************************************************************** +* POINT2D, POINT3D, POINT3DM, POINT4D +*/ +typedef struct +{ + double x, y; +} +POINT2D; + +typedef struct +{ + double x, y, z; +} +POINT3DZ; + +typedef struct +{ + double x, y, z; +} +POINT3D; + +typedef struct +{ + double x, y, m; +} +POINT3DM; + +typedef struct +{ + double x, y, z, m; +} +POINT4D; + +/****************************************************************** +* POINTARRAY +* Point array abstracts a lot of the complexity of points and point lists. +* It handles 2d/3d translation +* (2d points converted to 3d will have z=0 or NaN) +* DO NOT MIX 2D and 3D POINTS! EVERYTHING* is either one or the other +*/ +typedef struct +{ + uint32_t npoints; /* how many points we are currently storing */ + uint32_t maxpoints; /* how many points we have space for in serialized_pointlist */ + + /* Use FLAGS_* macros to handle */ + lwflags_t flags; + + /* Array of POINT 2D, 3D or 4D, possibly misaligned. */ + uint8_t *serialized_pointlist; +} +POINTARRAY; + +/****************************************************************** +* GSERIALIZED +*/ + +typedef struct +{ + uint32_t size; /* For PgSQL use only, use VAR* macros to manipulate. */ + uint8_t srid[3]; /* 24 bits of SRID */ + uint8_t gflags; /* HasZ, HasM, HasBBox, IsGeodetic */ + uint8_t data[1]; /* See gserialized.txt */ +} GSERIALIZED; + +/****************************************************************** +* LWGEOM (any geometry type) +* +* Abstract type, note that 'type', 'bbox' and 'srid' are available in +* all geometry variants. +*/ +typedef struct +{ + GBOX *bbox; + void *data; + int32_t srid; + lwflags_t flags; + uint8_t type; + char pad[1]; /* Padding to 24 bytes (unused) */ +} +LWGEOM; + +/* POINTYPE */ +typedef struct +{ + GBOX *bbox; + POINTARRAY *point; /* hide 2d/3d (this will be an array of 1 point) */ + int32_t srid; + lwflags_t flags; + uint8_t type; /* POINTTYPE */ + char pad[1]; /* Padding to 24 bytes (unused) */ +} +LWPOINT; /* "light-weight point" */ + +/* LINETYPE */ +typedef struct +{ + GBOX *bbox; + POINTARRAY *points; /* array of POINT3D */ + int32_t srid; + lwflags_t flags; + uint8_t type; /* LINETYPE */ + char pad[1]; /* Padding to 24 bytes (unused) */ +} +LWLINE; /* "light-weight line" */ + +/* TRIANGLE */ +typedef struct +{ + GBOX *bbox; + POINTARRAY *points; + int32_t srid; + lwflags_t flags; + uint8_t type; + char pad[1]; /* Padding to 24 bytes (unused) */ +} +LWTRIANGLE; + +/* CIRCSTRINGTYPE */ +typedef struct +{ + GBOX *bbox; + POINTARRAY *points; /* array of POINT(3D/3DM) */ + int32_t srid; + lwflags_t flags; + uint8_t type; /* CIRCSTRINGTYPE */ + char pad[1]; /* Padding to 24 bytes (unused) */ +} +LWCIRCSTRING; /* "light-weight circularstring" */ + +/* POLYGONTYPE */ +typedef struct +{ + GBOX *bbox; + POINTARRAY **rings; /* list of rings (list of points) */ + int32_t srid; + lwflags_t flags; + uint8_t type; /* POLYGONTYPE */ + char pad[1]; /* Padding to 24 bytes (unused) */ + uint32_t nrings; /* how many rings we are currently storing */ + uint32_t maxrings; /* how many rings we have space for in **rings */ +} +LWPOLY; /* "light-weight polygon" */ + +/* MULTIPOINTTYPE */ +typedef struct +{ + GBOX *bbox; + LWPOINT **geoms; + int32_t srid; + lwflags_t flags; + uint8_t type; /* MULTYPOINTTYPE */ + char pad[1]; /* Padding to 24 bytes (unused) */ + uint32_t ngeoms; /* how many geometries we are currently storing */ + uint32_t maxgeoms; /* how many geometries we have space for in **geoms */ +} +LWMPOINT; + +/* MULTILINETYPE */ +typedef struct +{ + GBOX *bbox; + LWLINE **geoms; + int32_t srid; + lwflags_t flags; + uint8_t type; /* MULTILINETYPE */ + char pad[1]; /* Padding to 24 bytes (unused) */ + uint32_t ngeoms; /* how many geometries we are currently storing */ + uint32_t maxgeoms; /* how many geometries we have space for in **geoms */ +} +LWMLINE; + +/* MULTIPOLYGONTYPE */ +typedef struct +{ + GBOX *bbox; + LWPOLY **geoms; + int32_t srid; + lwflags_t flags; + uint8_t type; /* MULTIPOLYGONTYPE */ + char pad[1]; /* Padding to 24 bytes (unused) */ + uint32_t ngeoms; /* how many geometries we are currently storing */ + uint32_t maxgeoms; /* how many geometries we have space for in **geoms */ +} +LWMPOLY; + +/* COLLECTIONTYPE */ +typedef struct +{ + GBOX *bbox; + LWGEOM **geoms; + int32_t srid; + lwflags_t flags; + uint8_t type; /* COLLECTIONTYPE */ + char pad[1]; /* Padding to 24 bytes (unused) */ + uint32_t ngeoms; /* how many geometries we are currently storing */ + uint32_t maxgeoms; /* how many geometries we have space for in **geoms */ +} +LWCOLLECTION; + +/* COMPOUNDTYPE */ +typedef struct +{ + GBOX *bbox; + LWGEOM **geoms; + int32_t srid; + lwflags_t flags; + uint8_t type; /* COLLECTIONTYPE */ + char pad[1]; /* Padding to 24 bytes (unused) */ + uint32_t ngeoms; /* how many geometries we are currently storing */ + uint32_t maxgeoms; /* how many geometries we have space for in **geoms */ +} +LWCOMPOUND; /* "light-weight compound line" */ + +/* CURVEPOLYTYPE */ +typedef struct +{ + GBOX *bbox; + LWGEOM **rings; + int32_t srid; + lwflags_t flags; + uint8_t type; /* CURVEPOLYTYPE */ + char pad[1]; /* Padding to 24 bytes (unused) */ + uint32_t nrings; /* how many rings we are currently storing */ + uint32_t maxrings; /* how many rings we have space for in **rings */ +} +LWCURVEPOLY; /* "light-weight polygon" */ + +/* MULTICURVE */ +typedef struct +{ + GBOX *bbox; + LWGEOM **geoms; + int32_t srid; + lwflags_t flags; + uint8_t type; /* MULTICURVE */ + char pad[1]; /* Padding to 24 bytes (unused) */ + uint32_t ngeoms; /* how many geometries we are currently storing */ + uint32_t maxgeoms; /* how many geometries we have space for in **geoms */ +} +LWMCURVE; + +/* MULTISURFACETYPE */ +typedef struct +{ + GBOX *bbox; + LWGEOM **geoms; + int32_t srid; + lwflags_t flags; + uint8_t type; /* MULTISURFACETYPE */ + char pad[1]; /* Padding to 24 bytes (unused) */ + uint32_t ngeoms; /* how many geometries we are currently storing */ + uint32_t maxgeoms; /* how many geometries we have space for in **geoms */ +} +LWMSURFACE; + +/* POLYHEDRALSURFACETYPE */ +typedef struct +{ + GBOX *bbox; + LWPOLY **geoms; + int32_t srid; + lwflags_t flags; + uint8_t type; /* POLYHEDRALSURFACETYPE */ + char pad[1]; /* Padding to 24 bytes (unused) */ + uint32_t ngeoms; /* how many geometries we are currently storing */ + uint32_t maxgeoms; /* how many geometries we have space for in **geoms */ +} +LWPSURFACE; + +/* TINTYPE */ +typedef struct +{ + GBOX *bbox; + LWTRIANGLE **geoms; + int32_t srid; + lwflags_t flags; + uint8_t type; /* TINTYPE */ + char pad[1]; /* Padding to 24 bytes (unused) */ + uint32_t ngeoms; /* how many geometries we are currently storing */ + uint32_t maxgeoms; /* how many geometries we have space for in **geoms */ +} +LWTIN; + +/* Casts LWGEOM->LW* (return NULL if cast is illegal) */ +extern LWMPOLY *lwgeom_as_lwmpoly(const LWGEOM *lwgeom); +extern LWMLINE *lwgeom_as_lwmline(const LWGEOM *lwgeom); +extern LWMPOINT *lwgeom_as_lwmpoint(const LWGEOM *lwgeom); +extern LWCOLLECTION *lwgeom_as_lwcollection(const LWGEOM *lwgeom); +extern LWPOLY *lwgeom_as_lwpoly(const LWGEOM *lwgeom); +extern LWLINE *lwgeom_as_lwline(const LWGEOM *lwgeom); + +extern LWCIRCSTRING *lwgeom_as_lwcircstring(const LWGEOM *lwgeom); +extern LWCURVEPOLY *lwgeom_as_lwcurvepoly(const LWGEOM *lwgeom); +extern LWCOMPOUND *lwgeom_as_lwcompound(const LWGEOM *lwgeom); +extern LWPSURFACE *lwgeom_as_lwpsurface(const LWGEOM *lwgeom); +extern LWTRIANGLE *lwgeom_as_lwtriangle(const LWGEOM *lwgeom); +extern LWTIN *lwgeom_as_lwtin(const LWGEOM *lwgeom); +extern LWGEOM *lwgeom_as_multi(const LWGEOM *lwgeom); +extern LWGEOM *lwgeom_as_curve(const LWGEOM *lwgeom); + +/* Casts LW*->LWGEOM (always cast) */ +extern LWGEOM *lwtin_as_lwgeom(const LWTIN *obj); +extern LWGEOM *lwtriangle_as_lwgeom(const LWTRIANGLE *obj); +extern LWGEOM *lwpsurface_as_lwgeom(const LWPSURFACE *obj); +extern LWGEOM *lwmpoly_as_lwgeom(const LWMPOLY *obj); +extern LWGEOM *lwmline_as_lwgeom(const LWMLINE *obj); +extern LWGEOM *lwmpoint_as_lwgeom(const LWMPOINT *obj); +extern LWGEOM *lwcollection_as_lwgeom(const LWCOLLECTION *obj); +extern LWGEOM *lwcircstring_as_lwgeom(const LWCIRCSTRING *obj); +extern LWGEOM *lwcompound_as_lwgeom(const LWCOMPOUND *obj); +extern LWGEOM *lwcurvepoly_as_lwgeom(const LWCURVEPOLY *obj); +extern LWGEOM *lwpoly_as_lwgeom(const LWPOLY *obj); +extern LWGEOM *lwline_as_lwgeom(const LWLINE *obj); +extern LWGEOM *lwpoint_as_lwgeom(const LWPOINT *obj); + + +extern LWCOLLECTION* lwcollection_add_lwgeom(LWCOLLECTION *col, const LWGEOM *geom); +extern LWMPOINT* lwmpoint_add_lwpoint(LWMPOINT *mobj, const LWPOINT *obj); +extern LWMLINE* lwmline_add_lwline(LWMLINE *mobj, const LWLINE *obj); +extern LWMPOLY* lwmpoly_add_lwpoly(LWMPOLY *mobj, const LWPOLY *obj); +extern LWPSURFACE* lwpsurface_add_lwpoly(LWPSURFACE *mobj, const LWPOLY *obj); +extern LWTIN* lwtin_add_lwtriangle(LWTIN *mobj, const LWTRIANGLE *obj); + +extern LWCOLLECTION* lwcollection_concat_in_place(LWCOLLECTION* col1, const LWCOLLECTION* col2); + +/** +* Construct a new flags bitmask. +*/ +extern lwflags_t lwflags(int hasz, int hasm, int geodetic); + + + +/*********************************************************************** +** GSERIALIZED API +*/ + +/** +* Read standard lwflags from gserialized +*/ +extern lwflags_t gserialized_get_lwflags(const GSERIALIZED *g); + +/** +* Access to the float bounding box, if there is one. +* NULL if there is not. +*/ +extern const float * gserialized_get_float_box_p(const GSERIALIZED *g, size_t *ndims); + +/** +* Extract the geometry type from the serialized form (it hides in +* the anonymous data area, so this is a handy function). +*/ +extern uint32_t gserialized_get_type(const GSERIALIZED *g); + +/** +* Returns the size in bytes to read from toast to get the basic +* information from a geometry: GSERIALIZED struct, bbox and type +*/ +extern uint32_t gserialized_max_header_size(void); + +/** +* Returns a hash code for the srid/type/geometry information +* in the GSERIALIZED. Ignores metadata like flags and optional +* boxes, etc. +*/ +extern int32_t gserialized_hash(const GSERIALIZED *g); + +/** +* Extract the SRID from the serialized form (it is packed into +* three bytes so this is a handy function). +*/ +extern int32_t gserialized_get_srid(const GSERIALIZED *g); + +/** +* Write the SRID into the serialized form (it is packed into +* three bytes so this is a handy function). +*/ +extern void gserialized_set_srid(GSERIALIZED *g, int32_t srid); + +/** +* Check if a #GSERIALIZED is empty without deserializing first. +* Only checks if the number of elements of the parent geometry +* is zero, will not catch collections of empty, eg: +* GEOMETRYCOLLECTION(POINT EMPTY) +*/ +extern int gserialized_is_empty(const GSERIALIZED *g); + +/** +* Check if a #GSERIALIZED has a bounding box without deserializing first. +*/ +extern int gserialized_has_bbox(const GSERIALIZED *gser); + +/** +* Check if a #GSERIALIZED has a Z ordinate. +*/ +extern int gserialized_has_z(const GSERIALIZED *gser); + +/** +* Check if a #GSERIALIZED has an M ordinate. +*/ +extern int gserialized_has_m(const GSERIALIZED *gser); + +/** +* Check if a #GSERIALIZED is a geography. +*/ +extern int gserialized_is_geodetic(const GSERIALIZED *gser); + +/** +* Return the number of dimensions (2, 3, 4) in a geometry +*/ +extern int gserialized_ndims(const GSERIALIZED *gser); + +/** +* Return -1 if g1 is "less than" g2, 1 if g1 is "greater than" +* g2 and 0 if g1 and g2 are the "same". Equality is evaluated +* with a memcmp and size check. So it is possible that two +* identical objects where one lacks a bounding box could be +* evaluated as non-equal initially. Greater and less than +* are evaluated by calculating a sortable key from the center +* point of the object bounds. +*/ +extern int gserialized_cmp(const GSERIALIZED *g1, const GSERIALIZED *g2); + +/** +* Allocate a new #GSERIALIZED from an #LWGEOM. For all non-point types, a bounding +* box will be calculated and embedded in the serialization. The geodetic flag is used +* to control the box calculation (cartesian or geocentric). If set, the size pointer +* will contain the size of the final output, which is useful for setting the PgSQL +* VARSIZE information. +*/ +extern GSERIALIZED* gserialized_from_lwgeom(LWGEOM *geom, size_t *size); + +/** +* Allocate a new #LWGEOM from a #GSERIALIZED. The resulting #LWGEOM will have coordinates +* that are double aligned and suitable for direct reading using getPoint2d_cp +*/ +extern LWGEOM* lwgeom_from_gserialized(const GSERIALIZED *g); + +/** +* Pull a #GBOX from the header of a #GSERIALIZED, if one is available. If +* it is not, calculate it from the geometry. If that doesn't work (null +* or empty) return LW_FAILURE. +*/ +extern int gserialized_get_gbox_p(const GSERIALIZED *g, GBOX *box); + +/** +* Pull a #GBOX from the header of a #GSERIALIZED, if one is available. If +* it is not, return LW_FAILURE. +*/ +extern int gserialized_fast_gbox_p(const GSERIALIZED *g, GBOX *box); + +/** +* Copy a new bounding box into an existing gserialized. +* If necessary a new #GSERIALIZED will be allocated. Test +* that input != output before freeing input. +*/ +extern GSERIALIZED *gserialized_set_gbox(GSERIALIZED *g, GBOX *gbox); + +/** +* Remove the bounding box from a #GSERIALIZED. Returns a freshly +* allocated #GSERIALIZED every time. +*/ +extern GSERIALIZED* gserialized_drop_gbox(GSERIALIZED *g); + +/** +* Return the serialization version +*/ +extern uint32_t gserialized_get_version(const GSERIALIZED *g); + +/** +* Pull the first point values of a #GSERIALIZED. Only works for POINTTYPE +*/ +extern int gserialized_peek_first_point(const GSERIALIZED *g, POINT4D *out_point); + +/*****************************************************************************/ + + +/** +* Call this function to drop BBOX and SRID +* from LWGEOM. If LWGEOM type is *not* flagged +* with the HASBBOX flag and has a bbox, it +* will be released. +*/ +extern void lwgeom_drop_bbox(LWGEOM *lwgeom); +extern void lwgeom_drop_srid(LWGEOM *lwgeom); + +/** + * Compute a bbox if not already computed + * + * After calling this function lwgeom->bbox is only + * NULL if the geometry is empty. + */ +extern void lwgeom_add_bbox(LWGEOM *lwgeom); +/** +* Drop current bbox and calculate a fresh one. +*/ +extern void lwgeom_refresh_bbox(LWGEOM *lwgeom); +/** +* Compute a box for geom and all sub-geometries, if not already computed +*/ +extern void lwgeom_add_bbox_deep(LWGEOM *lwgeom, GBOX *gbox); + +/** + * Get a non-empty geometry bounding box, computing and + * caching it if not already there + * + * NOTE: empty geometries don't have a bounding box so + * you'd still get a NULL for them. + */ +extern const GBOX *lwgeom_get_bbox(const LWGEOM *lwgeom); + +/** +* Determine whether a LWGEOM can contain sub-geometries or not +*/ +extern int lwgeom_is_collection(const LWGEOM *lwgeom); + +/** +* Check if a LWGEOM has any non-finite (NaN or Inf) coordinates. +*/ +extern int lwgeom_isfinite(const LWGEOM *lwgeom); + + +/******************************************************************/ +/* Functions that work on type numbers */ + +/** +* Determine whether a type number is a collection or not +*/ +extern int lwtype_is_collection(uint8_t type); + +/** +* Given an lwtype number, what homogeneous collection can hold it? +*/ +extern uint32_t lwtype_get_collectiontype(uint8_t type); + +/** +* Return the type name string associated with a type number +* (e.g. Point, LineString, Polygon) +*/ +extern const char *lwtype_name(uint8_t type); +extern uint8_t lwtype_multitype(uint8_t type); + +/******************************************************************/ + +/* + * copies a point from the point array into the parameter point + * will set point's z=0 (or NaN) if pa is 2d + * will set point's m=0 (or NaN) if pa is 3d or 2d + * NOTE: point is a real POINT3D *not* a pointer + */ +extern POINT4D getPoint4d(const POINTARRAY *pa, uint32_t n); + +/* + * copies a point from the point array into the parameter point + * will set point's z=0 (or NaN) if pa is 2d + * will set point's m=0 (or NaN) if pa is 3d or 2d + * NOTE: this will modify the point4d pointed to by 'point'. + */ +extern int getPoint4d_p(const POINTARRAY *pa, uint32_t n, POINT4D *point); + +/* + * copies a point from the point array into the parameter point + * will set point's z=0 (or NaN) if pa is 2d + * NOTE: point is a real POINT3D *not* a pointer + */ +extern POINT3DZ getPoint3dz(const POINTARRAY *pa, uint32_t n); +extern POINT3DM getPoint3dm(const POINTARRAY *pa, uint32_t n); + +/* + * copies a point from the point array into the parameter point + * will set point's z=0 (or NaN) if pa is 2d + * NOTE: this will modify the point3d pointed to by 'point'. + */ +extern int getPoint3dz_p(const POINTARRAY *pa, uint32_t n, POINT3DZ *point); +extern int getPoint3dm_p(const POINTARRAY *pa, uint32_t n, POINT3DM *point); + + +/* + * copies a point from the point array into the parameter point + * z value (if present is not returned) + * NOTE: point is a real POINT3D *not* a pointer + */ +extern POINT2D getPoint2d(const POINTARRAY *pa, uint32_t n); + +/* + * copies a point from the point array into the parameter point + * z value (if present is not returned) + * NOTE: this will modify the point2d pointed to by 'point'. + */ +extern int getPoint2d_p(const POINTARRAY *pa, uint32_t n, POINT2D *point); + +/* + * set point N to the given value + * NOTE that the pointarray can be of any + * dimension, the appropriate ordinate values + * will be extracted from it + * + * N must be a valid point index + */ +extern void ptarray_set_point4d(POINTARRAY *pa, uint32_t n, const POINT4D *p4d); + +/** +* Construct an empty pointarray, allocating storage and setting +* the npoints, but not filling in any information. Should be used in conjunction +* with ptarray_set_point4d to fill in the information in the array. +*/ +extern POINTARRAY* ptarray_construct(char hasz, char hasm, uint32_t npoints); + +/** +* Construct a new #POINTARRAY, copying in the data from ptlist +*/ +extern POINTARRAY* ptarray_construct_copy_data(char hasz, char hasm, uint32_t npoints, const uint8_t *ptlist); + +/** +* Construct a new #POINTARRAY, referencing to the data from ptlist +*/ +extern POINTARRAY* ptarray_construct_reference_data(char hasz, char hasm, uint32_t npoints, uint8_t *ptlist); + +/** +* Create a new #POINTARRAY with no points. Allocate enough storage +* to hold maxpoints vertices before having to reallocate the storage +* area. +*/ +extern POINTARRAY* ptarray_construct_empty(char hasz, char hasm, uint32_t maxpoints); + +/** +* Append a point to the end of an existing #POINTARRAY +* If allow_duplicate is LW_FALSE, then a duplicate point will +* not be added. +*/ +extern int ptarray_append_point(POINTARRAY *pa, const POINT4D *pt, int allow_duplicates); + +/** + * Append a #POINTARRAY, pa2 to the end of an existing #POINTARRAY, pa1. + * + * If gap_tolerance is >= 0 then the end point of pa1 will be checked for + * being within gap_tolerance 2d distance from start point of pa2 or an + * error will be raised and LW_FAILURE returned. + * A gap_tolerance < 0 disables the check. + * + * If end point of pa1 and start point of pa2 are 2d-equal, then pa2 first + * point will not be appended. + */ +extern int ptarray_append_ptarray(POINTARRAY *pa1, POINTARRAY *pa2, double gap_tolerance); + +/** +* Insert a point into an existing #POINTARRAY. Zero +* is the index of the start of the array. +*/ +extern int ptarray_insert_point(POINTARRAY *pa, const POINT4D *p, uint32_t where); + +/** +* Remove a point from an existing #POINTARRAY. Zero +* is the index of the start of the array. +*/ +extern int ptarray_remove_point(POINTARRAY *pa, uint32_t where); + +/** + * @brief Add a point in a pointarray. + * + * @param pa the source POINTARRAY + * @param p the point to add + * @param pdims number of ordinates in p (2..4) + * @param where to insert the point. 0 prepends, pa->npoints appends + * + * @returns a newly constructed POINTARRAY using a newly allocated buffer + * for the actual points, or NULL on error. + */ +extern POINTARRAY *ptarray_addPoint(const POINTARRAY *pa, uint8_t *p, size_t pdims, uint32_t where); + +/** + * @brief Remove a point from a pointarray. + * @param where - is the offset (starting at 0) + * @return #POINTARRAY is newly allocated + */ +extern POINTARRAY *ptarray_removePoint(POINTARRAY *pa, uint32_t where); + +/** + * @brief Merge two given POINTARRAY and returns a pointer + * on the new aggregate one. + * Warning: this function free the two inputs POINTARRAY + * @return #POINTARRAY is newly allocated + */ +extern POINTARRAY *ptarray_merge(POINTARRAY *pa1, POINTARRAY *pa2); + +extern int ptarray_is_closed(const POINTARRAY *pa); +extern int ptarray_is_closed_2d(const POINTARRAY *pa); +extern int ptarray_is_closed_3d(const POINTARRAY *pa); +extern int ptarray_is_closed_z(const POINTARRAY *pa); +extern POINTARRAY* ptarray_flip_coordinates(POINTARRAY *pa); + +/** + * @d1 start location (distance from start / total distance) + * @d2 end location (distance from start / total distance) + * @param tolerance snap to vertices at locations < tolerance away from given ones + */ +extern POINTARRAY *ptarray_substring(POINTARRAY *pa, double d1, double d2, + double tolerance); + +/** + * @param pa the subject pointarray + * @param qp the query point + * @param dist optional output for actual distance from vertex + */ +extern int ptarray_closest_vertex_2d(const POINTARRAY *pa, + const POINT2D *qp, double *dist); + +/** + * @param pa the subject pointarray + * @param qp the query point + * @param dist optional output for actual distance from segment + * @return 0-based segment index for the closest segment + * (earliest segment in case of same distance) + */ +extern int ptarray_closest_segment_2d(const POINTARRAY *pa, + const POINT2D *qp, double *dist); + + + +/** +* Strip out the Z/M components of an #LWGEOM +*/ +extern LWGEOM* lwgeom_force_2d(const LWGEOM *geom); +extern LWGEOM* lwgeom_force_3dz(const LWGEOM *geom, double zval); +extern LWGEOM* lwgeom_force_3dm(const LWGEOM *geom, double mval); +extern LWGEOM* lwgeom_force_4d(const LWGEOM *geom, double zval, double mval); + +extern LWGEOM* lwgeom_set_effective_area(const LWGEOM *igeom, int set_area, double area); +extern LWGEOM* lwgeom_chaikin(const LWGEOM *igeom, int n_iterations, int preserve_endpoint); +extern LWGEOM* lwgeom_filter_m(LWGEOM *geom, double min, double max, int returnm); + +/* + * Force to use SFS 1.1 geometry type + * (rather than SFS 1.2 and/or SQL/MM) + */ +extern LWGEOM* lwgeom_force_sfs(LWGEOM *geom, int version); + + +/*-------------------------------------------------------- + * all the base types (point/line/polygon) will have a + * basic constructor, basic de-serializer, basic serializer, + * bounding box finder and (TODO) serialized form size finder. + *--------------------------------------------------------*/ + +/* + * convenience functions to hide the POINTARRAY + */ +extern int lwpoint_getPoint2d_p(const LWPOINT *point, POINT2D *out); +extern int lwpoint_getPoint3dz_p(const LWPOINT *point, POINT3DZ *out); +extern int lwpoint_getPoint3dm_p(const LWPOINT *point, POINT3DM *out); +extern int lwpoint_getPoint4d_p(const LWPOINT *point, POINT4D *out); + +/****************************************************************** + * LWLINE functions + ******************************************************************/ + +/** + * Add a LWPOINT to an LWLINE + */ +extern int lwline_add_lwpoint(LWLINE *line, LWPOINT *point, uint32_t where); + +/** + * Interpolate one or more points along a line + */ +extern POINTARRAY* lwline_interpolate_points(const LWLINE *line, double length_fraction, char repeat); + +/** + * Interpolate one point along a line in 3D + */ +extern LWPOINT* lwline_interpolate_point_3d(const LWLINE *line, double distance); + +/** + * Extend the ends of a line + */ +extern LWLINE* lwline_extend(const LWLINE *line, double distance_forward, double distance_backward); + +/****************************************************************** + * LWPOLY functions + ******************************************************************/ + +/** +* Add a ring, allocating extra space if necessary. The polygon takes +* ownership of the passed point array. +*/ +extern int lwpoly_add_ring(LWPOLY *poly, POINTARRAY *pa); + +/** +* Add a ring, allocating extra space if necessary. The curvepolygon takes +* ownership of the passed point array. +*/ +extern int lwcurvepoly_add_ring(LWCURVEPOLY *poly, LWGEOM *ring); + +/** +* Add a component, allocating extra space if necessary. The compoundcurve +* takes owership of the passed geometry. +*/ +extern int lwcompound_add_lwgeom(LWCOMPOUND *comp, LWGEOM *geom); + +/** +* Construct an equivalent compound curve from a linestring. +* Compound curves can have linear components, so this works fine +*/ +extern LWCOMPOUND* lwcompound_construct_from_lwline(const LWLINE *lwpoly); + +/** +* Construct an equivalent curve polygon from a polygon. Curve polygons +* can have linear rings as their rings, so this works fine (in theory?) +*/ +extern LWCURVEPOLY* lwcurvepoly_construct_from_lwpoly(LWPOLY *lwpoly); + + +/****************************************************************** + * LWGEOM functions + ******************************************************************/ + +extern int lwcollection_ngeoms(const LWCOLLECTION *col); + +/* Given a generic geometry/collection, return the "simplest" form. + * The elements of the homogenized collection are deeply cloned + * */ +extern LWGEOM *lwgeom_homogenize(const LWGEOM *geom); + + +/****************************************************************** + * LWMULTIx and LWCOLLECTION functions + ******************************************************************/ + +LWGEOM *lwcollection_getsubgeom(LWCOLLECTION *col, int gnum); +LWCOLLECTION* lwcollection_extract(const LWCOLLECTION *col, uint32_t type); + + +/****************************************************************** + * SERIALIZED FORM functions + ******************************************************************/ + +/** +* Set the SRID on an LWGEOM +* For collections, only the parent gets an SRID, all +* the children get SRID_UNKNOWN. +*/ +extern void lwgeom_set_srid(LWGEOM *geom, int32_t srid); + +/*------------------------------------------------------ + * other stuff + * + * handle the double-to-float conversion. The results of this + * will usually be a slightly bigger box because of the difference + * between float8 and float4 representations. + */ + +extern BOX3D* box3d_from_gbox(const GBOX *gbox); +extern GBOX* box3d_to_gbox(const BOX3D *b3d); + +void expand_box3d(BOX3D *box, double d); + + +/**************************************************************** + * MEMORY MANAGEMENT + ****************************************************************/ + +/* +* The *_free family of functions frees *all* memory associated +* with the pointer. When the recursion gets to the level of the +* POINTARRAY, the POINTARRAY is only freed if it is not flagged +* as "read only". LWGEOMs constructed on top of GSERIALIZED +* from PgSQL use read only point arrays. +*/ + +extern void ptarray_free(POINTARRAY *pa); +extern void lwpoint_free(LWPOINT *pt); +extern void lwline_free(LWLINE *line); +extern void lwpoly_free(LWPOLY *poly); +extern void lwtriangle_free(LWTRIANGLE *triangle); +extern void lwmpoint_free(LWMPOINT *mpt); +extern void lwmline_free(LWMLINE *mline); +extern void lwmpoly_free(LWMPOLY *mpoly); +extern void lwpsurface_free(LWPSURFACE *psurf); +extern void lwtin_free(LWTIN *tin); +extern void lwcollection_free(LWCOLLECTION *col); +extern void lwcircstring_free(LWCIRCSTRING *curve); +extern void lwgeom_free(LWGEOM *geom); + +/* +* The *_release family of functions frees the LWGEOM structures +* surrounding the POINTARRAYs but leaves the POINTARRAYs +* intact. Useful when re-shaping geometries between types, +* or splicing geometries together. +*/ + +extern void lwpoint_release(LWPOINT *lwpoint); +extern void lwline_release(LWLINE *lwline); +extern void lwpoly_release(LWPOLY *lwpoly); +extern void lwtriangle_release(LWTRIANGLE *lwtriangle); +extern void lwcircstring_release(LWCIRCSTRING *lwcirc); +extern void lwmpoint_release(LWMPOINT *lwpoint); +extern void lwmline_release(LWMLINE *lwline); +extern void lwmpoly_release(LWMPOLY *lwpoly); +extern void lwpsurface_release(LWPSURFACE *lwpsurface); +extern void lwtin_release(LWTIN *lwtin); +extern void lwcollection_release(LWCOLLECTION *lwcollection); +extern void lwgeom_release(LWGEOM *lwgeom); + + +/**************************************************************** +* Utility +****************************************************************/ + +extern void printBOX3D(BOX3D *b); +extern void printPA(POINTARRAY *pa); +extern void printLWPOINT(LWPOINT *point); +extern void printLWLINE(LWLINE *line); +extern void printLWPOLY(LWPOLY *poly); +extern void printLWTRIANGLE(LWTRIANGLE *triangle); +extern void printLWPSURFACE(LWPSURFACE *psurf); +extern void printLWTIN(LWTIN *tin); + +extern float next_float_down(double d); +extern float next_float_up(double d); + +/* general utilities 2D */ +extern double distance2d_pt_pt(const POINT2D *p1, const POINT2D *p2); +extern double distance2d_sqr_pt_seg(const POINT2D *p, const POINT2D *A, const POINT2D *B); +extern LWGEOM* lwgeom_closest_line(const LWGEOM *lw1, const LWGEOM *lw2); +extern LWGEOM* lwgeom_furthest_line(const LWGEOM *lw1, const LWGEOM *lw2); +extern LWGEOM* lwgeom_closest_point(const LWGEOM *lw1, const LWGEOM *lw2); +extern LWGEOM* lwgeom_furthest_point(const LWGEOM *lw1, const LWGEOM *lw2); +extern double lwgeom_mindistance2d(const LWGEOM *lw1, const LWGEOM *lw2); +extern double lwgeom_mindistance2d_tolerance(const LWGEOM *lw1, const LWGEOM *lw2, double tolerance); +extern double lwgeom_maxdistance2d(const LWGEOM *lw1, const LWGEOM *lw2); +extern double lwgeom_maxdistance2d_tolerance(const LWGEOM *lw1, const LWGEOM *lw2, double tolerance); + +/* 3D */ +extern double distance3d_pt_pt(const POINT3D *p1, const POINT3D *p2); +extern double distance3d_pt_seg(const POINT3D *p, const POINT3D *A, const POINT3D *B); + +extern LWGEOM* lwgeom_furthest_line_3d(LWGEOM *lw1, LWGEOM *lw2); +extern LWGEOM* lwgeom_closest_line_3d(const LWGEOM *lw1, const LWGEOM *lw2); +extern LWGEOM* lwgeom_closest_point_3d(const LWGEOM *lw1, const LWGEOM *lw2); + + +extern double lwgeom_mindistance3d(const LWGEOM *lw1, const LWGEOM *lw2); +extern double lwgeom_mindistance3d_tolerance(const LWGEOM *lw1, const LWGEOM *lw2, double tolerance); +extern double lwgeom_maxdistance3d(const LWGEOM *lw1, const LWGEOM *lw2); +extern double lwgeom_maxdistance3d_tolerance(const LWGEOM *lw1, const LWGEOM *lw2, double tolerance); + +extern double lwgeom_area(const LWGEOM *geom); +extern double lwgeom_length(const LWGEOM *geom); +extern double lwgeom_length_2d(const LWGEOM *geom); +extern double lwgeom_perimeter(const LWGEOM *geom); +extern double lwgeom_perimeter_2d(const LWGEOM *geom); +extern int lwgeom_dimension(const LWGEOM *geom); + +extern LWPOINT* lwline_get_lwpoint(const LWLINE *line, uint32_t where); +extern LWPOINT* lwcircstring_get_lwpoint(const LWCIRCSTRING *circ, uint32_t where); + +extern LWPOINT* lwcompound_get_startpoint(const LWCOMPOUND *lwcmp); +extern LWPOINT* lwcompound_get_endpoint(const LWCOMPOUND *lwcmp); +extern LWPOINT* lwcompound_get_lwpoint(const LWCOMPOUND *lwcmp, uint32_t where); + +extern double ptarray_length_2d(const POINTARRAY *pts); +extern int pt_in_ring_2d(const POINT2D *p, const POINTARRAY *ring); +extern int azimuth_pt_pt(const POINT2D *p1, const POINT2D *p2, double *ret); +extern LWPOINT* lwpoint_project_lwpoint(const LWPOINT* lwpoint1, const LWPOINT* lwpoint2, double distance); +extern LWPOINT* lwpoint_project(const LWPOINT* lwpoint1, double distance, double azimuth); +extern int lwpoint_inside_circle(const LWPOINT *p, double cx, double cy, double rad); + +extern LWGEOM* lwgeom_reverse(const LWGEOM *lwgeom); +extern char* lwgeom_summary(const LWGEOM *lwgeom, int offset); +extern char* lwpoint_to_latlon(const LWPOINT *p, const char *format); +extern int lwgeom_startpoint(const LWGEOM* lwgeom, POINT4D* pt); + +extern void interpolate_point4d(const POINT4D *A, const POINT4D *B, POINT4D *I, double F); + +/** +* Ensure the outer ring is clockwise oriented and all inner rings +* are counter-clockwise. +*/ +extern int lwgeom_is_clockwise(LWGEOM *lwgeom); + + +/** +* Simplification +*/ +extern LWGEOM* lwgeom_simplify(const LWGEOM *igeom, double dist, int preserve_collapsed); +extern LWGEOM* lwgeom_remove_repeated_points(const LWGEOM *in, double tolerance); + +/** +* Snap-to-grid +*/ +typedef struct gridspec_t +{ + double ipx; + double ipy; + double ipz; + double ipm; + double xsize; + double ysize; + double zsize; + double msize; +} +gridspec; + +extern LWGEOM* lwgeom_grid(const LWGEOM *lwgeom, const gridspec *grid); +extern void lwgeom_grid_in_place(LWGEOM *lwgeom, const gridspec *grid); + + +/**************************************************************** +* READ/WRITE FUNCTIONS +* +* Coordinate writing functions, which will alter the coordinates +* and potentially the structure of the input geometry. When +* called from within PostGIS, the LWGEOM argument should be built +* on top of a gserialized copy, created using +* PG_GETARG_GSERIALIZED_P_COPY() +****************************************************************/ + +extern void lwgeom_reverse_in_place(LWGEOM *lwgeom); +extern void lwgeom_force_clockwise(LWGEOM *lwgeom); +extern void lwgeom_longitude_shift(LWGEOM *lwgeom); +extern int lwgeom_simplify_in_place(LWGEOM *igeom, double dist, int preserve_collapsed); +extern void lwgeom_affine(LWGEOM *geom, const AFFINE *affine); +extern void lwgeom_scale(LWGEOM *geom, const POINT4D *factors); +extern int lwgeom_remove_repeated_points_in_place(LWGEOM *in, double tolerance); + + +/** + * @brief wrap geometry on given cut x value + * + * For a positive amount, shifts anything that is on the left + * of "cutx" to the right by the given amount. + * + * For a negative amount, shifts anything that is on the right + * of "cutx" to the left by the given absolute amount. + * + * @param cutx the X value to perform wrapping on + * @param amount shift amount and wrapping direction + */ +LWGEOM *lwgeom_wrapx(const LWGEOM *lwgeom, double cutx, double amount); + + +/** +* @brief Check whether or not a lwgeom is big enough to warrant a bounding box. +* +* Check whether or not a lwgeom is big enough to warrant a bounding box +* when stored in the serialized form on disk. Currently only points are +* considered small enough to not require a bounding box, because the +* index operations can generate a large number of box-retrieval operations +* when scanning keys. +*/ +extern int lwgeom_needs_bbox(const LWGEOM *geom); + +/** +* Count the total number of vertices in any #LWGEOM. +*/ +extern uint32_t lwgeom_count_vertices(const LWGEOM *geom); + +/** +* Count the total number of rings in any #LWGEOM. Multipolygons +* and other collections get counted, not the same as OGC st_numrings. +*/ +extern uint32_t lwgeom_count_rings(const LWGEOM *geom); + +/** +* Return true or false depending on whether a geometry has +* a valid SRID set. +*/ +extern int lwgeom_has_srid(const LWGEOM *geom); + +/** +* Return true or false depending on whether a geometry is a linear +* feature that closes on itself. +*/ +extern int lwgeom_is_closed(const LWGEOM *geom); + +/** +* Return the dimensionality (relating to point/line/poly) of an lwgeom +*/ +extern int lwgeom_dimensionality(const LWGEOM *geom); + +/* Is lwgeom1 geometrically equal to lwgeom2 ? */ +extern char lwgeom_same(const LWGEOM *lwgeom1, const LWGEOM *lwgeom2); + + +/** + * @brief Clone LWGEOM object. Serialized point lists are not copied. + * + * #GBOX are copied + * + * @see ptarray_clone + */ +extern LWGEOM *lwgeom_clone(const LWGEOM *lwgeom); + +/** +* Deep clone an LWGEOM, everything is copied +*/ +extern LWGEOM *lwgeom_clone_deep(const LWGEOM *lwgeom); +extern POINTARRAY *ptarray_clone_deep(const POINTARRAY *ptarray); + + +/* +* Geometry constructors. These constructors to not copy the point arrays +* passed to them, they just take references, so do not free them out +* from underneath the geometries. +*/ +extern LWPOINT* lwpoint_construct(int32_t srid, GBOX *bbox, POINTARRAY *point); +extern LWMPOINT *lwmpoint_construct(int32_t srid, const POINTARRAY *pa); +extern LWLINE* lwline_construct(int32_t srid, GBOX *bbox, POINTARRAY *points); +extern LWCIRCSTRING* lwcircstring_construct(int32_t srid, GBOX *bbox, POINTARRAY *points); +extern LWPOLY* lwpoly_construct(int32_t srid, GBOX *bbox, uint32_t nrings, POINTARRAY **points); +extern LWCURVEPOLY* lwcurvepoly_construct(int32_t srid, GBOX *bbox, uint32_t nrings, LWGEOM **geoms); +extern LWTRIANGLE* lwtriangle_construct(int32_t srid, GBOX *bbox, POINTARRAY *points); +extern LWCOLLECTION* lwcollection_construct(uint8_t type, int32_t srid, GBOX *bbox, uint32_t ngeoms, LWGEOM **geoms); +/* +* Empty geometry constructors. +*/ +extern LWGEOM* lwgeom_construct_empty(uint8_t type, int32_t srid, char hasz, char hasm); +extern LWPOINT* lwpoint_construct_empty(int32_t srid, char hasz, char hasm); +extern LWLINE* lwline_construct_empty(int32_t srid, char hasz, char hasm); +extern LWPOLY* lwpoly_construct_empty(int32_t srid, char hasz, char hasm); +extern LWCURVEPOLY* lwcurvepoly_construct_empty(int32_t srid, char hasz, char hasm); +extern LWCIRCSTRING* lwcircstring_construct_empty(int32_t srid, char hasz, char hasm); +extern LWCOMPOUND* lwcompound_construct_empty(int32_t srid, char hasz, char hasm); +extern LWTRIANGLE* lwtriangle_construct_empty(int32_t srid, char hasz, char hasm); +extern LWMPOINT* lwmpoint_construct_empty(int32_t srid, char hasz, char hasm); +extern LWMLINE* lwmline_construct_empty(int32_t srid, char hasz, char hasm); +extern LWMPOLY* lwmpoly_construct_empty(int32_t srid, char hasz, char hasm); +extern LWCOLLECTION* lwcollection_construct_empty(uint8_t type, int32_t srid, char hasz, char hasm); + + +/* Other constructors */ +extern LWPOINT *lwpoint_make2d(int32_t srid, double x, double y); +extern LWPOINT *lwpoint_make3dz(int32_t srid, double x, double y, double z); +extern LWPOINT *lwpoint_make3dm(int32_t srid, double x, double y, double m); +extern LWPOINT *lwpoint_make4d(int32_t srid, double x, double y, double z, double m); +extern LWPOINT *lwpoint_make(int32_t srid, int hasz, int hasm, const POINT4D *p); +extern LWLINE *lwline_from_lwgeom_array(int32_t srid, uint32_t ngeoms, LWGEOM **geoms); +extern LWLINE *lwline_from_ptarray(int32_t srid, uint32_t npoints, LWPOINT **points); /* TODO: deprecate */ +extern LWLINE *lwline_from_lwmpoint(int32_t srid, const LWMPOINT *mpoint); +extern LWLINE *lwline_addpoint(LWLINE *line, LWPOINT *point, uint32_t where); +extern LWLINE *lwline_removepoint(LWLINE *line, uint32_t which); +extern void lwline_setPoint4d(LWLINE *line, uint32_t which, POINT4D *newpoint); +extern LWPOLY *lwpoly_from_lwlines(const LWLINE *shell, uint32_t nholes, const LWLINE **holes); +extern LWPOLY *lwpoly_construct_rectangle(char hasz, char hasm, POINT4D *p1, POINT4D *p2, POINT4D *p3, POINT4D *p4); +extern LWPOLY *lwpoly_construct_envelope(int32_t srid, double x1, double y1, double x2, double y2); +extern LWPOLY *lwpoly_construct_circle(int32_t srid, double x, double y, double radius, uint32_t segments_per_quarter, char exterior); +extern LWTRIANGLE *lwtriangle_from_lwline(const LWLINE *shell); +extern LWMPOINT *lwmpoint_from_lwgeom(const LWGEOM *g); /* Extract the coordinates of an LWGEOM into an LWMPOINT */ + +/* Some point accessors */ +extern double lwpoint_get_x(const LWPOINT *point); +extern double lwpoint_get_y(const LWPOINT *point); +extern double lwpoint_get_z(const LWPOINT *point); +extern double lwpoint_get_m(const LWPOINT *point); + +/** +* Return SRID number +*/ +extern int32_t lwgeom_get_srid(const LWGEOM *geom); + +/** +* Return #LW_TRUE if geometry has Z ordinates +*/ +extern int lwgeom_has_z(const LWGEOM *geom); + +/** +* Return #LW_TRUE if geometry has M ordinates. +*/ +extern int lwgeom_has_m(const LWGEOM *geom); + +/** +* Return #LW_TRUE if geometry has SOLID flag. +*/ +extern int lwgeom_is_solid(const LWGEOM *geom); + +/** +* Return the number of dimensions (2, 3, 4) in a geometry +*/ +extern int lwgeom_ndims(const LWGEOM *geom); + +/* + * Given a point, returns the location of closest point on pointarray + * as a fraction of total length (0: first point -- 1: last point). + * + * If not-null, the third argument will be set to the actual distance + * of the point from the pointarray. + */ +extern double ptarray_locate_point(const POINTARRAY *pa, const POINT4D *pt, double *dist, POINT4D *p_located); + +/** +* Add a measure dimension to a line, interpolating linearly from the start +* to the end value. +*/ +extern LWLINE *lwline_measured_from_lwline(const LWLINE *lwline, double m_start, double m_end); +extern LWMLINE* lwmline_measured_from_lwmline(const LWMLINE *lwmline, double m_start, double m_end); + +/** +* Determine the location(s) along a measured line where m occurs and +* return as a multipoint. Offset to left (positive) or right (negative). +*/ +extern LWGEOM* lwgeom_locate_along(const LWGEOM *lwin, double m, double offset); + +/** +* Determine the segments along a measured line that fall within the m-range +* given. Return as a multiline or geometrycollection. +* Offset to left (positive) or right (negative). +*/ +extern LWCOLLECTION* lwgeom_locate_between(const LWGEOM *lwin, double from, double to, double offset); + +/** +* Find the measure value at the location on the line closest to the point. +*/ +extern double lwgeom_interpolate_point(const LWGEOM *lwin, const LWPOINT *lwpt); + +/** +* Find the time of closest point of approach +* +* @param mindist if not null will be set to the minimum distance between +* the trajectories at the closest point of approach. +* +* @return the time value in which the minimum distance was reached, -1 +* if inputs are invalid (lwerror is called in that case), +* -2 if the trajectories do not share any point in time. +*/ +extern double lwgeom_tcpa(const LWGEOM *g1, const LWGEOM *g2, double *mindist); + +/** +* Is the closest point of approach within a distance ? +* +* @return LW_TRUE or LW_FALSE +*/ +extern int lwgeom_cpa_within(const LWGEOM *g1, const LWGEOM *g2, double maxdist); + +/** +* Return LW_TRUE or LW_FALSE depending on whether or not a geometry is +* a linestring with measure value growing from start to end vertex +*/ +extern int lwgeom_is_trajectory(const LWGEOM *geom); +extern int lwline_is_trajectory(const LWLINE *geom); + +/* + * Ensure every segment is at most 'dist' long. + * Returned LWGEOM might is unchanged if a POINT. + */ +extern LWGEOM *lwgeom_segmentize2d(const LWGEOM *line, double dist); +extern POINTARRAY *ptarray_segmentize2d(const POINTARRAY *ipa, double dist); +extern LWLINE *lwline_segmentize2d(const LWLINE *line, double dist); +extern LWPOLY *lwpoly_segmentize2d(const LWPOLY *line, double dist); +extern LWCOLLECTION *lwcollection_segmentize2d(const LWCOLLECTION *coll, double dist); + +/* + * Point density functions + */ +extern LWMPOINT *lwpoly_to_points(const LWPOLY *poly, uint32_t npoints, int32_t seed); +extern LWMPOINT *lwmpoly_to_points(const LWMPOLY *mpoly, uint32_t npoints, int32_t seed); +extern LWMPOINT *lwgeom_to_points(const LWGEOM *lwgeom, uint32_t npoints, int32_t seed); + +/* + * Geometric median + */ +extern LWPOINT* lwgeom_median(const LWGEOM *g, double tol, uint32_t maxiter, char fail_if_not_converged); +extern LWPOINT* lwmpoint_median(const LWMPOINT *g, double tol, uint32_t maxiter, char fail_if_not_converged); + +/** +* Calculate the GeoHash (http://geohash.org) string for a geometry. Caller must free. +*/ +lwvarlena_t *lwgeom_geohash(const LWGEOM *lwgeom, int precision); +unsigned int geohash_point_as_int(POINT2D *pt); + + +/** +* The return values of lwline_crossing_direction() +*/ +enum CG_LINE_CROSS_TYPE { + LINE_NO_CROSS = 0, + LINE_CROSS_LEFT = -1, + LINE_CROSS_RIGHT = 1, + LINE_MULTICROSS_END_LEFT = -2, + LINE_MULTICROSS_END_RIGHT = 2, + LINE_MULTICROSS_END_SAME_FIRST_LEFT = -3, + LINE_MULTICROSS_END_SAME_FIRST_RIGHT = 3 +}; + +/** +* Given two lines, characterize how (and if) they cross each other +*/ +int lwline_crossing_direction(const LWLINE *l1, const LWLINE *l2); + +/** +* Given a geometry clip based on the from/to range of one of its ordinates (x, y, z, m). Use for m- and z- clipping. +*/ +LWCOLLECTION* lwgeom_clip_to_ordinate_range(const LWGEOM *lwin, char ordinate, double from, double to, double offset); + +/** + * Macros for specifying GML options. + * @{ + */ +/** For GML3 only, include srsDimension attribute in output */ +#define LW_GML_IS_DIMS (1<<0) +/** For GML3 only, declare that datas are lat/lon. Swaps axis order */ +#define LW_GML_IS_DEGREE (1<<1) +/** For GML3, use rather than for lines */ +#define LW_GML_SHORTLINE (1<<2) +/** For GML2 and GML3, output only extent of geometry */ +#define LW_GML_EXTENT (1<<4) + + +#define IS_DIMS(x) ((x) & LW_GML_IS_DIMS) +#define IS_DEGREE(x) ((x) & LW_GML_IS_DEGREE) +/** @} */ + +/** + * Macros for specifying X3D options. + * @{ + */ +/** For flip X/Y coordinates to Y/X */ +#define LW_X3D_FLIP_XY (1<<0) +#define LW_X3D_USE_GEOCOORDS (1<<1) +#define X3D_USE_GEOCOORDS(x) ((x) & LW_X3D_USE_GEOCOORDS) + + + +extern lwvarlena_t* lwgeom_to_gml2(const LWGEOM *geom, const char *srs, int precision, const char *prefix); +extern lwvarlena_t* lwgeom_extent_to_gml2(const LWGEOM *geom, const char *srs, int precision, const char *prefix); +/** + * @param opts output options bitfield, see LW_GML macros for meaning + */ +extern lwvarlena_t* lwgeom_extent_to_gml3(const LWGEOM *geom, const char *srs, int precision, int opts, const char *prefix); +extern lwvarlena_t* lwgeom_to_gml3(const LWGEOM *geom, const char *srs, int precision, int opts, const char *prefix, const char *id); +extern lwvarlena_t* lwgeom_to_kml2(const LWGEOM *geom, int precision, const char *prefix); +extern lwvarlena_t* lwgeom_to_geojson(const LWGEOM *geo, const char *srs, int precision, int has_bbox); +extern lwvarlena_t* lwgeom_to_x3d3(const LWGEOM *geom, int precision, int opts, const char *defid); +extern lwvarlena_t* lwgeom_to_svg(const LWGEOM *geom, int precision, int relative); +extern lwvarlena_t* lwgeom_to_encoded_polyline(const LWGEOM *geom, int precision); + +/** + * Create an LWGEOM object from a GeoJSON representation + * + * @param geojson the GeoJSON input + * @param srs output parameter. Will be set to a newly allocated + * string holding the spatial reference string, or NULL + * if no such parameter is found in input. + * If not null, the pointer must be freed with lwfree. + */ +extern LWGEOM* lwgeom_from_geojson(const char *geojson, char **srs); + +/** + * Create an LWGEOM object from an Encoded Polyline representation + * + * @param encodedpolyline the Encoded Polyline input + */ +extern LWGEOM* lwgeom_from_encoded_polyline(const char *encodedpolyline, int precision); + +/** +* Initialize a spheroid object for use in geodetic functions. +*/ +extern void spheroid_init(SPHEROID *s, double a, double b); + +/** +* Calculate the geodetic distance from lwgeom1 to lwgeom2 on the spheroid. +* A spheroid with major axis == minor axis will be treated as a sphere. +* Pass in a tolerance in spheroid units. +*/ +extern double lwgeom_distance_spheroid(const LWGEOM *lwgeom1, const LWGEOM *lwgeom2, const SPHEROID *spheroid, double tolerance); + +/** +* Calculate the location of a point on a spheroid, give a start point, bearing and distance. +*/ +extern LWPOINT* lwgeom_project_spheroid(const LWPOINT *r, const SPHEROID *spheroid, double distance, double azimuth); + +/** +* Calculate the location of a point on a spheroid, give a start point, end point and distance. +*/ +extern LWPOINT* lwgeom_project_spheroid_lwpoint(const LWPOINT *from, const LWPOINT *to, const SPHEROID *spheroid, double distance); + +/** +* Derive a new geometry with vertices added to ensure no vertex is more +* than max_seg_length (in radians) from any other vertex. +*/ +extern LWGEOM* lwgeom_segmentize_sphere(const LWGEOM *lwg_in, double max_seg_length); + +/** +* Calculate the bearing between two points on a spheroid. +*/ +extern double lwgeom_azumith_spheroid(const LWPOINT *r, const LWPOINT *s, const SPHEROID *spheroid); + +/** +* Calculate the geodetic area of a lwgeom on the sphere. The result +* will be multiplied by the average radius of the supplied spheroid. +*/ +extern double lwgeom_area_sphere(const LWGEOM *lwgeom, const SPHEROID *spheroid); + +/** +* Calculate the geodetic area of a lwgeom on the spheroid. The result +* will have the squared units of the spheroid axes. +*/ +extern double lwgeom_area_spheroid(const LWGEOM *lwgeom, const SPHEROID *spheroid); + +/** +* Calculate the geodetic length of a lwgeom on the unit sphere. The result +* will have to by multiplied by the real radius to get the real length. +*/ +extern double lwgeom_length_spheroid(const LWGEOM *geom, const SPHEROID *s); + +/** +* Calculate covers predicate for two lwgeoms on the sphere. Currently +* only handles point-in-polygon. +*/ +extern int lwgeom_covers_lwgeom_sphere(const LWGEOM *lwgeom1, const LWGEOM *lwgeom2); + + +extern LWGEOM * geography_substring(const LWLINE *line, + const SPHEROID *s, + double from, double to, + double tolerance); + +extern LWGEOM * geography_interpolate_points( + const LWLINE *line, + double length_fraction, + const SPHEROID *s, char repeat); + +extern double +ptarray_locate_point_spheroid( + const POINTARRAY *pa, + const POINT4D *p4d, + const SPHEROID *s, + double tolerance, + double *mindistout, + POINT4D *proj4d); + + +typedef struct { + POINT2D* center; + double radius; +} LWBOUNDINGCIRCLE; + +extern void lwboundingcircle_destroy(LWBOUNDINGCIRCLE* c); + +/* Calculates the minimum circle that encloses all of the points in g, using a + * two-dimensional implementation of the algorithm proposed in: + * + * Welzl, Emo (1991), "Smallest enclosing disks (balls and elipsoids)." + * New Results and Trends in Computer Science (H. Maurer, Ed.), Lecture Notes + * in Computer Science, 555 (1991) 359-370. + * + * Available online at the time of this writing at + * https://www.inf.ethz.ch/personal/emo/PublFiles/SmallEnclDisk_LNCS555_91.pdf + * + * Returns NULL if the circle could not be calculated. + */ +extern LWBOUNDINGCIRCLE* lwgeom_calculate_mbc(const LWGEOM* g); + +/** + * Swap ordinate values in every vertex of the geometry. + * + * Ordinates to swap are specified using an index with meaning: + * 0=x, 1=y, 2=z, 3=m + * + * Swapping an existing ordinate with an unexisting one results + * in undefined value being written in the existing ordinate. + * Caller should verify and prevent such calls. + * + * Availability: 2.2.0 + */ +extern void lwgeom_swap_ordinates(LWGEOM *in, LWORD o1, LWORD o2); + + +struct LWPOINTITERATOR; +typedef struct LWPOINTITERATOR LWPOINTITERATOR; + +/** + * Create a new LWPOINTITERATOR over supplied LWGEOM* + */ +extern LWPOINTITERATOR* lwpointiterator_create(const LWGEOM* g); + +/** + * Create a new LWPOINTITERATOR over supplied LWGEOM* + * Supports modification of coordinates during iteration. + */ +extern LWPOINTITERATOR* lwpointiterator_create_rw(LWGEOM* g); + +/** + * Free all memory associated with the iterator + */ +extern void lwpointiterator_destroy(LWPOINTITERATOR* s); + +/** + * Returns LW_TRUE if there is another point available in the iterator. + */ +extern int lwpointiterator_has_next(LWPOINTITERATOR* s); + +/** + * Attempts to replace the next point int the iterator with p, and advances + * the iterator to the next point. + * Returns LW_SUCCESS if the assignment was successful, LW_FAILURE otherwise. + * */ +extern int lwpointiterator_modify_next(LWPOINTITERATOR* s, const POINT4D* p); + +/** + * Attempts to assign the next point in the iterator to p, and advances + * the iterator to the next point. If p is NULL, the iterator will be + * advanced without reading a point. + * Returns LW_SUCCESS if the assignment was successful, LW_FAILURE otherwise. + * */ +extern int lwpointiterator_next(LWPOINTITERATOR* s, POINT4D* p); + +/** + * Attempts to assigns the next point in the iterator to p. Does not advance. + * Returns LW_SUCCESS if the assignment was successful, LW_FAILURE otherwise. + */ +extern int lwpointiterator_peek(LWPOINTITERATOR* s, POINT4D* p); + + +/** +* Convert a single hex digit into the corresponding char +*/ +extern uint8_t parse_hex(char *str); + +/** +* Convert a char into a human readable hex digit +*/ +extern void deparse_hex(uint8_t str, char *result); + + + +/*********************************************************************** +** Functions for managing serialized forms and bounding boxes. +*/ + +/** +* Check that coordinates of LWGEOM are all within the geodetic range (-180, -90, 180, 90) +*/ +extern int lwgeom_check_geodetic(const LWGEOM *geom); + +/** +* Gently move coordinates of LWGEOM if they are close enough into geodetic range. +*/ +extern int lwgeom_nudge_geodetic(LWGEOM *geom); + +/** +* Force coordinates of LWGEOM into geodetic range (-180, -90, 180, 90) +*/ +extern int lwgeom_force_geodetic(LWGEOM *geom); + +/** +* Set the FLAGS geodetic bit on geometry an all sub-geometries and pointlists +*/ +extern void lwgeom_set_geodetic(LWGEOM *geom, int value); + +/** +* Calculate the geodetic bounding box for an LWGEOM. Z/M coordinates are +* ignored for this calculation. Pass in non-null, geodetic bounding box for function +* to fill out. LWGEOM must have been built from a GSERIALIZED to provide +* double aligned point arrays. +*/ +extern int lwgeom_calculate_gbox_geodetic(const LWGEOM *geom, GBOX *gbox); + +/** +* Calculate the 2-4D bounding box of a geometry. Z/M coordinates are honored +* for this calculation, though for curves they are not included in calculations +* of curvature. +*/ +extern int lwgeom_calculate_gbox_cartesian(const LWGEOM *lwgeom, GBOX *gbox); + +/** +* Calculate bounding box of a geometry, automatically taking into account +* whether it is cartesian or geodetic. +*/ +extern int lwgeom_calculate_gbox(const LWGEOM *lwgeom, GBOX *gbox); + +/** +* Calculate geodetic (x/y/z) box and add values to gbox. Return #LW_SUCCESS on success. +*/ +extern int ptarray_calculate_gbox_geodetic(const POINTARRAY *pa, GBOX *gbox); + +/** +* Calculate box (x/y) and add values to gbox. Return #LW_SUCCESS on success. +*/ +extern int ptarray_calculate_gbox_cartesian(const POINTARRAY *pa, GBOX *gbox ); + +/** +* Calculate a spherical point that falls outside the geocentric gbox +*/ +int gbox_pt_outside(const GBOX *gbox, POINT2D *pt_outside); + +/** +* Create a new gbox with the dimensionality indicated by the flags. Caller +* is responsible for freeing. +*/ +extern GBOX* gbox_new(lwflags_t flags); + +/** +* Zero out all the entries in the #GBOX. Useful for cleaning +* statically allocated gboxes. +*/ +extern void gbox_init(GBOX *gbox); + +/** +* Update the merged #GBOX to be large enough to include itself and the new box. +*/ +extern int gbox_merge(const GBOX *new_box, GBOX *merged_box); + +/** +* Update the output #GBOX to be large enough to include both inputs. +*/ +extern int gbox_union(const GBOX *g1, const GBOX *g2, GBOX *gout); + +/** +* Move the box minimums down and the maximums up by the distance provided. +*/ +extern void gbox_expand(GBOX *g, double d); + +/** +* Move the box minimums down and the maximums up by the distances provided. +*/ +extern void gbox_expand_xyzm(GBOX *g, double dx, double dy, double dz, double dm); + +/** +* Initialize a #GBOX using the values of the point. +*/ +extern int gbox_init_point3d(const POINT3D *p, GBOX *gbox); + +/** +* Update the #GBOX to be large enough to include itself and the new point. +*/ +extern int gbox_merge_point3d(const POINT3D *p, GBOX *gbox); + +/** +* Return true if the point is inside the gbox +*/ +extern int gbox_contains_point3d(const GBOX *gbox, const POINT3D *pt); + +/** +* Allocate a string representation of the #GBOX, based on dimensionality of flags. +*/ +extern char* gbox_to_string(const GBOX *gbox); + +/** +* Return a copy of the #GBOX, based on dimensionality of flags. +*/ +extern GBOX* gbox_copy(const GBOX *gbox); + +/** +* Warning, do not use this function, it is very particular about inputs. +*/ +extern GBOX* gbox_from_string(const char *str); + +/** +* Return #LW_TRUE if the #GBOX overlaps, #LW_FALSE otherwise. +*/ +extern int gbox_overlaps(const GBOX *g1, const GBOX *g2); + +/** +* Return #LW_TRUE if the #GBOX overlaps on the 2d plane, #LW_FALSE otherwise. +*/ +extern int gbox_overlaps_2d(const GBOX *g1, const GBOX *g2); + +/** +* Return #LW_TRUE if the first #GBOX contains the second on the 2d plane, #LW_FALSE otherwise. +*/ +extern int gbox_contains_2d(const GBOX *g1, const GBOX *g2); + +/** +* Copy the values of original #GBOX into duplicate. +*/ +extern void gbox_duplicate(const GBOX *original, GBOX *duplicate); + +/** +* Return the number of bytes necessary to hold a #GBOX of this dimension in +* serialized form. +*/ +extern size_t gbox_serialized_size(lwflags_t flags); + +/** +* Check if 2 given Gbox are the same +*/ +extern int gbox_same(const GBOX *g1, const GBOX *g2); + +/** +* Check if 2 given GBOX are the same in x and y +*/ +extern int gbox_same_2d(const GBOX *g1, const GBOX *g2); + +/** +* Check if two given GBOX are the same in x and y, or would round to the same +* GBOX in x and if serialized in GSERIALIZED +*/ +extern int gbox_same_2d_float(const GBOX *g1, const GBOX *g2); + +/** + * Round given GBOX to float boundaries + * + * This turns a GBOX into the version it would become + * after a serialize/deserialize round trip. + */ +extern void gbox_float_round(GBOX *gbox); + +/** +* Return false if any of the dimensions is NaN or infinite +*/ +extern int gbox_is_valid(const GBOX *gbox); + +/** +* Return a sortable key based on the center point of the GBOX. +*/ +extern uint64_t gbox_get_sortable_hash(const GBOX *g, const int32_t srid); + +/** +* Return a sortable key based on gserialized. +*/ +extern uint64_t gserialized_get_sortable_hash(const GSERIALIZED *g); + +/** +* Utility function to get type number from string. For example, a string 'POINTZ' +* would return type of 1 and z of 1 and m of 0. Valid +*/ +extern int geometry_type_from_string(const char *str, uint8_t *type, int *z, int *m); + +/** + * Parser check flags + * + * @see lwgeom_from_wkb + * @see lwgeom_from_hexwkb + * @see lwgeom_parse_wkt + */ +#define LW_PARSER_CHECK_MINPOINTS 1 +#define LW_PARSER_CHECK_ODD 2 +#define LW_PARSER_CHECK_CLOSURE 4 +#define LW_PARSER_CHECK_ZCLOSURE 8 + +#define LW_PARSER_CHECK_NONE 0 +#define LW_PARSER_CHECK_ALL (LW_PARSER_CHECK_MINPOINTS | LW_PARSER_CHECK_ODD | LW_PARSER_CHECK_CLOSURE) + +/** + * Parser result structure: returns the result of attempting to convert + * (E)WKT/(E)WKB to LWGEOM + */ +typedef struct struct_lwgeom_parser_result +{ + const char *wkinput; /* Copy of pointer to input WKT/WKB */ + uint8_t *serialized_lwgeom; /* Pointer to serialized LWGEOM */ + size_t size; /* Size of serialized LWGEOM in bytes */ + LWGEOM *geom; /* Pointer to LWGEOM struct */ + const char *message; /* Error/warning message */ + int errcode; /* Error/warning number */ + int errlocation; /* Location of error */ + int parser_check_flags; /* Bitmask of validity checks run during this parse */ +} +LWGEOM_PARSER_RESULT; + +/* + * Parser error messages (these must match the message array in lwgparse.c) + */ +#define PARSER_ERROR_MOREPOINTS 1 +#define PARSER_ERROR_ODDPOINTS 2 +#define PARSER_ERROR_UNCLOSED 3 +#define PARSER_ERROR_MIXDIMS 4 +#define PARSER_ERROR_INVALIDGEOM 5 +#define PARSER_ERROR_INVALIDWKBTYPE 6 +#define PARSER_ERROR_INCONTINUOUS 7 +#define PARSER_ERROR_TRIANGLEPOINTS 8 +#define PARSER_ERROR_LESSPOINTS 9 +#define PARSER_ERROR_OTHER 10 + + + +/* + * Unparser result structure: returns the result of attempting to convert LWGEOM to (E)WKT/(E)WKB + */ +typedef struct struct_lwgeom_unparser_result +{ + uint8_t *serialized_lwgeom; /* Copy of pointer to input serialized LWGEOM */ + char *wkoutput; /* Pointer to WKT or WKB output */ + size_t size; /* Size of serialized LWGEOM in bytes */ + const char *message; /* Error/warning message */ + int errlocation; /* Location of error */ +} +LWGEOM_UNPARSER_RESULT; + +/* + * Unparser error messages (these must match the message array in lwgunparse.c) + */ +#define UNPARSER_ERROR_MOREPOINTS 1 +#define UNPARSER_ERROR_ODDPOINTS 2 +#define UNPARSER_ERROR_UNCLOSED 3 + + +/* +** Variants available for WKB and WKT output types +*/ + +#define WKB_ISO 0x01 +#define WKB_SFSQL 0x02 +#define WKB_EXTENDED 0x04 +#define WKB_NDR 0x08 +#define WKB_XDR 0x10 +#define WKB_HEX 0x20 +#define WKB_NO_NPOINTS 0x40 /* Internal use only */ +#define WKB_NO_SRID 0x80 /* Internal use only */ + +#define WKT_ISO 0x01 +#define WKT_SFSQL 0x02 +#define WKT_EXTENDED 0x04 + + +/* +** Variants available for TWKB +*/ +#define TWKB_BBOX 0x01 /* User wants bboxes */ +#define TWKB_SIZE 0x02 /* User wants sizes */ +#define TWKB_ID 0x04 /* User wants id */ +#define TWKB_NO_TYPE 0x10 /* No type because it is a sub geometry */ +#define TWKB_NO_ID 0x20 /* No ID because it is a subgeometry */ +#define TWKB_DEFAULT_PRECISION 0 /* Aim for 1m (or ft) rounding by default */ + +/* +** New parsing and unparsing functions. +*/ + +/** +* @param geom geometry to convert to WKT +* @param variant output format to use (WKT_ISO, WKT_SFSQL, WKT_EXTENDED) +* @param precision Double precision +* @param size_out (Out parameter) size of the buffer +*/ +extern char* lwgeom_to_wkt(const LWGEOM *geom, uint8_t variant, int precision, size_t *size_out); + +/** +* @param geom geometry to convert to WKT +* @param variant output format to use (WKT_ISO, WKT_SFSQL, WKT_EXTENDED) +* @param precision Double precision +*/ +extern lwvarlena_t* lwgeom_to_wkt_varlena(const LWGEOM *geom, uint8_t variant, int precision); + +/** +* @param geom geometry to convert to WKB +* @param variant output format to use +* (WKB_ISO, WKB_SFSQL, WKB_EXTENDED, WKB_NDR, WKB_XDR) +*/ +extern uint8_t* lwgeom_to_wkb_buffer(const LWGEOM *geom, uint8_t variant); +extern lwvarlena_t* lwgeom_to_wkb_varlena(const LWGEOM *geom, uint8_t variant); + +/** +* @param geom geometry to convert to HEXWKB +* @param variant output format to use +* (WKB_ISO, WKB_SFSQL, WKB_EXTENDED, WKB_NDR, WKB_XDR) +* @param size_out (Out parameter) size of the buffer +*/ +extern char* lwgeom_to_hexwkb_buffer(const LWGEOM *geom, uint8_t variant); +extern lwvarlena_t* lwgeom_to_hexwkb_varlena(const LWGEOM *geom, uint8_t variant); + +/** +* @param lwgeom geometry to convert to EWKT +*/ +extern char *lwgeom_to_ewkt(const LWGEOM *lwgeom); + +/** + * @param wkb_size length of WKB byte buffer + * @param wkb WKB byte buffer + * @param check parser check flags, see LW_PARSER_CHECK_* macros + */ +extern LWGEOM* lwgeom_from_wkb(const uint8_t *wkb, const size_t wkb_size, const char check); + +/** + * @param wkt WKT string + * @param check parser check flags, see LW_PARSER_CHECK_* macros + */ +extern LWGEOM* lwgeom_from_wkt(const char *wkt, const char check); + +/** + * @param check parser check flags, see LW_PARSER_CHECK_* macros + */ +extern LWGEOM* lwgeom_from_hexwkb(const char *hexwkb, const char check); + +extern uint8_t* bytes_from_hexbytes(const char *hexbuf, size_t hexsize); + +extern char* hexbytes_from_bytes(const uint8_t *bytes, size_t size); + +/* +* WKT detailed parsing support +*/ +extern int lwgeom_parse_wkt(LWGEOM_PARSER_RESULT *parser_result, char *wktstr, int parse_flags); +void lwgeom_parser_result_init(LWGEOM_PARSER_RESULT *parser_result); +void lwgeom_parser_result_free(LWGEOM_PARSER_RESULT *parser_result); + + +/* Memory management */ +extern void *lwalloc(size_t size); +extern void *lwrealloc(void *mem, size_t size); +extern void lwfree(void *mem); + +/* Utilities */ +extern char *lwmessage_truncate(char *str, int startpos, int endpos, int maxlength, int truncdirection); + +/* +* TWKB functions +*/ + +/** + * @param twkb Input twkb buffer + * @param twkb_size parser check flags, see LW_PARSER_CHECK_* macros + * @param check parser check flags, see LW_PARSER_CHECK_* macros + */ +extern LWGEOM* lwgeom_from_twkb(const uint8_t *twkb, size_t twkb_size, char check); + +/** + * @param geom input geometry + * @param variant what variations on TWKB are requested? + * @param twkb_size returns the length of the output TWKB in bytes if set + */ +extern lwvarlena_t* lwgeom_to_twkb(const LWGEOM *geom, uint8_t variant, int8_t precision_xy, int8_t precision_z, int8_t precision_m); + +extern lwvarlena_t* lwgeom_to_twkb_with_idlist(const LWGEOM *geom, int64_t *idlist, uint8_t variant, int8_t precision_xy, int8_t precision_z, int8_t precision_m); + +/** + * Trim the bits of an LWGEOM in place, to optimize it for compression. + * Sets all bits to zero that are not required to maintain a specified + * number of digits after the decimal point. Negative precision values + * indicate digits before the decimal point do not need to be preserved. + * + * @param geom input geometry + * @param prec_x precision (digits after decimal point) in x dimension + * @param prec_y precision (digits after decimal point) in y dimension + * @param prec_z precision (digits after decimal point) in z dimension + * @param prec_m precision (digits after decimal point) in m dimension + */ +extern void lwgeom_trim_bits_in_place(LWGEOM *geom, int32_t prec_x, int32_t prec_y, int32_t prec_z, int32_t prec_m); + +extern LWGEOM* lwgeom_boundary(LWGEOM* lwgeom); + +/******************************************************************************* + * SQLMM internal functions + ******************************************************************************/ + +/** +* Geometry includes at least one actual circular arc +*/ +int lwgeom_has_arc(const LWGEOM *geom); +/** +* Geometry type is one of the potentially "arc containing" +* types (circstring, multicurve, etc) but does not necessarily +* contain an actual arc. +*/ +int lwgeom_type_arc(const LWGEOM *geom); +/** +* Convert type with arcs into equivalent linearized type +*/ +LWGEOM *lwgeom_stroke(const LWGEOM *geom, uint32_t perQuad); +/** +* Convert linearized type into arc type, de-linearizing the +* strokes where possible. +*/ +LWGEOM *lwgeom_unstroke(const LWGEOM *geom); + +/** + * Semantic of the `tolerance` argument passed to + * lwcurve_linearize + */ +typedef enum { + /** + * Tolerance expresses the number of segments to use + * for each quarter of circle (quadrant). Must be + * an integer. + */ + LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD = 0, + /** + * Tolerance expresses the maximum distance between + * an arbitrary point on the curve and the closest + * point to it on the resulting approximation, in + * cartesian units. + */ + LW_LINEARIZE_TOLERANCE_TYPE_MAX_DEVIATION = 1, + /** + * Tolerance expresses the maximum angle between + * the radii generating approximation line vertices, + * given in radiuses. A value of 1 would result + * in an approximation of a semicircle composed by + * 180 segments + */ + LW_LINEARIZE_TOLERANCE_TYPE_MAX_ANGLE = 2 +} LW_LINEARIZE_TOLERANCE_TYPE; + +typedef enum { + /** + * Symmetric linearization means that the output + * vertices would be the same no matter the order + * of the points defining the input curve. + */ + LW_LINEARIZE_FLAG_SYMMETRIC = 1 << 0, + + /** + * Retain angle instructs the engine to try its best + * to retain the requested angle between generating + * radii (where angle can be given explicitly with + * LW_LINEARIZE_TOLERANCE_TYPE_MAX_ANGLE or implicitly + * with LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD or + * LW_LINEARIZE_TOLERANCE_TYPE_MAX_DEVIATION). + * + * It only makes sense with LW_LINEARIZE_FLAG_SYMMETRIC + * which would otherwise reduce the angle as needed to + * keep it constant among all radiis so that all + * segments are of the same length. + * + * When this flag is set, the first and last generating + * angles (and thus the first and last segments) may + * instead be smaller (shorter) than the others. + * + */ + LW_LINEARIZE_FLAG_RETAIN_ANGLE = 1 << 1 +} LW_LINEARIZE_FLAGS; + +/** + * @param geom input geometry + * @param tol tolerance, semantic driven by tolerance_type + * @param type see LW_LINEARIZE_TOLERANCE_TYPE + * @param flags bitwise OR of operational flags, see LW_LINEARIZE_FLAGS + * + * @return a newly allocated LWGEOM + */ +extern LWGEOM* lwcurve_linearize(const LWGEOM *geom, double tol, LW_LINEARIZE_TOLERANCE_TYPE type, int flags); + +/******************************************************************************* + * GEOS proxy functions on LWGEOM + ******************************************************************************/ + +/** Return GEOS version string (not to be freed) */ +const char* lwgeom_geos_version(void); +const char* lwgeom_geos_compiled_version(void); + +/** Convert an LWGEOM to a GEOS Geometry and convert back -- for debug only */ +LWGEOM* lwgeom_geos_noop(const LWGEOM *geom) ; + +LWGEOM *lwgeom_normalize(const LWGEOM *geom); +LWGEOM *lwgeom_intersection(const LWGEOM *geom1, const LWGEOM *geom2); +LWGEOM *lwgeom_intersection_prec(const LWGEOM *geom1, const LWGEOM *geom2, double gridSize); +LWGEOM *lwgeom_difference(const LWGEOM *geom1, const LWGEOM *geom2); +LWGEOM *lwgeom_difference_prec(const LWGEOM *geom1, const LWGEOM *geom2, double gridSize); +LWGEOM *lwgeom_symdifference(const LWGEOM* geom1, const LWGEOM* geom2); +LWGEOM *lwgeom_symdifference_prec(const LWGEOM* geom1, const LWGEOM* geom2, double gridSize); +LWGEOM *lwgeom_pointonsurface(const LWGEOM* geom); +LWGEOM *lwgeom_reduceprecision(const LWGEOM* geom, double gridSize); +LWGEOM *lwgeom_centroid(const LWGEOM* geom); +LWGEOM *lwgeom_union(const LWGEOM *geom1, const LWGEOM *geom2); +LWGEOM *lwgeom_union_prec(const LWGEOM *geom1, const LWGEOM *geom2, double gridSize); +LWGEOM *lwgeom_linemerge(const LWGEOM *geom1); +LWGEOM *lwgeom_linemerge_directed(const LWGEOM *geom1, int directed); +LWGEOM *lwgeom_unaryunion(const LWGEOM *geom1); +LWGEOM *lwgeom_unaryunion_prec(const LWGEOM *geom1, double gridSize); +LWGEOM *lwgeom_clip_by_rect(const LWGEOM *geom1, double x0, double y0, double x1, double y1); +LWCOLLECTION *lwgeom_subdivide(const LWGEOM *geom, uint32_t maxvertices); +LWCOLLECTION *lwgeom_subdivide_prec(const LWGEOM *geom, uint32_t maxvertices, double gridSize); + +/** + * Snap vertices and segments of a geometry to another using a given tolerance. + * + * @param geom1 the geometry to snap + * @param geom2 the geometry to snap to + * @param tolerance the distance under which vertices and segments are snapped + */ +LWGEOM* lwgeom_snap(const LWGEOM* geom1, const LWGEOM* geom2, double tolerance); + +/* + * Return the set of paths shared between two linear geometries, + * and their direction (same or opposite). + * + * @param geom1 a lineal geometry + * @param geom2 another lineal geometry + */ +LWGEOM* lwgeom_sharedpaths(const LWGEOM* geom1, const LWGEOM* geom2); + +/* + * An offset curve against the input line. + * + * @param geom a lineal geometry or collection of them + * @param size offset distance. Offset left if negative and right if positive + * @param quadsegs number of quadrature segments in curves (try 8) + * @param joinStyle (1 = round, 2 = mitre, 3 = bevel) + * @param mitreLimit (try 5.0) + * @return derived geometry (linestring or multilinestring) + * + */ +LWGEOM* lwgeom_offsetcurve(const LWGEOM *geom, double size, int quadsegs, int joinStyle, double mitreLimit); + +/* + * Return true if the input geometry is "simple" as per OGC defn. + * + * @return 1 if simple, 0 if non-simple, -1 on exception (lwerror is called + * in that case) + */ +int lwgeom_is_simple(const LWGEOM *lwgeom); + + +/******************************************************************************* + * PROJ4-dependent extra functions on LWGEOM + ******************************************************************************/ + +int lwgeom_transform_from_str(LWGEOM *geom, const char* instr, const char* outstr); +/** + * Transform (reproject) a geometry in-place. + * @param geom the geometry to transform + * @param pj the transformation + */ +int lwgeom_transform(LWGEOM *geom, LWPROJ* pj); +int ptarray_transform(POINTARRAY *pa, LWPROJ* pj); +int box3d_transform(GBOX *box, LWPROJ *pj); + +/** + * Allocate a new LWPROJ containing the reference to the PROJ's PJ + * If extra_geography_data is true, it will generate the following values for + * the source srs: is_latlong (geometric or not) and spheroid values + */ +LWPROJ *lwproj_from_str(const char* str_in, const char* str_out); + +/** + * Transform (reproject) a geometry in-place using a PROJ pipeline. + * @param geom the geometry to transform + * @param pipeline the coordinate operation pipeline string. Either: + * - `urn:ogc:def:coordinateOperation:AUTHORITY::CODE` + * - a PROJ pipeline string: `+proj=pipeline ...` + * - concatenated operations: `urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618` + * @param is_forward whether to execute the pipeline in a forward or inverse + * direction. + */ +int lwgeom_transform_pipeline(LWGEOM *geom, const char* pipeline, bool is_forward); + +/** + * Allocate a new LWPROJ containing the reference to the PROJ's PJ using a + * PROJ pipeline definition: + * @param str_pipeline the coordinate operation pipeline string. Either: + * - `urn:ogc:def:coordinateOperation:AUTHORITY::CODE` + * - a PROJ pipeline: `+proj=pipeline ...` + * - concatenated operations: `urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618` + * - any other coordinate operation accepted via `proj_create()` + * @param is_foward whether to execute the the pipeline in a forward or inverse + * direction. + */ +LWPROJ *lwproj_from_str_pipeline(const char* str_pipeline, bool is_forward); + + +/******************************************************************************* + * GEOS-dependent extra functions on LWGEOM + ******************************************************************************/ + +/** + * Take a geometry and return an areal geometry + * (Polygon or MultiPolygon). + * Actually a wrapper around GEOSpolygonize, + * transforming the resulting collection into + * a valid polygon Geometry. + */ +LWGEOM* lwgeom_buildarea(const LWGEOM *geom) ; + +/** + * Attempts to make an invalid geometries valid w/out losing points. + */ +LWGEOM* lwgeom_make_valid(LWGEOM* geom); +LWGEOM* lwgeom_make_valid_params(LWGEOM* geom, char *make_valid_params); + +/* + * Split (multi)polygon by line; (multi)line by (multi)line, + * (multi)point or (multi)polygon boundary. + * + * Collections are accepted as first argument. + * Returns all obtained pieces as a collection. + */ +LWGEOM* lwgeom_split(const LWGEOM* lwgeom_in, const LWGEOM* blade_in); + +/* + * Fully node a set of linestrings, using the least nodes preserving + * all the input ones. + */ +LWGEOM* lwgeom_node(const LWGEOM* lwgeom_in); + +/** + * Take vertices of a geometry and build a delaunay + * triangulation on them. + * + * @param geom the input geometry + * @param tolerance an optional snapping tolerance for improved robustness + * @param edgeOnly if non-zero the result will be a MULTILINESTRING, + * otherwise it'll be a COLLECTION of polygons. + */ +LWGEOM* lwgeom_delaunay_triangulation(const LWGEOM *geom, double tolerance, int32_t edgeOnly); + + +/** + * Take vertices of a geometry and build the Voronoi diagram + * + * @param g the input geometry + * @param env an optional envelope for clipping the results + * @param tolerance an optional snapping tolerance for improved robustness + * @param output_edges if non-zero the result will be a MULTILINESTRING, + * otherwise it'll be a COLLECTION of polygons. + */ +LWGEOM* lwgeom_voronoi_diagram(const LWGEOM* g, const GBOX* env, double tolerance, int output_edges); + +#if POSTGIS_GEOS_VERSION >= 31100 +/** + * Take a geometry and build the concave hull. The concave + * hull is smaller than the convex hull, but still encompasses all the points + * of the input. For polygon input the hull encompasses all the input + * area. For line input the hull encompasses all the input lines. + * + * @param g the input geometry + * @param ratio proportion of output vs input + * @param allow_holes can there be holes in the output. large performance penalty. + */ +LWGEOM* lwgeom_concavehull(const LWGEOM* geom, double ratio, uint32_t allow_holes); + +/** + * Computes a boundary-respecting hull of a polygonal geometry, + * with hull shape determined by a target parameter + * specifying the fraction of the input vertices retained in the result. + * Larger values produce less concave results. + * + * @param geom the input geometry + * @param vertex_fraction proportion of vertices in output vs input. + * @param is_outer expand geometry while removing edges. + */ +LWGEOM* +lwgeom_simplify_polygonal(const LWGEOM* geom, double vertex_fraction, uint32_t is_outer); + +/** + * Take vertices of a polygon and build a constrained triangulation + * that respects the boundary of the polygon. + * + * @param geom the input geometry + */ +LWGEOM* lwgeom_triangulate_polygon(const LWGEOM* geom); + +#endif + +/** +* Take a list of LWGEOMs and a number of clusters and return an integer +* array indicating which cluster each geometry is in. +* +* @param geoms the input array of LWGEOM pointers +* @param ngeoms the number of elements in the array +* @param k the number of clusters to calculate +* @param max_radius maxmimum radius of cluster before it's split +*/ +int * lwgeom_cluster_kmeans(const LWGEOM **geoms, uint32_t n, uint32_t k, double max_radius); + +#include "lwinline.h" + +#endif /* !defined _LIBLWGEOM_H */ + diff --git a/mgist-postgis/liblwgeom/liblwgeom_internal.h b/mgist-postgis/liblwgeom/liblwgeom_internal.h new file mode 100644 index 0000000..edb6a85 --- /dev/null +++ b/mgist-postgis/liblwgeom/liblwgeom_internal.h @@ -0,0 +1,493 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2011-2021 Sandro Santilli + * Copyright (C) 2011 Paul Ramsey + * Copyright (C) 2007-2008 Mark Cave-Ayland + * Copyright (C) 2001-2006 Refractions Research Inc. + * + **********************************************************************/ + + +#ifndef _LIBLWGEOM_INTERNAL_H +#define _LIBLWGEOM_INTERNAL_H 1 + +// #include "../postgis_config.h" + +#include "lwgeom_log.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#if HAVE_IEEEFP_H +#include +#endif + +#include "liblwgeom.h" + +/** +* Floating point comparators. +*/ +#define FP_TOLERANCE 1e-12 +#define FP_IS_ZERO(A) (fabs(A) <= FP_TOLERANCE) +#define FP_MAX(A, B) (((A) > (B)) ? (A) : (B)) +#define FP_MIN(A, B) (((A) < (B)) ? (A) : (B)) +#define FP_ABS(a) ((a) < (0) ? -(a) : (a)) +#define FP_EQUALS(A, B) (fabs((A)-(B)) <= FP_TOLERANCE) +#define FP_NEQUALS(A, B) (fabs((A)-(B)) > FP_TOLERANCE) +#define FP_LT(A, B) (((A) + FP_TOLERANCE) < (B)) +#define FP_LTEQ(A, B) (((A) - FP_TOLERANCE) <= (B)) +#define FP_GT(A, B) (((A) - FP_TOLERANCE) > (B)) +#define FP_GTEQ(A, B) (((A) + FP_TOLERANCE) >= (B)) +#define FP_CONTAINS_TOP(A, X, B) (FP_LT(A, X) && FP_LTEQ(X, B)) +#define FP_CONTAINS_BOTTOM(A, X, B) (FP_LTEQ(A, X) && FP_LT(X, B)) +#define FP_CONTAINS_INCL(A, X, B) (FP_LTEQ(A, X) && FP_LTEQ(X, B)) +#define FP_CONTAINS_EXCL(A, X, B) (FP_LT(A, X) && FP_LT(X, B)) +#define FP_CONTAINS(A, X, B) FP_CONTAINS_EXCL(A, X, B) + +#define STR_EQUALS(A, B) strcmp((A), (B)) == 0 +#define STR_IEQUALS(A, B) (strcasecmp((A), (B)) == 0) +#define STR_ISTARTS(A, B) (strncasecmp((A), (B), strlen((B))) == 0) + + +/* +* this will change to NaN when I figure out how to +* get NaN in a platform-independent way +*/ +#define NO_VALUE 0.0 +#define NO_Z_VALUE NO_VALUE +#define NO_M_VALUE NO_VALUE + + +/** +* Well-Known Text (WKT) Output Variant Types +*/ +#define WKT_NO_TYPE 0x08 /* Internal use only */ +#define WKT_NO_PARENS 0x10 /* Internal use only */ +#define WKT_IS_CHILD 0x20 /* Internal use only */ + +/** +* Well-Known Binary (WKB) Output Variant Types +*/ + +#define WKB_DOUBLE_SIZE 8 /* Internal use only */ +#define WKB_INT_SIZE 4 /* Internal use only */ +#define WKB_BYTE_SIZE 1 /* Internal use only */ + +/** +* Well-Known Binary (WKB) Geometry Types +*/ +#define WKB_POINT_TYPE 1 +#define WKB_LINESTRING_TYPE 2 +#define WKB_POLYGON_TYPE 3 +#define WKB_MULTIPOINT_TYPE 4 +#define WKB_MULTILINESTRING_TYPE 5 +#define WKB_MULTIPOLYGON_TYPE 6 +#define WKB_GEOMETRYCOLLECTION_TYPE 7 +#define WKB_CIRCULARSTRING_TYPE 8 +#define WKB_COMPOUNDCURVE_TYPE 9 +#define WKB_CURVEPOLYGON_TYPE 10 +#define WKB_MULTICURVE_TYPE 11 +#define WKB_MULTISURFACE_TYPE 12 +#define WKB_CURVE_TYPE 13 /* from ISO draft, not sure is real */ +#define WKB_SURFACE_TYPE 14 /* from ISO draft, not sure is real */ +#define WKB_POLYHEDRALSURFACE_TYPE 15 +#define WKB_TIN_TYPE 16 +#define WKB_TRIANGLE_TYPE 17 + + +/** +* Macro that returns: +* -1 if n < 0, +* 1 if n > 0, +* 0 if n == 0 +*/ +#define SIGNUM(n) (((n) > 0) - ((n) < 0)) + +/** +* Tolerance used to determine equality. +*/ +#define EPSILON_SQLMM 1e-8 + +/* + * Export functions + */ + +/* Any (absolute) values outside this range will be printed in scientific notation */ +#define OUT_MIN_DOUBLE 1E-8 +#define OUT_MAX_DOUBLE 1E15 +#define OUT_DEFAULT_DECIMAL_DIGITS 15 + +/* 17 digits are sufficient for round-tripping + * Then we might add up to 8 (from OUT_MIN_DOUBLE) max leading zeroes (or 2 digits for "e+") */ +#define OUT_MAX_DIGITS 17 + 8 + +/* Limit for the max amount of characters that a double can use, including dot and sign */ +/* */ +#define OUT_MAX_BYTES_DOUBLE (1 /* Sign */ + 2 /* 0.x */ + OUT_MAX_DIGITS) +#define OUT_DOUBLE_BUFFER_SIZE OUT_MAX_BYTES_DOUBLE + 1 /* +1 including NULL */ + +/** +* Constants for point-in-polygon return values +*/ +#define LW_INSIDE 1 +#define LW_BOUNDARY 0 +#define LW_OUTSIDE -1 + +/* +* Internal prototypes +*/ + +/* Machine endianness */ +#define XDR 0 /* big endian */ +#define NDR 1 /* little endian */ + + +/* +* Force dims +*/ +LWGEOM* lwgeom_force_dims(const LWGEOM *lwgeom, int hasz, int hasm, double zval, double mval); +LWPOINT* lwpoint_force_dims(const LWPOINT *lwpoint, int hasz, int hasm, double zval, double mval); +LWLINE* lwline_force_dims(const LWLINE *lwline, int hasz, int hasm, double zval, double mval); +LWPOLY* lwpoly_force_dims(const LWPOLY *lwpoly, int hasz, int hasm, double zval, double mval); +LWCOLLECTION* lwcollection_force_dims(const LWCOLLECTION *lwcol, int hasz, int hasm, double zval, double mval); +POINTARRAY* ptarray_force_dims(const POINTARRAY *pa, int hasz, int hasm, double zval, double mval); + +/** + * Swap ordinate values o1 and o2 on a given POINTARRAY + * + * Ordinates semantic is: 0=x 1=y 2=z 3=m + */ +void ptarray_swap_ordinates(POINTARRAY *pa, LWORD o1, LWORD o2); + +/* +* Is Empty? +*/ +int lwpoly_is_empty(const LWPOLY *poly); +int lwcollection_is_empty(const LWCOLLECTION *col); +int lwcircstring_is_empty(const LWCIRCSTRING *circ); +int lwtriangle_is_empty(const LWTRIANGLE *triangle); +int lwline_is_empty(const LWLINE *line); +int lwpoint_is_empty(const LWPOINT *point); + +/* +* Number of vertices? +*/ +uint32_t lwline_count_vertices(const LWLINE *line); +uint32_t lwpoly_count_vertices(const LWPOLY *poly); +uint32_t lwcollection_count_vertices(const LWCOLLECTION *col); + +/* +* DP simplification +*/ + +/** + * @param minpts minimum number of points to retain, if possible. + */ +void ptarray_simplify_in_place(POINTARRAY *pa, double tolerance, uint32_t minpts); + +/* +* The possible ways a pair of segments can interact. Returned by lw_segment_intersects +*/ +enum CG_SEGMENT_INTERSECTION_TYPE { + SEG_ERROR = -1, + SEG_NO_INTERSECTION = 0, + SEG_COLINEAR = 1, + SEG_CROSS_LEFT = 2, + SEG_CROSS_RIGHT = 3, + SEG_TOUCH_LEFT = 4, + SEG_TOUCH_RIGHT = 5 +}; + +/* +* Do the segments intersect? How? +*/ +int lw_segment_intersects(const POINT2D *p1, const POINT2D *p2, const POINT2D *q1, const POINT2D *q2); + +/* +* Get/Set an enumeratoed ordinate. (x,y,z,m) +*/ +double lwpoint_get_ordinate(const POINT4D *p, char ordinate); +void lwpoint_set_ordinate(POINT4D *p, char ordinate, double value); + +/* +* Generate an interpolated coordinate p given an interpolation value and ordinate to apply it to +*/ +int point_interpolate(const POINT4D *p1, const POINT4D *p2, POINT4D *p, int hasz, int hasm, char ordinate, double interpolation_value); + + +/* +* Geohash +*/ +int lwgeom_geohash_precision(GBOX bbox, GBOX *bounds); +lwvarlena_t *geohash_point(double longitude, double latitude, int precision); +void decode_geohash_bbox(char *geohash, double *lat, double *lon, int precision); + +/* +* Point comparisons +*/ +int p4d_same(const POINT4D *p1, const POINT4D *p2); +int p3d_same(const POINT3D *p1, const POINT3D *p2); +int p2d_same(const POINT2D *p1, const POINT2D *p2); + +/* +* Projections +*/ +int project_pt(const POINT2D *P, double distance, double azimuth, POINT2D *R); +int project_pt_pt(const POINT4D *A, const POINT4D *B, double distance, POINT4D *R); + +/* +* Area calculations +*/ +double lwpoly_area(const LWPOLY *poly); +double lwcurvepoly_area(const LWCURVEPOLY *curvepoly); +double lwtriangle_area(const LWTRIANGLE *triangle); + +/** +* Pull a #GBOX from the header of a #GSERIALIZED, if one is available. If +* it is not, return LW_FAILURE. +*/ +int gserialized_read_gbox_p(const GSERIALIZED *g, GBOX *gbox); + +/* + * Populate a bounding box *without* allocating an LWGEOM. Useful for some performance + * purposes. Use only if gserialized_read_gbox_p failed + */ +int gserialized_peek_gbox_p(const GSERIALIZED *g, GBOX *gbox); + +/** +* Calculate required memory segment to contain a serialized form of the LWGEOM. +* Primarily used internally by the serialization code. Exposed to allow the cunit +* tests to exercise it. +*/ +size_t gserialized_from_lwgeom_size(const LWGEOM *geom); + +/* +* Length calculations +*/ +double lwcompound_length(const LWCOMPOUND *comp); +double lwcompound_length_2d(const LWCOMPOUND *comp); +double lwline_length(const LWLINE *line); +double lwline_length_2d(const LWLINE *line); +double lwcircstring_length(const LWCIRCSTRING *circ); +double lwcircstring_length_2d(const LWCIRCSTRING *circ); +double lwpoly_perimeter(const LWPOLY *poly); +double lwpoly_perimeter_2d(const LWPOLY *poly); +double lwcurvepoly_perimeter(const LWCURVEPOLY *poly); +double lwcurvepoly_perimeter_2d(const LWCURVEPOLY *poly); +double lwtriangle_perimeter(const LWTRIANGLE *triangle); +double lwtriangle_perimeter_2d(const LWTRIANGLE *triangle); + +/* +* Segmentization +*/ +LWPOLY *lwcurvepoly_stroke(const LWCURVEPOLY *curvepoly, uint32_t perQuad); + +/* +* Affine +*/ +void ptarray_affine(POINTARRAY *pa, const AFFINE *affine); +void affine_invert(AFFINE *affine); + +/* +* Scale +*/ +void ptarray_scale(POINTARRAY *pa, const POINT4D *factor); + +/* +* Scroll +*/ +int ptarray_scroll_in_place(POINTARRAY *pa, const POINT4D *newbase); + +/* +* PointArray +*/ +int ptarray_has_z(const POINTARRAY *pa); +int ptarray_has_m(const POINTARRAY *pa); +double ptarray_signed_area(const POINTARRAY *pa); + +/* +* Length +*/ +double ptarray_length(const POINTARRAY *pts); +double ptarray_arc_length_2d(const POINTARRAY *pts); + +/* +* Clone support +*/ +LWPOINT *lwpoint_clone(const LWPOINT *lwgeom); +LWLINE *lwline_clone(const LWLINE *lwgeom); +LWPOLY *lwpoly_clone(const LWPOLY *lwgeom); +LWTRIANGLE *lwtriangle_clone(const LWTRIANGLE *lwgeom); +LWCOLLECTION *lwcollection_clone(const LWCOLLECTION *lwgeom); +LWCIRCSTRING *lwcircstring_clone(const LWCIRCSTRING *curve); +POINTARRAY *ptarray_clone(const POINTARRAY *ptarray); +LWLINE *lwline_clone_deep(const LWLINE *lwgeom); +LWPOLY *lwpoly_clone_deep(const LWPOLY *lwgeom); +LWCOLLECTION *lwcollection_clone_deep(const LWCOLLECTION *lwgeom); +GBOX *gbox_clone(const GBOX *gbox); + +/* +* Clockwise +*/ +void lwpoly_force_clockwise(LWPOLY *poly); +void lwtriangle_force_clockwise(LWTRIANGLE *triangle); +int lwpoly_is_clockwise(LWPOLY *poly); +int lwtriangle_is_clockwise(LWTRIANGLE *triangle); +int ptarray_isccw(const POINTARRAY *pa); + +/* +* Same +*/ +char ptarray_same2d(const POINTARRAY *pa1, const POINTARRAY *pa2); +char ptarray_same(const POINTARRAY *pa1, const POINTARRAY *pa2); +char lwpoint_same2d(const LWPOINT *p1, const LWPOINT *p2); +char lwpoint_same(const LWPOINT *p1, const LWPOINT *p2); +char lwline_same(const LWLINE *p1, const LWLINE *p2); +char lwpoly_same(const LWPOLY *p1, const LWPOLY *p2); +char lwtriangle_same(const LWTRIANGLE *p1, const LWTRIANGLE *p2); +char lwcollection_same(const LWCOLLECTION *p1, const LWCOLLECTION *p2); +char lwcircstring_same(const LWCIRCSTRING *p1, const LWCIRCSTRING *p2); + +/* +* Shift +*/ +void ptarray_longitude_shift(POINTARRAY *pa); + +/* +* Support for in place modification of point arrays, fast +* function to move coordinate values around +*/ +void ptarray_copy_point(POINTARRAY *pa, uint32_t from, uint32_t to); + +/* +* Reverse +*/ +void ptarray_reverse_in_place(POINTARRAY *pa); + +/* +* Startpoint +*/ +int lwpoly_startpoint(const LWPOLY* lwpoly, POINT4D* pt); +int ptarray_startpoint(const POINTARRAY* pa, POINT4D* pt); +int lwcollection_startpoint(const LWCOLLECTION* col, POINT4D* pt); + +/* + * Write into *ret the coordinates of the closest point on + * segment A-B to the reference input point R + */ +void closest_point_on_segment(const POINT4D *R, const POINT4D *A, const POINT4D *B, POINT4D *ret); + +/* +* Repeated points +*/ +POINTARRAY *ptarray_remove_repeated_points(const POINTARRAY *in, double tolerance); +LWGEOM* lwline_remove_repeated_points(const LWLINE *in, double tolerance); +void ptarray_remove_repeated_points_in_place(POINTARRAY *pa, double tolerance, uint32_t min_points); + +/* +* Closure test +*/ +int lwline_is_closed(const LWLINE *line); +int lwpoly_is_closed(const LWPOLY *poly); +int lwcircstring_is_closed(const LWCIRCSTRING *curve); +int lwcompound_is_closed(const LWCOMPOUND *curve); +int lwpsurface_is_closed(const LWPSURFACE *psurface); +int lwtin_is_closed(const LWTIN *tin); + +/** +* Snap to grid +*/ +void ptarray_grid_in_place(POINTARRAY *pa, const gridspec *grid); + +/* +* What side of the line formed by p1 and p2 does q fall? +* Returns -1 for left and 1 for right and 0 for co-linearity +*/ +int lw_segment_side(const POINT2D *p1, const POINT2D *p2, const POINT2D *q); +int lw_arc_side(const POINT2D *A1, const POINT2D *A2, const POINT2D *A3, const POINT2D *Q); +int lw_arc_calculate_gbox_cartesian_2d(const POINT2D *A1, const POINT2D *A2, const POINT2D *A3, GBOX *gbox); +double lw_arc_center(const POINT2D *p1, const POINT2D *p2, const POINT2D *p3, POINT2D *result); +int lw_pt_in_seg(const POINT2D *P, const POINT2D *A1, const POINT2D *A2); +int lw_pt_in_arc(const POINT2D *P, const POINT2D *A1, const POINT2D *A2, const POINT2D *A3); +int lw_arc_is_pt(const POINT2D *A1, const POINT2D *A2, const POINT2D *A3); +double lw_seg_length(const POINT2D *A1, const POINT2D *A2); +double lw_arc_length(const POINT2D *A1, const POINT2D *A2, const POINT2D *A3); +int pt_in_ring_2d(const POINT2D *p, const POINTARRAY *ring); +int ptarray_contains_point(const POINTARRAY *pa, const POINT2D *pt); +int ptarrayarc_contains_point(const POINTARRAY *pa, const POINT2D *pt); +int ptarray_contains_point_partial(const POINTARRAY *pa, const POINT2D *pt, int check_closed, int *winding_number); +int ptarrayarc_contains_point_partial(const POINTARRAY *pa, const POINT2D *pt, int check_closed, int *winding_number); +int lwcompound_contains_point(const LWCOMPOUND *comp, const POINT2D *pt); +int lwgeom_contains_point(const LWGEOM *geom, const POINT2D *pt); + +/** +* Split a line by a point and push components to the provided multiline. +* If the point doesn't split the line, push nothing to the container. +* Returns 0 if the point is off the line. +* Returns 1 if the point is on the line boundary (endpoints). +* Return 2 if the point is on the interior of the line (only case in which +* a split happens). +* +* NOTE: the components pushed to the output vector have their SRID stripped +*/ +int lwline_split_by_point_to(const LWLINE* ln, const LWPOINT* pt, LWMLINE* to); + +/** Ensure the collection can hold at least up to ngeoms geometries */ +void lwcollection_reserve(LWCOLLECTION *col, uint32_t ngeoms); + +/** Check if subtype is allowed in collectiontype */ +int lwcollection_allows_subtype(int collectiontype, int subtype); + +/** GBOX utility functions to figure out coverage/location on the globe */ +double gbox_angular_height(const GBOX* gbox); +double gbox_angular_width(const GBOX* gbox); +int gbox_centroid(const GBOX* gbox, POINT2D* out); + +/* Utilities */ +int lwprint_double(double d, int maxdd, char *buf); +extern uint8_t MULTITYPE[NUMTYPES]; + +extern lwinterrupt_callback *_lwgeom_interrupt_callback; +extern int _lwgeom_interrupt_requested; +#define LW_ON_INTERRUPT(x) { \ + if ( _lwgeom_interrupt_callback ) { \ + (*_lwgeom_interrupt_callback)(); \ + } \ + if ( _lwgeom_interrupt_requested ) { \ + _lwgeom_interrupt_requested = 0; \ + lwnotice("liblwgeom code interrupted"); \ + x; \ + } \ +} + +int ptarray_npoints_in_rect(const POINTARRAY *pa, const GBOX *gbox); +int gbox_contains_point2d(const GBOX *g, const POINT2D *p); +int lwpoly_contains_point(const LWPOLY *poly, const POINT2D *pt); +POINT4D* lwmpoint_extract_points_4d(const LWMPOINT* g, uint32_t* npoints, int* input_empty); +char* lwstrdup(const char* a); + +#endif /* _LIBLWGEOM_INTERNAL_H */ diff --git a/mgist-postgis/liblwgeom/liblwgeom_topo.h b/mgist-postgis/liblwgeom/liblwgeom_topo.h new file mode 100644 index 0000000..276d9d7 --- /dev/null +++ b/mgist-postgis/liblwgeom/liblwgeom_topo.h @@ -0,0 +1,1416 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2015-2021 Sandro Santilli + * + **********************************************************************/ + + +#ifndef LIBLWGEOM_TOPO_H +#define LIBLWGEOM_TOPO_H 1 + +#include "liblwgeom.h" + +/* INT64 */ +typedef int64_t LWT_INT64; + +/** Identifier of topology element */ +typedef LWT_INT64 LWT_ELEMID; + +/* + * ISO primitive elements + */ + +/** NODE */ +typedef struct +{ + LWT_ELEMID node_id; + LWT_ELEMID containing_face; /* -1 if not isolated */ + LWPOINT *geom; +} +LWT_ISO_NODE; + +void lwt_iso_node_release(LWT_ISO_NODE* node); + +/** Node fields */ +#define LWT_COL_NODE_NODE_ID 1<<0 +#define LWT_COL_NODE_CONTAINING_FACE 1<<1 +#define LWT_COL_NODE_GEOM 1<<2 +#define LWT_COL_NODE_ALL (1<<3)-1 + +/** EDGE */ +typedef struct +{ + LWT_ELEMID edge_id; + LWT_ELEMID start_node; + LWT_ELEMID end_node; + LWT_ELEMID face_left; + LWT_ELEMID face_right; + LWT_ELEMID next_left; + LWT_ELEMID next_right; + LWLINE *geom; +} +LWT_ISO_EDGE; + +/** Edge fields */ +#define LWT_COL_EDGE_EDGE_ID 1<<0 +#define LWT_COL_EDGE_START_NODE 1<<1 +#define LWT_COL_EDGE_END_NODE 1<<2 +#define LWT_COL_EDGE_FACE_LEFT 1<<3 +#define LWT_COL_EDGE_FACE_RIGHT 1<<4 +#define LWT_COL_EDGE_NEXT_LEFT 1<<5 +#define LWT_COL_EDGE_NEXT_RIGHT 1<<6 +#define LWT_COL_EDGE_GEOM 1<<7 +#define LWT_COL_EDGE_ALL (1<<8)-1 + +/** FACE */ +typedef struct +{ + LWT_ELEMID face_id; + GBOX *mbr; +} +LWT_ISO_FACE; + +/** Face fields */ +#define LWT_COL_FACE_FACE_ID 1<<0 +#define LWT_COL_FACE_MBR 1<<1 +#define LWT_COL_FACE_ALL (1<<2)-1 + +typedef enum LWT_SPATIALTYPE_T { + LWT_PUNTAL = 0, + LWT_LINEAL = 1, + LWT_AREAL = 2, + LWT_COLLECTION = 3 +} LWT_SPATIALTYPE; + +/* + * Backend handling functions + */ + +/* opaque pointers referencing native backend objects */ + +/** + * Backend private data pointer + * + * Only the backend handler needs to know what it really is. + * It will be passed to all registered callback functions. + */ +typedef struct LWT_BE_DATA_T LWT_BE_DATA; + +/** + * Backend interface handler + * + * Embeds all registered backend callbacks and private data pointer. + * Will need to be passed (directly or indirectly) to al public facing + * APIs of this library. + */ +typedef struct LWT_BE_IFACE_T LWT_BE_IFACE; + +/** + * Topology handler. + * + * Embeds backend interface handler. + * Will need to be passed to all topology manipulation APIs + * of this library. + */ +typedef struct LWT_BE_TOPOLOGY_T LWT_BE_TOPOLOGY; + +/** + * Structure containing base backend callbacks + * + * Used for registering into the backend iface + */ +typedef struct LWT_BE_CALLBACKS_T { + + /** + * Read last error message from backend + * + * @return NULL-terminated error string + */ + const char* (*lastErrorMessage) (const LWT_BE_DATA* be); + + /** + * Create a new topology in the backend + * + * @param name the topology name + * @param srid the topology SRID + * @param precision the topology precision/tolerance + * @param hasZ non-zero if topology primitives should have a Z ordinate + * @return a topology handler, which embeds the backend data/params + * or NULL on error (@see lastErrorMessage) + */ + LWT_BE_TOPOLOGY *(*createTopology)(const LWT_BE_DATA *be, const char *name, int32_t srid, double precision, int hasZ); + + /** + * Load a topology from the backend + * + * @param name the topology name + * @return a topology handler, which embeds the backend data/params + * or NULL on error (@see lastErrorMessage) + */ + LWT_BE_TOPOLOGY* (*loadTopologyByName) ( + const LWT_BE_DATA* be, + const char* name + ); + + /** + * Release memory associated to a backend topology + * + * @param topo the backend topology handler + * @return 1 on success, 0 on error (@see lastErrorMessage) + */ + int (*freeTopology) (LWT_BE_TOPOLOGY* topo); + + /** + * Get nodes by id + * + * @param topo the topology to act upon + * @param ids an array of element identifiers + * @param numelems input/output parameter, pass number of node identifiers + * in the input array, gets number of node in output array. + * @param fields fields to be filled in the returned structure, see + * LWT_COL_NODE_* macros + * + * @return an array of nodes + * or NULL in the following cases: + * - no edge found ("numelems" is set to 0) + * - error ("numelems" is set to -1) + * (@see lastErrorMessage) + * + */ + LWT_ISO_NODE *(*getNodeById)(const LWT_BE_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields); + + /** + * Get nodes within distance by point + * + * @param topo the topology to act upon + * @param pt the query point + * @param dist the distance + * @param numelems output parameter, gets number of elements found + * if the return is not null, otherwise see @return + * section for semantic. + * @param fields fields to be filled in the returned structure, see + * LWT_COL_NODE_* macros + * @param limit max number of nodes to return, 0 for no limit, -1 + * to only check for existance if a matching row. + * + * @return an array of nodes or null in the following cases: + * - limit=-1 ("numelems" is set to 1 if found, 0 otherwise) + * - limit>0 and no records found ("numelems" is set to 0) + * - error ("numelems" is set to -1) + * + */ + LWT_ISO_NODE *(*getNodeWithinDistance2D)(const LWT_BE_TOPOLOGY *topo, + const LWPOINT *pt, + double dist, + uint64_t *numelems, + int fields, + int64_t limit); + + /** + * Insert nodes + * + * Insert node primitives in the topology, performing no + * consistency checks. + * + * @param topo the topology to act upon + * @param nodes the nodes to insert. Those with a node_id set to -1 + * it will be replaced to an automatically assigned identifier + * @param nelems number of elements in the nodes array + * + * @return 1 on success, 0 on error (@see lastErrorMessage) + */ + int (*insertNodes)(const LWT_BE_TOPOLOGY *topo, LWT_ISO_NODE *nodes, uint64_t numelems); + + /** + * Get edge by id + * + * @param topo the topology to act upon + * @param ids an array of element identifiers + * @param numelems input/output parameter, pass number of edge identifiers + * in the input array, gets number of edges in output array + * if the return is not null, otherwise see @return + * section for semantic. + * @param fields fields to be filled in the returned structure, see + * LWT_COL_EDGE_* macros + * + * @return an array of edges or NULL in the following cases: + * - none found ("numelems" is set to 0) + * - error ("numelems" is set to -1) + */ + LWT_ISO_EDGE *(*getEdgeById)(const LWT_BE_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields); + + /** + * Get edges within distance by point + * + * @param topo the topology to act upon + * @param pt the query point + * @param dist the distance + * @param numelems output parameter, gets number of elements found + * if the return is not null, otherwise see @return + * section for semantic. + * @param fields fields to be filled in the returned structure, see + * LWT_COL_EDGE_* macros + * @param limit max number of edges to return, 0 for no limit, -1 + * to only check for existence if a matching row. + * + * @return an array of edges or null in the following cases: + * - limit=-1 ("numelems" is set to 1 if found, 0 otherwise) + * - limit>0 and no records found ("numelems" is set to 0) + * - error ("numelems" is set to -1) + * + */ + LWT_ISO_EDGE *(*getEdgeWithinDistance2D)(const LWT_BE_TOPOLOGY *topo, + const LWPOINT *pt, + double dist, + uint64_t *numelems, + int fields, + int64_t limit); + + /** + * Get next available edge identifier + * + * Identifiers returned by this function should not be considered + * available anymore. + * + * @param topo the topology to act upon + * + * @return next available edge identifier or -1 on error + */ + LWT_ELEMID (*getNextEdgeId) ( + const LWT_BE_TOPOLOGY* topo + ); + + /** + * Insert edges + * + * Insert edge primitives in the topology, performing no + * consistency checks. + * + * @param topo the topology to act upon + * @param edges the edges to insert. Those with a edge_id set to -1 + * it will be replaced to an automatically assigned identifier + * @param nelems number of elements in the edges array + * + * @return number of inserted edges, or -1 (@see lastErrorMessage) + */ + int (*insertEdges)(const LWT_BE_TOPOLOGY *topo, LWT_ISO_EDGE *edges, uint64_t numelems); + + /** + * Update edges selected by fields match/mismatch + * + * @param topo the topology to act upon + * @param sel_edge an LWT_ISO_EDGE object with selecting fields set. + * @param sel_fields fields used to select edges to be updated, + * see LWT_COL_EDGE_* macros + * @param upd_edge an LWT_ISO_EDGE object with updated fields set. + * @param upd_fields fields to be updated for the selected edges, + * see LWT_COL_EDGE_* macros + * @param exc_edge an LWT_ISO_EDGE object with exclusion fields set, + * can be NULL if no exlusion condition exists. + * @param exc_fields fields used for excluding edges from the update, + * see LWT_COL_EDGE_* macros + * + * @return number of edges being updated or -1 on error + * (@see lastErroMessage) + */ + int (*updateEdges) ( + const LWT_BE_TOPOLOGY* topo, + const LWT_ISO_EDGE* sel_edge, int sel_fields, + const LWT_ISO_EDGE* upd_edge, int upd_fields, + const LWT_ISO_EDGE* exc_edge, int exc_fields + ); + + /** + * Get faces by id + * + * @param topo the topology to act upon + * @param ids an array of element identifiers + * @param numelems input/output parameter, pass number of edge identifiers + * in the input array, gets number of node in output array + * if the return is not null, otherwise see @return + * section for semantic. + * @param fields fields to be filled in the returned structure, see + * LWT_COL_FACE_* macros + * + * @return an array of faces or NULL in the following cases: + * - none found ("numelems" is set to 0) + * - error ("numelems" is set to -1) + */ + LWT_ISO_FACE *(*getFaceById)(const LWT_BE_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields); + + /** + * Update TopoGeometry objects after an edge split event + * + * @param topo the topology to act upon + * @param split_edge identifier of the edge that was split. + * @param new_edge1 identifier of the first new edge that was created + * as a result of edge splitting. + * @param new_edge2 identifier of the second new edge that was created + * as a result of edge splitting, or -1 if the old edge was + * modified rather than replaced. + * + * @return 1 on success, 0 on error + * + * @note on splitting an edge, the new edges both have the + * same direction as the original one. If a second new edge was + * created, its start node will be equal to the first new edge + * end node. + */ + int (*updateTopoGeomEdgeSplit) ( + const LWT_BE_TOPOLOGY* topo, + LWT_ELEMID split_edge, LWT_ELEMID new_edge1, LWT_ELEMID new_edge2 + ); + + /** + * Delete edges + * + * @param topo the topology to act upon + * @param sel_edge an LWT_ISO_EDGE object with selecting fields set. + * @param sel_fields fields used to select edges to be deleted, + * see LWT_COL_EDGE_* macros + * + * @return number of edges being deleted or -1 on error + * (@see lastErroMessage) + */ + int (*deleteEdges) ( + const LWT_BE_TOPOLOGY* topo, + const LWT_ISO_EDGE* sel_edge, int sel_fields + ); + + /** + * Get edges whose bounding box overlaps a given 2D bounding box + * + * @param topo the topology to act upon + * @param box the query box + * @param numelems output parameter, gets number of elements found + * if the return is not null, otherwise see @return + * section for semantic. + * @param fields fields to be filled in the returned structure, see + * LWT_COL_NODE_* macros + * @param limit max number of nodes to return, 0 for no limit, -1 + * to only check for existence if a matching row. + * + * @return an array of nodes or null in the following cases: + * - limit=-1 ("numelems" is set to 1 if found, 0 otherwise) + * - limit>0 and no records found ("numelems" is set to 0) + * - error ("numelems" is set to -1) + * + */ + LWT_ISO_NODE *( + *getNodeWithinBox2D)(const LWT_BE_TOPOLOGY *topo, const GBOX *box, uint64_t *numelems, int fields, int limit); + + /** + * Get edges whose bounding box overlaps a given 2D bounding box + * + * @param topo the topology to act upon + * @param box the query box, to be considered infinite if NULL + * @param numelems output parameter, gets number of elements found + * if the return is not null, otherwise see @return + * section for semantic. + * @param fields fields to be filled in the returned structure, see + * LWT_COL_EDGE_* macros + * @param limit max number of edges to return, 0 for no limit, -1 + * to only check for existence if a matching row. + * + * @return an array of edges or null in the following cases: + * - limit=-1 ("numelems" is set to 1 if found, 0 otherwise) + * - limit>0 and no records found ("numelems" is set to 0) + * - error ("numelems" is set to -1) + * + */ + LWT_ISO_EDGE *( + *getEdgeWithinBox2D)(const LWT_BE_TOPOLOGY *topo, const GBOX *box, uint64_t *numelems, int fields, int limit); + + /** + * Get edges that start or end on any of the given node identifiers + * + * @param topo the topology to act upon + * @param ids an array of node identifiers + * @param numelems input/output parameter, pass number of node identifiers + * in the input array, gets number of edges in output array + * if the return is not null, otherwise see @return + * section for semantic. + * @param fields fields to be filled in the returned structure, see + * LWT_COL_EDGE_* macros + * + * @return an array of edges that are incident to a node + * or NULL in the following cases: + * - no edge found ("numelems" is set to 0) + * - error ("numelems" is set to -1) + * (@see lastErrorMessage) + */ + LWT_ISO_EDGE *(*getEdgeByNode)(const LWT_BE_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields); + + /** + * Update nodes selected by fields match/mismatch + * + * @param topo the topology to act upon + * @param sel_node an LWT_ISO_NODE object with selecting fields set. + * @param sel_fields fields used to select nodes to be updated, + * see LWT_COL_NODE_* macros + * @param upd_node an LWT_ISO_NODE object with updated fields set. + * @param upd_fields fields to be updated for the selected nodes, + * see LWT_COL_NODE_* macros + * @param exc_node an LWT_ISO_NODE object with exclusion fields set, + * can be NULL if no exclusion condition exists. + * @param exc_fields fields used for excluding nodes from the update, + * see LWT_COL_NODE_* macros + * + * @return number of nodes being updated or -1 on error + * (@see lastErroMessage) + */ + int (*updateNodes) ( + const LWT_BE_TOPOLOGY* topo, + const LWT_ISO_NODE* sel_node, int sel_fields, + const LWT_ISO_NODE* upd_node, int upd_fields, + const LWT_ISO_NODE* exc_node, int exc_fields + ); + + /** + * Update TopoGeometry objects after a face split event + * + * @param topo the topology to act upon + * @param split_face identifier of the face that was split. + * @param new_face1 identifier of the first new face that was created + * as a result of face splitting. + * @param new_face2 identifier of the second new face that was created + * as a result of face splitting, or -1 if the old face was + * modified rather than replaced. + * + * @return 1 on success, 0 on error (@see lastErroMessage) + * + */ + int (*updateTopoGeomFaceSplit) ( + const LWT_BE_TOPOLOGY* topo, + LWT_ELEMID split_face, LWT_ELEMID new_face1, LWT_ELEMID new_face2 + ); + + /** + * Insert faces + * + * Insert face primitives in the topology, performing no + * consistency checks. + * + * @param topo the topology to act upon + * @param faces the faces to insert. Those with a node_id set to -1 + * it will be replaced to an automatically assigned identifier + * @param nelems number of elements in the faces array + * + * @return number of inserted faces, or -1 (@see lastErrorMessage) + */ + int (*insertFaces)(const LWT_BE_TOPOLOGY *topo, LWT_ISO_FACE *faces, uint64_t numelems); + + /** + * Update faces by id + * + * @param topo the topology to act upon + * @param faces an array of LWT_ISO_FACE object with selecting id + * and setting mbr. + * @param numfaces number of faces in the "faces" array + * + * @return number of faces being updated or UINT64_MAX on error + * (@see lastErroMessage) + */ + uint64_t (*updateFacesById) ( + const LWT_BE_TOPOLOGY* topo, + const LWT_ISO_FACE* faces, uint64_t numfaces + ); + + /* + * Get the ordered list edge visited by a side walk + * + * The walk starts from the side of an edge and stops when + * either the max number of visited edges OR the starting + * position is reached. The output list never includes a + * duplicated signed edge identifier. + * + * It is expected that the walk uses the "next_left" and "next_right" + * attributes of ISO edges to perform the walk (rather than recomputing + * the turns at each node). + * + * @param topo the topology to operate on + * @param edge walk start position and direction: + * abs value identifies the edge, sign expresses + * side (left if positive, right if negative) + * and direction (forward if positive, backward if negative). + * @param numedges output parameter, gets the number of edges visited + * + * @param limit max edges to return (to avoid an infinite loop in case + * of a corrupted topology). 0 is for unlimited. + * The function is expected to error out if the limit is hit. + * + * @return an array of signed edge identifiers (positive edges being + * walked in their direction, negative ones in opposite) or + * NULL on error (@see lastErrorMessage) + */ + LWT_ELEMID *(*getRingEdges)(const LWT_BE_TOPOLOGY *topo, LWT_ELEMID edge, uint64_t *numedges, int limit); + + /** + * Update edges by id + * + * @param topo the topology to act upon + * @param edges an array of LWT_ISO_EDGE object with selecting id + * and updating fields. + * @param numedges number of edges in the "edges" array + * @param upd_fields fields to be updated for the selected edges, + * see LWT_COL_EDGE_* macros + * + * @return number of edges being updated or -1 on error + * (@see lastErrorMessage) + */ + int (*updateEdgesById)(const LWT_BE_TOPOLOGY *topo, const LWT_ISO_EDGE *edges, uint64_t numedges, int upd_fields); + + /** + * \brief + * Get edges that have any of the given faces on the left or right side + * and optionally whose bounding box overlaps the given one. + * + * @param topo the topology to act upon + * @param ids an array of face identifiers + * @param numelems input/output parameter, pass number of face identifiers + * in the input array, gets number of edges in output array + * if the return is not null, otherwise see @return + * section for semantic. + * @param fields fields to be filled in the returned structure, see + * LWT_COL_EDGE_* macros + * @param box optional bounding box to further restrict matches, use + * NULL for no further restriction. + * + * @return an array of edges identifiers or NULL in the following cases: + * - no edge found ("numelems" is set to 0) + * - error ("numelems" is set to -1) + */ + LWT_ISO_EDGE *(*getEdgeByFace)(const LWT_BE_TOPOLOGY *topo, + const LWT_ELEMID *ids, + uint64_t *numelems, + int fields, + const GBOX *box); + + /** + * Get isolated nodes contained in any of the given faces + * + * @param topo the topology to act upon + * @param faces an array of face identifiers + * @param numelems input/output parameter, pass number of face + * identifiers in the input array, gets number of + * nodes in output array if the return is not null, + * otherwise see @return section for semantic. + * @param fields fields to be filled in the returned structure, see + * LWT_COL_NODE_* macros + * @param box optional bounding box to further restrict matches, use + * NULL for no further restriction. + * + * @return an array of nodes or NULL in the following cases: + * - no nod found ("numelems" is set to 0) + * - error ("numelems" is set to -1, @see lastErrorMessage) + */ + LWT_ISO_NODE *(*getNodeByFace)(const LWT_BE_TOPOLOGY *topo, + const LWT_ELEMID *faces, + uint64_t *numelems, + int fields, + const GBOX *box); + + /** + * Update nodes by id + * + * @param topo the topology to act upon + * @param nodes an array of LWT_ISO_EDGE objects with selecting id + * and updating fields. + * @param numnodes number of nodes in the "nodes" array + * @param upd_fields fields to be updated for the selected edges, + * see LWT_COL_NODE_* macros + * + * @return number of nodes being updated or -1 on error + * (@see lastErrorMessage) + */ + int (*updateNodesById)(const LWT_BE_TOPOLOGY *topo, const LWT_ISO_NODE *nodes, uint64_t numnodes, int upd_fields); + + /** + * Delete faces by id + * + * @param topo the topology to act upon + * @param ids an array of face identifiers + * @param numelems number of face identifiers in the ids array + * + * @return number of faces being deleted or -1 on error + * (@see lastErrorMessage) + */ + int (*deleteFacesById)(const LWT_BE_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t numelems); + + /** + * Get topology SRID + * @return 0 for unknown + */ + int (*topoGetSRID) ( + const LWT_BE_TOPOLOGY* topo + ); + + /** + * Get topology precision + */ + double (*topoGetPrecision) ( + const LWT_BE_TOPOLOGY* topo + ); + + /** + * Get topology Z flag + * @return 1 if topology elements do have Z value, 0 otherwise + */ + int (*topoHasZ) ( + const LWT_BE_TOPOLOGY* topo + ); + + /** + * Delete nodes by id + * + * @param topo the topology to act upon + * @param ids an array of node identifiers + * @param numelems number of node identifiers in the ids array + * + * @return number of nodes being deleted or -1 on error + * (@see lastErrorMessage) + */ + int (*deleteNodesById)(const LWT_BE_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t numelems); + + /** + * Check TopoGeometry objects before an edge removal event + * + * @param topo the topology to act upon + * @param rem_edge identifier of the edge that's been removed + * @param face_left identifier of the face on the edge's left side + * @param face_right identifier of the face on the edge's right side + * + * @return 1 to allow, 0 to forbid the operation + * (reporting reason via lastErrorMessage) + * + */ + int (*checkTopoGeomRemEdge) ( + const LWT_BE_TOPOLOGY* topo, + LWT_ELEMID rem_edge, + LWT_ELEMID face_left, + LWT_ELEMID face_right + ); + + /** + * Update TopoGeometry objects after healing two faces + * + * @param topo the topology to act upon + * @param face1 identifier of the first face + * @param face2 identifier of the second face + * @param newface identifier of the new face + * + * @note that newface may or may not be equal to face1 or face2, + * while face1 should never be the same as face2. + * + * @return 1 on success, 0 on error (@see lastErrorMessage) + * + */ + int (*updateTopoGeomFaceHeal) ( + const LWT_BE_TOPOLOGY* topo, + LWT_ELEMID face1, LWT_ELEMID face2, LWT_ELEMID newface + ); + + /** + * Check TopoGeometry objects before a node removal event + * + * @param topo the topology to act upon + * @param rem_node identifier of the node that's been removed + * @param e1 identifier of the first connected edge + * @param e2 identifier of the second connected edge + * + * The operation should be forbidden if any TopoGeometry object + * exists which contains only one of the two healed edges. + * + * The operation should also be forbidden if the removed node + * takes part in the definition of a TopoGeometry, although + * this wasn't the case yet as of PostGIS version 3.1: + * https://trac.osgeo.org/postgis/ticket/3239 + * + * @return 1 to allow, 0 to forbid the operation + * (reporting reason via lastErrorMessage) + * + */ + int (*checkTopoGeomRemNode) ( + const LWT_BE_TOPOLOGY* topo, + LWT_ELEMID rem_node, + LWT_ELEMID e1, + LWT_ELEMID e2 + ); + + /** + * Update TopoGeometry objects after healing two edges + * + * @param topo the topology to act upon + * @param edge1 identifier of the first edge + * @param edge2 identifier of the second edge + * @param newedge identifier of the new edge, taking the space + * previously occupied by both original edges + * + * @note that newedge may or may not be equal to edge1 or edge2, + * while edge1 should never be the same as edge2. + * + * @return 1 on success, 0 on error (@see lastErrorMessage) + * + */ + int (*updateTopoGeomEdgeHeal) ( + const LWT_BE_TOPOLOGY* topo, + LWT_ELEMID edge1, LWT_ELEMID edge2, LWT_ELEMID newedge + ); + + /** + * Get faces whose bounding box overlaps a given 2D bounding box + * + * @param topo the topology to act upon + * @param box the query box + * @param numelems output parameter, gets number of elements found + * if the return is not null, otherwise see @return + * section for semantic. + * @param fields fields to be filled in the returned structure, see + * LWT_COL_FACE_* macros + * @param limit max number of faces to return, 0 for no limit, -1 + * to only check for existence if a matching row. + * + * @return an array of faces or null in the following cases: + * - limit=-1 ("numelems" is set to 1 if found, 0 otherwise) + * - limit>0 and no records found ("numelems" is set to 0) + * - error ("numelems" is set to -1) + * + */ + LWT_ISO_FACE *(*getFaceWithinBox2D)(const LWT_BE_TOPOLOGY *topo, + const GBOX *box, + uint64_t *numelems, + int fields, + int limit); + + /** + * Check TopoGeometry objects before an isolated node removal event + * + * @param topo the topology to act upon + * @param rem_node identifier of the isolated node that's been removed + * + * The operation should be forbidden if the removed node + * takes part in the definition of a TopoGeometry. + * + * @return 1 to allow, 0 to forbid the operation + * (reporting reason via lastErrorMessage) + * + */ + int (*checkTopoGeomRemIsoNode) ( + const LWT_BE_TOPOLOGY* topo, + LWT_ELEMID rem_node + ); + + /** + * Check TopoGeometry objects before an isolated edge removal event + * + * @param topo the topology to act upon + * @param rem_edge identifier of the edge that's been removed + * + * @return 1 to allow, 0 to forbid the operation + * (reporting reason via lastErrorMessage) + */ + int (*checkTopoGeomRemIsoEdge) ( + const LWT_BE_TOPOLOGY* topo, + LWT_ELEMID rem_edge + ); + + + /** + * Get closest edge to a given point + * + * @param topo the topology to act upon + * @param pt the query point + * @param numelems output parameter, gets number of elements found + * or UINT64_MAX on error (@see lastErrorMessage) + * @param fields fields to be filled in the returned structure, see + * LWT_COL_EDGE_* macros + * + * @return an array of 1 edges or null in the following cases: + * - no edges are in the topology ("numelems" is set to 0) + * - error ("numelems" is set to UINT64_MAX) + * + */ + LWT_ISO_EDGE *(*getClosestEdge)(const LWT_BE_TOPOLOGY *topo, + const LWPOINT *pt, + uint64_t *numelems, + int fields); + + /** + * Compute minimum bounding rectangle of a face + * + * @param topo the topology to act upon + * @param face the face identifier + * + * @return a GBOX describing the minimum bounding rectangle of the face. + * + */ + GBOX *(*computeFaceMBR)(const LWT_BE_TOPOLOGY *topo, LWT_ELEMID face); + +} LWT_BE_CALLBACKS; + + +/** + * Create a new backend interface + * + * Ownership to caller delete with lwt_FreeBackendIface + * + * @param data Backend data, passed as first parameter to all callback functions + */ +LWT_BE_IFACE* lwt_CreateBackendIface(const LWT_BE_DATA* data); + +/** + * Register backend callbacks into the opaque iface handler + * + * @param iface the backend interface handler (see lwt_CreateBackendIface) + * @param cb a pointer to the callbacks structure; ownership left to caller. + */ +void lwt_BackendIfaceRegisterCallbacks(LWT_BE_IFACE* iface, const LWT_BE_CALLBACKS* cb); + +/** Release memory associated with an LWT_BE_IFACE */ +void lwt_FreeBackendIface(LWT_BE_IFACE* iface); + +/******************************************************************** + * + * End of BE interface + * + *******************************************************************/ + +/** + * Topology errors type + */ +typedef enum LWT_TOPOERR_TYPE_T { + LWT_TOPOERR_EDGE_CROSSES_NODE, + LWT_TOPOERR_EDGE_INVALID, + LWT_TOPOERR_EDGE_NOT_SIMPLE, + LWT_TOPOERR_EDGE_CROSSES_EDGE, + LWT_TOPOERR_EDGE_STARTNODE_MISMATCH, + LWT_TOPOERR_EDGE_ENDNODE_MISMATCH, + LWT_TOPOERR_FACE_WITHOUT_EDGES, + LWT_TOPOERR_FACE_HAS_NO_RINGS, + LWT_TOPOERR_FACE_OVERLAPS_FACE, + LWT_TOPOERR_FACE_WITHIN_FACE +} LWT_TOPOERR_TYPE; + +/** Topology error */ +typedef struct LWT_TOPOERR_T { + /** Type of error */ + LWT_TOPOERR_TYPE err; + /** Identifier of first affected element */ + LWT_ELEMID elem1; + /** Identifier of second affected element (0 if inapplicable) */ + LWT_ELEMID elem2; +} LWT_TOPOERR; + +/* + * Topology functions + */ + +/** Opaque topology structure + * + * Embeds backend interface and topology + */ +typedef struct LWT_TOPOLOGY_T LWT_TOPOLOGY; + + +/******************************************************************* + * + * Non-ISO signatures here + * + *******************************************************************/ + +/** + * Initializes a new topology + * + * @param iface the backend interface handler (see lwt_CreateBackendIface) + * @param name name of the new topology + * @param srid the topology SRID + * @param prec the topology precision/tolerance + * @param hasz non-zero if topology primitives should have a Z ordinate + * + * @return the handler of the topology, or NULL on error + * (liblwgeom error handler will be invoked with error message) + */ +LWT_TOPOLOGY *lwt_CreateTopology(LWT_BE_IFACE *iface, const char *name, int32_t srid, double prec, int hasz); + +/** + * Loads an existing topology by name from the database + * + * @param iface the backend interface handler (see lwt_CreateBackendIface) + * @param name name of the topology to load + * + * @return the handler of the topology, or NULL on error + * (liblwgeom error handler will be invoked with error message) + */ +LWT_TOPOLOGY *lwt_LoadTopology(LWT_BE_IFACE *iface, const char *name); + +/** + * Drop a topology and all its associated objects from the database + * + * @param topo the topology to drop + */ +void lwt_DropTopology(LWT_TOPOLOGY* topo); + +/** Release memory associated with an LWT_TOPOLOGY + * + * @param topo the topology to release (it's not removed from db) + */ +void lwt_FreeTopology(LWT_TOPOLOGY* topo); + +/** + * Retrieve the id of a node at a point location + * + * @param topo the topology to operate on + * @param point the point to use for query + * @param tol max distance around the given point to look for a node + * @return a node identifier if one is found, 0 if none is found, -1 + * on error (multiple nodes within distance). + * The liblwgeom error handler will be invoked in case of error. + */ +LWT_ELEMID lwt_GetNodeByPoint(LWT_TOPOLOGY *topo, LWPOINT *pt, double tol); + +/** + * Find the edge-id of an edge that intersects a given point + * + * @param topo the topology to operate on + * @param point the point to use for query + * @param tol max distance around the given point to look for an + * intersecting edge + * @return an edge identifier if one is found, 0 if none is found, -1 + * on error (multiple edges within distance). + * The liblwgeom error handler will be invoked in case of error. + */ +LWT_ELEMID lwt_GetEdgeByPoint(LWT_TOPOLOGY *topo, LWPOINT *pt, double tol); + +/** + * Find the face-id of a face containing a given point + * + * @param topo the topology to operate on + * @param point the point to use for query + * @param tol max distance around the given point to look for a + * containing face + * @return a face identifier if one is found (0 if universe), -1 + * on error (multiple faces within distance or point on node + * or edge). + * The liblwgeom error handler will be invoked in case of error. + */ +LWT_ELEMID lwt_GetFaceByPoint(LWT_TOPOLOGY *topo, const LWPOINT *pt, double tol); + +/** + * Find the face-id of the face properly containing a given point + * + * @param topo the topology to operate on + * @param point the point to use for query + * + * @return a face identifier if one is found (0 if universe), -1 + * on error (point intersects non-dangling edge). + * The liblwgeom error handler will be invoked in case of error. + */ +LWT_ELEMID lwt_GetFaceContainingPoint(LWT_TOPOLOGY* topo, const LWPOINT* pt); + + + +/******************************************************************* + * + * Topology population (non-ISO) + * + *******************************************************************/ + +/** + * Adds a point to the topology + * + * The given point will snap to existing nodes or edges within given + * tolerance. An existing edge may be split by the point. + * + * @param topo the topology to operate on + * @param point the point to add + * @param tol snap tolerance, the topology tolerance will be used if 0 + * + * @return identifier of added (or pre-existing) node or -1 on error + * (liblwgeom error handler will be invoked with error message) + */ +LWT_ELEMID lwt_AddPoint(LWT_TOPOLOGY* topo, LWPOINT* point, double tol); + +/** + * Adds a linestring to the topology + * + * The given line will snap to existing nodes or edges within given + * tolerance. Existing edges or faces may be split by the line. + * + * @param topo the topology to operate on + * @param line the line to add + * @param tol snap tolerance, the topology tolerance will be used if 0 + * @param nedges output parameter, will be set to number of edges the + * line was split into, or -1 on error + * (liblwgeom error handler will be invoked with error message) + * + * @return an array of edge identifiers that sewed togheter + * will build up the input linestring (after snapping). Caller + * will need to free the array using lwfree(), if not null. + */ +LWT_ELEMID* lwt_AddLine(LWT_TOPOLOGY* topo, LWLINE* line, double tol, + int* nedges); + +/** + * Adds a linestring to the topology without determining generated faces + * + * The given line will snap to existing nodes or edges within given + * tolerance. Existing edges or faces may be split by the line. + * + * Side faces for the new edges will not be determined and no new + * faces will be created, effectively leaving the topology in an + * invalid state (WARNING!) + * + * @param topo the topology to operate on + * @param line the line to add + * @param tol snap tolerance, the topology tolerance will be used if 0 + * @param nedges output parameter, will be set to number of edges the + * line was split into, or -1 on error + * (liblwgeom error handler will be invoked with error message) + * + * @return an array of edge identifiers that sewed togheter + * will build up the input linestring (after snapping). Caller + * will need to free the array using lwfree(), if not null. + */ +LWT_ELEMID* lwt_AddLineNoFace(LWT_TOPOLOGY* topo, LWLINE* line, double tol, + int* nedges); + +/* + * Determine and register all topology faces: + * + * - Determines which faces are generated by existing + * edges. + * - Creates face records with correct mbr + * - Update edge left/right face attributes + * + * Precondition: + * - the topology edges are correctly linked + * - there are no faces registered in the topology + * + * Postconditions: + * - all left/right face attributes of edges + * reference faces with correct mbr. + * + * Notes: + * - does not attempt to assign isolated nodes to their + * containing faces + * - does not remove existing face records + * - loads in memory all the topology edges + * + * @param topo the topology to operate on + * + * @return 0 on success, -1 on error + * (librtgeom error handler will be invoked with error + * message) + */ +int lwt_Polygonize(LWT_TOPOLOGY* topo); + +/** + * Adds a polygon to the topology + * + * The boundary of the given polygon will snap to existing nodes or + * edges within given tolerance. + * Existing edges or faces may be split by the boundary of the polygon. + * + * @param topo the topology to operate on + * @param poly the polygon to add + * @param tol snap tolerance, the topology tolerance will be used if 0 + * @param nfaces output parameter, will be set to number of faces the + * polygon was split into, or -1 on error + * (liblwgeom error handler will be invoked with error message) + * + * @return an array of face identifiers that sewed togheter + * will build up the input polygon (after snapping). Caller + * will need to free the array using lwfree(), if not null. + */ +LWT_ELEMID* lwt_AddPolygon(LWT_TOPOLOGY* topo, LWPOLY* poly, double tol, + int* nfaces); + +/******************************************************************* + * + * ISO signatures here + * + *******************************************************************/ + +/** + * Populate an empty topology with data from a simple geometry + * + * For ST_CreateTopoGeo + * + * @param topo the topology to operate on + * @param geom the geometry to import + * + */ +void lwt_CreateTopoGeo(LWT_TOPOLOGY* topo, LWGEOM *geom); + +/** + * Add an isolated node + * + * For ST_AddIsoNode + * + * @param topo the topology to operate on + * @param face the identifier of containing face or -1 for "unknown" + * @param pt the node position + * @param skipChecks if non-zero skips consistency checks + * (coincident nodes, crossing edges, + * actual face containment) + * + * @return ID of the newly added node, or -1 on error + * (liblwgeom error handler will be invoked with error message) + * + */ +LWT_ELEMID lwt_AddIsoNode(LWT_TOPOLOGY* topo, LWT_ELEMID face, + LWPOINT* pt, int skipChecks); + +/** + * Move an isolated node + * + * For ST_MoveIsoNode + * + * @param topo the topology to operate on + * @param node the identifier of the nod to be moved + * @param pt the new node position + * @return 0 on success, -1 on error + * (liblwgeom error handler will be invoked with error message) + * + */ +int lwt_MoveIsoNode(LWT_TOPOLOGY* topo, + LWT_ELEMID node, LWPOINT* pt); + +/** + * Remove an isolated node + * + * For ST_RemoveIsoNode + * + * @param topo the topology to operate on + * @param node the identifier of the node to be moved + * @return 0 on success, -1 on error + * (liblwgeom error handler will be invoked with error message) + * + */ +int lwt_RemoveIsoNode(LWT_TOPOLOGY* topo, LWT_ELEMID node); + +/** + * Remove an isolated edge + * + * For ST_RemIsoEdge + * + * @param topo the topology to operate on + * @param edge the identifier of the edge to be moved + * @return 0 on success, -1 on error + * (liblwgeom error handler will be invoked with error message) + * + */ +int lwt_RemIsoEdge(LWT_TOPOLOGY* topo, LWT_ELEMID edge); + +/** + * Add an isolated edge connecting two existing isolated nodes + * + * For ST_AddIsoEdge + * + * @param topo the topology to operate on + * @param start_node identifier of the starting node + * @param end_node identifier of the ending node + * @param geom the edge geometry + * @return ID of the newly added edge, or -1 on error + * (liblwgeom error handler will be invoked with error message) + * + */ +LWT_ELEMID lwt_AddIsoEdge(LWT_TOPOLOGY* topo, + LWT_ELEMID startNode, LWT_ELEMID endNode, + const LWLINE *geom); + +/** + * Add a new edge possibly splitting a face (modifying it) + * + * For ST_AddEdgeModFace + * + * If the new edge splits a face, the face is shrunk and a new one + * is created. Unless the face being split is the Universal Face, the + * new face will be on the right side of the newly added edge. + * + * @param topo the topology to operate on + * @param start_node identifier of the starting node + * @param end_node identifier of the ending node + * @param geom the edge geometry + * @param skipChecks if non-zero skips consistency checks + * (curve being simple and valid, start/end nodes + * consistency actual face containment) + * + * @return ID of the newly added edge or null on error + * (liblwgeom error handler will be invoked with error message) + * + */ +LWT_ELEMID lwt_AddEdgeModFace(LWT_TOPOLOGY* topo, + LWT_ELEMID start_node, LWT_ELEMID end_node, + LWLINE *geom, int skipChecks); + +/** + * Add a new edge possibly splitting a face (replacing with two new faces) + * + * For ST_AddEdgeNewFaces + * + * If the new edge splits a face, the face is replaced by two new faces. + * + * @param topo the topology to operate on + * @param start_node identifier of the starting node + * @param end_node identifier of the ending node + * @param geom the edge geometry + * @param skipChecks if non-zero skips consistency checks + * (curve being simple and valid, start/end nodes + * consistency actual face containment) + * @return ID of the newly added edge + * + */ +LWT_ELEMID lwt_AddEdgeNewFaces(LWT_TOPOLOGY* topo, + LWT_ELEMID start_node, LWT_ELEMID end_node, + LWLINE *geom, int skipChecks); + +/** + * Remove an edge, possibly merging two faces (replacing both with a new one) + * + * For ST_RemEdgeNewFace + * + * @param topo the topology to operate on + * @param edge identifier of the edge to be removed + * @return the id of newly created face, 0 if no new face was created + * or -1 on error + * + */ +LWT_ELEMID lwt_RemEdgeNewFace(LWT_TOPOLOGY* topo, LWT_ELEMID edge); + +/** + * Remove an edge, possibly merging two faces (replacing one with the other) + * + * For ST_RemEdgeModFace + * + * Preferentially keep the face on the right, to be symmetric with + * lwt_AddEdgeModFace. + * + * @param topo the topology to operate on + * @param edge identifier of the edge to be removed + * @return the id of the face that takes the space previously occupied + * by the removed edge, or -1 on error + * (liblwgeom error handler will be invoked with error message) + * + */ +LWT_ELEMID lwt_RemEdgeModFace(LWT_TOPOLOGY* topo, LWT_ELEMID edge); + +/** + * Changes the shape of an edge without affecting the topology structure. + * + * For ST_ChangeEdgeGeom + * + * @param topo the topology to operate on + * @param curve the edge geometry + * @return 0 on success, -1 on error + * (liblwgeom error handler will be invoked with error message) + * + */ +int lwt_ChangeEdgeGeom(LWT_TOPOLOGY* topo, LWT_ELEMID edge, LWLINE* curve); + +/** + * Split an edge by a node, modifying the original edge and adding a new one. + * + * For ST_ModEdgeSplit + * + * @param topo the topology to operate on + * @param edge identifier of the edge to be split + * @param pt geometry of the new node + * @param skipChecks if non-zero skips consistency checks + * (coincident node, point not on edge...) + * @return the id of newly created node, or -1 on error + * (liblwgeom error handler will be invoked with error message) + * + */ +LWT_ELEMID lwt_ModEdgeSplit(LWT_TOPOLOGY* topo, LWT_ELEMID edge, LWPOINT* pt, int skipChecks); + +/** + * Split an edge by a node, replacing it with two new edges + * + * For ST_NewEdgesSplit + * + * @param topo the topology to operate on + * @param edge identifier of the edge to be split + * @param pt geometry of the new node + * @param skipChecks if non-zero skips consistency checks + * (coincident node, point not on edge...) + * @return the id of newly created node + * + */ +LWT_ELEMID lwt_NewEdgesSplit(LWT_TOPOLOGY* topo, LWT_ELEMID edge, LWPOINT* pt, int skipChecks); + +/** + * Merge two edges, modifying the first and deleting the second + * + * For ST_ModEdgeHeal + * + * @param topo the topology to operate on + * @param e1 identifier of first edge + * @param e2 identifier of second edge + * @return the id of the removed node or -1 on error + * (liblwgeom error handler will be invoked with error message) + * + */ +LWT_ELEMID lwt_ModEdgeHeal(LWT_TOPOLOGY* topo, LWT_ELEMID e1, LWT_ELEMID e2); + +/** + * Merge two edges, replacing both with a new one + * + * For ST_NewEdgeHeal + * + * @param topo the topology to operate on + * @param e1 identifier of first edge + * @param e2 identifier of second edge + * @return the id of the new edge or -1 on error + * (liblwgeom error handler will be invoked with error message) + * + */ +LWT_ELEMID lwt_NewEdgeHeal(LWT_TOPOLOGY* topo, LWT_ELEMID e1, LWT_ELEMID e2); + +/** + * Return the list of directed edges bounding a face + * + * For ST_GetFaceEdges + * + * @param topo the topology to operate on + * @param face identifier of the face + * @param edges will be set to an array of signed edge identifiers, will + * need to be released with lwfree + * @return the number of edges in the edges array, or -1 on error + * (liblwgeom error handler will be invoked with error message) + * + */ +int lwt_GetFaceEdges(LWT_TOPOLOGY* topo, LWT_ELEMID face, LWT_ELEMID **edges); + +/** + * Return the geometry of a face + * + * For ST_GetFaceGeometry + * + * @param topo the topology to operate on + * @param face identifier of the face + * @return a polygon geometry representing the face, ownership to caller, + * to be released with lwgeom_release, or NULL on error + * (liblwgeom error handler will be invoked with error message) + */ +LWGEOM* lwt_GetFaceGeometry(LWT_TOPOLOGY* topo, LWT_ELEMID face); + +#endif /* LIBLWGEOM_TOPO_H */ diff --git a/mgist-postgis/liblwgeom/liblwgeom_topo_internal.h b/mgist-postgis/liblwgeom/liblwgeom_topo_internal.h new file mode 100644 index 0000000..f8f6b5a --- /dev/null +++ b/mgist-postgis/liblwgeom/liblwgeom_topo_internal.h @@ -0,0 +1,98 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2015 Sandro Santilli + * + **********************************************************************/ + + +#ifndef LIBLWGEOM_TOPO_INTERNAL_H +#define LIBLWGEOM_TOPO_INTERNAL_H 1 + +// #include "../postgis_config.h" + +#include "liblwgeom.h" +#include "liblwgeom_topo.h" + +/************************************************************************ + * + * Generic SQL handler + * + ************************************************************************/ + +struct LWT_BE_IFACE_T +{ + const LWT_BE_DATA *data; + const LWT_BE_CALLBACKS *cb; +}; + +const char* lwt_be_lastErrorMessage(const LWT_BE_IFACE* be); + +LWT_BE_TOPOLOGY * lwt_be_loadTopologyByName(LWT_BE_IFACE *be, const char *name); + +int lwt_be_freeTopology(LWT_TOPOLOGY *topo); + +LWT_ISO_NODE *lwt_be_getNodeWithinDistance2D(LWT_TOPOLOGY *topo, + LWPOINT *pt, + double dist, + uint64_t *numelems, + int fields, + int64_t limit); + +LWT_ISO_NODE *lwt_be_getNodeById(LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields); + +int lwt_be_ExistsCoincidentNode(LWT_TOPOLOGY* topo, LWPOINT* pt); +int lwt_be_insertNodes(LWT_TOPOLOGY *topo, LWT_ISO_NODE *node, uint64_t numelems); + +int lwt_be_ExistsEdgeIntersectingPoint(LWT_TOPOLOGY* topo, LWPOINT* pt); + +LWT_ELEMID lwt_be_getNextEdgeId(LWT_TOPOLOGY* topo); +LWT_ISO_EDGE *lwt_be_getEdgeById(LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields); +LWT_ISO_EDGE *lwt_be_getEdgeWithinDistance2D(LWT_TOPOLOGY *topo, + const LWPOINT *pt, + double dist, + uint64_t *numelems, + int fields, + int64_t limit); +int lwt_be_insertEdges(LWT_TOPOLOGY *topo, LWT_ISO_EDGE *edge, uint64_t numelems); +int +lwt_be_updateEdges(LWT_TOPOLOGY* topo, const LWT_ISO_EDGE* sel_edge, int sel_fields, const LWT_ISO_EDGE* upd_edge, int upd_fields, const LWT_ISO_EDGE* exc_edge, int exc_fields); +int +lwt_be_deleteEdges(LWT_TOPOLOGY* topo, const LWT_ISO_EDGE* sel_edge, int sel_fields); + +int lwt_be_updateTopoGeomEdgeSplit(LWT_TOPOLOGY* topo, LWT_ELEMID split_edge, LWT_ELEMID new_edge1, LWT_ELEMID new_edge2); + + +/************************************************************************ + * + * Internal objects + * + ************************************************************************/ + +struct LWT_TOPOLOGY_T +{ + const LWT_BE_IFACE *be_iface; + LWT_BE_TOPOLOGY *be_topo; + int32_t srid; + double precision; + int hasZ; +}; + +#endif /* LIBLWGEOM_TOPO_INTERNAL_H */ diff --git a/mgist-postgis/liblwgeom/lookup3.c b/mgist-postgis/liblwgeom/lookup3.c new file mode 100644 index 0000000..3a2865c --- /dev/null +++ b/mgist-postgis/liblwgeom/lookup3.c @@ -0,0 +1,783 @@ +/* +------------------------------------------------------------------------------- +lookup3.c, by Bob Jenkins, May 2006, Public Domain. + +These are functions for producing 32-bit hashes for hash table lookup. +hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final() +are externally useful functions. Routines to test the hash are included +if SELF_TEST is defined. You can use this free for any purpose. It's in +the public domain. It has no warranty. + +You probably want to use hashlittle(). hashlittle() and hashbig() +hash byte arrays. hashlittle() is is faster than hashbig() on +little-endian machines. Intel and AMD are little-endian machines. +On second thought, you probably want hashlittle2(), which is identical to +hashlittle() except it returns two 32-bit hashes for the price of one. +You could implement hashbig2() if you wanted but I haven't bothered here. + +If you want to find a hash of, say, exactly 7 integers, do + a = i1; b = i2; c = i3; + mix(a,b,c); + a += i4; b += i5; c += i6; + mix(a,b,c); + a += i7; + final(a,b,c); +then use c as the hash value. If you have a variable length array of +4-byte integers to hash, use hashword(). If you have a byte array (like +a character string), use hashlittle(). If you have several byte arrays, or +a mix of things, see the comments above hashlittle(). + +Why is this so big? I read 12 bytes at a time into 3 4-byte integers, +then mix those integers. This is fast (you can do a lot more thorough +mixing with 12*3 instructions on 3 integers than you can with 3 instructions +on 1 byte), but shoehorning those bytes into integers efficiently is messy. +------------------------------------------------------------------------------- +*/ +#define SELF_TEST 1 + +#include /* defines printf for tests */ +#include /* defines time_t for timings in the test */ +#include /* defines uint32_t etc */ +#ifdef linux +#include /* attempt to define endianness */ +# include /* attempt to define endianness */ +#endif + +/* + * My best guess at if you are big-endian or little-endian. This may + * need adjustment. + */ +#if defined (__WIN32__) // windows is always little-endian except for NT +# define HASH_LITTLE_ENDIAN 1 +# define HASH_BIG_ENDIAN 0 +#elif (defined(WORDS_BIGENDIAN)) +# define HASH_LITTLE_ENDIAN 0 +# define HASH_BIG_ENDIAN 1 +#elif (defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ + __BYTE_ORDER == __LITTLE_ENDIAN) || \ + (defined(i386) || defined(__i386__) || defined(__i486__) || \ + defined(__i586__) || defined(__i686__) || defined(vax) || defined(MIPSEL)) +# define HASH_LITTLE_ENDIAN 1 +# define HASH_BIG_ENDIAN 0 +#elif (defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ + __BYTE_ORDER == __BIG_ENDIAN) || \ + (defined(sparc) || defined(POWERPC) || defined(mc68000) || defined(sel)) +# define HASH_LITTLE_ENDIAN 0 +# define HASH_BIG_ENDIAN 1 +#else +# define HASH_LITTLE_ENDIAN 0 +# define HASH_BIG_ENDIAN 0 +#endif + +#define hashsize(n) ((uint32_t)1<<(n)) +#define hashmask(n) (hashsize(n)-1) +#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k)))) + +#if 0 +static uint32_t hashword(const uint32_t *k, size_t length, uint32_t initval); +static void hashword2 (const uint32_t *k, size_t length, uint32_t *pc, uint32_t *pb); +static uint32_t hashlittle( const void *key, size_t length, uint32_t initval); +static uint32_t hashbig( const void *key, size_t length, uint32_t initval); +#endif + +void hashlittle2(const void *key, size_t length, uint32_t *pc, uint32_t *pb); + +/* +------------------------------------------------------------------------------- +mix -- mix 3 32-bit values reversibly. + +This is reversible, so any information in (a,b,c) before mix() is +still in (a,b,c) after mix(). + +If four pairs of (a,b,c) inputs are run through mix(), or through +mix() in reverse, there are at least 32 bits of the output that +are sometimes the same for one pair and different for another pair. +This was tested for: +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that +satisfy this are + 4 6 8 16 19 4 + 9 15 3 18 27 15 + 14 9 3 7 17 3 +Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing +for "differ" defined as + with a one-bit base and a two-bit delta. I +used http://burtleburtle.net/bob/hash/avalanche.html to choose +the operations, constants, and arrangements of the variables. + +This does not achieve avalanche. There are input bits of (a,b,c) +that fail to affect some output bits of (a,b,c), especially of a. The +most thoroughly mixed value is c, but it doesn't really even achieve +avalanche in c. + +This allows some parallelism. Read-after-writes are good at doubling +the number of bits affected, so the goal of mixing pulls in the opposite +direction as the goal of parallelism. I did what I could. Rotates +seem to cost as much as shifts on every machine I could lay my hands +on, and rotates are much kinder to the top and bottom bits, so I used +rotates. +------------------------------------------------------------------------------- +*/ +#define mix(a,b,c) \ +{ \ + a -= c; a ^= rot(c, 4); c += b; \ + b -= a; b ^= rot(a, 6); a += c; \ + c -= b; c ^= rot(b, 8); b += a; \ + a -= c; a ^= rot(c,16); c += b; \ + b -= a; b ^= rot(a,19); a += c; \ + c -= b; c ^= rot(b, 4); b += a; \ +} + +/* +------------------------------------------------------------------------------- +final -- final mixing of 3 32-bit values (a,b,c) into c + +Pairs of (a,b,c) values differing in only a few bits will usually +produce values of c that look totally different. This was tested for +* pairs that differed by one bit, by two bits, in any combination + of top bits of (a,b,c), or in any combination of bottom bits of + (a,b,c). +* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed + the output delta to a Gray code (a^(a>>1)) so a string of 1's (as + is commonly produced by subtraction) look like a single 1-bit + difference. +* the base values were pseudorandom, all zero but one bit set, or + all zero plus a counter that starts at zero. + +These constants passed: + 14 11 25 16 4 14 24 + 12 14 25 16 4 14 24 +and these came close: + 4 8 15 26 3 22 24 + 10 8 15 26 3 22 24 + 11 8 15 26 3 22 24 +------------------------------------------------------------------------------- +*/ +#define final(a,b,c) \ +{ \ + c ^= b; c -= rot(b,14); \ + a ^= c; a -= rot(c,11); \ + b ^= a; b -= rot(a,25); \ + c ^= b; c -= rot(b,16); \ + a ^= c; a -= rot(c,4); \ + b ^= a; b -= rot(a,14); \ + c ^= b; c -= rot(b,24); \ +} + +#if 0 +/* +-------------------------------------------------------------------- + This works on all machines. To be useful, it requires + -- that the key be an array of uint32_t's, and + -- that the length be the number of uint32_t's in the key + + The function hashword() is identical to hashlittle() on little-endian + machines, and identical to hashbig() on big-endian machines, + except that the length has to be measured in uint32_ts rather than in + bytes. hashlittle() is more complicated than hashword() only because + hashlittle() has to dance around fitting the key bytes into registers. +-------------------------------------------------------------------- +*/ +static uint32_t hashword( +const uint32_t *k, /* the key, an array of uint32_t values */ +size_t length, /* the length of the key, in uint32_ts */ +uint32_t initval) /* the previous hash, or an arbitrary value */ +{ + uint32_t a,b,c; + + /* Set up the internal state */ + a = b = c = 0xdeadbeef + (((uint32_t)length)<<2) + initval; + + /*------------------------------------------------- handle most of the key */ + while (length > 3) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 3; + k += 3; + } + + /*------------------------------------------- handle the last 3 uint32_t's */ + switch(length) /* all the case statements fall through */ + { + case 3 : c+=k[2]; + case 2 : b+=k[1]; + case 1 : a+=k[0]; + final(a,b,c); + case 0: /* case 0: nothing left to add */ + break; + } + /*------------------------------------------------------ report the result */ + return c; +} +#endif + +#if 0 +/* +-------------------------------------------------------------------- +hashword2() -- same as hashword(), but take two seeds and return two +32-bit values. pc and pb must both be nonnull, and *pc and *pb must +both be initialized with seeds. If you pass in (*pb)==0, the output +(*pc) will be the same as the return value from hashword(). +-------------------------------------------------------------------- +*/ +static void hashword2 ( +const uint32_t *k, /* the key, an array of uint32_t values */ +size_t length, /* the length of the key, in uint32_ts */ +uint32_t *pc, /* IN: seed OUT: primary hash value */ +uint32_t *pb) /* IN: more seed OUT: secondary hash value */ +{ + uint32_t a,b,c; + + /* Set up the internal state */ + a = b = c = 0xdeadbeef + ((uint32_t)(length<<2)) + *pc; + c += *pb; + + /*------------------------------------------------- handle most of the key */ + while (length > 3) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 3; + k += 3; + } + + /*------------------------------------------- handle the last 3 uint32_t's */ + switch(length) /* all the case statements fall through */ + { + case 3 : c+=k[2]; + case 2 : b+=k[1]; + case 1 : a+=k[0]; + final(a,b,c); + case 0: /* case 0: nothing left to add */ + break; + } + /*------------------------------------------------------ report the result */ + *pc=c; *pb=b; +} +#endif + +/* +------------------------------------------------------------------------------- +hashlittle() -- hash a variable-length key into a 32-bit value + k : the key (the unaligned variable-length array of bytes) + length : the length of the key, counting by bytes + initval : can be any 4-byte value +Returns a 32-bit value. Every bit of the key affects every bit of +the return value. Two keys differing by one or two bits will have +totally different hash values. + +The best hash table sizes are powers of 2. There is no need to do +mod a prime (mod is sooo slow!). If you need less than 32 bits, +use a bitmask. For example, if you need only 10 bits, do + h = (h & hashmask(10)); +In which case, the hash table should have hashsize(10) elements. + +If you are hashing n strings (uint8_t **)k, do it like this: + for (i=0, h=0; i 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; + } + + /*----------------------------- handle the last (probably partial) block */ + /* + * "k[2]&0xffffff" actually reads beyond the end of the string, but + * then masks off the part it's not allowed to read. Because the + * string is aligned, the masked-off tail is in the same word as the + * rest of the string. Every machine with memory protection I've seen + * does it on word boundaries, so is OK with this. But VALGRIND will + * still catch it and complain. The masking trick does make the hash + * noticably faster for short strings (like English words). + */ +#ifndef VALGRIND + + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; + case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; + case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=k[1]&0xffffff; a+=k[0]; break; + case 6 : b+=k[1]&0xffff; a+=k[0]; break; + case 5 : b+=k[1]&0xff; a+=k[0]; break; + case 4 : a+=k[0]; break; + case 3 : a+=k[0]&0xffffff; break; + case 2 : a+=k[0]&0xffff; break; + case 1 : a+=k[0]&0xff; break; + case 0 : return c; /* zero length strings require no mixing */ + } + +#else /* make valgrind happy */ + + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ + case 1 : a+=k8[0]; break; + case 0 : return c; + } + +#endif /* !valgrind */ + + } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { + const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ + const uint8_t *k8; + + /*--------------- all but last block: aligned reads and different mixing */ + while (length > 12) + { + a += k[0] + (((uint32_t)k[1])<<16); + b += k[2] + (((uint32_t)k[3])<<16); + c += k[4] + (((uint32_t)k[5])<<16); + mix(a,b,c); + length -= 12; + k += 6; + } + + /*----------------------------- handle the last (probably partial) block */ + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[4]+(((uint32_t)k[5])<<16); + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=k[4]; + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=k[2]; + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=k[0]; + break; + case 1 : a+=k8[0]; + break; + case 0 : return c; /* zero length requires no mixing */ + } + + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; + + /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + a += ((uint32_t)k[1])<<8; + a += ((uint32_t)k[2])<<16; + a += ((uint32_t)k[3])<<24; + b += k[4]; + b += ((uint32_t)k[5])<<8; + b += ((uint32_t)k[6])<<16; + b += ((uint32_t)k[7])<<24; + c += k[8]; + c += ((uint32_t)k[9])<<8; + c += ((uint32_t)k[10])<<16; + c += ((uint32_t)k[11])<<24; + mix(a,b,c); + length -= 12; + k += 12; + } + + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + case 12: c+=((uint32_t)k[11])<<24; + case 11: c+=((uint32_t)k[10])<<16; + case 10: c+=((uint32_t)k[9])<<8; + case 9 : c+=k[8]; + case 8 : b+=((uint32_t)k[7])<<24; + case 7 : b+=((uint32_t)k[6])<<16; + case 6 : b+=((uint32_t)k[5])<<8; + case 5 : b+=k[4]; + case 4 : a+=((uint32_t)k[3])<<24; + case 3 : a+=((uint32_t)k[2])<<16; + case 2 : a+=((uint32_t)k[1])<<8; + case 1 : a+=k[0]; + break; + case 0 : return c; + } + } + + final(a,b,c); + return c; +} +#endif + +/* + * hashlittle2: return 2 32-bit hash values + * + * This is identical to hashlittle(), except it returns two 32-bit hash + * values instead of just one. This is good enough for hash table + * lookup with 2^^64 buckets, or if you want a second hash if you're not + * happy with the first, or if you want a probably-unique 64-bit ID for + * the key. *pc is better mixed than *pb, so use *pc first. If you want + * a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)". + */ +void hashlittle2( + const void *key, /* the key to hash */ + size_t length, /* length of the key */ + uint32_t *pc, /* IN: primary initval, OUT: primary hash */ + uint32_t *pb) /* IN: secondary initval, OUT: secondary hash */ +{ + uint32_t a,b,c; /* internal state */ + union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */ + + /* Set up the internal state */ + a = b = c = 0xdeadbeef + ((uint32_t)length) + *pc; + c += *pb; + + u.ptr = key; + if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) { + const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */ + // const uint8_t *k8; + + /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; + } + + /*----------------------------- handle the last (probably partial) block */ + /* + * "k[2]&0xffffff" actually reads beyond the end of the string, but + * then masks off the part it's not allowed to read. Because the + * string is aligned, the masked-off tail is in the same word as the + * rest of the string. Every machine with memory protection I've seen + * does it on word boundaries, so is OK with this. But VALGRIND will + * still catch it and complain. The masking trick does make the hash + * noticably faster for short strings (like English words). + */ +#ifndef VALGRIND + + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break; + case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break; + case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break; + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=k[1]&0xffffff; a+=k[0]; break; + case 6 : b+=k[1]&0xffff; a+=k[0]; break; + case 5 : b+=k[1]&0xff; a+=k[0]; break; + case 4 : a+=k[0]; break; + case 3 : a+=k[0]&0xffffff; break; + case 2 : a+=k[0]&0xffff; break; + case 1 : a+=k[0]&0xff; break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + +#else /* make valgrind happy */ + + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<8; /* fall through */ + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<8; /* fall through */ + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<8; /* fall through */ + case 1 : a+=k8[0]; break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + +#endif /* !valgrind */ + + } else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) { + const uint16_t *k = (const uint16_t *)key; /* read 16-bit chunks */ + const uint8_t *k8; + + /*--------------- all but last block: aligned reads and different mixing */ + while (length > 12) + { + a += k[0] + (((uint32_t)k[1])<<16); + b += k[2] + (((uint32_t)k[3])<<16); + c += k[4] + (((uint32_t)k[5])<<16); + mix(a,b,c); + length -= 12; + k += 6; + } + + /*----------------------------- handle the last (probably partial) block */ + k8 = (const uint8_t *)k; + switch(length) + { + case 12: c+=k[4]+(((uint32_t)k[5])<<16); + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 11: c+=((uint32_t)k8[10])<<16; /* fall through */ + case 10: c+=k[4]; + b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 9 : c+=k8[8]; /* fall through */ + case 8 : b+=k[2]+(((uint32_t)k[3])<<16); + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 7 : b+=((uint32_t)k8[6])<<16; /* fall through */ + case 6 : b+=k[2]; + a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 5 : b+=k8[4]; /* fall through */ + case 4 : a+=k[0]+(((uint32_t)k[1])<<16); + break; + case 3 : a+=((uint32_t)k8[2])<<16; /* fall through */ + case 2 : a+=k[0]; + break; + case 1 : a+=k8[0]; + break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; + + /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + a += ((uint32_t)k[1])<<8; + a += ((uint32_t)k[2])<<16; + a += ((uint32_t)k[3])<<24; + b += k[4]; + b += ((uint32_t)k[5])<<8; + b += ((uint32_t)k[6])<<16; + b += ((uint32_t)k[7])<<24; + c += k[8]; + c += ((uint32_t)k[9])<<8; + c += ((uint32_t)k[10])<<16; + c += ((uint32_t)k[11])<<24; + mix(a,b,c); + length -= 12; + k += 12; + } + + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + case 12: c+=((uint32_t)k[11])<<24; /* fall through */ + case 11: c+=((uint32_t)k[10])<<16; /* fall through */ + case 10: c+=((uint32_t)k[9])<<8; /* fall through */ + case 9 : c+=k[8]; /* fall through */ + case 8 : b+=((uint32_t)k[7])<<24; /* fall through */ + case 7 : b+=((uint32_t)k[6])<<16; /* fall through */ + case 6 : b+=((uint32_t)k[5])<<8; /* fall through */ + case 5 : b+=k[4]; /* fall through */ + case 4 : a+=((uint32_t)k[3])<<24; /* fall through */ + case 3 : a+=((uint32_t)k[2])<<16; /* fall through */ + case 2 : a+=((uint32_t)k[1])<<8; /* fall through */ + case 1 : a+=k[0]; + break; + case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */ + } + } + + final(a,b,c); + *pc=c; *pb=b; +} + + +#if 0 +/* + * hashbig(): + * This is the same as hashword() on big-endian machines. It is different + * from hashlittle() on all machines. hashbig() takes advantage of + * big-endian byte ordering. + */ +static uint32_t hashbig( const void *key, size_t length, uint32_t initval) +{ + uint32_t a,b,c; + union { const void *ptr; size_t i; } u; /* to cast key to (size_t) happily */ + + /* Set up the internal state */ + a = b = c = 0xdeadbeef + ((uint32_t)length) + initval; + + u.ptr = key; + if (HASH_BIG_ENDIAN && ((u.i & 0x3) == 0)) { + const uint32_t *k = (const uint32_t *)key; /* read 32-bit chunks */ + // const uint8_t *k8; + + /*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */ + while (length > 12) + { + a += k[0]; + b += k[1]; + c += k[2]; + mix(a,b,c); + length -= 12; + k += 3; + } + + /*----------------------------- handle the last (probably partial) block */ + /* + * "k[2]<<8" actually reads beyond the end of the string, but + * then shifts out the part it's not allowed to read. Because the + * string is aligned, the illegal read is in the same word as the + * rest of the string. Every machine with memory protection I've seen + * does it on word boundaries, so is OK with this. But VALGRIND will + * still catch it and complain. The masking trick does make the hash + * noticably faster for short strings (like English words). + */ +#ifndef VALGRIND + + switch(length) + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=k[2]&0xffffff00; b+=k[1]; a+=k[0]; break; + case 10: c+=k[2]&0xffff0000; b+=k[1]; a+=k[0]; break; + case 9 : c+=k[2]&0xff000000; b+=k[1]; a+=k[0]; break; + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=k[1]&0xffffff00; a+=k[0]; break; + case 6 : b+=k[1]&0xffff0000; a+=k[0]; break; + case 5 : b+=k[1]&0xff000000; a+=k[0]; break; + case 4 : a+=k[0]; break; + case 3 : a+=k[0]&0xffffff00; break; + case 2 : a+=k[0]&0xffff0000; break; + case 1 : a+=k[0]&0xff000000; break; + case 0 : return c; /* zero length strings require no mixing */ + } + +#else /* make valgrind happy */ + + k8 = (const uint8_t *)k; + switch(length) /* all the case statements fall through */ + { + case 12: c+=k[2]; b+=k[1]; a+=k[0]; break; + case 11: c+=((uint32_t)k8[10])<<8; /* fall through */ + case 10: c+=((uint32_t)k8[9])<<16; /* fall through */ + case 9 : c+=((uint32_t)k8[8])<<24; /* fall through */ + case 8 : b+=k[1]; a+=k[0]; break; + case 7 : b+=((uint32_t)k8[6])<<8; /* fall through */ + case 6 : b+=((uint32_t)k8[5])<<16; /* fall through */ + case 5 : b+=((uint32_t)k8[4])<<24; /* fall through */ + case 4 : a+=k[0]; break; + case 3 : a+=((uint32_t)k8[2])<<8; /* fall through */ + case 2 : a+=((uint32_t)k8[1])<<16; /* fall through */ + case 1 : a+=((uint32_t)k8[0])<<24; break; + case 0 : return c; + } + +#endif /* !VALGRIND */ + + } else { /* need to read the key one byte at a time */ + const uint8_t *k = (const uint8_t *)key; + + /*--------------- all but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) + { + a += ((uint32_t)k[0])<<24; + a += ((uint32_t)k[1])<<16; + a += ((uint32_t)k[2])<<8; + a += ((uint32_t)k[3]); + b += ((uint32_t)k[4])<<24; + b += ((uint32_t)k[5])<<16; + b += ((uint32_t)k[6])<<8; + b += ((uint32_t)k[7]); + c += ((uint32_t)k[8])<<24; + c += ((uint32_t)k[9])<<16; + c += ((uint32_t)k[10])<<8; + c += ((uint32_t)k[11]); + mix(a,b,c); + length -= 12; + k += 12; + } + + /*-------------------------------- last block: affect all 32 bits of (c) */ + switch(length) /* all the case statements fall through */ + { + case 12: c+=k[11]; + case 11: c+=((uint32_t)k[10])<<8; + case 10: c+=((uint32_t)k[9])<<16; + case 9 : c+=((uint32_t)k[8])<<24; + case 8 : b+=k[7]; + case 7 : b+=((uint32_t)k[6])<<8; + case 6 : b+=((uint32_t)k[5])<<16; + case 5 : b+=((uint32_t)k[4])<<24; + case 4 : a+=k[3]; + case 3 : a+=((uint32_t)k[2])<<8; + case 2 : a+=((uint32_t)k[1])<<16; + case 1 : a+=((uint32_t)k[0])<<24; + break; + case 0 : return c; + } + } + + final(a,b,c); + return c; +} +#endif + diff --git a/mgist-postgis/liblwgeom/lwalgorithm.c b/mgist-postgis/liblwgeom/lwalgorithm.c new file mode 100644 index 0000000..a222004 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwalgorithm.c @@ -0,0 +1,898 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2008 Paul Ramsey + * + **********************************************************************/ + + +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" +#include /* for tolower */ +#include + +int +p4d_same(const POINT4D *p1, const POINT4D *p2) +{ + return FP_EQUALS(p1->x, p2->x) + && FP_EQUALS(p1->y, p2->y) + && FP_EQUALS(p1->z, p2->z) + && FP_EQUALS(p1->m, p2->m); +} + +int +p3d_same(const POINT3D *p1, const POINT3D *p2) +{ + return FP_EQUALS(p1->x, p2->x) + && FP_EQUALS(p1->y, p2->y) + && FP_EQUALS(p1->z, p2->z); +} + +int +p2d_same(const POINT2D *p1, const POINT2D *p2) +{ + return FP_EQUALS(p1->x, p2->x) + && FP_EQUALS(p1->y, p2->y); +} + +/** +* lw_segment_side() +* +* Return -1 if point Q is left of segment P +* Return 1 if point Q is right of segment P +* Return 0 if point Q in on segment P +*/ +int lw_segment_side(const POINT2D *p1, const POINT2D *p2, const POINT2D *q) +{ + double side = ( (q->x - p1->x) * (p2->y - p1->y) - (p2->x - p1->x) * (q->y - p1->y) ); + return SIGNUM(side); +} + +/** +* Returns the length of a linear segment +*/ +double +lw_seg_length(const POINT2D *A1, const POINT2D *A2) +{ + return sqrt((A1->x-A2->x)*(A1->x-A2->x)+(A1->y-A2->y)*(A1->y-A2->y)); +} + +/** +* Returns true if P is on the same side of the plane partition +* defined by A1/A3 as A2 is. Only makes sense if P has already been +* determined to be on the circle defined by A1/A2/A3. +*/ +int +lw_pt_in_arc(const POINT2D *P, const POINT2D *A1, const POINT2D *A2, const POINT2D *A3) +{ + return lw_segment_side(A1, A3, A2) == lw_segment_side(A1, A3, P); +} + +/** +* Returns true if P is between A1/A2. Only makes sense if P has already been +* deterined to be on the line defined by A1/A2. +*/ +int +lw_pt_in_seg(const POINT2D *P, const POINT2D *A1, const POINT2D *A2) +{ + return ((A1->x <= P->x && P->x < A2->x) || (A1->x >= P->x && P->x > A2->x)) || + ((A1->y <= P->y && P->y < A2->y) || (A1->y >= P->y && P->y > A2->y)); +} + +/** +* Returns true if arc A is actually a point (all vertices are the same) . +*/ +int +lw_arc_is_pt(const POINT2D *A1, const POINT2D *A2, const POINT2D *A3) +{ + if ( A1->x == A2->x && A2->x == A3->x && + A1->y == A2->y && A2->y == A3->y ) + return LW_TRUE; + else + return LW_FALSE; +} + +/** +* Returns the length of a circular arc segment +*/ +double +lw_arc_length(const POINT2D *A1, const POINT2D *A2, const POINT2D *A3) +{ + POINT2D C; + double radius_A, circumference_A; + int a2_side, clockwise; + double a1, a3; + double angle; + + if ( lw_arc_is_pt(A1, A2, A3) ) + return 0.0; + + radius_A = lw_arc_center(A1, A2, A3, &C); + + /* Co-linear! Return linear distance! */ + if ( radius_A < 0 ) + { + double dx = A1->x - A3->x; + double dy = A1->y - A3->y; + return sqrt(dx*dx + dy*dy); + } + + /* Closed circle! Return the circumference! */ + circumference_A = M_PI * 2 * radius_A; + if ( p2d_same(A1, A3) ) + return circumference_A; + + /* Determine the orientation of the arc */ + a2_side = lw_segment_side(A1, A3, A2); + + /* The side of the A1/A3 line that A2 falls on dictates the sweep + direction from A1 to A3. */ + if ( a2_side == -1 ) + clockwise = LW_TRUE; + else + clockwise = LW_FALSE; + + /* Angles of each point that defines the arc section */ + a1 = atan2(A1->y - C.y, A1->x - C.x); + a3 = atan2(A3->y - C.y, A3->x - C.x); + + /* What's the sweep from A1 to A3? */ + if ( clockwise ) + { + if ( a1 > a3 ) + angle = a1 - a3; + else + angle = 2*M_PI + a1 - a3; + } + else + { + if ( a3 > a1 ) + angle = a3 - a1; + else + angle = 2*M_PI + a3 - a1; + } + + /* Length as proportion of circumference */ + return circumference_A * (angle / (2*M_PI)); +} + +int lw_arc_side(const POINT2D *A1, const POINT2D *A2, const POINT2D *A3, const POINT2D *Q) +{ + POINT2D C; + double radius_A; + double side_Q, side_A2; + double d; + + side_Q = lw_segment_side(A1, A3, Q); + radius_A = lw_arc_center(A1, A2, A3, &C); + side_A2 = lw_segment_side(A1, A3, A2); + + /* Linear case */ + if ( radius_A < 0 ) + return side_Q; + + d = distance2d_pt_pt(Q, &C); + + /* Q is on the arc boundary */ + if ( d == radius_A && side_Q == side_A2 ) + { + return 0; + } + + /* Q on A1-A3 line, so its on opposite side to A2 */ + if ( side_Q == 0 ) + { + return -1 * side_A2; + } + + /* + * Q is inside the arc boundary, so it's not on the side we + * might think from examining only the end points + */ + if ( d < radius_A && side_Q == side_A2 ) + { + side_Q *= -1; + } + + return side_Q; +} + +/** +* Determines the center of the circle defined by the three given points. +* In the event the circle is complete, the midpoint of the segment defined +* by the first and second points is returned. If the points are collinear, +* as determined by equal slopes, then -1.0 is returned. If the interior +* point is coincident with either end point, they are taken as collinear. +* For non-collinear cases, arc radious is returned. +*/ +double +lw_arc_center(const POINT2D *p1, const POINT2D *p2, const POINT2D *p3, POINT2D *result) +{ + POINT2D c; + double cx, cy, cr; + double dx21, dy21, dx31, dy31, h21, h31, d; + + c.x = c.y = 0.0; + + LWDEBUGF(2, "lw_arc_center called (%.16f,%.16f), (%.16f,%.16f), (%.16f,%.16f).", p1->x, p1->y, p2->x, p2->y, p3->x, p3->y); + + /* Closed circle */ + if (fabs(p1->x - p3->x) < EPSILON_SQLMM && + fabs(p1->y - p3->y) < EPSILON_SQLMM) + { + cx = p1->x + (p2->x - p1->x) / 2.0; + cy = p1->y + (p2->y - p1->y) / 2.0; + c.x = cx; + c.y = cy; + *result = c; + cr = sqrt(pow(cx - p1->x, 2.0) + pow(cy - p1->y, 2.0)); + return cr; + } + + /* Using cartesian eguations from page https://en.wikipedia.org/wiki/Circumscribed_circle */ + dx21 = p2->x - p1->x; + dy21 = p2->y - p1->y; + dx31 = p3->x - p1->x; + dy31 = p3->y - p1->y; + + h21 = pow(dx21, 2.0) + pow(dy21, 2.0); + h31 = pow(dx31, 2.0) + pow(dy31, 2.0); + + /* 2 * |Cross product|, d<0 means clockwise and d>0 counterclockwise sweeping angle */ + d = 2 * (dx21 * dy31 - dx31 * dy21); + + /* Check colinearity, |Cross product| = 0 */ + if (fabs(d) < EPSILON_SQLMM) + return -1.0; + + /* Calculate centroid coordinates and radius */ + cx = p1->x + (h21 * dy31 - h31 * dy21) / d; + cy = p1->y - (h21 * dx31 - h31 * dx21) / d; + c.x = cx; + c.y = cy; + *result = c; + cr = sqrt(pow(cx - p1->x, 2) + pow(cy - p1->y, 2)); + + LWDEBUGF(2, "lw_arc_center center is (%.16f,%.16f)", result->x, result->y); + + return cr; +} + +int +pt_in_ring_2d(const POINT2D *p, const POINTARRAY *ring) +{ + int cn = 0; /* the crossing number counter */ + uint32_t i; + const POINT2D *v1, *v2; + const POINT2D *first, *last; + + first = getPoint2d_cp(ring, 0); + last = getPoint2d_cp(ring, ring->npoints-1); + if ( memcmp(first, last, sizeof(POINT2D)) ) + { + lwerror("pt_in_ring_2d: V[n] != V[0] (%g %g != %g %g)", + first->x, first->y, last->x, last->y); + return LW_FALSE; + + } + + LWDEBUGF(2, "pt_in_ring_2d called with point: %g %g", p->x, p->y); + /* printPA(ring); */ + + /* loop through all edges of the polygon */ + v1 = getPoint2d_cp(ring, 0); + for (i=0; inpoints-1; i++) + { + double vt; + v2 = getPoint2d_cp(ring, i+1); + + /* edge from vertex i to vertex i+1 */ + if + ( + /* an upward crossing */ + ((v1->y <= p->y) && (v2->y > p->y)) + /* a downward crossing */ + || ((v1->y > p->y) && (v2->y <= p->y)) + ) + { + + vt = (double)(p->y - v1->y) / (v2->y - v1->y); + + /* P->x x < v1->x + vt * (v2->x - v1->x)) + { + /* a valid crossing of y=p->y right of p->x */ + ++cn; + } + } + v1 = v2; + } + + LWDEBUGF(3, "pt_in_ring_2d returning %d", cn&1); + + return (cn&1); /* 0 if even (out), and 1 if odd (in) */ +} + + +static int +lw_seg_interact(const POINT2D *p1, const POINT2D *p2, const POINT2D *q1, const POINT2D *q2) +{ + double minq=FP_MIN(q1->x,q2->x); + double maxq=FP_MAX(q1->x,q2->x); + double minp=FP_MIN(p1->x,p2->x); + double maxp=FP_MAX(p1->x,p2->x); + + if (FP_GT(minp,maxq) || FP_LT(maxp,minq)) + return LW_FALSE; + + minq=FP_MIN(q1->y,q2->y); + maxq=FP_MAX(q1->y,q2->y); + minp=FP_MIN(p1->y,p2->y); + maxp=FP_MAX(p1->y,p2->y); + + if (FP_GT(minp,maxq) || FP_LT(maxp,minq)) + return LW_FALSE; + + return LW_TRUE; +} + +/** +** @brief returns the kind of #CG_SEGMENT_INTERSECTION_TYPE behavior of lineseg 1 (constructed from p1 and p2) and lineseg 2 (constructed from q1 and q2) +** @param p1 start point of first straight linesegment +** @param p2 end point of first straight linesegment +** @param q1 start point of second line segment +** @param q2 end point of second line segment +** @return a #CG_SEGMENT_INTERSECTION_TYPE +** Returns one of +** SEG_ERROR = -1, +** SEG_NO_INTERSECTION = 0, +** SEG_COLINEAR = 1, +** SEG_CROSS_LEFT = 2, +** SEG_CROSS_RIGHT = 3, +*/ +int lw_segment_intersects(const POINT2D *p1, const POINT2D *p2, const POINT2D *q1, const POINT2D *q2) +{ + + int pq1, pq2, qp1, qp2; + + /* No envelope interaction => we are done. */ + if (!lw_seg_interact(p1, p2, q1, p2)) + { + return SEG_NO_INTERSECTION; + } + + /* Are the start and end points of q on the same side of p? */ + pq1=lw_segment_side(p1,p2,q1); + pq2=lw_segment_side(p1,p2,q2); + if ((pq1>0 && pq2>0) || (pq1<0 && pq2<0)) + { + return SEG_NO_INTERSECTION; + } + + /* Are the start and end points of p on the same side of q? */ + qp1=lw_segment_side(q1,q2,p1); + qp2=lw_segment_side(q1,q2,p2); + if ( (qp1 > 0.0 && qp2 > 0.0) || (qp1 < 0.0 && qp2 < 0.0) ) + { + return SEG_NO_INTERSECTION; + } + + /* Nobody is on one side or another? Must be colinear. */ + if ( pq1 == 0.0 && pq2 == 0.0 && qp1 == 0.0 && qp2 == 0.0 ) + { + return SEG_COLINEAR; + } + + /* + ** When one end-point touches, the sidedness is determined by the + ** location of the other end-point. Only touches by the first point + ** will be considered "real" to avoid double counting. + */ + LWDEBUGF(4, "pq1=%.15g pq2=%.15g", pq1, pq2); + LWDEBUGF(4, "qp1=%.15g qp2=%.15g", qp1, qp2); + + /* Second point of p or q touches, it's not a crossing. */ + if ( pq2 == 0 || qp2 == 0 ) + { + return SEG_NO_INTERSECTION; + } + + /* First point of p touches, it's a "crossing". */ + if ( pq1 == 0 ) + { + if ( pq2 > 0 ) + return SEG_CROSS_RIGHT; + else + return SEG_CROSS_LEFT; + } + + /* First point of q touches, it's a crossing. */ + if ( qp1 == 0 ) + { + if ( pq1 < pq2 ) + return SEG_CROSS_RIGHT; + else + return SEG_CROSS_LEFT; + } + + /* The segments cross, what direction is the crossing? */ + if ( pq1 < pq2 ) + return SEG_CROSS_RIGHT; + else + return SEG_CROSS_LEFT; + + /* This should never happen! */ + return SEG_ERROR; +} + +/** +** @brief lwline_crossing_direction: returns the kind of #CG_LINE_CROSS_TYPE behavior of 2 linestrings +** @param l1 first line string +** @param l2 second line string +** @return a #CG_LINE_CROSS_TYPE +** LINE_NO_CROSS = 0 +** LINE_CROSS_LEFT = -1 +** LINE_CROSS_RIGHT = 1 +** LINE_MULTICROSS_END_LEFT = -2 +** LINE_MULTICROSS_END_RIGHT = 2 +** LINE_MULTICROSS_END_SAME_FIRST_LEFT = -3 +** LINE_MULTICROSS_END_SAME_FIRST_RIGHT = 3 +** +*/ +int lwline_crossing_direction(const LWLINE *l1, const LWLINE *l2) +{ + uint32_t i = 0, j = 0; + const POINT2D *p1, *p2, *q1, *q2; + POINTARRAY *pa1 = NULL, *pa2 = NULL; + int cross_left = 0; + int cross_right = 0; + int first_cross = 0; + int this_cross = 0; +#if POSTGIS_DEBUG_LEVEL >= 4 + char *geom_ewkt; +#endif + + pa1 = (POINTARRAY*)l1->points; + pa2 = (POINTARRAY*)l2->points; + + /* One-point lines can't intersect (and shouldn't exist). */ + if ( pa1->npoints < 2 || pa2->npoints < 2 ) + return LINE_NO_CROSS; + + /* Zero length lines don't have a side. */ + if ( ptarray_length_2d(pa1) == 0 || ptarray_length_2d(pa2) == 0 ) + return LINE_NO_CROSS; + + +#if POSTGIS_DEBUG_LEVEL >= 4 + geom_ewkt = lwgeom_to_ewkt((LWGEOM*)l1); + LWDEBUGF(4, "l1 = %s", geom_ewkt); + lwfree(geom_ewkt); + geom_ewkt = lwgeom_to_ewkt((LWGEOM*)l2); + LWDEBUGF(4, "l2 = %s", geom_ewkt); + lwfree(geom_ewkt); +#endif + + /* Initialize first point of q */ + q1 = getPoint2d_cp(pa2, 0); + + for ( i = 1; i < pa2->npoints; i++ ) + { + + /* Update second point of q to next value */ + q2 = getPoint2d_cp(pa2, i); + + /* Initialize first point of p */ + p1 = getPoint2d_cp(pa1, 0); + + for ( j = 1; j < pa1->npoints; j++ ) + { + + /* Update second point of p to next value */ + p2 = getPoint2d_cp(pa1, j); + + this_cross = lw_segment_intersects(p1, p2, q1, q2); + + LWDEBUGF(4, "i=%d, j=%d (%.8g %.8g, %.8g %.8g)", this_cross, i, j, p1->x, p1->y, p2->x, p2->y); + + if ( this_cross == SEG_CROSS_LEFT ) + { + LWDEBUG(4,"this_cross == SEG_CROSS_LEFT"); + cross_left++; + if ( ! first_cross ) + first_cross = SEG_CROSS_LEFT; + } + + if ( this_cross == SEG_CROSS_RIGHT ) + { + LWDEBUG(4,"this_cross == SEG_CROSS_RIGHT"); + cross_right++; + if ( ! first_cross ) + first_cross = SEG_CROSS_RIGHT; + } + + /* + ** Crossing at a co-linearity can be turned handled by extending + ** segment to next vertex and seeing if the end points straddle + ** the co-linear segment. + */ + if ( this_cross == SEG_COLINEAR ) + { + LWDEBUG(4,"this_cross == SEG_COLINEAR"); + /* TODO: Add logic here and in segment_intersects() + continue; + */ + } + + LWDEBUG(4,"this_cross == SEG_NO_INTERSECTION"); + + /* Turn second point of p into first point */ + p1 = p2; + + } + + /* Turn second point of q into first point */ + q1 = q2; + + } + + LWDEBUGF(4, "first_cross=%d, cross_left=%d, cross_right=%d", first_cross, cross_left, cross_right); + + if ( !cross_left && !cross_right ) + return LINE_NO_CROSS; + + if ( !cross_left && cross_right == 1 ) + return LINE_CROSS_RIGHT; + + if ( !cross_right && cross_left == 1 ) + return LINE_CROSS_LEFT; + + if ( cross_left - cross_right == 1 ) + return LINE_MULTICROSS_END_LEFT; + + if ( cross_left - cross_right == -1 ) + return LINE_MULTICROSS_END_RIGHT; + + if ( cross_left - cross_right == 0 && first_cross == SEG_CROSS_LEFT ) + return LINE_MULTICROSS_END_SAME_FIRST_LEFT; + + if ( cross_left - cross_right == 0 && first_cross == SEG_CROSS_RIGHT ) + return LINE_MULTICROSS_END_SAME_FIRST_RIGHT; + + return LINE_NO_CROSS; + +} + + + + + +static char *base32 = "0123456789bcdefghjkmnpqrstuvwxyz"; + +/* +** Calculate the geohash, iterating downwards and gaining precision. +** From geohash-native.c, (c) 2008 David Troy +** Released under the MIT License. +*/ +lwvarlena_t * +geohash_point(double longitude, double latitude, int precision) +{ + int is_even=1, i=0; + double lat[2], lon[2], mid; + char bits[] = {16,8,4,2,1}; + int bit=0, ch=0; + lwvarlena_t *v = lwalloc(precision + LWVARHDRSZ); + LWSIZE_SET(v->size, precision + LWVARHDRSZ); + char *geohash = v->data; + + lat[0] = -90.0; + lat[1] = 90.0; + lon[0] = -180.0; + lon[1] = 180.0; + + while (i < precision) + { + if (is_even) + { + mid = (lon[0] + lon[1]) / 2; + if (longitude >= mid) + { + ch |= bits[bit]; + lon[0] = mid; + } + else + { + lon[1] = mid; + } + } + else + { + mid = (lat[0] + lat[1]) / 2; + if (latitude >= mid) + { + ch |= bits[bit]; + lat[0] = mid; + } + else + { + lat[1] = mid; + } + } + + is_even = !is_even; + if (bit < 4) + { + bit++; + } + else + { + geohash[i++] = base32[ch]; + bit = 0; + ch = 0; + } + } + + return v; +} + + +/* +** Calculate the geohash, iterating downwards and gaining precision. +** From geohash-native.c, (c) 2008 David Troy +** Released under the MIT License. +*/ +unsigned int geohash_point_as_int(POINT2D *pt) +{ + int is_even=1; + double lat[2], lon[2], mid; + int bit=32; + unsigned int ch = 0; + + double longitude = pt->x; + double latitude = pt->y; + + lat[0] = -90.0; + lat[1] = 90.0; + lon[0] = -180.0; + lon[1] = 180.0; + + while (--bit >= 0) + { + if (is_even) + { + mid = (lon[0] + lon[1]) / 2; + if (longitude > mid) + { + ch |= 0x0001u << bit; + lon[0] = mid; + } + else + { + lon[1] = mid; + } + } + else + { + mid = (lat[0] + lat[1]) / 2; + if (latitude > mid) + { + ch |= 0x0001 << bit; + lat[0] = mid; + } + else + { + lat[1] = mid; + } + } + + is_even = !is_even; + } + return ch; +} + +/* +** Decode a GeoHash into a bounding box. The lat and lon arguments should +** both be passed as double arrays of length 2 at a minimum where the values +** set in them will be the southwest and northeast coordinates of the bounding +** box accordingly. A precision less than 0 indicates that the entire length +** of the GeoHash should be used. +** It will call `lwerror` if an invalid character is found +*/ +void decode_geohash_bbox(char *geohash, double *lat, double *lon, int precision) +{ + bool is_even = 1; + + lat[0] = -90.0; + lat[1] = 90.0; + lon[0] = -180.0; + lon[1] = 180.0; + + size_t hashlen = strlen(geohash); + if (precision < 0 || (size_t)precision > hashlen) + { + precision = (int)hashlen; + } + + for (int i = 0; i < precision; i++) + { + char c = tolower(geohash[i]); + + /* Valid characters are all digits in base32 */ + char *base32_pos = strchr(base32, c); + if (!base32_pos) + { + lwerror("%s: Invalid character '%c'", __func__, geohash[i]); + return; + } + char cd = base32_pos - base32; + + for (size_t j = 0; j < 5; j++) + { + const char bits[] = {16, 8, 4, 2, 1}; + char mask = bits[j]; + if (is_even) + { + lon[!(cd & mask)] = (lon[0] + lon[1]) / 2; + } + else + { + lat[!(cd & mask)] = (lat[0] + lat[1]) / 2; + } + is_even = !is_even; + } + } +} + +int lwgeom_geohash_precision(GBOX bbox, GBOX *bounds) +{ + double minx, miny, maxx, maxy; + double latmax, latmin, lonmax, lonmin; + double lonwidth, latwidth; + double latmaxadjust, lonmaxadjust, latminadjust, lonminadjust; + int precision = 0; + + /* Get the bounding box, return error if things don't work out. */ + minx = bbox.xmin; + miny = bbox.ymin; + maxx = bbox.xmax; + maxy = bbox.ymax; + + if ( minx == maxx && miny == maxy ) + { + /* It's a point. Doubles have 51 bits of precision. + ** 2 * 51 / 5 == 20 */ + return 20; + } + + lonmin = -180.0; + latmin = -90.0; + lonmax = 180.0; + latmax = 90.0; + + /* Shrink a world bounding box until one of the edges interferes with the + ** bounds of our rectangle. */ + while ( 1 ) + { + lonwidth = lonmax - lonmin; + latwidth = latmax - latmin; + latmaxadjust = lonmaxadjust = latminadjust = lonminadjust = 0.0; + + if ( minx > lonmin + lonwidth / 2.0 ) + { + lonminadjust = lonwidth / 2.0; + } + else if ( maxx < lonmax - lonwidth / 2.0 ) + { + lonmaxadjust = -1 * lonwidth / 2.0; + } + if ( lonminadjust || lonmaxadjust ) + { + lonmin += lonminadjust; + lonmax += lonmaxadjust; + /* Each adjustment cycle corresponds to 2 bits of storage in the + ** geohash. */ + precision++; + } + else + { + break; + } + + if ( miny > latmin + latwidth / 2.0 ) + { + latminadjust = latwidth / 2.0; + } + else if (maxy < latmax - latwidth / 2.0 ) + { + latmaxadjust = -1 * latwidth / 2.0; + } + /* Only adjust if adjustments are legal (we haven't crossed any edges). */ + if ( latminadjust || latmaxadjust ) + { + latmin += latminadjust; + latmax += latmaxadjust; + /* Each adjustment cycle corresponds to 2 bits of storage in the + ** geohash. */ + precision++; + } + else + { + break; + } + } + + /* Save the edges of our bounds, in case someone cares later. */ + bounds->xmin = lonmin; + bounds->xmax = lonmax; + bounds->ymin = latmin; + bounds->ymax = latmax; + + /* Each geohash character (base32) can contain 5 bits of information. + ** We are returning the precision in characters, so here we divide. */ + return precision / 5; +} + + +/* +** Return a geohash string for the geometry. +** Where the precision is non-positive, calculate a precision based on the +** bounds of the feature. Big features have loose precision. +** Small features have tight precision. +*/ +lwvarlena_t * +lwgeom_geohash(const LWGEOM *lwgeom, int precision) +{ + GBOX gbox = {0}; + GBOX gbox_bounds = {0}; + double lat, lon; + int result; + + gbox_init(&gbox); + gbox_init(&gbox_bounds); + + result = lwgeom_calculate_gbox_cartesian(lwgeom, &gbox); + if ( result == LW_FAILURE ) return NULL; + + /* Return error if we are being fed something outside our working bounds */ + if ( gbox.xmin < -180 || gbox.ymin < -90 || gbox.xmax > 180 || gbox.ymax > 90 ) + { + lwerror("Geohash requires inputs in decimal degrees, got (%g %g, %g %g).", + gbox.xmin, gbox.ymin, + gbox.xmax, gbox.ymax); + return NULL; + } + + /* What is the center of our geometry bounds? We'll use that to + ** approximate location. */ + lon = gbox.xmin + (gbox.xmax - gbox.xmin) / 2; + lat = gbox.ymin + (gbox.ymax - gbox.ymin) / 2; + + if ( precision <= 0 ) + { + precision = lwgeom_geohash_precision(gbox, &gbox_bounds); + } + + /* + ** Return the geohash of the center, with a precision determined by the + ** extent of the bounds. + ** Possible change: return the point at the center of the precision bounds? + */ + return geohash_point(lon, lat, precision); +} diff --git a/mgist-postgis/liblwgeom/lwboundingcircle.c b/mgist-postgis/liblwgeom/lwboundingcircle.c new file mode 100644 index 0000000..ba6bc40 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwboundingcircle.c @@ -0,0 +1,293 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2015 Daniel Baston + * + **********************************************************************/ + + +#include +#include "liblwgeom_internal.h" + +typedef struct { + const POINT2D* p1; + const POINT2D* p2; + const POINT2D* p3; +} SUPPORTING_POINTS; + +static SUPPORTING_POINTS* +supporting_points_create() +{ + SUPPORTING_POINTS* s = lwalloc(sizeof(SUPPORTING_POINTS)); + s->p1 = NULL; + s->p2 = NULL; + s->p3 = NULL; + + return s; +} + +static void +supporting_points_destroy(SUPPORTING_POINTS* s) +{ + lwfree(s); +} + +static uint32_t +num_supporting_points(SUPPORTING_POINTS* support) +{ + uint32_t N = 0; + + if (support->p1 != NULL) + N++; + if (support->p2 != NULL) + N++; + if (support->p3 != NULL) + N++; + + return N; +} + +static int +add_supporting_point(SUPPORTING_POINTS* support, const POINT2D* p) +{ + switch(num_supporting_points(support)) + { + case 0: support->p1 = p; + break; + case 1: support->p2 = p; + break; + case 2: support->p3 = p; + break; + default: return LW_FAILURE; + } + + return LW_SUCCESS; +} + +static int +point_inside_circle(const POINT2D* p, const LWBOUNDINGCIRCLE* c) +{ + if (!c) + return LW_FALSE; + + if (distance2d_pt_pt(p, c->center) - c->radius > DBL_EPSILON) + return LW_FALSE; + + return LW_TRUE; +} + +static double +det(double m00, double m01, double m10, double m11) +{ + return m00 * m11 - m01 * m10; +} + +static void +circumcenter(const POINT2D* a, const POINT2D* b, const POINT2D* c, POINT2D* result) +{ + double cx = c->x; + double cy = c->y; + double ax = a->x - cx; + double ay = a->y - cy; + double bx = b->x - cx; + double by = b->y - cy; + + double denom = 2 * det(ax, ay, bx, by); + double numx = det(ay, ax * ax + ay * ay, by, bx * bx + by * by); + double numy = det(ax, ax * ax + ay * ay, bx, bx * bx + by * by); + + result->x = cx - numx / denom; + result->y = cy + numy / denom; +} + +static void +calculate_mbc_1(const SUPPORTING_POINTS* support, LWBOUNDINGCIRCLE* mbc) +{ + mbc->radius = 0; + mbc->center->x = support->p1->x; + mbc->center->y = support->p1->y; +} + +static void +calculate_mbc_2(const SUPPORTING_POINTS* support, LWBOUNDINGCIRCLE* mbc) +{ + double d1, d2; + + mbc->center->x = 0.5*(support->p1->x + support->p2->x); + mbc->center->y = 0.5*(support->p1->y + support->p2->y); + + d1 = distance2d_pt_pt(mbc->center, support->p1); + d2 = distance2d_pt_pt(mbc->center, support->p2); + + mbc->radius = FP_MAX(d1, d2); +} + +static void +calculate_mbc_3(const SUPPORTING_POINTS* support, LWBOUNDINGCIRCLE* mbc) +{ + double d1, d2, d3; + circumcenter(support->p1, support->p2, support->p3, mbc->center); + + d1 = distance2d_pt_pt(mbc->center, support->p1); + d2 = distance2d_pt_pt(mbc->center, support->p2); + d3 = distance2d_pt_pt(mbc->center, support->p3); + + mbc->radius = FP_MAX(FP_MAX(d1, d2), d3); +} + +static int +calculate_mbc_from_support(SUPPORTING_POINTS* support, LWBOUNDINGCIRCLE* mbc) +{ + switch(num_supporting_points(support)) + { + case 0: break; + case 1: calculate_mbc_1(support, mbc); + break; + case 2: calculate_mbc_2(support, mbc); + break; + case 3: calculate_mbc_3(support, mbc); + break; + default: return LW_FAILURE; + } + + return LW_SUCCESS; +} + +static int +calculate_mbc(const POINT2D** points, uint32_t max_n, SUPPORTING_POINTS* support, LWBOUNDINGCIRCLE* mbc) +{ + uint32_t i; + + if(!calculate_mbc_from_support(support, mbc)) + { + return LW_FAILURE; + } + + if (num_supporting_points(support) == 3) + { + /* If we're entering the function with three supporting points already, our circle + * is already fully constrained - we couldn't add another supporting point if we + * needed to. So, there's no point in proceeding further. Welzl (1991) provides + * a much better explanation of why this works. + * */ + return LW_SUCCESS; + } + + for (i = 0; i < max_n; i++) + { + if (!point_inside_circle(points[i], mbc)) + { + /* We've run into a point that isn't inside our circle. To fix this, we'll + * go back in time, and re-run the algorithm for each point we've seen so + * far, with the constraint that the current point must be on the boundary + * of the circle. Then, we'll continue on in this loop with the modified + * circle that by definition includes the current point. */ + SUPPORTING_POINTS next_support; + memcpy(&next_support, support, sizeof(SUPPORTING_POINTS)); + + add_supporting_point(&next_support, points[i]); + if (!calculate_mbc(points, i, &next_support, mbc)) + { + return LW_FAILURE; + } + } + } + + return LW_SUCCESS; +} + +static LWBOUNDINGCIRCLE* +lwboundingcircle_create() +{ + LWBOUNDINGCIRCLE* c = lwalloc(sizeof(LWBOUNDINGCIRCLE)); + c->center = lwalloc(sizeof(POINT2D)); + + c->radius = 0.0; + c->center->x = 0.0; + c->center->y = 0.0; + + return c; +} + +void +lwboundingcircle_destroy(LWBOUNDINGCIRCLE* c) +{ + lwfree(c->center); + lwfree(c); +} + +LWBOUNDINGCIRCLE* +lwgeom_calculate_mbc(const LWGEOM* g) +{ + SUPPORTING_POINTS* support; + LWBOUNDINGCIRCLE* result; + LWPOINTITERATOR* it; + uint32_t num_points; + POINT2D** points; + POINT4D p; + uint32_t i; + int success; + + if(g == NULL || lwgeom_is_empty(g)) + return LW_FAILURE; + + num_points = lwgeom_count_vertices(g); + it = lwpointiterator_create(g); + points = lwalloc(num_points * sizeof(POINT2D*)); + for (i = 0; i < num_points; i++) + { + if(!lwpointiterator_next(it, &p)) + { + uint32_t j; + for (j = 0; j < i; j++) + { + lwfree(points[j]); + } + lwpointiterator_destroy(it); + lwfree(points); + return LW_FAILURE; + } + + points[i] = lwalloc(sizeof(POINT2D)); + points[i]->x = p.x; + points[i]->y = p.y; + } + lwpointiterator_destroy(it); + + support = supporting_points_create(); + result = lwboundingcircle_create(); + /* Technically, a randomized algorithm would demand that we shuffle the input points + * before we call calculate_mbc(). However, we make the (perhaps poor) assumption that + * the order we happen to find the points is as good as random, or close enough. + * */ + success = calculate_mbc((const POINT2D**) points, num_points, support, result); + + for (i = 0; i < num_points; i++) + { + lwfree(points[i]); + } + lwfree(points); + supporting_points_destroy(support); + + if (!success) + return NULL; + + return result; +} diff --git a/mgist-postgis/liblwgeom/lwchaikins.c b/mgist-postgis/liblwgeom/lwchaikins.c new file mode 100644 index 0000000..4312a86 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwchaikins.c @@ -0,0 +1,202 @@ + +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2018 Nicklas Avén + * + **********************************************************************/ + + + #include "liblwgeom_internal.h" + + + +static POINTARRAY * ptarray_chaikin(POINTARRAY *inpts, int preserve_endpoint, int isclosed) +{ + uint32_t p, i, n_out_points=0, p1_set=0, p2_set=0; + POINT4D p1, p2; + POINTARRAY *opts; + double *dlist; + double deltaval, quarter_delta, val1, val2; + uint32_t ndims = 2 + ptarray_has_z(inpts) + ptarray_has_m(inpts); + int new_npoints = inpts->npoints * 2; + opts = ptarray_construct_empty(FLAGS_GET_Z(inpts->flags), FLAGS_GET_M(inpts->flags), new_npoints); + + dlist = (double*)(opts->serialized_pointlist); + + p1 = getPoint4d(inpts, 0); + /*if first point*/ + if(preserve_endpoint) + { + ptarray_append_point(opts, &p1, LW_TRUE); + n_out_points++; + } + + for (p=1;pnpoints;p++) + { + memcpy(&p2, &p1, sizeof(POINT4D)); + p1 = getPoint4d(inpts, p); + if(p>0) + { + p1_set = p2_set = 0; + for (i=0;i 1) + { + dlist[n_out_points * ndims + i] = val2 + quarter_delta; + p1_set = 1; + } + if(!preserve_endpoint || p < inpts->npoints - 1) + { + dlist[(n_out_points + p1_set) * ndims + i] = val1 - quarter_delta; + p2_set = 1; + } + } + n_out_points+=(p1_set + p2_set); + } + } + + /*if last point*/ + if(preserve_endpoint) + { + opts->npoints = n_out_points; + ptarray_append_point(opts, &p1, LW_TRUE); + n_out_points++; + } + + if(isclosed && !preserve_endpoint) + { + opts->npoints = n_out_points; + POINT4D first_point = getPoint4d(opts, 0); + ptarray_append_point(opts, &first_point, LW_TRUE); + n_out_points++; + } + opts->npoints = n_out_points; + + return opts; + +} + +static LWLINE* lwline_chaikin(const LWLINE *iline, int n_iterations) +{ + POINTARRAY *pa, *pa_new; + int j; + LWLINE *oline; + + if( lwline_is_empty(iline)) + return lwline_clone(iline); + + pa = iline->points; + for (j=0;j0) + ptarray_free(pa); + pa=pa_new; + } + oline = lwline_construct(iline->srid, NULL, pa); + + oline->type = iline->type; + return oline; + +} + + +static LWPOLY* lwpoly_chaikin(const LWPOLY *ipoly, int n_iterations, int preserve_endpoint) +{ + uint32_t i; + int j; + POINTARRAY *pa, *pa_new; + LWPOLY *opoly = lwpoly_construct_empty(ipoly->srid, FLAGS_GET_Z(ipoly->flags), FLAGS_GET_M(ipoly->flags)); + + if( lwpoly_is_empty(ipoly) ) + return opoly; + for (i = 0; i < ipoly->nrings; i++) + { + pa = ipoly->rings[i]; + for(j=0;j0) + ptarray_free(pa); + pa=pa_new; + } + if(pa->npoints>=4) + { + if( lwpoly_add_ring(opoly,pa ) == LW_FAILURE ) + return NULL; + } + } + + opoly->type = ipoly->type; + + if( lwpoly_is_empty(opoly) ) + return NULL; + + return opoly; + +} + + +static LWCOLLECTION* lwcollection_chaikin(const LWCOLLECTION *igeom, int n_iterations, int preserve_endpoint) +{ + LWDEBUG(2, "Entered lwcollection_set_effective_area"); + uint32_t i; + + LWCOLLECTION *out = lwcollection_construct_empty(igeom->type, igeom->srid, FLAGS_GET_Z(igeom->flags), FLAGS_GET_M(igeom->flags)); + + if( lwcollection_is_empty(igeom) ) + return out; /* should we return NULL instead ? */ + + for( i = 0; i < igeom->ngeoms; i++ ) + { + LWGEOM *ngeom = lwgeom_chaikin(igeom->geoms[i],n_iterations,preserve_endpoint); + if ( ngeom ) out = lwcollection_add_lwgeom(out, ngeom); + } + + return out; +} + + +LWGEOM* lwgeom_chaikin(const LWGEOM *igeom, int n_iterations, int preserve_endpoint) +{ + LWDEBUG(2, "Entered lwgeom_set_effective_area"); + switch (igeom->type) + { + case POINTTYPE: + case MULTIPOINTTYPE: + return lwgeom_clone(igeom); + case LINETYPE: + return (LWGEOM*)lwline_chaikin((LWLINE*)igeom, n_iterations); + case POLYGONTYPE: + return (LWGEOM*)lwpoly_chaikin((LWPOLY*)igeom,n_iterations,preserve_endpoint); + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + return (LWGEOM*)lwcollection_chaikin((LWCOLLECTION *)igeom,n_iterations,preserve_endpoint); + default: + lwerror("lwgeom_chaikin: unsupported geometry type: %s",lwtype_name(igeom->type)); + } + return NULL; +} diff --git a/mgist-postgis/liblwgeom/lwcircstring.c b/mgist-postgis/liblwgeom/lwcircstring.c new file mode 100644 index 0000000..71a1556 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwcircstring.c @@ -0,0 +1,301 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2001-2006 Refractions Research Inc. + * + **********************************************************************/ + + +/* basic LWCIRCSTRING functions */ + +#include +#include +#include +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + +void printLWCIRCSTRING(LWCIRCSTRING *curve); +void lwcircstring_release(LWCIRCSTRING *lwcirc); +char lwcircstring_same(const LWCIRCSTRING *me, const LWCIRCSTRING *you); +LWCIRCSTRING *lwcircstring_from_lwpointarray(int32_t srid, uint32_t npoints, LWPOINT **points); +LWCIRCSTRING *lwcircstring_from_lwmpoint(int32_t srid, LWMPOINT *mpoint); +LWCIRCSTRING *lwcircstring_addpoint(LWCIRCSTRING *curve, LWPOINT *point, uint32_t where); +LWCIRCSTRING *lwcircstring_removepoint(LWCIRCSTRING *curve, uint32_t index); +void lwcircstring_setPoint4d(LWCIRCSTRING *curve, uint32_t index, POINT4D *newpoint); + + + +/* + * Construct a new LWCIRCSTRING. points will *NOT* be copied + * use SRID=SRID_UNKNOWN for unknown SRID (will have 8bit type's S = 0) + */ +LWCIRCSTRING * +lwcircstring_construct(int32_t srid, GBOX *bbox, POINTARRAY *points) +{ + LWCIRCSTRING *result; + + /* + * The first arc requires three points. Each additional + * arc requires two more points. Thus the minimum point count + * is three, and the count must be odd. + */ + if (points->npoints % 2 != 1 || points->npoints < 3) + { + lwnotice("lwcircstring_construct: invalid point count %d", points->npoints); + } + + result = (LWCIRCSTRING*) lwalloc(sizeof(LWCIRCSTRING)); + + result->type = CIRCSTRINGTYPE; + + result->flags = points->flags; + FLAGS_SET_BBOX(result->flags, bbox?1:0); + + result->srid = srid; + result->points = points; + result->bbox = bbox; + + return result; +} + +LWCIRCSTRING * +lwcircstring_construct_empty(int32_t srid, char hasz, char hasm) +{ + LWCIRCSTRING *result = lwalloc(sizeof(LWCIRCSTRING)); + result->type = CIRCSTRINGTYPE; + result->flags = lwflags(hasz,hasm,0); + result->srid = srid; + result->points = ptarray_construct_empty(hasz, hasm, 1); + result->bbox = NULL; + return result; +} + +void +lwcircstring_release(LWCIRCSTRING *lwcirc) +{ + lwgeom_release(lwcircstring_as_lwgeom(lwcirc)); +} + + +void lwcircstring_free(LWCIRCSTRING *curve) +{ + if ( ! curve ) return; + + if ( curve->bbox ) + lwfree(curve->bbox); + if ( curve->points ) + ptarray_free(curve->points); + lwfree(curve); +} + + + +void printLWCIRCSTRING(LWCIRCSTRING *curve) +{ + lwnotice("LWCIRCSTRING {"); + lwnotice(" ndims = %i", (int)FLAGS_NDIMS(curve->flags)); + lwnotice(" srid = %i", (int)curve->srid); + printPA(curve->points); + lwnotice("}"); +} + +/* @brief Clone LWCIRCSTRING object. Serialized point lists are not copied. + * + * @see ptarray_clone + */ +LWCIRCSTRING * +lwcircstring_clone(const LWCIRCSTRING *g) +{ + return (LWCIRCSTRING *)lwline_clone((LWLINE *)g); +} + +/* check coordinate equality */ +char +lwcircstring_same(const LWCIRCSTRING *me, const LWCIRCSTRING *you) +{ + return ptarray_same(me->points, you->points); +} + +/* + * Construct a LWCIRCSTRING from an array of LWPOINTs + * LWCIRCSTRING dimensions are large enough to host all input dimensions. + */ +LWCIRCSTRING * +lwcircstring_from_lwpointarray(int32_t srid, uint32_t npoints, LWPOINT **points) +{ + int zmflag=0; + uint32_t i; + POINTARRAY *pa; + uint8_t *newpoints, *ptr; + size_t ptsize, size; + + /* + * Find output dimensions, check integrity + */ + for (i = 0; i < npoints; i++) + { + if (points[i]->type != POINTTYPE) + { + lwerror("lwcurve_from_lwpointarray: invalid input type: %s", + lwtype_name(points[i]->type)); + return NULL; + } + if (FLAGS_GET_Z(points[i]->flags)) zmflag |= 2; + if (FLAGS_GET_M(points[i]->flags)) zmflag |= 1; + if (zmflag == 3) break; + } + + if (zmflag == 0) ptsize = 2 * sizeof(double); + else if (zmflag == 3) ptsize = 4 * sizeof(double); + else ptsize = 3 * sizeof(double); + + /* + * Allocate output points array + */ + size = ptsize * npoints; + newpoints = lwalloc(size); + memset(newpoints, 0, size); + + ptr = newpoints; + for (i = 0; i < npoints; i++) + { + size = ptarray_point_size(points[i]->point); + memcpy(ptr, getPoint_internal(points[i]->point, 0), size); + ptr += ptsize; + } + pa = ptarray_construct_reference_data(zmflag&2, zmflag&1, npoints, newpoints); + + return lwcircstring_construct(srid, NULL, pa); +} + +/* + * Construct a LWCIRCSTRING from a LWMPOINT + */ +LWCIRCSTRING * +lwcircstring_from_lwmpoint(int32_t srid, LWMPOINT *mpoint) +{ + uint32_t i; + POINTARRAY *pa; + char zmflag = FLAGS_GET_ZM(mpoint->flags); + size_t ptsize, size; + uint8_t *newpoints, *ptr; + + if (zmflag == 0) ptsize = 2 * sizeof(double); + else if (zmflag == 3) ptsize = 4 * sizeof(double); + else ptsize = 3 * sizeof(double); + + /* Allocate space for output points */ + size = ptsize * mpoint->ngeoms; + newpoints = lwalloc(size); + memset(newpoints, 0, size); + + ptr = newpoints; + for (i = 0; i < mpoint->ngeoms; i++) + { + memcpy(ptr, + getPoint_internal(mpoint->geoms[i]->point, 0), + ptsize); + ptr += ptsize; + } + + pa = ptarray_construct_reference_data(zmflag&2, zmflag&1, mpoint->ngeoms, newpoints); + + LWDEBUGF(3, "lwcurve_from_lwmpoint: constructed pointarray for %d points, %d zmflag", mpoint->ngeoms, zmflag); + + return lwcircstring_construct(srid, NULL, pa); +} + +LWCIRCSTRING * +lwcircstring_addpoint(LWCIRCSTRING *curve, LWPOINT *point, uint32_t where) +{ + POINTARRAY *newpa; + LWCIRCSTRING *ret; + + newpa = ptarray_addPoint(curve->points, + getPoint_internal(point->point, 0), + FLAGS_NDIMS(point->flags), where); + ret = lwcircstring_construct(curve->srid, NULL, newpa); + + return ret; +} + +LWCIRCSTRING * +lwcircstring_removepoint(LWCIRCSTRING *curve, uint32_t index) +{ + POINTARRAY *newpa; + LWCIRCSTRING *ret; + + newpa = ptarray_removePoint(curve->points, index); + ret = lwcircstring_construct(curve->srid, NULL, newpa); + + return ret; +} + +/* + * Note: input will be changed, make sure you have permissions for this. + * */ +void +lwcircstring_setPoint4d(LWCIRCSTRING *curve, uint32_t index, POINT4D *newpoint) +{ + ptarray_set_point4d(curve->points, index, newpoint); +} + +int +lwcircstring_is_closed(const LWCIRCSTRING *curve) +{ + if (lwgeom_has_z((LWGEOM*)curve)) + return ptarray_is_closed_3d(curve->points); + + return ptarray_is_closed_2d(curve->points); +} + +double lwcircstring_length(const LWCIRCSTRING *circ) +{ + return lwcircstring_length_2d(circ); +} + +double lwcircstring_length_2d(const LWCIRCSTRING *circ) +{ + if ( lwcircstring_is_empty(circ) ) + return 0.0; + + return ptarray_arc_length_2d(circ->points); +} + +/* + * Returns freshly allocated #LWPOINT that corresponds to the index where. + * Returns NULL if the geometry is empty or the index invalid. + */ +LWPOINT* lwcircstring_get_lwpoint(const LWCIRCSTRING *circ, uint32_t where) { + POINT4D pt; + LWPOINT *lwpoint; + POINTARRAY *pa; + + if ( lwcircstring_is_empty(circ) || where >= circ->points->npoints ) + return NULL; + + pa = ptarray_construct_empty(FLAGS_GET_Z(circ->flags), FLAGS_GET_M(circ->flags), 1); + pt = getPoint4d(circ->points, where); + ptarray_append_point(pa, &pt, LW_TRUE); + lwpoint = lwpoint_construct(circ->srid, NULL, pa); + return lwpoint; +} + + diff --git a/mgist-postgis/liblwgeom/lwcollection.c b/mgist-postgis/liblwgeom/lwcollection.c new file mode 100644 index 0000000..2cba81c --- /dev/null +++ b/mgist-postgis/liblwgeom/lwcollection.c @@ -0,0 +1,558 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2001-2006 Refractions Research Inc. + * + **********************************************************************/ + + +#include +#include +#include +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + + +#define CHECK_LWGEOM_ZM 1 + +void +lwcollection_release(LWCOLLECTION *lwcollection) +{ + lwgeom_release(lwcollection_as_lwgeom(lwcollection)); +} + +LWCOLLECTION * +lwcollection_construct(uint8_t type, int32_t srid, GBOX *bbox, uint32_t ngeoms, LWGEOM **geoms) +{ + LWCOLLECTION *ret; + int hasz, hasm; +#ifdef CHECK_LWGEOM_ZM + char zm; + uint32_t i; +#endif + + LWDEBUGF(2, "lwcollection_construct called with %d, %d, %p, %d, %p.", type, srid, bbox, ngeoms, geoms); + + if( ! lwtype_is_collection(type) ) + lwerror("Non-collection type specified in collection constructor!"); + + hasz = 0; + hasm = 0; + if ( ngeoms > 0 ) + { + hasz = FLAGS_GET_Z(geoms[0]->flags); + hasm = FLAGS_GET_M(geoms[0]->flags); +#ifdef CHECK_LWGEOM_ZM + zm = FLAGS_GET_ZM(geoms[0]->flags); + + LWDEBUGF(3, "lwcollection_construct type[0]=%d", geoms[0]->type); + + for (i=1; itype); + + if ( zm != FLAGS_GET_ZM(geoms[i]->flags) ) + lwerror("lwcollection_construct: mixed dimension geometries: %d/%d", zm, FLAGS_GET_ZM(geoms[i]->flags)); + } +#endif + } + + + ret = lwalloc(sizeof(LWCOLLECTION)); + ret->type = type; + ret->flags = lwflags(hasz,hasm,0); + FLAGS_SET_BBOX(ret->flags, bbox?1:0); + ret->srid = srid; + ret->ngeoms = ngeoms; + ret->maxgeoms = ngeoms; + ret->geoms = geoms; + ret->bbox = bbox; + + return ret; +} + +LWCOLLECTION * +lwcollection_construct_empty(uint8_t type, int32_t srid, char hasz, char hasm) +{ + LWCOLLECTION *ret; + if( ! lwtype_is_collection(type) ) + { + lwerror("Non-collection type specified in collection constructor!"); + return NULL; + } + + ret = lwalloc(sizeof(LWCOLLECTION)); + ret->type = type; + ret->flags = lwflags(hasz,hasm,0); + ret->srid = srid; + ret->ngeoms = 0; + ret->maxgeoms = 1; /* Allocate room for sub-members, just in case. */ + ret->geoms = lwalloc(ret->maxgeoms * sizeof(LWGEOM*)); + ret->bbox = NULL; + + return ret; +} + +LWGEOM * +lwcollection_getsubgeom(LWCOLLECTION *col, int gnum) +{ + return (LWGEOM *)col->geoms[gnum]; +} + +/** + * @brief Clone #LWCOLLECTION object. #POINTARRAY are not copied. + * Bbox is cloned if present in input. + */ +LWCOLLECTION * +lwcollection_clone(const LWCOLLECTION *g) +{ + uint32_t i; + LWCOLLECTION *ret = lwalloc(sizeof(LWCOLLECTION)); + memcpy(ret, g, sizeof(LWCOLLECTION)); + if ( g->ngeoms > 0 ) + { + ret->geoms = lwalloc(sizeof(LWGEOM *)*g->ngeoms); + for (i=0; ingeoms; i++) + { + ret->geoms[i] = lwgeom_clone(g->geoms[i]); + } + if ( g->bbox ) ret->bbox = gbox_copy(g->bbox); + } + else + { + ret->bbox = NULL; /* empty collection */ + ret->geoms = NULL; + } + return ret; +} + +/** +* @brief Deep clone #LWCOLLECTION object. #POINTARRAY are copied. +*/ +LWCOLLECTION * +lwcollection_clone_deep(const LWCOLLECTION *g) +{ + uint32_t i; + LWCOLLECTION *ret = lwalloc(sizeof(LWCOLLECTION)); + memcpy(ret, g, sizeof(LWCOLLECTION)); + if ( g->ngeoms > 0 ) + { + ret->geoms = lwalloc(sizeof(LWGEOM *)*g->ngeoms); + for (i=0; ingeoms; i++) + { + ret->geoms[i] = lwgeom_clone_deep(g->geoms[i]); + } + if ( g->bbox ) ret->bbox = gbox_copy(g->bbox); + } + else + { + ret->bbox = NULL; /* empty collection */ + ret->geoms = NULL; + } + return ret; +} + +/** + * Ensure the collection can hold up at least ngeoms + */ +void lwcollection_reserve(LWCOLLECTION *col, uint32_t ngeoms) +{ + if ( ngeoms <= col->maxgeoms ) return; + + /* Allocate more space if we need it */ + do { col->maxgeoms *= 2; } while ( col->maxgeoms < ngeoms ); + col->geoms = lwrealloc(col->geoms, sizeof(LWGEOM*) * col->maxgeoms); +} + +/** +* Appends geom to the collection managed by col. Does not copy or +* clone, simply takes a reference on the passed geom. +*/ +LWCOLLECTION* lwcollection_add_lwgeom(LWCOLLECTION *col, const LWGEOM *geom) +{ + if (!col || !geom) return NULL; + + if (!col->geoms && (col->ngeoms || col->maxgeoms)) + { + lwerror("Collection is in inconsistent state. Null memory but non-zero collection counts."); + return NULL; + } + + /* Check type compatibility */ + if ( ! lwcollection_allows_subtype(col->type, geom->type) ) { + lwerror("%s cannot contain %s element", lwtype_name(col->type), lwtype_name(geom->type)); + return NULL; + } + + /* In case this is a truly empty, make some initial space */ + if (!col->geoms) + { + col->maxgeoms = 2; + col->ngeoms = 0; + col->geoms = lwalloc(col->maxgeoms * sizeof(LWGEOM*)); + } + + /* Allocate more space if we need it */ + lwcollection_reserve(col, col->ngeoms + 1); + +#if PARANOIA_LEVEL > 1 + /* See http://trac.osgeo.org/postgis/ticket/2933 */ + /* Make sure we don't already have a reference to this geom */ + { + uint32_t i = 0; + for (i = 0; i < col->ngeoms; i++) + { + if (col->geoms[i] == geom) + { + lwerror("%s [%d] found duplicate geometry in collection %p == %p", __FILE__, __LINE__, col->geoms[i], geom); + return col; + } + } + } +#endif + + col->geoms[col->ngeoms] = (LWGEOM*)geom; + col->ngeoms++; + return col; +} + +/** + * Appends all geometries from col2 to col1 in place. + * Caller is responsible to release col2. + */ +LWCOLLECTION * +lwcollection_concat_in_place(LWCOLLECTION *col1, const LWCOLLECTION *col2) +{ + uint32_t i; + if (!col1 || !col2) return NULL; + for (i = 0; i < col2->ngeoms; i++) + col1 = lwcollection_add_lwgeom(col1, col2->geoms[i]); + return col1; +} + +LWCOLLECTION* +lwcollection_segmentize2d(const LWCOLLECTION* col, double dist) +{ + uint32_t i, j; + LWGEOM** newgeoms; + + if (!col->ngeoms) return lwcollection_clone(col); + + newgeoms = lwalloc(sizeof(LWGEOM*) * col->ngeoms); + for (i = 0; i < col->ngeoms; i++) + { + newgeoms[i] = lwgeom_segmentize2d(col->geoms[i], dist); + if (!newgeoms[i]) + { + for (j = 0; j < i; j++) + lwgeom_free(newgeoms[j]); + lwfree(newgeoms); + return NULL; + } + } + + return lwcollection_construct( + col->type, col->srid, NULL, col->ngeoms, newgeoms); +} + +/** @brief check for same geometry composition + * + */ +char +lwcollection_same(const LWCOLLECTION *c1, const LWCOLLECTION *c2) +{ + uint32_t i; + + LWDEBUG(2, "lwcollection_same called"); + + if ( c1->type != c2->type ) return LW_FALSE; + if ( c1->ngeoms != c2->ngeoms ) return LW_FALSE; + + for ( i = 0; i < c1->ngeoms; i++ ) + { + if ( ! lwgeom_same(c1->geoms[i], c2->geoms[i]) ) + return LW_FALSE; + } + + /* Former method allowed out-of-order equality between collections + + hit = lwalloc(sizeof(uint32_t)*c1->ngeoms); + memset(hit, 0, sizeof(uint32_t)*c1->ngeoms); + + for (i=0; ingeoms; i++) + { + char found=0; + for (j=0; jngeoms; j++) + { + if ( hit[j] ) continue; + if ( lwgeom_same(c1->geoms[i], c2->geoms[j]) ) + { + hit[j] = 1; + found=1; + break; + } + } + if ( ! found ) return LW_FALSE; + } + */ + + return LW_TRUE; +} + +int lwcollection_ngeoms(const LWCOLLECTION *col) +{ + uint32_t i; + int ngeoms = 0; + + if ( ! col ) + { + lwerror("Null input geometry."); + return 0; + } + + for ( i = 0; i < col->ngeoms; i++ ) + { + if ( col->geoms[i]) + { + switch (col->geoms[i]->type) + { + case POINTTYPE: + case LINETYPE: + case CIRCSTRINGTYPE: + case POLYGONTYPE: + ngeoms += 1; + break; + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTICURVETYPE: + case MULTIPOLYGONTYPE: + ngeoms += col->ngeoms; + break; + case COLLECTIONTYPE: + ngeoms += lwcollection_ngeoms((LWCOLLECTION*)col->geoms[i]); + break; + } + } + } + return ngeoms; +} + +void lwcollection_free(LWCOLLECTION *col) +{ + uint32_t i; + if ( ! col ) return; + + if ( col->bbox ) + { + lwfree(col->bbox); + } + for ( i = 0; i < col->ngeoms; i++ ) + { + LWDEBUGF(4,"freeing geom[%d]", i); + if ( col->geoms && col->geoms[i] ) + lwgeom_free(col->geoms[i]); + } + if ( col->geoms ) + { + lwfree(col->geoms); + } + lwfree(col); +} + +/** +* Examines contents of collection and finds the largest coordinate +* dimension of all components. Areal > linear > puntal. +*/ +static uint32_t +lwcollection_largest_dimension(const LWCOLLECTION *col) +{ + int largest_type = 0; + size_t i; + for (i = 0; i < col->ngeoms; i++) + { + LWGEOM *g = col->geoms[i]; + int gtype = lwgeom_get_type(g); + if (lwgeom_is_collection(g)) + { + gtype = lwcollection_largest_dimension((LWCOLLECTION*)g); + } + + if (gtype == POINTTYPE || gtype == LINETYPE || gtype == POLYGONTYPE) + { + if (gtype > largest_type) + largest_type = gtype; + } + } + return largest_type; +} + + +static int +lwcollection_extract_recursive(const LWCOLLECTION* col, uint32_t type, LWCOLLECTION *col_out) +{ + size_t i; + size_t geoms_added = 0; + + for (i = 0; i < col->ngeoms; i++) + { + LWGEOM *g = col->geoms[i]; + if (lwgeom_is_collection(g)) + { + LWCOLLECTION *col_part = lwgeom_as_lwcollection(g); + geoms_added += lwcollection_extract_recursive(col_part, type, col_out); + } + + if (lwgeom_get_type(g) == type && !lwgeom_is_empty(g)) + { + lwcollection_add_lwgeom(col_out, lwgeom_clone(col->geoms[i])); + geoms_added++; + } + } + return geoms_added; +} + +LWCOLLECTION* +lwcollection_extract(const LWCOLLECTION* col, uint32_t type) +{ + LWCOLLECTION* outcol; + + if (!col) return NULL; + + /* Self-discover output type when it is not specified */ + if (!type) + type = lwcollection_largest_dimension(col); + + /* + * If self-discovery failed, there were no primitive points + * lines or polygons in the collection, so send back an + * empty collection. + */ + if (!type) + { + return lwcollection_construct_empty(COLLECTIONTYPE, + col->srid, + FLAGS_GET_Z(col->flags), + FLAGS_GET_M(col->flags)); + } + + if (!(type == POINTTYPE || type == LINETYPE || type == POLYGONTYPE)) + { + lwerror( + "Only POLYGON, LINESTRING and POINT are supported by " + "lwcollection_extract. %s requested.", + lwtype_name(type)); + return NULL; + } + + outcol = lwcollection_construct_empty(lwtype_multitype(type), + col->srid, + FLAGS_GET_Z(col->flags), + FLAGS_GET_M(col->flags)); + + lwcollection_extract_recursive(col, type, outcol); + lwgeom_add_bbox(lwcollection_as_lwgeom(outcol)); + return outcol; +} + + +LWCOLLECTION* +lwcollection_force_dims(const LWCOLLECTION *col, int hasz, int hasm, double zval, double mval) +{ + LWCOLLECTION *colout; + + /* Return 2D empty */ + if( lwcollection_is_empty(col) ) + { + colout = lwcollection_construct_empty(col->type, col->srid, hasz, hasm); + } + else + { + uint32_t i; + LWGEOM **geoms = NULL; + geoms = lwalloc(sizeof(LWGEOM*) * col->ngeoms); + for( i = 0; i < col->ngeoms; i++ ) + { + geoms[i] = lwgeom_force_dims(col->geoms[i], hasz, hasm, zval, mval); + } + colout = lwcollection_construct(col->type, col->srid, NULL, col->ngeoms, geoms); + } + return colout; +} + + +uint32_t lwcollection_count_vertices(const LWCOLLECTION *col) +{ + uint32_t i = 0; + uint32_t v = 0; /* vertices */ + assert(col); + for ( i = 0; i < col->ngeoms; i++ ) + { + v += lwgeom_count_vertices(col->geoms[i]); + } + return v; +} + + +int lwcollection_allows_subtype(int collectiontype, int subtype) +{ + if ( collectiontype == COLLECTIONTYPE ) + return LW_TRUE; + if ( collectiontype == MULTIPOINTTYPE && + subtype == POINTTYPE ) + return LW_TRUE; + if ( collectiontype == MULTILINETYPE && + subtype == LINETYPE ) + return LW_TRUE; + if ( collectiontype == MULTIPOLYGONTYPE && + subtype == POLYGONTYPE ) + return LW_TRUE; + if ( collectiontype == COMPOUNDTYPE && + (subtype == LINETYPE || subtype == CIRCSTRINGTYPE) ) + return LW_TRUE; + if ( collectiontype == CURVEPOLYTYPE && + (subtype == CIRCSTRINGTYPE || subtype == LINETYPE || subtype == COMPOUNDTYPE) ) + return LW_TRUE; + if ( collectiontype == MULTICURVETYPE && + (subtype == CIRCSTRINGTYPE || subtype == LINETYPE || subtype == COMPOUNDTYPE) ) + return LW_TRUE; + if ( collectiontype == MULTISURFACETYPE && + (subtype == POLYGONTYPE || subtype == CURVEPOLYTYPE) ) + return LW_TRUE; + if ( collectiontype == POLYHEDRALSURFACETYPE && + subtype == POLYGONTYPE ) + return LW_TRUE; + if ( collectiontype == TINTYPE && + subtype == TRIANGLETYPE ) + return LW_TRUE; + + /* Must be a bad combination! */ + return LW_FALSE; +} + +int +lwcollection_startpoint(const LWCOLLECTION* col, POINT4D* pt) +{ + if ( col->ngeoms < 1 ) + return LW_FAILURE; + + return lwgeom_startpoint(col->geoms[0], pt); +} + + diff --git a/mgist-postgis/liblwgeom/lwcompound.c b/mgist-postgis/liblwgeom/lwcompound.c new file mode 100644 index 0000000..03f4201 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwcompound.c @@ -0,0 +1,271 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2001-2006 Refractions Research Inc. + * + **********************************************************************/ + + +#include +#include +#include +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + + + +int +lwcompound_is_closed(const LWCOMPOUND *compound) +{ + size_t size; + int npoints=0; + + if ( lwgeom_has_z((LWGEOM*)compound) ) + { + size = sizeof(POINT3D); + } + else + { + size = sizeof(POINT2D); + } + + if ( compound->geoms[compound->ngeoms - 1]->type == CIRCSTRINGTYPE ) + { + npoints = ((LWCIRCSTRING *)compound->geoms[compound->ngeoms - 1])->points->npoints; + } + else if (compound->geoms[compound->ngeoms - 1]->type == LINETYPE) + { + npoints = ((LWLINE *)compound->geoms[compound->ngeoms - 1])->points->npoints; + } + + if ( memcmp(getPoint_internal( (POINTARRAY *)compound->geoms[0]->data, 0), + getPoint_internal( (POINTARRAY *)compound->geoms[compound->ngeoms - 1]->data, + npoints - 1), + size) ) + { + return LW_FALSE; + } + + return LW_TRUE; +} + +double lwcompound_length(const LWCOMPOUND *comp) +{ + return lwcompound_length_2d(comp); +} + +double lwcompound_length_2d(const LWCOMPOUND *comp) +{ + uint32_t i; + double length = 0.0; + if ( lwgeom_is_empty((LWGEOM*)comp) ) + return 0.0; + + for (i = 0; i < comp->ngeoms; i++) + { + length += lwgeom_length_2d(comp->geoms[i]); + } + return length; +} + +int lwcompound_add_lwgeom(LWCOMPOUND *comp, LWGEOM *geom) +{ + LWCOLLECTION *col = (LWCOLLECTION*)comp; + + /* Empty things can't continuously join up with other things */ + if ( lwgeom_is_empty(geom) ) + { + LWDEBUG(4, "Got an empty component for a compound curve!"); + return LW_FAILURE; + } + + if( col->ngeoms > 0 ) + { + POINT4D last, first; + /* First point of the component we are adding */ + LWLINE *newline = (LWLINE*)geom; + /* Last point of the previous component */ + LWLINE *prevline = (LWLINE*)(col->geoms[col->ngeoms-1]); + + getPoint4d_p(newline->points, 0, &first); + getPoint4d_p(prevline->points, prevline->points->npoints-1, &last); + + if ( !(FP_EQUALS(first.x,last.x) && FP_EQUALS(first.y,last.y)) ) + { + LWDEBUG(4, "Components don't join up end-to-end!"); + LWDEBUGF(4, "first pt (%g %g %g %g) last pt (%g %g %g %g)", first.x, first.y, first.z, first.m, last.x, last.y, last.z, last.m); + return LW_FAILURE; + } + } + + col = lwcollection_add_lwgeom(col, geom); + return LW_SUCCESS; +} + +LWCOMPOUND * +lwcompound_construct_empty(int32_t srid, char hasz, char hasm) +{ + LWCOMPOUND *ret = (LWCOMPOUND*)lwcollection_construct_empty(COMPOUNDTYPE, srid, hasz, hasm); + return ret; +} + +int lwgeom_contains_point(const LWGEOM *geom, const POINT2D *pt) +{ + switch( geom->type ) + { + case LINETYPE: + return ptarray_contains_point(((LWLINE*)geom)->points, pt); + case CIRCSTRINGTYPE: + return ptarrayarc_contains_point(((LWCIRCSTRING*)geom)->points, pt); + case COMPOUNDTYPE: + return lwcompound_contains_point((LWCOMPOUND*)geom, pt); + } + lwerror("lwgeom_contains_point failed"); + return LW_FAILURE; +} + +int +lwcompound_contains_point(const LWCOMPOUND *comp, const POINT2D *pt) +{ + uint32_t i; + LWLINE *lwline; + LWCIRCSTRING *lwcirc; + int wn = 0; + int winding_number = 0; + int result; + + for ( i = 0; i < comp->ngeoms; i++ ) + { + LWGEOM *lwgeom = comp->geoms[i]; + if ( lwgeom->type == LINETYPE ) + { + lwline = lwgeom_as_lwline(lwgeom); + if ( comp->ngeoms == 1 ) + { + return ptarray_contains_point(lwline->points, pt); + } + else + { + /* Don't check closure while doing p-i-p test */ + result = ptarray_contains_point_partial(lwline->points, pt, LW_FALSE, &winding_number); + } + } + else + { + lwcirc = lwgeom_as_lwcircstring(lwgeom); + if ( ! lwcirc ) { + lwerror("Unexpected component of type %s in compound curve", lwtype_name(lwgeom->type)); + return 0; + } + if ( comp->ngeoms == 1 ) + { + return ptarrayarc_contains_point(lwcirc->points, pt); + } + else + { + /* Don't check closure while doing p-i-p test */ + result = ptarrayarc_contains_point_partial(lwcirc->points, pt, LW_FALSE, &winding_number); + } + } + + /* Propogate boundary condition */ + if ( result == LW_BOUNDARY ) + return LW_BOUNDARY; + + wn += winding_number; + } + + /* Outside */ + if (wn == 0) + return LW_OUTSIDE; + + /* Inside */ + return LW_INSIDE; +} + +LWCOMPOUND * +lwcompound_construct_from_lwline(const LWLINE *lwline) +{ + LWCOMPOUND* ogeom = lwcompound_construct_empty(lwline->srid, FLAGS_GET_Z(lwline->flags), FLAGS_GET_M(lwline->flags)); + lwcompound_add_lwgeom(ogeom, lwgeom_clone((LWGEOM*)lwline)); + /* ogeom->bbox = lwline->bbox; */ + return ogeom; +} + +LWPOINT* +lwcompound_get_lwpoint(const LWCOMPOUND *lwcmp, uint32_t where) +{ + uint32_t i; + uint32_t count = 0; + uint32_t npoints = 0; + if ( lwgeom_is_empty((LWGEOM*)lwcmp) ) + return NULL; + + npoints = lwgeom_count_vertices((LWGEOM*)lwcmp); + if ( where >= npoints ) + { + lwerror("%s: index %d is not in range of number of vertices (%d) in input", __func__, where, npoints); + return NULL; + } + + for ( i = 0; i < lwcmp->ngeoms; i++ ) + { + LWGEOM* part = lwcmp->geoms[i]; + uint32_t npoints_part = lwgeom_count_vertices(part); + if ( where >= count && where < count + npoints_part ) + { + return lwline_get_lwpoint((LWLINE*)part, where - count); + } + else + { + count += npoints_part; + } + } + + return NULL; +} + + + +LWPOINT * +lwcompound_get_startpoint(const LWCOMPOUND *lwcmp) +{ + return lwcompound_get_lwpoint(lwcmp, 0); +} + +LWPOINT * +lwcompound_get_endpoint(const LWCOMPOUND *lwcmp) +{ + LWLINE *lwline; + if ( lwcmp->ngeoms < 1 ) + { + return NULL; + } + + lwline = (LWLINE*)(lwcmp->geoms[lwcmp->ngeoms-1]); + + if ( (!lwline) || (!lwline->points) || (lwline->points->npoints < 1) ) + { + return NULL; + } + + return lwline_get_lwpoint(lwline, lwline->points->npoints-1); +} + diff --git a/mgist-postgis/liblwgeom/lwcurvepoly.c b/mgist-postgis/liblwgeom/lwcurvepoly.c new file mode 100644 index 0000000..e8317e6 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwcurvepoly.c @@ -0,0 +1,168 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2001-2006 Refractions Research Inc. + * + **********************************************************************/ + + +/* basic LWCURVEPOLY manipulation */ + +#include +#include +#include +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + +LWCURVEPOLY * +lwcurvepoly_construct_empty(int32_t srid, char hasz, char hasm) +{ + LWCURVEPOLY *ret; + + ret = lwalloc(sizeof(LWCURVEPOLY)); + ret->type = CURVEPOLYTYPE; + ret->flags = lwflags(hasz, hasm, 0); + ret->srid = srid; + ret->nrings = 0; + ret->maxrings = 1; /* Allocate room for sub-members, just in case. */ + ret->rings = lwalloc(ret->maxrings * sizeof(LWGEOM*)); + ret->bbox = NULL; + + return ret; +} + +LWCURVEPOLY * +lwcurvepoly_construct_from_lwpoly(LWPOLY *lwpoly) +{ + LWCURVEPOLY *ret; + uint32_t i; + ret = lwalloc(sizeof(LWCURVEPOLY)); + ret->type = CURVEPOLYTYPE; + ret->flags = lwpoly->flags; + ret->srid = lwpoly->srid; + ret->nrings = lwpoly->nrings; + ret->maxrings = lwpoly->nrings; /* Allocate room for sub-members, just in case. */ + ret->rings = lwalloc(ret->maxrings * sizeof(LWGEOM*)); + ret->bbox = lwpoly->bbox ? gbox_clone(lwpoly->bbox) : NULL; + for ( i = 0; i < ret->nrings; i++ ) + { + ret->rings[i] = lwline_as_lwgeom(lwline_construct(ret->srid, NULL, ptarray_clone_deep(lwpoly->rings[i]))); + } + return ret; +} + +int lwcurvepoly_add_ring(LWCURVEPOLY *poly, LWGEOM *ring) +{ + uint32_t i; + + /* Can't do anything with NULLs */ + if( ! poly || ! ring ) + { + LWDEBUG(4,"NULL inputs!!! quitting"); + return LW_FAILURE; + } + + /* Check that we're not working with garbage */ + if ( poly->rings == NULL && (poly->nrings || poly->maxrings) ) + { + LWDEBUG(4,"mismatched nrings/maxrings"); + lwerror("Curvepolygon is in inconsistent state. Null memory but non-zero collection counts."); + return LW_FAILURE; + } + + /* Check that we're adding an allowed ring type */ + if ( ! ( ring->type == LINETYPE || ring->type == CIRCSTRINGTYPE || ring->type == COMPOUNDTYPE ) ) + { + LWDEBUGF(4,"got incorrect ring type: %s",lwtype_name(ring->type)); + return LW_FAILURE; + } + + + /* In case this is a truly empty, make some initial space */ + if ( poly->rings == NULL ) + { + poly->maxrings = 2; + poly->nrings = 0; + poly->rings = lwalloc(poly->maxrings * sizeof(LWGEOM*)); + } + + /* Allocate more space if we need it */ + if ( poly->nrings == poly->maxrings ) + { + poly->maxrings *= 2; + poly->rings = lwrealloc(poly->rings, sizeof(LWGEOM*) * poly->maxrings); + } + + /* Make sure we don't already have a reference to this geom */ + for ( i = 0; i < poly->nrings; i++ ) + { + if ( poly->rings[i] == ring ) + { + LWDEBUGF(4, "Found duplicate geometry in collection %p == %p", poly->rings[i], ring); + return LW_SUCCESS; + } + } + + /* Add the ring and increment the ring count */ + poly->rings[poly->nrings] = (LWGEOM*)ring; + poly->nrings++; + return LW_SUCCESS; +} + +/** + * This should be rewritten to make use of the curve itself. + */ +double +lwcurvepoly_area(const LWCURVEPOLY *curvepoly) +{ + double area = 0.0; + LWPOLY *poly; + if( lwgeom_is_empty((LWGEOM*)curvepoly) ) + return 0.0; + poly = lwcurvepoly_stroke(curvepoly, 32); + area = lwpoly_area(poly); + lwpoly_free(poly); + return area; +} + + +double +lwcurvepoly_perimeter(const LWCURVEPOLY *poly) +{ + double result=0.0; + uint32_t i; + + for (i=0; inrings; i++) + result += lwgeom_length(poly->rings[i]); + + return result; +} + +double +lwcurvepoly_perimeter_2d(const LWCURVEPOLY *poly) +{ + double result=0.0; + uint32_t i; + + for (i=0; inrings; i++) + result += lwgeom_length_2d(poly->rings[i]); + + return result; +} diff --git a/mgist-postgis/liblwgeom/lwgeodetic.c b/mgist-postgis/liblwgeom/lwgeodetic.c new file mode 100644 index 0000000..982e4e4 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeodetic.c @@ -0,0 +1,3719 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2009 Paul Ramsey + * Copyright 2009 David Skea + * + **********************************************************************/ + + +#include "liblwgeom_internal.h" +#include "lwgeodetic.h" +#include "lwgeom_log.h" + +/** +* For testing geodetic bounding box, we have a magic global variable. +* When this is true (when the cunit tests set it), use the slow, but +* guaranteed correct, algorithm. Otherwise use the regular one. +*/ +int gbox_geocentric_slow = LW_FALSE; + +/** +* Utility function for ptarray_contains_point_sphere() +*/ +static int +point3d_equals(const POINT3D *p1, const POINT3D *p2) +{ + return FP_EQUALS(p1->x, p2->x) && FP_EQUALS(p1->y, p2->y) && FP_EQUALS(p1->z, p2->z); +} + +/** +* Convert a longitude to the range of -PI,PI +*/ +double longitude_radians_normalize(double lon) +{ + if ( lon == -1.0 * M_PI ) + return M_PI; + if ( lon == -2.0 * M_PI ) + return 0.0; + + if ( lon > 2.0 * M_PI ) + lon = remainder(lon, 2.0 * M_PI); + + if ( lon < -2.0 * M_PI ) + lon = remainder(lon, -2.0 * M_PI); + + if ( lon > M_PI ) + lon = -2.0 * M_PI + lon; + + if ( lon < -1.0 * M_PI ) + lon = 2.0 * M_PI + lon; + + if ( lon == -2.0 * M_PI ) + lon *= -1.0; + + return lon; +} + +/** +* Convert a latitude to the range of -PI/2,PI/2 +*/ +double latitude_radians_normalize(double lat) +{ + + if ( lat > 2.0 * M_PI ) + lat = remainder(lat, 2.0 * M_PI); + + if ( lat < -2.0 * M_PI ) + lat = remainder(lat, -2.0 * M_PI); + + if ( lat > M_PI ) + lat = M_PI - lat; + + if ( lat < -1.0 * M_PI ) + lat = -1.0 * M_PI - lat; + + if ( lat > M_PI_2 ) + lat = M_PI - lat; + + if ( lat < -1.0 * M_PI_2 ) + lat = -1.0 * M_PI - lat; + + return lat; +} + +/** +* Convert a longitude to the range of -180,180 +* @param lon longitude in degrees +*/ +double longitude_degrees_normalize(double lon) +{ + if ( lon > 360.0 ) + lon = remainder(lon, 360.0); + + if ( lon < -360.0 ) + lon = remainder(lon, -360.0); + + if ( lon > 180.0 ) + lon = -360.0 + lon; + + if ( lon < -180.0 ) + lon = 360 + lon; + + if ( lon == -180.0 ) + return 180.0; + + if ( lon == -360.0 ) + return 0.0; + + return lon; +} + +/** +* Convert a latitude to the range of -90,90 +* @param lat latitude in degrees +*/ +double latitude_degrees_normalize(double lat) +{ + + if ( lat > 360.0 ) + lat = remainder(lat, 360.0); + + if ( lat < -360.0 ) + lat = remainder(lat, -360.0); + + if ( lat > 180.0 ) + lat = 180.0 - lat; + + if ( lat < -180.0 ) + lat = -180.0 - lat; + + if ( lat > 90.0 ) + lat = 180.0 - lat; + + if ( lat < -90.0 ) + lat = -180.0 - lat; + + return lat; +} + +/** +* Shift a point around by a number of radians +*/ +void point_shift(GEOGRAPHIC_POINT *p, double shift) +{ + double lon = p->lon + shift; + if ( lon > M_PI ) + p->lon = -1.0 * M_PI + (lon - M_PI); + else + p->lon = lon; + return; +} + +int geographic_point_equals(const GEOGRAPHIC_POINT *g1, const GEOGRAPHIC_POINT *g2) +{ + return FP_EQUALS(g1->lat, g2->lat) && FP_EQUALS(g1->lon, g2->lon); +} + +/** +* Initialize a geographic point +* @param lon longitude in degrees +* @param lat latitude in degrees +*/ +void geographic_point_init(double lon, double lat, GEOGRAPHIC_POINT *g) +{ + g->lat = latitude_radians_normalize(deg2rad(lat)); + g->lon = longitude_radians_normalize(deg2rad(lon)); +} + +/** Returns the angular height (latitudinal span) of the box in radians */ +double +gbox_angular_height(const GBOX* gbox) +{ + double d[6]; + int i; + double zmin = FLT_MAX; + double zmax = -1 * FLT_MAX; + POINT3D pt; + + /* Take a copy of the box corners so we can treat them as a list */ + /* Elements are xmin, xmax, ymin, ymax, zmin, zmax */ + memcpy(d, &(gbox->xmin), 6*sizeof(double)); + + /* Generate all 8 corner vectors of the box */ + for ( i = 0; i < 8; i++ ) + { + pt.x = d[i / 4]; + pt.y = d[2 + (i % 4) / 2]; + pt.z = d[4 + (i % 2)]; + normalize(&pt); + if ( pt.z < zmin ) zmin = pt.z; + if ( pt.z > zmax ) zmax = pt.z; + } + return asin(zmax) - asin(zmin); +} + +/** Returns the angular width (longitudinal span) of the box in radians */ +double +gbox_angular_width(const GBOX* gbox) +{ + double d[6]; + int i, j; + POINT3D pt[3]; + double maxangle; + double magnitude; + + /* Take a copy of the box corners so we can treat them as a list */ + /* Elements are xmin, xmax, ymin, ymax, zmin, zmax */ + memcpy(d, &(gbox->xmin), 6*sizeof(double)); + + /* Start with the bottom corner */ + pt[0].x = gbox->xmin; + pt[0].y = gbox->ymin; + magnitude = sqrt(pt[0].x*pt[0].x + pt[0].y*pt[0].y); + pt[0].x /= magnitude; + pt[0].y /= magnitude; + + /* Generate all 8 corner vectors of the box */ + /* Find the vector furthest from our seed vector */ + for ( j = 0; j < 2; j++ ) + { + maxangle = -1 * FLT_MAX; + for ( i = 0; i < 4; i++ ) + { + double angle, dotprod; + POINT3D pt_n; + + pt_n.x = d[i / 2]; + pt_n.y = d[2 + (i % 2)]; + magnitude = sqrt(pt_n.x*pt_n.x + pt_n.y*pt_n.y); + pt_n.x /= magnitude; + pt_n.y /= magnitude; + pt_n.z = 0.0; + + dotprod = pt_n.x*pt[j].x + pt_n.y*pt[j].y; + angle = acos(dotprod > 1.0 ? 1.0 : dotprod); + if ( angle > maxangle ) + { + pt[j+1] = pt_n; + maxangle = angle; + } + } + } + + /* Return the distance between the two furthest vectors */ + return maxangle; +} + +/** Computes the average(ish) center of the box and returns success. */ +int +gbox_centroid(const GBOX* gbox, POINT2D* out) +{ + double d[6]; + GEOGRAPHIC_POINT g; + POINT3D pt; + int i; + + /* Take a copy of the box corners so we can treat them as a list */ + /* Elements are xmin, xmax, ymin, ymax, zmin, zmax */ + memcpy(d, &(gbox->xmin), 6*sizeof(double)); + + /* Zero out our return vector */ + pt.x = pt.y = pt.z = 0.0; + + for ( i = 0; i < 8; i++ ) + { + POINT3D pt_n; + + pt_n.x = d[i / 4]; + pt_n.y = d[2 + ((i % 4) / 2)]; + pt_n.z = d[4 + (i % 2)]; + normalize(&pt_n); + + pt.x += pt_n.x; + pt.y += pt_n.y; + pt.z += pt_n.z; + } + + pt.x /= 8.0; + pt.y /= 8.0; + pt.z /= 8.0; + normalize(&pt); + + cart2geog(&pt, &g); + out->x = longitude_degrees_normalize(rad2deg(g.lon)); + out->y = latitude_degrees_normalize(rad2deg(g.lat)); + + return LW_SUCCESS; +} + +/** +* Check to see if this geocentric gbox is wrapped around a pole. +* Only makes sense if this gbox originated from a polygon, as it's assuming +* the box is generated from external edges and there's an "interior" which +* contains the pole. +* +* This function is overdetermined, for very large polygons it might add an +* unwarranted pole. STILL NEEDS WORK! +*/ +static int gbox_check_poles(GBOX *gbox) +{ + int rv = LW_FALSE; +#if POSTGIS_DEBUG_LEVEL >= 4 + char *gbox_str = gbox_to_string(gbox); + LWDEBUG(4, "checking poles"); + LWDEBUGF(4, "gbox %s", gbox_str); + lwfree(gbox_str); +#endif + /* Z axis */ + if (gbox->xmin < 0.0 && gbox->xmax > 0.0 && + gbox->ymin < 0.0 && gbox->ymax > 0.0) + { + /* Extrema lean positive */ + if ((gbox->zmin > 0.0) && (gbox->zmax > 0.0)) + { + LWDEBUG(4, "enclosed positive z axis"); + gbox->zmax = 1.0; + } + /* Extrema lean negative */ + else if ((gbox->zmin < 0.0) && (gbox->zmax < 0.0)) + { + LWDEBUG(4, "enclosed negative z axis"); + gbox->zmin = -1.0; + } + /* Extrema both sides! */ + else + { + LWDEBUG(4, "enclosed both z axes"); + gbox->zmin = -1.0; + gbox->zmax = 1.0; + } + rv = LW_TRUE; + } + + /* Y axis */ + if (gbox->xmin < 0.0 && gbox->xmax > 0.0 && + gbox->zmin < 0.0 && gbox->zmax > 0.0) + { + if ((gbox->ymin > 0.0) && (gbox->ymax > 0.0)) + { + LWDEBUG(4, "enclosed positive y axis"); + gbox->ymax = 1.0; + } + else if ((gbox->ymin < 0.0) && (gbox->ymax < 0.0)) + { + LWDEBUG(4, "enclosed negative y axis"); + gbox->ymin = -1.0; + } + else + { + LWDEBUG(4, "enclosed both y axes"); + gbox->ymax = 1.0; + gbox->ymin = -1.0; + } + rv = LW_TRUE; + } + + /* X axis */ + if (gbox->ymin < 0.0 && gbox->ymax > 0.0 && + gbox->zmin < 0.0 && gbox->zmax > 0.0) + { + if ((gbox->xmin > 0.0) && (gbox->xmax > 0.0)) + { + LWDEBUG(4, "enclosed positive x axis"); + gbox->xmax = 1.0; + } + else if ((gbox->xmin < 0.0) && (gbox->xmax < 0.0)) + { + LWDEBUG(4, "enclosed negative x axis"); + gbox->xmin = -1.0; + } + else + { + LWDEBUG(4, "enclosed both x axes"); + gbox->xmax = 1.0; + gbox->xmin = -1.0; + } + + rv = LW_TRUE; + } + + return rv; +} + +/** +* Convert spherical coordinates to cartesian coordinates on unit sphere +*/ +void geog2cart(const GEOGRAPHIC_POINT *g, POINT3D *p) +{ + p->x = cos(g->lat) * cos(g->lon); + p->y = cos(g->lat) * sin(g->lon); + p->z = sin(g->lat); +} + +/** +* Convert cartesian coordinates on unit sphere to spherical coordinates +*/ +void cart2geog(const POINT3D *p, GEOGRAPHIC_POINT *g) +{ + g->lon = atan2(p->y, p->x); + g->lat = asin(p->z); +} + +/** +* Convert lon/lat coordinates to cartesian coordinates on unit sphere +*/ +void ll2cart(const POINT2D *g, POINT3D *p) +{ + double x_rad = M_PI * g->x / 180.0; + double y_rad = M_PI * g->y / 180.0; + double cos_y_rad = cos(y_rad); + p->x = cos_y_rad * cos(x_rad); + p->y = cos_y_rad * sin(x_rad); + p->z = sin(y_rad); +} + +/** +* Convert cartesian coordinates on unit sphere to lon/lat coordinates +static void cart2ll(const POINT3D *p, POINT2D *g) +{ + g->x = longitude_degrees_normalize(180.0 * atan2(p->y, p->x) / M_PI); + g->y = latitude_degrees_normalize(180.0 * asin(p->z) / M_PI); +} +*/ + +/** +* Calculate the dot product of two unit vectors +* (-1 == opposite, 0 == orthogonal, 1 == identical) +*/ +static double dot_product(const POINT3D *p1, const POINT3D *p2) +{ + return (p1->x*p2->x) + (p1->y*p2->y) + (p1->z*p2->z); +} + +/** +* Calculate the cross product of two vectors +*/ +static void cross_product(const POINT3D *a, const POINT3D *b, POINT3D *n) +{ + n->x = a->y * b->z - a->z * b->y; + n->y = a->z * b->x - a->x * b->z; + n->z = a->x * b->y - a->y * b->x; + return; +} + +/** +* Calculate the sum of two vectors +*/ +void vector_sum(const POINT3D *a, const POINT3D *b, POINT3D *n) +{ + n->x = a->x + b->x; + n->y = a->y + b->y; + n->z = a->z + b->z; + return; +} + +/** +* Calculate the difference of two vectors +*/ +static void vector_difference(const POINT3D *a, const POINT3D *b, POINT3D *n) +{ + n->x = a->x - b->x; + n->y = a->y - b->y; + n->z = a->z - b->z; + return; +} + +/** +* Scale a vector out by a factor +*/ +void vector_scale(POINT3D *n, double scale) +{ + n->x *= scale; + n->y *= scale; + n->z *= scale; + return; +} + +/* +* static inline double vector_magnitude(const POINT3D* v) +* { +* return sqrt(v->x*v->x + v->y*v->y + v->z*v->z); +* } +*/ + +/** +* Angle between two unit vectors +*/ +double vector_angle(const POINT3D* v1, const POINT3D* v2) +{ + POINT3D v3, normal; + double angle, x, y; + + cross_product(v1, v2, &normal); + normalize(&normal); + cross_product(&normal, v1, &v3); + + x = dot_product(v1, v2); + y = dot_product(v2, &v3); + + angle = atan2(y, x); + return angle; +} + +/** +* Normalize to a unit vector. +*/ +static void normalize2d(POINT2D *p) +{ + double d = sqrt(p->x*p->x + p->y*p->y); + if (FP_IS_ZERO(d)) + { + p->x = p->y = 0.0; + return; + } + p->x = p->x / d; + p->y = p->y / d; + return; +} + +/** +* Calculates the unit normal to two vectors, trying to avoid +* problems with over-narrow or over-wide cases. +*/ +void unit_normal(const POINT3D *P1, const POINT3D *P2, POINT3D *normal) +{ + double p_dot = dot_product(P1, P2); + POINT3D P3; + + /* If edge is really large, calculate a narrower equivalent angle A1/A3. */ + if ( p_dot < 0 ) + { + vector_sum(P1, P2, &P3); + normalize(&P3); + } + /* If edge is narrow, calculate a wider equivalent angle A1/A3. */ + else if ( p_dot > 0.95 ) + { + vector_difference(P2, P1, &P3); + normalize(&P3); + } + /* Just keep the current angle in A1/A3. */ + else + { + P3 = *P2; + } + + /* Normals to the A-plane and B-plane */ + cross_product(P1, &P3, normal); + normalize(normal); +} + +/** +* Rotates v1 through an angle (in radians) within the plane defined by v1/v2, returns +* the rotated vector in n. +*/ +void vector_rotate(const POINT3D* v1, const POINT3D* v2, double angle, POINT3D* n) +{ + POINT3D u; + double cos_a = cos(angle); + double sin_a = sin(angle); + double uxuy, uyuz, uxuz; + double ux2, uy2, uz2; + double rxx, rxy, rxz, ryx, ryy, ryz, rzx, rzy, rzz; + + /* Need a unit vector normal to rotate around */ + unit_normal(v1, v2, &u); + + uxuy = u.x * u.y; + uxuz = u.x * u.z; + uyuz = u.y * u.z; + + ux2 = u.x * u.x; + uy2 = u.y * u.y; + uz2 = u.z * u.z; + + rxx = cos_a + ux2 * (1 - cos_a); + rxy = uxuy * (1 - cos_a) - u.z * sin_a; + rxz = uxuz * (1 - cos_a) + u.y * sin_a; + + ryx = uxuy * (1 - cos_a) + u.z * sin_a; + ryy = cos_a + uy2 * (1 - cos_a); + ryz = uyuz * (1 - cos_a) - u.x * sin_a; + + rzx = uxuz * (1 - cos_a) - u.y * sin_a; + rzy = uyuz * (1 - cos_a) + u.x * sin_a; + rzz = cos_a + uz2 * (1 - cos_a); + + n->x = rxx * v1->x + rxy * v1->y + rxz * v1->z; + n->y = ryx * v1->x + ryy * v1->y + ryz * v1->z; + n->z = rzx * v1->x + rzy * v1->y + rzz * v1->z; + + normalize(n); +} + +/** +* Normalize to a unit vector. +*/ +void normalize(POINT3D *p) +{ + double d = sqrt(p->x*p->x + p->y*p->y + p->z*p->z); + if (FP_IS_ZERO(d)) + { + p->x = p->y = p->z = 0.0; + return; + } + p->x = p->x / d; + p->y = p->y / d; + p->z = p->z / d; + return; +} + + +/** +* Computes the cross product of two vectors using their lat, lng representations. +* Good even for small distances between p and q. +*/ +void robust_cross_product(const GEOGRAPHIC_POINT *p, const GEOGRAPHIC_POINT *q, POINT3D *a) +{ + double lon_qpp = (q->lon + p->lon) / -2.0; + double lon_qmp = (q->lon - p->lon) / 2.0; + double sin_p_lat_minus_q_lat = sin(p->lat-q->lat); + double sin_p_lat_plus_q_lat = sin(p->lat+q->lat); + double sin_lon_qpp = sin(lon_qpp); + double sin_lon_qmp = sin(lon_qmp); + double cos_lon_qpp = cos(lon_qpp); + double cos_lon_qmp = cos(lon_qmp); + a->x = sin_p_lat_minus_q_lat * sin_lon_qpp * cos_lon_qmp - + sin_p_lat_plus_q_lat * cos_lon_qpp * sin_lon_qmp; + a->y = sin_p_lat_minus_q_lat * cos_lon_qpp * cos_lon_qmp + + sin_p_lat_plus_q_lat * sin_lon_qpp * sin_lon_qmp; + a->z = cos(p->lat) * cos(q->lat) * sin(q->lon-p->lon); +} + +void x_to_z(POINT3D *p) +{ + double tmp = p->z; + p->z = p->x; + p->x = tmp; +} + +void y_to_z(POINT3D *p) +{ + double tmp = p->z; + p->z = p->y; + p->y = tmp; +} + + +int crosses_dateline(const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e) +{ + double sign_s = SIGNUM(s->lon); + double sign_e = SIGNUM(e->lon); + double ss = fabs(s->lon); + double ee = fabs(e->lon); + if ( sign_s == sign_e ) + { + return LW_FALSE; + } + else + { + double dl = ss + ee; + if ( dl < M_PI ) + return LW_FALSE; + else if ( FP_EQUALS(dl, M_PI) ) + return LW_FALSE; + else + return LW_TRUE; + } +} + +/** +* Returns -1 if the point is to the left of the plane formed +* by the edge, 1 if the point is to the right, and 0 if the +* point is on the plane. +*/ +static int +edge_point_side(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p) +{ + POINT3D normal, pt; + double w; + /* Normal to the plane defined by e */ + robust_cross_product(&(e->start), &(e->end), &normal); + normalize(&normal); + geog2cart(p, &pt); + /* We expect the dot product of with normal with any vector in the plane to be zero */ + w = dot_product(&normal, &pt); + LWDEBUGF(4,"dot product %.9g",w); + if ( FP_IS_ZERO(w) ) + { + LWDEBUG(4, "point is on plane (dot product is zero)"); + return 0; + } + + if ( w < 0 ) + return -1; + else + return 1; +} + +/** +* Returns the angle in radians at point B of the triangle formed by A-B-C +*/ +static double +sphere_angle(const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const GEOGRAPHIC_POINT *c) +{ + POINT3D normal1, normal2; + robust_cross_product(b, a, &normal1); + robust_cross_product(b, c, &normal2); + normalize(&normal1); + normalize(&normal2); + return sphere_distance_cartesian(&normal1, &normal2); +} + +/** +* Computes the spherical area of a triangle. If C is to the left of A/B, +* the area is negative. If C is to the right of A/B, the area is positive. +* +* @param a The first triangle vertex. +* @param b The second triangle vertex. +* @param c The last triangle vertex. +* @return the signed area in radians. +*/ +static double +sphere_signed_area(const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const GEOGRAPHIC_POINT *c) +{ + double angle_a, angle_b, angle_c; + double area_radians = 0.0; + int side; + GEOGRAPHIC_EDGE e; + + angle_a = sphere_angle(b,a,c); + angle_b = sphere_angle(a,b,c); + angle_c = sphere_angle(b,c,a); + + area_radians = angle_a + angle_b + angle_c - M_PI; + + /* What's the direction of the B/C edge? */ + e.start = *a; + e.end = *b; + side = edge_point_side(&e, c); + + /* Co-linear points implies no area */ + if ( side == 0 ) + return 0.0; + + /* Add the sign to the area */ + return side * area_radians; +} + + + +/** +* Returns true if the point p is on the great circle plane. +* Forms the scalar triple product of A,B,p and if the volume of the +* resulting parallelepiped is near zero the point p is on the +* great circle plane. +*/ +int edge_point_on_plane(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p) +{ + int side = edge_point_side(e, p); + if ( side == 0 ) + return LW_TRUE; + + return LW_FALSE; +} + +/** +* Returns true if the point p is inside the cone defined by the +* two ends of the edge e. +*/ +int edge_point_in_cone(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p) +{ + POINT3D vcp, vs, ve, vp; + double vs_dot_vcp, vp_dot_vcp; + geog2cart(&(e->start), &vs); + geog2cart(&(e->end), &ve); + /* Antipodal case, everything is inside. */ + if ( vs.x == -1.0 * ve.x && vs.y == -1.0 * ve.y && vs.z == -1.0 * ve.z ) + return LW_TRUE; + geog2cart(p, &vp); + /* The normalized sum bisects the angle between start and end. */ + vector_sum(&vs, &ve, &vcp); + normalize(&vcp); + /* The projection of start onto the center defines the minimum similarity */ + vs_dot_vcp = dot_product(&vs, &vcp); + LWDEBUGF(4,"vs_dot_vcp %.19g",vs_dot_vcp); + /* The projection of candidate p onto the center */ + vp_dot_vcp = dot_product(&vp, &vcp); + LWDEBUGF(4,"vp_dot_vcp %.19g",vp_dot_vcp); + /* If p is more similar than start then p is inside the cone */ + LWDEBUGF(4,"fabs(vp_dot_vcp - vs_dot_vcp) %.39g",fabs(vp_dot_vcp - vs_dot_vcp)); + + /* + ** We want to test that vp_dot_vcp is >= vs_dot_vcp but there are + ** numerical stability issues for values that are very very nearly + ** equal. Unfortunately there are also values of vp_dot_vcp that are legitimately + ** very close to but still less than vs_dot_vcp which we also need to catch. + ** The tolerance of 10-17 seems to do the trick on 32-bit and 64-bit architectures, + ** for the test cases here. + ** However, tuning the tolerance value feels like a dangerous hack. + ** Fundamentally, the problem is that this test is so sensitive. + */ + + /* 1.1102230246251565404236316680908203125e-16 */ + + if ( vp_dot_vcp > vs_dot_vcp || fabs(vp_dot_vcp - vs_dot_vcp) < 2e-16 ) + { + LWDEBUG(4, "point is in cone"); + return LW_TRUE; + } + LWDEBUG(4, "point is not in cone"); + return LW_FALSE; +} + +/** +* True if the longitude of p is within the range of the longitude of the ends of e +*/ +int edge_contains_coplanar_point(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p) +{ + GEOGRAPHIC_EDGE g; + GEOGRAPHIC_POINT q; + double slon = fabs((e->start).lon) + fabs((e->end).lon); + double dlon = fabs(fabs((e->start).lon) - fabs((e->end).lon)); + double slat = (e->start).lat + (e->end).lat; + + LWDEBUGF(4, "e.start == GPOINT(%.6g %.6g) ", (e->start).lat, (e->start).lon); + LWDEBUGF(4, "e.end == GPOINT(%.6g %.6g) ", (e->end).lat, (e->end).lon); + LWDEBUGF(4, "p == GPOINT(%.6g %.6g) ", p->lat, p->lon); + + /* Copy values into working registers */ + g = *e; + q = *p; + + /* Vertical plane, we need to do this calculation in latitude */ + if ( FP_EQUALS( g.start.lon, g.end.lon ) ) + { + LWDEBUG(4, "vertical plane, we need to do this calculation in latitude"); + /* Supposed to be co-planar... */ + if ( ! FP_EQUALS( q.lon, g.start.lon ) ) + return LW_FALSE; + + if ( ( g.start.lat <= q.lat && q.lat <= g.end.lat ) || + ( g.end.lat <= q.lat && q.lat <= g.start.lat ) ) + { + return LW_TRUE; + } + else + { + return LW_FALSE; + } + } + + /* Over the pole, we need normalize latitude and do this calculation in latitude */ + if ( FP_EQUALS( slon, M_PI ) && ( SIGNUM(g.start.lon) != SIGNUM(g.end.lon) || FP_EQUALS(dlon, M_PI) ) ) + { + LWDEBUG(4, "over the pole..."); + /* Antipodal, everything (or nothing?) is inside */ + if ( FP_EQUALS( slat, 0.0 ) ) + return LW_TRUE; + + /* Point *is* the north pole */ + if ( slat > 0.0 && FP_EQUALS(q.lat, M_PI_2 ) ) + return LW_TRUE; + + /* Point *is* the south pole */ + if ( slat < 0.0 && FP_EQUALS(q.lat, -1.0 * M_PI_2) ) + return LW_TRUE; + + LWDEBUG(4, "coplanar?..."); + + /* Supposed to be co-planar... */ + if ( ! FP_EQUALS( q.lon, g.start.lon ) ) + return LW_FALSE; + + LWDEBUG(4, "north or south?..."); + + /* Over north pole, test based on south pole */ + if ( slat > 0.0 ) + { + LWDEBUG(4, "over the north pole..."); + if ( q.lat > FP_MIN(g.start.lat, g.end.lat) ) + return LW_TRUE; + else + return LW_FALSE; + } + else + /* Over south pole, test based on north pole */ + { + LWDEBUG(4, "over the south pole..."); + if ( q.lat < FP_MAX(g.start.lat, g.end.lat) ) + return LW_TRUE; + else + return LW_FALSE; + } + } + + /* Dateline crossing, flip everything to the opposite hemisphere */ + else if ( slon > M_PI && ( SIGNUM(g.start.lon) != SIGNUM(g.end.lon) ) ) + { + LWDEBUG(4, "crosses dateline, flip longitudes..."); + if ( g.start.lon > 0.0 ) + g.start.lon -= M_PI; + else + g.start.lon += M_PI; + if ( g.end.lon > 0.0 ) + g.end.lon -= M_PI; + else + g.end.lon += M_PI; + + if ( q.lon > 0.0 ) + q.lon -= M_PI; + else + q.lon += M_PI; + } + + if ( ( g.start.lon <= q.lon && q.lon <= g.end.lon ) || + ( g.end.lon <= q.lon && q.lon <= g.start.lon ) ) + { + LWDEBUG(4, "true, this edge contains point"); + return LW_TRUE; + } + + LWDEBUG(4, "false, this edge does not contain point"); + return LW_FALSE; +} + + +/** +* Given two points on a unit sphere, calculate their distance apart in radians. +*/ +double sphere_distance(const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e) +{ + double d_lon, cos_d_lon, cos_lat_e, sin_lat_e, cos_lat_s, sin_lat_s; + double a1, a2, a, b; + + if (FP_EQUALS(s->lat, e->lat) && FP_EQUALS(s->lon, e->lon)) return 0.0; + d_lon = e->lon - s->lon; + cos_d_lon = cos(d_lon); + cos_lat_e = cos(e->lat); + sin_lat_e = sin(e->lat); + cos_lat_s = cos(s->lat); + sin_lat_s = sin(s->lat); + + a1 = POW2(cos_lat_e * sin(d_lon)); + a2 = POW2(cos_lat_s * sin_lat_e - sin_lat_s * cos_lat_e * cos_d_lon); + a = sqrt(a1 + a2); + b = sin_lat_s * sin_lat_e + cos_lat_s * cos_lat_e * cos_d_lon; + return atan2(a, b); +} + +/** +* Given two unit vectors, calculate their distance apart in radians. +*/ +double sphere_distance_cartesian(const POINT3D *s, const POINT3D *e) +{ + return acos(FP_MIN(1.0, dot_product(s, e))); +} + +/** +* Given two points on a unit sphere, calculate the direction from s to e. +*/ +double sphere_direction(const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e, double d) +{ + double heading = 0.0; + double f; + + /* Starting from the poles? Special case. */ + if ( FP_IS_ZERO(cos(s->lat)) ) + return (s->lat > 0.0) ? M_PI : 0.0; + + f = (sin(e->lat) - sin(s->lat) * cos(d)) / (sin(d) * cos(s->lat)); + if ( FP_EQUALS(f, 1.0) ) + heading = 0.0; + else if ( FP_EQUALS(f, -1.0) ) + heading = M_PI; + else if ( fabs(f) > 1.0 ) + { + LWDEBUGF(4, "f = %g", f); + heading = acos(f); + } + else + heading = acos(f); + + if ( sin(e->lon - s->lon) < 0.0 ) + heading = -1 * heading; + + return heading; +} + +#if 0 /* unused */ +/** +* Computes the spherical excess of a spherical triangle defined by +* the three vertices A, B, C. Computes on the unit sphere (i.e., divides +* edge lengths by the radius, even if the radius is 1.0). The excess is +* signed based on the sign of the delta longitude of A and B. +* +* @param a The first triangle vertex. +* @param b The second triangle vertex. +* @param c The last triangle vertex. +* @return the signed spherical excess. +*/ +static double sphere_excess(const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const GEOGRAPHIC_POINT *c) +{ + double a_dist = sphere_distance(b, c); + double b_dist = sphere_distance(c, a); + double c_dist = sphere_distance(a, b); + double hca = sphere_direction(c, a, b_dist); + double hcb = sphere_direction(c, b, a_dist); + double sign = SIGNUM(hcb-hca); + double ss = (a_dist + b_dist + c_dist) / 2.0; + double E = tan(ss/2.0)*tan((ss-a_dist)/2.0)*tan((ss-b_dist)/2.0)*tan((ss-c_dist)/2.0); + return 4.0 * atan(sqrt(fabs(E))) * sign; +} +#endif + + +/** +* Returns true if the point p is on the minor edge defined by the +* end points of e. +*/ +int edge_contains_point(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p) +{ + if ( edge_point_in_cone(e, p) && edge_point_on_plane(e, p) ) + /* if ( edge_contains_coplanar_point(e, p) && edge_point_on_plane(e, p) ) */ + { + LWDEBUG(4, "point is on edge"); + return LW_TRUE; + } + LWDEBUG(4, "point is not on edge"); + return LW_FALSE; +} + +/** +* Used in great circle to compute the pole of the great circle. +*/ +double z_to_latitude(double z, int top) +{ + double sign = SIGNUM(z); + double tlat = acos(z); + LWDEBUGF(4, "inputs: z(%.8g) sign(%.8g) tlat(%.8g)", z, sign, tlat); + if (FP_IS_ZERO(z)) + { + if (top) return M_PI_2; + else return -1.0 * M_PI_2; + } + if (fabs(tlat) > M_PI_2 ) + { + tlat = sign * (M_PI - fabs(tlat)); + } + else + { + tlat = sign * tlat; + } + LWDEBUGF(4, "output: tlat(%.8g)", tlat); + return tlat; +} + +/** +* Computes the pole of the great circle disk which is the intersection of +* the great circle with the line of maximum/minimum gradient that lies on +* the great circle plane. +*/ +int clairaut_cartesian(const POINT3D *start, const POINT3D *end, GEOGRAPHIC_POINT *g_top, GEOGRAPHIC_POINT *g_bottom) +{ + POINT3D t1, t2; + GEOGRAPHIC_POINT vN1, vN2; + LWDEBUG(4,"entering function"); + unit_normal(start, end, &t1); + unit_normal(end, start, &t2); + LWDEBUGF(4, "unit normal t1 == POINT(%.8g %.8g %.8g)", t1.x, t1.y, t1.z); + LWDEBUGF(4, "unit normal t2 == POINT(%.8g %.8g %.8g)", t2.x, t2.y, t2.z); + cart2geog(&t1, &vN1); + cart2geog(&t2, &vN2); + g_top->lat = z_to_latitude(t1.z,LW_TRUE); + g_top->lon = vN2.lon; + g_bottom->lat = z_to_latitude(t2.z,LW_FALSE); + g_bottom->lon = vN1.lon; + LWDEBUGF(4, "clairaut top == GPOINT(%.6g %.6g)", g_top->lat, g_top->lon); + LWDEBUGF(4, "clairaut bottom == GPOINT(%.6g %.6g)", g_bottom->lat, g_bottom->lon); + return LW_SUCCESS; +} + +/** +* Computes the pole of the great circle disk which is the intersection of +* the great circle with the line of maximum/minimum gradient that lies on +* the great circle plane. +*/ +int clairaut_geographic(const GEOGRAPHIC_POINT *start, const GEOGRAPHIC_POINT *end, GEOGRAPHIC_POINT *g_top, GEOGRAPHIC_POINT *g_bottom) +{ + POINT3D t1, t2; + GEOGRAPHIC_POINT vN1, vN2; + LWDEBUG(4,"entering function"); + robust_cross_product(start, end, &t1); + normalize(&t1); + robust_cross_product(end, start, &t2); + normalize(&t2); + LWDEBUGF(4, "unit normal t1 == POINT(%.8g %.8g %.8g)", t1.x, t1.y, t1.z); + LWDEBUGF(4, "unit normal t2 == POINT(%.8g %.8g %.8g)", t2.x, t2.y, t2.z); + cart2geog(&t1, &vN1); + cart2geog(&t2, &vN2); + g_top->lat = z_to_latitude(t1.z,LW_TRUE); + g_top->lon = vN2.lon; + g_bottom->lat = z_to_latitude(t2.z,LW_FALSE); + g_bottom->lon = vN1.lon; + LWDEBUGF(4, "clairaut top == GPOINT(%.6g %.6g)", g_top->lat, g_top->lon); + LWDEBUGF(4, "clairaut bottom == GPOINT(%.6g %.6g)", g_bottom->lat, g_bottom->lon); + return LW_SUCCESS; +} + +/** +* Returns true if an intersection can be calculated, and places it in *g. +* Returns false otherwise. +*/ +int edge_intersection(const GEOGRAPHIC_EDGE *e1, const GEOGRAPHIC_EDGE *e2, GEOGRAPHIC_POINT *g) +{ + POINT3D ea, eb, v; + LWDEBUGF(4, "e1 start(%.20g %.20g) end(%.20g %.20g)", e1->start.lat, e1->start.lon, e1->end.lat, e1->end.lon); + LWDEBUGF(4, "e2 start(%.20g %.20g) end(%.20g %.20g)", e2->start.lat, e2->start.lon, e2->end.lat, e2->end.lon); + + LWDEBUGF(4, "e1 start(%.20g %.20g) end(%.20g %.20g)", rad2deg(e1->start.lon), rad2deg(e1->start.lat), rad2deg(e1->end.lon), rad2deg(e1->end.lat)); + LWDEBUGF(4, "e2 start(%.20g %.20g) end(%.20g %.20g)", rad2deg(e2->start.lon), rad2deg(e2->start.lat), rad2deg(e2->end.lon), rad2deg(e2->end.lat)); + + if ( geographic_point_equals(&(e1->start), &(e2->start)) ) + { + *g = e1->start; + return LW_TRUE; + } + if ( geographic_point_equals(&(e1->end), &(e2->end)) ) + { + *g = e1->end; + return LW_TRUE; + } + if ( geographic_point_equals(&(e1->end), &(e2->start)) ) + { + *g = e1->end; + return LW_TRUE; + } + if ( geographic_point_equals(&(e1->start), &(e2->end)) ) + { + *g = e1->start; + return LW_TRUE; + } + + robust_cross_product(&(e1->start), &(e1->end), &ea); + normalize(&ea); + robust_cross_product(&(e2->start), &(e2->end), &eb); + normalize(&eb); + LWDEBUGF(4, "e1 cross product == POINT(%.12g %.12g %.12g)", ea.x, ea.y, ea.z); + LWDEBUGF(4, "e2 cross product == POINT(%.12g %.12g %.12g)", eb.x, eb.y, eb.z); + LWDEBUGF(4, "fabs(dot_product(ea, eb)) == %.14g", fabs(dot_product(&ea, &eb))); + if ( FP_EQUALS(fabs(dot_product(&ea, &eb)), 1.0) ) + { + LWDEBUGF(4, "parallel edges found! dot_product = %.12g", dot_product(&ea, &eb)); + /* Parallel (maybe equal) edges! */ + /* Hack alert, only returning ONE end of the edge right now, most do better later. */ + /* Hack alert #2, returning a value of 2 to indicate a co-linear crossing event. */ + if ( edge_contains_point(e1, &(e2->start)) ) + { + *g = e2->start; + return 2; + } + if ( edge_contains_point(e1, &(e2->end)) ) + { + *g = e2->end; + return 2; + } + if ( edge_contains_point(e2, &(e1->start)) ) + { + *g = e1->start; + return 2; + } + if ( edge_contains_point(e2, &(e1->end)) ) + { + *g = e1->end; + return 2; + } + } + unit_normal(&ea, &eb, &v); + LWDEBUGF(4, "v == POINT(%.12g %.12g %.12g)", v.x, v.y, v.z); + g->lat = atan2(v.z, sqrt(v.x * v.x + v.y * v.y)); + g->lon = atan2(v.y, v.x); + LWDEBUGF(4, "g == GPOINT(%.12g %.12g)", g->lat, g->lon); + LWDEBUGF(4, "g == POINT(%.12g %.12g)", rad2deg(g->lon), rad2deg(g->lat)); + if ( edge_contains_point(e1, g) && edge_contains_point(e2, g) ) + { + return LW_TRUE; + } + else + { + LWDEBUG(4, "flipping point to other side of sphere"); + g->lat = -1.0 * g->lat; + g->lon = g->lon + M_PI; + if ( g->lon > M_PI ) + { + g->lon = -1.0 * (2.0 * M_PI - g->lon); + } + if ( edge_contains_point(e1, g) && edge_contains_point(e2, g) ) + { + return LW_TRUE; + } + } + return LW_FALSE; +} + +double edge_distance_to_point(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *gp, GEOGRAPHIC_POINT *closest) +{ + double d1 = 1000000000.0, d2, d3, d_nearest; + POINT3D n, p, k; + GEOGRAPHIC_POINT gk, g_nearest; + + /* Zero length edge, */ + if ( geographic_point_equals(&(e->start), &(e->end)) ) + { + if (closest) + *closest = e->start; + + return sphere_distance(&(e->start), gp); + } + + robust_cross_product(&(e->start), &(e->end), &n); + normalize(&n); + geog2cart(gp, &p); + vector_scale(&n, dot_product(&p, &n)); + vector_difference(&p, &n, &k); + normalize(&k); + cart2geog(&k, &gk); + if ( edge_point_in_cone(e, &gk) ) + { + d1 = sphere_distance(gp, &gk); + } + d2 = sphere_distance(gp, &(e->start)); + d3 = sphere_distance(gp, &(e->end)); + + d_nearest = d1; + g_nearest = gk; + + if ( d2 < d_nearest ) + { + d_nearest = d2; + g_nearest = e->start; + } + if ( d3 < d_nearest ) + { + d_nearest = d3; + g_nearest = e->end; + } + if (closest) + *closest = g_nearest; + + return d_nearest; +} + +/** +* Calculate the distance between two edges. +* IMPORTANT: this test does not check for edge intersection!!! (distance == 0) +* You have to check for intersection before calling this function. +*/ +double edge_distance_to_edge(const GEOGRAPHIC_EDGE *e1, const GEOGRAPHIC_EDGE *e2, GEOGRAPHIC_POINT *closest1, GEOGRAPHIC_POINT *closest2) +{ + double d; + GEOGRAPHIC_POINT gcp1s, gcp1e, gcp2s, gcp2e, c1, c2; + double d1s = edge_distance_to_point(e1, &(e2->start), &gcp1s); + double d1e = edge_distance_to_point(e1, &(e2->end), &gcp1e); + double d2s = edge_distance_to_point(e2, &(e1->start), &gcp2s); + double d2e = edge_distance_to_point(e2, &(e1->end), &gcp2e); + + d = d1s; + c1 = gcp1s; + c2 = e2->start; + + if ( d1e < d ) + { + d = d1e; + c1 = gcp1e; + c2 = e2->end; + } + + if ( d2s < d ) + { + d = d2s; + c1 = e1->start; + c2 = gcp2s; + } + + if ( d2e < d ) + { + d = d2e; + c1 = e1->end; + c2 = gcp2e; + } + + if ( closest1 ) *closest1 = c1; + if ( closest2 ) *closest2 = c2; + + return d; +} + + +/** +* Given a starting location r, a distance and an azimuth +* to the new point, compute the location of the projected point on the unit sphere. +*/ +int sphere_project(const GEOGRAPHIC_POINT *r, double distance, double azimuth, GEOGRAPHIC_POINT *n) +{ + double d = distance; + double lat1 = r->lat; + double lon1 = r->lon; + double lat2, lon2; + + lat2 = asin(sin(lat1)*cos(d) + cos(lat1)*sin(d)*cos(azimuth)); + + /* If we're going straight up or straight down, we don't need to calculate the longitude */ + /* TODO: this isn't quite true, what if we're going over the pole? */ + if ( FP_EQUALS(azimuth, M_PI) || FP_EQUALS(azimuth, 0.0) ) + { + lon2 = r->lon; + } + else + { + lon2 = lon1 + atan2(sin(azimuth)*sin(d)*cos(lat1), cos(d)-sin(lat1)*sin(lat2)); + } + + if ( isnan(lat2) || isnan(lon2) ) + return LW_FAILURE; + + n->lat = lat2; + n->lon = lon2; + + return LW_SUCCESS; +} + + +int edge_calculate_gbox_slow(const GEOGRAPHIC_EDGE *e, GBOX *gbox) +{ + int steps = 1000000; + int i; + double dx, dy, dz; + double distance = sphere_distance(&(e->start), &(e->end)); + POINT3D pn, p, start, end; + + /* Edge is zero length, just return the naive box */ + if ( FP_IS_ZERO(distance) ) + { + LWDEBUG(4, "edge is zero length. returning"); + geog2cart(&(e->start), &start); + geog2cart(&(e->end), &end); + gbox_init_point3d(&start, gbox); + gbox_merge_point3d(&end, gbox); + return LW_SUCCESS; + } + + /* Edge is antipodal (one point on each side of the globe), + set the box to contain the whole world and return */ + if ( FP_EQUALS(distance, M_PI) ) + { + LWDEBUG(4, "edge is antipodal. setting to maximum size box, and returning"); + gbox->xmin = gbox->ymin = gbox->zmin = -1.0; + gbox->xmax = gbox->ymax = gbox->zmax = 1.0; + return LW_SUCCESS; + } + + /* Walk along the chord between start and end incrementally, + normalizing at each step. */ + geog2cart(&(e->start), &start); + geog2cart(&(e->end), &end); + dx = (end.x - start.x)/steps; + dy = (end.y - start.y)/steps; + dz = (end.z - start.z)/steps; + p = start; + gbox->xmin = gbox->xmax = p.x; + gbox->ymin = gbox->ymax = p.y; + gbox->zmin = gbox->zmax = p.z; + for ( i = 0; i < steps; i++ ) + { + p.x += dx; + p.y += dy; + p.z += dz; + pn = p; + normalize(&pn); + gbox_merge_point3d(&pn, gbox); + } + return LW_SUCCESS; +} + +/** +* The magic function, given an edge in spherical coordinates, calculate a +* 3D bounding box that fully contains it, taking into account the curvature +* of the sphere on which it is inscribed. +* +* Any arc on the sphere defines a plane that bisects the sphere. In this plane, +* the arc is a portion of a unit circle. +* Projecting the end points of the axes (1,0,0), (-1,0,0) etc, into the plane +* and normalizing yields potential extrema points. Those points on the +* side of the plane-dividing line formed by the end points that is opposite +* the origin of the plane are extrema and should be added to the bounding box. +*/ +int edge_calculate_gbox(const POINT3D *A1, const POINT3D *A2, GBOX *gbox) +{ + POINT2D R1, R2, RX, O; + POINT3D AN, A3; + POINT3D X[6]; + int i, o_side; + + /* Initialize the box with the edge end points */ + gbox_init_point3d(A1, gbox); + gbox_merge_point3d(A2, gbox); + + /* Zero length edge, just return! */ + if ( p3d_same(A1, A2) ) + return LW_SUCCESS; + + /* Error out on antipodal edge */ + if ( FP_EQUALS(A1->x, -1*A2->x) && FP_EQUALS(A1->y, -1*A2->y) && FP_EQUALS(A1->z, -1*A2->z) ) + { + lwerror("Antipodal (180 degrees long) edge detected!"); + return LW_FAILURE; + } + + /* Create A3, a vector in the plane of A1/A2, orthogonal to A1 */ + unit_normal(A1, A2, &AN); + unit_normal(&AN, A1, &A3); + + /* Project A1 and A2 into the 2-space formed by the plane A1/A3 */ + R1.x = 1.0; + R1.y = 0.0; + R2.x = dot_product(A2, A1); + R2.y = dot_product(A2, &A3); + + /* Initialize our 3-space axis points (x+, x-, y+, y-, z+, z-) */ + memset(X, 0, sizeof(POINT3D) * 6); + X[0].x = X[2].y = X[4].z = 1.0; + X[1].x = X[3].y = X[5].z = -1.0; + + /* Initialize a 2-space origin point. */ + O.x = O.y = 0.0; + /* What side of the line joining R1/R2 is O? */ + o_side = lw_segment_side(&R1, &R2, &O); + + /* Add any extrema! */ + for ( i = 0; i < 6; i++ ) + { + /* Convert 3-space axis points to 2-space unit vectors */ + RX.x = dot_product(&(X[i]), A1); + RX.y = dot_product(&(X[i]), &A3); + normalize2d(&RX); + + /* Any axis end on the side of R1/R2 opposite the origin */ + /* is an extreme point in the arc, so we add the 3-space */ + /* version of the point on R1/R2 to the gbox */ + if ( lw_segment_side(&R1, &R2, &RX) != o_side ) + { + POINT3D Xn; + Xn.x = RX.x * A1->x + RX.y * A3.x; + Xn.y = RX.x * A1->y + RX.y * A3.y; + Xn.z = RX.x * A1->z + RX.y * A3.z; + + gbox_merge_point3d(&Xn, gbox); + } + } + + return LW_SUCCESS; +} + +/* +* When we have a globe-covering gbox but we still want an outside +* point, we do this Very Bad Hack, which is look at the first two points +* in the ring and then nudge a point to the left of that arc. +* There is an assumption of convexity built in there, as well as that +* the shape doesn't have a sharp reversal in it. It's ugly, but +* it fixes some common cases (large selection polygons) that users +* are generating. At some point all of geodetic needs a clean-room +* rewrite. +* There is also an assumption of CCW exterior ring, which is how the +* GeoJSON spec defined geographic ring orientation. +*/ +static int lwpoly_pt_outside_hack(const LWPOLY *poly, POINT2D *pt_outside) +{ + GEOGRAPHIC_POINT g1, g2, gSum; + POINT4D p1, p2; + POINT3D q1, q2, qMid, qCross, qSum; + POINTARRAY *pa; + if (lwgeom_is_empty((LWGEOM*)poly)) + return LW_FAILURE; + if (poly->nrings < 1) + return LW_FAILURE; + pa = poly->rings[0]; + if (pa->npoints < 2) + return LW_FAILURE; + + /* First two points of ring */ + getPoint4d_p(pa, 0, &p1); + getPoint4d_p(pa, 1, &p2); + /* Convert to XYZ unit vectors */ + geographic_point_init(p1.x, p1.y, &g1); + geographic_point_init(p2.x, p2.y, &g2); + geog2cart(&g1, &q1); + geog2cart(&g2, &q2); + /* Mid-point of first two points */ + vector_sum(&q1, &q2, &qMid); + normalize(&qMid); + /* Cross product of first two points (perpendicular) */ + cross_product(&q1, &q2, &qCross); + normalize(&qCross); + /* Invert it to put it outside, and scale down */ + vector_scale(&qCross, -0.2); + /* Project midpoint to the right */ + vector_sum(&qMid, &qCross, &qSum); + normalize(&qSum); + /* Convert back to lon/lat */ + cart2geog(&qSum, &gSum); + pt_outside->x = rad2deg(gSum.lon); + pt_outside->y = rad2deg(gSum.lat); + return LW_SUCCESS; +} + +int lwpoly_pt_outside(const LWPOLY *poly, POINT2D *pt_outside) +{ + int rv; + /* Make sure we have boxes */ + if ( poly->bbox ) + { + rv = gbox_pt_outside(poly->bbox, pt_outside); + } + else + { + GBOX gbox; + lwgeom_calculate_gbox_geodetic((LWGEOM*)poly, &gbox); + rv = gbox_pt_outside(&gbox, pt_outside); + } + + if (rv == LW_FALSE) + return lwpoly_pt_outside_hack(poly, pt_outside); + + return rv; +} + +/** +* Given a unit geocentric gbox, return a lon/lat (degrees) coordinate point point that is +* guaranteed to be outside the box (and therefore anything it contains). +*/ +int gbox_pt_outside(const GBOX *gbox, POINT2D *pt_outside) +{ + double grow = M_PI / 180.0 / 60.0; /* one arc-minute */ + int i; + GBOX ge; + POINT3D corners[8]; + POINT3D pt; + GEOGRAPHIC_POINT g; + + while ( grow < M_PI ) + { + /* Assign our box and expand it slightly. */ + ge = *gbox; + if ( ge.xmin > -1 ) ge.xmin -= grow; + if ( ge.ymin > -1 ) ge.ymin -= grow; + if ( ge.zmin > -1 ) ge.zmin -= grow; + if ( ge.xmax < 1 ) ge.xmax += grow; + if ( ge.ymax < 1 ) ge.ymax += grow; + if ( ge.zmax < 1 ) ge.zmax += grow; + + /* Build our eight corner points */ + corners[0].x = ge.xmin; + corners[0].y = ge.ymin; + corners[0].z = ge.zmin; + + corners[1].x = ge.xmin; + corners[1].y = ge.ymax; + corners[1].z = ge.zmin; + + corners[2].x = ge.xmin; + corners[2].y = ge.ymin; + corners[2].z = ge.zmax; + + corners[3].x = ge.xmax; + corners[3].y = ge.ymin; + corners[3].z = ge.zmin; + + corners[4].x = ge.xmax; + corners[4].y = ge.ymax; + corners[4].z = ge.zmin; + + corners[5].x = ge.xmax; + corners[5].y = ge.ymin; + corners[5].z = ge.zmax; + + corners[6].x = ge.xmin; + corners[6].y = ge.ymax; + corners[6].z = ge.zmax; + + corners[7].x = ge.xmax; + corners[7].y = ge.ymax; + corners[7].z = ge.zmax; + + LWDEBUG(4, "trying to use a box corner point..."); + for ( i = 0; i < 8; i++ ) + { + normalize(&(corners[i])); + LWDEBUGF(4, "testing corner %d: POINT(%.8g %.8g %.8g)", i, corners[i].x, corners[i].y, corners[i].z); + if ( ! gbox_contains_point3d(gbox, &(corners[i])) ) + { + LWDEBUGF(4, "corner %d is outside our gbox", i); + pt = corners[i]; + normalize(&pt); + cart2geog(&pt, &g); + pt_outside->x = rad2deg(g.lon); + pt_outside->y = rad2deg(g.lat); + LWDEBUGF(4, "returning POINT(%.8g %.8g) as outside point", pt_outside->x, pt_outside->y); + return LW_SUCCESS; + } + } + + /* Try a wider growth to push the corners outside the original box. */ + grow *= 2.0; + } + + /* This should never happen! */ + // lwerror("BOOM! Could not generate outside point!"); + return LW_FAILURE; +} + + +static int ptarray_segmentize_sphere_edge_recursive ( + const POINT3D *p1, const POINT3D *p2, /* 3-space points we are interpolating between */ + const POINT4D *v1, const POINT4D *v2, /* real values and z/m values */ + double d, double max_seg_length, /* current segment length and segment limit */ + POINTARRAY *pa) /* write out results here */ +{ + GEOGRAPHIC_POINT g; + /* Reached the terminal leaf in recursion. Add */ + /* the left-most point to the pointarray here */ + /* We recurse down the left side first, so outputs should */ + /* end up added to the array in order this way */ + if (d <= max_seg_length) + { + POINT4D p; + cart2geog(p1, &g); + p.x = v1->x; + p.y = v1->y; + p.z = v1->z; + p.m = v1->m; + return ptarray_append_point(pa, &p, LW_FALSE); + } + /* Find the mid-point and recurse on the left and then the right */ + else + { + /* Calculate mid-point */ + POINT3D mid; + mid.x = (p1->x + p2->x) / 2.0; + mid.y = (p1->y + p2->y) / 2.0; + mid.z = (p1->z + p2->z) / 2.0; + normalize(&mid); + + /* Calculate z/m mid-values */ + POINT4D midv; + cart2geog(&mid, &g); + midv.x = rad2deg(g.lon); + midv.y = rad2deg(g.lat); + midv.z = (v1->z + v2->z) / 2.0; + midv.m = (v1->m + v2->m) / 2.0; + /* Recurse on the left first */ + ptarray_segmentize_sphere_edge_recursive(p1, &mid, v1, &midv, d/2.0, max_seg_length, pa); + ptarray_segmentize_sphere_edge_recursive(&mid, p2, &midv, v2, d/2.0, max_seg_length, pa); + return LW_SUCCESS; + } +} + +/** +* Create a new point array with no segment longer than the input segment length (expressed in radians!) +* @param pa_in - input point array pointer +* @param max_seg_length - maximum output segment length in radians +*/ +static POINTARRAY* +ptarray_segmentize_sphere(const POINTARRAY *pa_in, double max_seg_length) +{ + POINTARRAY *pa_out; + int hasz = ptarray_has_z(pa_in); + int hasm = ptarray_has_m(pa_in); + POINT4D p1, p2; + POINT3D q1, q2; + GEOGRAPHIC_POINT g1, g2; + uint32_t i; + + /* Just crap out on crazy input */ + if ( ! pa_in ) + lwerror("%s: null input pointarray", __func__); + if ( max_seg_length <= 0.0 ) + lwerror("%s: maximum segment length must be positive", __func__); + + /* Empty starting array */ + pa_out = ptarray_construct_empty(hasz, hasm, pa_in->npoints); + + /* Simple loop per edge */ + for (i = 1; i < pa_in->npoints; i++) + { + getPoint4d_p(pa_in, i-1, &p1); + getPoint4d_p(pa_in, i, &p2); + geographic_point_init(p1.x, p1.y, &g1); + geographic_point_init(p2.x, p2.y, &g2); + + /* Skip duplicate points (except in case of 2-point lines!) */ + if ((pa_in->npoints > 2) && p4d_same(&p1, &p2)) + continue; + + /* How long is this edge? */ + double d = sphere_distance(&g1, &g2); + + if (d > max_seg_length) + { + geog2cart(&g1, &q1); + geog2cart(&g2, &q2); + /* 3-d end points, XYZM end point, current edge size, min edge size */ + ptarray_segmentize_sphere_edge_recursive(&q1, &q2, &p1, &p2, d, max_seg_length, pa_out); + } + /* If we don't segmentize, we need to add first point manually */ + else + { + ptarray_append_point(pa_out, &p1, LW_TRUE); + } + } + /* Always add the last point */ + ptarray_append_point(pa_out, &p2, LW_TRUE); + return pa_out; +} + +/** +* Create a new, densified geometry where no segment is longer than max_seg_length. +* Input geometry is not altered, output geometry must be freed by caller. +* @param lwg_in = input geometry +* @param max_seg_length = maximum segment length in radians +*/ +LWGEOM* +lwgeom_segmentize_sphere(const LWGEOM *lwg_in, double max_seg_length) +{ + POINTARRAY *pa_out; + LWLINE *lwline; + LWPOLY *lwpoly_in, *lwpoly_out; + LWCOLLECTION *lwcol_in, *lwcol_out; + uint32_t i; + + /* Reflect NULL */ + if ( ! lwg_in ) + return NULL; + + /* Clone empty */ + if ( lwgeom_is_empty(lwg_in) ) + return lwgeom_clone(lwg_in); + + switch (lwg_in->type) + { + case MULTIPOINTTYPE: + case POINTTYPE: + return lwgeom_clone_deep(lwg_in); + break; + case LINETYPE: + lwline = lwgeom_as_lwline(lwg_in); + pa_out = ptarray_segmentize_sphere(lwline->points, max_seg_length); + return lwline_as_lwgeom(lwline_construct(lwg_in->srid, NULL, pa_out)); + break; + case POLYGONTYPE: + lwpoly_in = lwgeom_as_lwpoly(lwg_in); + lwpoly_out = lwpoly_construct_empty(lwg_in->srid, lwgeom_has_z(lwg_in), lwgeom_has_m(lwg_in)); + for ( i = 0; i < lwpoly_in->nrings; i++ ) + { + pa_out = ptarray_segmentize_sphere(lwpoly_in->rings[i], max_seg_length); + lwpoly_add_ring(lwpoly_out, pa_out); + } + return lwpoly_as_lwgeom(lwpoly_out); + break; + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + lwcol_in = lwgeom_as_lwcollection(lwg_in); + lwcol_out = lwcollection_construct_empty(lwg_in->type, lwg_in->srid, lwgeom_has_z(lwg_in), lwgeom_has_m(lwg_in)); + for ( i = 0; i < lwcol_in->ngeoms; i++ ) + { + lwcollection_add_lwgeom(lwcol_out, lwgeom_segmentize_sphere(lwcol_in->geoms[i], max_seg_length)); + } + return lwcollection_as_lwgeom(lwcol_out); + break; + default: + lwerror("lwgeom_segmentize_sphere: unsupported input geometry type: %d - %s", + lwg_in->type, lwtype_name(lwg_in->type)); + break; + } + + lwerror("lwgeom_segmentize_sphere got to the end of the function, should not happen"); + return NULL; +} + + +/** +* Returns the area of the ring (ring must be closed) in square radians (surface of +* the sphere is 4*PI). +*/ +double +ptarray_area_sphere(const POINTARRAY *pa) +{ + uint32_t i; + const POINT2D *p; + GEOGRAPHIC_POINT a, b, c; + double area = 0.0; + + /* Return zero on nonsensical inputs */ + if ( ! pa || pa->npoints < 4 ) + return 0.0; + + p = getPoint2d_cp(pa, 0); + geographic_point_init(p->x, p->y, &a); + p = getPoint2d_cp(pa, 1); + geographic_point_init(p->x, p->y, &b); + + for ( i = 2; i < pa->npoints-1; i++ ) + { + p = getPoint2d_cp(pa, i); + geographic_point_init(p->x, p->y, &c); + area += sphere_signed_area(&a, &b, &c); + b = c; + } + + return fabs(area); +} + + +static double ptarray_distance_spheroid(const POINTARRAY *pa1, const POINTARRAY *pa2, const SPHEROID *s, double tolerance, int check_intersection) +{ + GEOGRAPHIC_EDGE e1, e2; + GEOGRAPHIC_POINT g1, g2; + GEOGRAPHIC_POINT nearest1, nearest2; + POINT3D A1, A2, B1, B2; + const POINT2D *p; + double distance; + uint32_t i, j; + int use_sphere = (s->a == s->b ? 1 : 0); + + /* Make result really big, so that everything will be smaller than it */ + distance = FLT_MAX; + + /* Empty point arrays? Return negative */ + if ( pa1->npoints == 0 || pa2->npoints == 0 ) + return -1.0; + + /* Handle point/point case here */ + if ( pa1->npoints == 1 && pa2->npoints == 1 ) + { + p = getPoint2d_cp(pa1, 0); + geographic_point_init(p->x, p->y, &g1); + p = getPoint2d_cp(pa2, 0); + geographic_point_init(p->x, p->y, &g2); + /* Sphere special case, axes equal */ + distance = s->radius * sphere_distance(&g1, &g2); + if ( use_sphere ) + return distance; + /* Below tolerance, actual distance isn't of interest */ + else if ( distance < 0.95 * tolerance ) + return distance; + /* Close or greater than tolerance, get the real answer to be sure */ + else + return spheroid_distance(&g1, &g2, s); + } + + /* Handle point/line case here */ + if ( pa1->npoints == 1 || pa2->npoints == 1 ) + { + /* Handle one/many case here */ + uint32_t i; + const POINTARRAY *pa_one; + const POINTARRAY *pa_many; + + if ( pa1->npoints == 1 ) + { + pa_one = pa1; + pa_many = pa2; + } + else + { + pa_one = pa2; + pa_many = pa1; + } + + /* Initialize our point */ + p = getPoint2d_cp(pa_one, 0); + geographic_point_init(p->x, p->y, &g1); + + /* Initialize start of line */ + p = getPoint2d_cp(pa_many, 0); + geographic_point_init(p->x, p->y, &(e1.start)); + + /* Iterate through the edges in our line */ + for ( i = 1; i < pa_many->npoints; i++ ) + { + double d; + p = getPoint2d_cp(pa_many, i); + geographic_point_init(p->x, p->y, &(e1.end)); + /* Get the spherical distance between point and edge */ + d = s->radius * edge_distance_to_point(&e1, &g1, &g2); + /* New shortest distance! Record this distance / location */ + if ( d < distance ) + { + distance = d; + nearest2 = g2; + } + /* We've gotten closer than the tolerance... */ + if ( d <= tolerance ) + { + /* Working on a sphere? The answer is correct, return */ + if ( use_sphere ) + { + return d; + } + /* Far enough past the tolerance that the spheroid calculation won't change things */ + else if ( d <= tolerance * 0.95 ) + { + return d; + } + /* On a spheroid and near the tolerance? Confirm that we are *actually* closer than tolerance */ + else + { + d = spheroid_distance(&g1, &nearest2, s); + /* Yes, closer than tolerance, return! */ + if ( d <= tolerance ) + return d; + } + } + e1.start = e1.end; + } + + /* On sphere, return answer */ + if ( use_sphere ) + return distance; + /* On spheroid, calculate final answer based on closest approach */ + else + return spheroid_distance(&g1, &nearest2, s); + + } + + /* Initialize start of line 1 */ + p = getPoint2d_cp(pa1, 0); + geographic_point_init(p->x, p->y, &(e1.start)); + geog2cart(&(e1.start), &A1); + + + /* Handle line/line case */ + for ( i = 1; i < pa1->npoints; i++ ) + { + p = getPoint2d_cp(pa1, i); + geographic_point_init(p->x, p->y, &(e1.end)); + geog2cart(&(e1.end), &A2); + + /* Initialize start of line 2 */ + p = getPoint2d_cp(pa2, 0); + geographic_point_init(p->x, p->y, &(e2.start)); + geog2cart(&(e2.start), &B1); + + for ( j = 1; j < pa2->npoints; j++ ) + { + double d; + + p = getPoint2d_cp(pa2, j); + geographic_point_init(p->x, p->y, &(e2.end)); + geog2cart(&(e2.end), &B2); + + LWDEBUGF(4, "e1.start == GPOINT(%.6g %.6g) ", e1.start.lat, e1.start.lon); + LWDEBUGF(4, "e1.end == GPOINT(%.6g %.6g) ", e1.end.lat, e1.end.lon); + LWDEBUGF(4, "e2.start == GPOINT(%.6g %.6g) ", e2.start.lat, e2.start.lon); + LWDEBUGF(4, "e2.end == GPOINT(%.6g %.6g) ", e2.end.lat, e2.end.lon); + + if ( check_intersection && edge_intersects(&A1, &A2, &B1, &B2) ) + { + LWDEBUG(4,"edge intersection! returning 0.0"); + return 0.0; + } + d = s->radius * edge_distance_to_edge(&e1, &e2, &g1, &g2); + LWDEBUGF(4,"got edge_distance_to_edge %.8g", d); + + if ( d < distance ) + { + distance = d; + nearest1 = g1; + nearest2 = g2; + } + if ( d <= tolerance ) + { + if ( use_sphere ) + { + return d; + } + else + { + d = spheroid_distance(&nearest1, &nearest2, s); + if ( d <= tolerance ) + return d; + } + } + + /* Copy end to start to allow a new end value in next iteration */ + e2.start = e2.end; + B1 = B2; + } + + /* Copy end to start to allow a new end value in next iteration */ + e1.start = e1.end; + A1 = A2; + LW_ON_INTERRUPT(return -1.0); + } + LWDEBUGF(4,"finished all loops, returning %.8g", distance); + + if ( use_sphere ) + return distance; + else + return spheroid_distance(&nearest1, &nearest2, s); +} + + +/** +* Calculate the area of an LWGEOM. Anything except POLYGON, MULTIPOLYGON +* and GEOMETRYCOLLECTION return zero immediately. Multi's recurse, polygons +* calculate external ring area and subtract internal ring area. A GBOX is +* required to calculate an outside point. +*/ +double lwgeom_area_sphere(const LWGEOM *lwgeom, const SPHEROID *spheroid) +{ + int type; + double radius2 = spheroid->radius * spheroid->radius; + + assert(lwgeom); + + /* No area in nothing */ + if ( lwgeom_is_empty(lwgeom) ) + return 0.0; + + /* Read the geometry type number */ + type = lwgeom->type; + + /* Anything but polygons and collections returns zero */ + if ( ! ( type == POLYGONTYPE || type == MULTIPOLYGONTYPE || type == COLLECTIONTYPE ) ) + return 0.0; + + /* Actually calculate area */ + if ( type == POLYGONTYPE ) + { + LWPOLY *poly = (LWPOLY*)lwgeom; + uint32_t i; + double area = 0.0; + + /* Just in case there's no rings */ + if ( poly->nrings < 1 ) + return 0.0; + + /* First, the area of the outer ring */ + area += radius2 * ptarray_area_sphere(poly->rings[0]); + + /* Subtract areas of inner rings */ + for ( i = 1; i < poly->nrings; i++ ) + { + area -= radius2 * ptarray_area_sphere(poly->rings[i]); + } + return area; + } + + /* Recurse into sub-geometries to get area */ + if ( type == MULTIPOLYGONTYPE || type == COLLECTIONTYPE ) + { + LWCOLLECTION *col = (LWCOLLECTION*)lwgeom; + uint32_t i; + double area = 0.0; + + for ( i = 0; i < col->ngeoms; i++ ) + { + area += lwgeom_area_sphere(col->geoms[i], spheroid); + } + return area; + } + + /* Shouldn't get here. */ + return 0.0; +} + + +/** +* Calculate a projected point given a source point, a distance and a bearing. +* @param r - location of first point. +* @param spheroid - spheroid definition. +* @param distance - distance, in units of the spheroid def'n. +* @param azimuth - azimuth in radians. +* @return s - location of projected point. +* +*/ +LWPOINT* lwgeom_project_spheroid(const LWPOINT *r, const SPHEROID *spheroid, double distance, double azimuth) +{ + GEOGRAPHIC_POINT geo_source, geo_dest; + POINT4D pt_dest; + double x, y; + LWPOINT *lwp; + int has_z, has_m; + + /* Normalize distance to be positive*/ + if ( distance < 0.0 ) { + distance = -distance; + azimuth += M_PI; + } + + /* Normalize azimuth */ + azimuth -= 2.0 * M_PI * floor(azimuth / (2.0 * M_PI)); + + /* Check the distance validity */ + if ( distance > (M_PI * spheroid->radius) ) + { + lwerror("Distance must not be greater than %g", M_PI * spheroid->radius); + return NULL; + } + + /* Convert to ta geodetic point */ + x = lwpoint_get_x(r); + y = lwpoint_get_y(r); + has_z = lwgeom_has_z(lwpoint_as_lwgeom(r)); + has_m = lwgeom_has_m(lwpoint_as_lwgeom(r)); + geographic_point_init(x, y, &geo_source); + + /* Try the projection */ + if( spheroid_project(&geo_source, spheroid, distance, azimuth, &geo_dest) == LW_FAILURE ) + { + LWDEBUGF(3, "Unable to project from (%g %g) with azimuth %g and distance %g", x, y, azimuth, distance); + lwerror("Unable to project from (%g %g) with azimuth %g and distance %g", x, y, azimuth, distance); + return NULL; + } + + /* Build the output LWPOINT */ + pt_dest.x = rad2deg(longitude_radians_normalize(geo_dest.lon)); + pt_dest.y = rad2deg(latitude_radians_normalize(geo_dest.lat)); + pt_dest.z = has_z ? lwpoint_get_z(r) : 0.0; + pt_dest.m = has_m ? lwpoint_get_m(r) : 0.0; + lwp = lwpoint_make(r->srid, has_z, has_m, &pt_dest); + lwgeom_set_geodetic(lwpoint_as_lwgeom(lwp), LW_TRUE); + return lwp; +} + +LWPOINT* lwgeom_project_spheroid_lwpoint(const LWPOINT *from, const LWPOINT *to, const SPHEROID *spheroid, double distance) +{ + double azimuth = lwgeom_azumith_spheroid(from, to, spheroid); + LWPOINT *lwp = lwgeom_project_spheroid(to, spheroid, distance, azimuth); + return lwp; +} + + +/** +* Calculate a bearing (azimuth) given a source and destination point. +https://accesd.desjardins.ca/coast* @param r - location of first point. +* @param s - location of second point. +* @param spheroid - spheroid definition. +* @return azimuth - azimuth in radians. +* +*/ +double lwgeom_azumith_spheroid(const LWPOINT *r, const LWPOINT *s, const SPHEROID *spheroid) +{ + GEOGRAPHIC_POINT g1, g2; + double x1, y1, x2, y2, az; + + /* Convert r to a geodetic point */ + x1 = lwpoint_get_x(r); + y1 = lwpoint_get_y(r); + geographic_point_init(x1, y1, &g1); + + /* Convert s to a geodetic point */ + x2 = lwpoint_get_x(s); + y2 = lwpoint_get_y(s); + geographic_point_init(x2, y2, &g2); + + /* Same point, return NaN */ + if ( FP_EQUALS(x1, x2) && FP_EQUALS(y1, y2) ) + { + return NAN; + } + + /* Do the direction calculation */ + az = spheroid_direction(&g1, &g2, spheroid); + /* Ensure result is positive */ + return az < -0 ? 2*M_PI + az : az; + // return az; +} + +/** +* Calculate the distance between two LWGEOMs, using the coordinates are +* longitude and latitude. Return immediately when the calculated distance drops +* below the tolerance (useful for dwithin calculations). +* Return a negative distance for incalculable cases. +*/ +double lwgeom_distance_spheroid(const LWGEOM *lwgeom1, const LWGEOM *lwgeom2, const SPHEROID *spheroid, double tolerance) +{ + uint8_t type1, type2; + int check_intersection = LW_FALSE; + GBOX gbox1, gbox2; + + gbox_init(&gbox1); + gbox_init(&gbox2); + + assert(lwgeom1); + assert(lwgeom2); + + LWDEBUGF(4, "entered function, tolerance %.8g", tolerance); + + /* What's the distance to an empty geometry? We don't know. + Return a negative number so the caller can catch this case. */ + if ( lwgeom_is_empty(lwgeom1) || lwgeom_is_empty(lwgeom2) ) + { + return -1.0; + } + + type1 = lwgeom1->type; + type2 = lwgeom2->type; + + /* Make sure we have boxes */ + if ( lwgeom1->bbox ) + gbox1 = *(lwgeom1->bbox); + else + lwgeom_calculate_gbox_geodetic(lwgeom1, &gbox1); + + /* Make sure we have boxes */ + if ( lwgeom2->bbox ) + gbox2 = *(lwgeom2->bbox); + else + lwgeom_calculate_gbox_geodetic(lwgeom2, &gbox2); + + /* If the boxes aren't disjoint, we have to check for edge intersections */ + if ( gbox_overlaps(&gbox1, &gbox2) ) + check_intersection = LW_TRUE; + + /* Point/line combinations can all be handled with simple point array iterations */ + if ( ( type1 == POINTTYPE || type1 == LINETYPE ) && + ( type2 == POINTTYPE || type2 == LINETYPE ) ) + { + POINTARRAY *pa1, *pa2; + + if ( type1 == POINTTYPE ) + pa1 = ((LWPOINT*)lwgeom1)->point; + else + pa1 = ((LWLINE*)lwgeom1)->points; + + if ( type2 == POINTTYPE ) + pa2 = ((LWPOINT*)lwgeom2)->point; + else + pa2 = ((LWLINE*)lwgeom2)->points; + + return ptarray_distance_spheroid(pa1, pa2, spheroid, tolerance, check_intersection); + } + + /* Point/Polygon cases, if point-in-poly, return zero, else return distance. */ + if ( ( type1 == POLYGONTYPE && type2 == POINTTYPE ) || + ( type2 == POLYGONTYPE && type1 == POINTTYPE ) ) + { + const POINT2D *p; + LWPOLY *lwpoly; + LWPOINT *lwpt; + double distance = FLT_MAX; + uint32_t i; + + if ( type1 == POINTTYPE ) + { + lwpt = (LWPOINT*)lwgeom1; + lwpoly = (LWPOLY*)lwgeom2; + } + else + { + lwpt = (LWPOINT*)lwgeom2; + lwpoly = (LWPOLY*)lwgeom1; + } + p = getPoint2d_cp(lwpt->point, 0); + + /* Point in polygon implies zero distance */ + if ( lwpoly_covers_point2d(lwpoly, p) ) + { + return 0.0; + } + + /* Not inside, so what's the actual distance? */ + for ( i = 0; i < lwpoly->nrings; i++ ) + { + double ring_distance = ptarray_distance_spheroid(lwpoly->rings[i], lwpt->point, spheroid, tolerance, check_intersection); + if ( ring_distance < distance ) + distance = ring_distance; + if ( distance <= tolerance ) + return distance; + } + return distance; + } + + /* Line/polygon case, if start point-in-poly, return zero, else return distance. */ + if ( ( type1 == POLYGONTYPE && type2 == LINETYPE ) || + ( type2 == POLYGONTYPE && type1 == LINETYPE ) ) + { + const POINT2D *p; + LWPOLY *lwpoly; + LWLINE *lwline; + double distance = FLT_MAX; + uint32_t i; + + if ( type1 == LINETYPE ) + { + lwline = (LWLINE*)lwgeom1; + lwpoly = (LWPOLY*)lwgeom2; + } + else + { + lwline = (LWLINE*)lwgeom2; + lwpoly = (LWPOLY*)lwgeom1; + } + p = getPoint2d_cp(lwline->points, 0); + + LWDEBUG(4, "checking if a point of line is in polygon"); + + /* Point in polygon implies zero distance */ + if ( lwpoly_covers_point2d(lwpoly, p) ) + return 0.0; + + LWDEBUG(4, "checking ring distances"); + + /* Not contained, so what's the actual distance? */ + for ( i = 0; i < lwpoly->nrings; i++ ) + { + double ring_distance = ptarray_distance_spheroid(lwpoly->rings[i], lwline->points, spheroid, tolerance, check_intersection); + LWDEBUGF(4, "ring[%d] ring_distance = %.8g", i, ring_distance); + if ( ring_distance < distance ) + distance = ring_distance; + if ( distance <= tolerance ) + return distance; + } + LWDEBUGF(4, "all rings checked, returning distance = %.8g", distance); + return distance; + + } + + /* Polygon/polygon case, if start point-in-poly, return zero, else + * return distance. */ + if (type1 == POLYGONTYPE && type2 == POLYGONTYPE) + { + const POINT2D* p; + LWPOLY* lwpoly1 = (LWPOLY*)lwgeom1; + LWPOLY* lwpoly2 = (LWPOLY*)lwgeom2; + double distance = FLT_MAX; + uint32_t i, j; + + /* Point of 2 in polygon 1 implies zero distance */ + p = getPoint2d_cp(lwpoly1->rings[0], 0); + if (lwpoly_covers_point2d(lwpoly2, p)) return 0.0; + + /* Point of 1 in polygon 2 implies zero distance */ + p = getPoint2d_cp(lwpoly2->rings[0], 0); + if (lwpoly_covers_point2d(lwpoly1, p)) return 0.0; + + /* Not contained, so what's the actual distance? */ + for (i = 0; i < lwpoly1->nrings; i++) + { + for (j = 0; j < lwpoly2->nrings; j++) + { + double ring_distance = + ptarray_distance_spheroid( + lwpoly1->rings[i], + lwpoly2->rings[j], + spheroid, + tolerance, + check_intersection); + if (ring_distance < distance) + distance = ring_distance; + if (distance <= tolerance) return distance; + } + } + return distance; + } + + /* Recurse into collections */ + if ( lwtype_is_collection(type1) ) + { + uint32_t i; + double distance = FLT_MAX; + LWCOLLECTION *col = (LWCOLLECTION*)lwgeom1; + + for ( i = 0; i < col->ngeoms; i++ ) + { + double geom_distance = lwgeom_distance_spheroid( + col->geoms[i], lwgeom2, spheroid, tolerance); + if ( geom_distance < distance ) + distance = geom_distance; + if ( distance <= tolerance ) + return distance; + } + return distance; + } + + /* Recurse into collections */ + if ( lwtype_is_collection(type2) ) + { + uint32_t i; + double distance = FLT_MAX; + LWCOLLECTION *col = (LWCOLLECTION*)lwgeom2; + + for ( i = 0; i < col->ngeoms; i++ ) + { + double geom_distance = lwgeom_distance_spheroid(lwgeom1, col->geoms[i], spheroid, tolerance); + if ( geom_distance < distance ) + distance = geom_distance; + if ( distance <= tolerance ) + return distance; + } + return distance; + } + + + lwerror("arguments include unsupported geometry type (%s, %s)", lwtype_name(type1), lwtype_name(type1)); + return -1.0; + +} + + +int lwgeom_covers_lwgeom_sphere(const LWGEOM *lwgeom1, const LWGEOM *lwgeom2) +{ + int type1, type2; + GBOX gbox1, gbox2; + gbox1.flags = gbox2.flags = 0; + + assert(lwgeom1); + assert(lwgeom2); + + type1 = lwgeom1->type; + type2 = lwgeom2->type; + + /* dim(geom2) > dim(geom1) always returns false (because geom2 is bigger) */ + if ( (type1 == POINTTYPE && type2 == LINETYPE) + || (type1 == POINTTYPE && type2 == POLYGONTYPE) + || (type1 == LINETYPE && type2 == POLYGONTYPE) ) + { + LWDEBUG(4, "dimension of geom2 is bigger than geom1"); + return LW_FALSE; + } + + /* Make sure we have boxes */ + if ( lwgeom1->bbox ) + gbox1 = *(lwgeom1->bbox); + else + lwgeom_calculate_gbox_geodetic(lwgeom1, &gbox1); + + /* Make sure we have boxes */ + if ( lwgeom2->bbox ) + gbox2 = *(lwgeom2->bbox); + else + lwgeom_calculate_gbox_geodetic(lwgeom2, &gbox2); + + + /* Handle the polygon/point case */ + if ( type1 == POLYGONTYPE && type2 == POINTTYPE ) + { + POINT2D pt_to_test; + getPoint2d_p(((LWPOINT*)lwgeom2)->point, 0, &pt_to_test); + return lwpoly_covers_point2d((LWPOLY*)lwgeom1, &pt_to_test); + } + else if ( type1 == POLYGONTYPE && type2 == LINETYPE) + { + return lwpoly_covers_lwline((LWPOLY*)lwgeom1, (LWLINE*)lwgeom2); + } + else if ( type1 == POLYGONTYPE && type2 == POLYGONTYPE) + { + return lwpoly_covers_lwpoly((LWPOLY*)lwgeom1, (LWPOLY*)lwgeom2); + } + else if ( type1 == LINETYPE && type2 == POINTTYPE) + { + return lwline_covers_lwpoint((LWLINE*)lwgeom1, (LWPOINT*)lwgeom2); + } + else if ( type1 == LINETYPE && type2 == LINETYPE) + { + return lwline_covers_lwline((LWLINE*)lwgeom1, (LWLINE*)lwgeom2); + } + else if ( type1 == POINTTYPE && type2 == POINTTYPE) + { + return lwpoint_same((LWPOINT*)lwgeom1, (LWPOINT*)lwgeom2); + } + + /* If any of the first argument parts covers the second argument, it's true */ + if ( lwtype_is_collection( type1 ) ) + { + uint32_t i; + LWCOLLECTION *col = (LWCOLLECTION*)lwgeom1; + + for ( i = 0; i < col->ngeoms; i++ ) + { + if ( lwgeom_covers_lwgeom_sphere(col->geoms[i], lwgeom2) ) + { + return LW_TRUE; + } + } + return LW_FALSE; + } + + /* Only if all of the second arguments are covered by the first argument is the condition true */ + if ( lwtype_is_collection( type2 ) ) + { + uint32_t i; + LWCOLLECTION *col = (LWCOLLECTION*)lwgeom2; + + for ( i = 0; i < col->ngeoms; i++ ) + { + if ( ! lwgeom_covers_lwgeom_sphere(lwgeom1, col->geoms[i]) ) + { + return LW_FALSE; + } + } + return LW_TRUE; + } + + /* Don't get here */ + lwerror("lwgeom_covers_lwgeom_sphere: reached end of function without resolution"); + return LW_FALSE; + +} + +/** +* Given a polygon (lon/lat decimal degrees) and point (lon/lat decimal degrees) and +* a guaranteed outside point (lon/lat decimal degrees) (calculate with gbox_pt_outside()) +* return LW_TRUE if point is inside or on edge of polygon. +*/ +int lwpoly_covers_point2d(const LWPOLY *poly, const POINT2D *pt_to_test) +{ + uint32_t i; + int in_hole_count = 0; + POINT3D p; + GEOGRAPHIC_POINT gpt_to_test; + POINT2D pt_outside; + GBOX gbox; +#if POSTGIS_DEBUG_LEVEL >= 4 + char *geom_ewkt; +#endif + gbox.flags = 0; + + /* Nulls and empties don't contain anything! */ + if ( ! poly || lwgeom_is_empty((LWGEOM*)poly) ) + { + LWDEBUG(4,"returning false, geometry is empty or null"); + return LW_FALSE; + } + + /* Make sure we have boxes */ + if ( poly->bbox ) + gbox = *(poly->bbox); + else + lwgeom_calculate_gbox_geodetic((LWGEOM*)poly, &gbox); + + /* Point not in box? Done! */ + geographic_point_init(pt_to_test->x, pt_to_test->y, &gpt_to_test); + geog2cart(&gpt_to_test, &p); + if ( ! gbox_contains_point3d(&gbox, &p) ) + { + LWDEBUG(4, "the point is not in the box!"); + return LW_FALSE; + } + + /* Calculate our outside point from the gbox */ + lwpoly_pt_outside(poly, &pt_outside); + + LWDEBUGF(4, "pt_outside POINT(%.18g %.18g)", pt_outside.x, pt_outside.y); + LWDEBUGF(4, "pt_to_test POINT(%.18g %.18g)", pt_to_test->x, pt_to_test->y); +#if POSTGIS_DEBUG_LEVEL >= 4 + geom_ewkt = lwgeom_to_ewkt((LWGEOM*)poly); + LWDEBUGF(4, "polygon %s", geom_ewkt); + lwfree(geom_ewkt); + geom_ewkt = gbox_to_string(&gbox); + LWDEBUGF(4, "gbox %s", geom_ewkt); + lwfree(geom_ewkt); +#endif + + /* Not in outer ring? We're done! */ + if ( ! ptarray_contains_point_sphere(poly->rings[0], &pt_outside, pt_to_test) ) + { + LWDEBUG(4,"returning false, point is outside ring"); + return LW_FALSE; + } + + LWDEBUGF(4, "testing %d rings", poly->nrings); + + /* But maybe point is in a hole... */ + for ( i = 1; i < poly->nrings; i++ ) + { + LWDEBUGF(4, "ring test loop %d", i); + /* Count up hole containment. Odd => outside boundary. */ + if ( ptarray_contains_point_sphere(poly->rings[i], &pt_outside, pt_to_test) ) + in_hole_count++; + } + + LWDEBUGF(4, "in_hole_count == %d", in_hole_count); + + if ( in_hole_count % 2 ) + { + LWDEBUG(4,"returning false, inner ring containment count is odd"); + return LW_FALSE; + } + + LWDEBUG(4,"returning true, inner ring containment count is even"); + return LW_TRUE; +} + +/** + * Given a polygon1 check if all points of polygon2 are inside polygon1 and no + * intersections of the polygon edges occur. + * return LW_TRUE if polygon is inside or on edge of polygon. + */ +int lwpoly_covers_lwpoly(const LWPOLY *poly1, const LWPOLY *poly2) +{ + uint32_t i; + + /* Nulls and empties don't contain anything! */ + if ( ! poly1 || lwgeom_is_empty((LWGEOM*)poly1) ) + { + LWDEBUG(4,"returning false, geometry1 is empty or null"); + return LW_FALSE; + } + + /* Nulls and empties don't contain anything! */ + if ( ! poly2 || lwgeom_is_empty((LWGEOM*)poly2) ) + { + LWDEBUG(4,"returning false, geometry2 is empty or null"); + return LW_FALSE; + } + + /* check if all vertices of poly2 are inside poly1 */ + for (i = 0; i < poly2->nrings; i++) + { + if (LW_FALSE == lwpoly_covers_pointarray(poly1, poly2->rings[i])) + { + LWDEBUG(4,"returning false, geometry2 has point outside of geometry1"); + return LW_FALSE; + } + } + + /* check for any edge intersections, so nothing is partially outside of poly1 */ + for (i = 0; i < poly2->nrings; i++) + { + if (LW_TRUE == lwpoly_intersects_line(poly1, poly2->rings[i])) + { + LWDEBUG(4,"returning false, geometry2 is partially outside of geometry1"); + return LW_FALSE; + } + } + + /* no abort condition found, so the poly2 should be completly inside poly1 */ + return LW_TRUE; +} + +/** + * + */ +int lwpoly_covers_lwline(const LWPOLY *poly, const LWLINE *line) +{ + /* Nulls and empties don't contain anything! */ + if ( ! poly || lwgeom_is_empty((LWGEOM*)poly) ) + { + LWDEBUG(4,"returning false, geometry1 is empty or null"); + return LW_FALSE; + } + + /* Nulls and empties don't contain anything! */ + if ( ! line || lwgeom_is_empty((LWGEOM*)line) ) + { + LWDEBUG(4,"returning false, geometry2 is empty or null"); + return LW_FALSE; + } + + if (LW_FALSE == lwpoly_covers_pointarray(poly, line->points)) + { + LWDEBUG(4,"returning false, geometry2 has point outside of geometry1"); + return LW_FALSE; + } + + /* check for any edge intersections, so nothing is partially outside of poly1 */ + if (LW_TRUE == lwpoly_intersects_line(poly, line->points)) + { + LWDEBUG(4,"returning false, geometry2 is partially outside of geometry1"); + return LW_FALSE; + } + + /* no abort condition found, so the poly2 should be completely inside poly1 */ + return LW_TRUE; +} + +/** + * return LW_TRUE if all points are inside the polygon + */ +int lwpoly_covers_pointarray(const LWPOLY* lwpoly, const POINTARRAY* pta) +{ + uint32_t i; + for (i = 0; i < pta->npoints; i++) { + const POINT2D* pt_to_test = getPoint2d_cp(pta, i); + + if ( LW_FALSE == lwpoly_covers_point2d(lwpoly, pt_to_test) ) { + LWDEBUG(4,"returning false, geometry2 has point outside of geometry1"); + return LW_FALSE; + } + } + + return LW_TRUE; +} + +/** + * Checks if any edges of lwpoly intersect with the line formed by the pointarray + * return LW_TRUE if any intersection between the given polygon and the line + */ +int lwpoly_intersects_line(const LWPOLY* lwpoly, const POINTARRAY* line) +{ + uint32_t i, j, k; + POINT3D pa1, pa2, pb1, pb2; + for (i = 0; i < lwpoly->nrings; i++) + { + for (j = 0; j < lwpoly->rings[i]->npoints - 1; j++) + { + const POINT2D* a1 = getPoint2d_cp(lwpoly->rings[i], j); + const POINT2D* a2 = getPoint2d_cp(lwpoly->rings[i], j+1); + + /* Set up our stab line */ + ll2cart(a1, &pa1); + ll2cart(a2, &pa2); + + for (k = 0; k < line->npoints - 1; k++) + { + const POINT2D* b1 = getPoint2d_cp(line, k); + const POINT2D* b2 = getPoint2d_cp(line, k+1); + + /* Set up our stab line */ + ll2cart(b1, &pb1); + ll2cart(b2, &pb2); + + int inter = edge_intersects(&pa1, &pa2, &pb1, &pb2); + + /* ignore same edges */ + if (inter & PIR_INTERSECTS + && !(inter & PIR_B_TOUCH_RIGHT || inter & PIR_COLINEAR) ) + { + return LW_TRUE; + } + } + } + } + + return LW_FALSE; +} + +/** + * return LW_TRUE if any of the line segments covers the point + */ +int lwline_covers_lwpoint(const LWLINE* lwline, const LWPOINT* lwpoint) +{ + uint32_t i; + GEOGRAPHIC_POINT p; + GEOGRAPHIC_EDGE e; + + for ( i = 0; i < lwline->points->npoints - 1; i++) + { + const POINT2D* a1 = getPoint2d_cp(lwline->points, i); + const POINT2D* a2 = getPoint2d_cp(lwline->points, i+1); + + geographic_point_init(a1->x, a1->y, &(e.start)); + geographic_point_init(a2->x, a2->y, &(e.end)); + + geographic_point_init(lwpoint_get_x(lwpoint), lwpoint_get_y(lwpoint), &p); + + if ( edge_contains_point(&e, &p) ) { + return LW_TRUE; + } + } + + return LW_FALSE; +} + +/** + * Check if first and last point of line2 are covered by line1 and then each + * point in between has to be one line1 in the exact same order + * return LW_TRUE if all edge points of line2 are on line1 + */ +int lwline_covers_lwline(const LWLINE* lwline1, const LWLINE* lwline2) +{ + uint32_t i, j; + GEOGRAPHIC_EDGE e1, e2; + GEOGRAPHIC_POINT p1, p2; + int start = LW_FALSE; + int changed = LW_FALSE; + + /* first point on line */ + if ( ! lwline_covers_lwpoint(lwline1, lwline_get_lwpoint(lwline2, 0))) + { + LWDEBUG(4,"returning false, first point of line2 is not covered by line1"); + return LW_FALSE; + } + + /* last point on line */ + if ( ! lwline_covers_lwpoint(lwline1, lwline_get_lwpoint(lwline2, lwline2->points->npoints - 1))) + { + LWDEBUG(4,"returning false, last point of line2 is not covered by line1"); + return LW_FALSE; + } + + j = 0; + i = 0; + while (i < lwline1->points->npoints - 1 && j < lwline2->points->npoints - 1) + { + changed = LW_FALSE; + const POINT2D* a1 = getPoint2d_cp(lwline1->points, i); + const POINT2D* a2 = getPoint2d_cp(lwline1->points, i+1); + const POINT2D* b1 = getPoint2d_cp(lwline2->points, j); + const POINT2D* b2 = getPoint2d_cp(lwline2->points, j+1); + + geographic_point_init(a1->x, a1->y, &(e1.start)); + geographic_point_init(a2->x, a2->y, &(e1.end)); + geographic_point_init(b1->x, b1->y, &p2); + + /* we already know, that the last point is on line1, so we're done */ + if ( j == lwline2->points->npoints - 1) + { + return LW_TRUE; + } + else if (start == LW_TRUE) + { + /* point is on current line1 edge, check next point in line2 */ + if ( edge_contains_point(&e1, &p2)) { + j++; + changed = LW_TRUE; + } + + geographic_point_init(a1->x, a1->y, &(e2.start)); + geographic_point_init(a2->x, b2->y, &(e2.end)); + geographic_point_init(a1->x, a1->y, &p1); + + /* point is on current line2 edge, check next point in line1 */ + if ( edge_contains_point(&e2, &p1)) { + i++; + changed = LW_TRUE; + } + + /* no edge progressed -> point left one line */ + if ( changed == LW_FALSE ) + { + LWDEBUG(4,"returning false, found point not covered by both lines"); + return LW_FALSE; + } + else + { + continue; + } + } + + /* find first edge to cover line2 */ + if (edge_contains_point(&e1, &p2)) + { + start = LW_TRUE; + } + + /* next line1 edge */ + i++; + } + + /* no uncovered point found */ + return LW_TRUE; +} + +int ptarray_calculate_gbox_geodetic(const POINTARRAY *pa, GBOX *gbox) +{ + uint32_t i; + int first = LW_TRUE; + const POINT2D *p; + POINT3D A1, A2; + GBOX edge_gbox; + + assert(gbox); + assert(pa); + + gbox_init(&edge_gbox); + edge_gbox.flags = gbox->flags; + + if ( pa->npoints == 0 ) return LW_FAILURE; + + if ( pa->npoints == 1 ) + { + p = getPoint2d_cp(pa, 0); + ll2cart(p, &A1); + gbox->xmin = gbox->xmax = A1.x; + gbox->ymin = gbox->ymax = A1.y; + gbox->zmin = gbox->zmax = A1.z; + return LW_SUCCESS; + } + + p = getPoint2d_cp(pa, 0); + ll2cart(p, &A1); + + for ( i = 1; i < pa->npoints; i++ ) + { + + p = getPoint2d_cp(pa, i); + ll2cart(p, &A2); + + edge_calculate_gbox(&A1, &A2, &edge_gbox); + + /* Initialize the box */ + if ( first ) + { + gbox_duplicate(&edge_gbox, gbox); + first = LW_FALSE; + } + /* Expand the box where necessary */ + else + { + gbox_merge(&edge_gbox, gbox); + } + + A1 = A2; + } + + return LW_SUCCESS; +} + +static int lwpoint_calculate_gbox_geodetic(const LWPOINT *point, GBOX *gbox) +{ + assert(point); + return ptarray_calculate_gbox_geodetic(point->point, gbox); +} + +static int lwline_calculate_gbox_geodetic(const LWLINE *line, GBOX *gbox) +{ + assert(line); + return ptarray_calculate_gbox_geodetic(line->points, gbox); +} + +static int lwpolygon_calculate_gbox_geodetic(const LWPOLY *poly, GBOX *gbox) +{ + GBOX ringbox; + uint32_t i; + int first = LW_TRUE; + assert(poly); + if ( poly->nrings == 0 ) + return LW_FAILURE; + ringbox.flags = gbox->flags; + for ( i = 0; i < poly->nrings; i++ ) + { + if ( ptarray_calculate_gbox_geodetic(poly->rings[i], &ringbox) == LW_FAILURE ) + return LW_FAILURE; + if ( first ) + { + gbox_duplicate(&ringbox, gbox); + first = LW_FALSE; + } + else + { + gbox_merge(&ringbox, gbox); + } + } + + /* If the box wraps a poly, push that axis to the absolute min/max as appropriate */ + gbox_check_poles(gbox); + + return LW_SUCCESS; +} + +static int lwtriangle_calculate_gbox_geodetic(const LWTRIANGLE *triangle, GBOX *gbox) +{ + assert(triangle); + return ptarray_calculate_gbox_geodetic(triangle->points, gbox); +} + + +static int lwcollection_calculate_gbox_geodetic(const LWCOLLECTION *coll, GBOX *gbox) +{ + GBOX subbox = {0}; + uint32_t i; + int result = LW_FAILURE; + int first = LW_TRUE; + assert(coll); + if ( coll->ngeoms == 0 ) + return LW_FAILURE; + + subbox.flags = gbox->flags; + + for ( i = 0; i < coll->ngeoms; i++ ) + { + if ( lwgeom_calculate_gbox_geodetic((LWGEOM*)(coll->geoms[i]), &subbox) == LW_SUCCESS ) + { + /* Keep a copy of the sub-bounding box for later */ + if ( coll->geoms[i]->bbox ) + lwfree(coll->geoms[i]->bbox); + coll->geoms[i]->bbox = gbox_copy(&subbox); + if ( first ) + { + gbox_duplicate(&subbox, gbox); + first = LW_FALSE; + } + else + { + gbox_merge(&subbox, gbox); + } + result = LW_SUCCESS; + } + } + return result; +} + +int lwgeom_calculate_gbox_geodetic(const LWGEOM *geom, GBOX *gbox) +{ + int result = LW_FAILURE; + LWDEBUGF(4, "got type %d", geom->type); + + /* Add a geodetic flag to the incoming gbox */ + gbox->flags = lwflags(FLAGS_GET_Z(geom->flags),FLAGS_GET_M(geom->flags),1); + + switch (geom->type) + { + case POINTTYPE: + result = lwpoint_calculate_gbox_geodetic((LWPOINT*)geom, gbox); + break; + case LINETYPE: + result = lwline_calculate_gbox_geodetic((LWLINE *)geom, gbox); + break; + case POLYGONTYPE: + result = lwpolygon_calculate_gbox_geodetic((LWPOLY *)geom, gbox); + break; + case TRIANGLETYPE: + result = lwtriangle_calculate_gbox_geodetic((LWTRIANGLE *)geom, gbox); + break; + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + result = lwcollection_calculate_gbox_geodetic((LWCOLLECTION *)geom, gbox); + break; + default: + lwerror("lwgeom_calculate_gbox_geodetic: unsupported input geometry type: %d - %s", + geom->type, lwtype_name(geom->type)); + break; + } + return result; +} + + + +static int ptarray_check_geodetic(const POINTARRAY *pa) +{ + uint32_t t; + POINT2D pt; + + assert(pa); + + for (t=0; tnpoints; t++) + { + getPoint2d_p(pa, t, &pt); + /* printf( "%d (%g, %g)\n", t, pt.x, pt.y); */ + if ( pt.x < -180.0 || pt.y < -90.0 || pt.x > 180.0 || pt.y > 90.0 ) + return LW_FALSE; + } + + return LW_TRUE; +} + +static int lwpoint_check_geodetic(const LWPOINT *point) +{ + assert(point); + return ptarray_check_geodetic(point->point); +} + +static int lwline_check_geodetic(const LWLINE *line) +{ + assert(line); + return ptarray_check_geodetic(line->points); +} + +static int lwpoly_check_geodetic(const LWPOLY *poly) +{ + uint32_t i = 0; + assert(poly); + + for ( i = 0; i < poly->nrings; i++ ) + { + if ( ptarray_check_geodetic(poly->rings[i]) == LW_FALSE ) + return LW_FALSE; + } + return LW_TRUE; +} + +static int lwtriangle_check_geodetic(const LWTRIANGLE *triangle) +{ + assert(triangle); + return ptarray_check_geodetic(triangle->points); +} + + +static int lwcollection_check_geodetic(const LWCOLLECTION *col) +{ + uint32_t i = 0; + assert(col); + + for ( i = 0; i < col->ngeoms; i++ ) + { + if ( lwgeom_check_geodetic(col->geoms[i]) == LW_FALSE ) + return LW_FALSE; + } + return LW_TRUE; +} + +int lwgeom_check_geodetic(const LWGEOM *geom) +{ + if ( lwgeom_is_empty(geom) ) + return LW_TRUE; + + switch (geom->type) + { + case POINTTYPE: + return lwpoint_check_geodetic((LWPOINT *)geom); + case LINETYPE: + return lwline_check_geodetic((LWLINE *)geom); + case POLYGONTYPE: + return lwpoly_check_geodetic((LWPOLY *)geom); + case TRIANGLETYPE: + return lwtriangle_check_geodetic((LWTRIANGLE *)geom); + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + return lwcollection_check_geodetic((LWCOLLECTION *)geom); + default: + lwerror("lwgeom_check_geodetic: unsupported input geometry type: %d - %s", + geom->type, lwtype_name(geom->type)); + } + return LW_FALSE; +} + +static int ptarray_force_geodetic(POINTARRAY *pa) +{ + uint32_t t; + int changed = LW_FALSE; + POINT4D pt; + + assert(pa); + + for ( t=0; t < pa->npoints; t++ ) + { + getPoint4d_p(pa, t, &pt); + if ( pt.x < -180.0 || pt.x > 180.0 || pt.y < -90.0 || pt.y > 90.0 ) + { + pt.x = longitude_degrees_normalize(pt.x); + pt.y = latitude_degrees_normalize(pt.y); + ptarray_set_point4d(pa, t, &pt); + changed = LW_TRUE; + } + } + return changed; +} + +static int lwpoint_force_geodetic(LWPOINT *point) +{ + assert(point); + return ptarray_force_geodetic(point->point); +} + +static int lwline_force_geodetic(LWLINE *line) +{ + assert(line); + return ptarray_force_geodetic(line->points); +} + +static int lwpoly_force_geodetic(LWPOLY *poly) +{ + uint32_t i = 0; + int changed = LW_FALSE; + assert(poly); + + for ( i = 0; i < poly->nrings; i++ ) + { + if ( ptarray_force_geodetic(poly->rings[i]) == LW_TRUE ) + changed = LW_TRUE; + } + return changed; +} + +static int lwcollection_force_geodetic(LWCOLLECTION *col) +{ + uint32_t i = 0; + int changed = LW_FALSE; + assert(col); + + for ( i = 0; i < col->ngeoms; i++ ) + { + if ( lwgeom_force_geodetic(col->geoms[i]) == LW_TRUE ) + changed = LW_TRUE; + } + return changed; +} + +int lwgeom_force_geodetic(LWGEOM *geom) +{ + switch ( lwgeom_get_type(geom) ) + { + case POINTTYPE: + return lwpoint_force_geodetic((LWPOINT *)geom); + case LINETYPE: + return lwline_force_geodetic((LWLINE *)geom); + case POLYGONTYPE: + return lwpoly_force_geodetic((LWPOLY *)geom); + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + return lwcollection_force_geodetic((LWCOLLECTION *)geom); + default: + lwerror("unsupported input geometry type: %d", lwgeom_get_type(geom)); + } + return LW_FALSE; +} + + +double ptarray_length_spheroid(const POINTARRAY *pa, const SPHEROID *s) +{ + GEOGRAPHIC_POINT a, b; + double za = 0.0, zb = 0.0; + POINT4D p; + uint32_t i; + int hasz = LW_FALSE; + double length = 0.0; + double seglength = 0.0; + + /* Return zero on non-sensical inputs */ + if ( ! pa || pa->npoints < 2 ) + return 0.0; + + /* See if we have a third dimension */ + hasz = FLAGS_GET_Z(pa->flags); + + /* Initialize first point */ + getPoint4d_p(pa, 0, &p); + geographic_point_init(p.x, p.y, &a); + if ( hasz ) + za = p.z; + + /* Loop and sum the length for each segment */ + for ( i = 1; i < pa->npoints; i++ ) + { + seglength = 0.0; + getPoint4d_p(pa, i, &p); + geographic_point_init(p.x, p.y, &b); + if ( hasz ) + zb = p.z; + + /* Special sphere case */ + if ( s->a == s->b ) + seglength = s->radius * sphere_distance(&a, &b); + /* Spheroid case */ + else + seglength = spheroid_distance(&a, &b, s); + + /* Add in the vertical displacement if we're in 3D */ + if ( hasz ) + seglength = sqrt( (zb-za)*(zb-za) + seglength*seglength ); + + /* Add this segment length to the total */ + length += seglength; + + /* B gets incremented in the next loop, so we save the value here */ + a = b; + za = zb; + } + return length; +} + +double lwgeom_length_spheroid(const LWGEOM *geom, const SPHEROID *s) +{ + int type; + uint32_t i = 0; + double length = 0.0; + + assert(geom); + + /* No area in nothing */ + if ( lwgeom_is_empty(geom) ) + return 0.0; + + type = geom->type; + + if ( type == POINTTYPE || type == MULTIPOINTTYPE ) + return 0.0; + + if ( type == LINETYPE ) + return ptarray_length_spheroid(((LWLINE*)geom)->points, s); + + if ( type == POLYGONTYPE ) + { + LWPOLY *poly = (LWPOLY*)geom; + for ( i = 0; i < poly->nrings; i++ ) + { + length += ptarray_length_spheroid(poly->rings[i], s); + } + return length; + } + + if ( type == TRIANGLETYPE ) + return ptarray_length_spheroid(((LWTRIANGLE*)geom)->points, s); + + if ( lwtype_is_collection( type ) ) + { + LWCOLLECTION *col = (LWCOLLECTION*)geom; + + for ( i = 0; i < col->ngeoms; i++ ) + { + length += lwgeom_length_spheroid(col->geoms[i], s); + } + return length; + } + + lwerror("unsupported type passed to lwgeom_length_sphere"); + return 0.0; +} + +/** +* When features are snapped or sometimes they are just this way, they are very close to +* the geodetic bounds but slightly over. This routine nudges those points, and only +* those points, back over to the bounds. +* http://trac.osgeo.org/postgis/ticket/1292 +*/ +static int +ptarray_nudge_geodetic(POINTARRAY *pa) +{ + + uint32_t i; + POINT4D p; + int altered = LW_FALSE; + int rv = LW_FALSE; + static double tolerance = 1e-10; + + if ( ! pa ) + lwerror("ptarray_nudge_geodetic called with null input"); + + for(i = 0; i < pa->npoints; i++ ) + { + getPoint4d_p(pa, i, &p); + if ( p.x < -180.0 && (-180.0 - p.x <= tolerance) ) + { + p.x = -180.0; + altered = LW_TRUE; + } + if ( p.x > 180.0 && (p.x - 180.0 <= tolerance) ) + { + p.x = 180.0; + altered = LW_TRUE; + } + if ( p.y < -90.0 && (-90.0 - p.y <= tolerance) ) + { + p.y = -90.0; + altered = LW_TRUE; + } + if ( p.y > 90.0 && (p.y - 90.0 <= tolerance) ) + { + p.y = 90.0; + altered = LW_TRUE; + } + if ( altered == LW_TRUE ) + { + ptarray_set_point4d(pa, i, &p); + altered = LW_FALSE; + rv = LW_TRUE; + } + } + return rv; +} + +/** +* When features are snapped or sometimes they are just this way, they are very close to +* the geodetic bounds but slightly over. This routine nudges those points, and only +* those points, back over to the bounds. +* http://trac.osgeo.org/postgis/ticket/1292 +*/ +int +lwgeom_nudge_geodetic(LWGEOM *geom) +{ + int type; + uint32_t i = 0; + int rv = LW_FALSE; + + assert(geom); + + /* No points in nothing */ + if ( lwgeom_is_empty(geom) ) + return LW_FALSE; + + type = geom->type; + + if ( type == POINTTYPE ) + return ptarray_nudge_geodetic(((LWPOINT*)geom)->point); + + if ( type == LINETYPE ) + return ptarray_nudge_geodetic(((LWLINE*)geom)->points); + + if ( type == POLYGONTYPE ) + { + LWPOLY *poly = (LWPOLY*)geom; + for ( i = 0; i < poly->nrings; i++ ) + { + int n = ptarray_nudge_geodetic(poly->rings[i]); + rv = (rv == LW_TRUE ? rv : n); + } + return rv; + } + + if ( type == TRIANGLETYPE ) + return ptarray_nudge_geodetic(((LWTRIANGLE*)geom)->points); + + if ( lwtype_is_collection( type ) ) + { + LWCOLLECTION *col = (LWCOLLECTION*)geom; + + for ( i = 0; i < col->ngeoms; i++ ) + { + int n = lwgeom_nudge_geodetic(col->geoms[i]); + rv = (rv == LW_TRUE ? rv : n); + } + return rv; + } + + lwerror("unsupported type (%s) passed to lwgeom_nudge_geodetic", lwtype_name(type)); + return rv; +} + + +/** +* Utility function for checking if P is within the cone defined by A1/A2. +*/ +static int +point_in_cone(const POINT3D *A1, const POINT3D *A2, const POINT3D *P) +{ + POINT3D AC; /* Center point of A1/A2 */ + double min_similarity, similarity; + + /* Boundary case */ + if (point3d_equals(A1, P) || point3d_equals(A2, P)) + return LW_TRUE; + + /* The normalized sum bisects the angle between start and end. */ + vector_sum(A1, A2, &AC); + normalize(&AC); + + /* The projection of start onto the center defines the minimum similarity */ + min_similarity = dot_product(A1, &AC); + + /* If the edge is sufficiently curved, use the dot product test */ + if (fabs(1.0 - min_similarity) > 1e-10) + { + /* The projection of candidate p onto the center */ + similarity = dot_product(P, &AC); + + /* If the projection of the candidate is larger than */ + /* the projection of the start point, the candidate */ + /* must be closer to the center than the start, so */ + /* therefor inside the cone */ + if (similarity > min_similarity) + { + return LW_TRUE; + } + else + { + return LW_FALSE; + } + } + else + { + /* Where the edge is very narrow, the dot product test */ + /* fails, but we can use the almost-planar nature of the */ + /* problem space then to test if the vector from the */ + /* candidate to the start point in a different direction */ + /* to the vector from candidate to end point */ + /* If so, then candidate is between start and end */ + POINT3D PA1, PA2; + vector_difference(P, A1, &PA1); + vector_difference(P, A2, &PA2); + normalize(&PA1); + normalize(&PA2); + if (dot_product(&PA1, &PA2) < 0.0) + { + return LW_TRUE; + } + else + { + return LW_FALSE; + } + } + return LW_FALSE; +} + + + +/** +* Utility function for edge_intersects(), signum with a tolerance +* in determining if the value is zero. +*/ +static int +dot_product_side(const POINT3D *p, const POINT3D *q) +{ + double dp = dot_product(p, q); + + if ( FP_IS_ZERO(dp) ) + return 0; + + return dp < 0.0 ? -1 : 1; +} + +/** +* Returns non-zero if edges A and B interact. The type of interaction is given in the +* return value with the bitmask elements defined above. +*/ +uint32_t +edge_intersects(const POINT3D *A1, const POINT3D *A2, const POINT3D *B1, const POINT3D *B2) +{ + POINT3D AN, BN, VN; /* Normals to plane A and plane B */ + double ab_dot; + int a1_side, a2_side, b1_side, b2_side; + int rv = PIR_NO_INTERACT; + + /* Normals to the A-plane and B-plane */ + unit_normal(A1, A2, &AN); + unit_normal(B1, B2, &BN); + + /* Are A-plane and B-plane basically the same? */ + ab_dot = dot_product(&AN, &BN); + + if ( FP_EQUALS(fabs(ab_dot), 1.0) ) + { + /* Co-linear case */ + if ( point_in_cone(A1, A2, B1) || point_in_cone(A1, A2, B2) || + point_in_cone(B1, B2, A1) || point_in_cone(B1, B2, A2) ) + { + rv |= PIR_INTERSECTS; + rv |= PIR_COLINEAR; + } + return rv; + } + + /* What side of plane-A and plane-B do the end points */ + /* of A and B fall? */ + a1_side = dot_product_side(&BN, A1); + a2_side = dot_product_side(&BN, A2); + b1_side = dot_product_side(&AN, B1); + b2_side = dot_product_side(&AN, B2); + + /* Both ends of A on the same side of plane B. */ + if ( a1_side == a2_side && a1_side != 0 ) + { + /* No intersection. */ + return PIR_NO_INTERACT; + } + + /* Both ends of B on the same side of plane A. */ + if ( b1_side == b2_side && b1_side != 0 ) + { + /* No intersection. */ + return PIR_NO_INTERACT; + } + + /* A straddles B and B straddles A, so... */ + if ( a1_side != a2_side && (a1_side + a2_side) == 0 && + b1_side != b2_side && (b1_side + b2_side) == 0 ) + { + /* Have to check if intersection point is inside both arcs */ + unit_normal(&AN, &BN, &VN); + if ( point_in_cone(A1, A2, &VN) && point_in_cone(B1, B2, &VN) ) + { + return PIR_INTERSECTS; + } + + /* Have to check if intersection point is inside both arcs */ + vector_scale(&VN, -1); + if ( point_in_cone(A1, A2, &VN) && point_in_cone(B1, B2, &VN) ) + { + return PIR_INTERSECTS; + } + + return PIR_NO_INTERACT; + } + + /* The rest are all intersects variants... */ + rv |= PIR_INTERSECTS; + + /* A touches B */ + if ( a1_side == 0 ) + { + /* Touches at A1, A2 is on what side? */ + rv |= (a2_side < 0 ? PIR_A_TOUCH_RIGHT : PIR_A_TOUCH_LEFT); + } + else if ( a2_side == 0 ) + { + /* Touches at A2, A1 is on what side? */ + rv |= (a1_side < 0 ? PIR_A_TOUCH_RIGHT : PIR_A_TOUCH_LEFT); + } + + /* B touches A */ + if ( b1_side == 0 ) + { + /* Touches at B1, B2 is on what side? */ + rv |= (b2_side < 0 ? PIR_B_TOUCH_RIGHT : PIR_B_TOUCH_LEFT); + } + else if ( b2_side == 0 ) + { + /* Touches at B2, B1 is on what side? */ + rv |= (b1_side < 0 ? PIR_B_TOUCH_RIGHT : PIR_B_TOUCH_LEFT); + } + + return rv; +} + +/** +* This routine returns LW_TRUE if the stabline joining the pt_outside and pt_to_test +* crosses the ring an odd number of times, or if the pt_to_test is on the ring boundary itself, +* returning LW_FALSE otherwise. +* The pt_outside *must* be guaranteed to be outside the ring (use the geography_pt_outside() function +* to derive one in postgis, or the gbox_pt_outside() function if you don't mind burning CPU cycles +* building a gbox first). +*/ +int ptarray_contains_point_sphere(const POINTARRAY *pa, const POINT2D *pt_outside, const POINT2D *pt_to_test) +{ + POINT3D S1, S2; /* Stab line end points */ + POINT3D E1, E2; /* Edge end points (3-space) */ + POINT2D p; /* Edge end points (lon/lat) */ + uint32_t count = 0, i, inter; + + /* Null input, not enough points for a ring? You ain't closed! */ + if ( ! pa || pa->npoints < 4 ) + return LW_FALSE; + + /* Set up our stab line */ + ll2cart(pt_to_test, &S1); + ll2cart(pt_outside, &S2); + + /* Initialize first point */ + getPoint2d_p(pa, 0, &p); + ll2cart(&p, &E1); + + /* Walk every edge and see if the stab line hits it */ + for ( i = 1; i < pa->npoints; i++ ) + { + LWDEBUGF(4, "testing edge (%d)", i); + LWDEBUGF(4, " start point == POINT(%.12g %.12g)", p.x, p.y); + + /* Read next point. */ + getPoint2d_p(pa, i, &p); + ll2cart(&p, &E2); + + /* Skip over too-short edges. */ + if ( point3d_equals(&E1, &E2) ) + { + continue; + } + + /* Our test point is on an edge end! Point is "in ring" by our definition */ + if ( point3d_equals(&S1, &E1) ) + { + return LW_TRUE; + } + + /* Calculate relationship between stab line and edge */ + inter = edge_intersects(&S1, &S2, &E1, &E2); + + /* We have some kind of interaction... */ + if ( inter & PIR_INTERSECTS ) + { + /* If the stabline is touching the edge, that implies the test point */ + /* is on the edge, so we're done, the point is in (on) the ring. */ + if ( (inter & PIR_A_TOUCH_RIGHT) || (inter & PIR_A_TOUCH_LEFT) ) + { + return LW_TRUE; + } + + /* It's a touching interaction, disregard all the left-side ones. */ + /* It's a co-linear intersection, ignore those. */ + if ( inter & PIR_B_TOUCH_RIGHT || inter & PIR_COLINEAR ) + { + /* Do nothing, to avoid double counts. */ + LWDEBUGF(4," edge (%d) crossed, disregarding to avoid double count", i, count); + } + else + { + /* Increment crossingn count. */ + count++; + LWDEBUGF(4," edge (%d) crossed, count == %d", i, count); + } + } + else + { + LWDEBUGF(4," edge (%d) did not cross", i); + } + + /* Increment to next edge */ + E1 = E2; + } + + LWDEBUGF(4,"final count == %d", count); + + /* An odd number of crossings implies containment! */ + if ( count % 2 ) + { + return LW_TRUE; + } + + return LW_FALSE; +} diff --git a/mgist-postgis/liblwgeom/lwgeodetic.h b/mgist-postgis/liblwgeom/lwgeodetic.h new file mode 100644 index 0000000..6364db2 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeodetic.h @@ -0,0 +1,170 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2009 Paul Ramsey + * + **********************************************************************/ + + +#ifndef _LWGEODETIC_H +#define _LWGEODETIC_H 1 + +/* For NAN */ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include + +#ifndef NAN +#define NAN 0.0/0.0 +#endif + +/* Override tolerance for geodetic */ +#ifdef FP_TOLERANCE +#undef FP_TOLERANCE +#define FP_TOLERANCE 5e-14 +#endif + +extern int gbox_geocentric_slow; + +#define POW2(x) ((x)*(x)) + +/** +* Point in spherical coordinates on the world. Units of radians. +*/ +typedef struct +{ + double lon; + double lat; +} GEOGRAPHIC_POINT; + +/** +* Two-point great circle segment from a to b. +*/ +typedef struct +{ + GEOGRAPHIC_POINT start; + GEOGRAPHIC_POINT end; +} GEOGRAPHIC_EDGE; + +/** +* Holder for sorting points in distance algorithm +*/ +typedef struct +{ + double measure; + uint32_t index; +} DISTANCE_ORDER; + +/** +* Conversion functions +*/ +#define deg2rad(d) (M_PI * (d) / 180.0) +#define rad2deg(r) (180.0 * (r) / M_PI) + + +/** +* Bitmask elements for edge_intersects() return value. +*/ +#define PIR_NO_INTERACT 0x00 +#define PIR_INTERSECTS 0x01 +#define PIR_COLINEAR 0x02 +#define PIR_A_TOUCH_RIGHT 0x04 +#define PIR_A_TOUCH_LEFT 0x08 +#define PIR_B_TOUCH_RIGHT 0x10 +#define PIR_B_TOUCH_LEFT 0x20 + + +/* +* Geodetic calculations +*/ +void geog2cart(const GEOGRAPHIC_POINT *g, POINT3D *p); +void cart2geog(const POINT3D *p, GEOGRAPHIC_POINT *g); +void robust_cross_product(const GEOGRAPHIC_POINT *p, const GEOGRAPHIC_POINT *q, POINT3D *a); +void x_to_z(POINT3D *p); +void y_to_z(POINT3D *p); +int edge_point_on_plane(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p); +int edge_point_in_cone(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p); +int edge_contains_coplanar_point(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p); +int edge_contains_point(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p); +double z_to_latitude(double z, int top); +int clairaut_cartesian(const POINT3D *start, const POINT3D *end, GEOGRAPHIC_POINT *g_top, GEOGRAPHIC_POINT *g_bottom); +int clairaut_geographic(const GEOGRAPHIC_POINT *start, const GEOGRAPHIC_POINT *end, GEOGRAPHIC_POINT *g_top, GEOGRAPHIC_POINT *g_bottom); +double sphere_distance(const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e); +double sphere_distance_cartesian(const POINT3D *s, const POINT3D *e); +int sphere_project(const GEOGRAPHIC_POINT *r, double distance, double azimuth, GEOGRAPHIC_POINT *n); +int edge_calculate_gbox_slow(const GEOGRAPHIC_EDGE *e, GBOX *gbox); +int edge_calculate_gbox(const POINT3D *A1, const POINT3D *A2, GBOX *gbox); +int edge_intersection(const GEOGRAPHIC_EDGE *e1, const GEOGRAPHIC_EDGE *e2, GEOGRAPHIC_POINT *g); +uint32_t edge_intersects(const POINT3D *A1, const POINT3D *A2, const POINT3D *B1, const POINT3D *B2); +double edge_distance_to_point(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *gp, GEOGRAPHIC_POINT *closest); +double edge_distance_to_edge(const GEOGRAPHIC_EDGE *e1, const GEOGRAPHIC_EDGE *e2, GEOGRAPHIC_POINT *closest1, GEOGRAPHIC_POINT *closest2); +void geographic_point_init(double lon, double lat, GEOGRAPHIC_POINT *g); +int ptarray_contains_point_sphere(const POINTARRAY *pa, const POINT2D *pt_outside, const POINT2D *pt_to_test); +int lwpoly_covers_point2d(const LWPOLY *poly, const POINT2D *pt_to_test); +int lwpoly_covers_lwpoly(const LWPOLY *lwpoly1, const LWPOLY *lwpoly2); +int lwpoly_covers_pointarray(const LWPOLY* lwpoly, const POINTARRAY* pta); +int lwpoly_covers_lwline(const LWPOLY *poly, const LWLINE *line); +int lwline_covers_lwline(const LWLINE* lwline1, const LWLINE* lwline2); +int lwline_covers_lwpoint(const LWLINE* lwline, const LWPOINT* lwpoint); +int lwpoly_intersects_line(const LWPOLY* lwpoly, const POINTARRAY* line); +int lwpoly_pt_outside(const LWPOLY *poly, POINT2D *pt_outside); +int ptarray_point_in_ring(const POINTARRAY *pa, const POINT2D *pt_outside, const POINT2D *pt_to_test); +double ptarray_area_sphere(const POINTARRAY *pa); +double latitude_degrees_normalize(double lat); +double longitude_degrees_normalize(double lon); +double ptarray_length_spheroid(const POINTARRAY *pa, const SPHEROID *s); +int geographic_point_equals(const GEOGRAPHIC_POINT *g1, const GEOGRAPHIC_POINT *g2); +int crosses_dateline(const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e); +void point_shift(GEOGRAPHIC_POINT *p, double shift); +double longitude_radians_normalize(double lon); +double latitude_radians_normalize(double lat); +void vector_sum(const POINT3D *a, const POINT3D *b, POINT3D *n); +void vector_scale(POINT3D *a, double s); +double vector_angle(const POINT3D* v1, const POINT3D* v2); +void vector_rotate(const POINT3D* v1, const POINT3D* v2, double angle, POINT3D* n); +void normalize(POINT3D *p); +void unit_normal(const POINT3D *P1, const POINT3D *P2, POINT3D *normal); +double sphere_direction(const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e, double d); +void ll2cart(const POINT2D *g, POINT3D *p); + +/* +** Prototypes for spheroid functions. +*/ +double spheroid_distance(const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const SPHEROID *spheroid); +double spheroid_direction(const GEOGRAPHIC_POINT *r, const GEOGRAPHIC_POINT *s, const SPHEROID *spheroid); +int spheroid_project(const GEOGRAPHIC_POINT *r, const SPHEROID *spheroid, double distance, double azimuth, GEOGRAPHIC_POINT *g); + + +#endif /* _LWGEODETIC_H */ + + + +/** +* Notes for rewrite +* +* Define separate POINT types for 2-d-points-in-radiands and 3-d-points-in-geocentric +* Maintain consistent units (radians?) throughout all calculations +* Put an index pointer onto LWGEOM itself, and cache the indexed LWGEOM instead of a bare tree +* only primitive objects should get a tree +*/ + + diff --git a/mgist-postgis/liblwgeom/lwgeodetic_measures.c b/mgist-postgis/liblwgeom/lwgeodetic_measures.c new file mode 100644 index 0000000..1bca0a7 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeodetic_measures.c @@ -0,0 +1,553 @@ +/***************************************************************************** + * + * This MobilityDB code is provided under The PostgreSQL License. + * Copyright (c) 2016-2023, Université libre de Bruxelles and MobilityDB + * contributors + * + * MobilityDB includes portions of PostGIS version 3 source code released + * under the GNU General Public License (GPLv2 or later). + * Copyright (c) 2001-2023, PostGIS contributors + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose, without fee, and without a written + * agreement is hereby granted, provided that the above copyright notice and + * this paragraph and the following two paragraphs appear in all copies. + * + * IN NO EVENT SHALL UNIVERSITE LIBRE DE BRUXELLES BE LIABLE TO ANY PARTY FOR + * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING + * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, + * EVEN IF UNIVERSITE LIBRE DE BRUXELLES HAS BEEN ADVISED OF THE POSSIBILITY + * OF SUCH DAMAGE. + * + * UNIVERSITE LIBRE DE BRUXELLES SPECIFICALLY DISCLAIMS ANY WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON + * AN "AS IS" BASIS, AND UNIVERSITE LIBRE DE BRUXELLES HAS NO OBLIGATIONS TO + * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + * + *****************************************************************************/ + +/** + * @file + * @brief Spatial functions for PostGIS geography. + * + * These functions are supposed to be included in a forthcoming version of + * PostGIS, to be proposed as a PR. This still remains to be done. + * These functions are not needed in MobilityDB. + */ + +// #include "point/geography_funcs.h" + +/* C */ +#include +/* PostGIS */ +#include +#include + +/*****************************************************************************/ + + +/*********************************************************************** + * ST_LineSubstring for geographies + ***********************************************************************/ + +/** + * Find interpolation point p between geography points p1 and p2 + * so that the len(p1,p) == len(p1,p2) + * f and p falls on p1,p2 segment + */ +static void +interpolate_point4d_spheroid( + const POINT4D *p1, const POINT4D *p2, POINT4D *p, + const SPHEROID *s, double f) + +{ + GEOGRAPHIC_POINT g, g1, g2; + geographic_point_init(p1->x, p1->y, &g1); + geographic_point_init(p2->x, p2->y, &g2); + int success; + double dist, dir; + + /* Special sphere case */ + if ( s == NULL || s->a == s->b ) + { + /* Calculate distance and direction between g1 and g2 */ + dist = sphere_distance(&g1, &g2); + dir = sphere_direction(&g1, &g2, dist); + /* Compute interpolation point */ + success = sphere_project(&g1, dist*f, dir, &g); + } + /* Spheroid case */ + else + { + /* Calculate distance and direction between g1 and g2 */ + dist = spheroid_distance(&g1, &g2, s); + dir = spheroid_direction(&g1, &g2, s); + /* Compute interpolation point */ + success = spheroid_project(&g1, s, dist*f, dir, &g); + } + + /* If success, use newly computed lat and lon, + * otherwise return precomputed cartesian result */ + if (success == LW_SUCCESS) + { + p->x = rad2deg(longitude_radians_normalize(g.lon)); + p->y = rad2deg(latitude_radians_normalize(g.lat)); + } +} + + +/** + * @brief Return the part of a line between two fractional locations. + */ +LWGEOM * +geography_substring( + const LWLINE *lwline, + const SPHEROID *s, + double from, double to, + double tolerance) +{ + POINTARRAY *dpa; + POINTARRAY *ipa = lwline->points; + LWGEOM *lwresult; + POINT4D pt; + POINT4D p1, p2; + GEOGRAPHIC_POINT g1, g2; + int nsegs, i; + double length, slength, tlength; + int state = 0; /* 0 = before, 1 = inside */ + uint32_t srid = lwline->srid; + + /* + * Create a dynamic pointarray with an initial capacity + * equal to full copy of input points + */ + dpa = ptarray_construct_empty( + (char) FLAGS_GET_Z(ipa->flags), + (char) FLAGS_GET_M(ipa->flags), + ipa->npoints); + + /* Compute total line length */ + length = ptarray_length_spheroid(ipa, s); + + /* Get 'from' and 'to' lengths */ + from = length * from; + to = length * to; + tlength = 0; + getPoint4d_p(ipa, 0, &p1); + geographic_point_init(p1.x, p1.y, &g1); + nsegs = ipa->npoints - 1; + for (i = 0; i < nsegs; i++) + { + double dseg; + getPoint4d_p(ipa, (uint32_t) i+1, &p2); + geographic_point_init(p2.x, p2.y, &g2); + + /* Find the length of this segment */ + /* Special sphere case */ + if ( s->a == s->b ) + slength = s->radius * sphere_distance(&g1, &g2); + /* Spheroid case */ + else + slength = spheroid_distance(&g1, &g2, s); + + /* We are before requested start. */ + if (state == 0) /* before */ + { + if (fabs ( from - ( tlength + slength ) ) <= tolerance) + { + /* Second point is our start */ + ptarray_append_point(dpa, &p2, LW_FALSE); + state = 1; /* we're inside now */ + goto END; + } + else if (fabs(from - tlength) <= tolerance) + { + /* First point is our start */ + ptarray_append_point(dpa, &p1, LW_FALSE); + /* + * We're inside now, but will check + * 'to' point as well + */ + state = 1; + } + /* + * Didn't reach the 'from' point, + * nothing to do + */ + else if (from > tlength + slength) + { + goto END; + } + else /* tlength < from < tlength+slength */ + { + /* Our start is between first and second point */ + dseg = (from - tlength) / slength; + interpolate_point4d_spheroid(&p1, &p2, &pt, s, dseg); + ptarray_append_point(dpa, &pt, LW_FALSE); + /* We're inside now, but will check 'to' point as well */ + state = 1; + } + } + + if (state == 1) /* inside */ + { + /* 'to' point is our second point. */ + if (fabs(to - ( tlength + slength ) ) <= tolerance ) + { + ptarray_append_point(dpa, &p2, LW_FALSE); + break; /* substring complete */ + } + /* + * 'to' point is our first point. + * (should only happen if 'to' is 0) + */ + else if (fabs(to - tlength) <= tolerance) + { + ptarray_append_point(dpa, &p1, LW_FALSE); + break; /* substring complete */ + } + /* + * Didn't reach the 'end' point, + * just copy second point + */ + else if (to > tlength + slength) + { + ptarray_append_point(dpa, &p2, LW_FALSE); + goto END; + } + /* + * 'to' point falls on this segment + * Interpolate and break. + */ + else if (to < tlength + slength ) + { + dseg = (to - tlength) / slength; + interpolate_point4d_spheroid(&p1, &p2, &pt, s, dseg); + ptarray_append_point(dpa, &pt, LW_FALSE); + break; + } + } + + END: + tlength += slength; + memcpy(&p1, &p2, sizeof(POINT4D)); + } + + if (dpa->npoints <= 1) { + lwresult = lwpoint_as_lwgeom(lwpoint_construct(srid, NULL, dpa)); + } + else { + lwresult = lwline_as_lwgeom(lwline_construct(srid, NULL, dpa)); + } + + return lwresult; +} + + +/*********************************************************************** + * Interpolate a point along a geographic line. + ***********************************************************************/ + +/** + * @brief Interpolate a point along a geographic line. + */ +LWGEOM * +geography_interpolate_points( + const LWLINE *line, double length_fraction, + const SPHEROID *s, char repeat) +{ + POINT4D pt; + uint32_t i; + uint32_t points_to_interpolate; + uint32_t points_found = 0; + double length; + double length_fraction_increment = length_fraction; + double length_fraction_consumed = 0; + char has_z = (char) lwgeom_has_z(lwline_as_lwgeom(line)); + char has_m = (char) lwgeom_has_m(lwline_as_lwgeom(line)); + const POINTARRAY* ipa = line->points; + POINTARRAY *opa; + POINT4D p1, p2; + POINT3D q1, q2; + LWGEOM *lwresult; + GEOGRAPHIC_POINT g1, g2; + uint32_t srid = line->srid; + + /* Empty.InterpolatePoint == Point Empty */ + if ( lwline_is_empty(line) ) + { + return lwgeom_clone_deep(lwline_as_lwgeom(line)); + } + + /* + * If distance is one of the two extremes, return the point on that + * end rather than doing any computations + */ + if ( length_fraction == 0.0 || length_fraction == 1.0 ) + { + if ( length_fraction == 0.0 ) + getPoint4d_p(ipa, 0, &pt); + else + getPoint4d_p(ipa, ipa->npoints-1, &pt); + + opa = ptarray_construct(has_z, has_m, 1); + ptarray_set_point4d(opa, 0, &pt); + return lwpoint_as_lwgeom(lwpoint_construct(srid, NULL, opa)); + } + + /* Interpolate points along the line */ + length = ptarray_length_spheroid(ipa, s); + points_to_interpolate = repeat ? (uint32_t) floor(1 / length_fraction) : 1; + opa = ptarray_construct(has_z, has_m, points_to_interpolate); + + getPoint4d_p(ipa, 0, &p1); + geographic_point_init(p1.x, p1.y, &g1); + for ( i = 0; i < ipa->npoints - 1 && points_found < points_to_interpolate; i++ ) + { + double segment_length_frac; + getPoint4d_p(ipa, i+1, &p2); + geographic_point_init(p2.x, p2.y, &g2); + + /* Special sphere case */ + if ( s->a == s->b ) + segment_length_frac = s->radius * sphere_distance(&g1, &g2) / length; + /* Spheroid case */ + else + segment_length_frac = spheroid_distance(&g1, &g2, s) / length; + + /* If our target distance is before the total length we've seen + * so far. create a new point some distance down the current + * segment. + */ + while ( length_fraction < length_fraction_consumed + segment_length_frac && points_found < points_to_interpolate ) + { + geog2cart(&g1, &q1); + geog2cart(&g2, &q2); + double segment_fraction = (length_fraction - length_fraction_consumed) / segment_length_frac; + interpolate_point4d_spheroid(&p1, &p2, &pt, s, segment_fraction); + ptarray_set_point4d(opa, points_found++, &pt); + length_fraction += length_fraction_increment; + } + + length_fraction_consumed += segment_length_frac; + + p1 = p2; + g1 = g2; + } + + /* Return the last point on the line. This shouldn't happen, but + * could if there's some floating point rounding errors. */ + if (points_found < points_to_interpolate) + { + getPoint4d_p(ipa, ipa->npoints - 1, &pt); + ptarray_set_point4d(opa, points_found, &pt); + } + + if (opa->npoints <= 1) + { + lwresult = lwpoint_as_lwgeom(lwpoint_construct(srid, NULL, opa)); + } else { + lwresult = lwmpoint_as_lwgeom(lwmpoint_construct(srid, opa)); + } + + return lwresult; +} + +/** + * @brief Locate a point along the point array defining a geographic line. + */ +double +ptarray_locate_point_spheroid( + const POINTARRAY *pa, + const POINT4D *p4d, + const SPHEROID *s, + double tolerance, + double *mindistout, + POINT4D *proj4d) +{ + GEOGRAPHIC_EDGE e; + GEOGRAPHIC_POINT a, b, nearest = {0}; /* make compiler quiet */ + POINT4D p1, p2; + const POINT2D *p; + POINT2D proj; + uint32_t i, seg = 0; + int use_sphere = (s->a == s->b ? 1 : 0); + int hasz; + double za = 0.0, zb = 0.0; + double distance, + length, /* Used for computing lengths */ + seglength = 0.0, /* length of the segment where the closest point is located */ + partlength = 0.0, /* length from the beginning of the point array to the closest point */ + totlength = 0.0; /* length of the point array */ + + /* Initialize our point */ + geographic_point_init(p4d->x, p4d->y, &a); + + /* Handle point/point case here */ + if ( pa->npoints <= 1) + { + double mindist = 0.0; + if ( pa->npoints == 1 ) + { + p = getPoint2d_cp(pa, 0); + geographic_point_init(p->x, p->y, &b); + /* Sphere special case, axes equal */ + mindist = s->radius * sphere_distance(&a, &b); + /* If close or greater than tolerance, get the real answer to be sure */ + if ( ! use_sphere || mindist > 0.95 * tolerance ) + { + mindist = spheroid_distance(&a, &b, s); + } + } + if ( mindistout ) *mindistout = mindist; + return 0.0; + } + + /* Make result really big, so that everything will be smaller than it */ + distance = FLT_MAX; + + /* Initialize start of line */ + p = getPoint2d_cp(pa, 0); + geographic_point_init(p->x, p->y, &(e.start)); + + /* Iterate through the edges in our line */ + for ( i = 1; i < pa->npoints; i++ ) + { + double d; + p = getPoint2d_cp(pa, i); + geographic_point_init(p->x, p->y, &(e.end)); + /* Get the spherical distance between point and edge */ + d = s->radius * edge_distance_to_point(&e, &a, &b); + /* New shortest distance! Record this distance / location / segment */ + if ( d < distance ) + { + distance = d; + nearest = b; + seg = i - 1; + } + /* We've gotten closer than the tolerance... */ + if ( d < tolerance ) + { + /* Working on a sphere? The answer is correct, return */ + if ( use_sphere ) + { + break; + } + /* Far enough past the tolerance that the spheroid calculation won't change things */ + else if ( d < tolerance * 0.95 ) + { + break; + } + /* On a spheroid and near the tolerance? Confirm that we are *actually* closer than tolerance */ + else + { + d = spheroid_distance(&a, &nearest, s); + /* Yes, closer than tolerance, return! */ + if ( d < tolerance ) + break; + } + } + e.start = e.end; + } + + if ( mindistout ) *mindistout = distance; + + /* See if we have a third dimension */ + hasz = (bool) FLAGS_GET_Z(pa->flags); + + /* Initialize first point of array */ + getPoint4d_p(pa, 0, &p1); + geographic_point_init(p1.x, p1.y, &a); + if ( hasz ) + za = p1.z; + + /* Loop and sum the length for each segment */ + for ( i = 1; i < pa->npoints; i++ ) + { + getPoint4d_p(pa, i, &p1); + geographic_point_init(p1.x, p1.y, &b); + if ( hasz ) + zb = p1.z; + + /* Special sphere case */ + if ( s->a == s->b ) + length = s->radius * sphere_distance(&a, &b); + /* Spheroid case */ + else + length = spheroid_distance(&a, &b, s); + + /* Add in the vertical displacement if we're in 3D */ + if ( hasz ) + length = sqrt( (zb-za)*(zb-za) + length*length ); + + /* Add this segment length to the total length */ + totlength += length; + + /* Add this segment length to the partial length */ + if (i - 1 < seg) + partlength += length; + else if (i - 1 == seg) + /* Save segment length for computing the final value of partlength */ + seglength = length; + + /* B gets incremented in the next loop, so we save the value here */ + a = b; + za = zb; + } + + /* Copy nearest into 2D/4D holder */ + proj4d->x = proj.x = rad2deg(nearest.lon); + proj4d->y = proj.y = rad2deg(nearest.lat); + + /* Compute distance from beginning of the segment to closest point */ + + /* Start of the segment */ + getPoint4d_p(pa, seg, &p1); + geographic_point_init(p1.x, p1.y, &a); + + /* Closest point */ + geographic_point_init(proj4d->x, proj4d->y, &b); + + /* Special sphere case */ + if ( s->a == s->b ) + length = s->radius * sphere_distance(&a, &b); + /* Spheroid case */ + else + length = spheroid_distance(&a, &b, s); + + if ( hasz ) + { + /* Compute Z and M values for closest point */ + double f = length / seglength; + getPoint4d_p(pa, seg + 1, &p2); + proj4d->z = p1.z + ((p2.z - p1.z) * f); + proj4d->m = p1.m + ((p2.m - p1.m) * f); + /* Add in the vertical displacement if we're in 3D */ + za = p1.z; + zb = proj4d->z; + length = sqrt( (zb-za)*(zb-za) + length*length ); + } + + /* Add this segment length to the total */ + partlength += length; + + /* Location of any point on a zero-length line is 0 */ + /* See http://trac.osgeo.org/postgis/ticket/1772#comment:2 */ + if ( partlength == 0 || totlength == 0 ) + return 0.0; + + /* For robustness, force 0 when closest point == startpoint */ + p = getPoint2d_cp(pa, 0); + if ( seg == 0 && p2d_same(&proj, p) ) + return 0.0; + + /* For robustness, force 1 when closest point == endpoint */ + p = getPoint2d_cp(pa, pa->npoints - 1); + if ( (seg >= (pa->npoints-2)) && p2d_same(&proj, p) ) + return 1.0; + + return partlength / totlength; +} + +/*****************************************************************************/ diff --git a/mgist-postgis/liblwgeom/lwgeodetic_tree.c b/mgist-postgis/liblwgeom/lwgeodetic_tree.c new file mode 100644 index 0000000..a425046 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeodetic_tree.c @@ -0,0 +1,1108 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2012-2015 Paul Ramsey + * Copyright (C) 2012-2015 Sandro Santilli + * + **********************************************************************/ + +#include "liblwgeom_internal.h" +#include "lwgeodetic_tree.h" +#include "lwgeom_log.h" + + +/* Internal prototype */ +static CIRC_NODE* circ_nodes_merge(CIRC_NODE** nodes, int num_nodes); +static double circ_tree_distance_tree_internal(const CIRC_NODE* n1, const CIRC_NODE* n2, double threshold, double* min_dist, double* max_dist, GEOGRAPHIC_POINT* closest1, GEOGRAPHIC_POINT* closest2); + + +/** +* Internal nodes have their point references set to NULL. +*/ +static inline int +circ_node_is_leaf(const CIRC_NODE* node) +{ + return (node->num_nodes == 0); +} + +/** +* Recurse from top of node tree and free all children. +* does not free underlying point array. +*/ +void +circ_tree_free(CIRC_NODE* node) +{ + uint32_t i; + if ( ! node ) return; + + if (node->nodes) + { + for (i = 0; i < node->num_nodes; i++) + circ_tree_free(node->nodes[i]); + lwfree(node->nodes); + } + lwfree(node); +} + + +/** +* Create a new leaf node, storing pointers back to the end points for later. +*/ +static CIRC_NODE* +circ_node_leaf_new(const POINTARRAY* pa, int i) +{ + POINT2D *p1, *p2; + POINT3D q1, q2, c; + GEOGRAPHIC_POINT g1, g2, gc; + CIRC_NODE *node; + double diameter; + + p1 = (POINT2D*)getPoint_internal(pa, i); + p2 = (POINT2D*)getPoint_internal(pa, i+1); + geographic_point_init(p1->x, p1->y, &g1); + geographic_point_init(p2->x, p2->y, &g2); + + LWDEBUGF(3,"edge #%d (%g %g, %g %g)", i, p1->x, p1->y, p2->x, p2->y); + + diameter = sphere_distance(&g1, &g2); + + /* Zero length edge, doesn't get a node */ + if ( FP_EQUALS(diameter, 0.0) ) + return NULL; + + /* Allocate */ + node = lwalloc(sizeof(CIRC_NODE)); + node->p1 = p1; + node->p2 = p2; + + /* Convert ends to X/Y/Z, sum, and normalize to get mid-point */ + geog2cart(&g1, &q1); + geog2cart(&g2, &q2); + vector_sum(&q1, &q2, &c); + normalize(&c); + cart2geog(&c, &gc); + node->center = gc; + node->radius = diameter / 2.0; + + LWDEBUGF(3,"edge #%d CENTER(%g %g) RADIUS=%g", i, gc.lon, gc.lat, node->radius); + + /* Leaf has no children */ + node->num_nodes = 0; + node->nodes = NULL; + node->edge_num = i; + + /* Zero out metadata */ + node->pt_outside.x = 0.0; + node->pt_outside.y = 0.0; + node->geom_type = 0; + + return node; +} + +/** +* Return a point node (zero radius, referencing one point) +*/ +static CIRC_NODE* +circ_node_leaf_point_new(const POINTARRAY* pa) +{ + CIRC_NODE* tree = lwalloc(sizeof(CIRC_NODE)); + tree->p1 = tree->p2 = (POINT2D*)getPoint_internal(pa, 0); + geographic_point_init(tree->p1->x, tree->p1->y, &(tree->center)); + tree->radius = 0.0; + tree->nodes = NULL; + tree->num_nodes = 0; + tree->edge_num = 0; + tree->geom_type = POINTTYPE; + tree->pt_outside.x = 0.0; + tree->pt_outside.y = 0.0; + return tree; +} + +/** +* Comparing on geohash ensures that nearby nodes will be close +* to each other in the list. +*/ +static int +circ_node_compare(const void* v1, const void* v2) +{ + POINT2D p1, p2; + unsigned int u1, u2; + CIRC_NODE *c1 = *((CIRC_NODE**)v1); + CIRC_NODE *c2 = *((CIRC_NODE**)v2); + p1.x = rad2deg((c1->center).lon); + p1.y = rad2deg((c1->center).lat); + p2.x = rad2deg((c2->center).lon); + p2.y = rad2deg((c2->center).lat); + u1 = geohash_point_as_int(&p1); + u2 = geohash_point_as_int(&p2); + if ( u1 < u2 ) return -1; + if ( u1 > u2 ) return 1; + return 0; +} + +/** +* Given the centers of two circles, and the offset distance we want to put the new center between them +* (calculated as the distance implied by the radii of the inputs and the distance between the centers) +* figure out where the new center point is, by getting the direction from c1 to c2 and projecting +* from c1 in that direction by the offset distance. +*/ +static int +circ_center_spherical(const GEOGRAPHIC_POINT* c1, const GEOGRAPHIC_POINT* c2, double distance, double offset, GEOGRAPHIC_POINT* center) +{ + /* Direction from c1 to c2 */ + double dir = sphere_direction(c1, c2, distance); + + LWDEBUGF(4,"calculating spherical center", dir); + + LWDEBUGF(4,"dir is %g", dir); + + /* Catch sphere_direction when it barfs */ + if ( isnan(dir) ) + return LW_FAILURE; + + /* Center of new circle is projection from start point, using offset distance*/ + return sphere_project(c1, offset, dir, center); +} + +/** +* Where the circ_center_spherical() function fails, we need a fall-back. The failures +* happen in short arcs, where the spherical distance between two points is practically +* the same as the straight-line distance, so our fallback will be to use the straight-line +* between the two to calculate the new projected center. For proportions far from 0.5 +* this will be increasingly more incorrect. +*/ +static int +circ_center_cartesian(const GEOGRAPHIC_POINT* c1, const GEOGRAPHIC_POINT* c2, double distance, double offset, GEOGRAPHIC_POINT* center) +{ + POINT3D p1, p2; + POINT3D p1p2, pc; + double proportion = offset/distance; + + LWDEBUG(4,"calculating cartesian center"); + + geog2cart(c1, &p1); + geog2cart(c2, &p2); + + /* Difference between p2 and p1 */ + p1p2.x = p2.x - p1.x; + p1p2.y = p2.y - p1.y; + p1p2.z = p2.z - p1.z; + + /* Scale difference to proportion */ + p1p2.x *= proportion; + p1p2.y *= proportion; + p1p2.z *= proportion; + + /* Add difference to p1 to get approximate center point */ + pc.x = p1.x + p1p2.x; + pc.y = p1.y + p1p2.y; + pc.z = p1.z + p1p2.z; + normalize(&pc); + + /* Convert center point to geographics */ + cart2geog(&pc, center); + + return LW_SUCCESS; +} + + +/** +* Create a new internal node, calculating the new measure range for the node, +* and storing pointers to the child nodes. +*/ +static CIRC_NODE* +circ_node_internal_new(CIRC_NODE** c, uint32_t num_nodes) +{ + CIRC_NODE *node = NULL; + GEOGRAPHIC_POINT new_center, c1; + double new_radius; + double offset1, dist, D, r1, ri; + uint32_t i, new_geom_type; + + LWDEBUGF(3, "called with %d nodes --", num_nodes); + + /* Can't do anything w/ empty input */ + if ( num_nodes < 1 ) + return node; + + /* Initialize calculation with values of the first circle */ + new_center = c[0]->center; + new_radius = c[0]->radius; + new_geom_type = c[0]->geom_type; + + /* Merge each remaining circle into the new circle */ + for ( i = 1; i < num_nodes; i++ ) + { + c1 = new_center; + r1 = new_radius; + + dist = sphere_distance(&c1, &(c[i]->center)); + ri = c[i]->radius; + + /* Promote geometry types up the tree, getting more and more collected */ + /* Go until we find a value */ + if ( ! new_geom_type ) + { + new_geom_type = c[i]->geom_type; + } + /* Promote singleton to a multi-type */ + else if ( ! lwtype_is_collection(new_geom_type) ) + { + /* Anonymous collection if types differ */ + if ( new_geom_type != c[i]->geom_type ) + { + new_geom_type = COLLECTIONTYPE; + } + else + { + new_geom_type = lwtype_get_collectiontype(new_geom_type); + } + } + /* If we can't add next feature to this collection cleanly, promote again to anonymous collection */ + else if ( new_geom_type != lwtype_get_collectiontype(c[i]->geom_type) ) + { + new_geom_type = COLLECTIONTYPE; + } + + + LWDEBUGF(3, "distance between new (%g %g) and %i (%g %g) is %g", c1.lon, c1.lat, i, c[i]->center.lon, c[i]->center.lat, dist); + + if ( FP_EQUALS(dist, 0) ) + { + LWDEBUG(3, " distance between centers is zero"); + new_radius = r1 + 2*dist; + new_center = c1; + } + else if ( dist < fabs(r1 - ri) ) + { + /* new contains next */ + if ( r1 > ri ) + { + LWDEBUG(3, " c1 contains ci"); + new_center = c1; + new_radius = r1; + } + /* next contains new */ + else + { + LWDEBUG(3, " ci contains c1"); + new_center = c[i]->center; + new_radius = ri; + } + } + else + { + LWDEBUG(3, " calculating new center"); + /* New circle diameter */ + D = dist + r1 + ri; + LWDEBUGF(3," D is %g", D); + + /* New radius */ + new_radius = D / 2.0; + + /* Distance from cn1 center to the new center */ + offset1 = ri + (D - (2.0*r1 + 2.0*ri)) / 2.0; + LWDEBUGF(3," offset1 is %g", offset1); + + /* Sometimes the sphere_direction function fails... this causes the center calculation */ + /* to fail too. In that case, we're going to fall back to a cartesian calculation, which */ + /* is less exact, so we also have to pad the radius by (hack alert) an arbitrary amount */ + /* which is hopefully always big enough to contain the input edges */ + if ( circ_center_spherical(&c1, &(c[i]->center), dist, offset1, &new_center) == LW_FAILURE ) + { + circ_center_cartesian(&c1, &(c[i]->center), dist, offset1, &new_center); + new_radius *= 1.1; + } + } + LWDEBUGF(3, " new center is (%g %g) new radius is %g", new_center.lon, new_center.lat, new_radius); + } + + node = lwalloc(sizeof(CIRC_NODE)); + node->p1 = NULL; + node->p2 = NULL; + node->center = new_center; + node->radius = new_radius; + node->num_nodes = num_nodes; + node->nodes = c; + node->edge_num = -1; + node->geom_type = new_geom_type; + node->pt_outside.x = 0.0; + node->pt_outside.y = 0.0; + return node; +} + +/** +* Build a tree of nodes from a point array, one node per edge. +*/ +CIRC_NODE* +circ_tree_new(const POINTARRAY* pa) +{ + int num_edges; + int i, j; + CIRC_NODE **nodes; + CIRC_NODE *node; + CIRC_NODE *tree; + + /* Can't do anything with no points */ + if ( pa->npoints < 1 ) + return NULL; + + /* Special handling for a single point */ + if ( pa->npoints == 1 ) + return circ_node_leaf_point_new(pa); + + /* First create a flat list of nodes, one per edge. */ + num_edges = pa->npoints - 1; + nodes = lwalloc(sizeof(CIRC_NODE*) * pa->npoints); + j = 0; + for ( i = 0; i < num_edges; i++ ) + { + node = circ_node_leaf_new(pa, i); + if ( node ) /* Not zero length? */ + nodes[j++] = node; + } + + /* Special case: only zero-length edges. Make a point node. */ + if ( j == 0 ) { + lwfree(nodes); + return circ_node_leaf_point_new(pa); + } + + /* Merge the node list pairwise up into a tree */ + tree = circ_nodes_merge(nodes, j); + + /* Free the old list structure, leaving the tree in place */ + lwfree(nodes); + + return tree; +} + +/** +* Given a list of nodes, sort them into a spatially consistent +* order, then pairwise merge them up into a tree. Should make +* handling multipoints and other collections more efficient +*/ +static void +circ_nodes_sort(CIRC_NODE** nodes, int num_nodes) +{ + qsort(nodes, num_nodes, sizeof(CIRC_NODE*), circ_node_compare); +} + + +static CIRC_NODE* +circ_nodes_merge(CIRC_NODE** nodes, int num_nodes) +{ + CIRC_NODE **inodes = NULL; + int num_children = num_nodes; + int inode_num = 0; + int num_parents = 0; + int j; + + /* TODO, roll geom_type *up* as tree is built, changing to collection types as simple types are merged + * TODO, change the distance algorithm to drive down to simple types first, test pip on poly/other cases, then test edges + */ + + while( num_children > 1 ) + { + for ( j = 0; j < num_children; j++ ) + { + inode_num = (j % CIRC_NODE_SIZE); + if ( inode_num == 0 ) + inodes = lwalloc(sizeof(CIRC_NODE*)*CIRC_NODE_SIZE); + + inodes[inode_num] = nodes[j]; + + if ( inode_num == CIRC_NODE_SIZE-1 ) + nodes[num_parents++] = circ_node_internal_new(inodes, CIRC_NODE_SIZE); + } + + /* Clean up any remaining nodes... */ + if ( inode_num == 0 ) + { + /* Promote solo nodes without merging */ + nodes[num_parents++] = inodes[0]; + lwfree(inodes); + } + else if ( inode_num < CIRC_NODE_SIZE-1 ) + { + /* Merge spare nodes */ + nodes[num_parents++] = circ_node_internal_new(inodes, inode_num+1); + } + + num_children = num_parents; + num_parents = 0; + } + + /* Return a reference to the head of the tree */ + return nodes[0]; +} + + +/** +* Returns a #POINT2D that is a vertex of the input shape +*/ +int circ_tree_get_point(const CIRC_NODE* node, POINT2D* pt) +{ + if ( circ_node_is_leaf(node) ) + { + pt->x = node->p1->x; + pt->y = node->p1->y; + return LW_SUCCESS; + } + else + { + return circ_tree_get_point(node->nodes[0], pt); + } +} + +int circ_tree_get_point_outside(const CIRC_NODE* node, POINT2D* pt) +{ + POINT3D center3d; + GEOGRAPHIC_POINT g; + // if (node->radius >= M_PI) return LW_FAILURE; + geog2cart(&(node->center), ¢er3d); + vector_scale(¢er3d, -1.0); + cart2geog(¢er3d, &g); + pt->x = rad2deg(g.lon); + pt->y = rad2deg(g.lat); + return LW_SUCCESS; +} + + +/** +* Walk the tree and count intersections between the stab line and the edges. +* odd => containment, even => no containment. +* KNOWN PROBLEM: Grazings (think of a sharp point, just touching the +* stabline) will be counted for one, which will throw off the count. +*/ +int circ_tree_contains_point(const CIRC_NODE* node, const POINT2D* pt, const POINT2D* pt_outside, int level, int* on_boundary) +{ + GEOGRAPHIC_POINT closest; + GEOGRAPHIC_EDGE stab_edge, edge; + POINT3D S1, S2, E1, E2; + double d; + uint32_t i, c; + + /* Construct a stabline edge from our "inside" to our known outside point */ + geographic_point_init(pt->x, pt->y, &(stab_edge.start)); + geographic_point_init(pt_outside->x, pt_outside->y, &(stab_edge.end)); + geog2cart(&(stab_edge.start), &S1); + geog2cart(&(stab_edge.end), &S2); + + LWDEBUGF(3, "%*s entered", level, ""); + + /* + * If the stabline doesn't cross within the radius of a node, there's no + * way it can cross. + */ + + LWDEBUGF(3, "%*s :working on node %p, edge_num %d, radius %g, center POINT(%.12g %.12g)", level, "", node, node->edge_num, node->radius, rad2deg(node->center.lon), rad2deg(node->center.lat)); + d = edge_distance_to_point(&stab_edge, &(node->center), &closest); + LWDEBUGF(3, "%*s :edge_distance_to_point=%g, node_radius=%g", level, "", d, node->radius); + if ( FP_LTEQ(d, node->radius) ) + { + LWDEBUGF(3,"%*s :entering this branch (%p)", level, "", node); + + /* Return the crossing number of this leaf */ + if ( circ_node_is_leaf(node) ) + { + int inter; + LWDEBUGF(3, "%*s :leaf node calculation (edge %d)", level, "", node->edge_num); + geographic_point_init(node->p1->x, node->p1->y, &(edge.start)); + geographic_point_init(node->p2->x, node->p2->y, &(edge.end)); + geog2cart(&(edge.start), &E1); + geog2cart(&(edge.end), &E2); + + inter = edge_intersects(&S1, &S2, &E1, &E2); + LWDEBUGF(3, "%*s :inter = %d", level, "", inter); + + if ( inter & PIR_INTERSECTS ) + { + LWDEBUGF(3,"%*s ::got stab line edge_intersection with this edge!", level, ""); + /* To avoid double counting crossings-at-a-vertex, */ + /* always ignore crossings at "lower" ends of edges*/ + GEOGRAPHIC_POINT e1, e2; + cart2geog(&E1,&e1); cart2geog(&E2,&e2); + + LWDEBUGF(3,"%*s LINESTRING(%.15g %.15g,%.15g %.15g)", level, "", + pt->x, pt->y, + pt_outside->x, pt_outside->y + ); + + LWDEBUGF(3,"%*s LINESTRING(%.15g %.15g,%.15g %.15g)", level, "", + rad2deg(e1.lon), rad2deg(e1.lat), + rad2deg(e2.lon), rad2deg(e2.lat) + ); + + if ( inter & PIR_B_TOUCH_RIGHT || inter & PIR_COLINEAR ) + { + LWDEBUGF(3,"%*s ::rejecting stab line grazing by left-side edge", level, ""); + return 0; + } + else + { + LWDEBUGF(3,"%*s ::accepting stab line intersection", level, ""); + return 1; + } + } + else + { + LWDEBUGF(3,"%*s edge does not intersect", level, ""); + } + } + /* Or, add up the crossing numbers of all children of this node. */ + else + { + c = 0; + for ( i = 0; i < node->num_nodes; i++ ) + { + LWDEBUGF(3,"%*s calling circ_tree_contains_point on child %d!", level, "", i); + c += circ_tree_contains_point(node->nodes[i], pt, pt_outside, level + 1, on_boundary); + } + return c % 2; + } + } + else + { + LWDEBUGF(3,"%*s skipping this branch (%p)", level, "", node); + } + return 0; +} + +static double +circ_node_min_distance(const CIRC_NODE* n1, const CIRC_NODE* n2) +{ + double d = sphere_distance(&(n1->center), &(n2->center)); + double r1 = n1->radius; + double r2 = n2->radius; + + if ( d < r1 + r2 ) + return 0.0; + + return d - r1 - r2; +} + +static double +circ_node_max_distance(const CIRC_NODE *n1, const CIRC_NODE *n2) +{ + return sphere_distance(&(n1->center), &(n2->center)) + n1->radius + n2->radius; +} + +double +circ_tree_distance_tree(const CIRC_NODE* n1, const CIRC_NODE* n2, const SPHEROID* spheroid, double threshold) +{ + double min_dist = FLT_MAX; + double max_dist = FLT_MAX; + GEOGRAPHIC_POINT closest1, closest2; + /* Quietly decrease the threshold just a little to avoid cases where */ + /* the actual spheroid distance is larger than the sphere distance */ + /* causing the return value to be larger than the threshold value */ + double threshold_radians = 0.95 * threshold / spheroid->radius; + + circ_tree_distance_tree_internal(n1, n2, threshold_radians, &min_dist, &max_dist, &closest1, &closest2); + + /* Spherical case */ + if ( spheroid->a == spheroid->b ) + { + return spheroid->radius * sphere_distance(&closest1, &closest2); + } + else + { + return spheroid_distance(&closest1, &closest2, spheroid); + } +} + + +/*********************************************************************** +* Internal node sorting routine to make distance calculations faster? +*/ + +struct sort_node { + CIRC_NODE *node; + double d; +}; + +static int +circ_nodes_sort_cmp(const void *a, const void *b) +{ + struct sort_node *node_a = (struct sort_node *)(a); + struct sort_node *node_b = (struct sort_node *)(b); + if (node_a->d < node_b->d) return -1; + else if (node_a->d > node_b->d) return 1; + else return 0; +} + +static void +circ_internal_nodes_sort(CIRC_NODE **nodes, uint32_t num_nodes, const CIRC_NODE *target_node) +{ + uint32_t i; + struct sort_node sort_nodes[CIRC_NODE_SIZE]; + + /* Copy incoming nodes into sorting array and calculate */ + /* distance to the target node */ + for (i = 0; i < num_nodes; i++) + { + sort_nodes[i].node = nodes[i]; + sort_nodes[i].d = sphere_distance(&(nodes[i]->center), &(target_node->center)); + } + + /* Sort the nodes and copy the result back into the input array */ + qsort(sort_nodes, num_nodes, sizeof(struct sort_node), circ_nodes_sort_cmp); + for (i = 0; i < num_nodes; i++) + { + nodes[i] = sort_nodes[i].node; + } + return; +} + +/***********************************************************************/ + +double +circ_tree_distance_tree_internal(const CIRC_NODE* n1, const CIRC_NODE* n2, double threshold, double* min_dist, double* max_dist, GEOGRAPHIC_POINT* closest1, GEOGRAPHIC_POINT* closest2) +{ + double max; + double d, d_min; + uint32_t i; + + LWDEBUGF(4, "entered, min_dist=%.8g max_dist=%.8g, type1=%d, type2=%d", *min_dist, *max_dist, n1->geom_type, n2->geom_type); + + // printf("-==-\n"); + // circ_tree_print(n1, 0); + // printf("--\n"); + // circ_tree_print(n2, 0); + + /* Short circuit if we've already hit the minimum */ + if( *min_dist < threshold || *min_dist == 0.0 ) + return *min_dist; + + /* If your minimum is greater than anyone's maximum, you can't hold the winner */ + if( circ_node_min_distance(n1, n2) > *max_dist ) + { + LWDEBUGF(4, "pruning pair %p, %p", n1, n2); + return FLT_MAX; + } + + /* If your maximum is a new low, we'll use that as our new global tolerance */ + max = circ_node_max_distance(n1, n2); + LWDEBUGF(5, "max %.8g", max); + if( max < *max_dist ) + *max_dist = max; + + /* Polygon on one side, primitive type on the other. Check for point-in-polygon */ + /* short circuit. */ + if ( n1->geom_type == POLYGONTYPE && n2->geom_type && ! lwtype_is_collection(n2->geom_type) ) + { + POINT2D pt; + circ_tree_get_point(n2, &pt); + LWDEBUGF(4, "n1 is polygon, testing if contains (%.5g,%.5g)", pt.x, pt.y); + if ( circ_tree_contains_point(n1, &pt, &(n1->pt_outside), 0, NULL) ) + { + LWDEBUG(4, "it does"); + *min_dist = 0.0; + geographic_point_init(pt.x, pt.y, closest1); + geographic_point_init(pt.x, pt.y, closest2); + return *min_dist; + } + } + /* Polygon on one side, primitive type on the other. Check for point-in-polygon */ + /* short circuit. */ + if ( n2->geom_type == POLYGONTYPE && n1->geom_type && ! lwtype_is_collection(n1->geom_type) ) + { + POINT2D pt; + circ_tree_get_point(n1, &pt); + LWDEBUGF(4, "n2 is polygon, testing if contains (%.5g,%.5g)", pt.x, pt.y); + if ( circ_tree_contains_point(n2, &pt, &(n2->pt_outside), 0, NULL) ) + { + LWDEBUG(4, "it does"); + geographic_point_init(pt.x, pt.y, closest1); + geographic_point_init(pt.x, pt.y, closest2); + *min_dist = 0.0; + return *min_dist; + } + } + + /* Both leaf nodes, do a real distance calculation */ + if( circ_node_is_leaf(n1) && circ_node_is_leaf(n2) ) + { + double d; + GEOGRAPHIC_POINT close1, close2; + LWDEBUGF(4, "testing leaf pair [%d], [%d]", n1->edge_num, n2->edge_num); + /* One of the nodes is a point */ + if ( n1->p1 == n1->p2 || n2->p1 == n2->p2 ) + { + GEOGRAPHIC_EDGE e; + GEOGRAPHIC_POINT gp1, gp2; + + /* Both nodes are points! */ + if ( n1->p1 == n1->p2 && n2->p1 == n2->p2 ) + { + geographic_point_init(n1->p1->x, n1->p1->y, &gp1); + geographic_point_init(n2->p1->x, n2->p1->y, &gp2); + close1 = gp1; close2 = gp2; + d = sphere_distance(&gp1, &gp2); + } + /* Node 1 is a point */ + else if ( n1->p1 == n1->p2 ) + { + geographic_point_init(n1->p1->x, n1->p1->y, &gp1); + geographic_point_init(n2->p1->x, n2->p1->y, &(e.start)); + geographic_point_init(n2->p2->x, n2->p2->y, &(e.end)); + close1 = gp1; + d = edge_distance_to_point(&e, &gp1, &close2); + } + /* Node 2 is a point */ + else + { + geographic_point_init(n2->p1->x, n2->p1->y, &gp1); + geographic_point_init(n1->p1->x, n1->p1->y, &(e.start)); + geographic_point_init(n1->p2->x, n1->p2->y, &(e.end)); + close1 = gp1; + d = edge_distance_to_point(&e, &gp1, &close2); + } + LWDEBUGF(4, " got distance %g", d); + } + /* Both nodes are edges */ + else + { + GEOGRAPHIC_EDGE e1, e2; + GEOGRAPHIC_POINT g; + POINT3D A1, A2, B1, B2; + geographic_point_init(n1->p1->x, n1->p1->y, &(e1.start)); + geographic_point_init(n1->p2->x, n1->p2->y, &(e1.end)); + geographic_point_init(n2->p1->x, n2->p1->y, &(e2.start)); + geographic_point_init(n2->p2->x, n2->p2->y, &(e2.end)); + geog2cart(&(e1.start), &A1); + geog2cart(&(e1.end), &A2); + geog2cart(&(e2.start), &B1); + geog2cart(&(e2.end), &B2); + if ( edge_intersects(&A1, &A2, &B1, &B2) ) + { + d = 0.0; + edge_intersection(&e1, &e2, &g); + close1 = close2 = g; + } + else + { + d = edge_distance_to_edge(&e1, &e2, &close1, &close2); + } + LWDEBUGF(4, "edge_distance_to_edge returned %g", d); + } + if ( d < *min_dist ) + { + *min_dist = d; + *closest1 = close1; + *closest2 = close2; + } + return d; + } + else + { + d_min = FLT_MAX; + /* Drive the recursion into the COLLECTION types first so we end up with */ + /* pairings of primitive geometries that can be forced into the point-in-polygon */ + /* tests above. */ + if ( n1->geom_type && lwtype_is_collection(n1->geom_type) ) + { + circ_internal_nodes_sort(n1->nodes, n1->num_nodes, n2); + for ( i = 0; i < n1->num_nodes; i++ ) + { + d = circ_tree_distance_tree_internal(n1->nodes[i], n2, threshold, min_dist, max_dist, closest1, closest2); + d_min = FP_MIN(d_min, d); + } + } + else if ( n2->geom_type && lwtype_is_collection(n2->geom_type) ) + { + circ_internal_nodes_sort(n2->nodes, n2->num_nodes, n1); + for ( i = 0; i < n2->num_nodes; i++ ) + { + d = circ_tree_distance_tree_internal(n1, n2->nodes[i], threshold, min_dist, max_dist, closest1, closest2); + d_min = FP_MIN(d_min, d); + } + } + else if ( ! circ_node_is_leaf(n1) ) + { + circ_internal_nodes_sort(n1->nodes, n1->num_nodes, n2); + for ( i = 0; i < n1->num_nodes; i++ ) + { + d = circ_tree_distance_tree_internal(n1->nodes[i], n2, threshold, min_dist, max_dist, closest1, closest2); + d_min = FP_MIN(d_min, d); + } + } + else if ( ! circ_node_is_leaf(n2) ) + { + circ_internal_nodes_sort(n2->nodes, n2->num_nodes, n1); + for ( i = 0; i < n2->num_nodes; i++ ) + { + d = circ_tree_distance_tree_internal(n1, n2->nodes[i], threshold, min_dist, max_dist, closest1, closest2); + d_min = FP_MIN(d_min, d); + } + } + else + { + /* Never get here */ + } + + return d_min; + } +} + + + + + +void circ_tree_print(const CIRC_NODE* node, int depth) +{ + uint32_t i; + + if (circ_node_is_leaf(node)) + { + printf("%*s[%d] C(%.5g %.5g) R(%.5g) ((%.5g %.5g),(%.5g,%.5g))", + 3*depth + 6, "NODE", node->edge_num, + node->center.lon, node->center.lat, + node->radius, + node->p1->x, node->p1->y, + node->p2->x, node->p2->y + ); + if ( node->geom_type ) + { + printf(" %s", lwtype_name(node->geom_type)); + } + if ( node->geom_type == POLYGONTYPE ) + { + printf(" O(%.5g %.5g)", node->pt_outside.x, node->pt_outside.y); + } + printf("\n"); + + } + else + { + printf("%*s C(%.5g %.5g) R(%.5g)", + 3*depth + 6, "NODE", + node->center.lon, node->center.lat, + node->radius + ); + if ( node->geom_type ) + { + printf(" %s", lwtype_name(node->geom_type)); + } + if ( node->geom_type == POLYGONTYPE ) + { + printf(" O(%.15g %.15g)", node->pt_outside.x, node->pt_outside.y); + } + printf("\n"); + } + for ( i = 0; i < node->num_nodes; i++ ) + { + circ_tree_print(node->nodes[i], depth + 1); + } + return; +} + + +static CIRC_NODE* +lwpoint_calculate_circ_tree(const LWPOINT* lwpoint) +{ + CIRC_NODE* node; + node = circ_tree_new(lwpoint->point); + node->geom_type = lwgeom_get_type((LWGEOM*)lwpoint);; + return node; +} + +static CIRC_NODE* +lwline_calculate_circ_tree(const LWLINE* lwline) +{ + CIRC_NODE* node; + node = circ_tree_new(lwline->points); + node->geom_type = lwgeom_get_type((LWGEOM*)lwline); + return node; +} + +static CIRC_NODE* +lwpoly_calculate_circ_tree(const LWPOLY* lwpoly) +{ + uint32_t i = 0, j = 0; + CIRC_NODE** nodes; + CIRC_NODE* node; + + /* One ring? Handle it like a line. */ + if ( lwpoly->nrings == 1 ) + { + node = circ_tree_new(lwpoly->rings[0]); + } + else + { + /* Calculate a tree for each non-trivial ring of the polygon */ + nodes = lwalloc(lwpoly->nrings * sizeof(CIRC_NODE*)); + for ( i = 0; i < lwpoly->nrings; i++ ) + { + node = circ_tree_new(lwpoly->rings[i]); + if ( node ) + nodes[j++] = node; + } + /* Put the trees into a spatially correlated order */ + circ_nodes_sort(nodes, j); + /* Merge the trees pairwise up to a parent node and return */ + node = circ_nodes_merge(nodes, j); + /* Don't need the working list any more */ + lwfree(nodes); + } + + /* Metadata about polygons, we need this to apply P-i-P tests */ + /* selectively when doing distance calculations */ + node->geom_type = lwgeom_get_type((LWGEOM*)lwpoly); + lwpoly_pt_outside(lwpoly, &(node->pt_outside)); + + return node; +} + +static CIRC_NODE* +lwcollection_calculate_circ_tree(const LWCOLLECTION* lwcol) +{ + uint32_t i = 0, j = 0; + CIRC_NODE** nodes; + CIRC_NODE* node; + + /* One geometry? Done! */ + if ( lwcol->ngeoms == 1 ) + return lwgeom_calculate_circ_tree(lwcol->geoms[0]); + + /* Calculate a tree for each sub-geometry*/ + nodes = lwalloc(lwcol->ngeoms * sizeof(CIRC_NODE*)); + for ( i = 0; i < lwcol->ngeoms; i++ ) + { + node = lwgeom_calculate_circ_tree(lwcol->geoms[i]); + if ( node ) + nodes[j++] = node; + } + /* Put the trees into a spatially correlated order */ + circ_nodes_sort(nodes, j); + /* Merge the trees pairwise up to a parent node and return */ + node = circ_nodes_merge(nodes, j); + /* Don't need the working list any more */ + lwfree(nodes); + node->geom_type = lwgeom_get_type((LWGEOM*)lwcol); + return node; +} + +CIRC_NODE* +lwgeom_calculate_circ_tree(const LWGEOM* lwgeom) +{ + if ( lwgeom_is_empty(lwgeom) ) + return NULL; + + switch ( lwgeom->type ) + { + case POINTTYPE: + return lwpoint_calculate_circ_tree((LWPOINT*)lwgeom); + case LINETYPE: + return lwline_calculate_circ_tree((LWLINE*)lwgeom); + case POLYGONTYPE: + return lwpoly_calculate_circ_tree((LWPOLY*)lwgeom); + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + return lwcollection_calculate_circ_tree((LWCOLLECTION*)lwgeom); + default: + lwerror("Unable to calculate spherical index tree for type %s", lwtype_name(lwgeom->type)); + return NULL; + } + +} + + +/*********************************************************************** + * Closest point and closest line functions for geographies. + ***********************************************************************/ + +LWGEOM * +geography_tree_closestpoint(const LWGEOM* lwgeom1, const LWGEOM* lwgeom2, double threshold) +{ + CIRC_NODE* circ_tree1 = NULL; + CIRC_NODE* circ_tree2 = NULL; + double min_dist = FLT_MAX; + double max_dist = FLT_MAX; + GEOGRAPHIC_POINT closest1, closest2; + LWGEOM *result; + POINT4D p; + + circ_tree1 = lwgeom_calculate_circ_tree(lwgeom1); + circ_tree2 = lwgeom_calculate_circ_tree(lwgeom2); + + /* Quietly decrease the threshold just a little to avoid cases where */ + /* the actual spheroid distance is larger than the sphere distance */ + /* causing the return value to be larger than the threshold value */ + // double threshold_radians = 0.95 * threshold / spheroid->radius; + double threshold_radians = threshold / WGS84_RADIUS; + + circ_tree_distance_tree_internal( + circ_tree1, circ_tree2, threshold_radians, + &min_dist, &max_dist, &closest1, &closest2); + + p.x = rad2deg(closest1.lon); + p.y = rad2deg(closest1.lat); + result = (LWGEOM *)lwpoint_make2d(lwgeom_get_srid(lwgeom1), p.x, p.y); + + circ_tree_free(circ_tree1); + circ_tree_free(circ_tree2); + return result; +} + + +LWGEOM * +geography_tree_shortestline(const LWGEOM* lwgeom1, const LWGEOM* lwgeom2, double threshold, const SPHEROID *spheroid) +{ + CIRC_NODE* circ_tree1 = NULL; + CIRC_NODE* circ_tree2 = NULL; + double min_dist = FLT_MAX; + double max_dist = FLT_MAX; + GEOGRAPHIC_POINT closest1, closest2; + LWGEOM *geoms[2]; + LWGEOM *result; + POINT4D p1, p2; + uint32_t srid = lwgeom1->srid; + + circ_tree1 = lwgeom_calculate_circ_tree(lwgeom1); + circ_tree2 = lwgeom_calculate_circ_tree(lwgeom2); + + /* Quietly decrease the threshold just a little to avoid cases where */ + /* the actual spheroid distance is larger than the sphere distance */ + /* causing the return value to be larger than the threshold value */ + // double threshold_radians = 0.95 * threshold / spheroid->radius; + double threshold_radians = threshold / spheroid->radius; + + circ_tree_distance_tree_internal(circ_tree1, circ_tree2, threshold_radians, + &min_dist, &max_dist, &closest1, &closest2); + + p1.x = rad2deg(closest1.lon); + p1.y = rad2deg(closest1.lat); + p2.x = rad2deg(closest2.lon); + p2.y = rad2deg(closest2.lat); + + geoms[0] = (LWGEOM *)lwpoint_make2d(srid, p1.x, p1.y); + geoms[1] = (LWGEOM *)lwpoint_make2d(srid, p2.x, p2.y); + result = (LWGEOM *)lwline_from_lwgeom_array(srid, 2, geoms); + + lwgeom_free(geoms[0]); + lwgeom_free(geoms[1]); + circ_tree_free(circ_tree1); + circ_tree_free(circ_tree2); + return result; +} diff --git a/mgist-postgis/liblwgeom/lwgeodetic_tree.h b/mgist-postgis/liblwgeom/lwgeodetic_tree.h new file mode 100644 index 0000000..90987a6 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeodetic_tree.h @@ -0,0 +1,65 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2012-2015 Paul Ramsey + * + **********************************************************************/ + + +#ifndef _LWGEODETIC_TREE_H +#define _LWGEODETIC_TREE_H 1 + +#include "lwgeodetic.h" + +#define CIRC_NODE_SIZE 8 + +/** +* Note that p1 and p2 are pointers into an independent POINTARRAY, do not free them. +*/ +typedef struct circ_node +{ + GEOGRAPHIC_POINT center; + double radius; + uint32_t num_nodes; + struct circ_node** nodes; + int edge_num; + uint32_t geom_type; + double d; + POINT2D pt_outside; + POINT2D* p1; + POINT2D* p2; +} CIRC_NODE; + +void circ_tree_print(const CIRC_NODE* node, int depth); +CIRC_NODE* circ_tree_new(const POINTARRAY* pa); +void circ_tree_free(CIRC_NODE* node); +int circ_tree_contains_point(const CIRC_NODE* node, const POINT2D* pt, const POINT2D* pt_outside, int level, int* on_boundary); +double circ_tree_distance_tree(const CIRC_NODE* n1, const CIRC_NODE* n2, const SPHEROID *spheroid, double threshold); +CIRC_NODE* lwgeom_calculate_circ_tree(const LWGEOM* lwgeom); +int circ_tree_get_point(const CIRC_NODE* node, POINT2D* pt); +int circ_tree_get_point_outside(const CIRC_NODE* node, POINT2D* pt); + +LWGEOM * geography_tree_closestpoint(const LWGEOM* g1, const LWGEOM* g2, double threshold); +LWGEOM * geography_tree_shortestline(const LWGEOM* g1, const LWGEOM* g2, double threshold, const SPHEROID *spheroid); + + +#endif /* _LWGEODETIC_TREE_H */ + + diff --git a/mgist-postgis/liblwgeom/lwgeom.c b/mgist-postgis/liblwgeom/lwgeom.c new file mode 100644 index 0000000..6a1ec79 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeom.c @@ -0,0 +1,2727 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2001-2006 Refractions Research Inc. + * Copyright (C) 2017-2018 Daniel Baston + * + **********************************************************************/ + + +#include +#include +#include + +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + +#define out_stack_size 32 + +/** Force Right-hand-rule on LWGEOM polygons **/ +void +lwgeom_force_clockwise(LWGEOM *lwgeom) +{ + LWCOLLECTION *coll; + uint32_t i; + + switch (lwgeom->type) + { + case POLYGONTYPE: + lwpoly_force_clockwise((LWPOLY *)lwgeom); + return; + + case TRIANGLETYPE: + lwtriangle_force_clockwise((LWTRIANGLE *)lwgeom); + return; + + /* Not handle POLYHEDRALSURFACE and TIN + as they are supposed to be well oriented */ + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + coll = (LWCOLLECTION *)lwgeom; + for (i=0; ingeoms; i++) + lwgeom_force_clockwise(coll->geoms[i]); + return; + } +} + +/** Check clockwise orientation on LWGEOM polygons **/ +int +lwgeom_is_clockwise(LWGEOM *lwgeom) +{ + switch (lwgeom->type) + { + case POLYGONTYPE: + return lwpoly_is_clockwise((LWPOLY *)lwgeom); + + case TRIANGLETYPE: + return lwtriangle_is_clockwise((LWTRIANGLE *)lwgeom); + + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + { + uint32_t i; + LWCOLLECTION* coll = (LWCOLLECTION *)lwgeom; + + for (i=0; i < coll->ngeoms; i++) + if (!lwgeom_is_clockwise(coll->geoms[i])) + return LW_FALSE; + return LW_TRUE; + } + default: + return LW_TRUE; + return LW_FALSE; + } +} + +LWGEOM * +lwgeom_reverse(const LWGEOM *geom) +{ + LWGEOM *geomout = lwgeom_clone_deep(geom); + lwgeom_reverse_in_place(geomout); + return geomout; +} + +/** Reverse vertex order of LWGEOM **/ +void +lwgeom_reverse_in_place(LWGEOM *geom) +{ + uint32_t i; + LWCOLLECTION *col; + if (!geom) + return; + + switch (geom->type) + { + case MULTIPOINTTYPE: + case POINTTYPE: + { + return; + } + case TRIANGLETYPE: + case CIRCSTRINGTYPE: + case LINETYPE: + { + LWLINE *line = (LWLINE *)(geom); + ptarray_reverse_in_place(line->points); + return; + } + case POLYGONTYPE: + { + LWPOLY *poly = (LWPOLY *)(geom); + if (!poly->rings) + return; + uint32_t r; + for (r = 0; r < poly->nrings; r++) + ptarray_reverse_in_place(poly->rings[r]); + return; + } + /* CompoundCurve needs to also reverse the sub-geometries */ + /* so that the end-points remain coincident */ + case COMPOUNDTYPE: + { + uint32_t ngeoms; + col = (LWCOLLECTION *)(geom); + if (!col->geoms) + return; + ngeoms = col->ngeoms; + for (i=0; igeoms[i]); + for (i=0; ingeoms/2; i++) { + LWGEOM* tmp = col->geoms[i]; + col->geoms[i] = col->geoms[ngeoms-i-1]; + col->geoms[ngeoms-i-1] = tmp; + } + return; + } + case MULTICURVETYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case MULTISURFACETYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + case CURVEPOLYTYPE: + { + col = (LWCOLLECTION *)(geom); + if (!col->geoms) + return; + for (i=0; ingeoms; i++) + lwgeom_reverse_in_place(col->geoms[i]); + return; + } + default: + { + lwerror("%s: Unknown geometry type: %s", __func__, lwtype_name(geom->type)); + return; + } + + } +} + +LWLINE * +lwgeom_as_lwline(const LWGEOM *lwgeom) +{ + if ( lwgeom == NULL ) return NULL; + if ( lwgeom->type == LINETYPE ) + return (LWLINE *)lwgeom; + else return NULL; +} + +LWCIRCSTRING * +lwgeom_as_lwcircstring(const LWGEOM *lwgeom) +{ + if ( lwgeom == NULL ) return NULL; + if ( lwgeom->type == CIRCSTRINGTYPE ) + return (LWCIRCSTRING *)lwgeom; + else return NULL; +} + +LWCOMPOUND * +lwgeom_as_lwcompound(const LWGEOM *lwgeom) +{ + if ( lwgeom == NULL ) return NULL; + if ( lwgeom->type == COMPOUNDTYPE ) + return (LWCOMPOUND *)lwgeom; + else return NULL; +} + +LWCURVEPOLY * +lwgeom_as_lwcurvepoly(const LWGEOM *lwgeom) +{ + if ( lwgeom == NULL ) return NULL; + if ( lwgeom->type == CURVEPOLYTYPE ) + return (LWCURVEPOLY *)lwgeom; + else return NULL; +} + +LWPOLY * +lwgeom_as_lwpoly(const LWGEOM *lwgeom) +{ + if ( lwgeom == NULL ) return NULL; + if ( lwgeom->type == POLYGONTYPE ) + return (LWPOLY *)lwgeom; + else return NULL; +} + +LWTRIANGLE * +lwgeom_as_lwtriangle(const LWGEOM *lwgeom) +{ + if ( lwgeom == NULL ) return NULL; + if ( lwgeom->type == TRIANGLETYPE ) + return (LWTRIANGLE *)lwgeom; + else return NULL; +} + +LWCOLLECTION * +lwgeom_as_lwcollection(const LWGEOM *lwgeom) +{ + if ( lwgeom == NULL ) return NULL; + if ( lwgeom_is_collection(lwgeom) ) + return (LWCOLLECTION*)lwgeom; + else return NULL; +} + +LWMPOINT * +lwgeom_as_lwmpoint(const LWGEOM *lwgeom) +{ + if ( lwgeom == NULL ) return NULL; + if ( lwgeom->type == MULTIPOINTTYPE ) + return (LWMPOINT *)lwgeom; + else return NULL; +} + +LWMLINE * +lwgeom_as_lwmline(const LWGEOM *lwgeom) +{ + if ( lwgeom == NULL ) return NULL; + if ( lwgeom->type == MULTILINETYPE ) + return (LWMLINE *)lwgeom; + else return NULL; +} + +LWMPOLY * +lwgeom_as_lwmpoly(const LWGEOM *lwgeom) +{ + if ( lwgeom == NULL ) return NULL; + if ( lwgeom->type == MULTIPOLYGONTYPE ) + return (LWMPOLY *)lwgeom; + else return NULL; +} + +LWPSURFACE * +lwgeom_as_lwpsurface(const LWGEOM *lwgeom) +{ + if ( lwgeom->type == POLYHEDRALSURFACETYPE ) + return (LWPSURFACE *)lwgeom; + else return NULL; +} + +LWTIN * +lwgeom_as_lwtin(const LWGEOM *lwgeom) +{ + if ( lwgeom->type == TINTYPE ) + return (LWTIN *)lwgeom; + else return NULL; +} + +LWGEOM *lwtin_as_lwgeom(const LWTIN *obj) +{ + return (LWGEOM *)obj; +} + +LWGEOM *lwpsurface_as_lwgeom(const LWPSURFACE *obj) +{ + return (LWGEOM *)obj; +} + +LWGEOM *lwmpoly_as_lwgeom(const LWMPOLY *obj) +{ + if ( obj == NULL ) return NULL; + return (LWGEOM *)obj; +} +LWGEOM *lwmline_as_lwgeom(const LWMLINE *obj) +{ + if ( obj == NULL ) return NULL; + return (LWGEOM *)obj; +} +LWGEOM *lwmpoint_as_lwgeom(const LWMPOINT *obj) +{ + if ( obj == NULL ) return NULL; + return (LWGEOM *)obj; +} +LWGEOM *lwcollection_as_lwgeom(const LWCOLLECTION *obj) +{ + if ( obj == NULL ) return NULL; + return (LWGEOM *)obj; +} +LWGEOM *lwcircstring_as_lwgeom(const LWCIRCSTRING *obj) +{ + if ( obj == NULL ) return NULL; + return (LWGEOM *)obj; +} +LWGEOM *lwcurvepoly_as_lwgeom(const LWCURVEPOLY *obj) +{ + if ( obj == NULL ) return NULL; + return (LWGEOM *)obj; +} +LWGEOM *lwcompound_as_lwgeom(const LWCOMPOUND *obj) +{ + if ( obj == NULL ) return NULL; + return (LWGEOM *)obj; +} +LWGEOM *lwpoly_as_lwgeom(const LWPOLY *obj) +{ + if ( obj == NULL ) return NULL; + return (LWGEOM *)obj; +} +LWGEOM *lwtriangle_as_lwgeom(const LWTRIANGLE *obj) +{ + if ( obj == NULL ) return NULL; + return (LWGEOM *)obj; +} +LWGEOM *lwline_as_lwgeom(const LWLINE *obj) +{ + if ( obj == NULL ) return NULL; + return (LWGEOM *)obj; +} +LWGEOM *lwpoint_as_lwgeom(const LWPOINT *obj) +{ + if ( obj == NULL ) return NULL; + return (LWGEOM *)obj; +} + + +/** +** Look-up for the correct MULTI* type promotion for singleton types. +*/ +uint8_t MULTITYPE[NUMTYPES] = +{ + 0, + MULTIPOINTTYPE, /* 1 */ + MULTILINETYPE, /* 2 */ + MULTIPOLYGONTYPE, /* 3 */ + 0,0,0,0, + MULTICURVETYPE, /* 8 */ + MULTICURVETYPE, /* 9 */ + MULTISURFACETYPE, /* 10 */ + POLYHEDRALSURFACETYPE, /* 11 */ + 0, 0, + TINTYPE, /* 14 */ + 0 +}; + +uint8_t lwtype_multitype(uint8_t type) +{ + if (type > 15) return 0; + return MULTITYPE[type]; +} + +/** +* Create a new LWGEOM of the appropriate MULTI* type. +*/ +LWGEOM * +lwgeom_as_multi(const LWGEOM *lwgeom) +{ + LWGEOM **ogeoms; + LWGEOM *ogeom = NULL; + GBOX *box = NULL; + int type; + + type = lwgeom->type; + + if ( ! MULTITYPE[type] ) return lwgeom_clone(lwgeom); + + if( lwgeom_is_empty(lwgeom) ) + { + ogeom = (LWGEOM *)lwcollection_construct_empty( + MULTITYPE[type], + lwgeom->srid, + FLAGS_GET_Z(lwgeom->flags), + FLAGS_GET_M(lwgeom->flags) + ); + } + else + { + ogeoms = lwalloc(sizeof(LWGEOM*)); + ogeoms[0] = lwgeom_clone(lwgeom); + + /* Sub-geometries are not allowed to have bboxes or SRIDs, move the bbox to the collection */ + box = ogeoms[0]->bbox; + ogeoms[0]->bbox = NULL; + ogeoms[0]->srid = SRID_UNKNOWN; + + ogeom = (LWGEOM *)lwcollection_construct(MULTITYPE[type], lwgeom->srid, box, 1, ogeoms); + } + + return ogeom; +} + +/** +* Create a new LWGEOM of the appropriate CURVE* type. +*/ +LWGEOM * +lwgeom_as_curve(const LWGEOM *lwgeom) +{ + LWGEOM *ogeom; + int type = lwgeom->type; + /* + int hasz = FLAGS_GET_Z(lwgeom->flags); + int hasm = FLAGS_GET_M(lwgeom->flags); + int32_t srid = lwgeom->srid; + */ + + switch(type) + { + case LINETYPE: + /* turn to COMPOUNDCURVE */ + ogeom = (LWGEOM*)lwcompound_construct_from_lwline((LWLINE*)lwgeom); + break; + case POLYGONTYPE: + ogeom = (LWGEOM*)lwcurvepoly_construct_from_lwpoly(lwgeom_as_lwpoly(lwgeom)); + break; + case MULTILINETYPE: + /* turn to MULTICURVE */ + ogeom = lwgeom_clone(lwgeom); + ogeom->type = MULTICURVETYPE; + break; + case MULTIPOLYGONTYPE: + /* turn to MULTISURFACE */ + ogeom = lwgeom_clone(lwgeom); + ogeom->type = MULTISURFACETYPE; + break; + case COLLECTIONTYPE: + default: + ogeom = lwgeom_clone(lwgeom); + break; + } + + /* TODO: copy bbox from input geom ? */ + + return ogeom; +} + + +/** +* Free the containing LWGEOM and the associated BOX. Leave the underlying +* geoms/points/point objects intact. Useful for functions that are stripping +* out subcomponents of complex objects, or building up new temporary objects +* on top of subcomponents. +*/ +void +lwgeom_release(LWGEOM *lwgeom) +{ + if ( ! lwgeom ) + lwerror("lwgeom_release: someone called on 0x0"); + + LWDEBUGF(3, "releasing type %s", lwtype_name(lwgeom->type)); + + /* Drop bounding box (always a copy) */ + if ( lwgeom->bbox ) + { + LWDEBUGF(3, "lwgeom_release: releasing bbox. %p", lwgeom->bbox); + lwfree(lwgeom->bbox); + } + lwfree(lwgeom); + +} + + +/* @brief Clone LWGEOM object. Serialized point lists are not copied. + * + * @see ptarray_clone + */ +LWGEOM * +lwgeom_clone(const LWGEOM *lwgeom) +{ + LWDEBUGF(2, "lwgeom_clone called with %p, %s", + lwgeom, lwtype_name(lwgeom->type)); + + switch (lwgeom->type) + { + case POINTTYPE: + return (LWGEOM *)lwpoint_clone((LWPOINT *)lwgeom); + case LINETYPE: + return (LWGEOM *)lwline_clone((LWLINE *)lwgeom); + case CIRCSTRINGTYPE: + return (LWGEOM *)lwcircstring_clone((LWCIRCSTRING *)lwgeom); + case POLYGONTYPE: + return (LWGEOM *)lwpoly_clone((LWPOLY *)lwgeom); + case TRIANGLETYPE: + return (LWGEOM *)lwtriangle_clone((LWTRIANGLE *)lwgeom); + case COMPOUNDTYPE: + case CURVEPOLYTYPE: + case MULTICURVETYPE: + case MULTISURFACETYPE: + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + return (LWGEOM *)lwcollection_clone((LWCOLLECTION *)lwgeom); + default: + lwerror("lwgeom_clone: Unknown geometry type: %s", lwtype_name(lwgeom->type)); + return NULL; + } +} + +/** +* Deep-clone an #LWGEOM object. #POINTARRAY are copied. +*/ +LWGEOM * +lwgeom_clone_deep(const LWGEOM *lwgeom) +{ + LWDEBUGF(2, "lwgeom_clone called with %p, %s", + lwgeom, lwtype_name(lwgeom->type)); + + switch (lwgeom->type) + { + case POINTTYPE: + case LINETYPE: + case CIRCSTRINGTYPE: + case TRIANGLETYPE: + return (LWGEOM *)lwline_clone_deep((LWLINE *)lwgeom); + case POLYGONTYPE: + return (LWGEOM *)lwpoly_clone_deep((LWPOLY *)lwgeom); + case COMPOUNDTYPE: + case CURVEPOLYTYPE: + case MULTICURVETYPE: + case MULTISURFACETYPE: + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + return (LWGEOM *)lwcollection_clone_deep((LWCOLLECTION *)lwgeom); + default: + lwerror("lwgeom_clone_deep: Unknown geometry type: %s", lwtype_name(lwgeom->type)); + return NULL; + } +} + + +/** + * Return an alloced string + */ +char* +lwgeom_to_ewkt(const LWGEOM *lwgeom) +{ + char* wkt = NULL; + size_t wkt_size = 0; + + wkt = lwgeom_to_wkt(lwgeom, WKT_EXTENDED, 12, &wkt_size); + + if ( ! wkt ) + { + lwerror("Error writing geom %p to WKT", lwgeom); + } + + return wkt; +} + +/** + * @brief geom1 same as geom2 + * iff + * + have same type + * + have same # objects + * + have same bvol + * + each object in geom1 has a corresponding object in geom2 (see above) + * @param lwgeom1 + * @param lwgeom2 + */ +char +lwgeom_same(const LWGEOM *lwgeom1, const LWGEOM *lwgeom2) +{ + LWDEBUGF(2, "lwgeom_same(%s, %s) called", + lwtype_name(lwgeom1->type), + lwtype_name(lwgeom2->type)); + + if ( lwgeom1->type != lwgeom2->type ) + { + LWDEBUG(3, " type differ"); + + return LW_FALSE; + } + + if ( FLAGS_GET_ZM(lwgeom1->flags) != FLAGS_GET_ZM(lwgeom2->flags) ) + { + LWDEBUG(3, " ZM flags differ"); + + return LW_FALSE; + } + + /* Check boxes if both already computed */ + if ( lwgeom1->bbox && lwgeom2->bbox ) + { + /*lwnotice("bbox1:%p, bbox2:%p", lwgeom1->bbox, lwgeom2->bbox);*/ + if ( ! gbox_same(lwgeom1->bbox, lwgeom2->bbox) ) + { + LWDEBUG(3, " bounding boxes differ"); + + return LW_FALSE; + } + } + + /* geoms have same type, invoke type-specific function */ + switch (lwgeom1->type) + { + case POINTTYPE: + return lwpoint_same((LWPOINT *)lwgeom1, + (LWPOINT *)lwgeom2); + case LINETYPE: + return lwline_same((LWLINE *)lwgeom1, + (LWLINE *)lwgeom2); + case POLYGONTYPE: + return lwpoly_same((LWPOLY *)lwgeom1, + (LWPOLY *)lwgeom2); + case TRIANGLETYPE: + return lwtriangle_same((LWTRIANGLE *)lwgeom1, + (LWTRIANGLE *)lwgeom2); + case CIRCSTRINGTYPE: + return lwcircstring_same((LWCIRCSTRING *)lwgeom1, + (LWCIRCSTRING *)lwgeom2); + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case MULTICURVETYPE: + case MULTISURFACETYPE: + case COMPOUNDTYPE: + case CURVEPOLYTYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + return lwcollection_same((LWCOLLECTION *)lwgeom1, + (LWCOLLECTION *)lwgeom2); + default: + lwerror("lwgeom_same: unsupported geometry type: %s", + lwtype_name(lwgeom1->type)); + return LW_FALSE; + } + +} + +int +lwpoint_inside_circle(const LWPOINT *p, double cx, double cy, double rad) +{ + const POINT2D *pt; + POINT2D center; + + if ( ! p || ! p->point ) + return LW_FALSE; + + pt = getPoint2d_cp(p->point, 0); + + center.x = cx; + center.y = cy; + + if ( distance2d_pt_pt(pt, ¢er) < rad ) + return LW_TRUE; + + return LW_FALSE; +} + +void +lwgeom_drop_bbox(LWGEOM *lwgeom) +{ + if ( lwgeom->bbox ) lwfree(lwgeom->bbox); + lwgeom->bbox = NULL; + FLAGS_SET_BBOX(lwgeom->flags, 0); +} + +/** + * Ensure there's a box in the LWGEOM. + * If the box is already there just return, + * else compute it. + */ +void +lwgeom_add_bbox(LWGEOM *lwgeom) +{ + /* an empty LWGEOM has no bbox */ + if ( lwgeom_is_empty(lwgeom) ) return; + + if ( lwgeom->bbox ) return; + FLAGS_SET_BBOX(lwgeom->flags, 1); + lwgeom->bbox = gbox_new(lwgeom->flags); + lwgeom_calculate_gbox(lwgeom, lwgeom->bbox); +} + +void +lwgeom_refresh_bbox(LWGEOM *lwgeom) +{ + lwgeom_drop_bbox(lwgeom); + lwgeom_add_bbox(lwgeom); +} + +void +lwgeom_add_bbox_deep(LWGEOM *lwgeom, GBOX *gbox) +{ + if ( lwgeom_is_empty(lwgeom) ) return; + + FLAGS_SET_BBOX(lwgeom->flags, 1); + + if ( ! ( gbox || lwgeom->bbox ) ) + { + lwgeom->bbox = gbox_new(lwgeom->flags); + lwgeom_calculate_gbox(lwgeom, lwgeom->bbox); + } + else if ( gbox && ! lwgeom->bbox ) + { + lwgeom->bbox = gbox_clone(gbox); + } + + if ( lwgeom_is_collection(lwgeom) ) + { + uint32_t i; + LWCOLLECTION *lwcol = (LWCOLLECTION*)lwgeom; + + for ( i = 0; i < lwcol->ngeoms; i++ ) + { + lwgeom_add_bbox_deep(lwcol->geoms[i], lwgeom->bbox); + } + } +} + +const GBOX * +lwgeom_get_bbox(const LWGEOM *lwg) +{ + /* add it if not already there */ + lwgeom_add_bbox((LWGEOM *)lwg); + return lwg->bbox; +} + + +/** +* Calculate the gbox for this geometry, a cartesian box or +* geodetic box, depending on how it is flagged. +*/ +int lwgeom_calculate_gbox(const LWGEOM *lwgeom, GBOX *gbox) +{ + gbox->flags = lwgeom->flags; + if( FLAGS_GET_GEODETIC(lwgeom->flags) ) + return lwgeom_calculate_gbox_geodetic(lwgeom, gbox); + else + return lwgeom_calculate_gbox_cartesian(lwgeom, gbox); +} + +void +lwgeom_drop_srid(LWGEOM *lwgeom) +{ + lwgeom->srid = SRID_UNKNOWN; /* TODO: To be changed to SRID_UNKNOWN */ +} + +LWGEOM * +lwgeom_segmentize2d(const LWGEOM *lwgeom, double dist) +{ + switch (lwgeom->type) + { + case LINETYPE: + return (LWGEOM *)lwline_segmentize2d((LWLINE *)lwgeom, + dist); + case POLYGONTYPE: + return (LWGEOM *)lwpoly_segmentize2d((LWPOLY *)lwgeom, + dist); + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + return (LWGEOM *)lwcollection_segmentize2d( + (LWCOLLECTION *)lwgeom, dist); + + default: + return lwgeom_clone(lwgeom); + } +} + +LWGEOM* +lwgeom_force_2d(const LWGEOM *geom) +{ + return lwgeom_force_dims(geom, 0, 0, 0, 0); +} + +LWGEOM* +lwgeom_force_3dz(const LWGEOM *geom, double zval) +{ + return lwgeom_force_dims(geom, 1, 0, zval, 0); +} + +LWGEOM* +lwgeom_force_3dm(const LWGEOM *geom, double mval) +{ + return lwgeom_force_dims(geom, 0, 1, 0, mval); +} + +LWGEOM* +lwgeom_force_4d(const LWGEOM *geom, double zval, double mval) +{ + return lwgeom_force_dims(geom, 1, 1, zval, mval); +} + +LWGEOM* +lwgeom_force_dims(const LWGEOM *geom, int hasz, int hasm, double zval, double mval) +{ + if (!geom) + return NULL; + switch(geom->type) + { + case POINTTYPE: + return lwpoint_as_lwgeom(lwpoint_force_dims((LWPOINT*)geom, hasz, hasm, zval, mval)); + case CIRCSTRINGTYPE: + case LINETYPE: + case TRIANGLETYPE: + return lwline_as_lwgeom(lwline_force_dims((LWLINE*)geom, hasz, hasm, zval, mval)); + case POLYGONTYPE: + return lwpoly_as_lwgeom(lwpoly_force_dims((LWPOLY*)geom, hasz, hasm, zval, mval)); + case COMPOUNDTYPE: + case CURVEPOLYTYPE: + case MULTICURVETYPE: + case MULTISURFACETYPE: + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + return lwcollection_as_lwgeom(lwcollection_force_dims((LWCOLLECTION*)geom, hasz, hasm, zval, mval)); + default: + lwerror("lwgeom_force_2d: unsupported geom type: %s", lwtype_name(geom->type)); + return NULL; + } +} + +LWGEOM* +lwgeom_force_sfs(LWGEOM *geom, int version) +{ + LWCOLLECTION *col; + uint32_t i; + LWGEOM *g; + + /* SFS 1.2 version */ + if (version == 120) + { + switch(geom->type) + { + /* SQL/MM types */ + case CIRCSTRINGTYPE: + case COMPOUNDTYPE: + case CURVEPOLYTYPE: + case MULTICURVETYPE: + case MULTISURFACETYPE: + return lwgeom_stroke(geom, 32); + + case COLLECTIONTYPE: + col = (LWCOLLECTION*)geom; + for ( i = 0; i < col->ngeoms; i++ ) + col->geoms[i] = lwgeom_force_sfs((LWGEOM*)col->geoms[i], version); + + return lwcollection_as_lwgeom((LWCOLLECTION*)geom); + + default: + return (LWGEOM *)geom; + } + } + + + /* SFS 1.1 version */ + switch(geom->type) + { + /* SQL/MM types */ + case CIRCSTRINGTYPE: + case COMPOUNDTYPE: + case CURVEPOLYTYPE: + case MULTICURVETYPE: + case MULTISURFACETYPE: + return lwgeom_stroke(geom, 32); + + /* SFS 1.2 types */ + case TRIANGLETYPE: + g = lwpoly_as_lwgeom(lwpoly_from_lwlines((LWLINE*)geom, 0, NULL)); + lwgeom_free(geom); + return g; + + case TINTYPE: + col = (LWCOLLECTION*) geom; + for ( i = 0; i < col->ngeoms; i++ ) + { + g = lwpoly_as_lwgeom(lwpoly_from_lwlines((LWLINE*)col->geoms[i], 0, NULL)); + lwgeom_free(col->geoms[i]); + col->geoms[i] = g; + } + col->type = COLLECTIONTYPE; + return lwmpoly_as_lwgeom((LWMPOLY*)geom); + + case POLYHEDRALSURFACETYPE: + geom->type = COLLECTIONTYPE; + return (LWGEOM *)geom; + + /* Collection */ + case COLLECTIONTYPE: + col = (LWCOLLECTION*)geom; + for ( i = 0; i < col->ngeoms; i++ ) + col->geoms[i] = lwgeom_force_sfs((LWGEOM*)col->geoms[i], version); + + return lwcollection_as_lwgeom((LWCOLLECTION*)geom); + + default: + return (LWGEOM *)geom; + } +} + +int32_t +lwgeom_get_srid(const LWGEOM *geom) +{ + if ( ! geom ) return SRID_UNKNOWN; + return geom->srid; +} + +int +lwgeom_has_z(const LWGEOM *geom) +{ + if ( ! geom ) return LW_FALSE; + return FLAGS_GET_Z(geom->flags); +} + +int +lwgeom_has_m(const LWGEOM *geom) +{ + if ( ! geom ) return LW_FALSE; + return FLAGS_GET_M(geom->flags); +} + +int +lwgeom_is_solid(const LWGEOM *geom) +{ + if ( ! geom ) return LW_FALSE; + return FLAGS_GET_GEODETIC(geom->flags); +} + +int +lwgeom_ndims(const LWGEOM *geom) +{ + if ( ! geom ) return 0; + return FLAGS_NDIMS(geom->flags); +} + + + +void +lwgeom_set_geodetic(LWGEOM *geom, int value) +{ + LWPOINT *pt; + LWLINE *ln; + LWPOLY *ply; + LWCOLLECTION *col; + uint32_t i; + + FLAGS_SET_GEODETIC(geom->flags, value); + if ( geom->bbox ) + FLAGS_SET_GEODETIC(geom->bbox->flags, value); + + switch(geom->type) + { + case POINTTYPE: + pt = (LWPOINT*)geom; + if ( pt->point ) + FLAGS_SET_GEODETIC(pt->point->flags, value); + break; + case LINETYPE: + ln = (LWLINE*)geom; + if ( ln->points ) + FLAGS_SET_GEODETIC(ln->points->flags, value); + break; + case POLYGONTYPE: + ply = (LWPOLY*)geom; + for ( i = 0; i < ply->nrings; i++ ) + FLAGS_SET_GEODETIC(ply->rings[i]->flags, value); + break; + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + col = (LWCOLLECTION*)geom; + for ( i = 0; i < col->ngeoms; i++ ) + lwgeom_set_geodetic(col->geoms[i], value); + break; + default: + lwerror("lwgeom_set_geodetic: unsupported geom type: %s", lwtype_name(geom->type)); + return; + } +} + +void +lwgeom_longitude_shift(LWGEOM *lwgeom) +{ + uint32_t i; + switch (lwgeom->type) + { + LWPOINT *point; + LWLINE *line; + LWPOLY *poly; + LWTRIANGLE *triangle; + LWCOLLECTION *coll; + + case POINTTYPE: + point = (LWPOINT *)lwgeom; + ptarray_longitude_shift(point->point); + return; + case LINETYPE: + line = (LWLINE *)lwgeom; + ptarray_longitude_shift(line->points); + return; + case POLYGONTYPE: + poly = (LWPOLY *)lwgeom; + for (i=0; inrings; i++) + ptarray_longitude_shift(poly->rings[i]); + return; + case TRIANGLETYPE: + triangle = (LWTRIANGLE *)lwgeom; + ptarray_longitude_shift(triangle->points); + return; + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + coll = (LWCOLLECTION *)lwgeom; + for (i=0; ingeoms; i++) + lwgeom_longitude_shift(coll->geoms[i]); + return; + default: + lwerror("lwgeom_longitude_shift: unsupported geom type: %s", + lwtype_name(lwgeom->type)); + } +} + +int +lwgeom_is_closed(const LWGEOM *geom) +{ + int type = geom->type; + + if( lwgeom_is_empty(geom) ) + return LW_FALSE; + + /* Test linear types for closure */ + switch (type) + { + case LINETYPE: + return lwline_is_closed((LWLINE*)geom); + case POLYGONTYPE: + return lwpoly_is_closed((LWPOLY*)geom); + case CIRCSTRINGTYPE: + return lwcircstring_is_closed((LWCIRCSTRING*)geom); + case COMPOUNDTYPE: + return lwcompound_is_closed((LWCOMPOUND*)geom); + case TINTYPE: + return lwtin_is_closed((LWTIN*)geom); + case POLYHEDRALSURFACETYPE: + return lwpsurface_is_closed((LWPSURFACE*)geom); + } + + /* Recurse into collections and see if anything is not closed */ + if ( lwgeom_is_collection(geom) ) + { + LWCOLLECTION *col = lwgeom_as_lwcollection(geom); + uint32_t i; + int closed; + for ( i = 0; i < col->ngeoms; i++ ) + { + closed = lwgeom_is_closed(col->geoms[i]); + if ( ! closed ) + return LW_FALSE; + } + return LW_TRUE; + } + + /* All non-linear non-collection types we will call closed */ + return LW_TRUE; +} + +int +lwgeom_is_collection(const LWGEOM *geom) +{ + if( ! geom ) return LW_FALSE; + return lwtype_is_collection(geom->type); +} + +/** Return TRUE if the geometry may contain sub-geometries, i.e. it is a MULTI* or COMPOUNDCURVE */ +int +lwtype_is_collection(uint8_t type) +{ + switch (type) + { + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + case CURVEPOLYTYPE: + case COMPOUNDTYPE: + case MULTICURVETYPE: + case MULTISURFACETYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + return LW_TRUE; + break; + + default: + return LW_FALSE; + } +} + +/** +* Given an lwtype number, what homogeneous collection can hold it? +*/ +uint32_t +lwtype_get_collectiontype(uint8_t type) +{ + switch (type) + { + case POINTTYPE: + return MULTIPOINTTYPE; + case LINETYPE: + return MULTILINETYPE; + case POLYGONTYPE: + return MULTIPOLYGONTYPE; + case CIRCSTRINGTYPE: + return MULTICURVETYPE; + case COMPOUNDTYPE: + return MULTICURVETYPE; + case CURVEPOLYTYPE: + return MULTISURFACETYPE; + case TRIANGLETYPE: + return TINTYPE; + default: + return COLLECTIONTYPE; + } +} + + +void lwgeom_free(LWGEOM *lwgeom) +{ + + /* There's nothing here to free... */ + if( ! lwgeom ) return; + + LWDEBUGF(5,"freeing a %s",lwtype_name(lwgeom->type)); + + switch (lwgeom->type) + { + case POINTTYPE: + lwpoint_free((LWPOINT *)lwgeom); + break; + case LINETYPE: + lwline_free((LWLINE *)lwgeom); + break; + case POLYGONTYPE: + lwpoly_free((LWPOLY *)lwgeom); + break; + case CIRCSTRINGTYPE: + lwcircstring_free((LWCIRCSTRING *)lwgeom); + break; + case TRIANGLETYPE: + lwtriangle_free((LWTRIANGLE *)lwgeom); + break; + case MULTIPOINTTYPE: + lwmpoint_free((LWMPOINT *)lwgeom); + break; + case MULTILINETYPE: + lwmline_free((LWMLINE *)lwgeom); + break; + case MULTIPOLYGONTYPE: + lwmpoly_free((LWMPOLY *)lwgeom); + break; + case POLYHEDRALSURFACETYPE: + lwpsurface_free((LWPSURFACE *)lwgeom); + break; + case TINTYPE: + lwtin_free((LWTIN *)lwgeom); + break; + case CURVEPOLYTYPE: + case COMPOUNDTYPE: + case MULTICURVETYPE: + case MULTISURFACETYPE: + case COLLECTIONTYPE: + lwcollection_free((LWCOLLECTION *)lwgeom); + break; + default: + lwerror("lwgeom_free called with unknown type (%d) %s", lwgeom->type, lwtype_name(lwgeom->type)); + } + return; +} + +int lwgeom_needs_bbox(const LWGEOM *geom) +{ + assert(geom); + if ( geom->type == POINTTYPE ) + { + return LW_FALSE; + } + else if ( geom->type == LINETYPE ) + { + if ( lwgeom_count_vertices(geom) <= 2 ) + return LW_FALSE; + else + return LW_TRUE; + } + else if ( geom->type == MULTIPOINTTYPE ) + { + if ( ((LWCOLLECTION*)geom)->ngeoms == 1 ) + return LW_FALSE; + else + return LW_TRUE; + } + else if ( geom->type == MULTILINETYPE ) + { + if ( ((LWCOLLECTION*)geom)->ngeoms == 1 && lwgeom_count_vertices(geom) <= 2 ) + return LW_FALSE; + else + return LW_TRUE; + } + else + { + return LW_TRUE; + } +} + +/** +* Count points in an #LWGEOM. +* TODO: Make sure the internal functions don't overflow +*/ +uint32_t lwgeom_count_vertices(const LWGEOM *geom) +{ + int result = 0; + + /* Null? Zero. */ + if( ! geom ) return 0; + + LWDEBUGF(4, "lwgeom_count_vertices got type %s", + lwtype_name(geom->type)); + + /* Empty? Zero. */ + if( lwgeom_is_empty(geom) ) return 0; + + switch (geom->type) + { + case POINTTYPE: + result = 1; + break; + case TRIANGLETYPE: + case CIRCSTRINGTYPE: + case LINETYPE: + result = lwline_count_vertices((const LWLINE *)geom); + break; + case POLYGONTYPE: + result = lwpoly_count_vertices((const LWPOLY *)geom); + break; + case COMPOUNDTYPE: + case CURVEPOLYTYPE: + case MULTICURVETYPE: + case MULTISURFACETYPE: + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + result = lwcollection_count_vertices((const LWCOLLECTION *)geom); + break; + default: + lwerror("%s: unsupported input geometry type: %s", + __func__, lwtype_name(geom->type)); + break; + } + LWDEBUGF(3, "counted %d vertices", result); + return result; +} + +/** +* For an #LWGEOM, returns 0 for points, 1 for lines, +* 2 for polygons, 3 for volume, and the max dimension +* of a collection. +*/ +int lwgeom_dimension(const LWGEOM *geom) +{ + + /* Null? Zero. */ + if( ! geom ) return -1; + + LWDEBUGF(4, "lwgeom_dimension got type %s", + lwtype_name(geom->type)); + + /* Empty? Zero. */ + /* if( lwgeom_is_empty(geom) ) return 0; */ + + switch (geom->type) + { + case POINTTYPE: + case MULTIPOINTTYPE: + return 0; + case CIRCSTRINGTYPE: + case LINETYPE: + case COMPOUNDTYPE: + case MULTICURVETYPE: + case MULTILINETYPE: + return 1; + case TRIANGLETYPE: + case POLYGONTYPE: + case CURVEPOLYTYPE: + case MULTISURFACETYPE: + case MULTIPOLYGONTYPE: + case TINTYPE: + return 2; + case POLYHEDRALSURFACETYPE: + { + /* A closed polyhedral surface contains a volume. */ + int closed = lwpsurface_is_closed((LWPSURFACE*)geom); + return ( closed ? 3 : 2 ); + } + case COLLECTIONTYPE: + { + int maxdim = 0; + uint32_t i; + LWCOLLECTION *col = (LWCOLLECTION*)geom; + for( i = 0; i < col->ngeoms; i++ ) + { + int dim = lwgeom_dimension(col->geoms[i]); + maxdim = ( dim > maxdim ? dim : maxdim ); + } + return maxdim; + } + default: + lwerror("%s: unsupported input geometry type: %s", + __func__, lwtype_name(geom->type)); + } + return -1; +} + +/** +* Count rings in an #LWGEOM. +*/ +uint32_t lwgeom_count_rings(const LWGEOM *geom) +{ + int result = 0; + + /* Null? Empty? Zero. */ + if( ! geom || lwgeom_is_empty(geom) ) + return 0; + + switch (geom->type) + { + case POINTTYPE: + case CIRCSTRINGTYPE: + case COMPOUNDTYPE: + case MULTICURVETYPE: + case MULTIPOINTTYPE: + case MULTILINETYPE: + case LINETYPE: + result = 0; + break; + case TRIANGLETYPE: + result = 1; + break; + case POLYGONTYPE: + result = ((LWPOLY *)geom)->nrings; + break; + case CURVEPOLYTYPE: + result = ((LWCURVEPOLY *)geom)->nrings; + break; + case MULTISURFACETYPE: + case MULTIPOLYGONTYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + { + LWCOLLECTION *col = (LWCOLLECTION*)geom; + uint32_t i = 0; + for( i = 0; i < col->ngeoms; i++ ) + result += lwgeom_count_rings(col->geoms[i]); + break; + } + default: + lwerror("lwgeom_count_rings: unsupported input geometry type: %s", lwtype_name(geom->type)); + break; + } + LWDEBUGF(3, "counted %d rings", result); + return result; +} + +int lwgeom_has_srid(const LWGEOM *geom) +{ + if ( geom->srid != SRID_UNKNOWN ) + return LW_TRUE; + + return LW_FALSE; +} + + +static int lwcollection_dimensionality(const LWCOLLECTION *col) +{ + uint32_t i; + int dimensionality = 0; + for ( i = 0; i < col->ngeoms; i++ ) + { + int d = lwgeom_dimensionality(col->geoms[i]); + if ( d > dimensionality ) + dimensionality = d; + } + return dimensionality; +} + +extern int lwgeom_dimensionality(const LWGEOM *geom) +{ + int dim; + + LWDEBUGF(3, "lwgeom_dimensionality got type %s", + lwtype_name(geom->type)); + + switch (geom->type) + { + case POINTTYPE: + case MULTIPOINTTYPE: + return 0; + break; + case LINETYPE: + case CIRCSTRINGTYPE: + case MULTILINETYPE: + case COMPOUNDTYPE: + case MULTICURVETYPE: + return 1; + break; + case POLYGONTYPE: + case TRIANGLETYPE: + case CURVEPOLYTYPE: + case MULTIPOLYGONTYPE: + case MULTISURFACETYPE: + return 2; + break; + + case POLYHEDRALSURFACETYPE: + case TINTYPE: + dim = lwgeom_is_closed(geom)?3:2; + return dim; + break; + + case COLLECTIONTYPE: + return lwcollection_dimensionality((const LWCOLLECTION *)geom); + break; + default: + lwerror("lwgeom_dimensionality: unsupported input geometry type: %s", + lwtype_name(geom->type)); + break; + } + return 0; +} + +extern LWGEOM* lwgeom_remove_repeated_points(const LWGEOM *in, double tolerance) +{ + LWGEOM *out = lwgeom_clone_deep(in); + lwgeom_remove_repeated_points_in_place(out, tolerance); + return out; +} + +void lwgeom_swap_ordinates(LWGEOM *in, LWORD o1, LWORD o2) +{ + LWCOLLECTION *col; + LWPOLY *poly; + uint32_t i; + + if ( (!in) || lwgeom_is_empty(in) ) return; + + /* TODO: check for lwgeom NOT having the specified dimension ? */ + + LWDEBUGF(4, "lwgeom_flip_coordinates, got type: %s", + lwtype_name(in->type)); + + switch (in->type) + { + case POINTTYPE: + ptarray_swap_ordinates(lwgeom_as_lwpoint(in)->point, o1, o2); + break; + + case LINETYPE: + ptarray_swap_ordinates(lwgeom_as_lwline(in)->points, o1, o2); + break; + + case CIRCSTRINGTYPE: + ptarray_swap_ordinates(lwgeom_as_lwcircstring(in)->points, o1, o2); + break; + + case POLYGONTYPE: + poly = (LWPOLY *) in; + for (i=0; inrings; i++) + { + ptarray_swap_ordinates(poly->rings[i], o1, o2); + } + break; + + case TRIANGLETYPE: + ptarray_swap_ordinates(lwgeom_as_lwtriangle(in)->points, o1, o2); + break; + + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + case COMPOUNDTYPE: + case CURVEPOLYTYPE: + case MULTISURFACETYPE: + case MULTICURVETYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + col = (LWCOLLECTION *) in; + for (i=0; ingeoms; i++) + { + lwgeom_swap_ordinates(col->geoms[i], o1, o2); + } + break; + + default: + lwerror("lwgeom_swap_ordinates: unsupported geometry type: %s", + lwtype_name(in->type)); + return; + } + + /* only refresh bbox if X or Y changed */ + if ( in->bbox && (o1 < 2 || o2 < 2) ) + { + lwgeom_refresh_bbox(in); + } +} + +void lwgeom_set_srid(LWGEOM *geom, int32_t srid) +{ + uint32_t i; + + LWDEBUGF(4,"entered with srid=%d",srid); + + geom->srid = srid; + + if ( lwgeom_is_collection(geom) ) + { + /* All the children are set to the same SRID value */ + LWCOLLECTION *col = lwgeom_as_lwcollection(geom); + for ( i = 0; i < col->ngeoms; i++ ) + { + lwgeom_set_srid(col->geoms[i], srid); + } + } +} + + +/**************************************************************/ + +static int +cmp_point_x(const void *pa, const void *pb) +{ + LWPOINT *p1 = *((LWPOINT **)pa); + LWPOINT *p2 = *((LWPOINT **)pb); + + const POINT2D *pt1 = getPoint2d_cp(p1->point, 0); + const POINT2D *pt2 = getPoint2d_cp(p2->point, 0); + + if (pt1 && pt2) + return (pt1->x > pt2->x) ? 1 : ((pt1->x < pt2->x) ? -1 : 0); + + if (pt1) return -1; + if (pt2) return 1; + return 0; +} + +static int +cmp_point_y(const void *pa, const void *pb) +{ + LWPOINT *p1 = *((LWPOINT **)pa); + LWPOINT *p2 = *((LWPOINT **)pb); + + const POINT2D *pt1 = getPoint2d_cp(p1->point, 0); + const POINT2D *pt2 = getPoint2d_cp(p2->point, 0); + + if (pt1 && pt2) + return (pt1->y > pt2->y) ? 1 : ((pt1->y < pt2->y) ? -1 : 0); + + if (pt1) return -1; + if (pt2) return 1; + return 0; +} + +int +lwgeom_remove_repeated_points_in_place(LWGEOM *geom, double tolerance) +{ + int geometry_modified = LW_FALSE; + switch (geom->type) + { + /* No-op! Cannot remove points */ + case POINTTYPE: + case TRIANGLETYPE: + return geometry_modified; + case LINETYPE: { + LWLINE *g = (LWLINE *)(geom); + POINTARRAY *pa = g->points; + uint32_t npoints = pa->npoints; + ptarray_remove_repeated_points_in_place(pa, tolerance, 2); + geometry_modified = npoints != pa->npoints; + /* Invalid input, discard the collapsed line */ + if (pa->npoints < 2) + { + pa->npoints = 0; + geometry_modified = LW_TRUE; + } + break; + } + case POLYGONTYPE: { + uint32_t j = 0; + LWPOLY *g = (LWPOLY *)(geom); + for (uint32_t i = 0; i < g->nrings; i++) + { + POINTARRAY *pa = g->rings[i]; + uint32_t npoints = pa->npoints; + ptarray_remove_repeated_points_in_place(pa, tolerance, 4); + geometry_modified |= npoints != pa->npoints; + /* Drop collapsed rings */ + if (pa->npoints < 4) + { + geometry_modified = LW_TRUE; + ptarray_free(pa); + continue; + } + g->rings[j++] = pa; + } + /* Update ring count */ + g->nrings = j; + break; + } + case MULTIPOINTTYPE: { + double tolsq = tolerance * tolerance; + LWMPOINT *mpt = (LWMPOINT *)geom; + + for (uint8_t dim = 0; dim < 2; dim++) + { + /* sort by y, then by x - this way the result is sorted by x */ + qsort(mpt->geoms, mpt->ngeoms, sizeof(LWPOINT *), dim ? cmp_point_x : cmp_point_y); + for (uint32_t i = 0; i < mpt->ngeoms; i++) + { + if (!mpt->geoms[i]) + continue; + + const POINT2D *pti = getPoint2d_cp(mpt->geoms[i]->point, 0); + if (!pti) continue; + + /* check upcoming points if they're within tolerance of current one */ + for (uint32_t j = i + 1; j < mpt->ngeoms; j++) + { + if (!mpt->geoms[j]) + continue; + + const POINT2D *ptj = getPoint2d_cp(mpt->geoms[j]->point, 0); + if (!ptj) continue; + + /* check that the point is in the strip of tolerance around the point */ + if ((dim ? ptj->x - pti->x : ptj->y - pti->y) > tolerance) + break; + + /* remove any upcoming point that is within tolerance circle */ + if (distance2d_sqr_pt_pt(pti, ptj) <= tolsq) + { + lwpoint_free(mpt->geoms[j]); + mpt->geoms[j] = NULL; + geometry_modified = LW_TRUE; + } + } + } + + /* null out empties */ + for (uint32_t j = 0; j < mpt->ngeoms; j++) + { + if (mpt->geoms[j] && lwpoint_is_empty(mpt->geoms[j])) + { + lwpoint_free(mpt->geoms[j]); + mpt->geoms[j] = NULL; + geometry_modified = LW_TRUE; + } + } + + /* compactify array of points */ + uint32_t i = 0; + for (uint32_t j = 0; j < mpt->ngeoms; j++) + if (mpt->geoms[j]) + mpt->geoms[i++] = mpt->geoms[j]; + mpt->ngeoms = i; + } + + break; + } + + case CIRCSTRINGTYPE: + /* Dunno how to handle these, will return untouched */ + return geometry_modified; + + /* Can process most multi* types as generic collection */ + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case TINTYPE: + case COLLECTIONTYPE: + /* Curve types we mostly ignore, but allow the linear */ + /* portions to be processed by recursing into them */ + case MULTICURVETYPE: + case CURVEPOLYTYPE: + case MULTISURFACETYPE: + case COMPOUNDTYPE: { + uint32_t i, j = 0; + LWCOLLECTION *col = (LWCOLLECTION *)(geom); + for (i = 0; i < col->ngeoms; i++) + { + LWGEOM *g = col->geoms[i]; + if (!g) + continue; + geometry_modified |= lwgeom_remove_repeated_points_in_place(g, tolerance); + /* Drop zero'ed out geometries */ + if (lwgeom_is_empty(g)) + { + lwgeom_free(g); + continue; + } + col->geoms[j++] = g; + } + /* Update geometry count */ + col->ngeoms = j; + break; + } + default: { + lwerror("%s: unsupported geometry type: %s", __func__, lwtype_name(geom->type)); + break; + } + } + + if (geometry_modified) + lwgeom_drop_bbox(geom); + return geometry_modified; +} + + +/**************************************************************/ + +int +lwgeom_simplify_in_place(LWGEOM *geom, double epsilon, int preserve_collapsed) +{ + int modified = LW_FALSE; + switch (geom->type) + { + /* No-op! Cannot simplify points or triangles */ + case POINTTYPE: + return modified; + case TRIANGLETYPE: + { + if (preserve_collapsed) + return modified; + LWTRIANGLE *t = lwgeom_as_lwtriangle(geom); + POINTARRAY *pa = t->points; + ptarray_simplify_in_place(pa, epsilon, 0); + if (pa->npoints < 3) + { + pa->npoints = 0; + modified = LW_TRUE; + } + break; + } + case LINETYPE: + { + LWLINE *g = (LWLINE*)(geom); + POINTARRAY *pa = g->points; + uint32_t in_npoints = pa->npoints; + ptarray_simplify_in_place(pa, epsilon, 2); + modified = in_npoints != pa->npoints; + /* Invalid output */ + if (pa->npoints == 1 && pa->maxpoints > 1) + { + /* Use first point as last point */ + if (preserve_collapsed) + { + pa->npoints = 2; + ptarray_copy_point(pa, 0, 1); + } + /* Finish the collapse process */ + else + { + pa->npoints = 0; + } + } + /* Duped output, force collapse */ + if (pa->npoints == 2 && !preserve_collapsed) + { + if (p2d_same(getPoint2d_cp(pa, 0), getPoint2d_cp(pa, 1))) + pa->npoints = 0; + } + break; + } + case POLYGONTYPE: + { + uint32_t i, j = 0; + LWPOLY *g = (LWPOLY*)(geom); + for (i = 0; i < g->nrings; i++) + { + POINTARRAY *pa = g->rings[i]; + /* Only stop collapse on first ring */ + int minpoints = (preserve_collapsed && i == 0) ? 4 : 0; + /* Skip zero'ed out rings */ + if(!pa) + continue; + uint32_t in_npoints = pa->npoints; + ptarray_simplify_in_place(pa, epsilon, minpoints); + modified |= in_npoints != pa->npoints; + /* Drop collapsed rings */ + if(pa->npoints < 4) + { + if (i == 0) + { + /* If the outter ring is dropped, all can be dropped */ + for (i = 0; i < g->nrings; i++) + { + pa = g->rings[i]; + ptarray_free(pa); + } + break; + } + else + { + /* Drop this inner ring only */ + ptarray_free(pa); + continue; + } + } + g->rings[j++] = pa; + } + /* Update ring count */ + g->nrings = j; + break; + } + /* Can process all multi* types as generic collection */ + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case TINTYPE: + case COLLECTIONTYPE: + { + uint32_t i, j = 0; + LWCOLLECTION *col = (LWCOLLECTION*)geom; + for (i = 0; i < col->ngeoms; i++) + { + LWGEOM *g = col->geoms[i]; + if (!g) continue; + modified |= lwgeom_simplify_in_place(g, epsilon, preserve_collapsed); + /* Drop zero'ed out geometries */ + if(lwgeom_is_empty(g)) + { + lwgeom_free(g); + continue; + } + col->geoms[j++] = g; + } + /* Update geometry count */ + col->ngeoms = j; + break; + } + default: + { + lwerror("%s: unsupported geometry type: %s", __func__, lwtype_name(geom->type)); + break; + } + } + + if (modified) + { + lwgeom_drop_bbox(geom); + } + return modified; +} + +LWGEOM* lwgeom_simplify(const LWGEOM *igeom, double dist, int preserve_collapsed) +{ + LWGEOM *lwgeom_out = lwgeom_clone_deep(igeom); + lwgeom_simplify_in_place(lwgeom_out, dist, preserve_collapsed); + if (lwgeom_is_empty(lwgeom_out)) + { + lwgeom_free(lwgeom_out); + return NULL; + } + return lwgeom_out; +} + +/**************************************************************/ + + +double lwgeom_area(const LWGEOM *geom) +{ + int type = geom->type; + + if ( type == POLYGONTYPE ) + return lwpoly_area((LWPOLY*)geom); + else if ( type == CURVEPOLYTYPE ) + return lwcurvepoly_area((LWCURVEPOLY*)geom); + else if (type == TRIANGLETYPE ) + return lwtriangle_area((LWTRIANGLE*)geom); + else if ( lwgeom_is_collection(geom) ) + { + double area = 0.0; + uint32_t i; + LWCOLLECTION *col = (LWCOLLECTION*)geom; + for ( i = 0; i < col->ngeoms; i++ ) + area += lwgeom_area(col->geoms[i]); + return area; + } + else + return 0.0; +} + +double lwgeom_perimeter(const LWGEOM *geom) +{ + int type = geom->type; + if ( type == POLYGONTYPE ) + return lwpoly_perimeter((LWPOLY*)geom); + else if ( type == CURVEPOLYTYPE ) + return lwcurvepoly_perimeter((LWCURVEPOLY*)geom); + else if ( type == TRIANGLETYPE ) + return lwtriangle_perimeter((LWTRIANGLE*)geom); + else if ( lwgeom_is_collection(geom) ) + { + double perimeter = 0.0; + uint32_t i; + LWCOLLECTION *col = (LWCOLLECTION*)geom; + for ( i = 0; i < col->ngeoms; i++ ) + perimeter += lwgeom_perimeter(col->geoms[i]); + return perimeter; + } + else + return 0.0; +} + +double lwgeom_perimeter_2d(const LWGEOM *geom) +{ + int type = geom->type; + if ( type == POLYGONTYPE ) + return lwpoly_perimeter_2d((LWPOLY*)geom); + else if ( type == CURVEPOLYTYPE ) + return lwcurvepoly_perimeter_2d((LWCURVEPOLY*)geom); + else if ( type == TRIANGLETYPE ) + return lwtriangle_perimeter_2d((LWTRIANGLE*)geom); + else if ( lwgeom_is_collection(geom) ) + { + double perimeter = 0.0; + uint32_t i; + LWCOLLECTION *col = (LWCOLLECTION*)geom; + for ( i = 0; i < col->ngeoms; i++ ) + perimeter += lwgeom_perimeter_2d(col->geoms[i]); + return perimeter; + } + else + return 0.0; +} + +double lwgeom_length(const LWGEOM *geom) +{ + int type = geom->type; + if ( type == LINETYPE ) + return lwline_length((LWLINE*)geom); + else if ( type == CIRCSTRINGTYPE ) + return lwcircstring_length((LWCIRCSTRING*)geom); + else if ( type == COMPOUNDTYPE ) + return lwcompound_length((LWCOMPOUND*)geom); + else if ( lwgeom_is_collection(geom) ) + { + double length = 0.0; + uint32_t i; + LWCOLLECTION *col = (LWCOLLECTION*)geom; + for ( i = 0; i < col->ngeoms; i++ ) + length += lwgeom_length(col->geoms[i]); + return length; + } + else + return 0.0; +} + +double lwgeom_length_2d(const LWGEOM *geom) +{ + int type = geom->type; + if ( type == LINETYPE ) + return lwline_length_2d((LWLINE*)geom); + else if ( type == CIRCSTRINGTYPE ) + return lwcircstring_length_2d((LWCIRCSTRING*)geom); + else if ( type == COMPOUNDTYPE ) + return lwcompound_length_2d((LWCOMPOUND*)geom); + else if ( lwgeom_is_collection(geom) ) + { + double length = 0.0; + uint32_t i; + LWCOLLECTION *col = (LWCOLLECTION*)geom; + for ( i = 0; i < col->ngeoms; i++ ) + length += lwgeom_length_2d(col->geoms[i]); + return length; + } + else + return 0.0; +} + +void +lwgeom_affine(LWGEOM *geom, const AFFINE *affine) +{ + int type = geom->type; + uint32_t i; + + switch(type) + { + /* Take advantage of fact tht pt/ln/circ/tri have same memory structure */ + case POINTTYPE: + case LINETYPE: + case CIRCSTRINGTYPE: + case TRIANGLETYPE: + { + LWLINE *l = (LWLINE*)geom; + ptarray_affine(l->points, affine); + break; + } + case POLYGONTYPE: + { + LWPOLY *p = (LWPOLY*)geom; + for( i = 0; i < p->nrings; i++ ) + ptarray_affine(p->rings[i], affine); + break; + } + case CURVEPOLYTYPE: + { + LWCURVEPOLY *c = (LWCURVEPOLY*)geom; + for( i = 0; i < c->nrings; i++ ) + lwgeom_affine(c->rings[i], affine); + break; + } + default: + { + if( lwgeom_is_collection(geom) ) + { + LWCOLLECTION *c = (LWCOLLECTION*)geom; + for( i = 0; i < c->ngeoms; i++ ) + { + lwgeom_affine(c->geoms[i], affine); + } + } + else + { + lwerror("lwgeom_affine: unable to handle type '%s'", lwtype_name(type)); + } + } + } + + /* Recompute bbox if needed */ + if (geom->bbox) + lwgeom_refresh_bbox(geom); +} + +void +lwgeom_scale(LWGEOM *geom, const POINT4D *factor) +{ + int type = geom->type; + uint32_t i; + + switch(type) + { + /* Take advantage of fact tht pt/ln/circ/tri have same memory structure */ + case POINTTYPE: + case LINETYPE: + case CIRCSTRINGTYPE: + case TRIANGLETYPE: + { + LWLINE *l = (LWLINE*)geom; + ptarray_scale(l->points, factor); + break; + } + case POLYGONTYPE: + { + LWPOLY *p = (LWPOLY*)geom; + for( i = 0; i < p->nrings; i++ ) + ptarray_scale(p->rings[i], factor); + break; + } + case CURVEPOLYTYPE: + { + LWCURVEPOLY *c = (LWCURVEPOLY*)geom; + for( i = 0; i < c->nrings; i++ ) + lwgeom_scale(c->rings[i], factor); + break; + } + default: + { + if( lwgeom_is_collection(geom) ) + { + LWCOLLECTION *c = (LWCOLLECTION*)geom; + for( i = 0; i < c->ngeoms; i++ ) + { + lwgeom_scale(c->geoms[i], factor); + } + } + else + { + lwerror("lwgeom_scale: unable to handle type '%s'", lwtype_name(type)); + } + } + } + + /* Recompute bbox if needed */ + if (geom->bbox) + lwgeom_refresh_bbox(geom); +} + +LWGEOM * +lwgeom_construct_empty(uint8_t type, int32_t srid, char hasz, char hasm) +{ + switch(type) + { + case POINTTYPE: + return lwpoint_as_lwgeom(lwpoint_construct_empty(srid, hasz, hasm)); + case LINETYPE: + return lwline_as_lwgeom(lwline_construct_empty(srid, hasz, hasm)); + case POLYGONTYPE: + return lwpoly_as_lwgeom(lwpoly_construct_empty(srid, hasz, hasm)); + case CURVEPOLYTYPE: + return lwcurvepoly_as_lwgeom(lwcurvepoly_construct_empty(srid, hasz, hasm)); + case CIRCSTRINGTYPE: + return lwcircstring_as_lwgeom(lwcircstring_construct_empty(srid, hasz, hasm)); + case TRIANGLETYPE: + return lwtriangle_as_lwgeom(lwtriangle_construct_empty(srid, hasz, hasm)); + case COMPOUNDTYPE: + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + return lwcollection_as_lwgeom(lwcollection_construct_empty(type, srid, hasz, hasm)); + default: + lwerror("lwgeom_construct_empty: unsupported geometry type: %s", + lwtype_name(type)); + return NULL; + } +} + +int +lwgeom_startpoint(const LWGEOM *lwgeom, POINT4D *pt) +{ + if ( ! lwgeom || lwgeom_is_empty(lwgeom) ) + return LW_FAILURE; + + switch( lwgeom->type ) + { + case POINTTYPE: + return ptarray_startpoint(((LWPOINT*)lwgeom)->point, pt); + case TRIANGLETYPE: + case CIRCSTRINGTYPE: + case LINETYPE: + return ptarray_startpoint(((LWLINE*)lwgeom)->points, pt); + case POLYGONTYPE: + return lwpoly_startpoint((LWPOLY*)lwgeom, pt); + case TINTYPE: + case CURVEPOLYTYPE: + case COMPOUNDTYPE: + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + case POLYHEDRALSURFACETYPE: + return lwcollection_startpoint((LWCOLLECTION*)lwgeom, pt); + default: + lwerror("lwgeom_startpoint: unsupported geometry type: %s", lwtype_name(lwgeom->type)); + return LW_FAILURE; + } +} + +void +lwgeom_grid_in_place(LWGEOM *geom, const gridspec *grid) +{ + if (!geom) return; + if (lwgeom_is_empty(geom)) return; + switch ( geom->type ) + { + case POINTTYPE: + { + LWPOINT *pt = (LWPOINT*)(geom); + ptarray_grid_in_place(pt->point, grid); + return; + } + case CIRCSTRINGTYPE: + case TRIANGLETYPE: + case LINETYPE: + { + LWLINE *ln = (LWLINE*)(geom); + ptarray_grid_in_place(ln->points, grid); + /* For invalid line, return an EMPTY */ + if (ln->points->npoints < 2) + ln->points->npoints = 0; + return; + } + case POLYGONTYPE: + { + LWPOLY *ply = (LWPOLY*)(geom); + if (!ply->rings) return; + + /* Check first the external ring */ + uint32_t i = 0; + POINTARRAY *pa = ply->rings[0]; + ptarray_grid_in_place(pa, grid); + if (pa->npoints < 4) + { + /* External ring collapsed: free everything */ + for (i = 0; i < ply->nrings; i++) + { + ptarray_free(ply->rings[i]); + } + ply->nrings = 0; + return; + } + + /* Check the other rings */ + uint32_t j = 1; + for (i = 1; i < ply->nrings; i++) + { + POINTARRAY *pa = ply->rings[i]; + ptarray_grid_in_place(pa, grid); + + /* Skip bad rings */ + if (pa->npoints >= 4) + { + ply->rings[j++] = pa; + } + else + { + ptarray_free(pa); + } + } + /* Adjust ring count appropriately */ + ply->nrings = j; + return; + } + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case TINTYPE: + case COLLECTIONTYPE: + case COMPOUNDTYPE: + { + LWCOLLECTION *col = (LWCOLLECTION*)(geom); + uint32_t i, j = 0; + if (!col->geoms) return; + for (i = 0; i < col->ngeoms; i++) + { + LWGEOM *g = col->geoms[i]; + lwgeom_grid_in_place(g, grid); + /* Empty geoms need to be freed */ + /* before we move on */ + if (lwgeom_is_empty(g)) + { + lwgeom_free(g); + continue; + } + col->geoms[j++] = g; + } + col->ngeoms = j; + return; + } + default: + { + lwerror("%s: Unsupported geometry type: %s", __func__, + lwtype_name(geom->type)); + return; + } + } +} + + +LWGEOM * +lwgeom_grid(const LWGEOM *lwgeom, const gridspec *grid) +{ + LWGEOM *lwgeom_out = lwgeom_clone_deep(lwgeom); + lwgeom_grid_in_place(lwgeom_out, grid); + return lwgeom_out; +} + + +/* Prototype for recursion */ +static void lwgeom_subdivide_recursive(const LWGEOM *geom, + uint8_t dimension, + uint32_t maxvertices, + uint32_t depth, + LWCOLLECTION *col, + double gridSize); + +static void +lwgeom_subdivide_recursive(const LWGEOM *geom, + uint8_t dimension, + uint32_t maxvertices, + uint32_t depth, + LWCOLLECTION *col, + double gridSize) +{ + const uint32_t maxdepth = 50; + + if (!geom) + return; + + const GBOX *box_in = lwgeom_get_bbox(geom); + if (!box_in) + return; + + LW_ON_INTERRUPT(return); + + GBOX clip; + gbox_duplicate(box_in, &clip); + double width = clip.xmax - clip.xmin; + double height = clip.ymax - clip.ymin; + + if ( geom->type == POLYHEDRALSURFACETYPE || geom->type == TINTYPE ) + lwerror("%s: unsupported geometry type '%s'", __func__, lwtype_name(geom->type)); + + if ( width == 0.0 && height == 0.0 ) + { + if ( geom->type == POINTTYPE && dimension == 0) + lwcollection_add_lwgeom(col, lwgeom_clone_deep(geom)); + return; + } + + if (width == 0.0) + { + clip.xmax += FP_TOLERANCE; + clip.xmin -= FP_TOLERANCE; + width = 2 * FP_TOLERANCE; + } + if (height == 0.0) + { + clip.ymax += FP_TOLERANCE; + clip.ymin -= FP_TOLERANCE; + height = 2 * FP_TOLERANCE; + } + + /* Always just recurse into collections */ + if ( lwgeom_is_collection(geom) && geom->type != MULTIPOINTTYPE ) + { + LWCOLLECTION *incol = (LWCOLLECTION*)geom; + /* Don't increment depth yet, since we aren't actually + * subdividing geometries yet */ + for (uint32_t i = 0; i < incol->ngeoms; i++ ) + lwgeom_subdivide_recursive(incol->geoms[i], dimension, maxvertices, depth, col, gridSize); + return; + } + + if (lwgeom_dimension(geom) < dimension) + { + /* We've hit a lower dimension object produced by clipping at + * a shallower recursion level. Ignore it. */ + return; + } + + /* But don't go too far. 2^50 ~= 10^15, that's enough subdivision */ + /* Just add what's left */ + if ( depth > maxdepth ) + { + lwcollection_add_lwgeom(col, lwgeom_clone_deep(geom)); + return; + } + + uint32_t nvertices = lwgeom_count_vertices(geom); + + /* Skip empties entirely */ + if (nvertices == 0) + return; + + /* If it is under the vertex tolerance, just add it, we're done */ + if (nvertices <= maxvertices) + { + lwcollection_add_lwgeom(col, lwgeom_clone_deep(geom)); + return; + } + + uint8_t split_ordinate = (width > height) ? 0 : 1; + double center = (split_ordinate == 0) ? (clip.xmin + clip.xmax) / 2 : (clip.ymin + clip.ymax) / 2; + double pivot = DBL_MAX; + if (geom->type == POLYGONTYPE) + { + uint32_t ring_to_trim = 0; + double ring_area = 0; + double pivot_eps = DBL_MAX; + double pt_eps = DBL_MAX; + POINTARRAY *pa; + LWPOLY *lwpoly = (LWPOLY *)geom; + + /* if there are more points in holes than in outer ring */ + if (nvertices >= 2 * lwpoly->rings[0]->npoints) + { + /* trim holes starting from biggest */ + for (uint32_t i = 1; i < lwpoly->nrings; i++) + { + double current_ring_area = fabs(ptarray_signed_area(lwpoly->rings[i])); + if (current_ring_area >= ring_area) + { + ring_area = current_ring_area; + ring_to_trim = i; + } + } + } + + pa = lwpoly->rings[ring_to_trim]; + + /* find most central point in chosen ring */ + for (uint32_t i = 0; i < pa->npoints; i++) + { + double pt; + if (split_ordinate == 0) + pt = getPoint2d_cp(pa, i)->x; + else + pt = getPoint2d_cp(pa, i)->y; + pt_eps = fabs(pt - center); + if (pivot_eps > pt_eps) + { + pivot = pt; + pivot_eps = pt_eps; + } + } + } + GBOX subbox1, subbox2; + gbox_duplicate(&clip, &subbox1); + gbox_duplicate(&clip, &subbox2); + + if (pivot == DBL_MAX) + pivot = center; + + if (split_ordinate == 0) + { + if (FP_NEQUALS(subbox1.xmax, pivot) && FP_NEQUALS(subbox1.xmin, pivot)) + subbox1.xmax = subbox2.xmin = pivot; + else + subbox1.xmax = subbox2.xmin = center; + } + else + { + if (FP_NEQUALS(subbox1.ymax, pivot) && FP_NEQUALS(subbox1.ymin, pivot)) + subbox1.ymax = subbox2.ymin = pivot; + else + subbox1.ymax = subbox2.ymin = center; + } + + ++depth; + + { + LWGEOM *subbox = (LWGEOM *)lwpoly_construct_envelope( + geom->srid, subbox1.xmin, subbox1.ymin, subbox1.xmax, subbox1.ymax); + LWGEOM *clipped = lwgeom_intersection_prec(geom, subbox, gridSize); + lwgeom_simplify_in_place(clipped, 0.0, LW_TRUE); + lwgeom_free(subbox); + if (clipped && !lwgeom_is_empty(clipped)) + { + lwgeom_subdivide_recursive(clipped, dimension, maxvertices, depth, col, gridSize); + lwgeom_free(clipped); + } + } + { + LWGEOM *subbox = (LWGEOM *)lwpoly_construct_envelope( + geom->srid, subbox2.xmin, subbox2.ymin, subbox2.xmax, subbox2.ymax); + LWGEOM *clipped = lwgeom_intersection_prec(geom, subbox, gridSize); + lwgeom_simplify_in_place(clipped, 0.0, LW_TRUE); + lwgeom_free(subbox); + if (clipped && !lwgeom_is_empty(clipped)) + { + lwgeom_subdivide_recursive(clipped, dimension, maxvertices, depth, col, gridSize); + lwgeom_free(clipped); + } + } +} + +LWCOLLECTION * +lwgeom_subdivide_prec(const LWGEOM *geom, uint32_t maxvertices, double gridSize) +{ + static uint32_t startdepth = 0; + static uint32_t minmaxvertices = 5; + LWCOLLECTION *col; + + col = lwcollection_construct_empty(COLLECTIONTYPE, geom->srid, lwgeom_has_z(geom), lwgeom_has_m(geom)); + + if ( lwgeom_is_empty(geom) ) + return col; + + if ( maxvertices < minmaxvertices ) + { + lwcollection_free(col); + lwerror("%s: cannot subdivide to fewer than %d vertices per output", __func__, minmaxvertices); + } + + lwgeom_subdivide_recursive(geom, lwgeom_dimension(geom), maxvertices, startdepth, col, gridSize); + lwgeom_set_srid((LWGEOM*)col, geom->srid); + return col; +} + +LWCOLLECTION * +lwgeom_subdivide(const LWGEOM *geom, uint32_t maxvertices) +{ + return lwgeom_subdivide_prec(geom, maxvertices, -1); +} + + + +int +lwgeom_is_trajectory(const LWGEOM *geom) +{ + int type = geom->type; + + if( type != LINETYPE ) + { + lwnotice("Geometry is not a LINESTRING"); + return LW_FALSE; + } + return lwline_is_trajectory((LWLINE*)geom); +} + +#define STATIC_ASSERT(COND,MSG) typedef char static_assertion_##MSG[(COND)?1:-1] +STATIC_ASSERT(sizeof(double) == sizeof(uint64_t),this_should_be_true); + +static double trim_preserve_decimal_digits(double d, int32_t decimal_digits) +{ + uint64_t dint = 0; + memcpy(&dint, &d, sizeof(double)); + /* Extract the exponent from the IEEE 754 integer representation, which */ + /* corresponds to floor(log2(fabs(d))) */ + const int exponent = (int)((dint >> 52) & 2047) - 1023; + /* (x * 851 + 255) / 256 == 1 + (int)(x * log2(10)) for x in [0,30] */ + int bits_needed = 1 + exponent + (decimal_digits * 851 + 255) / 256; + /* for negative values, (x * 851 + 255) / 256 == (int)(x * log2(10)), so */ + /* substract one */ + if (decimal_digits < 0) + bits_needed --; + + /* This will also handle NaN and Inf since exponent = 1023, and thus for */ + /* reasonable decimal_digits values bits_needed will be > 52 */ + if (bits_needed >= 52) + { + return d; + } + if (bits_needed < 1 ) + bits_needed = 1; + const uint64_t mask = 0xffffffffffffffffULL << (52 - bits_needed); + dint &= mask; + memcpy(&d, &dint, sizeof(double)); + return d; +} + +void lwgeom_trim_bits_in_place(LWGEOM* geom, int32_t prec_x, int32_t prec_y, int32_t prec_z, int32_t prec_m) +{ + LWPOINTITERATOR* it = lwpointiterator_create_rw(geom); + POINT4D p; + + while (lwpointiterator_has_next(it)) + { + lwpointiterator_peek(it, &p); + p.x = trim_preserve_decimal_digits(p.x, prec_x); + p.y = trim_preserve_decimal_digits(p.y, prec_y); + if (lwgeom_has_z(geom)) + p.z = trim_preserve_decimal_digits(p.z, prec_z); + if (lwgeom_has_m(geom)) + p.m = trim_preserve_decimal_digits(p.m, prec_m); + lwpointiterator_modify_next(it, &p); + } + + lwpointiterator_destroy(it); +} + +LWGEOM * +lwgeom_boundary(LWGEOM *lwgeom) +{ + int32_t srid = lwgeom_get_srid(lwgeom); + uint8_t hasz = lwgeom_has_z(lwgeom); + uint8_t hasm = lwgeom_has_m(lwgeom); + + switch (lwgeom->type) + { + case POINTTYPE: + case MULTIPOINTTYPE: { + return lwgeom_construct_empty(lwgeom->type, srid, hasz, hasm); + } + case LINETYPE: + case CIRCSTRINGTYPE: { + if (lwgeom_is_closed(lwgeom) || lwgeom_is_empty(lwgeom)) + return (LWGEOM *)lwmpoint_construct_empty(srid, hasz, hasm); + else + { + LWLINE *lwline = (LWLINE *)lwgeom; + LWMPOINT *lwmpoint = lwmpoint_construct_empty(srid, hasz, hasm); + POINT4D pt; + getPoint4d_p(lwline->points, 0, &pt); + lwmpoint_add_lwpoint(lwmpoint, lwpoint_make(srid, hasz, hasm, &pt)); + getPoint4d_p(lwline->points, lwline->points->npoints - 1, &pt); + lwmpoint_add_lwpoint(lwmpoint, lwpoint_make(srid, hasz, hasm, &pt)); + + return (LWGEOM *)lwmpoint; + } + } + case MULTILINETYPE: + case MULTICURVETYPE: { + LWMLINE *lwmline = (LWMLINE *)lwgeom; + POINT4D *out = lwalloc(sizeof(POINT4D) * lwmline->ngeoms * 2); + uint32_t n = 0; + + for (uint32_t i = 0; i < lwmline->ngeoms; i++) + { + LWMPOINT *points = lwgeom_as_lwmpoint(lwgeom_boundary((LWGEOM *)lwmline->geoms[i])); + if (!points) + continue; + + for (uint32_t k = 0; k < points->ngeoms; k++) + { + POINT4D pt = getPoint4d(points->geoms[k]->point, 0); + + uint8_t seen = LW_FALSE; + for (uint32_t j = 0; j < n; j++) + { + if (memcmp(&(out[j]), &pt, sizeof(POINT4D)) == 0) + { + seen = LW_TRUE; + out[j] = out[--n]; + break; + } + } + if (!seen) + out[n++] = pt; + } + + lwgeom_free((LWGEOM *)points); + } + + LWMPOINT *lwmpoint = lwmpoint_construct_empty(srid, hasz, hasm); + + for (uint32_t i = 0; i < n; i++) + lwmpoint_add_lwpoint(lwmpoint, lwpoint_make(srid, hasz, hasm, &(out[i]))); + + lwfree(out); + + return (LWGEOM *)lwmpoint; + } + case TRIANGLETYPE: { + LWTRIANGLE *lwtriangle = (LWTRIANGLE *)lwgeom; + POINTARRAY *points = ptarray_clone_deep(lwtriangle->points); + return (LWGEOM *)lwline_construct(srid, 0, points); + } + case POLYGONTYPE: { + LWPOLY *lwpoly = (LWPOLY *)lwgeom; + + LWMLINE *lwmline = lwmline_construct_empty(srid, hasz, hasm); + for (uint32_t i = 0; i < lwpoly->nrings; i++) + { + POINTARRAY *ring = ptarray_clone_deep(lwpoly->rings[i]); + lwmline_add_lwline(lwmline, lwline_construct(srid, 0, ring)); + } + + /* Homogenize the multilinestring to hopefully get a single LINESTRING */ + LWGEOM *lwout = lwgeom_homogenize((LWGEOM *)lwmline); + lwgeom_free((LWGEOM *)lwmline); + return lwout; + } + case CURVEPOLYTYPE: { + LWCURVEPOLY *lwcurvepoly = (LWCURVEPOLY *)lwgeom; + LWCOLLECTION *lwcol = lwcollection_construct_empty(MULTICURVETYPE, srid, hasz, hasm); + + for (uint32_t i = 0; i < lwcurvepoly->nrings; i++) + lwcol = lwcollection_add_lwgeom(lwcol, lwgeom_clone_deep(lwcurvepoly->rings[i])); + + return (LWGEOM *)lwcol; + } + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + case TINTYPE: { + LWCOLLECTION *lwcol = (LWCOLLECTION *)lwgeom; + LWCOLLECTION *lwcol_boundary = lwcollection_construct_empty(COLLECTIONTYPE, srid, hasz, hasm); + + for (uint32_t i = 0; i < lwcol->ngeoms; i++) + lwcollection_add_lwgeom(lwcol_boundary, lwgeom_boundary(lwcol->geoms[i])); + + LWGEOM *lwout = lwgeom_homogenize((LWGEOM *)lwcol_boundary); + lwgeom_free((LWGEOM *)lwcol_boundary); + + return lwout; + } + default: + lwerror("%s: unsupported geometry type: %s", __func__, lwtype_name(lwgeom->type)); + return NULL; + } +} + +int +lwgeom_isfinite(const LWGEOM *lwgeom) +{ + LWPOINTITERATOR* it = lwpointiterator_create(lwgeom); + int hasz = lwgeom_has_z(lwgeom); + int hasm = lwgeom_has_m(lwgeom); + + while (lwpointiterator_has_next(it)) + { + POINT4D p; + lwpointiterator_next(it, &p); + int finite = isfinite(p.x) && + isfinite(p.y) && + (hasz ? isfinite(p.z) : 1) && + (hasm ? isfinite(p.m) : 1); + + if (!finite) + { + lwpointiterator_destroy(it); + return LW_FALSE; + } + } + lwpointiterator_destroy(it); + return LW_TRUE; +} diff --git a/mgist-postgis/liblwgeom/lwgeom_api.c b/mgist-postgis/liblwgeom/lwgeom_api.c new file mode 100644 index 0000000..ec8b5bb --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeom_api.c @@ -0,0 +1,677 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2001-2006 Refractions Research Inc. + * Copyright 2017 Darafei Praliaskouski + * + **********************************************************************/ + + + +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + +#include +#include +// #include "../postgis_revision.h" + +#define xstr(s) str(s) +#define str(s) #s + +const char * +lwgeom_version() +{ + static char *ptr = NULL; + static char buf[256]; + if ( ! ptr ) + { + ptr = buf; + snprintf(ptr, 256, LIBLWGEOM_VERSION" " xstr(POSTGIS_REVISION)); + } + + return ptr; +} + + +inline float +next_float_down(double d) +{ + float result; + if (d > (double)FLT_MAX) + return FLT_MAX; + if (d <= (double)-FLT_MAX) + return -FLT_MAX; + result = d; + + if ( ((double)result) <=d ) + return result; + + return nextafterf(result, -1*FLT_MAX); + +} + +/* + * Returns the float that's very close to the input, but >=. + * handles the funny differences in float4 and float8 reps. + */ +inline float +next_float_up(double d) +{ + float result; + if (d >= (double)FLT_MAX) + return FLT_MAX; + if (d < (double)-FLT_MAX) + return -FLT_MAX; + result = d; + + if ( ((double)result) >=d ) + return result; + + return nextafterf(result, FLT_MAX); +} + + + + +/************************************************************************ + * POINTARRAY support functions + * + * TODO: should be moved to ptarray.c probably + * + ************************************************************************/ + +/* + * Copies a point from the point array into the parameter point + * will set point's z=NO_Z_VALUE if pa is 2d + * will set point's m=NO_M_VALUE if pa is 3d or 2d + * + * NOTE: point is a real POINT3D *not* a pointer + */ +POINT4D +getPoint4d(const POINTARRAY *pa, uint32_t n) +{ + POINT4D result; + getPoint4d_p(pa, n, &result); + return result; +} + +/* + * Copies a point from the point array into the parameter point + * will set point's z=NO_Z_VALUE if pa is 2d + * will set point's m=NO_M_VALUE if pa is 3d or 2d + * + * NOTE: this will modify the point4d pointed to by 'point'. + * + * @return 0 on error, 1 on success + */ +int +getPoint4d_p(const POINTARRAY *pa, uint32_t n, POINT4D *op) +{ + uint8_t *ptr; + int zmflag; + + if ( ! pa ) + { + lwerror("%s [%d] NULL POINTARRAY input", __FILE__, __LINE__); + return 0; + } + + if ( n>=pa->npoints ) + { + LWDEBUGF(2, "%s [%d] called with n=%d and npoints=%d", __FILE__, __LINE__, n, pa->npoints); + return 0; + } + + LWDEBUG(4, "getPoint4d_p called."); + + /* Get a pointer to nth point offset and zmflag */ + ptr=getPoint_internal(pa, n); + zmflag=FLAGS_GET_ZM(pa->flags); + + LWDEBUGF(4, "ptr %p, zmflag %d", ptr, zmflag); + + switch (zmflag) + { + case 0: /* 2d */ + memcpy(op, ptr, sizeof(POINT2D)); + op->m=NO_M_VALUE; + op->z=NO_Z_VALUE; + break; + + case 3: /* ZM */ + memcpy(op, ptr, sizeof(POINT4D)); + break; + + case 2: /* Z */ + memcpy(op, ptr, sizeof(POINT3DZ)); + op->m=NO_M_VALUE; + break; + + case 1: /* M */ + memcpy(op, ptr, sizeof(POINT3DM)); + op->m=op->z; /* we use Z as temporary storage */ + op->z=NO_Z_VALUE; + break; + + default: + lwerror("Unknown ZM flag ??"); + return 0; + } + return 1; + +} + +/* + * Copy a point from the point array into the parameter point + * will set point's z=NO_Z_VALUE if pa is 2d + * NOTE: point is a real POINT3DZ *not* a pointer + */ +POINT3DZ +getPoint3dz(const POINTARRAY *pa, uint32_t n) +{ + POINT3DZ result; + getPoint3dz_p(pa, n, &result); + return result; +} + +/* + * Copy a point from the point array into the parameter point + * will set point's z=NO_Z_VALUE if pa is 2d + * + * NOTE: point is a real POINT3DZ *not* a pointer + */ +POINT3DM +getPoint3dm(const POINTARRAY *pa, uint32_t n) +{ + POINT3DM result; + getPoint3dm_p(pa, n, &result); + return result; +} + +/* + * Copy a point from the point array into the parameter point + * will set point's z=NO_Z_VALUE if pa is 2d + * + * NOTE: this will modify the point3dz pointed to by 'point'. + */ +int +getPoint3dz_p(const POINTARRAY *pa, uint32_t n, POINT3DZ *op) +{ + uint8_t *ptr; + + if ( ! pa ) + { + lwerror("%s [%d] NULL POINTARRAY input", __FILE__, __LINE__); + return 0; + } + + //assert(n < pa->npoints); --causes point emtpy/point empty to crash + if ( n>=pa->npoints ) + { + lwnotice("%s [%d] called with n=%d and npoints=%d", __FILE__, __LINE__, n, pa->npoints); + return 0; + } + + LWDEBUGF(2, "getPoint3dz_p called on array of %d-dimensions / %u pts", + FLAGS_NDIMS(pa->flags), pa->npoints); + + /* Get a pointer to nth point offset */ + ptr=getPoint_internal(pa, n); + + /* + * if input POINTARRAY has the Z, it is always + * at third position so make a single copy + */ + if ( FLAGS_GET_Z(pa->flags) ) + { + memcpy(op, ptr, sizeof(POINT3DZ)); + } + + /* + * Otherwise copy the 2d part and initialize + * Z to NO_Z_VALUE + */ + else + { + memcpy(op, ptr, sizeof(POINT2D)); + op->z=NO_Z_VALUE; + } + + return 1; + +} + +/* + * Copy a point from the point array into the parameter point + * will set point's m=NO_Z_VALUE if pa has no M + * + * NOTE: this will modify the point3dm pointed to by 'point'. + */ +int +getPoint3dm_p(const POINTARRAY *pa, uint32_t n, POINT3DM *op) +{ + uint8_t *ptr; + int zmflag; + + if (!pa) + { + lwerror("%s [%d] NULL POINTARRAY input", __FILE__, __LINE__); + return LW_FALSE; + } + + if (n >= pa->npoints) + { + lwerror("%s [%d] called with n=%d and npoints=%d", __FILE__, __LINE__, n, pa->npoints); + return LW_FALSE; + } + + /* Get a pointer to nth point offset and zmflag */ + ptr = getPoint_internal(pa, n); + zmflag = FLAGS_GET_ZM(pa->flags); + + /* + * if input POINTARRAY has the M and NO Z, + * we can issue a single memcpy + */ + if (zmflag == 1) + { + memcpy(op, ptr, sizeof(POINT3DM)); + return LW_TRUE; + } + + /* + * Otherwise copy the 2d part and + * initialize M to NO_M_VALUE + */ + memcpy(op, ptr, sizeof(POINT2D)); + + /* + * Then, if input has Z skip it and + * copy next double, otherwise initialize + * M to NO_M_VALUE + */ + if (zmflag == 3) + { + ptr += sizeof(POINT3DZ); + memcpy(&(op->m), ptr, sizeof(double)); + } + else + op->m = NO_M_VALUE; + + return LW_TRUE; +} + +/* + * Copy a point from the point array into the parameter point + * z value (if present) is not returned. + * + * NOTE: point is a real POINT2D *not* a pointer + */ +POINT2D +getPoint2d(const POINTARRAY *pa, uint32_t n) +{ + const POINT2D *result; + result = getPoint2d_cp(pa, n); + return *result; +} + +/* + * Copy a point from the point array into the parameter point + * z value (if present) is not returned. + * + * NOTE: this will modify the point2d pointed to by 'point'. + */ +int +getPoint2d_p(const POINTARRAY *pa, uint32_t n, POINT2D *point) +{ + if ( ! pa ) + { + lwerror("%s [%d] NULL POINTARRAY input", __FILE__, __LINE__); + return 0; + } + + if ( n>=pa->npoints ) + { + lwnotice("%s [%d] called with n=%d and npoints=%d", __FILE__, __LINE__, n, pa->npoints); + return 0; + } + + /* this does x,y */ + memcpy(point, getPoint_internal(pa, n), sizeof(POINT2D)); + return 1; +} + +/* + * set point N to the given value + * NOTE that the pointarray can be of any + * dimension, the appropriate ordinate values + * will be extracted from it + * + */ +void +ptarray_set_point4d(POINTARRAY *pa, uint32_t n, const POINT4D *p4d) +{ + uint8_t *ptr; + assert(n < pa->npoints); + ptr = getPoint_internal(pa, n); + switch ( FLAGS_GET_ZM(pa->flags) ) + { + case 3: + memcpy(ptr, p4d, sizeof(POINT4D)); + break; + case 2: + memcpy(ptr, p4d, sizeof(POINT3DZ)); + break; + case 1: + memcpy(ptr, p4d, sizeof(POINT2D)); + ptr+=sizeof(POINT2D); + memcpy(ptr, &(p4d->m), sizeof(double)); + break; + case 0: + memcpy(ptr, p4d, sizeof(POINT2D)); + break; + } +} + +void +ptarray_copy_point(POINTARRAY *pa, uint32_t from, uint32_t to) +{ + int ndims = FLAGS_NDIMS(pa->flags); + switch (ndims) + { + case 2: + { + POINT2D *p_from = (POINT2D*)(getPoint_internal(pa, from)); + POINT2D *p_to = (POINT2D*)(getPoint_internal(pa, to)); + *p_to = *p_from; + return; + } + case 3: + { + POINT3D *p_from = (POINT3D*)(getPoint_internal(pa, from)); + POINT3D *p_to = (POINT3D*)(getPoint_internal(pa, to)); + *p_to = *p_from; + return; + } + case 4: + { + POINT4D *p_from = (POINT4D*)(getPoint_internal(pa, from)); + POINT4D *p_to = (POINT4D*)(getPoint_internal(pa, to)); + *p_to = *p_from; + return; + } + default: + { + lwerror("%s: unsupported number of dimensions - %d", __func__, ndims); + return; + } + } + return; +} + + +/************************************************ + * debugging routines + ************************************************/ + +void printBOX3D(BOX3D *box) +{ + lwnotice("BOX3D: %g %g, %g %g", box->xmin, box->ymin, + box->xmax, box->ymax); +} + +void printPA(POINTARRAY *pa) +{ + uint32_t t; + POINT4D pt; + char *mflag; + + + if ( FLAGS_GET_M(pa->flags) ) mflag = "M"; + else mflag = ""; + + lwnotice(" POINTARRAY%s{", mflag); + lwnotice(" ndims=%i, ptsize=%i", + FLAGS_NDIMS(pa->flags), ptarray_point_size(pa)); + lwnotice(" npoints = %i", pa->npoints); + + if (!pa) + { + lwnotice(" PTARRAY is null pointer!"); + } + else + { + + for (t = 0; t < pa->npoints; t++) + { + getPoint4d_p(pa, t, &pt); + if (FLAGS_NDIMS(pa->flags) == 2) + lwnotice(" %i : %lf,%lf", t, pt.x, pt.y); + if (FLAGS_NDIMS(pa->flags) == 3) + lwnotice(" %i : %lf,%lf,%lf", t, pt.x, pt.y, pt.z); + if (FLAGS_NDIMS(pa->flags) == 4) + lwnotice(" %i : %lf,%lf,%lf,%lf", t, pt.x, pt.y, pt.z, pt.m); + } + } + lwnotice(" }"); +} + + +/** + * Given a string with at least 2 chars in it, convert them to + * a byte value. No error checking done! + */ +uint8_t +parse_hex(char *str) +{ + /* do this a little brute force to make it faster */ + + uint8_t result_high = 0; + uint8_t result_low = 0; + + switch (str[0]) + { + case '0' : + result_high = 0; + break; + case '1' : + result_high = 1; + break; + case '2' : + result_high = 2; + break; + case '3' : + result_high = 3; + break; + case '4' : + result_high = 4; + break; + case '5' : + result_high = 5; + break; + case '6' : + result_high = 6; + break; + case '7' : + result_high = 7; + break; + case '8' : + result_high = 8; + break; + case '9' : + result_high = 9; + break; + case 'A' : + case 'a' : + result_high = 10; + break; + case 'B' : + case 'b' : + result_high = 11; + break; + case 'C' : + case 'c' : + result_high = 12; + break; + case 'D' : + case 'd' : + result_high = 13; + break; + case 'E' : + case 'e' : + result_high = 14; + break; + case 'F' : + case 'f' : + result_high = 15; + break; + } + switch (str[1]) + { + case '0' : + result_low = 0; + break; + case '1' : + result_low = 1; + break; + case '2' : + result_low = 2; + break; + case '3' : + result_low = 3; + break; + case '4' : + result_low = 4; + break; + case '5' : + result_low = 5; + break; + case '6' : + result_low = 6; + break; + case '7' : + result_low = 7; + break; + case '8' : + result_low = 8; + break; + case '9' : + result_low = 9; + break; + case 'A' : + case 'a' : + result_low = 10; + break; + case 'B' : + case 'b' : + result_low = 11; + break; + case 'C' : + case 'c' : + result_low = 12; + break; + case 'D' : + case 'd' : + result_low = 13; + break; + case 'E' : + case 'e' : + result_low = 14; + break; + case 'F' : + case 'f' : + result_low = 15; + break; + } + return (uint8_t) ((result_high<<4) + result_low); +} + + +/** + * Given one byte, populate result with two byte representing + * the hex number. + * + * Ie. deparse_hex( 255, mystr) + * -> mystr[0] = 'F' and mystr[1] = 'F' + * + * No error checking done + */ +void +deparse_hex(uint8_t str, char *result) +{ + int input_high; + int input_low; + static char outchr[]= + { + "0123456789ABCDEF" + }; + + input_high = (str>>4); + input_low = (str & 0x0F); + + result[0] = outchr[input_high]; + result[1] = outchr[input_low]; + +} + + +/** + * Find interpolation point I + * between point A and point B + * so that the len(AI) == len(AB)*F + * and I falls on AB segment. + * + * Example: + * + * F=0.5 : A----I----B + * F=1 : A---------B==I + * F=0 : A==I---------B + * F=.2 : A-I-------B + */ +void +interpolate_point4d(const POINT4D *A, const POINT4D *B, POINT4D *I, double F) +{ +#if PARANOIA_LEVEL > 0 + if (F < 0 || F > 1) lwerror("interpolate_point4d: invalid F (%g)", F); +#endif + I->x=A->x+((B->x-A->x)*F); + I->y=A->y+((B->y-A->y)*F); + I->z=A->z+((B->z-A->z)*F); + I->m=A->m+((B->m-A->m)*F); +} + + +int _lwgeom_interrupt_requested = 0; +void +lwgeom_request_interrupt() { + _lwgeom_interrupt_requested = 1; +} +void +lwgeom_cancel_interrupt() { + _lwgeom_interrupt_requested = 0; +} + +lwinterrupt_callback *_lwgeom_interrupt_callback = 0; +lwinterrupt_callback * +lwgeom_register_interrupt_callback(lwinterrupt_callback *cb) { + lwinterrupt_callback *old = _lwgeom_interrupt_callback; + _lwgeom_interrupt_callback = cb; + return old; +} diff --git a/mgist-postgis/liblwgeom/lwgeom_debug.c b/mgist-postgis/liblwgeom/lwgeom_debug.c new file mode 100644 index 0000000..93cb1c5 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeom_debug.c @@ -0,0 +1,201 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2004 Refractions Research Inc. + * + **********************************************************************/ + + +#include "lwgeom_log.h" +#include "liblwgeom.h" + +#include +#include + +/* Place to hold the ZM string used in other summaries */ +static char tflags[6]; + +static char * +lwgeom_flagchars(LWGEOM *lwg) +{ + int flagno = 0; + if ( FLAGS_GET_Z(lwg->flags) ) tflags[flagno++] = 'Z'; + if ( FLAGS_GET_M(lwg->flags) ) tflags[flagno++] = 'M'; + if ( FLAGS_GET_BBOX(lwg->flags) ) tflags[flagno++] = 'B'; + if ( FLAGS_GET_GEODETIC(lwg->flags) ) tflags[flagno++] = 'G'; + if ( lwg->srid != SRID_UNKNOWN ) tflags[flagno++] = 'S'; + tflags[flagno] = '\0'; + + LWDEBUGF(4, "Flags: %s - returning %p", lwg->flags, tflags); + + return tflags; +} + +/* + * Returns an alloced string containing summary for the LWGEOM object + */ +static char * +lwpoint_summary(LWPOINT *point, int offset) +{ + char *result; + char *pad=""; + char *zmflags = lwgeom_flagchars((LWGEOM*)point); + size_t sz = 128+offset; + + result = (char *)lwalloc(sz); + + snprintf(result, sz, "%*.s%s[%s]", + offset, pad, lwtype_name(point->type), + zmflags); + return result; +} + +static char * +lwline_summary(LWLINE *line, int offset) +{ + char *result; + char *pad=""; + char *zmflags = lwgeom_flagchars((LWGEOM*)line); + size_t sz = 128+offset; + + result = (char *)lwalloc(sz); + + snprintf(result, sz, "%*.s%s[%s] with %d points", + offset, pad, lwtype_name(line->type), + zmflags, + line->points->npoints); + return result; +} + + +static char * +lwcollection_summary(LWCOLLECTION *col, int offset) +{ + size_t size = 128; + char *result; + char *tmp; + uint32_t i; + static char *nl = "\n"; + char *pad=""; + char *zmflags = lwgeom_flagchars((LWGEOM*)col); + + LWDEBUG(2, "lwcollection_summary called"); + + result = (char *)lwalloc(size); + + snprintf(result, size, "%*.s%s[%s] with %d element%s", + offset, pad, lwtype_name(col->type), + zmflags, + col->ngeoms, + col->ngeoms ? + ( col->ngeoms > 1 ? "s:\n" : ":\n") + : "s"); + + for (i=0; ingeoms; i++) + { + tmp = lwgeom_summary(col->geoms[i], offset+2); + size += strlen(tmp)+1; + result = lwrealloc(result, size); + + LWDEBUGF(4, "Reallocated %d bytes for result", size); + if ( i > 0 ) strcat(result,nl); + + strcat(result, tmp); + lwfree(tmp); + } + + LWDEBUG(3, "lwcollection_summary returning"); + + return result; +} + +static char * +lwpoly_summary(LWPOLY *poly, int offset) +{ + char tmp[256]; + size_t size = 64*(poly->nrings+1)+128; + char *result; + uint32_t i; + char *pad=""; + static char *nl = "\n"; + char *zmflags = lwgeom_flagchars((LWGEOM*)poly); + + LWDEBUG(2, "lwpoly_summary called"); + + result = (char *)lwalloc(size); + + snprintf(result, size, "%*.s%s[%s] with %i ring%s", + offset, pad, lwtype_name(poly->type), + zmflags, + poly->nrings, + poly->nrings ? + ( poly->nrings > 1 ? "s:\n" : ":\n") + : "s"); + + for (i=0; inrings; i++) + { + snprintf(tmp, sizeof(tmp), "%s ring %i has %i points", + pad, i, poly->rings[i]->npoints); + if ( i > 0 ) strcat(result,nl); + strcat(result,tmp); + } + + LWDEBUG(3, "lwpoly_summary returning"); + + return result; +} + +char * +lwgeom_summary(const LWGEOM *lwgeom, int offset) +{ + char *result; + + switch (lwgeom->type) + { + case POINTTYPE: + return lwpoint_summary((LWPOINT *)lwgeom, offset); + + case CIRCSTRINGTYPE: + case TRIANGLETYPE: + case LINETYPE: + return lwline_summary((LWLINE *)lwgeom, offset); + + case POLYGONTYPE: + return lwpoly_summary((LWPOLY *)lwgeom, offset); + + case TINTYPE: + case MULTISURFACETYPE: + case MULTICURVETYPE: + case CURVEPOLYTYPE: + case COMPOUNDTYPE: + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + return lwcollection_summary((LWCOLLECTION *)lwgeom, offset); + default: + result = (char *)lwalloc(256); + snprintf(result, 256, "Object is of unknown type: %d", + lwgeom->type); + return result; + } + + return NULL; +} diff --git a/mgist-postgis/liblwgeom/lwgeom_geos.c b/mgist-postgis/liblwgeom/lwgeom_geos.c new file mode 100644 index 0000000..bbaefa0 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeom_geos.c @@ -0,0 +1,2238 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2011-2020 Sandro Santilli + * Copyright 2015-2018 Daniel Baston + * Copyright 2017-2018 Darafei Praliaskouski + * + **********************************************************************/ + +#include "lwgeom_geos.h" +#include "liblwgeom.h" +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" +#include "lwrandom.h" + +#include +#include + +LWTIN* lwtin_from_geos(const GEOSGeometry* geom, uint8_t want3d); + +#define AUTOFIX LW_TRUE +#define LWGEOM_GEOS_ERRMSG_MAXSIZE 256 +char lwgeom_geos_errmsg[LWGEOM_GEOS_ERRMSG_MAXSIZE]; + +const char * +lwgeom_geos_compiled_version() +{ + static char ver[64]; + sprintf( + ver, + "%d.%d.%d", + (POSTGIS_GEOS_VERSION/10000), + ((POSTGIS_GEOS_VERSION%10000)/100), + ((POSTGIS_GEOS_VERSION)%100) + ); + return ver; +} + +extern void +lwgeom_geos_error(const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + + /* Call the supplied function */ + if (LWGEOM_GEOS_ERRMSG_MAXSIZE - 1 < vsnprintf(lwgeom_geos_errmsg, LWGEOM_GEOS_ERRMSG_MAXSIZE - 1, fmt, ap)) + lwgeom_geos_errmsg[LWGEOM_GEOS_ERRMSG_MAXSIZE - 1] = '\0'; + + va_end(ap); +} + +void +lwgeom_geos_error_minversion(const char *functionality, const char *minver) +{ + lwerror( + "%s requires a build against GEOS-%s or higher," + " this version of PostGIS was built against version %s", + functionality, minver, lwgeom_geos_compiled_version() + ); +} + +/* Destroy any non-null GEOSGeometry* pointers passed as arguments */ +#define GEOS_FREE(...) \ +do { \ + geos_destroy((sizeof((void*[]){__VA_ARGS__})/sizeof(void*)), __VA_ARGS__); \ +} while (0) + +/* Pass the latest GEOS error to lwerror, then return NULL */ +#define GEOS_FAIL() \ +do { \ + lwerror("%s: GEOS Error: %s", __func__, lwgeom_geos_errmsg); \ + return NULL; \ +} while (0) + +/* Pass the latest GEOS error to lwdebug, then return NULL */ +#define GEOS_FAIL_DEBUG() \ + do \ + { \ + lwdebug(1, "%s: GEOS Error: %s", __func__, lwgeom_geos_errmsg); \ + return NULL; \ + } while (0) + +#define GEOS_FREE_AND_FAIL(...) \ +do { \ + GEOS_FREE(__VA_ARGS__); \ + GEOS_FAIL(); \ +} while (0) + +#define GEOS_FREE_AND_FAIL_DEBUG(...) \ + do \ + { \ + GEOS_FREE(__VA_ARGS__); \ + GEOS_FAIL_DEBUG(); \ + } while (0) + +/* Return the consistent SRID of all inputs, or call lwerror + * in case of SRID mismatch. */ +#define RESULT_SRID(...) \ + (get_result_srid((sizeof((const void*[]){__VA_ARGS__})/sizeof(void*)), __func__, __VA_ARGS__)) + +/* Free any non-null GEOSGeometry* pointers passed as arguments * + * Called by GEOS_FREE, which populates 'count' */ +static void geos_destroy(size_t count, ...) { + va_list ap; + va_start(ap, count); + while (count--) + { + GEOSGeometry* g = va_arg(ap, GEOSGeometry*); + if (g) + { + GEOSGeom_destroy(g); + } + } + va_end(ap); +} + +/* +** GEOS <==> PostGIS conversion functions +** +** Default conversion creates a GEOS point array, then iterates through the +** PostGIS points, setting each value in the GEOS array one at a time. +** +*/ + +/* Return a POINTARRAY from a GEOSCoordSeq */ +POINTARRAY* +ptarray_from_GEOSCoordSeq(const GEOSCoordSequence* cs, uint8_t want3d) +{ + uint32_t dims = 2; + POINTARRAY* pa; + uint32_t size = 0; +#if POSTGIS_GEOS_VERSION < 31000 + uint32_t i; + POINT4D point = { 0.0, 0.0, 0.0, 0.0 }; +#endif + + LWDEBUG(2, "ptarray_fromGEOSCoordSeq called"); + + if (!GEOSCoordSeq_getSize(cs, &size)) lwerror("Exception thrown"); + + LWDEBUGF(4, " GEOSCoordSeq size: %d", size); + + if (want3d) + { + if (!GEOSCoordSeq_getDimensions(cs, &dims)) lwerror("Exception thrown"); + + LWDEBUGF(4, " GEOSCoordSeq dimensions: %d", dims); + + /* forget higher dimensions (if any) */ + if (dims > 3) dims = 3; + } + + LWDEBUGF(4, " output dimensions: %d", dims); + + pa = ptarray_construct((dims == 3), 0, size); +#if POSTGIS_GEOS_VERSION >= 31000 + GEOSCoordSeq_copyToBuffer(cs, (double*) pa->serialized_pointlist, (dims == 3), 0); + return pa; +#else + for (i = 0; i < size; i++) + { +#if POSTGIS_GEOS_VERSION < 30800 + GEOSCoordSeq_getX(cs, i, &(point.x)); + GEOSCoordSeq_getY(cs, i, &(point.y)); + if (dims >= 3) GEOSCoordSeq_getZ(cs, i, &(point.z)); +#else + if (dims >= 3) + GEOSCoordSeq_getXYZ(cs, i, &(point.x), &(point.y), &(point.z)); + else + GEOSCoordSeq_getXY(cs, i, &(point.x), &(point.y)); +#endif + ptarray_set_point4d(pa, i, &point); + } + + return pa; +#endif +} + +/* Return an LWGEOM from a Geometry */ +LWGEOM* +GEOS2LWGEOM(const GEOSGeometry* geom, uint8_t want3d) +{ + int type = GEOSGeomTypeId(geom); + int SRID = GEOSGetSRID(geom); + + /* GEOS's 0 is equivalent to our unknown as for SRID values */ + if (SRID == 0) SRID = SRID_UNKNOWN; + + if (want3d && !GEOSHasZ(geom)) + { + LWDEBUG(3, "Geometry has no Z, won't provide one"); + want3d = 0; + } + + switch (type) + { + const GEOSCoordSequence* cs; + POINTARRAY *pa, **ppaa; + const GEOSGeometry* g; + LWGEOM** geoms; + uint32_t i, ngeoms; + + case GEOS_POINT: + LWDEBUG(4, "lwgeom_from_geometry: it's a Point"); + cs = GEOSGeom_getCoordSeq(geom); + if (GEOSisEmpty(geom)) return (LWGEOM*)lwpoint_construct_empty(SRID, want3d, 0); + pa = ptarray_from_GEOSCoordSeq(cs, want3d); + return (LWGEOM*)lwpoint_construct(SRID, NULL, pa); + + case GEOS_LINESTRING: + case GEOS_LINEARRING: + LWDEBUG(4, "lwgeom_from_geometry: it's a LineString or LinearRing"); + if (GEOSisEmpty(geom)) return (LWGEOM*)lwline_construct_empty(SRID, want3d, 0); + + cs = GEOSGeom_getCoordSeq(geom); + pa = ptarray_from_GEOSCoordSeq(cs, want3d); + return (LWGEOM*)lwline_construct(SRID, NULL, pa); + + case GEOS_POLYGON: + LWDEBUG(4, "lwgeom_from_geometry: it's a Polygon"); + if (GEOSisEmpty(geom)) return (LWGEOM*)lwpoly_construct_empty(SRID, want3d, 0); + ngeoms = GEOSGetNumInteriorRings(geom); + ppaa = lwalloc(sizeof(POINTARRAY*) * (ngeoms + 1)); + g = GEOSGetExteriorRing(geom); + cs = GEOSGeom_getCoordSeq(g); + ppaa[0] = ptarray_from_GEOSCoordSeq(cs, want3d); + for (i = 0; i < ngeoms; i++) + { + g = GEOSGetInteriorRingN(geom, i); + cs = GEOSGeom_getCoordSeq(g); + ppaa[i + 1] = ptarray_from_GEOSCoordSeq(cs, want3d); + } + return (LWGEOM*)lwpoly_construct(SRID, NULL, ngeoms + 1, ppaa); + + case GEOS_MULTIPOINT: + case GEOS_MULTILINESTRING: + case GEOS_MULTIPOLYGON: + case GEOS_GEOMETRYCOLLECTION: + LWDEBUG(4, "lwgeom_from_geometry: it's a Collection or Multi"); + + ngeoms = GEOSGetNumGeometries(geom); + geoms = NULL; + if (ngeoms) + { + geoms = lwalloc(sizeof(LWGEOM*) * ngeoms); + for (i = 0; i < ngeoms; i++) + { + g = GEOSGetGeometryN(geom, i); + geoms[i] = GEOS2LWGEOM(g, want3d); + } + } + return (LWGEOM*)lwcollection_construct(type, SRID, NULL, ngeoms, geoms); + + default: + lwerror("GEOS2LWGEOM: unknown geometry type: %d", type); + return NULL; + } +} + +GEOSCoordSeq ptarray_to_GEOSCoordSeq(const POINTARRAY*, uint8_t fix_ring); + +GEOSCoordSeq +ptarray_to_GEOSCoordSeq(const POINTARRAY* pa, uint8_t fix_ring) +{ + uint32_t dims = 2; + uint32_t i; + int append_points = 0; + const POINT3D *p3d = NULL; + const POINT2D* p2d = NULL; + GEOSCoordSeq sq; + + if (FLAGS_GET_Z(pa->flags)) dims = 3; + + if (fix_ring) + { + if (pa->npoints < 1) + { + lwerror("ptarray_to_GEOSCoordSeq called with fix_ring and 0 vertices in ring, cannot fix"); + return NULL; + } + else + { + if (pa->npoints < 4) append_points = 4 - pa->npoints; + if (!ptarray_is_closed_2d(pa) && append_points == 0) append_points = 1; + } + } + +#if POSTGIS_GEOS_VERSION >= 31000 + if (append_points == 0) { + sq = GEOSCoordSeq_copyFromBuffer((const double*) pa->serialized_pointlist, pa->npoints, FLAGS_GET_Z(pa->flags), FLAGS_GET_M(pa->flags)); + if (!sq) + { + GEOS_FAIL(); + } + return sq; + } +#endif + if (!(sq = GEOSCoordSeq_create(pa->npoints + append_points, dims))) + { + GEOS_FAIL(); + } + + for (i = 0; i < pa->npoints; i++) + { + if (dims == 3) + { + p3d = getPoint3d_cp(pa, i); + p2d = (const POINT2D*)p3d; + LWDEBUGF(4, "Point: %g,%g,%g", p3d->x, p3d->y, p3d->z); + } + else + { + p2d = getPoint2d_cp(pa, i); + LWDEBUGF(4, "Point: %g,%g", p2d->x, p2d->y); + } + +#if POSTGIS_GEOS_VERSION < 30800 + GEOSCoordSeq_setX(sq, i, p2d->x); + GEOSCoordSeq_setY(sq, i, p2d->y); + if (dims == 3) GEOSCoordSeq_setZ(sq, i, p3d->z); +#else + if (dims == 3) + GEOSCoordSeq_setXYZ(sq, i, p2d->x, p2d->y, p3d->z); + else + GEOSCoordSeq_setXY(sq, i, p2d->x, p2d->y); +#endif + + } + + if (append_points) + { + if (dims == 3) + { + p3d = getPoint3d_cp(pa, 0); + p2d = (const POINT2D*)p3d; + } + else + p2d = getPoint2d_cp(pa, 0); + for (i = pa->npoints; i < pa->npoints + append_points; i++) + { +#if POSTGIS_GEOS_VERSION < 30800 + GEOSCoordSeq_setX(sq, i, p2d->x); + GEOSCoordSeq_setY(sq, i, p2d->y); +#else + GEOSCoordSeq_setXY(sq, i, p2d->x, p2d->y); +#endif + + if (dims == 3) GEOSCoordSeq_setZ(sq, i, p3d->z); + } + } + + return sq; + +} + +static inline GEOSGeometry* +ptarray_to_GEOSLinearRing(const POINTARRAY* pa, uint8_t autofix) +{ + GEOSCoordSeq sq; + GEOSGeom g; + sq = ptarray_to_GEOSCoordSeq(pa, autofix); + g = GEOSGeom_createLinearRing(sq); + return g; +} + +GEOSGeometry* +GBOX2GEOS(const GBOX* box) +{ + GEOSGeometry* envelope; + GEOSGeometry* ring; + GEOSCoordSequence* seq = GEOSCoordSeq_create(5, 2); + if (!seq) return NULL; + +#if POSTGIS_GEOS_VERSION < 30800 + GEOSCoordSeq_setX(seq, 0, box->xmin); + GEOSCoordSeq_setY(seq, 0, box->ymin); + + GEOSCoordSeq_setX(seq, 1, box->xmax); + GEOSCoordSeq_setY(seq, 1, box->ymin); + + GEOSCoordSeq_setX(seq, 2, box->xmax); + GEOSCoordSeq_setY(seq, 2, box->ymax); + + GEOSCoordSeq_setX(seq, 3, box->xmin); + GEOSCoordSeq_setY(seq, 3, box->ymax); + + GEOSCoordSeq_setX(seq, 4, box->xmin); + GEOSCoordSeq_setY(seq, 4, box->ymin); +#else + GEOSCoordSeq_setXY(seq, 0, box->xmin, box->ymin); + GEOSCoordSeq_setXY(seq, 1, box->xmax, box->ymin); + GEOSCoordSeq_setXY(seq, 2, box->xmax, box->ymax); + GEOSCoordSeq_setXY(seq, 3, box->xmin, box->ymax); + GEOSCoordSeq_setXY(seq, 4, box->xmin, box->ymin); +#endif + + ring = GEOSGeom_createLinearRing(seq); + if (!ring) + { + GEOSCoordSeq_destroy(seq); + return NULL; + } + + envelope = GEOSGeom_createPolygon(ring, NULL, 0); + if (!envelope) + { + GEOSGeom_destroy(ring); + return NULL; + } + + return envelope; +} + +GEOSGeometry* +LWGEOM2GEOS(const LWGEOM* lwgeom, uint8_t autofix) +{ + GEOSCoordSeq sq; + GEOSGeom g, shell; + GEOSGeom* geoms = NULL; + uint32_t ngeoms, i, j; + int is_empty = LW_FALSE; +#if LWDEBUG_LEVEL >= 4 + char* wkt; +#endif + + if (autofix) + { + /* cross fingers and try without autofix, maybe it'll work? */ + g = LWGEOM2GEOS(lwgeom, LW_FALSE); + if (g) return g; + } + + LWDEBUGF(4, "LWGEOM2GEOS got a %s", lwtype_name(lwgeom->type)); + + if (lwgeom_type_arc(lwgeom)) + { + LWGEOM* lwgeom_stroked = lwgeom_stroke(lwgeom, 32); + GEOSGeometry* g = LWGEOM2GEOS(lwgeom_stroked, autofix); + lwgeom_free(lwgeom_stroked); + return g; + } + + is_empty = lwgeom_is_empty(lwgeom); + + switch (lwgeom->type) + { + case POINTTYPE: + { + if (is_empty) + g = GEOSGeom_createEmptyPoint(); + else + { + LWPOINT* lwp = (LWPOINT*)lwgeom; + #if POSTGIS_GEOS_VERSION < 30800 + sq = ptarray_to_GEOSCoordSeq(lwp->point, 0); + g = GEOSGeom_createPoint(sq); + #else + if (lwgeom_has_z(lwgeom)) + { + sq = ptarray_to_GEOSCoordSeq(lwp->point, 0); + g = GEOSGeom_createPoint(sq); + } + else + { + const POINT2D* p = getPoint2d_cp(lwp->point, 0); + g = GEOSGeom_createPointFromXY(p->x, p->y); + } + #endif + } + if (!g) return NULL; + break; + } + + case LINETYPE: + { + if (is_empty) + g = GEOSGeom_createEmptyLineString(); + else + { + LWLINE* lwl = (LWLINE*)lwgeom; + /* TODO: if (autofix) */ + if (lwl->points->npoints == 1) + { + /* Duplicate point, to make geos-friendly */ + lwl->points = ptarray_addPoint(lwl->points, + getPoint_internal(lwl->points, 0), + FLAGS_NDIMS(lwl->points->flags), + lwl->points->npoints); + } + sq = ptarray_to_GEOSCoordSeq(lwl->points, 0); + g = GEOSGeom_createLineString(sq); + } + if (!g) return NULL; + break; + } + + case POLYGONTYPE: + { + LWPOLY* lwpoly = (LWPOLY*)lwgeom; + if (is_empty) + g = GEOSGeom_createEmptyPolygon(); + else + { + shell = ptarray_to_GEOSLinearRing(lwpoly->rings[0], autofix); + if (!shell) return NULL; + ngeoms = lwpoly->nrings - 1; + if (ngeoms > 0) geoms = lwalloc(sizeof(GEOSGeom) * ngeoms); + + for (i = 1; i < lwpoly->nrings; i++) + { + geoms[i - 1] = ptarray_to_GEOSLinearRing(lwpoly->rings[i], autofix); + if (!geoms[i - 1]) + { + uint32_t k; + for (k = 0; k < i - 1; k++) + GEOSGeom_destroy(geoms[k]); + lwfree(geoms); + GEOSGeom_destroy(shell); + return NULL; + } + } + g = GEOSGeom_createPolygon(shell, geoms, ngeoms); + if (geoms) lwfree(geoms); + } + if (!g) return NULL; + break; + } + + case TRIANGLETYPE: + { + if (is_empty) + g = GEOSGeom_createEmptyPolygon(); + else + { + LWTRIANGLE *lwt = (LWTRIANGLE *)lwgeom; + shell = ptarray_to_GEOSLinearRing(lwt->points, autofix); + if (!shell) return NULL; + g = GEOSGeom_createPolygon(shell, NULL, 0); + } + if (!g) return NULL; + break; + } + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case TINTYPE: + case COLLECTIONTYPE: + { + int geostype; + if (lwgeom->type == MULTIPOINTTYPE) + geostype = GEOS_MULTIPOINT; + else if (lwgeom->type == MULTILINETYPE) + geostype = GEOS_MULTILINESTRING; + else if (lwgeom->type == MULTIPOLYGONTYPE) + geostype = GEOS_MULTIPOLYGON; + else + geostype = GEOS_GEOMETRYCOLLECTION; + + LWCOLLECTION* lwc = (LWCOLLECTION*)lwgeom; + + ngeoms = lwc->ngeoms; + if (ngeoms > 0) geoms = lwalloc(sizeof(GEOSGeom) * ngeoms); + + j = 0; + for (i = 0; i < ngeoms; ++i) + { + GEOSGeometry* g; + + /* if (lwgeom_is_empty(lwc->geoms[i])) continue; */ + + g = LWGEOM2GEOS(lwc->geoms[i], 0); + if (!g) + { + uint32_t k; + for (k = 0; k < j; k++) + GEOSGeom_destroy(geoms[k]); + lwfree(geoms); + return NULL; + } + geoms[j++] = g; + } + g = GEOSGeom_createCollection(geostype, geoms, j); + if (ngeoms > 0) lwfree(geoms); + if (!g) return NULL; + break; + } + + default: + { + lwerror("Unknown geometry type: %d - %s", lwgeom->type, lwtype_name(lwgeom->type)); + return NULL; + } + } + + GEOSSetSRID(g, lwgeom->srid); + +#if LWDEBUG_LEVEL >= 4 + wkt = GEOSGeomToWKT(g); + LWDEBUGF(4, "LWGEOM2GEOS: GEOSGeom: %s", wkt); + GEOSFree(wkt); +#endif + + return g; +} + +GEOSGeometry* +make_geos_point(double x, double y) +{ + GEOSCoordSequence* seq = GEOSCoordSeq_create(1, 2); + GEOSGeometry* geom = NULL; + + if (!seq) return NULL; + +#if POSTGIS_GEOS_VERSION < 30800 + GEOSCoordSeq_setX(seq, 0, x); + GEOSCoordSeq_setY(seq, 0, y); +#else + GEOSCoordSeq_setXY(seq, 0, x, y); +#endif + + geom = GEOSGeom_createPoint(seq); + if (!geom) GEOSCoordSeq_destroy(seq); + return geom; +} + +GEOSGeometry* +make_geos_segment(double x1, double y1, double x2, double y2) +{ + GEOSCoordSequence* seq = GEOSCoordSeq_create(2, 2); + GEOSGeometry* geom = NULL; + + if (!seq) return NULL; + +#if POSTGIS_GEOS_VERSION < 30800 + GEOSCoordSeq_setX(seq, 0, x1); + GEOSCoordSeq_setY(seq, 0, y1); + GEOSCoordSeq_setX(seq, 1, x2); + GEOSCoordSeq_setY(seq, 1, y2); +#else + GEOSCoordSeq_setXY(seq, 0, x1, y1); + GEOSCoordSeq_setXY(seq, 1, x2, y2); +#endif + + geom = GEOSGeom_createLineString(seq); + if (!geom) GEOSCoordSeq_destroy(seq); + return geom; +} + +const char* +lwgeom_geos_version() +{ + const char* ver = GEOSversion(); + return ver; +} + +/* Return the consistent SRID of all input. + * Intended to be called from RESULT_SRID macro */ +static int32_t +get_result_srid(size_t count, const char* funcname, ...) +{ + va_list ap; + va_start(ap, funcname); + int32_t srid = SRID_INVALID; + size_t i; + for(i = 0; i < count; i++) + { + LWGEOM* g = va_arg(ap, LWGEOM*); + if (!g) + { + lwerror("%s: Geometry is null", funcname); + va_end(ap); + return SRID_INVALID; + } + if (i == 0) + { + srid = g->srid; + } + else + { + if (g->srid != srid) + { + lwerror("%s: Operation on mixed SRID geometries (%d != %d)", funcname, srid, g->srid); + va_end(ap); + return SRID_INVALID; + } + } + } + va_end(ap); + return srid; +} + +LWGEOM* +lwgeom_normalize(const LWGEOM* geom) +{ + LWGEOM* result; + int32_t srid = RESULT_SRID(geom); + uint8_t is3d = FLAGS_GET_Z(geom->flags); + GEOSGeometry* g; + + if (srid == SRID_INVALID) return NULL; + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL(); + + if (GEOSNormalize(g) == -1) GEOS_FREE_AND_FAIL(g); + GEOSSetSRID(g, srid); + + if (!(result = GEOS2LWGEOM(g, is3d))) GEOS_FREE_AND_FAIL(g); + + GEOSGeom_destroy(g); + return result; +} + +LWGEOM* +lwgeom_intersection(const LWGEOM* g1, const LWGEOM* g2) +{ + return lwgeom_intersection_prec(g1, g2, -1.0); +} + +LWGEOM* +lwgeom_intersection_prec(const LWGEOM* geom1, const LWGEOM* geom2, double prec) +{ + LWGEOM* result; + int32_t srid = RESULT_SRID(geom1, geom2); + uint8_t is3d = (FLAGS_GET_Z(geom1->flags) || FLAGS_GET_Z(geom2->flags)); + GEOSGeometry* g1; + GEOSGeometry* g2; + GEOSGeometry* g3; + + if (srid == SRID_INVALID) return NULL; + + /* A.Intersection(Empty) == Empty */ + if (lwgeom_is_empty(geom2)) return lwgeom_clone_deep(geom2); /* match empty type? */ + + /* Empty.Intersection(A) == Empty */ + if (lwgeom_is_empty(geom1)) return lwgeom_clone_deep(geom1); /* match empty type? */ + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g1 = LWGEOM2GEOS(geom1, AUTOFIX))) GEOS_FAIL(); + if (!(g2 = LWGEOM2GEOS(geom2, AUTOFIX))) GEOS_FREE_AND_FAIL(g1); + + if ( prec >= 0) { +#if POSTGIS_GEOS_VERSION < 30900 + lwgeom_geos_error_minversion("Fixed-precision intersection", "3.9"); + GEOS_FREE_AND_FAIL(g1, g2); + return NULL; +#else + g3 = GEOSIntersectionPrec(g1, g2, prec); +#endif + } + else + { + g3 = GEOSIntersection(g1, g2); + } + + if (!g3) GEOS_FREE_AND_FAIL(g1); + GEOSSetSRID(g3, srid); + + if (!(result = GEOS2LWGEOM(g3, is3d))) GEOS_FREE_AND_FAIL(g1, g2, g3); + + GEOS_FREE(g1, g2, g3); + return result; +} + +LWGEOM* +lwgeom_linemerge(const LWGEOM* geom) +{ + return lwgeom_linemerge_directed(geom, LW_FALSE); +} + +LWGEOM* +lwgeom_linemerge_directed(const LWGEOM* geom, int directed) +{ + LWGEOM* result; + int32_t srid = RESULT_SRID(geom); + uint8_t is3d = FLAGS_GET_Z(geom->flags); + GEOSGeometry* g1; + GEOSGeometry* g3; + + if (srid == SRID_INVALID) return NULL; + + /* Empty.Linemerge() == Empty */ + if (lwgeom_is_empty(geom)) return lwgeom_clone_deep(geom); /* match empty type to linestring? */ + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL(); + + if (directed) + { +#if POSTGIS_GEOS_VERSION < 31100 + lwgeom_geos_error_minversion("Directed line merging", "3.11"); + GEOS_FREE_AND_FAIL(g1); + return NULL; +#else + g3 = GEOSLineMergeDirected(g1); +#endif + } + else + { + g3 = GEOSLineMerge(g1); + } + + if (!g3) GEOS_FREE_AND_FAIL(g1); + GEOSSetSRID(g3, srid); + + if (!(result = GEOS2LWGEOM(g3, is3d))) + GEOS_FREE_AND_FAIL(g1, g3); + + GEOS_FREE(g1, g3); + + return result; +} + +LWGEOM* +lwgeom_unaryunion(const LWGEOM* geom) +{ + return lwgeom_unaryunion_prec(geom, -1.0); +} + +LWGEOM* +lwgeom_unaryunion_prec(const LWGEOM* geom, double prec) +{ + LWGEOM* result; + int32_t srid = RESULT_SRID(geom); + uint8_t is3d = FLAGS_GET_Z(geom->flags); + GEOSGeometry* g1; + GEOSGeometry* g3; + + if (srid == SRID_INVALID) return NULL; + + /* Empty.UnaryUnion() == Empty */ + if (lwgeom_is_empty(geom)) return lwgeom_clone_deep(geom); + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL(); + + if ( prec >= 0) { +#if POSTGIS_GEOS_VERSION < 30900 + lwgeom_geos_error_minversion("Fixed-precision unary union", "3.9"); + GEOS_FREE_AND_FAIL(g1); + return NULL; +#else + g3 = GEOSUnaryUnionPrec(g1, prec); +#endif + } + else + { + g3 = GEOSUnaryUnion(g1); + } + + if (!g3) GEOS_FREE_AND_FAIL(g1); + GEOSSetSRID(g3, srid); + + if (!(result = GEOS2LWGEOM(g3, is3d))) + GEOS_FREE_AND_FAIL(g1, g3); + + GEOS_FREE(g1, g3); + + return result; +} + +LWGEOM* +lwgeom_difference(const LWGEOM* geom1, const LWGEOM* geom2) +{ + return lwgeom_difference_prec(geom1, geom2, -1.0); +} + +LWGEOM* +lwgeom_difference_prec(const LWGEOM* geom1, const LWGEOM* geom2, double prec) +{ + LWGEOM* result; + int32_t srid = RESULT_SRID(geom1, geom2); + uint8_t is3d = (FLAGS_GET_Z(geom1->flags) || FLAGS_GET_Z(geom2->flags)); + GEOSGeometry *g1, *g2, *g3; + + if (srid == SRID_INVALID) return NULL; + + /* A.Intersection(Empty) == Empty */ + if (lwgeom_is_empty(geom2)) return lwgeom_clone_deep(geom1); /* match empty type? */ + + /* Empty.Intersection(A) == Empty */ + if (lwgeom_is_empty(geom1)) return lwgeom_clone_deep(geom1); /* match empty type? */ + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g1 = LWGEOM2GEOS(geom1, AUTOFIX))) GEOS_FAIL(); + if (!(g2 = LWGEOM2GEOS(geom2, AUTOFIX))) GEOS_FREE_AND_FAIL(g1); + + if ( prec >= 0) { +#if POSTGIS_GEOS_VERSION < 30900 + lwgeom_geos_error_minversion("Fixed-precision difference", "3.9"); + GEOS_FREE_AND_FAIL(g1, g2); + return NULL; +#else + g3 = GEOSDifferencePrec(g1, g2, prec); +#endif + } + else + { + g3 = GEOSDifference(g1, g2); + } + + if (!g3) GEOS_FREE_AND_FAIL(g1, g2); + GEOSSetSRID(g3, srid); + + if (!(result = GEOS2LWGEOM(g3, is3d))) + GEOS_FREE_AND_FAIL(g1, g2, g3); + + GEOS_FREE(g1, g2, g3); + return result; +} + +LWGEOM* +lwgeom_symdifference(const LWGEOM* geom1, const LWGEOM* geom2) +{ + return lwgeom_symdifference_prec(geom1, geom2, -1.0); +} + +LWGEOM* +lwgeom_symdifference_prec(const LWGEOM* geom1, const LWGEOM* geom2, double prec) +{ + LWGEOM* result; + int32_t srid = RESULT_SRID(geom1, geom2); + uint8_t is3d = (FLAGS_GET_Z(geom1->flags) || FLAGS_GET_Z(geom2->flags)); + GEOSGeometry *g1, *g2, *g3; + + if (srid == SRID_INVALID) return NULL; + + /* A.SymDifference(Empty) == A */ + if (lwgeom_is_empty(geom2)) return lwgeom_clone_deep(geom1); + + /* Empty.DymDifference(B) == B */ + if (lwgeom_is_empty(geom1)) return lwgeom_clone_deep(geom2); + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g1 = LWGEOM2GEOS(geom1, AUTOFIX))) GEOS_FAIL(); + if (!(g2 = LWGEOM2GEOS(geom2, AUTOFIX))) GEOS_FREE_AND_FAIL(g1); + + if ( prec >= 0) { +#if POSTGIS_GEOS_VERSION < 30900 + lwgeom_geos_error_minversion("Fixed-precision symdifference", "3.9"); + GEOS_FREE_AND_FAIL(g1, g2); + return NULL; +#else + g3 = GEOSSymDifferencePrec(g1, g2, prec); +#endif + } + else + { + g3 = GEOSSymDifference(g1, g2); + } + + if (!g3) GEOS_FREE_AND_FAIL(g1, g2); + GEOSSetSRID(g3, srid); + + if (!(result = GEOS2LWGEOM(g3, is3d))) + GEOS_FREE_AND_FAIL(g1, g2, g3); + + GEOS_FREE(g1, g2, g3); + return result; +} + +LWGEOM* +lwgeom_centroid(const LWGEOM* geom) +{ + LWGEOM* result; + int32_t srid = RESULT_SRID(geom); + uint8_t is3d = FLAGS_GET_Z(geom->flags); + GEOSGeometry *g1, *g3; + + if (srid == SRID_INVALID) return NULL; + + if (lwgeom_is_empty(geom)) + { + LWPOINT* lwp = lwpoint_construct_empty(srid, is3d, lwgeom_has_m(geom)); + return lwpoint_as_lwgeom(lwp); + } + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL(); + + g3 = GEOSGetCentroid(g1); + + if (!g3) GEOS_FREE_AND_FAIL(g1); + GEOSSetSRID(g3, srid); + + if (!(result = GEOS2LWGEOM(g3, is3d))) + GEOS_FREE_AND_FAIL(g1); + + GEOS_FREE(g1, g3); + + return result; +} + +LWGEOM* +lwgeom_reduceprecision(const LWGEOM* geom, double gridSize) +{ +#if POSTGIS_GEOS_VERSION < 30900 + lwgeom_geos_error_minversion("Precision reduction", "3.9"); + return NULL; +#else + + LWGEOM* result; + int32_t srid = RESULT_SRID(geom); + uint8_t is3d = FLAGS_GET_Z(geom->flags); + GEOSGeometry *g1, *g3; + + if (srid == SRID_INVALID) return NULL; + + if (lwgeom_is_empty(geom)) + return lwgeom_clone_deep(geom); + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL(); + + g3 = GEOSGeom_setPrecision(g1, gridSize, 0); + + if (!g3) GEOS_FREE_AND_FAIL(g1); + GEOSSetSRID(g3, srid); + + if (!(result = GEOS2LWGEOM(g3, is3d))) + GEOS_FREE_AND_FAIL(g1); + + GEOS_FREE(g1, g3); + + return result; +#endif +} + +LWGEOM * +lwgeom_pointonsurface(const LWGEOM *geom) +{ + LWGEOM *result; + int32_t srid = RESULT_SRID(geom); + uint8_t is3d = FLAGS_GET_Z(geom->flags); + GEOSGeometry *g1, *g3; + + if (srid == SRID_INVALID) return NULL; + + if (lwgeom_is_empty(geom)) + { + LWPOINT *lwp = lwpoint_construct_empty(srid, is3d, lwgeom_has_m(geom)); + return lwpoint_as_lwgeom(lwp); + } + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL(); + + g3 = GEOSPointOnSurface(g1); + + if (!g3) GEOS_FREE_AND_FAIL(g1); + GEOSSetSRID(g3, srid); + + if (!(result = GEOS2LWGEOM(g3, is3d))) + GEOS_FREE_AND_FAIL(g1, g3); + + GEOS_FREE(g1, g3); + + return result; +} + +LWGEOM* +lwgeom_union(const LWGEOM* g1, const LWGEOM* g2) +{ + return lwgeom_union_prec(g1, g2, -1.0); +} + +LWGEOM* +lwgeom_union_prec(const LWGEOM* geom1, const LWGEOM* geom2, double gridSize) +{ + LWGEOM* result; + int32_t srid = RESULT_SRID(geom1, geom2); + uint8_t is3d = (FLAGS_GET_Z(geom1->flags) || FLAGS_GET_Z(geom2->flags)); + GEOSGeometry *g1, *g2, *g3; + + if (srid == SRID_INVALID) return NULL; + + /* A.Union(empty) == A */ + if (lwgeom_is_empty(geom1)) return lwgeom_clone_deep(geom2); + + /* B.Union(empty) == B */ + if (lwgeom_is_empty(geom2)) return lwgeom_clone_deep(geom1); + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g1 = LWGEOM2GEOS(geom1, AUTOFIX))) GEOS_FAIL(); + if (!(g2 = LWGEOM2GEOS(geom2, AUTOFIX))) GEOS_FREE_AND_FAIL(g1); + + if ( gridSize >= 0) { +#if POSTGIS_GEOS_VERSION < 30900 + lwgeom_geos_error_minversion("Fixed-precision union", "3.9"); + GEOS_FREE_AND_FAIL(g1, g2); + return NULL; +#else + g3 = GEOSUnionPrec(g1, g2, gridSize); +#endif + } + else + { + g3 = GEOSUnion(g1, g2); + } + + if (!g3) GEOS_FREE_AND_FAIL(g1, g2); + GEOSSetSRID(g3, srid); + + if (!(result = GEOS2LWGEOM(g3, is3d))) + GEOS_FREE_AND_FAIL(g1, g2, g3); + + GEOS_FREE(g1, g2, g3); + return result; +} + +LWGEOM * +lwgeom_clip_by_rect(const LWGEOM *geom1, double x1, double y1, double x2, double y2) +{ + LWGEOM *result; + GEOSGeometry *g1, *g3; + int is3d; + + /* A.Intersection(Empty) == Empty */ + if ( lwgeom_is_empty(geom1) ) + return lwgeom_clone_deep(geom1); + + is3d = FLAGS_GET_Z(geom1->flags); + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g1 = LWGEOM2GEOS(geom1, AUTOFIX))) + GEOS_FAIL_DEBUG(); + + if (!(g3 = GEOSClipByRect(g1, x1, y1, x2, y2))) + GEOS_FREE_AND_FAIL_DEBUG(g1); + + GEOS_FREE(g1); + result = GEOS2LWGEOM(g3, is3d); + GEOS_FREE(g3); + + if (!result) + GEOS_FAIL_DEBUG(); + + result->srid = geom1->srid; + + return result; +} + +/* ------------ BuildArea stuff ---------------------------------------------------------------------{ */ +#if POSTGIS_GEOS_VERSION < 30800 +typedef struct Face_t +{ + const GEOSGeometry* geom; + GEOSGeometry* env; + double envarea; + struct Face_t* parent; /* if this face is an hole of another one, or NULL */ +} Face; + +static Face* newFace(const GEOSGeometry* g); +static void delFace(Face* f); +static unsigned int countParens(const Face* f); +static void findFaceHoles(Face** faces, int nfaces); + +static Face* +newFace(const GEOSGeometry* g) +{ + Face* f = lwalloc(sizeof(Face)); + f->geom = g; + f->env = GEOSEnvelope(f->geom); + GEOSArea(f->env, &f->envarea); + f->parent = NULL; + return f; +} + +static unsigned int +countParens(const Face* f) +{ + unsigned int pcount = 0; + while (f->parent) + { + ++pcount; + f = f->parent; + } + return pcount; +} + +/* Destroy the face and release memory associated with it */ +static void +delFace(Face* f) +{ + GEOSGeom_destroy(f->env); + lwfree(f); +} + +static int +compare_by_envarea(const void* g1, const void* g2) +{ + Face* f1 = *(Face**)g1; + Face* f2 = *(Face**)g2; + double n1 = f1->envarea; + double n2 = f2->envarea; + + if (n1 < n2) return 1; + if (n1 > n2) return -1; + return 0; +} + +/* Find holes of each face */ +static void +findFaceHoles(Face** faces, int nfaces) +{ + int i, j, h; + + /* We sort by envelope area so that we know holes are only after their shells */ + qsort(faces, nfaces, sizeof(Face*), compare_by_envarea); + for (i = 0; i < nfaces; ++i) + { + Face* f = faces[i]; + int nholes = GEOSGetNumInteriorRings(f->geom); + LWDEBUGF(2, "Scanning face %d with env area %g and %d holes", i, f->envarea, nholes); + for (h = 0; h < nholes; ++h) + { + const GEOSGeometry* hole = GEOSGetInteriorRingN(f->geom, h); + LWDEBUGF(2, + "Looking for hole %d/%d of face %d among %d other faces", + h + 1, + nholes, + i, + nfaces - i - 1); + for (j = i + 1; j < nfaces; ++j) + { + const GEOSGeometry* f2er; + Face* f2 = faces[j]; + if (f2->parent) continue; /* hole already assigned */ + f2er = GEOSGetExteriorRing(f2->geom); + /* TODO: can be optimized as the ring would have the same vertices, possibly in + * different order. Maybe comparing number of points could already be useful. */ + if (GEOSEquals(f2er, hole)) + { + LWDEBUGF(2, "Hole %d/%d of face %d is face %d", h + 1, nholes, i, j); + f2->parent = f; + break; + } + } + } + } +} + +static GEOSGeometry* +collectFacesWithEvenAncestors(Face** faces, int nfaces) +{ + GEOSGeometry** geoms = lwalloc(sizeof(GEOSGeometry*) * nfaces); + GEOSGeometry* ret; + unsigned int ngeoms = 0; + int i; + + for (i = 0; i < nfaces; ++i) + { + Face* f = faces[i]; + if (countParens(f) % 2) continue; /* we skip odd parents geoms */ + geoms[ngeoms++] = GEOSGeom_clone(f->geom); + } + + ret = GEOSGeom_createCollection(GEOS_MULTIPOLYGON, geoms, ngeoms); + lwfree(geoms); + return ret; +} + +GEOSGeometry* +LWGEOM_GEOS_buildArea(const GEOSGeometry* geom_in) +{ + GEOSGeometry* tmp; + GEOSGeometry *geos_result, *shp; + GEOSGeometry const* vgeoms[1]; + uint32_t i, ngeoms; + int srid = GEOSGetSRID(geom_in); + Face** geoms; +#if POSTGIS_DEBUG_LEVEL >= 3 + LWGEOM *geos_geom; + char *geom_ewkt; +#endif + + vgeoms[0] = geom_in; + geos_result = GEOSPolygonize(vgeoms, 1); + + LWDEBUGF(3, "GEOSpolygonize returned @ %p", geos_result); + + /* Null return from GEOSpolygonize (an exception) */ + if (!geos_result) return 0; + + /* We should now have a collection */ +#if PARANOIA_LEVEL > 0 + if (GEOSGeomTypeId(geos_result) != COLLECTIONTYPE) + { + GEOSGeom_destroy(geos_result); + lwerror("%s [%d] Unexpected return from GEOSpolygonize", __FILE__, __LINE__); + return 0; + } +#endif + + ngeoms = GEOSGetNumGeometries(geos_result); + +#if POSTGIS_DEBUG_LEVEL >= 3 + LWDEBUGF(3, "GEOSpolygonize: ngeoms in polygonize output: %d", ngeoms); + geos_geom = GEOS2LWGEOM(geos_result, 0); + geom_ewkt = lwgeom_to_ewkt(geos_geom); + LWDEBUGF(3, "GEOSpolygonize: polygonized:%s", geom_ewkt); + lwgeom_free(geos_geom); + lwfree(geom_ewkt); +#endif + + /* No geometries in collection, early out */ + if (ngeoms == 0) + { + GEOSSetSRID(geos_result, srid); + return geos_result; + } + + /* Return first geometry if we only have one in collection, to avoid the unnecessary Geometry clone below. */ + if (ngeoms == 1) + { + tmp = (GEOSGeometry*)GEOSGetGeometryN(geos_result, 0); + if (!tmp) + { + GEOSGeom_destroy(geos_result); + return 0; /* exception */ + } + shp = GEOSGeom_clone(tmp); + GEOSGeom_destroy(geos_result); /* only safe after the clone above */ + GEOSSetSRID(shp, srid); + return shp; + } + + LWDEBUGF(2, "Polygonize returned %d geoms", ngeoms); + + /* + * Polygonizer returns a polygon for each face in the built topology. + * + * This means that for any face with holes we'll have other faces representing each hole. We can imagine a + * parent-child relationship between these faces. + * + * In order to maximize the number of visible rings in output we only use those faces which have an even number + * of parents. + * + * Example: + * + * +---------------+ + * | L0 | L0 has no parents + * | +---------+ | + * | | L1 | | L1 is an hole of L0 + * | | +---+ | | + * | | |L2 | | | L2 is an hole of L1 (which is an hole of L0) + * | | | | | | + * | | +---+ | | + * | +---------+ | + * | | + * +---------------+ + * + * See http://trac.osgeo.org/postgis/ticket/1806 + * + */ + + /* Prepare face structures for later analysis */ + geoms = lwalloc(sizeof(Face**) * ngeoms); + for (i = 0; i < ngeoms; ++i) + geoms[i] = newFace(GEOSGetGeometryN(geos_result, i)); + + /* Find faces representing other faces holes */ + findFaceHoles(geoms, ngeoms); + + /* Build a MultiPolygon composed only by faces with an even number of ancestors */ + tmp = collectFacesWithEvenAncestors(geoms, ngeoms); + + /* Cleanup face structures */ + for (i = 0; i < ngeoms; ++i) + delFace(geoms[i]); + lwfree(geoms); + + /* Faces referenced memory owned by geos_result. It is safe to destroy geos_result after deleting them. */ + GEOSGeom_destroy(geos_result); + + /* Run a single overlay operation to dissolve shared edges */ + shp = GEOSUnionCascaded(tmp); + if (!shp) + { + GEOSGeom_destroy(tmp); + return 0; /* exception */ + } + + GEOSGeom_destroy(tmp); + + GEOSSetSRID(shp, srid); + + return shp; +} +#endif + +LWGEOM* +lwgeom_buildarea(const LWGEOM* geom) +{ + LWGEOM* result; + int32_t srid = RESULT_SRID(geom); + uint8_t is3d = FLAGS_GET_Z(geom->flags); + GEOSGeometry *g1, *g3; + + if (srid == SRID_INVALID) return NULL; + + /* Can't build an area from an empty! */ + if (lwgeom_is_empty(geom)) return (LWGEOM*)lwpoly_construct_empty(srid, is3d, 0); + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL(); + +#if POSTGIS_GEOS_VERSION < 30800 + g3 = LWGEOM_GEOS_buildArea(g1); +#else + g3 = GEOSBuildArea(g1); +#endif + + if (!g3) GEOS_FREE_AND_FAIL(g1); + GEOSSetSRID(g3, srid); + + /* If no geometries are in result collection, return NULL */ + if (GEOSGetNumGeometries(g3) == 0) + { + GEOS_FREE(g1); + return NULL; + } + + if (!(result = GEOS2LWGEOM(g3, is3d))) + GEOS_FREE_AND_FAIL(g1, g3); + + GEOS_FREE(g1, g3); + + return result; +} + +/* ------------ end of BuildArea stuff ---------------------------------------------------------------------} */ + +int +lwgeom_is_simple(const LWGEOM* geom) +{ + GEOSGeometry* g; + int simple; + + /* Empty is always simple */ + if (lwgeom_is_empty(geom)) return LW_TRUE; + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g = LWGEOM2GEOS(geom, AUTOFIX))) return -1; + + simple = GEOSisSimple(g); + GEOSGeom_destroy(g); + + if (simple == 2) /* exception thrown */ + { + lwerror("lwgeom_is_simple: %s", lwgeom_geos_errmsg); + return -1; + } + + return simple ? LW_TRUE : LW_FALSE; +} + +LWGEOM* +lwgeom_geos_noop(const LWGEOM* geom) +{ + LWGEOM* result; + int32_t srid = RESULT_SRID(geom); + uint8_t is3d = FLAGS_GET_Z(geom->flags); + GEOSGeometry* g; + + if (srid == SRID_INVALID) return NULL; + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL(); + + if (!g) GEOS_FREE_AND_FAIL(g); + GEOSSetSRID(g, srid); + + if (!(result = GEOS2LWGEOM(g, is3d))) + GEOS_FREE_AND_FAIL(g); + + GEOS_FREE(g); + + return result; +} + +LWGEOM* +lwgeom_snap(const LWGEOM* geom1, const LWGEOM* geom2, double tolerance) +{ + LWGEOM* result; + int32_t srid = RESULT_SRID(geom1, geom2); + uint8_t is3d = (FLAGS_GET_Z(geom1->flags) || FLAGS_GET_Z(geom2->flags)); + GEOSGeometry *g1, *g2, *g3; + + if (srid == SRID_INVALID) return NULL; + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g1 = LWGEOM2GEOS(geom1, AUTOFIX))) GEOS_FAIL(); + if (!(g2 = LWGEOM2GEOS(geom2, AUTOFIX))) GEOS_FREE_AND_FAIL(g1); + + g3 = GEOSSnap(g1, g2, tolerance); + + if (!g3) GEOS_FREE_AND_FAIL(g1, g2); + GEOSSetSRID(g3, srid); + + if (!(result = GEOS2LWGEOM(g3, is3d))) + GEOS_FREE_AND_FAIL(g1, g2, g3); + + GEOS_FREE(g1, g2, g3); + return result; +} + +LWGEOM* +lwgeom_sharedpaths(const LWGEOM* geom1, const LWGEOM* geom2) +{ + LWGEOM* result; + int32_t srid = RESULT_SRID(geom1, geom2); + uint8_t is3d = (FLAGS_GET_Z(geom1->flags) || FLAGS_GET_Z(geom2->flags)); + GEOSGeometry *g1, *g2, *g3; + + if (srid == SRID_INVALID) return NULL; + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g1 = LWGEOM2GEOS(geom1, AUTOFIX))) GEOS_FAIL(); + if (!(g2 = LWGEOM2GEOS(geom2, AUTOFIX))) GEOS_FREE_AND_FAIL(g1); + + g3 = GEOSSharedPaths(g1, g2); + + if (!g3) GEOS_FREE_AND_FAIL(g1, g2); + GEOSSetSRID(g3, srid); + + if (!(result = GEOS2LWGEOM(g3, is3d))) + GEOS_FREE_AND_FAIL(g1, g2, g3); + + GEOS_FREE(g1, g2, g3); + return result; +} + +static LWGEOM * +lwline_offsetcurve(const LWLINE *lwline, double size, int quadsegs, int joinStyle, double mitreLimit) +{ + LWGEOM* result; + LWGEOM* geom = lwline_as_lwgeom(lwline); + int32_t srid = RESULT_SRID(geom); + uint8_t is3d = FLAGS_GET_Z(geom->flags); + GEOSGeometry *g1, *g3; + + if (srid == SRID_INVALID) return NULL; + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL(); + + g3 = GEOSOffsetCurve(g1, size, quadsegs, joinStyle, mitreLimit); + + if (!g3) + { + GEOS_FREE(g1); + return NULL; + } + + GEOSSetSRID(g3, srid); + + if (!(result = GEOS2LWGEOM(g3, is3d))) + GEOS_FREE_AND_FAIL(g1, g3); + + GEOS_FREE(g1, g3); + return result; +} + +static LWGEOM * +lwcollection_offsetcurve(const LWCOLLECTION *col, double size, int quadsegs, int joinStyle, double mitreLimit) +{ + const LWGEOM *geom = lwcollection_as_lwgeom(col); + int32_t srid = RESULT_SRID(geom); + uint8_t is3d = FLAGS_GET_Z(col->flags); + LWCOLLECTION *result; + LWGEOM *tmp; + uint32_t i; + if (srid == SRID_INVALID) return NULL; + + result = lwcollection_construct_empty(MULTILINETYPE, srid, is3d, LW_FALSE); + + for (i = 0; i < col->ngeoms; i++) + { + tmp = lwgeom_offsetcurve(col->geoms[i], size, quadsegs, joinStyle, mitreLimit); + + if (!tmp) + { + lwcollection_free(result); + return NULL; + } + + if (!lwgeom_is_empty(tmp)) + { + if (lwgeom_is_collection(tmp)) + result = lwcollection_concat_in_place(result, lwgeom_as_lwcollection(tmp)); + else + result = lwcollection_add_lwgeom(result, tmp); + + if (!result) + { + lwgeom_free(tmp); + return NULL; + } + } + } + + if (result->ngeoms == 1) + { + tmp = result->geoms[0]; + lwcollection_release(result); + return tmp; + } + else + return lwcollection_as_lwgeom(result); +} + +LWGEOM* +lwgeom_offsetcurve(const LWGEOM* geom, double size, int quadsegs, int joinStyle, double mitreLimit) +{ + int32_t srid = RESULT_SRID(geom); + LWGEOM *result = NULL; + LWGEOM *noded = NULL; + if (srid == SRID_INVALID) return NULL; + + if (lwgeom_dimension(geom) != 1) + { + lwerror("%s: input is not linear", __func__, lwtype_name(geom->type)); + return NULL; + } + + while (!result) + { + switch (geom->type) + { + case LINETYPE: + result = lwline_offsetcurve(lwgeom_as_lwline(geom), size, quadsegs, joinStyle, mitreLimit); + break; + case COLLECTIONTYPE: + case MULTILINETYPE: + result = lwcollection_offsetcurve(lwgeom_as_lwcollection(geom), size, quadsegs, joinStyle, mitreLimit); + break; + default: + lwerror("%s: unsupported geometry type: %s", __func__, lwtype_name(geom->type)); + return NULL; + } + + if (result) + { + if (noded) lwgeom_free(noded); + return result; + } + else if (!noded) + { + noded = lwgeom_node(geom); + if (!noded) + { + lwerror("lwgeom_offsetcurve: cannot node input"); + return NULL; + } + geom = noded; + } + else + { + lwgeom_free(noded); + lwerror("lwgeom_offsetcurve: noded geometry cannot be offset"); + return NULL; + } + } + + return result; +} + +LWMPOINT* +lwpoly_to_points(const LWPOLY* lwpoly, uint32_t npoints, int32_t seed) +{ + double area, bbox_area, bbox_width, bbox_height; + GBOX bbox; + const LWGEOM* lwgeom = (LWGEOM*)lwpoly; + uint32_t sample_npoints, sample_sqrt, sample_width, sample_height; + double sample_cell_size; + uint32_t i, j, n; + uint32_t iterations = 0; + uint32_t npoints_generated = 0; + uint32_t npoints_tested = 0; + GEOSGeometry* g; + const GEOSPreparedGeometry* gprep; + GEOSGeometry* gpt; + GEOSCoordSequence* gseq; + LWMPOINT* mpt; + int32_t srid = lwgeom_get_srid(lwgeom); + int done = 0; + int* cells; + const size_t size = 2 * sizeof(int); + char tmp[2 * sizeof(int)]; + const size_t stride = 2 * sizeof(int); + + if (lwgeom_get_type(lwgeom) != POLYGONTYPE) + { + lwerror("%s: only polygons supported", __func__); + return NULL; + } + + if (npoints == 0 || lwgeom_is_empty(lwgeom)) return NULL; + + if (!lwpoly->bbox) + lwgeom_calculate_gbox(lwgeom, &bbox); + else + bbox = *(lwpoly->bbox); + + area = lwpoly_area(lwpoly); + bbox_width = bbox.xmax - bbox.xmin; + bbox_height = bbox.ymax - bbox.ymin; + bbox_area = bbox_width * bbox_height; + + if (area == 0.0 || bbox_area == 0.0) + { + lwerror("%s: zero area input polygon, TBD", __func__); + return NULL; + } + + /* Gross up our test set a bit (but not too much) to increase + * odds of getting coverage in one pass */ + sample_npoints = npoints * FP_MIN(bbox_area / area, 10000.0); + + /* We're going to generate points using a sample grid as described + * http://lin-ear-th-inking.blogspot.ca/2010/05/more-random-points-in-jts.html to try and get a more uniform + * "random" set of points. So we have to figure out how to stick a grid into our box */ + sample_sqrt = lround(sqrt(sample_npoints)); + if (sample_sqrt == 0) + sample_sqrt = 1; + + /* Calculate the grids we're going to randomize within */ + if (bbox_width > bbox_height) + { + sample_width = sample_sqrt; + sample_height = ceil((double)sample_npoints / (double)sample_width); + sample_cell_size = bbox_width / sample_width; + } + else + { + sample_height = sample_sqrt; + sample_width = ceil((double)sample_npoints / (double)sample_height); + sample_cell_size = bbox_height / sample_height; + } + + /* Prepare the polygon for fast true/false testing */ + initGEOS(lwnotice, lwgeom_geos_error); + g = (GEOSGeometry*)LWGEOM2GEOS(lwgeom, 0); + if (!g) + { + lwerror("%s: Geometry could not be converted to GEOS: %s", __func__, lwgeom_geos_errmsg); + return NULL; + } + gprep = GEOSPrepare(g); + + /* Get an empty multi-point ready to return */ + mpt = lwmpoint_construct_empty(srid, 0, 0); + + /* Initiate random number generator. + * Repeatable numbers are generated with seed values >= 1. + * When seed is zero and has not previously been set, it is based on + * Unix time (seconds) and process ID. */ + lwrandom_set_seed(seed); + + /* Now we fill in an array of cells, and then shuffle that array, */ + /* so we can visit the cells in random order to avoid visual ugliness */ + /* caused by visiting them sequentially */ + cells = lwalloc(2 * sizeof(int) * sample_height * sample_width); + for (i = 0; i < sample_width; i++) + { + for (j = 0; j < sample_height; j++) + { + cells[2 * (i * sample_height + j)] = i; + cells[2 * (i * sample_height + j) + 1] = j; + } + } + + /* Fisher-Yates shuffle */ + n = sample_height * sample_width; + if (n > 1) + { + for (i = n - 1; i > 0; i--) + { + size_t j = (size_t)(lwrandom_uniform() * (i + 1)); + + memcpy(tmp, (char *)cells + j * stride, size); + memcpy((char *)cells + j * stride, (char *)cells + i * stride, size); + memcpy((char *)cells + i * stride, tmp, size); + } + } + + /* Start testing points */ + while (npoints_generated < npoints) + { + iterations++; + for (i = 0; i < sample_width * sample_height; i++) + { + int contains = 0; + double y = bbox.ymin + cells[2 * i] * sample_cell_size; + double x = bbox.xmin + cells[2 * i + 1] * sample_cell_size; + x += lwrandom_uniform() * sample_cell_size; + y += lwrandom_uniform() * sample_cell_size; + if (x >= bbox.xmax || y >= bbox.ymax) continue; + + gseq = GEOSCoordSeq_create(1, 2); +#if POSTGIS_GEOS_VERSION < 30800 + GEOSCoordSeq_setX(gseq, 0, x); + GEOSCoordSeq_setY(gseq, 0, y); +#else + GEOSCoordSeq_setXY(gseq, 0, x, y); +#endif + gpt = GEOSGeom_createPoint(gseq); + + contains = GEOSPreparedIntersects(gprep, gpt); + + GEOSGeom_destroy(gpt); + + if (contains == 2) + { + GEOSPreparedGeom_destroy(gprep); + GEOSGeom_destroy(g); + lwerror("%s: GEOS exception on PreparedContains: %s", __func__, lwgeom_geos_errmsg); + return NULL; + } + if (contains) + { + npoints_generated++; + mpt = lwmpoint_add_lwpoint(mpt, lwpoint_make2d(srid, x, y)); + if (npoints_generated == npoints) + { + done = 1; + break; + } + } + + /* Short-circuit check for ctrl-c occasionally */ + npoints_tested++; + if (npoints_tested % 10000 == 0) + LW_ON_INTERRUPT(GEOSPreparedGeom_destroy(gprep); GEOSGeom_destroy(g); return NULL); + + if (done) break; + } + if (done || iterations > 100) break; + } + + GEOSPreparedGeom_destroy(gprep); + GEOSGeom_destroy(g); + lwfree(cells); + + return mpt; +} + +/* Allocate points to sub-geometries by area, then call lwgeom_poly_to_points and bundle up final result in a single + * multipoint. */ +LWMPOINT* +lwmpoly_to_points(const LWMPOLY* lwmpoly, uint32_t npoints, int32_t seed) +{ + const LWGEOM* lwgeom = (LWGEOM*)lwmpoly; + double area; + uint32_t i; + LWMPOINT* mpt = NULL; + + if (lwgeom_get_type(lwgeom) != MULTIPOLYGONTYPE) + { + lwerror("%s: only multipolygons supported", __func__); + return NULL; + } + if (npoints == 0 || lwgeom_is_empty(lwgeom)) return NULL; + + area = lwgeom_area(lwgeom); + + for (i = 0; i < lwmpoly->ngeoms; i++) + { + double sub_area = lwpoly_area(lwmpoly->geoms[i]); + int sub_npoints = lround(npoints * sub_area / area); + if (sub_npoints > 0) + { + LWMPOINT* sub_mpt = lwpoly_to_points(lwmpoly->geoms[i], sub_npoints, seed); + if (!mpt) + mpt = sub_mpt; + else + { + uint32_t j; + for (j = 0; j < sub_mpt->ngeoms; j++) + mpt = lwmpoint_add_lwpoint(mpt, sub_mpt->geoms[j]); + /* Just free the shell, leave the underlying lwpoints alone, as they are now owned by + * the returning multipoint */ + lwfree(sub_mpt->geoms); + lwgeom_release((LWGEOM*)sub_mpt); + } + } + } + return mpt; +} + +LWMPOINT* +lwgeom_to_points(const LWGEOM* lwgeom, uint32_t npoints, int32_t seed) +{ + switch (lwgeom_get_type(lwgeom)) + { + case MULTIPOLYGONTYPE: + return lwmpoly_to_points((LWMPOLY*)lwgeom, npoints, seed); + case POLYGONTYPE: + return lwpoly_to_points((LWPOLY*)lwgeom, npoints, seed); + default: + lwerror("%s: unsupported geometry type '%s'", __func__, lwtype_name(lwgeom_get_type(lwgeom))); + return NULL; + } +} + +LWTIN* +lwtin_from_geos(const GEOSGeometry* geom, uint8_t want3d) +{ + int type = GEOSGeomTypeId(geom); + int SRID = GEOSGetSRID(geom); + + /* GEOS's 0 is equivalent to our unknown as for SRID values */ + if (SRID == 0) SRID = SRID_UNKNOWN; + + if (want3d && !GEOSHasZ(geom)) + { + LWDEBUG(3, "Geometry has no Z, won't provide one"); + want3d = 0; + } + + switch (type) + { + LWTRIANGLE** geoms; + uint32_t i, ngeoms; + case GEOS_GEOMETRYCOLLECTION: + LWDEBUG(4, "lwgeom_from_geometry: it's a Collection or Multi"); + + ngeoms = GEOSGetNumGeometries(geom); + geoms = NULL; + if (ngeoms) + { + geoms = lwalloc(ngeoms * sizeof *geoms); + if (!geoms) + { + lwerror("lwtin_from_geos: can't allocate geoms"); + return NULL; + } + for (i = 0; i < ngeoms; i++) + { + const GEOSGeometry *poly, *ring; + const GEOSCoordSequence* cs; + POINTARRAY* pa; + + poly = GEOSGetGeometryN(geom, i); + ring = GEOSGetExteriorRing(poly); + cs = GEOSGeom_getCoordSeq(ring); + pa = ptarray_from_GEOSCoordSeq(cs, want3d); + + geoms[i] = lwtriangle_construct(SRID, NULL, pa); + } + } + return (LWTIN*)lwcollection_construct(TINTYPE, SRID, NULL, ngeoms, (LWGEOM**)geoms); + case GEOS_POLYGON: + case GEOS_MULTIPOINT: + case GEOS_MULTILINESTRING: + case GEOS_MULTIPOLYGON: + case GEOS_LINESTRING: + case GEOS_LINEARRING: + case GEOS_POINT: + lwerror("lwtin_from_geos: invalid geometry type for tin: %d", type); + break; + + default: + lwerror("GEOS2LWGEOM: unknown geometry type: %d", type); + return NULL; + } + + /* shouldn't get here */ + return NULL; +} +/* + * output = 1 for edges, 2 for TIN, 0 for polygons + */ +LWGEOM* +lwgeom_delaunay_triangulation(const LWGEOM* geom, double tolerance, int32_t output) +{ + LWGEOM* result; + int32_t srid = RESULT_SRID(geom); + uint8_t is3d = FLAGS_GET_Z(geom->flags); + GEOSGeometry *g1, *g3; + + if (output < 0 || output > 2) + { + lwerror("%s: invalid output type specified %d", __func__, output); + return NULL; + } + + if (srid == SRID_INVALID) return NULL; + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL(); + + /* if output != 1 we want polys */ + g3 = GEOSDelaunayTriangulation(g1, tolerance, output == 1); + + if (!g3) GEOS_FREE_AND_FAIL(g1); + GEOSSetSRID(g3, srid); + + if (output == 2) + { + result = (LWGEOM*)lwtin_from_geos(g3, is3d); + if (!result) + { + GEOS_FREE(g1, g3); + lwerror("%s: cannot convert output geometry", __func__); + return NULL; + } + lwgeom_set_srid(result, srid); + } + else if (!(result = GEOS2LWGEOM(g3, is3d))) + GEOS_FREE_AND_FAIL(g1, g3); + + GEOS_FREE(g1, g3); + return result; +} + + +static GEOSCoordSequence* +lwgeom_get_geos_coordseq_2d(const LWGEOM* g, uint32_t num_points) +{ + uint32_t i = 0; + uint8_t num_dims = 2; + LWPOINTITERATOR* it; + GEOSCoordSequence* coords; + POINT4D tmp; + + coords = GEOSCoordSeq_create(num_points, num_dims); + if (!coords) return NULL; + + it = lwpointiterator_create(g); + while (lwpointiterator_next(it, &tmp)) + { + if (i >= num_points) + { + lwerror("Incorrect num_points provided to lwgeom_get_geos_coordseq_2d"); + GEOSCoordSeq_destroy(coords); + lwpointiterator_destroy(it); + return NULL; + } + +#if POSTGIS_GEOS_VERSION < 30800 + if (!GEOSCoordSeq_setX(coords, i, tmp.x) || !GEOSCoordSeq_setY(coords, i, tmp.y)) +#else + if (!GEOSCoordSeq_setXY(coords, i, tmp.x, tmp.y)) +#endif + { + GEOSCoordSeq_destroy(coords); + lwpointiterator_destroy(it); + return NULL; + } + i++; + } + lwpointiterator_destroy(it); + + return coords; +} + +LWGEOM* +lwgeom_voronoi_diagram(const LWGEOM* g, const GBOX* env, double tolerance, int output_edges) +{ + uint32_t num_points = lwgeom_count_vertices(g); + LWGEOM* lwgeom_result; + char is_3d = LW_FALSE; + int32_t srid = lwgeom_get_srid(g); + GEOSCoordSequence* coords; + GEOSGeometry* geos_geom; + GEOSGeometry* geos_env = NULL; + GEOSGeometry* geos_result; + + if (num_points < 2) + { + LWCOLLECTION* empty = lwcollection_construct_empty(COLLECTIONTYPE, lwgeom_get_srid(g), 0, 0); + return lwcollection_as_lwgeom(empty); + } + + initGEOS(lwnotice, lwgeom_geos_error); + + /* Instead of using the standard LWGEOM2GEOS transformer, we read the vertices of the LWGEOM directly and put + * them into a single GEOS CoordinateSeq that can be used to define a LineString. This allows us to process + * geometry types that may not be supported by GEOS, and reduces the memory requirements in cases of many + * geometries with few points (such as LWMPOINT).*/ + coords = lwgeom_get_geos_coordseq_2d(g, num_points); + if (!coords) return NULL; + + geos_geom = GEOSGeom_createLineString(coords); + if (!geos_geom) + { + GEOSCoordSeq_destroy(coords); + return NULL; + } + + if (env) geos_env = GBOX2GEOS(env); + + geos_result = GEOSVoronoiDiagram(geos_geom, geos_env, tolerance, output_edges); + + GEOSGeom_destroy(geos_geom); + if (env) GEOSGeom_destroy(geos_env); + + if (!geos_result) + { + lwerror("GEOSVoronoiDiagram: %s", lwgeom_geos_errmsg); + return NULL; + } + + lwgeom_result = GEOS2LWGEOM(geos_result, is_3d); + GEOSGeom_destroy(geos_result); + + lwgeom_set_srid(lwgeom_result, srid); + + return lwgeom_result; +} + + +#if POSTGIS_GEOS_VERSION >= 31100 +LWGEOM* +lwgeom_concavehull(const LWGEOM* geom, double ratio, uint32_t allow_holes) +{ + LWGEOM* result; + int32_t srid = RESULT_SRID(geom); + uint8_t is3d = FLAGS_GET_Z(geom->flags); + GEOSGeometry *g1, *g3; + int geosGeomType; + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL(); + + geosGeomType = GEOSGeomTypeId(g1); + if (geosGeomType == GEOS_POLYGON || geosGeomType == GEOS_MULTIPOLYGON) { + int is_tight = LW_FALSE; + g3 = GEOSConcaveHullOfPolygons(g1, ratio, is_tight, allow_holes); + } + else { + g3 = GEOSConcaveHull(g1, ratio, allow_holes); + } + + if (!g3) + GEOS_FREE_AND_FAIL(g1); + + GEOSSetSRID(g3, srid); + + if (!(result = GEOS2LWGEOM(g3, is3d))) + GEOS_FREE_AND_FAIL(g1, g3); + + GEOS_FREE(g1, g3); + return result; +} + +LWGEOM* +lwgeom_simplify_polygonal(const LWGEOM* geom, double vertex_fraction, uint32_t is_outer) +{ + LWGEOM* result; + int32_t srid = RESULT_SRID(geom); + uint8_t is3d = FLAGS_GET_Z(geom->flags); + GEOSGeometry *g1, *g3; + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL(); + + g3 = GEOSPolygonHullSimplify(g1, is_outer, vertex_fraction); + + if (!g3) + GEOS_FREE_AND_FAIL(g1); + + GEOSSetSRID(g3, srid); + + if (!(result = GEOS2LWGEOM(g3, is3d))) + GEOS_FREE_AND_FAIL(g1, g3); + + GEOS_FREE(g1, g3); + return result; +} + +LWGEOM* +lwgeom_triangulate_polygon(const LWGEOM* geom) +{ + LWGEOM* result; + int32_t srid = RESULT_SRID(geom); + uint8_t is3d = FLAGS_GET_Z(geom->flags); + GEOSGeometry *g1, *g3; + + if (srid == SRID_INVALID) return NULL; + + initGEOS(lwnotice, lwgeom_geos_error); + + if (!(g1 = LWGEOM2GEOS(geom, AUTOFIX))) GEOS_FAIL(); + + /* if output != 1 we want polys */ + g3 = GEOSConstrainedDelaunayTriangulation(g1); + + if (!g3) GEOS_FREE_AND_FAIL(g1); + GEOSSetSRID(g3, srid); + + if (!(result = GEOS2LWGEOM(g3, is3d))) + GEOS_FREE_AND_FAIL(g1, g3); + + GEOS_FREE(g1, g3); + return result; +} + +#endif diff --git a/mgist-postgis/liblwgeom/lwgeom_geos.h b/mgist-postgis/liblwgeom/lwgeom_geos.h new file mode 100644 index 0000000..bf2f94b --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeom_geos.h @@ -0,0 +1,53 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2011 Sandro Santilli + * Copyright 2018 Darafei Praliaskouski + * + **********************************************************************/ + +#include "geos_c.h" + +#include "liblwgeom.h" +#include "lwunionfind.h" + +/* +** Public prototypes for GEOS utility functions. +*/ +LWGEOM* GEOS2LWGEOM(const GEOSGeometry* geom, uint8_t want3d); +GEOSGeometry* LWGEOM2GEOS(const LWGEOM* g, uint8_t autofix); +GEOSGeometry* GBOX2GEOS(const GBOX* g); +#if POSTGIS_GEOS_VERSION < 30800 +GEOSGeometry* LWGEOM_GEOS_buildArea(const GEOSGeometry* geom_in); +GEOSGeometry* LWGEOM_GEOS_makeValid(const GEOSGeometry*); +#endif + +GEOSGeometry* make_geos_point(double x, double y); +GEOSGeometry* make_geos_segment(double x1, double y1, double x2, double y2); + +int cluster_intersecting(GEOSGeometry **geoms, uint32_t num_geoms, GEOSGeometry ***clusterGeoms, uint32_t *num_clusters); +int union_intersecting_pairs(GEOSGeometry** geoms, uint32_t num_geoms, UNIONFIND* uf); +int cluster_within_distance(LWGEOM **geoms, uint32_t num_geoms, double tolerance, LWGEOM ***clusterGeoms, uint32_t *num_clusters); +int union_dbscan(LWGEOM **geoms, uint32_t num_geoms, UNIONFIND *uf, double eps, uint32_t min_points, char **is_in_cluster_ret); + +POINTARRAY* ptarray_from_GEOSCoordSeq(const GEOSCoordSequence* cs, uint8_t want3d); + +extern char lwgeom_geos_errmsg[]; +extern void lwgeom_geos_error(const char* fmt, ...); diff --git a/mgist-postgis/liblwgeom/lwgeom_geos_clean.c b/mgist-postgis/liblwgeom/lwgeom_geos_clean.c new file mode 100644 index 0000000..92ca3f8 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeom_geos_clean.c @@ -0,0 +1,1020 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2009-2010 Sandro Santilli + * + **********************************************************************/ + +// #include "../postgis_config.h" +/*#define POSTGIS_DEBUG_LEVEL 4*/ + +#include "liblwgeom.h" +#include "lwgeom_geos.h" +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" +#include "optionlist.h" + +#include +#include +#include + +/* #define PARANOIA_LEVEL 2 */ +#undef LWGEOM_PROFILE_MAKEVALID + +#if POSTGIS_GEOS_VERSION < 30800 +/* + * Return Nth vertex in GEOSGeometry as a POINT. + * May return NULL if the geometry has NO vertex. + */ +GEOSGeometry* LWGEOM_GEOS_getPointN(const GEOSGeometry*, uint32_t); +GEOSGeometry* +LWGEOM_GEOS_getPointN(const GEOSGeometry* g_in, uint32_t n) +{ + uint32_t dims = 0; + const GEOSCoordSequence* seq_in; + GEOSCoordSeq seq_out; + double val; + uint32_t sz = 0; + int gn; + GEOSGeometry* ret; + + switch (GEOSGeomTypeId(g_in)) + { + case GEOS_MULTIPOINT: + case GEOS_MULTILINESTRING: + case GEOS_MULTIPOLYGON: + case GEOS_GEOMETRYCOLLECTION: + { + for (gn = 0; gn < GEOSGetNumGeometries(g_in); ++gn) + { + const GEOSGeometry* g = GEOSGetGeometryN(g_in, gn); + ret = LWGEOM_GEOS_getPointN(g, n); + if (ret) return ret; + } + break; + } + + case GEOS_POLYGON: + { + ret = LWGEOM_GEOS_getPointN(GEOSGetExteriorRing(g_in), n); + if (ret) return ret; + for (gn = 0; gn < GEOSGetNumInteriorRings(g_in); ++gn) + { + const GEOSGeometry* g = GEOSGetInteriorRingN(g_in, gn); + ret = LWGEOM_GEOS_getPointN(g, n); + if (ret) return ret; + } + break; + } + + case GEOS_POINT: + case GEOS_LINESTRING: + case GEOS_LINEARRING: + break; + } + + seq_in = GEOSGeom_getCoordSeq(g_in); + if (!seq_in) return NULL; + if (!GEOSCoordSeq_getSize(seq_in, &sz)) return NULL; + if (!sz) return NULL; + + if (!GEOSCoordSeq_getDimensions(seq_in, &dims)) return NULL; + + seq_out = GEOSCoordSeq_create(1, dims); + if (!seq_out) return NULL; + + if (!GEOSCoordSeq_getX(seq_in, n, &val)) return NULL; + if (!GEOSCoordSeq_setX(seq_out, n, val)) return NULL; + if (!GEOSCoordSeq_getY(seq_in, n, &val)) return NULL; + if (!GEOSCoordSeq_setY(seq_out, n, val)) return NULL; + if (dims > 2) + { + if (!GEOSCoordSeq_getZ(seq_in, n, &val)) return NULL; + if (!GEOSCoordSeq_setZ(seq_out, n, val)) return NULL; + } + + return GEOSGeom_createPoint(seq_out); +} +#endif + +LWGEOM* lwcollection_make_geos_friendly(LWCOLLECTION* g); +LWGEOM* lwline_make_geos_friendly(LWLINE* line); +LWGEOM* lwpoly_make_geos_friendly(LWPOLY* poly); +POINTARRAY* ring_make_geos_friendly(POINTARRAY* ring); + +static void +ptarray_strip_nan_coords_in_place(POINTARRAY *pa) +{ + uint32_t i, j = 0; + POINT4D *p, *np; + int ndims = FLAGS_NDIMS(pa->flags); + for ( i = 0; i < pa->npoints; i++ ) + { + int isnan = 0; + p = (POINT4D *)(getPoint_internal(pa, i)); + if ( isnan(p->x) || isnan(p->y) ) isnan = 1; + else if (ndims > 2 && isnan(p->z) ) isnan = 1; + else if (ndims > 3 && isnan(p->m) ) isnan = 1; + if ( isnan ) continue; + + np = (POINT4D *)(getPoint_internal(pa, j++)); + if ( np != p ) { + np->x = p->x; + np->y = p->y; + if (ndims > 2) + np->z = p->z; + if (ndims > 3) + np->m = p->m; + } + } + pa->npoints = j; +} + + + +/* + * Ensure the geometry is "structurally" valid + * (enough for GEOS to accept it) + * May return the input untouched (if already valid). + * May return geometries of lower dimension (on collapses) + */ +static LWGEOM* +lwgeom_make_geos_friendly(LWGEOM* geom) +{ + LWDEBUGF(2, "lwgeom_make_geos_friendly enter (type %d)", geom->type); + switch (geom->type) + { + case POINTTYPE: + ptarray_strip_nan_coords_in_place(((LWPOINT*)geom)->point); + return geom; + + case LINETYPE: + /* lines need at least 2 points */ + return lwline_make_geos_friendly((LWLINE*)geom); + break; + + case POLYGONTYPE: + /* polygons need all rings closed and with npoints > 3 */ + return lwpoly_make_geos_friendly((LWPOLY*)geom); + break; + + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + case MULTIPOINTTYPE: + return lwcollection_make_geos_friendly((LWCOLLECTION*)geom); + break; + + case CIRCSTRINGTYPE: + case COMPOUNDTYPE: + case CURVEPOLYTYPE: + case MULTISURFACETYPE: + case MULTICURVETYPE: + default: + lwerror("lwgeom_make_geos_friendly: unsupported input geometry type: %s (%d)", + lwtype_name(geom->type), + geom->type); + break; + } + return 0; +} + +/* + * Close the point array, if not already closed in 2d. + * Returns the input if already closed in 2d, or a newly + * constructed POINTARRAY. + * TODO: move in ptarray.c + */ +static POINTARRAY* +ptarray_close2d(POINTARRAY* ring) +{ + POINTARRAY* newring; + + /* close the ring if not already closed (2d only) */ + if (!ptarray_is_closed_2d(ring)) + { + /* close it up */ + newring = ptarray_addPoint(ring, getPoint_internal(ring, 0), FLAGS_NDIMS(ring->flags), ring->npoints); + ring = newring; + } + return ring; +} + +/* May return the same input or a new one (never zero) */ +POINTARRAY* +ring_make_geos_friendly(POINTARRAY* ring) +{ + POINTARRAY* closedring; + POINTARRAY* ring_in = ring; + + ptarray_strip_nan_coords_in_place(ring_in); + + /* close the ring if not already closed (2d only) */ + closedring = ptarray_close2d(ring); + if (closedring != ring) ring = closedring; + + /* return 0 for collapsed ring (after closeup) */ + + while (ring->npoints < 4) + { + POINTARRAY* oring = ring; + LWDEBUGF(4, "ring has %d points, adding another", ring->npoints); + /* let's add another... */ + ring = ptarray_addPoint(ring, getPoint_internal(ring, 0), FLAGS_NDIMS(ring->flags), ring->npoints); + if (oring != ring_in) ptarray_free(oring); + } + + return ring; +} + +/* Make sure all rings are closed and have > 3 points. + * May return the input untouched. + */ +LWGEOM* +lwpoly_make_geos_friendly(LWPOLY* poly) +{ + LWGEOM* ret; + POINTARRAY** new_rings; + uint32_t i; + + /* If the polygon has no rings there's nothing to do */ + if (!poly->nrings) return (LWGEOM*)poly; + + /* Allocate enough pointers for all rings */ + new_rings = lwalloc(sizeof(POINTARRAY*) * poly->nrings); + + /* All rings must be closed and have > 3 points */ + for (i = 0; i < poly->nrings; i++) + { + POINTARRAY* ring_in = poly->rings[i]; + POINTARRAY* ring_out = ring_make_geos_friendly(ring_in); + + if (ring_in != ring_out) + { + LWDEBUGF( + 3, "lwpoly_make_geos_friendly: ring %d cleaned, now has %d points", i, ring_out->npoints); + ptarray_free(ring_in); + } + else + LWDEBUGF(3, "lwpoly_make_geos_friendly: ring %d untouched", i); + + assert(ring_out); + new_rings[i] = ring_out; + } + + lwfree(poly->rings); + poly->rings = new_rings; + ret = (LWGEOM*)poly; + + return ret; +} + +/* Need NO or >1 points. Duplicate first if only one. */ +LWGEOM* +lwline_make_geos_friendly(LWLINE* line) +{ + LWGEOM* ret; + + ptarray_strip_nan_coords_in_place(line->points); + + if (line->points->npoints == 1) /* 0 is fine, 2 is fine */ + { +#if 1 + /* Duplicate point */ + line->points = ptarray_addPoint(line->points, + getPoint_internal(line->points, 0), + FLAGS_NDIMS(line->points->flags), + line->points->npoints); + ret = (LWGEOM*)line; +#else + /* Turn into a point */ + ret = (LWGEOM*)lwpoint_construct(line->srid, 0, line->points); +#endif + return ret; + } + else + { + return (LWGEOM*)line; + /* return lwline_clone(line); */ + } +} + +LWGEOM* +lwcollection_make_geos_friendly(LWCOLLECTION* g) +{ + LWGEOM** new_geoms; + uint32_t i, new_ngeoms = 0; + LWCOLLECTION* ret; + + if ( ! g->ngeoms ) { + LWDEBUG(3, "lwcollection_make_geos_friendly: returning input untouched"); + return lwcollection_as_lwgeom(g); + } + + /* enough space for all components */ + new_geoms = lwalloc(sizeof(LWGEOM*) * g->ngeoms); + + ret = lwalloc(sizeof(LWCOLLECTION)); + memcpy(ret, g, sizeof(LWCOLLECTION)); + ret->maxgeoms = g->ngeoms; + + for (i = 0; i < g->ngeoms; i++) + { + LWGEOM* newg = lwgeom_make_geos_friendly(g->geoms[i]); + if (!newg) continue; + if ( newg != g->geoms[i] ) { + new_geoms[new_ngeoms++] = newg; + } else { + new_geoms[new_ngeoms++] = lwgeom_clone(newg); + } + } + + ret->bbox = NULL; /* recompute later... */ + + ret->ngeoms = new_ngeoms; + if (new_ngeoms) + ret->geoms = new_geoms; + else + { + free(new_geoms); + ret->geoms = NULL; + ret->maxgeoms = 0; + } + + return (LWGEOM*)ret; +} + +#if POSTGIS_GEOS_VERSION < 30800 + +/* + * Fully node given linework + */ +static GEOSGeometry* +LWGEOM_GEOS_nodeLines(const GEOSGeometry* lines) +{ + /* GEOS3.7 GEOSNode fails on regression tests */ + /* GEOS3.7 GEOSUnaryUnion fails on regression tests */ + + /* union of first point with geometry */ + GEOSGeometry *unioned, *point; + point = LWGEOM_GEOS_getPointN(lines, 0); + if (!point) return NULL; + unioned = GEOSUnion(lines, point); + GEOSGeom_destroy(point); + return unioned; +} + +/* + * We expect initGEOS being called already. + * Will return NULL on error (expect error handler being called by then) + */ +static GEOSGeometry* +LWGEOM_GEOS_makeValidPolygon(const GEOSGeometry* gin) +{ + GEOSGeom gout; + GEOSGeom geos_bound; + GEOSGeom geos_cut_edges, geos_area, collapse_points; + GEOSGeometry* vgeoms[3]; /* One for area, one for cut-edges */ + unsigned int nvgeoms = 0; + + assert(GEOSGeomTypeId(gin) == GEOS_POLYGON || GEOSGeomTypeId(gin) == GEOS_MULTIPOLYGON); + + geos_bound = GEOSBoundary(gin); + if (!geos_bound) return NULL; + + /* Use noded boundaries as initial "cut" edges */ + + geos_cut_edges = LWGEOM_GEOS_nodeLines(geos_bound); + if (!geos_cut_edges) + { + GEOSGeom_destroy(geos_bound); + lwnotice("LWGEOM_GEOS_nodeLines(): %s", lwgeom_geos_errmsg); + return NULL; + } + + /* NOTE: the noding process may drop lines collapsing to points. + * We want to retrieve any of those */ + { + GEOSGeometry* pi; + GEOSGeometry* po; + + pi = GEOSGeom_extractUniquePoints(geos_bound); + if (!pi) + { + GEOSGeom_destroy(geos_bound); + lwnotice("GEOSGeom_extractUniquePoints(): %s", lwgeom_geos_errmsg); + return NULL; + } + + po = GEOSGeom_extractUniquePoints(geos_cut_edges); + if (!po) + { + GEOSGeom_destroy(geos_bound); + GEOSGeom_destroy(pi); + lwnotice("GEOSGeom_extractUniquePoints(): %s", lwgeom_geos_errmsg); + return NULL; + } + + collapse_points = GEOSDifference(pi, po); + if (!collapse_points) + { + GEOSGeom_destroy(geos_bound); + GEOSGeom_destroy(pi); + GEOSGeom_destroy(po); + lwnotice("GEOSDifference(): %s", lwgeom_geos_errmsg); + return NULL; + } + + GEOSGeom_destroy(pi); + GEOSGeom_destroy(po); + } + GEOSGeom_destroy(geos_bound); + + /* And use an empty geometry as initial "area" */ + geos_area = GEOSGeom_createEmptyPolygon(); + if (!geos_area) + { + lwnotice("GEOSGeom_createEmptyPolygon(): %s", lwgeom_geos_errmsg); + GEOSGeom_destroy(geos_cut_edges); + return NULL; + } + + /* + * See if an area can be build with the remaining edges + * and if it can, symdifference with the original area. + * Iterate this until no more polygons can be created + * with left-over edges. + */ + while (GEOSGetNumGeometries(geos_cut_edges)) + { + GEOSGeometry* new_area = 0; + GEOSGeometry* new_area_bound = 0; + GEOSGeometry* symdif = 0; + GEOSGeometry* new_cut_edges = 0; + +#ifdef LWGEOM_PROFILE_MAKEVALID + lwnotice("ST_MakeValid: building area from %d edges", GEOSGetNumGeometries(geos_cut_edges)); +#endif + + /* + * ASSUMPTION: cut_edges should already be fully noded + */ + + new_area = LWGEOM_GEOS_buildArea(geos_cut_edges); + if (!new_area) /* must be an exception */ + { + GEOSGeom_destroy(geos_cut_edges); + GEOSGeom_destroy(geos_area); + lwnotice("LWGEOM_GEOS_buildArea() threw an error: %s", lwgeom_geos_errmsg); + return NULL; + } + + if (GEOSisEmpty(new_area)) + { + /* no more rings can be build with thes edges */ + GEOSGeom_destroy(new_area); + break; + } + + /* + * We succeeded in building a ring! + * Save the new ring boundaries first (to compute + * further cut edges later) + */ + new_area_bound = GEOSBoundary(new_area); + if (!new_area_bound) + { + /* We did check for empty area already so this must be some other error */ + lwnotice("GEOSBoundary('%s') threw an error: %s", + lwgeom_to_ewkt(GEOS2LWGEOM(new_area, 0)), + lwgeom_geos_errmsg); + GEOSGeom_destroy(new_area); + GEOSGeom_destroy(geos_area); + return NULL; + } + + /* + * Now symdif new and old area + */ + symdif = GEOSSymDifference(geos_area, new_area); + if (!symdif) /* must be an exception */ + { + GEOSGeom_destroy(geos_cut_edges); + GEOSGeom_destroy(new_area); + GEOSGeom_destroy(new_area_bound); + GEOSGeom_destroy(geos_area); + lwnotice("GEOSSymDifference() threw an error: %s", lwgeom_geos_errmsg); + return NULL; + } + + GEOSGeom_destroy(geos_area); + GEOSGeom_destroy(new_area); + geos_area = symdif; + symdif = 0; + + /* + * Now let's re-set geos_cut_edges with what's left + * from the original boundary. + * ASSUMPTION: only the previous cut-edges can be + * left, so we don't need to reconsider + * the whole original boundaries + * + * NOTE: this is an expensive operation. + * + */ + + new_cut_edges = GEOSDifference(geos_cut_edges, new_area_bound); + GEOSGeom_destroy(new_area_bound); + if (!new_cut_edges) /* an exception ? */ + { + /* cleanup and throw */ + GEOSGeom_destroy(geos_cut_edges); + GEOSGeom_destroy(geos_area); + lwerror("GEOSDifference() threw an error: %s", lwgeom_geos_errmsg); + return NULL; + } + GEOSGeom_destroy(geos_cut_edges); + geos_cut_edges = new_cut_edges; + } + + if (!GEOSisEmpty(geos_area)) + vgeoms[nvgeoms++] = geos_area; + else + GEOSGeom_destroy(geos_area); + + if (!GEOSisEmpty(geos_cut_edges)) + vgeoms[nvgeoms++] = geos_cut_edges; + else + GEOSGeom_destroy(geos_cut_edges); + + if (!GEOSisEmpty(collapse_points)) + vgeoms[nvgeoms++] = collapse_points; + else + GEOSGeom_destroy(collapse_points); + + if (1 == nvgeoms) + { + /* Return cut edges */ + gout = vgeoms[0]; + } + else + { + /* Collect areas and lines (if any line) */ + gout = GEOSGeom_createCollection(GEOS_GEOMETRYCOLLECTION, vgeoms, nvgeoms); + if (!gout) /* an exception again */ + { + /* cleanup and throw */ + lwerror("GEOSGeom_createCollection() threw an error: %s", lwgeom_geos_errmsg); + /* TODO: cleanup! */ + return NULL; + } + } + + return gout; +} + +static GEOSGeometry* +LWGEOM_GEOS_makeValidLine(const GEOSGeometry* gin) +{ + GEOSGeometry* noded; + noded = LWGEOM_GEOS_nodeLines(gin); + return noded; +} + +static GEOSGeometry* +LWGEOM_GEOS_makeValidMultiLine(const GEOSGeometry* gin) +{ + GEOSGeometry** lines; + GEOSGeometry** points; + GEOSGeometry* mline_out = 0; + GEOSGeometry* mpoint_out = 0; + GEOSGeometry* gout = 0; + uint32_t nlines = 0, nlines_alloc; + uint32_t npoints = 0; + uint32_t ngeoms = 0, nsubgeoms; + uint32_t i, j; + + ngeoms = GEOSGetNumGeometries(gin); + + nlines_alloc = ngeoms; + lines = lwalloc(sizeof(GEOSGeometry*) * nlines_alloc); + points = lwalloc(sizeof(GEOSGeometry*) * ngeoms); + + for (i = 0; i < ngeoms; ++i) + { + const GEOSGeometry* g = GEOSGetGeometryN(gin, i); + GEOSGeometry* vg; + vg = LWGEOM_GEOS_makeValidLine(g); + /* Drop any invalid or empty geometry */ + if (!vg) + continue; + if (GEOSisEmpty(vg)) + { + GEOSGeom_destroy(vg); + continue; + } + + if (GEOSGeomTypeId(vg) == GEOS_POINT) + points[npoints++] = vg; + else if (GEOSGeomTypeId(vg) == GEOS_LINESTRING) + lines[nlines++] = vg; + else if (GEOSGeomTypeId(vg) == GEOS_MULTILINESTRING) + { + nsubgeoms = GEOSGetNumGeometries(vg); + nlines_alloc += nsubgeoms; + lines = lwrealloc(lines, sizeof(GEOSGeometry*) * nlines_alloc); + for (j = 0; j < nsubgeoms; ++j) + { + const GEOSGeometry* gc = GEOSGetGeometryN(vg, j); + /* NOTE: ownership of the cloned geoms will be + * taken by final collection */ + lines[nlines++] = GEOSGeom_clone(gc); + } + } + else + { + /* NOTE: return from GEOSGeomType will leak + * but we really don't expect this to happen */ + lwerror("unexpected geom type returned by LWGEOM_GEOS_makeValid: %s", GEOSGeomType(vg)); + } + } + + if (npoints) + { + if (npoints > 1) + mpoint_out = GEOSGeom_createCollection(GEOS_MULTIPOINT, points, npoints); + else + mpoint_out = points[0]; + } + + if (nlines) + { + if (nlines > 1) + mline_out = GEOSGeom_createCollection(GEOS_MULTILINESTRING, lines, nlines); + else + mline_out = lines[0]; + } + + lwfree(lines); + + if (mline_out && mpoint_out) + { + points[0] = mline_out; + points[1] = mpoint_out; + gout = GEOSGeom_createCollection(GEOS_GEOMETRYCOLLECTION, points, 2); + } + else if (mline_out) + gout = mline_out; + + else if (mpoint_out) + gout = mpoint_out; + + lwfree(points); + + return gout; +} + +/* + * We expect initGEOS being called already. + * Will return NULL on error (expect error handler being called by then) + */ +static GEOSGeometry* +LWGEOM_GEOS_makeValidCollection(const GEOSGeometry* gin) +{ + int nvgeoms; + GEOSGeometry** vgeoms; + GEOSGeom gout; + int i; + + nvgeoms = GEOSGetNumGeometries(gin); + if (nvgeoms == -1) + { + lwerror("GEOSGetNumGeometries: %s", lwgeom_geos_errmsg); + return 0; + } + + vgeoms = lwalloc(sizeof(GEOSGeometry*) * nvgeoms); + if (!vgeoms) + { + lwerror("LWGEOM_GEOS_makeValidCollection: out of memory"); + return 0; + } + + for (i = 0; i < nvgeoms; ++i) + { + vgeoms[i] = LWGEOM_GEOS_makeValid(GEOSGetGeometryN(gin, i)); + if (!vgeoms[i]) + { + int j; + for (j = 0; j < i - 1; j++) + GEOSGeom_destroy(vgeoms[j]); + lwfree(vgeoms); + /* we expect lwerror being called already by makeValid */ + return NULL; + } + } + + /* Collect areas and lines (if any line) */ + gout = GEOSGeom_createCollection(GEOS_GEOMETRYCOLLECTION, vgeoms, nvgeoms); + if (!gout) /* an exception again */ + { + /* cleanup and throw */ + for (i = 0; i < nvgeoms; ++i) + GEOSGeom_destroy(vgeoms[i]); + lwfree(vgeoms); + lwerror("GEOSGeom_createCollection() threw an error: %s", lwgeom_geos_errmsg); + return NULL; + } + lwfree(vgeoms); + + return gout; +} + +GEOSGeometry* +LWGEOM_GEOS_makeValid(const GEOSGeometry* gin) +{ + GEOSGeometry* gout; + char ret_char; +#if POSTGIS_DEBUG_LEVEL >= 3 + LWGEOM *geos_geom; + char *geom_ewkt; +#endif + + /* + * Step 2: return what we got so far if already valid + */ + + ret_char = GEOSisValid(gin); + if (ret_char == 2) + { + /* I don't think should ever happen */ + lwerror("GEOSisValid(): %s", lwgeom_geos_errmsg); + return NULL; + } + else if (ret_char) + { +#if POSTGIS_DEBUG_LEVEL >= 3 + geos_geom = GEOS2LWGEOM(gin, 0); + geom_ewkt = lwgeom_to_ewkt(geos_geom); + LWDEBUGF(3, "Geometry [%s] is valid. ", geom_ewkt); + lwgeom_free(geos_geom); + lwfree(geom_ewkt); +#endif + + /* It's valid at this step, return what we have */ + return GEOSGeom_clone(gin); + } + +#if POSTGIS_DEBUG_LEVEL >= 3 + geos_geom = GEOS2LWGEOM(gin, 0); + geom_ewkt = lwgeom_to_ewkt(geos_geom); + LWDEBUGF(3, + "Geometry [%s] is still not valid: %s. Will try to clean up further.", + geom_ewkt, + lwgeom_geos_errmsg); + lwgeom_free(geos_geom); + lwfree(geom_ewkt); +#endif + + /* + * Step 3 : make what we got valid + */ + + switch (GEOSGeomTypeId(gin)) + { + case GEOS_MULTIPOINT: + case GEOS_POINT: + /* points are always valid, but we might have invalid ordinate values */ + lwnotice("PUNTUAL geometry resulted invalid to GEOS -- dunno how to clean that up"); + return NULL; + break; + + case GEOS_LINESTRING: + gout = LWGEOM_GEOS_makeValidLine(gin); + if (!gout) /* an exception or something */ + { + /* cleanup and throw */ + lwerror("%s", lwgeom_geos_errmsg); + return NULL; + } + break; /* we've done */ + + case GEOS_MULTILINESTRING: + gout = LWGEOM_GEOS_makeValidMultiLine(gin); + if (!gout) /* an exception or something */ + { + /* cleanup and throw */ + lwerror("%s", lwgeom_geos_errmsg); + return NULL; + } + break; /* we've done */ + + case GEOS_POLYGON: + case GEOS_MULTIPOLYGON: + { + gout = LWGEOM_GEOS_makeValidPolygon(gin); + if (!gout) /* an exception or something */ + { + /* cleanup and throw */ + lwerror("%s", lwgeom_geos_errmsg); + return NULL; + } + break; /* we've done */ + } + + case GEOS_GEOMETRYCOLLECTION: + { + gout = LWGEOM_GEOS_makeValidCollection(gin); + if (!gout) /* an exception or something */ + { + /* cleanup and throw */ + lwerror("%s", lwgeom_geos_errmsg); + return NULL; + } + break; /* we've done */ + } + + default: + { + char* typname = GEOSGeomType(gin); + lwnotice("ST_MakeValid: doesn't support geometry type: %s", typname); + GEOSFree(typname); + return NULL; + break; + } + } + +#if PARANOIA_LEVEL > 1 + /* + * Now check if every point of input is also found in output, or abort by returning NULL + * + * Input geometry was lwgeom_in + */ + { + int loss; + GEOSGeometry *pi, *po, *pd; + + /* TODO: handle some errors here... + * Lack of exceptions is annoying indeed, + * I'm getting old --strk; + */ + pi = GEOSGeom_extractUniquePoints(gin); + po = GEOSGeom_extractUniquePoints(gout); + pd = GEOSDifference(pi, po); /* input points - output points */ + GEOSGeom_destroy(pi); + GEOSGeom_destroy(po); + loss = pd && !GEOSisEmpty(pd); + GEOSGeom_destroy(pd); + if (loss) + { + lwnotice("%s [%d] Vertices lost in LWGEOM_GEOS_makeValid", __FILE__, __LINE__); + /* return NULL */ + } + } +#endif /* PARANOIA_LEVEL > 1 */ + + return gout; +} +#endif + +/* Exported. Uses GEOS internally */ +LWGEOM* +lwgeom_make_valid(LWGEOM* lwgeom_in) +{ + return lwgeom_make_valid_params(lwgeom_in, NULL); +} + +/* Exported. Uses GEOS internally */ +LWGEOM* +lwgeom_make_valid_params(LWGEOM* lwgeom_in, char* make_valid_params) +{ + int is3d; + GEOSGeom geosgeom; + GEOSGeometry* geosout; + LWGEOM* lwgeom_out; + + LWDEBUG(1, "lwgeom_make_valid enter"); + + is3d = FLAGS_GET_Z(lwgeom_in->flags); + + /* + * Step 1 : try to convert to GEOS, if impossible, clean that up first + * otherwise (adding only duplicates of existing points) + */ + + initGEOS(lwgeom_geos_error, lwgeom_geos_error); + + lwgeom_out = lwgeom_make_geos_friendly(lwgeom_in); + if (!lwgeom_out) lwerror("Could not make a geos friendly geometry out of input"); + + LWDEBUGF(4, "Input geom %p made GEOS-valid as %p", lwgeom_in, lwgeom_out); + + geosgeom = LWGEOM2GEOS(lwgeom_out, 1); + if ( lwgeom_in != lwgeom_out ) { + lwgeom_free(lwgeom_out); + } + if (!geosgeom) + { + lwerror("Couldn't convert POSTGIS geom to GEOS: %s", lwgeom_geos_errmsg); + return NULL; + } + else + { + LWDEBUG(4, "geom converted to GEOS"); + } + +#if POSTGIS_GEOS_VERSION < 30800 + geosout = LWGEOM_GEOS_makeValid(geosgeom); +#elif POSTGIS_GEOS_VERSION < 31000 + geosout = GEOSMakeValid(geosgeom); +#else + if (!make_valid_params) { + geosout = GEOSMakeValid(geosgeom); + } + else { + /* + * Set up a parameters object for this + * make valid operation before calling + * it + */ + const char *value; + char *param_list[OPTION_LIST_SIZE]; + char param_list_text[OPTION_LIST_SIZE]; + strncpy(param_list_text, make_valid_params, OPTION_LIST_SIZE-1); + param_list_text[OPTION_LIST_SIZE-1] = '\0'; /* ensure null-termination */ + memset(param_list, 0, sizeof(param_list)); + option_list_parse(param_list_text, param_list); + GEOSMakeValidParams *params = GEOSMakeValidParams_create(); + value = option_list_search(param_list, "method"); + if (value) { + if (strcasecmp(value, "linework") == 0) { + GEOSMakeValidParams_setMethod(params, GEOS_MAKE_VALID_LINEWORK); + } + else if (strcasecmp(value, "structure") == 0) { + GEOSMakeValidParams_setMethod(params, GEOS_MAKE_VALID_STRUCTURE); + } + else + { + GEOSMakeValidParams_destroy(params); + lwerror("Unsupported value for 'method', '%s'. Use 'linework' or 'structure'.", value); + } + } + value = option_list_search(param_list, "keepcollapsed"); + if (value) { + if (strcasecmp(value, "true") == 0) { + GEOSMakeValidParams_setKeepCollapsed(params, 1); + } + else if (strcasecmp(value, "false") == 0) { + GEOSMakeValidParams_setKeepCollapsed(params, 0); + } + else + { + GEOSMakeValidParams_destroy(params); + lwerror("Unsupported value for 'keepcollapsed', '%s'. Use 'true' or 'false'", value); + } + } + geosout = GEOSMakeValidWithParams(geosgeom, params); + GEOSMakeValidParams_destroy(params); + } +#endif + GEOSGeom_destroy(geosgeom); + if (!geosout) return NULL; + + lwgeom_out = GEOS2LWGEOM(geosout, is3d); + GEOSGeom_destroy(geosout); + + if (lwgeom_is_collection(lwgeom_in) && !lwgeom_is_collection(lwgeom_out)) + { + LWGEOM** ogeoms = lwalloc(sizeof(LWGEOM*)); + LWGEOM* ogeom; + LWDEBUG(3, "lwgeom_make_valid: forcing multi"); + /* NOTE: this is safe because lwgeom_out is surely not lwgeom_in or + * otherwise we couldn't have a collection and a non-collection */ + assert(lwgeom_in != lwgeom_out); + ogeoms[0] = lwgeom_out; + ogeom = (LWGEOM*)lwcollection_construct( + MULTITYPE[lwgeom_out->type], lwgeom_out->srid, lwgeom_out->bbox, 1, ogeoms); + lwgeom_out->bbox = NULL; + lwgeom_out = ogeom; + } + + lwgeom_out->srid = lwgeom_in->srid; + return lwgeom_out; +} diff --git a/mgist-postgis/liblwgeom/lwgeom_geos_cluster.c b/mgist-postgis/liblwgeom/lwgeom_geos_cluster.c new file mode 100644 index 0000000..c2378bf --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeom_geos_cluster.c @@ -0,0 +1,604 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2015-2016 Daniel Baston + * + **********************************************************************/ + +#include +#include "liblwgeom.h" +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" +#include "lwgeom_geos.h" +#include "lwunionfind.h" + +static const int STRTREE_NODE_CAPACITY = 10; + +/* Utility struct used to accumulate items in GEOSSTRtree_query callback */ +struct QueryContext +{ + void** items_found; + uint32_t items_found_size; + uint32_t num_items_found; +}; + +/* Utility struct to keep GEOSSTRtree and associated structures to be freed after use */ +struct STRTree +{ + GEOSSTRtree* tree; + GEOSGeometry** envelopes; + uint32_t* geom_ids; + uint32_t num_geoms; +}; + +static struct STRTree make_strtree(void** geoms, uint32_t num_geoms, char is_lwgeom); +static void destroy_strtree(struct STRTree * tree); +static int combine_geometries(UNIONFIND* uf, void** geoms, uint32_t num_geoms, void*** clustersGeoms, uint32_t* num_clusters, char is_lwgeom); + +/* Make a minimal GEOSGeometry* whose Envelope covers the same 2D extent as + * the supplied GBOX. This is faster and uses less memory than building a + * five-point polygon with GBOX2GEOS. + */ +static GEOSGeometry* +geos_envelope_surrogate(const LWGEOM* g) +{ + if (lwgeom_is_empty(g)) + return GEOSGeom_createEmptyPolygon(); + + if (lwgeom_get_type(g) == POINTTYPE) { + const POINT2D* pt = getPoint2d_cp(lwgeom_as_lwpoint(g)->point, 0); + return make_geos_point(pt->x, pt->y); + } else { + const GBOX* box = lwgeom_get_bbox(g); + if (!box) + return NULL; + + return make_geos_segment(box->xmin, box->ymin, box->xmax, box->ymax); + } +} + +/** Make a GEOSSTRtree that stores a pointer to a variable containing + * the array index of the input geoms */ +static struct STRTree +make_strtree(void** geoms, uint32_t num_geoms, char is_lwgeom) +{ + struct STRTree tree; + tree.envelopes = 0; + tree.num_geoms = 0; + tree.geom_ids = 0; + + tree.tree = GEOSSTRtree_create(STRTREE_NODE_CAPACITY); + if (tree.tree == NULL) + { + return tree; + } + tree.geom_ids = lwalloc(num_geoms * sizeof(uint32_t)); + tree.num_geoms = num_geoms; + + if (is_lwgeom) + { + uint32_t i; + tree.envelopes = lwalloc(num_geoms * sizeof(GEOSGeometry*)); + for (i = 0; i < num_geoms; i++) + { + tree.geom_ids[i] = i; + tree.envelopes[i] = geos_envelope_surrogate(geoms[i]); + GEOSSTRtree_insert(tree.tree, tree.envelopes[i], &(tree.geom_ids[i])); + } + } + else + { + uint32_t i; + tree.envelopes = NULL; + for (i = 0; i < num_geoms; i++) + { + tree.geom_ids[i] = i; + GEOSSTRtree_insert(tree.tree, geoms[i], &(tree.geom_ids[i])); + } + } + + return tree; +} + +/** Clean up STRTree after use */ +static void +destroy_strtree(struct STRTree * tree) +{ + size_t i; + GEOSSTRtree_destroy(tree->tree); + + if (tree->envelopes) + { + for (i = 0; i < tree->num_geoms; i++) + { + GEOSGeom_destroy(tree->envelopes[i]); + } + lwfree(tree->envelopes); + } + lwfree(tree->geom_ids); +} + +static void +query_accumulate(void* item, void* userdata) +{ + struct QueryContext *cxt = userdata; + if (!cxt->items_found) + { + cxt->items_found_size = 8; + cxt->items_found = lwalloc(cxt->items_found_size * sizeof(void*)); + } + + if (cxt->num_items_found >= cxt->items_found_size) + { + cxt->items_found_size = 2 * cxt->items_found_size; + cxt->items_found = lwrealloc(cxt->items_found, cxt->items_found_size * sizeof(void*)); + } + cxt->items_found[cxt->num_items_found++] = item; +} + +/* Identify intersecting geometries and mark them as being in the same set */ +int +union_intersecting_pairs(GEOSGeometry** geoms, uint32_t num_geoms, UNIONFIND* uf) +{ + uint32_t p, i; + struct STRTree tree; + struct QueryContext cxt = + { + .items_found = NULL, + .num_items_found = 0, + .items_found_size = 0 + }; + int success = LW_SUCCESS; + + if (num_geoms <= 1) + return LW_SUCCESS; + + tree = make_strtree((void**) geoms, num_geoms, LW_FALSE); + if (tree.tree == NULL) + { + destroy_strtree(&tree); + return LW_FAILURE; + } + + for (p = 0; p < num_geoms; p++) + { + const GEOSPreparedGeometry* prep = NULL; + + if (!geoms[p] || GEOSisEmpty(geoms[p])) + continue; + + cxt.num_items_found = 0; + GEOSSTRtree_query(tree.tree, geoms[p], &query_accumulate, &cxt); + + for (i = 0; i < cxt.num_items_found; i++) + { + uint32_t q = *((uint32_t*) cxt.items_found[i]); + + if (p != q && UF_find(uf, p) != UF_find(uf, q)) + { + int geos_type = GEOSGeomTypeId(geoms[p]); + int geos_result; + + /* Don't build prepared a geometry around a Point or MultiPoint - + * there are some problems in the implementation, and it's not clear + * there would be a performance benefit in any case. (See #3433) + */ + if (geos_type != GEOS_POINT && geos_type != GEOS_MULTIPOINT) + { + /* Lazy initialize prepared geometry */ + if (prep == NULL) + { + prep = GEOSPrepare(geoms[p]); + } + geos_result = GEOSPreparedIntersects(prep, geoms[q]); + } + else + { + geos_result = GEOSIntersects(geoms[p], geoms[q]); + } + if (geos_result > 1) + { + success = LW_FAILURE; + break; + } + else if (geos_result) + { + UF_union(uf, p, q); + } + } + } + + if (prep) + GEOSPreparedGeom_destroy(prep); + + if (!success) + break; + } + + if (cxt.items_found) + lwfree(cxt.items_found); + + destroy_strtree(&tree); + return success; +} + +/** Takes an array of GEOSGeometry* and constructs an array of GEOSGeometry*, where each element in the constructed + * array is a GeometryCollection representing a set of interconnected geometries. Caller is responsible for + * freeing the input array, but not for destroying the GEOSGeometry* items inside it. */ +int +cluster_intersecting(GEOSGeometry** geoms, uint32_t num_geoms, GEOSGeometry*** clusterGeoms, uint32_t* num_clusters) +{ + int cluster_success; + UNIONFIND* uf = UF_create(num_geoms); + + if (union_intersecting_pairs(geoms, num_geoms, uf) == LW_FAILURE) + { + UF_destroy(uf); + return LW_FAILURE; + } + + cluster_success = combine_geometries(uf, (void**) geoms, num_geoms, (void***) clusterGeoms, num_clusters, 0); + UF_destroy(uf); + return cluster_success; +} + +static int +dbscan_update_context(GEOSSTRtree* tree, struct QueryContext* cxt, LWGEOM** geoms, uint32_t p, double eps) +{ + cxt->num_items_found = 0; + + GEOSGeometry* query_envelope; + + LW_ON_INTERRUPT(return LW_FAILURE); + + if (geoms[p]->type == POINTTYPE) + { + const POINT2D* pt = getPoint2d_cp(lwgeom_as_lwpoint(geoms[p])->point, 0); + query_envelope = make_geos_segment( pt->x - eps, pt->y - eps, pt->x + eps, pt->y + eps ); + } else { + const GBOX* box = lwgeom_get_bbox(geoms[p]); + query_envelope = make_geos_segment( box->xmin - eps, box->ymin - eps, box->xmax + eps, box->ymax + eps ); + } + + if (!query_envelope) + return LW_FAILURE; + + GEOSSTRtree_query(tree, query_envelope, &query_accumulate, cxt); + + GEOSGeom_destroy(query_envelope); + + return LW_SUCCESS; +} + +/* Union p's cluster with q's cluster, if q is not a border point of another cluster. + * Applicable to DBSCAN with minpoints > 1. + */ +static void +union_if_available(UNIONFIND* uf, uint32_t p, uint32_t q, char* is_in_core, char* in_a_cluster) +{ + if (in_a_cluster[q]) + { + /* Can we merge p's cluster with q's cluster? We can do this only + * if both p and q are considered _core_ points of their respective + * clusters. + */ + if (is_in_core[q]) + { + UF_union(uf, p, q); + } + } + else + { + UF_union(uf, p, q); + in_a_cluster[q] = LW_TRUE; + } +} + +/* An optimized DBSCAN union for the case where min_points == 1. + * If min_points == 1, then we don't care how many neighbors we find; we can union clusters + * on the fly, as we go through the distance calculations. This potentially allows us + * to avoid some distance computations altogether. + */ +static int +union_dbscan_minpoints_1(LWGEOM** geoms, uint32_t num_geoms, UNIONFIND* uf, double eps, char** in_a_cluster_ret) +{ + uint32_t p, i; + struct STRTree tree; + struct QueryContext cxt = + { + .items_found = NULL, + .num_items_found = 0, + .items_found_size = 0 + }; + int success = LW_SUCCESS; + + if (in_a_cluster_ret) + { + char* in_a_cluster = lwalloc(num_geoms * sizeof(char)); + for (i = 0; i < num_geoms; i++) + in_a_cluster[i] = LW_TRUE; + *in_a_cluster_ret = in_a_cluster; + } + + if (num_geoms <= 1) + return LW_SUCCESS; + + tree = make_strtree((void**) geoms, num_geoms, LW_TRUE); + if (tree.tree == NULL) + { + destroy_strtree(&tree); + return LW_FAILURE; + } + + for (p = 0; p < num_geoms; p++) + { + int rv = LW_SUCCESS; + if (lwgeom_is_empty(geoms[p])) + continue; + + rv = dbscan_update_context(tree.tree, &cxt, geoms, p, eps); + if (rv == LW_FAILURE) + { + destroy_strtree(&tree); + return LW_FAILURE; + } + for (i = 0; i < cxt.num_items_found; i++) + { + uint32_t q = *((uint32_t*) cxt.items_found[i]); + + if (UF_find(uf, p) != UF_find(uf, q)) + { + double mindist = lwgeom_mindistance2d_tolerance(geoms[p], geoms[q], eps); + if (mindist == FLT_MAX) + { + success = LW_FAILURE; + break; + } + + if (mindist <= eps) + UF_union(uf, p, q); + } + } + } + + if (cxt.items_found) + lwfree(cxt.items_found); + + destroy_strtree(&tree); + + return success; +} + +static int +union_dbscan_general(LWGEOM** geoms, uint32_t num_geoms, UNIONFIND* uf, double eps, uint32_t min_points, char** in_a_cluster_ret) +{ + uint32_t p, i; + struct STRTree tree; + struct QueryContext cxt = + { + .items_found = NULL, + .num_items_found = 0, + .items_found_size = 0 + }; + int success = LW_SUCCESS; + uint32_t* neighbors; + char* in_a_cluster; + char* is_in_core; + + in_a_cluster = lwalloc(num_geoms * sizeof(char)); + memset(in_a_cluster, 0, num_geoms * sizeof(char)); + + if (in_a_cluster_ret) + *in_a_cluster_ret = in_a_cluster; + + /* Bail if we don't even have enough inputs to make a cluster. */ + if (num_geoms < min_points) + { + if (!in_a_cluster_ret) + lwfree(in_a_cluster); + return LW_SUCCESS; + } + + tree = make_strtree((void**) geoms, num_geoms, LW_TRUE); + if (tree.tree == NULL) + { + destroy_strtree(&tree); + return LW_FAILURE; + } + + is_in_core = lwalloc(num_geoms * sizeof(char)); + memset(is_in_core, 0, num_geoms * sizeof(char)); + neighbors = lwalloc(min_points * sizeof(uint32_t)); + + for (p = 0; p < num_geoms; p++) + { + uint32_t num_neighbors = 0; + int rv; + + if (lwgeom_is_empty(geoms[p])) + continue; + + rv = dbscan_update_context(tree.tree, &cxt, geoms, p, eps); + if (rv == LW_FAILURE) + { + destroy_strtree(&tree); + return LW_FAILURE; + } + + /* We didn't find enough points to do anything, even if they are all within eps. */ + if (cxt.num_items_found < min_points) + continue; + + for (i = 0; i < cxt.num_items_found; i++) + { + uint32_t q = *((uint32_t*) cxt.items_found[i]); + + if (num_neighbors >= min_points) + { + /* If we've already identified p as a core point, and it's already + * in the same cluster in q, then there's nothing to learn by + * computing the distance. + */ + if (UF_find(uf, p) == UF_find(uf, q)) + continue; + + /* Similarly, if q is already identifed as a border point of another + * cluster, there's no point figuring out what the distance is. + */ + if (in_a_cluster[q] && !is_in_core[q]) + continue; + } + + double mindist = lwgeom_mindistance2d_tolerance(geoms[p], geoms[q], eps); + if (mindist == FLT_MAX) + { + success = LW_FAILURE; + break; + } + + if (mindist <= eps) + { + /* If we haven't hit min_points yet, we don't know if we can union p and q. + * Just set q aside for now. + */ + if (num_neighbors < min_points) + { + neighbors[num_neighbors++] = q; + + /* If we just hit min_points, we can now union all of the neighbor geometries + * we've been saving. + */ + if (num_neighbors == min_points) + { + uint32_t j; + is_in_core[p] = LW_TRUE; + in_a_cluster[p] = LW_TRUE; + for (j = 0; j < num_neighbors; j++) + { + union_if_available(uf, p, neighbors[j], is_in_core, in_a_cluster); + } + } + } + else + { + /* If we're above min_points, no need to store our neighbors, just go ahead + * and union them now. This may allow us to cut out some distance + * computations. + */ + union_if_available(uf, p, q, is_in_core, in_a_cluster); + } + } + } + + if (!success) + break; + } + + lwfree(neighbors); + lwfree(is_in_core); + + /* Free in_a_cluster if we're not giving it to our caller */ + if (!in_a_cluster_ret) + lwfree(in_a_cluster); + + if (cxt.items_found) + lwfree(cxt.items_found); + + destroy_strtree(&tree); + return success; +} + +int union_dbscan(LWGEOM** geoms, uint32_t num_geoms, UNIONFIND* uf, double eps, uint32_t min_points, char** in_a_cluster_ret) +{ + if (min_points <= 1) + return union_dbscan_minpoints_1(geoms, num_geoms, uf, eps, in_a_cluster_ret); + else + return union_dbscan_general(geoms, num_geoms, uf, eps, min_points, in_a_cluster_ret); +} + +/** Takes an array of LWGEOM* and constructs an array of LWGEOM*, where each element in the constructed array is a + * GeometryCollection representing a set of geometries separated by no more than the specified tolerance. Caller is + * responsible for freeing the input array, but not the LWGEOM* items inside it. */ +int +cluster_within_distance(LWGEOM** geoms, uint32_t num_geoms, double tolerance, LWGEOM*** clusterGeoms, uint32_t* num_clusters) +{ + int cluster_success; + UNIONFIND* uf = UF_create(num_geoms); + + if (union_dbscan(geoms, num_geoms, uf, tolerance, 1, NULL) == LW_FAILURE) + { + UF_destroy(uf); + return LW_FAILURE; + } + + cluster_success = combine_geometries(uf, (void**) geoms, num_geoms, (void***) clusterGeoms, num_clusters, 1); + UF_destroy(uf); + return cluster_success; +} + +/** Uses a UNIONFIND to identify the set with which each input geometry is associated, and groups the geometries into + * GeometryCollections. Supplied geometry array may be of either LWGEOM* or GEOSGeometry*; is_lwgeom is used to + * identify which. Caller is responsible for freeing input geometry array but not the items contained within it. */ +static int +combine_geometries(UNIONFIND* uf, void** geoms, uint32_t num_geoms, void*** clusterGeoms, uint32_t* num_clusters, char is_lwgeom) +{ + size_t i, j, k; + + /* Combine components of each cluster into their own GeometryCollection */ + *num_clusters = uf->num_clusters; + *clusterGeoms = lwalloc(*num_clusters * sizeof(void*)); + + void** geoms_in_cluster = lwalloc(num_geoms * sizeof(void*)); + uint32_t* ordered_components = UF_ordered_by_cluster(uf); + for (i = 0, j = 0, k = 0; i < num_geoms; i++) + { + geoms_in_cluster[j++] = geoms[ordered_components[i]]; + + /* Is this the last geometry in the component? */ + if ((i == num_geoms - 1) || + (UF_find(uf, ordered_components[i]) != UF_find(uf, ordered_components[i+1]))) + { + if (k >= uf->num_clusters) { + /* Should not get here - it means that we have more clusters than uf->num_clusters thinks we should. */ + return LW_FAILURE; + } + + if (is_lwgeom) + { + LWGEOM** components = lwalloc(j * sizeof(LWGEOM*)); + memcpy(components, geoms_in_cluster, j * sizeof(LWGEOM*)); + (*clusterGeoms)[k++] = lwcollection_construct(COLLECTIONTYPE, components[0]->srid, NULL, j, (LWGEOM**) components); + } + else + { + int srid = GEOSGetSRID(((GEOSGeometry**) geoms_in_cluster)[0]); + GEOSGeometry* combined = GEOSGeom_createCollection(GEOS_GEOMETRYCOLLECTION, (GEOSGeometry**) geoms_in_cluster, j); + GEOSSetSRID(combined, srid); + (*clusterGeoms)[k++] = combined; + } + j = 0; + } + } + + lwfree(geoms_in_cluster); + lwfree(ordered_components); + + return LW_SUCCESS; +} diff --git a/mgist-postgis/liblwgeom/lwgeom_geos_node.c b/mgist-postgis/liblwgeom/lwgeom_geos_node.c new file mode 100644 index 0000000..8e5e8c3 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeom_geos_node.c @@ -0,0 +1,250 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2011 Sandro Santilli + * + **********************************************************************/ + + +#include "lwgeom_geos.h" +#include "liblwgeom_internal.h" + +#include +#include + +static int +lwgeom_ngeoms(const LWGEOM* n) +{ + const LWCOLLECTION* c = lwgeom_as_lwcollection(n); + if ( c ) return c->ngeoms; + else return 1; +} + +static const LWGEOM* +lwgeom_subgeom(const LWGEOM* g, int n) +{ + const LWCOLLECTION* c = lwgeom_as_lwcollection(g); + if ( c ) return lwcollection_getsubgeom((LWCOLLECTION*)c, n); + else return g; +} + + +static void +lwgeom_collect_endpoints(const LWGEOM* lwg, LWMPOINT* col) +{ + int i, n; + LWLINE* l; + + switch (lwg->type) + { + case MULTILINETYPE: + for ( i = 0, + n = lwgeom_ngeoms(lwg); + i < n; ++i ) + { + lwgeom_collect_endpoints( + lwgeom_subgeom(lwg, i), + col); + } + break; + case LINETYPE: + l = (LWLINE*)lwg; + col = lwmpoint_add_lwpoint(col, + lwline_get_lwpoint(l, 0)); + col = lwmpoint_add_lwpoint(col, + lwline_get_lwpoint(l, l->points->npoints-1)); + break; + default: + lwerror("lwgeom_collect_endpoints: invalid type %s", + lwtype_name(lwg->type)); + break; + } +} + +static LWMPOINT* +lwgeom_extract_endpoints(const LWGEOM* lwg) +{ + LWMPOINT* col = lwmpoint_construct_empty(SRID_UNKNOWN, + FLAGS_GET_Z(lwg->flags), + FLAGS_GET_M(lwg->flags)); + lwgeom_collect_endpoints(lwg, col); + + return col; +} + +/* Assumes initGEOS was called already */ +/* May return LWPOINT or LWMPOINT */ +static LWGEOM* +lwgeom_extract_unique_endpoints(const LWGEOM* lwg) +{ + LWGEOM* ret; + GEOSGeometry *gepu; + LWMPOINT *epall = lwgeom_extract_endpoints(lwg); + GEOSGeometry *gepall = LWGEOM2GEOS((LWGEOM*)epall, 1); + lwmpoint_free(epall); + if ( ! gepall ) { + lwerror("LWGEOM2GEOS: %s", lwgeom_geos_errmsg); + return NULL; + } + + /* UnaryUnion to remove duplicates */ + /* TODO: do it all within pgis using indices */ + gepu = GEOSUnaryUnion(gepall); + if ( ! gepu ) { + GEOSGeom_destroy(gepall); + lwerror("GEOSUnaryUnion: %s", lwgeom_geos_errmsg); + return NULL; + } + GEOSGeom_destroy(gepall); + + ret = GEOS2LWGEOM(gepu, FLAGS_GET_Z(lwg->flags)); + GEOSGeom_destroy(gepu); + if ( ! ret ) { + lwerror("Error during GEOS2LWGEOM"); + return NULL; + } + + return ret; +} + +/* exported */ +extern LWGEOM* lwgeom_node(const LWGEOM* lwgeom_in); +LWGEOM* +lwgeom_node(const LWGEOM* lwgeom_in) +{ + GEOSGeometry *g1, *gn, *gm; + LWGEOM *ep, *lines; + LWCOLLECTION *col, *tc; + int pn, ln, np, nl; + + if ( lwgeom_dimension(lwgeom_in) != 1 ) { + lwerror("Noding geometries of dimension != 1 is unsupported"); + return NULL; + } + + initGEOS(lwgeom_geos_error, lwgeom_geos_error); + g1 = LWGEOM2GEOS(lwgeom_in, 1); + if ( ! g1 ) { + lwerror("LWGEOM2GEOS: %s", lwgeom_geos_errmsg); + return NULL; + } + + ep = lwgeom_extract_unique_endpoints(lwgeom_in); + if ( ! ep ) { + GEOSGeom_destroy(g1); + lwerror("Error extracting unique endpoints from input"); + return NULL; + } + + gn = GEOSNode(g1); + GEOSGeom_destroy(g1); + if ( ! gn ) { + lwgeom_free(ep); + lwerror("GEOSNode: %s", lwgeom_geos_errmsg); + return NULL; + } + + gm = GEOSLineMerge(gn); + GEOSGeom_destroy(gn); + if ( ! gm ) { + lwgeom_free(ep); + lwerror("GEOSLineMerge: %s", lwgeom_geos_errmsg); + return NULL; + } + + lines = GEOS2LWGEOM(gm, FLAGS_GET_Z(lwgeom_in->flags)); + GEOSGeom_destroy(gm); + if ( ! lines ) { + lwgeom_free(ep); + lwerror("Error during GEOS2LWGEOM"); + return NULL; + } + + /* + * Reintroduce endpoints from input, using split-line-by-point. + * Note that by now we can be sure that each point splits at + * most _one_ segment as any point shared by multiple segments + * would already be a node. Also we can be sure that any of + * the segments endpoints won't split any other segment. + * We can use the above 2 assertions to early exit the loop. + */ + + col = lwcollection_construct_empty(MULTILINETYPE, lwgeom_in->srid, + FLAGS_GET_Z(lwgeom_in->flags), + FLAGS_GET_M(lwgeom_in->flags)); + + np = lwgeom_ngeoms(ep); + for (pn=0; pn ln+1) { + tc->geoms[nl] = tc->geoms[nl-1]; + --nl; + } + lwgeom_free(tc->geoms[ln]); + tc->geoms[ln] = col->geoms[0]; + tc->geoms[ln+1] = col->geoms[1]; + tc->ngeoms++; + } else { + lwgeom_free(lines); + /* transfer ownership rather than cloning */ + lines = (LWGEOM*)lwcollection_clone_deep(col); + assert(col->ngeoms == 2); + lwgeom_free(col->geoms[0]); + lwgeom_free(col->geoms[1]); + } + + /* reset the vector */ + assert(col->ngeoms == 2); + col->ngeoms = 0; + + break; + } + + } + + lwgeom_free(ep); + lwcollection_free(col); + + lwgeom_set_srid(lines, lwgeom_in->srid); + return (LWGEOM*)lines; +} + diff --git a/mgist-postgis/liblwgeom/lwgeom_geos_split.c b/mgist-postgis/liblwgeom/lwgeom_geos_split.c new file mode 100644 index 0000000..45e5771 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeom_geos_split.c @@ -0,0 +1,591 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2011-2015 Sandro Santilli + * + **********************************************************************/ + +// #include "../postgis_config.h" +/*#define POSTGIS_DEBUG_LEVEL 4*/ +#include "lwgeom_geos.h" +#include "liblwgeom_internal.h" + +#include +#include + +static LWGEOM* lwline_split_by_line(const LWLINE* lwgeom_in, const LWGEOM* blade_in); +static LWGEOM* lwline_split_by_point(const LWLINE* lwgeom_in, const LWPOINT* blade_in); +static LWGEOM* lwline_split_by_mpoint(const LWLINE* lwgeom_in, const LWMPOINT* blade_in); +static LWGEOM* lwline_split(const LWLINE* lwgeom_in, const LWGEOM* blade_in); +static LWGEOM* lwpoly_split_by_line(const LWPOLY* lwgeom_in, const LWGEOM* blade_in); +static LWGEOM* lwcollection_split(const LWCOLLECTION* lwcoll_in, const LWGEOM* blade_in); +static LWGEOM* lwpoly_split(const LWPOLY* lwpoly_in, const LWGEOM* blade_in); + +/* Initializes and uses GEOS internally */ +static LWGEOM* +lwline_split_by_line(const LWLINE* lwline_in, const LWGEOM* blade_in) +{ + LWGEOM** components; + LWGEOM* diff; + LWCOLLECTION* out; + GEOSGeometry* gdiff; /* difference */ + GEOSGeometry* g1; + GEOSGeometry* g2; + int ret; + + /* ASSERT blade_in is LINE or MULTILINE */ + assert (blade_in->type == LINETYPE || + blade_in->type == MULTILINETYPE || + blade_in->type == POLYGONTYPE || + blade_in->type == MULTIPOLYGONTYPE ); + + /* Possible outcomes: + * + * 1. The lines do not cross or overlap + * -> Return a collection with single element + * 2. The lines cross + * -> Return a collection of all elements resulting from the split + */ + + initGEOS(lwgeom_geos_error, lwgeom_geos_error); + + g1 = LWGEOM2GEOS((LWGEOM*)lwline_in, 0); + if ( ! g1 ) + { + lwerror("LWGEOM2GEOS: %s", lwgeom_geos_errmsg); + return NULL; + } + g2 = LWGEOM2GEOS(blade_in, 0); + if ( ! g2 ) + { + GEOSGeom_destroy(g1); + lwerror("LWGEOM2GEOS: %s", lwgeom_geos_errmsg); + return NULL; + } + + /* If blade is a polygon, pick its boundary */ + if ( blade_in->type == POLYGONTYPE || blade_in->type == MULTIPOLYGONTYPE ) + { + gdiff = GEOSBoundary(g2); + GEOSGeom_destroy(g2); + if ( ! gdiff ) + { + GEOSGeom_destroy(g1); + lwerror("GEOSBoundary: %s", lwgeom_geos_errmsg); + return NULL; + } + g2 = gdiff; gdiff = NULL; + } + + /* If interior intersecton is linear we can't split */ + ret = GEOSRelatePattern(g1, g2, "1********"); + if ( 2 == ret ) + { + lwerror("GEOSRelatePattern: %s", lwgeom_geos_errmsg); + GEOSGeom_destroy(g1); + GEOSGeom_destroy(g2); + return NULL; + } + if ( ret ) + { + GEOSGeom_destroy(g1); + GEOSGeom_destroy(g2); + lwerror("Splitter line has linear intersection with input"); + return NULL; + } + + + gdiff = GEOSDifference(g1,g2); + GEOSGeom_destroy(g1); + GEOSGeom_destroy(g2); + if (gdiff == NULL) + { + lwerror("GEOSDifference: %s", lwgeom_geos_errmsg); + return NULL; + } + + diff = GEOS2LWGEOM(gdiff, FLAGS_GET_Z(lwline_in->flags)); + GEOSGeom_destroy(gdiff); + if (NULL == diff) + { + lwerror("GEOS2LWGEOM: %s", lwgeom_geos_errmsg); + return NULL; + } + + out = lwgeom_as_lwcollection(diff); + if ( ! out ) + { + components = lwalloc(sizeof(LWGEOM*)*1); + components[0] = diff; + out = lwcollection_construct(COLLECTIONTYPE, lwline_in->srid, + NULL, 1, components); + } + else + { + /* Set SRID */ + lwgeom_set_srid((LWGEOM*)out, lwline_in->srid); + /* Force collection type */ + out->type = COLLECTIONTYPE; + } + + + return (LWGEOM*)out; +} + +static LWGEOM* +lwline_split_by_point(const LWLINE* lwline_in, const LWPOINT* blade_in) +{ + LWMLINE* out; + + out = lwmline_construct_empty(lwline_in->srid, + FLAGS_GET_Z(lwline_in->flags), + FLAGS_GET_M(lwline_in->flags)); + if ( lwline_split_by_point_to(lwline_in, blade_in, out) < 2 ) + { + lwmline_add_lwline(out, lwline_clone_deep(lwline_in)); + } + + /* Turn multiline into collection */ + out->type = COLLECTIONTYPE; + + return (LWGEOM*)out; +} + +static LWGEOM* +lwline_split_by_mpoint(const LWLINE* lwline_in, const LWMPOINT* mp) +{ + LWMLINE* out; + uint32_t i, j; + + out = lwmline_construct_empty(lwline_in->srid, + FLAGS_GET_Z(lwline_in->flags), + FLAGS_GET_M(lwline_in->flags)); + lwmline_add_lwline(out, lwline_clone_deep(lwline_in)); + + for (i=0; ingeoms; ++i) + { + for (j=0; jngeoms; ++j) + { + lwline_in = out->geoms[j]; + LWPOINT *blade_in = mp->geoms[i]; + int ret = lwline_split_by_point_to(lwline_in, blade_in, out); + if ( 2 == ret ) + { + /* the point splits this line, + * 2 splits were added to collection. + * We'll move the latest added into + * the slot of the current one. + */ + lwline_free(out->geoms[j]); + out->geoms[j] = out->geoms[--out->ngeoms]; + } + } + } + + /* Turn multiline into collection */ + out->type = COLLECTIONTYPE; + + return (LWGEOM*)out; +} + +int +lwline_split_by_point_to(const LWLINE* lwline_in, const LWPOINT* blade_in, + LWMLINE* v) +{ + double mindist_sqr = -1; + POINT4D pt, pt_projected; + POINT4D p1, p2; + POINTARRAY *ipa = lwline_in->points; + POINTARRAY* pa1; + POINTARRAY* pa2; + uint32_t i, nsegs, seg = UINT32_MAX; + + /* Possible outcomes: + * + * 1. The point is not on the line or on the boundary + * -> Leave collection untouched, return 0 + * 2. The point is on the boundary + * -> Leave collection untouched, return 1 + * 3. The point is in the line + * -> Push 2 elements on the collection: + * o start_point - cut_point + * o cut_point - last_point + * -> Return 2 + */ + + getPoint4d_p(blade_in->point, 0, &pt); + + /* Find closest segment */ + if ( ipa->npoints < 1 ) return 0; /* empty input line */ + getPoint4d_p(ipa, 0, &p1); + nsegs = ipa->npoints - 1; + for ( i = 0; i < nsegs; i++ ) + { + getPoint4d_p(ipa, i+1, &p2); + double dist_sqr = distance2d_sqr_pt_seg((POINT2D *)&pt, (POINT2D *)&p1, (POINT2D *)&p2); + LWDEBUGF(4, "Distance (squared) of point %g %g to segment %g %g, %g %g: %g", + pt.x, pt.y, + p1.x, p1.y, + p2.x, p2.y, + dist_sqr); + if (i == 0 || dist_sqr < mindist_sqr) + { + mindist_sqr = dist_sqr; + seg=i; + if (mindist_sqr == 0.0) + break; /* can't be closer than ON line */ + } + p1 = p2; + } + + LWDEBUGF(3, "Closest segment: %d", seg); + LWDEBUGF(3, "mindist: %g", mindist_sqr); + + /* No intersection */ + if (mindist_sqr > 0) + return 0; + + /* empty or single-point line, intersection on boundary */ + if ( seg == UINT32_MAX ) return 1; + + /* + * We need to project the + * point on the closest segment, + * to interpolate Z and M if needed + */ + getPoint4d_p(ipa, seg, &p1); + getPoint4d_p(ipa, seg+1, &p2); + closest_point_on_segment(&pt, &p1, &p2, &pt_projected); + /* But X and Y we want the ones of the input point, + * as on some architectures the interpolation math moves the + * coordinates (see #3422) + */ + pt_projected.x = pt.x; + pt_projected.y = pt.y; + + LWDEBUGF(3, "Projected point:(%g %g), seg:%d, p1:(%g %g), p2:(%g %g)", pt_projected.x, pt_projected.y, seg, p1.x, p1.y, p2.x, p2.y); + + /* When closest point == an endpoint, this is a boundary intersection */ + if ( ( (seg == nsegs-1) && p4d_same(&pt_projected, &p2) ) || + ( (seg == 0) && p4d_same(&pt_projected, &p1) ) ) + { + return 1; + } + + /* This is an internal intersection, let's build the two new pointarrays */ + + pa1 = ptarray_construct_empty(FLAGS_GET_Z(ipa->flags), FLAGS_GET_M(ipa->flags), seg+2); + /* TODO: replace with a memcpy ? */ + for (i=0; i<=seg; ++i) + { + getPoint4d_p(ipa, i, &p1); + ptarray_append_point(pa1, &p1, LW_FALSE); + } + ptarray_append_point(pa1, &pt_projected, LW_FALSE); + + pa2 = ptarray_construct_empty(FLAGS_GET_Z(ipa->flags), FLAGS_GET_M(ipa->flags), ipa->npoints-seg); + ptarray_append_point(pa2, &pt_projected, LW_FALSE); + /* TODO: replace with a memcpy (if so need to check for duplicated point) ? */ + for (i=seg+1; inpoints; ++i) + { + getPoint4d_p(ipa, i, &p1); + ptarray_append_point(pa2, &p1, LW_FALSE); + } + + /* NOTE: I've seen empty pointarrays with loc != 0 and loc != 1 */ + if ( pa1->npoints == 0 || pa2->npoints == 0 ) { + ptarray_free(pa1); + ptarray_free(pa2); + /* Intersection is on the boundary */ + return 1; + } + + lwmline_add_lwline(v, lwline_construct(SRID_UNKNOWN, NULL, pa1)); + lwmline_add_lwline(v, lwline_construct(SRID_UNKNOWN, NULL, pa2)); + return 2; +} + +static LWGEOM* +lwline_split(const LWLINE* lwline_in, const LWGEOM* blade_in) +{ + switch (blade_in->type) + { + case POINTTYPE: + return lwline_split_by_point(lwline_in, (LWPOINT*)blade_in); + case MULTIPOINTTYPE: + return lwline_split_by_mpoint(lwline_in, (LWMPOINT*)blade_in); + + case LINETYPE: + case MULTILINETYPE: + case POLYGONTYPE: + case MULTIPOLYGONTYPE: + return lwline_split_by_line(lwline_in, blade_in); + + default: + lwerror("Splitting a Line by a %s is unsupported", + lwtype_name(blade_in->type)); + return NULL; + } + return NULL; +} + +/* Initializes and uses GEOS internally */ +static LWGEOM* +lwpoly_split_by_line(const LWPOLY* lwpoly_in, const LWGEOM* blade_in) +{ + LWCOLLECTION* out; + GEOSGeometry* g1; + GEOSGeometry* g2; + GEOSGeometry* g1_bounds; + GEOSGeometry* polygons; + const GEOSGeometry *vgeoms[1]; + int i,n; + int hasZ = FLAGS_GET_Z(lwpoly_in->flags); + + + /* Possible outcomes: + * + * 1. The line does not split the polygon + * -> Return a collection with single element + * 2. The line does split the polygon + * -> Return a collection of all elements resulting from the split + */ + + initGEOS(lwgeom_geos_error, lwgeom_geos_error); + + g1 = LWGEOM2GEOS((LWGEOM*)lwpoly_in, 0); + if ( NULL == g1 ) + { + lwerror("LWGEOM2GEOS: %s", lwgeom_geos_errmsg); + return NULL; + } + g1_bounds = GEOSBoundary(g1); + if ( NULL == g1_bounds ) + { + GEOSGeom_destroy(g1); + lwerror("GEOSBoundary: %s", lwgeom_geos_errmsg); + return NULL; + } + + g2 = LWGEOM2GEOS(blade_in, 0); + if ( NULL == g2 ) + { + GEOSGeom_destroy(g1); + GEOSGeom_destroy(g1_bounds); + lwerror("LWGEOM2GEOS: %s", lwgeom_geos_errmsg); + return NULL; + } + + vgeoms[0] = GEOSUnion(g1_bounds, g2); + if ( NULL == vgeoms[0] ) + { + GEOSGeom_destroy(g1); + GEOSGeom_destroy(g2); + GEOSGeom_destroy(g1_bounds); + lwerror("GEOSUnion: %s", lwgeom_geos_errmsg); + return NULL; + } + + polygons = GEOSPolygonize(vgeoms, 1); + if ( NULL == polygons ) + { + GEOSGeom_destroy(g1); + GEOSGeom_destroy(g2); + GEOSGeom_destroy(g1_bounds); + GEOSGeom_destroy((GEOSGeometry*)vgeoms[0]); + lwerror("GEOSPolygonize: %s", lwgeom_geos_errmsg); + return NULL; + } + +#if PARANOIA_LEVEL > 0 + if ( GEOSGeomTypeId(polygons) != COLLECTIONTYPE ) + { + GEOSGeom_destroy(g1); + GEOSGeom_destroy(g2); + GEOSGeom_destroy(g1_bounds); + GEOSGeom_destroy((GEOSGeometry*)vgeoms[0]); + GEOSGeom_destroy(polygons); + lwerror("%s [%s] Unexpected return from GEOSpolygonize", __FILE__, __LINE__); + return 0; + } +#endif + + /* We should now have all polygons, just skip + * the ones which are in holes of the original + * geometries and return the rest in a collection + */ + n = GEOSGetNumGeometries(polygons); + out = lwcollection_construct_empty(COLLECTIONTYPE, lwpoly_in->srid, + hasZ, 0); + /* Allocate space for all polys */ + out->geoms = lwrealloc(out->geoms, sizeof(LWGEOM*)*n); + assert(0 == out->ngeoms); + for (i=0; igeoms[out->ngeoms++] = GEOS2LWGEOM(p, hasZ); + } + + GEOSGeom_destroy(g1); + GEOSGeom_destroy(g2); + GEOSGeom_destroy(g1_bounds); + GEOSGeom_destroy((GEOSGeometry*)vgeoms[0]); + GEOSGeom_destroy(polygons); + + return (LWGEOM*)out; +} + +static LWGEOM* +lwcollection_split(const LWCOLLECTION* lwcoll_in, const LWGEOM* blade_in) +{ + LWGEOM** split_vector=NULL; + LWCOLLECTION* out; + size_t split_vector_capacity; + size_t split_vector_size=0; + size_t i,j; + + split_vector_capacity=8; + split_vector = lwalloc(split_vector_capacity * sizeof(LWGEOM*)); + if ( ! split_vector ) + { + lwerror("Out of virtual memory"); + return NULL; + } + + for (i=0; ingeoms; ++i) + { + LWCOLLECTION* col; + LWGEOM* split = lwgeom_split(lwcoll_in->geoms[i], blade_in); + /* an exception should prevent this from ever returning NULL */ + if ( ! split ) return NULL; + + col = lwgeom_as_lwcollection(split); + /* Output, if any, will always be a collection */ + assert(col); + + /* Reallocate split_vector if needed */ + if ( split_vector_size + col->ngeoms > split_vector_capacity ) + { + /* NOTE: we could be smarter on reallocations here */ + split_vector_capacity += col->ngeoms; + split_vector = lwrealloc(split_vector, + split_vector_capacity * sizeof(LWGEOM*)); + if ( ! split_vector ) + { + lwerror("Out of virtual memory"); + return NULL; + } + } + + for (j=0; jngeoms; ++j) + { + col->geoms[j]->srid = SRID_UNKNOWN; /* strip srid */ + split_vector[split_vector_size++] = col->geoms[j]; + } + lwfree(col->geoms); + lwfree(col); + } + + /* Now split_vector has split_vector_size geometries */ + out = lwcollection_construct(COLLECTIONTYPE, lwcoll_in->srid, + NULL, split_vector_size, split_vector); + + return (LWGEOM*)out; +} + +static LWGEOM* +lwpoly_split(const LWPOLY* lwpoly_in, const LWGEOM* blade_in) +{ + switch (blade_in->type) + { + case MULTILINETYPE: + case LINETYPE: + return lwpoly_split_by_line(lwpoly_in, blade_in); + default: + lwerror("Splitting a Polygon by a %s is unsupported", + lwtype_name(blade_in->type)); + return NULL; + } + return NULL; +} + +/* exported */ +LWGEOM* +lwgeom_split(const LWGEOM* lwgeom_in, const LWGEOM* blade_in) +{ + switch (lwgeom_in->type) + { + case LINETYPE: + return lwline_split((const LWLINE*)lwgeom_in, blade_in); + + case POLYGONTYPE: + return lwpoly_split((const LWPOLY*)lwgeom_in, blade_in); + + case MULTIPOLYGONTYPE: + case MULTILINETYPE: + case COLLECTIONTYPE: + return lwcollection_split((const LWCOLLECTION*)lwgeom_in, blade_in); + + default: + lwerror("Splitting of %s geometries is unsupported", + lwtype_name(lwgeom_in->type)); + return NULL; + } + +} + diff --git a/mgist-postgis/liblwgeom/lwgeom_log.h b/mgist-postgis/liblwgeom/lwgeom_log.h new file mode 100644 index 0000000..feb2ac5 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeom_log.h @@ -0,0 +1,134 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2011 Sandro Santilli + * Copyright 2008 Paul Ramsey + * Copyright 2007-2008 Mark Cave-Ayland + * Copyright 2001-2006 Refractions Research Inc. + * + **********************************************************************/ + + +#ifndef LWGEOM_LOG_H +#define LWGEOM_LOG_H 1 + +#include + +/* + * Debug macros + */ +#if POSTGIS_DEBUG_LEVEL > 0 + +/* Display a notice at the given debug level */ +#define LWDEBUG(level, msg) \ + do { \ + if (POSTGIS_DEBUG_LEVEL >= level) \ + lwdebug(level, "[%s:%s:%d] " msg, __FILE__, __func__, __LINE__); \ + } while (0); + +/* Display a formatted notice at the given debug level + * (like printf, with variadic arguments) */ +#define LWDEBUGF(level, msg, ...) \ + do { \ + if (POSTGIS_DEBUG_LEVEL >= level) \ + lwdebug(level, "[%s:%s:%d] " msg, \ + __FILE__, __func__, __LINE__, __VA_ARGS__); \ + } while (0); + +/* Display a notice and a WKT representation of a geometry + * at the given debug level */ +#define LWDEBUGG(level, geom, msg) \ + if (POSTGIS_DEBUG_LEVEL >= level) \ + do { \ + size_t sz; \ + char *wkt = lwgeom_to_wkt(geom, WKT_EXTENDED, 15, &sz); \ + /* char *wkt = lwgeom_to_hexwkb(geom, WKT_EXTENDED, &sz); */ \ + LWDEBUGF(level, msg ": %s", wkt); \ + lwfree(wkt); \ + } while (0); + +/* Display a formatted notice and a WKT representation of a geometry + * at the given debug level */ +#define LWDEBUGGF(level, geom, fmt, ...) \ + if (POSTGIS_DEBUG_LEVEL >= level) \ + do { \ + size_t sz; \ + char *wkt = lwgeom_to_wkt(geom, WKT_EXTENDED, 15, &sz); \ + /* char *wkt = lwgeom_to_hexwkb(geom, WKT_EXTENDED, &sz); */ \ + LWDEBUGF(level, fmt ": %s", __VA_ARGS__, wkt); \ + lwfree(wkt); \ + } while (0); + +#else /* POSTGIS_DEBUG_LEVEL <= 0 */ + +/* Empty prototype that can be optimised away by the compiler + * for non-debug builds */ +#define LWDEBUG(level, msg) \ + ((void) 0) + +/* Empty prototype that can be optimised away by the compiler + * for non-debug builds */ +#define LWDEBUGF(level, msg, ...) \ + ((void) 0) + +/* Empty prototype that can be optimised away by the compiler + * for non-debug builds */ +#define LWDEBUGG(level, geom, msg) \ + ((void) 0) + +/* Empty prototype that can be optimised away by the compiler + * for non-debug builds */ +#define LWDEBUGGF(level, geom, fmt, ...) \ + ((void) 0) + +#endif /* POSTGIS_DEBUG_LEVEL <= 0 */ + +/** + * Write a notice out to the notice handler. + * + * Uses standard printf() substitutions. + * Use for messages you always want output. + * For debugging, use LWDEBUG() or LWDEBUGF(). + * @ingroup logging + */ +void lwnotice(const char *fmt, ...); + +/** + * Write a notice out to the error handler. + * + * Uses standard printf() substitutions. + * Use for errors you always want output. + * For debugging, use LWDEBUG() or LWDEBUGF(). + * @ingroup logging + */ +void lwerror(const char *fmt, ...); + +/** + * Write a debug message out. + * Don't call this function directly, use the + * macros, LWDEBUG() or LWDEBUGF(), for + * efficiency. + * @ingroup logging + */ +void lwdebug(int level, const char *fmt, ...); + + + +#endif /* LWGEOM_LOG_H */ diff --git a/mgist-postgis/liblwgeom/lwgeom_median.c b/mgist-postgis/liblwgeom/lwgeom_median.c new file mode 100644 index 0000000..0ff90f6 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeom_median.c @@ -0,0 +1,293 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2015 Daniel Baston + * Copyright 2017 Darafei Praliaskouski + * + **********************************************************************/ + +#include +#include + +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + +static double +calc_weighted_distances_3d(const POINT3D* curr, const POINT4D* points, uint32_t npoints, double* distances) +{ + uint32_t i; + double weight = 0.0; + for (i = 0; i < npoints; i++) + { + double dist = distance3d_pt_pt(curr, (POINT3D*)&points[i]); + distances[i] = dist / points[i].m; + weight += dist * points[i].m; + } + + return weight; +} + +static uint32_t +iterate_4d(POINT3D* curr, const POINT4D* points, const uint32_t npoints, const uint32_t max_iter, const double tol) +{ + uint32_t i, iter; + double delta; + double sum_curr = 0, sum_next = 0; + int hit = LW_FALSE; + double *distances = lwalloc(npoints * sizeof(double)); + + sum_curr = calc_weighted_distances_3d(curr, points, npoints, distances); + + for (iter = 0; iter < max_iter; iter++) + { + POINT3D next = { 0, 0, 0 }; + double denom = 0; + + /** Calculate denom to get the next point */ + for (i = 0; i < npoints; i++) + { + /* we need to use lower epsilon than in FP_IS_ZERO in the loop for calculation to converge */ + if (distances[i] > DBL_EPSILON) + { + next.x += points[i].x / distances[i]; + next.y += points[i].y / distances[i]; + next.z += points[i].z / distances[i]; + denom += 1.0 / distances[i]; + } + else + { + hit = LW_TRUE; + } + } + + if (denom < DBL_EPSILON) + { + /* No movement - Final point */ + break; + } + + /* Calculate the new point */ + next.x /= denom; + next.y /= denom; + next.z /= denom; + + /* If any of the intermediate points in the calculation is found in the + * set of input points, the standard Weiszfeld method gets stuck with a + * divide-by-zero. + * + * To get ourselves out of the hole, we follow an alternate procedure to + * get the next iteration, as described in: + * + * Vardi, Y. and Zhang, C. (2011) "A modified Weiszfeld algorithm for the + * Fermat-Weber location problem." Math. Program., Ser. A 90: 559-566. + * DOI 10.1007/s101070100222 + * + * Available online at the time of this writing at + * http://www.stat.rutgers.edu/home/cunhui/papers/43.pdf + */ + if (hit) + { + double dx = 0, dy = 0, dz = 0; + double d_sqr; + hit = LW_FALSE; + + for (i = 0; i < npoints; i++) + { + if (distances[i] > DBL_EPSILON) + { + dx += (points[i].x - curr->x) / distances[i]; + dy += (points[i].y - curr->y) / distances[i]; + dz += (points[i].z - curr->z) / distances[i]; + } + } + + d_sqr = sqrt(dx*dx + dy*dy + dz*dz); + if (d_sqr > DBL_EPSILON) + { + double r_inv = FP_MAX(0, 1.0 / d_sqr); + next.x = (1.0 - r_inv)*next.x + r_inv*curr->x; + next.y = (1.0 - r_inv)*next.y + r_inv*curr->y; + next.z = (1.0 - r_inv)*next.z + r_inv*curr->z; + } + } + + /* Check movement with next point */ + sum_next = calc_weighted_distances_3d(&next, points, npoints, distances); + delta = sum_curr - sum_next; + if (delta < tol) + { + break; + } + else + { + curr->x = next.x; + curr->y = next.y; + curr->z = next.z; + sum_curr = sum_next; + } + } + + lwfree(distances); + return iter; +} + +static POINT3D +init_guess(const POINT4D* points, uint32_t npoints) +{ + assert(npoints > 0); + POINT3D guess = { 0, 0, 0 }; + double mass = 0; + uint32_t i; + for (i = 0; i < npoints; i++) + { + guess.x += points[i].x * points[i].m; + guess.y += points[i].y * points[i].m; + guess.z += points[i].z * points[i].m; + mass += points[i].m; + } + guess.x /= mass; + guess.y /= mass; + guess.z /= mass; + return guess; +} + +POINT4D* +lwmpoint_extract_points_4d(const LWMPOINT* g, uint32_t* npoints, int* input_empty) +{ + uint32_t i; + uint32_t n = 0; + POINT4D* points = lwalloc(g->ngeoms * sizeof(POINT4D)); + int has_m = lwgeom_has_m((LWGEOM*) g); + + for (i = 0; i < g->ngeoms; i++) + { + LWGEOM* subg = lwcollection_getsubgeom((LWCOLLECTION*) g, i); + if (!lwgeom_is_empty(subg)) + { + *input_empty = LW_FALSE; + if (!getPoint4d_p(((LWPOINT*) subg)->point, 0, &points[n])) + { + lwerror("Geometric median: getPoint4d_p reported failure on point (POINT(%g %g %g %g), number %d of %d in input).", points[n].x, points[n].y, points[n].z, points[n].m, i, g->ngeoms); + lwfree(points); + return NULL; + } + if (has_m) + { + /* This limitation on non-negativity can be lifted + * by replacing Weiszfeld algorithm with different one. + * Possible option described in: + * + * Drezner, Zvi & O. Wesolowsky, George. (1991). + * The Weber Problem On The Plane With Some Negative Weights. + * INFOR. Information Systems and Operational Research. + * 29. 10.1080/03155986.1991.11732158. + */ + if (points[n].m < 0) + { + lwerror("Geometric median input contains points with negative weights (POINT(%g %g %g %g), number %d of %d in input). Implementation can't guarantee global minimum convergence.", points[n].x, points[n].y, points[n].z, points[n].m, i, g->ngeoms); + lwfree(points); + return NULL; + } + + /* points with zero weight are not going to affect calculation, drop them early */ + if (points[n].m > DBL_EPSILON) n++; + } + else + { + points[n].m = 1.0; + n++; + } + } + } + +#if PARANOIA_LEVEL > 0 + /* check Z=0 for 2D inputs*/ + if (!lwgeom_has_z((LWGEOM*) g)) for (i = 0; i < n; i++) assert(points[i].z == 0); +#endif + + *npoints = n; + return points; +} + + +LWPOINT* +lwmpoint_median(const LWMPOINT* g, double tol, uint32_t max_iter, char fail_if_not_converged) +{ + /* m ordinate is considered weight, if defined */ + uint32_t npoints = 0; /* we need to count this ourselves so we can exclude empties and weightless points */ + uint32_t i; + int input_empty = LW_TRUE; + POINT3D median; + POINT4D* points = lwmpoint_extract_points_4d(g, &npoints, &input_empty); + + /* input validation failed, error reported already */ + if (points == NULL) return NULL; + + if (npoints == 0) + { + lwfree(points); + if (input_empty) + { + return lwpoint_construct_empty(g->srid, 0, 0); + } + else + { + lwerror("Median failed to find non-empty input points with positive weight."); + return NULL; + } + } + + median = init_guess(points, npoints); + + i = iterate_4d(&median, points, npoints, max_iter, tol); + + lwfree(points); + + if (fail_if_not_converged && i >= max_iter) + { + lwerror("Median failed to converge within %g after %d iterations.", tol, max_iter); + return NULL; + } + + if (lwgeom_has_z((LWGEOM*) g)) + { + return lwpoint_make3dz(g->srid, median.x, median.y, median.z); + } + else + { + return lwpoint_make2d(g->srid, median.x, median.y); + } +} + +LWPOINT* +lwgeom_median(const LWGEOM* g, double tol, uint32_t max_iter, char fail_if_not_converged) +{ + switch (g->type) + { + case POINTTYPE: + return lwpoint_clone(lwgeom_as_lwpoint(g)); + case MULTIPOINTTYPE: + return lwmpoint_median(lwgeom_as_lwmpoint(g), tol, max_iter, fail_if_not_converged); + default: + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(g->type)); + return NULL; + } +} + diff --git a/mgist-postgis/liblwgeom/lwgeom_sfcgal.c b/mgist-postgis/liblwgeom/lwgeom_sfcgal.c new file mode 100644 index 0000000..0eb581f --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeom_sfcgal.c @@ -0,0 +1,611 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2012-2013 Oslandia + * + **********************************************************************/ + +#include "lwgeom_sfcgal.h" +#include + +static int SFCGAL_type_to_lwgeom_type(sfcgal_geometry_type_t type); +static POINTARRAY *ptarray_from_SFCGAL(const sfcgal_geometry_t *geom, int force3D); +static sfcgal_geometry_t *ptarray_to_SFCGAL(const POINTARRAY *pa, int type); + +/* Return SFCGAL version string */ +const char * +lwgeom_sfcgal_version() +{ + const char *version = sfcgal_version(); + return version; +} + +#define MAX_LENGTH_SFCGAL_FULL_VERSION 50 +/* Return SFCGAL full version string */ +const char * +lwgeom_sfcgal_full_version() +{ +#if POSTGIS_SFCGAL_VERSION >= 10400 + const char *version = sfcgal_full_version(); +#else + char *version = (char*)lwalloc(MAX_LENGTH_SFCGAL_FULL_VERSION); + snprintf(version, MAX_LENGTH_SFCGAL_FULL_VERSION, + "SFCGAL=\"%s\" CGAL=\"Unknown\" Boost=\"Unknown\"", + sfcgal_version()); +#endif + return version; +} + +/* + * Mapping between SFCGAL and PostGIS types + * + * Throw an error if type is unsupported + */ +static int +SFCGAL_type_to_lwgeom_type(sfcgal_geometry_type_t type) +{ + switch (type) + { + case SFCGAL_TYPE_POINT: + return POINTTYPE; + + case SFCGAL_TYPE_LINESTRING: + return LINETYPE; + + case SFCGAL_TYPE_POLYGON: + return POLYGONTYPE; + + case SFCGAL_TYPE_MULTIPOINT: + return MULTIPOINTTYPE; + + case SFCGAL_TYPE_MULTILINESTRING: + return MULTILINETYPE; + + case SFCGAL_TYPE_MULTIPOLYGON: + return MULTIPOLYGONTYPE; + + case SFCGAL_TYPE_MULTISOLID: + return COLLECTIONTYPE; /* Nota: PolyhedralSurface closed inside + aim is to use true solid type as soon + as available in OGC SFS */ + + case SFCGAL_TYPE_GEOMETRYCOLLECTION: + return COLLECTIONTYPE; + +#if 0 + case SFCGAL_TYPE_CIRCULARSTRING: + return CIRCSTRINGTYPE; + + case SFCGAL_TYPE_COMPOUNDCURVE: + return COMPOUNDTYPE; + + case SFCGAL_TYPE_CURVEPOLYGON: + return CURVEPOLYTYPE; + + case SFCGAL_TYPE_MULTICURVE: + return MULTICURVETYPE; + + case SFCGAL_TYPE_MULTISURFACE: + return MULTISURFACETYPE; +#endif + + case SFCGAL_TYPE_POLYHEDRALSURFACE: + return POLYHEDRALSURFACETYPE; + + case SFCGAL_TYPE_TRIANGULATEDSURFACE: + return TINTYPE; + + case SFCGAL_TYPE_TRIANGLE: + return TRIANGLETYPE; + + default: + lwerror("SFCGAL_type_to_lwgeom_type: Unknown Type"); + return 0; + } +} + +/* + * Return a PostGIS pointarray from a simple SFCGAL geometry: + * POINT, LINESTRING or TRIANGLE + * + * Throw an error on others types + */ +static POINTARRAY * +ptarray_from_SFCGAL(const sfcgal_geometry_t *geom, int want3d) +{ + POINT4D point; + uint32_t i, npoints; + POINTARRAY *pa = NULL; + + assert(geom); + + switch (sfcgal_geometry_type_id(geom)) + { + case SFCGAL_TYPE_POINT: + { + pa = ptarray_construct(want3d, 0, 1); + point.x = sfcgal_point_x(geom); + point.y = sfcgal_point_y(geom); + + if (sfcgal_geometry_is_3d(geom)) + point.z = sfcgal_point_z(geom); + else if (want3d) + point.z = 0.0; + + ptarray_set_point4d(pa, 0, &point); + } + break; + + case SFCGAL_TYPE_LINESTRING: + { + npoints = sfcgal_linestring_num_points(geom); + pa = ptarray_construct(want3d, 0, npoints); + + for (i = 0; i < npoints; i++) + { + const sfcgal_geometry_t *pt = sfcgal_linestring_point_n(geom, i); + point.x = sfcgal_point_x(pt); + point.y = sfcgal_point_y(pt); + + if (sfcgal_geometry_is_3d(geom)) + point.z = sfcgal_point_z(pt); + else if (want3d) + point.z = 0.0; + + ptarray_set_point4d(pa, i, &point); + } + } + break; + + case SFCGAL_TYPE_TRIANGLE: + { + pa = ptarray_construct(want3d, 0, 4); + + for (i = 0; i < 4; i++) + { + const sfcgal_geometry_t *pt = sfcgal_triangle_vertex(geom, (i % 3)); + point.x = sfcgal_point_x(pt); + point.y = sfcgal_point_y(pt); + + if (sfcgal_geometry_is_3d(geom)) + point.z = sfcgal_point_z(pt); + else if (want3d) + point.z = 0.0; + + ptarray_set_point4d(pa, i, &point); + } + } + break; + + /* Other types should not be called directly ... */ + default: + lwerror("ptarray_from_SFCGAL: Unknown Type"); + break; + } + return pa; +} + +/* + * Convert a PostGIS pointarray to SFCGAL structure + * + * Used for simple LWGEOM geometry POINT, LINESTRING, TRIANGLE + * and POLYGON rings + */ +static sfcgal_geometry_t * +ptarray_to_SFCGAL(const POINTARRAY *pa, int type) +{ + POINT3DZ point; + int is_3d; + uint32_t i; + + assert(pa); + + is_3d = FLAGS_GET_Z(pa->flags) != 0; + + switch (type) + { + case POINTTYPE: + { + getPoint3dz_p(pa, 0, &point); + if (is_3d) + return sfcgal_point_create_from_xyz(point.x, point.y, point.z); + else + return sfcgal_point_create_from_xy(point.x, point.y); + } + break; + + case LINETYPE: + { + sfcgal_geometry_t *line = sfcgal_linestring_create(); + + for (i = 0; i < pa->npoints; i++) + { + getPoint3dz_p(pa, i, &point); + if (is_3d) + { + sfcgal_linestring_add_point(line, + sfcgal_point_create_from_xyz(point.x, point.y, point.z)); + } + else + { + sfcgal_linestring_add_point(line, sfcgal_point_create_from_xy(point.x, point.y)); + } + } + + return line; + } + break; + + case TRIANGLETYPE: + { + sfcgal_geometry_t *triangle = sfcgal_triangle_create(); + + getPoint3dz_p(pa, 0, &point); + if (is_3d) + sfcgal_triangle_set_vertex_from_xyz(triangle, 0, point.x, point.y, point.z); + else + sfcgal_triangle_set_vertex_from_xy(triangle, 0, point.x, point.y); + + getPoint3dz_p(pa, 1, &point); + if (is_3d) + sfcgal_triangle_set_vertex_from_xyz(triangle, 1, point.x, point.y, point.z); + else + sfcgal_triangle_set_vertex_from_xy(triangle, 1, point.x, point.y); + + getPoint3dz_p(pa, 2, &point); + if (is_3d) + sfcgal_triangle_set_vertex_from_xyz(triangle, 2, point.x, point.y, point.z); + else + sfcgal_triangle_set_vertex_from_xy(triangle, 2, point.x, point.y); + + return triangle; + } + break; + + /* Other SFCGAL types should not be called directly ... */ + default: + lwerror("ptarray_from_SFCGAL: Unknown Type"); + return NULL; + } +} + +/* + * Convert a SFCGAL structure to PostGIS LWGEOM + * + * Throws an error on unsupported type + */ +LWGEOM * +SFCGAL2LWGEOM(const sfcgal_geometry_t *geom, int force3D, int32_t srid) +{ + uint32_t ngeoms, nshells, nsolids; + uint32_t i, j, k; + int want3d; + + assert(geom); + + want3d = force3D || sfcgal_geometry_is_3d(geom); + + switch (sfcgal_geometry_type_id(geom)) + { + case SFCGAL_TYPE_POINT: + { + if (sfcgal_geometry_is_empty(geom)) + return (LWGEOM *)lwpoint_construct_empty(srid, want3d, 0); + + POINTARRAY *pa = ptarray_from_SFCGAL(geom, want3d); + return (LWGEOM *)lwpoint_construct(srid, NULL, pa); + } + + case SFCGAL_TYPE_LINESTRING: + { + if (sfcgal_geometry_is_empty(geom)) + return (LWGEOM *)lwline_construct_empty(srid, want3d, 0); + + POINTARRAY *pa = ptarray_from_SFCGAL(geom, want3d); + return (LWGEOM *)lwline_construct(srid, NULL, pa); + } + + case SFCGAL_TYPE_TRIANGLE: + { + if (sfcgal_geometry_is_empty(geom)) + return (LWGEOM *)lwtriangle_construct_empty(srid, want3d, 0); + + POINTARRAY *pa = ptarray_from_SFCGAL(geom, want3d); + return (LWGEOM *)lwtriangle_construct(srid, NULL, pa); + } + + case SFCGAL_TYPE_POLYGON: + { + if (sfcgal_geometry_is_empty(geom)) + return (LWGEOM *)lwpoly_construct_empty(srid, want3d, 0); + + uint32_t nrings = sfcgal_polygon_num_interior_rings(geom) + 1; + POINTARRAY **pa = (POINTARRAY **)lwalloc(sizeof(POINTARRAY *) * nrings); + + pa[0] = ptarray_from_SFCGAL(sfcgal_polygon_exterior_ring(geom), want3d); + for (i = 1; i < nrings; i++) + pa[i] = ptarray_from_SFCGAL(sfcgal_polygon_interior_ring_n(geom, i - 1), want3d); + + return (LWGEOM *)lwpoly_construct(srid, NULL, nrings, pa); + } + + case SFCGAL_TYPE_MULTIPOINT: + case SFCGAL_TYPE_MULTILINESTRING: + case SFCGAL_TYPE_MULTIPOLYGON: + case SFCGAL_TYPE_MULTISOLID: + case SFCGAL_TYPE_GEOMETRYCOLLECTION: + { + ngeoms = sfcgal_geometry_collection_num_geometries(geom); + LWGEOM **geoms = NULL; + if (ngeoms) + { + nsolids = 0; + geoms = (LWGEOM **)lwalloc(sizeof(LWGEOM *) * ngeoms); + for (i = 0; i < ngeoms; i++) + { + const sfcgal_geometry_t *g = sfcgal_geometry_collection_geometry_n(geom, i); + geoms[i] = SFCGAL2LWGEOM(g, 0, srid); + if (FLAGS_GET_SOLID(geoms[i]->flags)) + ++nsolids; + } + geoms = (LWGEOM **)lwrealloc(geoms, sizeof(LWGEOM *) * ngeoms); + } + LWGEOM *rgeom = (LWGEOM *)lwcollection_construct( + SFCGAL_type_to_lwgeom_type(sfcgal_geometry_type_id(geom)), srid, NULL, ngeoms, geoms); + if (ngeoms) + { + if (ngeoms == nsolids) + FLAGS_SET_SOLID(rgeom->flags, 1); + else if (nsolids) + lwnotice( + "SFCGAL2LWGEOM: SOLID in heterogeneous collection will be treated as a POLYHEDRALSURFACETYPE"); + } + return rgeom; + } + +#if 0 + case SFCGAL_TYPE_CIRCULARSTRING: + case SFCGAL_TYPE_COMPOUNDCURVE: + case SFCGAL_TYPE_CURVEPOLYGON: + case SFCGAL_TYPE_MULTICURVE: + case SFCGAL_TYPE_MULTISURFACE: + case SFCGAL_TYPE_CURVE: + case SFCGAL_TYPE_SURFACE: + + /* TODO curve types handling */ +#endif + + case SFCGAL_TYPE_POLYHEDRALSURFACE: + { + ngeoms = sfcgal_polyhedral_surface_num_polygons(geom); + + LWGEOM **geoms = NULL; + if (ngeoms) + { + geoms = (LWGEOM **)lwalloc(sizeof(LWGEOM *) * ngeoms); + for (i = 0; i < ngeoms; i++) + { + const sfcgal_geometry_t *g = sfcgal_polyhedral_surface_polygon_n(geom, i); + geoms[i] = SFCGAL2LWGEOM(g, 0, srid); + } + } + return (LWGEOM *)lwcollection_construct(POLYHEDRALSURFACETYPE, srid, NULL, ngeoms, geoms); + } + + /* Solid is map as a closed PolyhedralSurface (for now) */ + case SFCGAL_TYPE_SOLID: + { + nshells = sfcgal_solid_num_shells(geom); + + for (ngeoms = 0, i = 0; i < nshells; i++) + ngeoms += sfcgal_polyhedral_surface_num_polygons(sfcgal_solid_shell_n(geom, i)); + + LWGEOM **geoms = 0; + if (ngeoms) + { + geoms = (LWGEOM **)lwalloc(sizeof(LWGEOM *) * ngeoms); + for (i = 0, k = 0; i < nshells; i++) + { + const sfcgal_geometry_t *shell = sfcgal_solid_shell_n(geom, i); + ngeoms = sfcgal_polyhedral_surface_num_polygons(shell); + + for (j = 0; j < ngeoms; j++) + { + const sfcgal_geometry_t *g = sfcgal_polyhedral_surface_polygon_n(shell, j); + geoms[k] = SFCGAL2LWGEOM(g, 1, srid); + k++; + } + } + } + LWGEOM *rgeom = (LWGEOM *)lwcollection_construct(POLYHEDRALSURFACETYPE, srid, NULL, ngeoms, geoms); + if (ngeoms) + FLAGS_SET_SOLID(rgeom->flags, 1); + return rgeom; + } + + case SFCGAL_TYPE_TRIANGULATEDSURFACE: + { + ngeoms = sfcgal_triangulated_surface_num_triangles(geom); + LWGEOM **geoms = NULL; + if (ngeoms) + { + geoms = (LWGEOM **)lwalloc(sizeof(LWGEOM *) * ngeoms); + for (i = 0; i < ngeoms; i++) + { + const sfcgal_geometry_t *g = sfcgal_triangulated_surface_triangle_n(geom, i); + geoms[i] = SFCGAL2LWGEOM(g, 0, srid); + } + } + return (LWGEOM *)lwcollection_construct(TINTYPE, srid, NULL, ngeoms, geoms); + } + + default: + lwerror("SFCGAL2LWGEOM: Unknown Type"); + return NULL; + } +} + +sfcgal_geometry_t * +LWGEOM2SFCGAL(const LWGEOM *geom) +{ + uint32_t i; + sfcgal_geometry_t *ret_geom = NULL; + + assert(geom); + + switch (geom->type) + { + case POINTTYPE: + { + const LWPOINT *lwp = (const LWPOINT *)geom; + if (lwgeom_is_empty(geom)) + return sfcgal_point_create(); + + return ptarray_to_SFCGAL(lwp->point, POINTTYPE); + } + break; + + case LINETYPE: + { + const LWLINE *line = (const LWLINE *)geom; + if (lwgeom_is_empty(geom)) + return sfcgal_linestring_create(); + + return ptarray_to_SFCGAL(line->points, LINETYPE); + } + break; + + case TRIANGLETYPE: + { + const LWTRIANGLE *triangle = (const LWTRIANGLE *)geom; + if (lwgeom_is_empty(geom)) + return sfcgal_triangle_create(); + return ptarray_to_SFCGAL(triangle->points, TRIANGLETYPE); + } + break; + + case POLYGONTYPE: + { + const LWPOLY *poly = (const LWPOLY *)geom; + uint32_t nrings = poly->nrings - 1; + + if (lwgeom_is_empty(geom)) + return sfcgal_polygon_create(); + + sfcgal_geometry_t *exterior_ring = ptarray_to_SFCGAL(poly->rings[0], LINETYPE); + ret_geom = sfcgal_polygon_create_from_exterior_ring(exterior_ring); + + for (i = 0; i < nrings; i++) + { + sfcgal_geometry_t *ring = ptarray_to_SFCGAL(poly->rings[i + 1], LINETYPE); + sfcgal_polygon_add_interior_ring(ret_geom, ring); + } + return ret_geom; + } + break; + + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + { + if (geom->type == MULTIPOINTTYPE) + ret_geom = sfcgal_multi_point_create(); + else if (geom->type == MULTILINETYPE) + ret_geom = sfcgal_multi_linestring_create(); + else if (geom->type == MULTIPOLYGONTYPE) + ret_geom = sfcgal_multi_polygon_create(); + else + ret_geom = sfcgal_geometry_collection_create(); + + const LWCOLLECTION *lwc = (const LWCOLLECTION *)geom; + for (i = 0; i < lwc->ngeoms; i++) + { + sfcgal_geometry_t *g = LWGEOM2SFCGAL(lwc->geoms[i]); + sfcgal_geometry_collection_add_geometry(ret_geom, g); + } + + return ret_geom; + } + break; + + case POLYHEDRALSURFACETYPE: + { + const LWPSURFACE *lwp = (const LWPSURFACE *)geom; + ret_geom = sfcgal_polyhedral_surface_create(); + + for (i = 0; i < lwp->ngeoms; i++) + { + sfcgal_geometry_t *g = LWGEOM2SFCGAL((const LWGEOM *)lwp->geoms[i]); + sfcgal_polyhedral_surface_add_polygon(ret_geom, g); + } + /* We treat polyhedral surface as the only exterior shell, + since we can't distinguish exterior from interior shells ... */ + if (FLAGS_GET_SOLID(lwp->flags)) + { + return sfcgal_solid_create_from_exterior_shell(ret_geom); + } + + return ret_geom; + } + break; + + case TINTYPE: + { + const LWTIN *lwp = (const LWTIN *)geom; + ret_geom = sfcgal_triangulated_surface_create(); + + for (i = 0; i < lwp->ngeoms; i++) + { + sfcgal_geometry_t *g = LWGEOM2SFCGAL((const LWGEOM *)lwp->geoms[i]); + sfcgal_triangulated_surface_add_triangle(ret_geom, g); + } + + return ret_geom; + } + break; + + default: + lwerror("LWGEOM2SFCGAL: Unsupported geometry type %s !", lwtype_name(geom->type)); + return NULL; + } +} + +/* + * No Operation SFCGAL function, used (only) for cunit tests + * Take a PostGIS geometry, send it to SFCGAL and return it unchanged (in theory) + */ +LWGEOM * +lwgeom_sfcgal_noop(const LWGEOM *geom_in) +{ + sfcgal_geometry_t *converted; + + assert(geom_in); + + converted = LWGEOM2SFCGAL(geom_in); + assert(converted); + + LWGEOM *geom_out = SFCGAL2LWGEOM(converted, 0, SRID_UNKNOWN); + sfcgal_geometry_delete(converted); + + /* copy SRID (SFCGAL does not store the SRID) */ + geom_out->srid = geom_in->srid; + return geom_out; +} diff --git a/mgist-postgis/liblwgeom/lwgeom_sfcgal.h b/mgist-postgis/liblwgeom/lwgeom_sfcgal.h new file mode 100644 index 0000000..fa21be6 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeom_sfcgal.h @@ -0,0 +1,43 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2012-2013 Oslandia + * + **********************************************************************/ + +#include "liblwgeom_internal.h" +#include + +/* return SFCGAL version string */ +const char *lwgeom_sfcgal_version(void); + +/* return SFCGAL full version string */ +const char *lwgeom_sfcgal_full_version(void); + +/* Convert SFCGAL structure to lwgeom PostGIS */ +LWGEOM *SFCGAL2LWGEOM(const sfcgal_geometry_t *geom, int force3D, int32_t SRID); + +/* Convert lwgeom PostGIS to SFCGAL structure */ +sfcgal_geometry_t *LWGEOM2SFCGAL(const LWGEOM *geom); + +/* No Operation SFCGAL function, used (only) for cunit tests + * Take a PostGIS geometry, send it to SFCGAL and return it unchanged + */ +LWGEOM *lwgeom_sfcgal_noop(const LWGEOM *geom_in); diff --git a/mgist-postgis/liblwgeom/lwgeom_topo.c b/mgist-postgis/liblwgeom/lwgeom_topo.c new file mode 100644 index 0000000..cbd8bdd --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeom_topo.c @@ -0,0 +1,7361 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2015-2022 Sandro Santilli + * + **********************************************************************/ + + + +// #include "../postgis_config.h" + +/*#define POSTGIS_DEBUG_LEVEL 1*/ +#include "lwgeom_log.h" + +#include "liblwgeom_internal.h" +#include "liblwgeom_topo_internal.h" +#include "lwgeom_geos.h" + +#include +#include /* for PRId64 */ +#include + +#ifdef WIN32 +# define LWTFMT_ELEMID "lld" +#else +# define LWTFMT_ELEMID PRId64 +#endif + +/* This is a non-tolerance based 2d equality for points + * whereas the p2d_same function is tolerance based + * TODO: move in higher headers ? + */ +#define P2D_SAME_STRICT(a,b) ((a)->x == (b)->x && (a)->y == (b)->y) + +/********************************************************************* + * + * Backend iface + * + ********************************************************************/ + +LWT_BE_IFACE* lwt_CreateBackendIface(const LWT_BE_DATA *data) +{ + LWT_BE_IFACE *iface = lwalloc(sizeof(LWT_BE_IFACE)); + iface->data = data; + iface->cb = NULL; + return iface; +} + +void lwt_BackendIfaceRegisterCallbacks(LWT_BE_IFACE *iface, + const LWT_BE_CALLBACKS* cb) +{ + iface->cb = cb; +} + +void lwt_FreeBackendIface(LWT_BE_IFACE* iface) +{ + lwfree(iface); +} + +/********************************************************************* + * + * Backend wrappers + * + ********************************************************************/ + +#define CHECKCB(be, method) do { \ + if ( ! (be)->cb || ! (be)->cb->method ) \ + lwerror("Callback " # method " not registered by backend"); \ +} while (0) + +#define CB0(be, method) \ + CHECKCB(be, method);\ + return (be)->cb->method((be)->data) + +#define CB1(be, method, a1) \ + CHECKCB(be, method);\ + return (be)->cb->method((be)->data, a1) + +#define CBT0(to, method) \ + CHECKCB((to)->be_iface, method);\ + return (to)->be_iface->cb->method((to)->be_topo) + +#define CBT1(to, method, a1) \ + CHECKCB((to)->be_iface, method);\ + return (to)->be_iface->cb->method((to)->be_topo, a1) + +#define CBT2(to, method, a1, a2) \ + CHECKCB((to)->be_iface, method);\ + return (to)->be_iface->cb->method((to)->be_topo, a1, a2) + +#define CBT3(to, method, a1, a2, a3) \ + CHECKCB((to)->be_iface, method);\ + return (to)->be_iface->cb->method((to)->be_topo, a1, a2, a3) + +#define CBT4(to, method, a1, a2, a3, a4) \ + CHECKCB((to)->be_iface, method);\ + return (to)->be_iface->cb->method((to)->be_topo, a1, a2, a3, a4) + +#define CBT5(to, method, a1, a2, a3, a4, a5) \ + CHECKCB((to)->be_iface, method);\ + return (to)->be_iface->cb->method((to)->be_topo, a1, a2, a3, a4, a5) + +#define CBT6(to, method, a1, a2, a3, a4, a5, a6) \ + CHECKCB((to)->be_iface, method);\ + return (to)->be_iface->cb->method((to)->be_topo, a1, a2, a3, a4, a5, a6) + +const char * +lwt_be_lastErrorMessage(const LWT_BE_IFACE* be) +{ + CB0(be, lastErrorMessage); +} + +LWT_BE_TOPOLOGY * +lwt_be_loadTopologyByName(LWT_BE_IFACE *be, const char *name) +{ + CB1(be, loadTopologyByName, name); +} + +static int +lwt_be_topoGetSRID(LWT_TOPOLOGY *topo) +{ + CBT0(topo, topoGetSRID); +} + +static double +lwt_be_topoGetPrecision(LWT_TOPOLOGY *topo) +{ + CBT0(topo, topoGetPrecision); +} + +static int +lwt_be_topoHasZ(LWT_TOPOLOGY *topo) +{ + CBT0(topo, topoHasZ); +} + +int +lwt_be_freeTopology(LWT_TOPOLOGY *topo) +{ + CBT0(topo, freeTopology); +} + +LWT_ISO_NODE * +lwt_be_getNodeById(LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields) +{ + CBT3(topo, getNodeById, ids, numelems, fields); +} + +LWT_ISO_NODE * +lwt_be_getNodeWithinDistance2D(LWT_TOPOLOGY *topo, + LWPOINT *pt, + double dist, + uint64_t *numelems, + int fields, + int64_t limit) +{ + CBT5(topo, getNodeWithinDistance2D, pt, dist, numelems, fields, limit); +} + +static LWT_ISO_NODE * +lwt_be_getNodeWithinBox2D(const LWT_TOPOLOGY *topo, const GBOX *box, uint64_t *numelems, int fields, uint64_t limit) +{ + CBT4(topo, getNodeWithinBox2D, box, numelems, fields, limit); +} + +static LWT_ISO_EDGE * +lwt_be_getEdgeWithinBox2D(const LWT_TOPOLOGY *topo, const GBOX *box, uint64_t *numelems, int fields, uint64_t limit) +{ + CBT4(topo, getEdgeWithinBox2D, box, numelems, fields, limit); +} + +static LWT_ISO_FACE * +lwt_be_getFaceWithinBox2D(const LWT_TOPOLOGY *topo, const GBOX *box, uint64_t *numelems, int fields, uint64_t limit) +{ + CBT4(topo, getFaceWithinBox2D, box, numelems, fields, limit); +} + +int +lwt_be_insertNodes(LWT_TOPOLOGY *topo, LWT_ISO_NODE *node, uint64_t numelems) +{ + CBT2(topo, insertNodes, node, numelems); +} + +static int +lwt_be_insertFaces(LWT_TOPOLOGY *topo, LWT_ISO_FACE *face, uint64_t numelems) +{ + CBT2(topo, insertFaces, face, numelems); +} + +static int +lwt_be_deleteFacesById(const LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t numelems) +{ + CBT2(topo, deleteFacesById, ids, numelems); +} + +static int +lwt_be_deleteNodesById(const LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t numelems) +{ + CBT2(topo, deleteNodesById, ids, numelems); +} + +LWT_ELEMID +lwt_be_getNextEdgeId(LWT_TOPOLOGY* topo) +{ + CBT0(topo, getNextEdgeId); +} + +LWT_ISO_EDGE * +lwt_be_getEdgeById(LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields) +{ + CBT3(topo, getEdgeById, ids, numelems, fields); +} + +static LWT_ISO_FACE * +lwt_be_getFaceById(LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields) +{ + CBT3(topo, getFaceById, ids, numelems, fields); +} + +static LWT_ISO_EDGE * +lwt_be_getEdgeByNode(LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields) +{ + CBT3(topo, getEdgeByNode, ids, numelems, fields); +} + +static LWT_ISO_EDGE * +lwt_be_getEdgeByFace(LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields, const GBOX *box) +{ + CBT4(topo, getEdgeByFace, ids, numelems, fields, box); +} + +static LWT_ISO_NODE * +lwt_be_getNodeByFace(LWT_TOPOLOGY *topo, const LWT_ELEMID *ids, uint64_t *numelems, int fields, const GBOX *box) +{ + CBT4(topo, getNodeByFace, ids, numelems, fields, box); +} + +LWT_ISO_EDGE * +lwt_be_getEdgeWithinDistance2D(LWT_TOPOLOGY *topo, + const LWPOINT *pt, + double dist, + uint64_t *numelems, + int fields, + int64_t limit) +{ + CBT5(topo, getEdgeWithinDistance2D, pt, dist, numelems, fields, limit); +} + +int +lwt_be_insertEdges(LWT_TOPOLOGY *topo, LWT_ISO_EDGE *edge, uint64_t numelems) +{ + CBT2(topo, insertEdges, edge, numelems); +} + +int +lwt_be_updateEdges(LWT_TOPOLOGY* topo, + const LWT_ISO_EDGE* sel_edge, int sel_fields, + const LWT_ISO_EDGE* upd_edge, int upd_fields, + const LWT_ISO_EDGE* exc_edge, int exc_fields +) +{ + CBT6(topo, updateEdges, sel_edge, sel_fields, + upd_edge, upd_fields, + exc_edge, exc_fields); +} + +static int +lwt_be_updateNodes(LWT_TOPOLOGY* topo, + const LWT_ISO_NODE* sel_node, int sel_fields, + const LWT_ISO_NODE* upd_node, int upd_fields, + const LWT_ISO_NODE* exc_node, int exc_fields +) +{ + CBT6(topo, updateNodes, sel_node, sel_fields, + upd_node, upd_fields, + exc_node, exc_fields); +} + +static uint64_t +lwt_be_updateFacesById(LWT_TOPOLOGY* topo, + const LWT_ISO_FACE* faces, uint64_t numfaces +) +{ + CBT2(topo, updateFacesById, faces, numfaces); +} + +static int +lwt_be_updateEdgesById(LWT_TOPOLOGY* topo, + const LWT_ISO_EDGE* edges, int numedges, int upd_fields +) +{ + CBT3(topo, updateEdgesById, edges, numedges, upd_fields); +} + +static int +lwt_be_updateNodesById(LWT_TOPOLOGY* topo, + const LWT_ISO_NODE* nodes, int numnodes, int upd_fields +) +{ + CBT3(topo, updateNodesById, nodes, numnodes, upd_fields); +} + +int +lwt_be_deleteEdges(LWT_TOPOLOGY* topo, + const LWT_ISO_EDGE* sel_edge, int sel_fields +) +{ + CBT2(topo, deleteEdges, sel_edge, sel_fields); +} + +int +lwt_be_updateTopoGeomEdgeSplit(LWT_TOPOLOGY* topo, LWT_ELEMID split_edge, LWT_ELEMID new_edge1, LWT_ELEMID new_edge2) +{ + CBT3(topo, updateTopoGeomEdgeSplit, split_edge, new_edge1, new_edge2); +} + +static int +lwt_be_updateTopoGeomFaceSplit(LWT_TOPOLOGY* topo, LWT_ELEMID split_face, + LWT_ELEMID new_face1, LWT_ELEMID new_face2) +{ + CBT3(topo, updateTopoGeomFaceSplit, split_face, new_face1, new_face2); +} + +static int +lwt_be_checkTopoGeomRemEdge(LWT_TOPOLOGY* topo, LWT_ELEMID edge_id, + LWT_ELEMID face_left, LWT_ELEMID face_right) +{ + CBT3(topo, checkTopoGeomRemEdge, edge_id, face_left, face_right); +} + +static int +lwt_be_checkTopoGeomRemIsoEdge(LWT_TOPOLOGY* topo, LWT_ELEMID edge_id) +{ + CBT1(topo, checkTopoGeomRemIsoEdge, edge_id); +} + +static int +lwt_be_checkTopoGeomRemNode(LWT_TOPOLOGY* topo, LWT_ELEMID node_id, + LWT_ELEMID eid1, LWT_ELEMID eid2) +{ + CBT3(topo, checkTopoGeomRemNode, node_id, eid1, eid2); +} + +static int +lwt_be_checkTopoGeomRemIsoNode(LWT_TOPOLOGY* topo, LWT_ELEMID node_id) +{ + CBT1(topo, checkTopoGeomRemIsoNode, node_id); +} + +static int +lwt_be_updateTopoGeomFaceHeal(LWT_TOPOLOGY* topo, + LWT_ELEMID face1, LWT_ELEMID face2, + LWT_ELEMID newface) +{ + CBT3(topo, updateTopoGeomFaceHeal, face1, face2, newface); +} + +static int +lwt_be_updateTopoGeomEdgeHeal(LWT_TOPOLOGY* topo, + LWT_ELEMID edge1, LWT_ELEMID edge2, + LWT_ELEMID newedge) +{ + CBT3(topo, updateTopoGeomEdgeHeal, edge1, edge2, newedge); +} + +static LWT_ELEMID * +lwt_be_getRingEdges(LWT_TOPOLOGY *topo, LWT_ELEMID edge, uint64_t *numedges, uint64_t limit) +{ + CBT3(topo, getRingEdges, edge, numedges, limit); +} + +int +lwt_be_ExistsCoincidentNode(LWT_TOPOLOGY* topo, LWPOINT* pt) +{ + uint64_t exists = 0; + lwt_be_getNodeWithinDistance2D(topo, pt, 0, &exists, 0, -1); + if (exists == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return 0; + } + return exists; +} + +int +lwt_be_ExistsEdgeIntersectingPoint(LWT_TOPOLOGY* topo, LWPOINT* pt) +{ + uint64_t exists = 0; + lwt_be_getEdgeWithinDistance2D(topo, pt, 0, &exists, 0, -1); + if (exists == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return 0; + } + return exists; +} + +static LWT_ISO_EDGE * +lwt_be_getClosestEdge(const LWT_TOPOLOGY *topo, const LWPOINT *pt, uint64_t *numelems, int fields) +{ + CBT3(topo, getClosestEdge, pt, numelems, fields); +} + +static GBOX * +lwt_be_computeFaceMBR(const LWT_TOPOLOGY *topo, LWT_ELEMID face) +{ + CBT1(topo, computeFaceMBR, face); +} + +/************************************************************************ + * + * Utility functions + * + ************************************************************************/ + +static LWGEOM * +_lwt_toposnap(LWGEOM *src, LWGEOM *tgt, double tol) +{ + LWGEOM *tmp = src; + LWGEOM *tmp2; + int changed; + int iterations = 0; + + int maxiterations = lwgeom_count_vertices(tgt); + + /* GEOS snapping can be unstable */ + /* See https://trac.osgeo.org/geos/ticket/760 */ + do { + tmp2 = lwgeom_snap(tmp, tgt, tol); + ++iterations; + changed = ( lwgeom_count_vertices(tmp2) != lwgeom_count_vertices(tmp) ); + LWDEBUGF(2, "After iteration %d, geometry changed ? %d (%d vs %d vertices)", iterations, changed, lwgeom_count_vertices(tmp2), lwgeom_count_vertices(tmp)); + if ( tmp != src ) lwgeom_free(tmp); + tmp = tmp2; + } while ( changed && iterations <= maxiterations ); + + LWDEBUGF(1, "It took %d/%d iterations to properly snap", + iterations, maxiterations); + + return tmp; +} + +static void +_lwt_release_faces(LWT_ISO_FACE *faces, int num_faces) +{ + int i; + for ( i=0; ibe_iface = iface; + topo->be_topo = be_topo; + topo->srid = lwt_be_topoGetSRID(topo); + topo->hasZ = lwt_be_topoHasZ(topo); + topo->precision = lwt_be_topoGetPrecision(topo); + + return topo; +} + +void +lwt_FreeTopology( LWT_TOPOLOGY* topo ) +{ + if ( ! lwt_be_freeTopology(topo) ) { + lwnotice("Could not release backend topology memory: %s", + lwt_be_lastErrorMessage(topo->be_iface)); + } + lwfree(topo); +} + +/** + * @param checkFace if non zero will check the given face + * for really containing the point or determine the + * face when given face is -1. Use 0 to simply use + * the given face value, no matter what (effectively + * allowing adding a non-isolated point when used + * with face=-1). + */ +static LWT_ELEMID +_lwt_AddIsoNode( LWT_TOPOLOGY* topo, LWT_ELEMID face, + LWPOINT* pt, int skipISOChecks, int checkFace ) +{ + LWT_ELEMID foundInFace = -1; + + if ( lwpoint_is_empty(pt) ) + { + lwerror("Cannot add empty point as isolated node"); + return -1; + } + + + if ( ! skipISOChecks ) + { + if ( lwt_be_ExistsCoincidentNode(topo, pt) ) /*x*/ + { + lwerror("SQL/MM Spatial exception - coincident node"); + return -1; + } + if ( lwt_be_ExistsEdgeIntersectingPoint(topo, pt) ) /*x*/ + { + lwerror("SQL/MM Spatial exception - edge crosses node."); + return -1; + } + } + + if ( checkFace && ( face == -1 || ! skipISOChecks ) ) + { + foundInFace = lwt_GetFaceContainingPoint(topo, pt); /*x*/ + if ( foundInFace == -1 ) { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if ( foundInFace == -1 ) foundInFace = 0; + } + + if ( face == -1 ) { + face = foundInFace; + } + else if ( ! skipISOChecks && foundInFace != face ) { +#if 0 + lwerror("SQL/MM Spatial exception - within face %d (not %d)", + foundInFace, face); +#else + lwerror("SQL/MM Spatial exception - not within face"); +#endif + return -1; + } + + LWT_ISO_NODE node; + node.node_id = -1; + node.containing_face = face; + node.geom = pt; + if ( ! lwt_be_insertNodes(topo, &node, 1) ) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + return node.node_id; +} + +LWT_ELEMID +lwt_AddIsoNode( LWT_TOPOLOGY* topo, LWT_ELEMID face, + LWPOINT* pt, int skipISOChecks ) +{ + return _lwt_AddIsoNode( topo, face, pt, skipISOChecks, 1 ); +} + +/* Check that an edge does not cross an existing node or edge + * + * @param myself the id of an edge to skip, if any + * (for ChangeEdgeGeom). Can use 0 for none. + * + * Return -1 on cross or error, 0 if everything is fine. + * Note that before returning -1, lwerror is invoked... + */ +static int +_lwt_CheckEdgeCrossing( LWT_TOPOLOGY* topo, + LWT_ELEMID start_node, LWT_ELEMID end_node, + const LWLINE *geom, LWT_ELEMID myself ) +{ + uint64_t i, num_nodes, num_edges; + LWT_ISO_EDGE *edges; + LWT_ISO_NODE *nodes; + const GBOX *edgebox; + GEOSGeometry *edgegg; + + initGEOS(lwnotice, lwgeom_geos_error); + + edgegg = LWGEOM2GEOS(lwline_as_lwgeom(geom), 0); + if (!edgegg) + { + lwerror("Could not convert edge geometry to GEOS: %s", lwgeom_geos_errmsg); + return -1; + } + edgebox = lwgeom_get_bbox( lwline_as_lwgeom(geom) ); + + /* loop over each node within the edge's gbox */ + nodes = lwt_be_getNodeWithinBox2D( topo, edgebox, &num_nodes, + LWT_COL_NODE_ALL, 0 ); + LWDEBUGF(1, "lwt_be_getNodeWithinBox2D returned %d nodes", num_nodes); + if (num_nodes == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + for ( i=0; inode_id == start_node ) continue; + if ( node->node_id == end_node ) continue; + /* check if the edge contains this node (not on boundary) */ + /* ST_RelateMatch(rec.relate, 'T********') */ + pt = getPoint2d_cp(node->geom->point, 0); + int contains = ptarray_contains_point_partial(geom->points, pt, LW_FALSE, NULL) == LW_BOUNDARY; + if ( contains ) + { + GEOSGeom_destroy(edgegg); + _lwt_release_nodes(nodes, num_nodes); + lwerror("SQL/MM Spatial exception - geometry crosses a node"); + return -1; + } + } + if ( nodes ) _lwt_release_nodes(nodes, num_nodes); + /* may be NULL if num_nodes == 0 */ + + /* loop over each edge within the edge's gbox */ + edges = lwt_be_getEdgeWithinBox2D( topo, edgebox, &num_edges, LWT_COL_EDGE_ALL, 0 ); + LWDEBUGF(1, "lwt_be_getEdgeWithinBox2D returned %d edges", num_edges); + if (num_edges == UINT64_MAX) + { + GEOSGeom_destroy(edgegg); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + for ( i=0; iedge_id; + GEOSGeometry *eegg; + char *relate; + int match; + + if ( edge_id == myself ) continue; + + if ( ! edge->geom ) { + _lwt_release_edges(edges, num_edges); + lwerror("Edge %d has NULL geometry!", edge_id); + return -1; + } + + eegg = LWGEOM2GEOS( lwline_as_lwgeom(edge->geom), 0 ); + if ( ! eegg ) { + GEOSGeom_destroy(edgegg); + _lwt_release_edges(edges, num_edges); + lwerror("Could not convert edge geometry to GEOS: %s", lwgeom_geos_errmsg); + return -1; + } + + LWDEBUGF(2, "Edge %d converted to GEOS", edge_id); + + /* check if the edge crosses our edge (not boundary-boundary) */ + + relate = GEOSRelateBoundaryNodeRule(eegg, edgegg, 2); + if ( ! relate ) { + GEOSGeom_destroy(eegg); + GEOSGeom_destroy(edgegg); + _lwt_release_edges(edges, num_edges); + lwerror("GEOSRelateBoundaryNodeRule error: %s", lwgeom_geos_errmsg); + return -1; + } + + LWDEBUGF(2, "Edge %d relate pattern is %s", edge_id, relate); + + match = GEOSRelatePatternMatch(relate, "F********"); + if ( match ) { + /* error or no interior intersection */ + GEOSGeom_destroy(eegg); + GEOSFree(relate); + if ( match == 2 ) { + _lwt_release_edges(edges, num_edges); + GEOSGeom_destroy(edgegg); + lwerror("GEOSRelatePatternMatch error: %s", lwgeom_geos_errmsg); + return -1; + } + else continue; /* no interior intersection */ + } + + match = GEOSRelatePatternMatch(relate, "1FFF*FFF2"); + if ( match ) { + _lwt_release_edges(edges, num_edges); + GEOSGeom_destroy(edgegg); + GEOSGeom_destroy(eegg); + GEOSFree(relate); + if ( match == 2 ) { + lwerror("GEOSRelatePatternMatch error: %s", lwgeom_geos_errmsg); + } else { + lwerror("SQL/MM Spatial exception - coincident edge %" LWTFMT_ELEMID, + edge_id); + } + return -1; + } + + match = GEOSRelatePatternMatch(relate, "1********"); + if ( match ) { + _lwt_release_edges(edges, num_edges); + GEOSGeom_destroy(edgegg); + GEOSGeom_destroy(eegg); + GEOSFree(relate); + if ( match == 2 ) { + lwerror("GEOSRelatePatternMatch error: %s", lwgeom_geos_errmsg); + } else { + lwerror("Spatial exception - geometry intersects edge %" + LWTFMT_ELEMID, edge_id); + } + return -1; + } + + match = GEOSRelatePatternMatch(relate, "T********"); + if ( match ) { + _lwt_release_edges(edges, num_edges); + GEOSGeom_destroy(edgegg); + GEOSGeom_destroy(eegg); + GEOSFree(relate); + if ( match == 2 ) { + lwerror("GEOSRelatePatternMatch error: %s", lwgeom_geos_errmsg); + } else { + lwerror("SQL/MM Spatial exception - geometry crosses edge %" + LWTFMT_ELEMID, edge_id); + } + return -1; + } + + LWDEBUGF(2, "Edge %d analisys completed, it does no harm", edge_id); + + GEOSFree(relate); + GEOSGeom_destroy(eegg); + } + if ( edges ) _lwt_release_edges(edges, num_edges); + /* would be NULL if num_edges was 0 */ + + GEOSGeom_destroy(edgegg); + + return 0; +} + + +LWT_ELEMID +lwt_AddIsoEdge( LWT_TOPOLOGY* topo, LWT_ELEMID startNode, + LWT_ELEMID endNode, const LWLINE* geom ) +{ + uint64_t num_nodes; + uint64_t i; + LWT_ISO_EDGE newedge; + LWT_ISO_NODE *endpoints; + LWT_ELEMID containing_face = -1; + LWT_ELEMID node_ids[2]; + LWT_ISO_NODE updated_nodes[2]; + int skipISOChecks = 0; + POINT2D p1, p2; + + /* NOT IN THE SPECS: + * A closed edge is never isolated (as it forms a face) + */ + if (startNode == endNode) + { + lwerror("Closed edges would not be isolated, try lwt_AddEdgeNewFaces"); + return -1; + } + + if ( ! skipISOChecks ) + { + /* Acurve must be simple */ + if ( ! lwgeom_is_simple(lwline_as_lwgeom(geom)) ) + { + lwerror("SQL/MM Spatial exception - curve not simple"); + return -1; + } + } + + /* + * Check for: + * existence of nodes + * nodes faces match + * Extract: + * nodes face id + * nodes geoms + */ + num_nodes = 2; + node_ids[0] = startNode; + node_ids[1] = endNode; + endpoints = lwt_be_getNodeById( topo, node_ids, &num_nodes, + LWT_COL_NODE_ALL ); + if (num_nodes == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + else if ( num_nodes < 2 ) + { + if ( num_nodes ) _lwt_release_nodes(endpoints, num_nodes); + lwerror("SQL/MM Spatial exception - non-existent node"); + return -1; + } + for ( i=0; icontaining_face == -1 ) + { + _lwt_release_nodes(endpoints, num_nodes); + lwerror("SQL/MM Spatial exception - not isolated node"); + return -1; + } + if ( containing_face == -1 ) containing_face = n->containing_face; + else if ( containing_face != n->containing_face ) + { + _lwt_release_nodes(endpoints, num_nodes); + lwerror("SQL/MM Spatial exception - nodes in different faces"); + return -1; + } + + if ( ! skipISOChecks ) + { + if ( n->node_id == startNode ) + { + /* l) Check that start point of acurve match start node geoms. */ + getPoint2d_p(geom->points, 0, &p1); + getPoint2d_p(n->geom->point, 0, &p2); + if ( ! P2D_SAME_STRICT(&p1, &p2) ) + { + _lwt_release_nodes(endpoints, num_nodes); + lwerror("SQL/MM Spatial exception - " + "start node not geometry start point."); + return -1; + } + } + else + { + /* m) Check that end point of acurve match end node geoms. */ + getPoint2d_p(geom->points, geom->points->npoints-1, &p1); + getPoint2d_p(n->geom->point, 0, &p2); + if ( ! P2D_SAME_STRICT(&p1, &p2) ) + { + _lwt_release_nodes(endpoints, num_nodes); + lwerror("SQL/MM Spatial exception - " + "end node not geometry end point."); + return -1; + } + } + } + } + + if ( num_nodes ) _lwt_release_nodes(endpoints, num_nodes); + + if ( ! skipISOChecks ) + { + if ( _lwt_CheckEdgeCrossing( topo, startNode, endNode, geom, 0 ) ) + { + /* would have called lwerror already, leaking :( */ + return -1; + } + } + + /* + * All checks passed, time to prepare the new edge + */ + + newedge.edge_id = lwt_be_getNextEdgeId( topo ); + if ( newedge.edge_id == -1 ) { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + /* TODO: this should likely be an exception instead ! */ + if ( containing_face == -1 ) containing_face = 0; + + newedge.start_node = startNode; + newedge.end_node = endNode; + newedge.face_left = newedge.face_right = containing_face; + newedge.next_left = -newedge.edge_id; + newedge.next_right = newedge.edge_id; + newedge.geom = (LWLINE *)geom; /* const cast.. */ + + int ret = lwt_be_insertEdges(topo, &newedge, 1); + if ( ret == -1 ) { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } else if ( ret == 0 ) { + lwerror("Insertion of split edge failed (no reason)"); + return -1; + } + + /* + * Update Node containing_face values + * + * the nodes anode and anothernode are no more isolated + * because now there is an edge connecting them + */ + updated_nodes[0].node_id = startNode; + updated_nodes[0].containing_face = -1; + updated_nodes[1].node_id = endNode; + updated_nodes[1].containing_face = -1; + ret = lwt_be_updateNodesById(topo, updated_nodes, 2, + LWT_COL_NODE_CONTAINING_FACE); + if ( ret == -1 ) { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + return newedge.edge_id; +} + +static LWCOLLECTION * +_lwt_EdgeSplit( LWT_TOPOLOGY* topo, LWT_ELEMID edge, LWPOINT* pt, int skipISOChecks, LWT_ISO_EDGE** oldedge ) +{ + LWGEOM *split; + LWCOLLECTION *split_col; + uint64_t i; + + /* Get edge */ + i = 1; + LWDEBUG(1, "calling lwt_be_getEdgeById"); + *oldedge = lwt_be_getEdgeById(topo, &edge, &i, LWT_COL_EDGE_ALL); + LWDEBUGF(1, "lwt_be_getEdgeById returned %p", *oldedge); + if ( ! *oldedge ) + { + LWDEBUGF(1, "lwt_be_getEdgeById returned NULL and set i=%d", i); + if (i == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return NULL; + } + else if ( i == 0 ) + { + lwerror("SQL/MM Spatial exception - non-existent edge"); + return NULL; + } + else + { + lwerror("Backend coding error: getEdgeById callback returned NULL " + "but numelements output parameter has value %d " + "(expected 0 or 1)", i); + return NULL; + } + } + + + /* + * - check if a coincident node already exists + */ + if ( ! skipISOChecks ) + { + LWDEBUG(1, "calling lwt_be_ExistsCoincidentNode"); + if ( lwt_be_ExistsCoincidentNode(topo, pt) ) /*x*/ + { + LWDEBUG(1, "lwt_be_ExistsCoincidentNode returned"); + _lwt_release_edges(*oldedge, 1); + lwerror("SQL/MM Spatial exception - coincident node"); + return NULL; + } + LWDEBUG(1, "lwt_be_ExistsCoincidentNode returned"); + } + + /* Split edge */ + split = lwgeom_split((LWGEOM*)(*oldedge)->geom, (LWGEOM*)pt); + if ( ! split ) + { + _lwt_release_edges(*oldedge, 1); + lwerror("could not split edge by point ?"); + return NULL; + } + split_col = lwgeom_as_lwcollection(split); + if ( ! split_col ) { + _lwt_release_edges(*oldedge, 1); + lwgeom_free(split); + lwerror("lwgeom_as_lwcollection returned NULL"); + return NULL; + } + if (split_col->ngeoms < 2) { + _lwt_release_edges(*oldedge, 1); + lwgeom_free(split); + lwerror("SQL/MM Spatial exception - point not on edge"); + return NULL; + } + +#if 0 + { + size_t sz; + char *wkt = lwgeom_to_wkt((LWGEOM*)split_col, WKT_EXTENDED, 2, &sz); + LWDEBUGF(1, "returning split col: %s", wkt); + lwfree(wkt); + } +#endif + return split_col; +} + +LWT_ELEMID +lwt_ModEdgeSplit( LWT_TOPOLOGY* topo, LWT_ELEMID edge, + LWPOINT* pt, int skipISOChecks ) +{ + LWT_ISO_NODE node; + LWT_ISO_EDGE* oldedge = NULL; + LWCOLLECTION *split_col; + const LWGEOM *oldedge_geom; + const LWGEOM *newedge_geom; + LWT_ISO_EDGE newedge1; + LWT_ISO_EDGE seledge, updedge, excedge; + int ret; + + split_col = _lwt_EdgeSplit( topo, edge, pt, skipISOChecks, &oldedge ); + if ( ! split_col ) return -1; /* should have raised an exception */ + oldedge_geom = split_col->geoms[0]; + newedge_geom = split_col->geoms[1]; + /* Make sure the SRID is set on the subgeom */ + ((LWGEOM*)oldedge_geom)->srid = split_col->srid; + ((LWGEOM*)newedge_geom)->srid = split_col->srid; + + /* Add new node, getting new id back */ + node.node_id = -1; + node.containing_face = -1; /* means not-isolated */ + node.geom = pt; + if ( ! lwt_be_insertNodes(topo, &node, 1) ) + { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if (node.node_id == -1) { + /* should have been set by backend */ + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("Backend coding error: " + "insertNodes callback did not return node_id"); + return -1; + } + + /* Insert the new edge */ + newedge1.edge_id = lwt_be_getNextEdgeId(topo); + if ( newedge1.edge_id == -1 ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + newedge1.start_node = node.node_id; + newedge1.end_node = oldedge->end_node; + newedge1.face_left = oldedge->face_left; + newedge1.face_right = oldedge->face_right; + newedge1.next_left = oldedge->next_left == -oldedge->edge_id ? + -newedge1.edge_id : oldedge->next_left; + newedge1.next_right = -oldedge->edge_id; + newedge1.geom = lwgeom_as_lwline(newedge_geom); + /* lwgeom_split of a line should only return lines ... */ + if ( ! newedge1.geom ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("first geometry in lwgeom_split output is not a line"); + return -1; + } + ret = lwt_be_insertEdges(topo, &newedge1, 1); + if ( ret == -1 ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } else if ( ret == 0 ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("Insertion of split edge failed (no reason)"); + return -1; + } + + /* Update the old edge */ + updedge.geom = lwgeom_as_lwline(oldedge_geom); + /* lwgeom_split of a line should only return lines ... */ + if ( ! updedge.geom ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("second geometry in lwgeom_split output is not a line"); + return -1; + } + updedge.next_left = newedge1.edge_id; + updedge.end_node = node.node_id; + ret = lwt_be_updateEdges(topo, + oldedge, LWT_COL_EDGE_EDGE_ID, + &updedge, LWT_COL_EDGE_GEOM|LWT_COL_EDGE_NEXT_LEFT|LWT_COL_EDGE_END_NODE, + NULL, 0); + if ( ret == -1 ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } else if ( ret == 0 ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("Edge being split (%d) disappeared during operations?", oldedge->edge_id); + return -1; + } else if ( ret > 1 ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("More than a single edge found with id %d !", oldedge->edge_id); + return -1; + } + + /* Update all next edge references to match new layout (ST_ModEdgeSplit) */ + + updedge.next_right = -newedge1.edge_id; + excedge.edge_id = newedge1.edge_id; + seledge.next_right = -oldedge->edge_id; + seledge.start_node = oldedge->end_node; + ret = lwt_be_updateEdges(topo, + &seledge, LWT_COL_EDGE_NEXT_RIGHT|LWT_COL_EDGE_START_NODE, + &updedge, LWT_COL_EDGE_NEXT_RIGHT, + &excedge, LWT_COL_EDGE_EDGE_ID); + if ( ret == -1 ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + updedge.next_left = -newedge1.edge_id; + excedge.edge_id = newedge1.edge_id; + seledge.next_left = -oldedge->edge_id; + seledge.end_node = oldedge->end_node; + ret = lwt_be_updateEdges(topo, + &seledge, LWT_COL_EDGE_NEXT_LEFT|LWT_COL_EDGE_END_NODE, + &updedge, LWT_COL_EDGE_NEXT_LEFT, + &excedge, LWT_COL_EDGE_EDGE_ID); + if ( ret == -1 ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + /* Update TopoGeometries composition */ + ret = lwt_be_updateTopoGeomEdgeSplit(topo, oldedge->edge_id, newedge1.edge_id, -1); + if ( ! ret ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + + /* return new node id */ + return node.node_id; +} + +LWT_ELEMID +lwt_NewEdgesSplit( LWT_TOPOLOGY* topo, LWT_ELEMID edge, + LWPOINT* pt, int skipISOChecks ) +{ + LWT_ISO_NODE node; + LWT_ISO_EDGE* oldedge = NULL; + LWCOLLECTION *split_col; + const LWGEOM *oldedge_geom; + const LWGEOM *newedge_geom; + LWT_ISO_EDGE newedges[2]; + LWT_ISO_EDGE seledge, updedge; + int ret; + + split_col = _lwt_EdgeSplit( topo, edge, pt, skipISOChecks, &oldedge ); + if ( ! split_col ) return -1; /* should have raised an exception */ + oldedge_geom = split_col->geoms[0]; + newedge_geom = split_col->geoms[1]; + /* Make sure the SRID is set on the subgeom */ + ((LWGEOM*)oldedge_geom)->srid = split_col->srid; + ((LWGEOM*)newedge_geom)->srid = split_col->srid; + + /* Add new node, getting new id back */ + node.node_id = -1; + node.containing_face = -1; /* means not-isolated */ + node.geom = pt; + if ( ! lwt_be_insertNodes(topo, &node, 1) ) + { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if (node.node_id == -1) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + /* should have been set by backend */ + lwerror("Backend coding error: " + "insertNodes callback did not return node_id"); + return -1; + } + + /* Delete the old edge */ + seledge.edge_id = edge; + ret = lwt_be_deleteEdges(topo, &seledge, LWT_COL_EDGE_EDGE_ID); + if ( ret == -1 ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + /* Get new edges identifiers */ + newedges[0].edge_id = lwt_be_getNextEdgeId(topo); + if ( newedges[0].edge_id == -1 ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + newedges[1].edge_id = lwt_be_getNextEdgeId(topo); + if ( newedges[1].edge_id == -1 ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + /* Define the first new edge (to new node) */ + newedges[0].start_node = oldedge->start_node; + newedges[0].end_node = node.node_id; + newedges[0].face_left = oldedge->face_left; + newedges[0].face_right = oldedge->face_right; + newedges[0].next_left = newedges[1].edge_id; + if ( oldedge->next_right == edge ) + newedges[0].next_right = newedges[0].edge_id; + else if ( oldedge->next_right == -edge ) + newedges[0].next_right = -newedges[1].edge_id; + else + newedges[0].next_right = oldedge->next_right; + newedges[0].geom = lwgeom_as_lwline(oldedge_geom); + /* lwgeom_split of a line should only return lines ... */ + if ( ! newedges[0].geom ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("first geometry in lwgeom_split output is not a line"); + return -1; + } + + /* Define the second new edge (from new node) */ + newedges[1].start_node = node.node_id; + newedges[1].end_node = oldedge->end_node; + newedges[1].face_left = oldedge->face_left; + newedges[1].face_right = oldedge->face_right; + newedges[1].next_right = -newedges[0].edge_id; + if ( oldedge->next_left == -edge ) + newedges[1].next_left = -newedges[1].edge_id; + else if ( oldedge->next_left == edge ) + newedges[1].next_left = newedges[0].edge_id; + else + newedges[1].next_left = oldedge->next_left; + newedges[1].geom = lwgeom_as_lwline(newedge_geom); + /* lwgeom_split of a line should only return lines ... */ + if ( ! newedges[1].geom ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("second geometry in lwgeom_split output is not a line"); + return -1; + } + + /* Insert both new edges */ + ret = lwt_be_insertEdges(topo, newedges, 2); + if ( ret == -1 ) { + _lwt_release_edges(oldedge, 1); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } else if ( ret == 0 ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("Insertion of split edge failed (no reason)"); + return -1; + } + + /* Update all next edge references pointing to old edge id */ + + updedge.next_right = newedges[1].edge_id; + seledge.next_right = edge; + seledge.start_node = oldedge->start_node; + ret = lwt_be_updateEdges(topo, + &seledge, LWT_COL_EDGE_NEXT_RIGHT|LWT_COL_EDGE_START_NODE, + &updedge, LWT_COL_EDGE_NEXT_RIGHT, + NULL, 0); + if ( ret == -1 ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + updedge.next_right = -newedges[0].edge_id; + seledge.next_right = -edge; + seledge.start_node = oldedge->end_node; + ret = lwt_be_updateEdges(topo, + &seledge, LWT_COL_EDGE_NEXT_RIGHT|LWT_COL_EDGE_START_NODE, + &updedge, LWT_COL_EDGE_NEXT_RIGHT, + NULL, 0); + if ( ret == -1 ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + updedge.next_left = newedges[0].edge_id; + seledge.next_left = edge; + seledge.end_node = oldedge->start_node; + ret = lwt_be_updateEdges(topo, + &seledge, LWT_COL_EDGE_NEXT_LEFT|LWT_COL_EDGE_END_NODE, + &updedge, LWT_COL_EDGE_NEXT_LEFT, + NULL, 0); + if ( ret == -1 ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + updedge.next_left = -newedges[1].edge_id; + seledge.next_left = -edge; + seledge.end_node = oldedge->end_node; + ret = lwt_be_updateEdges(topo, + &seledge, LWT_COL_EDGE_NEXT_LEFT|LWT_COL_EDGE_END_NODE, + &updedge, LWT_COL_EDGE_NEXT_LEFT, + NULL, 0); + if ( ret == -1 ) { + _lwt_release_edges(oldedge, 1); + lwcollection_release(split_col); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + /* Update TopoGeometries composition */ + ret = lwt_be_updateTopoGeomEdgeSplit(topo, oldedge->edge_id, newedges[0].edge_id, newedges[1].edge_id); + if ( ! ret ) { + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + _lwt_release_edges(oldedge, 1); + lwcollection_free(split_col); + + /* return new node id */ + return node.node_id; +} + +/* Data structure used by AddEdgeX functions */ +typedef struct edgeend_t { + /* Signed identifier of next clockwise edge (+outgoing,-incoming) */ + LWT_ELEMID nextCW; + /* Identifier of face between myaz and next CW edge */ + LWT_ELEMID cwFace; + /* Signed identifier of next counterclockwise edge (+outgoing,-incoming) */ + LWT_ELEMID nextCCW; + /* Identifier of face between myaz and next CCW edge */ + LWT_ELEMID ccwFace; + int was_isolated; + double myaz; /* azimuth of edgeend geometry */ +} edgeend; + +/* + * Get first distinct vertex from endpoint + * @param pa the pointarray to seek points in + * @param ref the point we want to search a distinct one + * @param from vertex index to start from (will really start from "from"+dir) + * @param dir 1 to go forward + * -1 to go backward + * @return 0 if edge is collapsed (no distinct points) + */ +static int +_lwt_FirstDistinctVertex2D(const POINTARRAY* pa, POINT2D *ref, int from, int dir, POINT2D *op) +{ + int i, toofar, inc; + POINT2D fp; + + if ( dir > 0 ) + { + toofar = pa->npoints; + inc = 1; + } + else + { + toofar = -1; + inc = -1; + } + + LWDEBUGF(1, "first point is index %d", from); + fp = *ref; /* getPoint2d_p(pa, from, &fp); */ + for ( i = from+inc; i != toofar; i += inc ) + { + LWDEBUGF(1, "testing point %d", i); + getPoint2d_p(pa, i, op); /* pick next point */ + if ( P2D_SAME_STRICT(op,&fp) ) continue; /* equal to startpoint */ + /* this is a good one, neither same of start nor of end point */ + return 1; /* found */ + } + + /* no distinct vertices found */ + return 0; +} + + +/* + * Return non-zero on failure (lwerror is invoked in that case) + * Possible failures: + * -1 no two distinct vertices exist + * -2 azimuth computation failed for first edge end + */ +static int +_lwt_InitEdgeEndByLine(edgeend *fee, edgeend *lee, LWLINE *edge, + POINT2D *fp, POINT2D *lp) +{ + POINTARRAY *pa = edge->points; + POINT2D pt; + + fee->nextCW = fee->nextCCW = + lee->nextCW = lee->nextCCW = 0; + fee->cwFace = fee->ccwFace = + lee->cwFace = lee->ccwFace = -1; + + /* Compute azimuth of first edge end */ + LWDEBUG(1, "computing azimuth of first edge end"); + if ( ! _lwt_FirstDistinctVertex2D(pa, fp, 0, 1, &pt) ) + { + lwerror("Invalid edge (no two distinct vertices exist)"); + return -1; + } + if ( ! azimuth_pt_pt(fp, &pt, &(fee->myaz)) ) { + lwerror("error computing azimuth of first edgeend [%.15g %.15g,%.15g %.15g]", + fp->x, fp->y, pt.x, pt.y); + return -2; + } + LWDEBUGF(1, "azimuth of first edge end [%.15g %.15g,%.15g %.15g] is %g", + fp->x, fp->y, pt.x, pt.y, fee->myaz); + + /* Compute azimuth of second edge end */ + LWDEBUG(1, "computing azimuth of second edge end"); + if ( ! _lwt_FirstDistinctVertex2D(pa, lp, pa->npoints-1, -1, &pt) ) + { + lwerror("Invalid edge (no two distinct vertices exist)"); + return -1; + } + if ( ! azimuth_pt_pt(lp, &pt, &(lee->myaz)) ) { + lwerror("error computing azimuth of last edgeend [%.15g %.15g,%.15g %.15g]", + lp->x, lp->y, pt.x, pt.y); + return -2; + } + LWDEBUGF(1, "azimuth of last edge end [%.15g %.15g,%.15g %.15g] is %g", + lp->x, lp->y, pt.x, pt.y, lee->myaz); + + return 0; +} + +/* + * Find the first edges encountered going clockwise and counterclockwise + * around a node, starting from the given azimuth, and take + * note of the face on the both sides. + * + * @param topo the topology to act upon + * @param node the identifier of the node to analyze + * @param data input (myaz) / output (nextCW, nextCCW) parameter + * @param other edgeend, if also incident to given node (closed edge). + * @param myedge_id identifier of the edge id that data->myaz belongs to + * @return number of incident edges found + * + */ +static int +_lwt_FindAdjacentEdges( LWT_TOPOLOGY* topo, LWT_ELEMID node, edgeend *data, + edgeend *other, int myedge_id ) +{ + LWT_ISO_EDGE *edges; + uint64_t numedges = 1; + uint64_t i; + double minaz, maxaz; + double az, azdif; + + data->nextCW = data->nextCCW = 0; + data->cwFace = data->ccwFace = -1; + + if ( other ) { + azdif = other->myaz - data->myaz; + if ( azdif < 0 ) azdif += 2 * M_PI; + minaz = maxaz = azdif; + /* TODO: set nextCW/nextCCW/cwFace/ccwFace to other->something ? */ + LWDEBUGF(1, "Other edge end has cwFace=%d and ccwFace=%d", + other->cwFace, other->ccwFace); + } else { + minaz = maxaz = -1; + } + + LWDEBUGF(1, "Looking for edges incident to node %" LWTFMT_ELEMID + " and adjacent to azimuth %g", node, data->myaz); + + /* Get incident edges */ + edges = lwt_be_getEdgeByNode( topo, &node, &numedges, LWT_COL_EDGE_ALL ); + if (numedges == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return 0; + } + + LWDEBUGF(1, "getEdgeByNode returned %d edges, minaz=%g, maxaz=%g", + numedges, minaz, maxaz); + + /* For each incident edge-end (1 or 2): */ + for ( i = 0; i < numedges; ++i ) + { + LWT_ISO_EDGE *edge; + LWGEOM *g; + LWGEOM *cleangeom; + POINT2D p1, p2; + POINTARRAY *pa; + + edge = &(edges[i]); + + if ( edge->edge_id == myedge_id ) continue; + + g = lwline_as_lwgeom(edge->geom); + /* NOTE: remove_repeated_points call could be replaced by + * some other mean to pick two distinct points for endpoints */ + cleangeom = lwgeom_remove_repeated_points( g, 0 ); + pa = lwgeom_as_lwline(cleangeom)->points; + + if ( pa->npoints < 2 ) {{ + LWT_ELEMID id = edge->edge_id; + _lwt_release_edges(edges, numedges); + lwgeom_free(cleangeom); + lwerror("corrupted topology: edge %" LWTFMT_ELEMID + " does not have two distinct points", id); + return -1; + }} + + if ( edge->start_node == node ) { + getPoint2d_p(pa, 0, &p1); + if ( ! _lwt_FirstDistinctVertex2D(pa, &p1, 0, 1, &p2) ) + { + lwerror("Edge %d has no distinct vertices: [%.15g %.15g,%.15g %.15g]: ", + edge->edge_id, p1.x, p1.y, p2.x, p2.y); + return -1; + } + LWDEBUGF(1, "edge %" LWTFMT_ELEMID + " starts on node %" LWTFMT_ELEMID + ", edgeend is [%.15g %.15g,%.15g %.15g]", + edge->edge_id, node, p1.x, p1.y, p2.x, p2.y); + if ( ! azimuth_pt_pt(&p1, &p2, &az) ) {{ + LWT_ELEMID id = edge->edge_id; + _lwt_release_edges(edges, numedges); + lwgeom_free(cleangeom); + lwerror("error computing azimuth of edge %d first edgeend [%.15g %.15g,%.15g %.15g]", + id, p1.x, p1.y, p2.x, p2.y); + return -1; + }} + azdif = az - data->myaz; + LWDEBUGF(1, "azimuth of edge %" LWTFMT_ELEMID + ": %.15g (diff: %.15g)", edge->edge_id, az, azdif); + + if ( azdif < 0 ) azdif += 2 * M_PI; + if ( minaz == -1 ) { + minaz = maxaz = azdif; + data->nextCW = data->nextCCW = edge->edge_id; /* outgoing */ + data->cwFace = edge->face_left; + data->ccwFace = edge->face_right; + LWDEBUGF(1, "new nextCW and nextCCW edge is %" LWTFMT_ELEMID + ", outgoing, " + "with face_left %" LWTFMT_ELEMID " and face_right %" LWTFMT_ELEMID + " (face_right is new ccwFace, face_left is new cwFace)", + edge->edge_id, edge->face_left, + edge->face_right); + } else { + if ( azdif < minaz ) { + data->nextCW = edge->edge_id; /* outgoing */ + data->cwFace = edge->face_left; + LWDEBUGF(1, "new nextCW edge is %" LWTFMT_ELEMID + ", outgoing, " + "with face_left %" LWTFMT_ELEMID " and face_right %" LWTFMT_ELEMID + " (previous had minaz=%g, face_left is new cwFace)", + edge->edge_id, edge->face_left, + edge->face_right, minaz); + minaz = azdif; + } + else if ( azdif > maxaz ) { + data->nextCCW = edge->edge_id; /* outgoing */ + data->ccwFace = edge->face_right; + LWDEBUGF(1, "new nextCCW edge is %" LWTFMT_ELEMID + ", outgoing, " + "with face_left %" LWTFMT_ELEMID " and face_right %" LWTFMT_ELEMID + " (previous had maxaz=%g, face_right is new ccwFace)", + edge->edge_id, edge->face_left, + edge->face_right, maxaz); + maxaz = azdif; + } + } + } + + if ( edge->end_node == node ) { + getPoint2d_p(pa, pa->npoints-1, &p1); + if ( ! _lwt_FirstDistinctVertex2D(pa, &p1, pa->npoints-1, -1, &p2) ) + { + lwerror("Edge %d has no distinct vertices: [%.15g %.15g,%.15g %.15g]: ", + edge->edge_id, p1.x, p1.y, p2.x, p2.y); + return -1; + } + LWDEBUGF(1, "edge %" LWTFMT_ELEMID " ends on node %" LWTFMT_ELEMID + ", edgeend is [%.15g %.15g,%.15g %.15g]", + edge->edge_id, node, p1.x, p1.y, p2.x, p2.y); + if ( ! azimuth_pt_pt(&p1, &p2, &az) ) {{ + LWT_ELEMID id = edge->edge_id; + _lwt_release_edges(edges, numedges); + lwgeom_free(cleangeom); + lwerror("error computing azimuth of edge %d last edgeend [%.15g %.15g,%.15g %.15g]", + id, p1.x, p1.y, p2.x, p2.y); + return -1; + }} + azdif = az - data->myaz; + LWDEBUGF(1, "azimuth of edge %" LWTFMT_ELEMID + ": %.15g (diff: %.15g)", edge->edge_id, az, azdif); + if ( azdif < 0 ) azdif += 2 * M_PI; + if ( minaz == -1 ) { + minaz = maxaz = azdif; + data->nextCW = data->nextCCW = -edge->edge_id; /* incoming */ + data->cwFace = edge->face_right; + data->ccwFace = edge->face_left; + LWDEBUGF(1, "new nextCW and nextCCW edge is %" LWTFMT_ELEMID + ", incoming, " + "with face_left %" LWTFMT_ELEMID " and face_right %" LWTFMT_ELEMID + " (face_right is new cwFace, face_left is new ccwFace)", + edge->edge_id, edge->face_left, + edge->face_right); + } else { + if ( azdif < minaz ) { + data->nextCW = -edge->edge_id; /* incoming */ + data->cwFace = edge->face_right; + LWDEBUGF(1, "new nextCW edge is %" LWTFMT_ELEMID + ", incoming, " + "with face_left %" LWTFMT_ELEMID " and face_right %" LWTFMT_ELEMID + " (previous had minaz=%g, face_right is new cwFace)", + edge->edge_id, edge->face_left, + edge->face_right, minaz); + minaz = azdif; + } + else if ( azdif > maxaz ) { + data->nextCCW = -edge->edge_id; /* incoming */ + data->ccwFace = edge->face_left; + LWDEBUGF(1, "new nextCCW edge is %" LWTFMT_ELEMID + ", outgoing, from start point, " + "with face_left %" LWTFMT_ELEMID " and face_right %" LWTFMT_ELEMID + " (previous had maxaz=%g, face_left is new ccwFace)", + edge->edge_id, edge->face_left, + edge->face_right, maxaz); + maxaz = azdif; + } + } + } + + lwgeom_free(cleangeom); + } + if ( numedges ) _lwt_release_edges(edges, numedges); + + LWDEBUGF(1, "edges adjacent to azimuth %g" + " (incident to node %" LWTFMT_ELEMID ")" + ": CW:%" LWTFMT_ELEMID "(%g) CCW:%" LWTFMT_ELEMID "(%g)", + data->myaz, node, data->nextCW, minaz, + data->nextCCW, maxaz); + + if ( myedge_id < 1 && numedges && data->cwFace != data->ccwFace ) + { + if ( data->cwFace != -1 && data->ccwFace != -1 ) { + lwerror("Corrupted topology: adjacent edges %" LWTFMT_ELEMID " and %" LWTFMT_ELEMID + " bind different face (%" LWTFMT_ELEMID " and %" LWTFMT_ELEMID ")", + data->nextCW, data->nextCCW, + data->cwFace, data->ccwFace); + return -1; + } + } + + /* Return number of incident edges found */ + return numedges; +} + +/* + * Get a point internal to the line and write it into the "ip" + * parameter + * + * return 0 on failure (line is empty or collapsed), 1 otherwise + */ +static int +_lwt_GetInteriorEdgePoint(const LWLINE* edge, POINT2D* ip) +{ + uint32_t i; + POINT2D fp, lp, tp; + POINTARRAY *pa = edge->points; + + if ( pa->npoints < 2 ) return 0; /* empty or structurally collapsed */ + + getPoint2d_p(pa, 0, &fp); /* save first point */ + getPoint2d_p(pa, pa->npoints-1, &lp); /* save last point */ + for (i=1; inpoints-1; ++i) + { + getPoint2d_p(pa, i, &tp); /* pick next point */ + if ( P2D_SAME_STRICT(&tp, &fp) ) continue; /* equal to startpoint */ + if ( P2D_SAME_STRICT(&tp, &lp) ) continue; /* equal to endpoint */ + /* this is a good one, neither same of start nor of end point */ + *ip = tp; + return 1; /* found */ + } + + /* no distinct vertex found */ + + /* interpolate if start point != end point */ + + if ( P2D_SAME_STRICT(&fp, &lp) ) return 0; /* no distinct points in edge */ + + ip->x = fp.x + ( (lp.x - fp.x) * 0.5 ); + ip->y = fp.y + ( (lp.y - fp.y) * 0.5 ); + + return 1; +} + +static LWPOLY * +_lwt_MakeRingShell(LWT_TOPOLOGY *topo, LWT_ELEMID *signed_edge_ids, uint64_t num_signed_edge_ids) +{ + LWT_ELEMID *edge_ids; + uint64_t numedges, i, j; + LWT_ISO_EDGE *ring_edges; + + /* Construct a polygon using edges of the ring */ + numedges = 0; + edge_ids = lwalloc(sizeof(LWT_ELEMID)*num_signed_edge_ids); + for (i=0; ibe_iface)); + return NULL; + } + else if ( i != numedges ) + { + lwfree( signed_edge_ids ); + _lwt_release_edges(ring_edges, i); + lwerror("Unexpected error: %d edges found when expecting %d", i, numedges); + return NULL; + } + + /* Should now build a polygon with those edges, in the order + * given by GetRingEdges. + */ + POINTARRAY *pa = NULL; + for ( i=0; igeom->points); + if ( eid < 0 ) ptarray_reverse_in_place(pa); + } + else + { + if ( eid < 0 ) + { + epa = ptarray_clone_deep(edge->geom->points); + ptarray_reverse_in_place(epa); + ptarray_append_ptarray(pa, epa, 0); + ptarray_free(epa); + } + else + { + /* avoid a clone here */ + ptarray_append_ptarray(pa, edge->geom->points, 0); + } + } + } + _lwt_release_edges(ring_edges, numedges); + POINTARRAY **points = lwalloc(sizeof(POINTARRAY*)); + points[0] = pa; + + /* NOTE: the ring may very well have collapsed components, + * which would make it topologically invalid + */ + LWPOLY* shell = lwpoly_construct(0, 0, 1, points); + return shell; +} + +/* + * Add a split face by walking on the edge side. + * + * @param topo the topology to act upon + * @param sedge edge id and walking side and direction + * (forward,left:positive backward,right:negative) + * @param face the face in which the edge identifier is known to be + * @param mbr_only do not create a new face but update MBR of the current + * + * @return: + * -1: if mbr_only was requested + * 0: if the edge does not form a ring + * -1: if it is impossible to create a face on the requested side + * ( new face on the side is the universe ) + * -2: error + * >0 : id of newly added face + */ +static LWT_ELEMID +_lwt_AddFaceSplit( LWT_TOPOLOGY* topo, + LWT_ELEMID sedge, LWT_ELEMID face, + int mbr_only ) +{ + uint64_t numfaceedges, i, j; + int newface_outside; + uint64_t num_signed_edge_ids; + LWT_ELEMID *signed_edge_ids; + LWT_ISO_EDGE *edges; + LWT_ISO_EDGE *forward_edges = NULL; + int forward_edges_count = 0; + LWT_ISO_EDGE *backward_edges = NULL; + int backward_edges_count = 0; + + signed_edge_ids = lwt_be_getRingEdges(topo, sedge, &num_signed_edge_ids, 0); + if (!signed_edge_ids) + { + lwerror("Backend error (no ring edges for edge %" LWTFMT_ELEMID "): %s", + sedge, + lwt_be_lastErrorMessage(topo->be_iface)); + return -2; + } + LWDEBUGF(1, "getRingEdges returned %d edges", num_signed_edge_ids); + + /* You can't get to the other side of an edge forming a ring */ + for (i=0; ibe_iface)); + return -2; + } + const POINTARRAY *pa = shell->rings[0]; + if ( ! ptarray_is_closed_2d(pa) ) + { + lwpoly_free(shell); + lwfree( signed_edge_ids ); + lwerror("Corrupted topology: ring of edge %" LWTFMT_ELEMID + " is geometrically not-closed", sedge); + return -2; + } + + int isccw = ptarray_isccw(pa); + LWDEBUGF(1, "Ring of edge %" LWTFMT_ELEMID " is %sclockwise", + sedge, isccw ? "counter" : ""); + const GBOX* shellbox = lwgeom_get_bbox(lwpoly_as_lwgeom(shell)); + + if ( face == 0 ) + { + /* Edge split the universe face */ + if ( ! isccw ) + { + lwpoly_free(shell); + lwfree( signed_edge_ids ); + /* Face on the left side of this ring is the universe face. + * Next call (for the other side) should create the split face + */ + LWDEBUG(1, "The left face of this clockwise ring is the universe, " + "won't create a new face there"); + return -1; + } + } + + if ( mbr_only && face != 0 ) + { + if ( isccw ) + {{ + LWT_ISO_FACE updface; + updface.face_id = face; + updface.mbr = (GBOX *)shellbox; /* const cast, we won't free it, later */ + int ret = lwt_be_updateFacesById( topo, &updface, 1 ); + if ( ret == -1 ) + { + lwfree( signed_edge_ids ); + lwpoly_free(shell); /* NOTE: owns shellbox above */ + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -2; + } + if ( ret != 1 ) + { + lwfree( signed_edge_ids ); + lwpoly_free(shell); /* NOTE: owns shellbox above */ + lwerror("Unexpected error: %d faces found when expecting 1", ret); + return -2; + } + }} + lwfree( signed_edge_ids ); + lwpoly_free(shell); /* NOTE: owns shellbox above */ + return -1; /* mbr only was requested */ + } + + LWT_ISO_FACE *oldface = NULL; + LWT_ISO_FACE newface; + newface.face_id = -1; + if ( face != 0 && ! isccw) + {{ + /* Face created an hole in an outer face */ + uint64_t nfaces = 1; + oldface = lwt_be_getFaceById(topo, &face, &nfaces, LWT_COL_FACE_ALL); + if (nfaces == UINT64_MAX) + { + lwfree( signed_edge_ids ); + lwpoly_free(shell); /* NOTE: owns shellbox */ + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -2; + } + if ( nfaces != 1 ) + { + lwfree( signed_edge_ids ); + lwpoly_free(shell); /* NOTE: owns shellbox */ + lwerror("Unexpected error: %d faces found when expecting 1", nfaces); + return -2; + } + newface.mbr = oldface->mbr; + }} + else + { + newface.mbr = (GBOX *)shellbox; /* const cast, we won't free it, later */ + } + + /* Insert the new face */ + int ret = lwt_be_insertFaces( topo, &newface, 1 ); + if ( ret == -1 ) + { + lwfree( signed_edge_ids ); + lwpoly_free(shell); /* NOTE: owns shellbox */ + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -2; + } + if ( ret != 1 ) + { + lwfree( signed_edge_ids ); + lwpoly_free(shell); /* NOTE: owns shellbox */ + lwerror("Unexpected error: %d faces inserted when expecting 1", ret); + return -2; + } + if ( oldface ) { + newface.mbr = NULL; /* it is a reference to oldface mbr... */ + _lwt_release_faces(oldface, 1); + } + + /* Update side location of new face edges */ + + /* We want the new face to be on the left, if possible */ + if ( face != 0 && ! isccw ) { /* ring is clockwise in a real face */ + /* face shrinked, must update all non-contained edges and nodes */ + LWDEBUG(1, "New face is on the outside of the ring, updating rings in former shell"); + newface_outside = 1; + /* newface is outside */ + } else { + LWDEBUG(1, "New face is on the inside of the ring, updating forward edges in new ring"); + newface_outside = 0; + /* newface is inside */ + } + + /* Update edges bounding the old face */ + /* (1) fetch all edges where left_face or right_face is = oldface */ + int fields = LWT_COL_EDGE_EDGE_ID | + LWT_COL_EDGE_FACE_LEFT | + LWT_COL_EDGE_FACE_RIGHT | + LWT_COL_EDGE_GEOM + ; + numfaceedges = 1; + edges = lwt_be_getEdgeByFace( topo, &face, &numfaceedges, fields, newface.mbr ); + if (numfaceedges == UINT64_MAX) + { + lwfree(signed_edge_ids); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -2; + } + LWDEBUGF(1, "_lwt_AddFaceSplit: lwt_be_getEdgeByFace(%d) returned %d edges", face, numfaceedges); + + if ( numfaceedges ) + { + forward_edges = lwalloc(sizeof(LWT_ISO_EDGE)*numfaceedges); + forward_edges_count = 0; + backward_edges = lwalloc(sizeof(LWT_ISO_EDGE)*numfaceedges); + backward_edges_count = 0; + + /* (2) loop over the results and: */ + for ( i=0; iedge_id ) + { + /* IDEA: remove entry from signed_edge_ids, to speed next loop ? */ + LWDEBUGF(1, "Edge %d is a known forward edge of the new ring", e->edge_id); + forward_edges[forward_edges_count].edge_id = e->edge_id; + forward_edges[forward_edges_count++].face_left = newface.face_id; + found++; + if ( found == 2 ) break; /* both edge sides are found on the ring */ + } + else if ( -seid == e->edge_id ) + { + /* IDEA: remove entry from signed_edge_ids, to speed next loop ? */ + LWDEBUGF(1, "Edge %d is a known backward edge of the new ring", e->edge_id); + backward_edges[backward_edges_count].edge_id = e->edge_id; + backward_edges[backward_edges_count++].face_right = newface.face_id; + found++; + if ( found == 2 ) break; /* both edge sides are found on the ring */ + } + } + if ( found ) continue; + LWDEBUGF(1, "Edge %d is not a known edge of the new ring", e->edge_id); + + /* Check if the edge is now binding a different face */ + + if ( ! getPoint2d_p(e->geom->points, 0, &ep) ) + { + lwfree(signed_edge_ids); + lwpoly_free(shell); + lwfree(forward_edges); /* contents owned by edges */ + lwfree(backward_edges); /* contents owned by edges */ + _lwt_release_edges(edges, numfaceedges); + lwerror("Edge %d is empty", e->edge_id); + return -2; + } + + /* IDEA: check that bounding box shortcut is taken, or use + * shellbox to do it here */ + contains = ptarray_contains_point(pa, &ep); + + LWDEBUGF(1, "Edge %d first point %s new ring", + e->edge_id, (contains == LW_INSIDE ? "inside" : + contains == LW_OUTSIDE ? "outside" : "on boundary of")); + + /* (2.2) skip edges (NOT, if newface_outside) contained in ring */ + if ( newface_outside ) + { + if ( contains != LW_OUTSIDE ) + { + LWDEBUGF(1, "Edge %d not outside of the new ring, not updating it", + e->edge_id); + continue; + } + } + else + { + if ( contains != LW_INSIDE ) + { + LWDEBUGF(1, "Edge %d not inside the new ring, not updating it", + e->edge_id); + continue; + } + } + + /* (2.3) push to forward_edges if left_face = oface */ + if ( e->face_left == face ) + { + LWDEBUGF(1, "Edge %d has new face on the left side", e->edge_id); + forward_edges[forward_edges_count].edge_id = e->edge_id; + forward_edges[forward_edges_count++].face_left = newface.face_id; + } + + /* (2.4) push to backward_edges if right_face = oface */ + if ( e->face_right == face ) + { + LWDEBUGF(1, "Edge %d has new face on the right side", e->edge_id); + backward_edges[backward_edges_count].edge_id = e->edge_id; + backward_edges[backward_edges_count++].face_right = newface.face_id; + } + } + + /* Update forward edges */ + if ( forward_edges_count ) + { + ret = lwt_be_updateEdgesById(topo, forward_edges, + forward_edges_count, + LWT_COL_EDGE_FACE_LEFT); + if ( ret == -1 ) + { + lwfree( signed_edge_ids ); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -2; + } + if ( ret != forward_edges_count ) + { + lwfree( signed_edge_ids ); + lwerror("Unexpected error: %d edges updated when expecting %d", + ret, forward_edges_count); + return -2; + } + } + + /* Update backward edges */ + if ( backward_edges_count ) + { + ret = lwt_be_updateEdgesById(topo, backward_edges, + backward_edges_count, + LWT_COL_EDGE_FACE_RIGHT); + if ( ret == -1 ) + { + lwfree( signed_edge_ids ); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -2; + } + if ( ret != backward_edges_count ) + { + lwfree( signed_edge_ids ); + lwerror("Unexpected error: %d edges updated when expecting %d", + ret, backward_edges_count); + return -2; + } + } + + lwfree(forward_edges); + lwfree(backward_edges); + + } + + _lwt_release_edges(edges, numfaceedges); + + /* Update isolated nodes which are now in new face */ + uint64_t numisonodes = 1; + fields = LWT_COL_NODE_NODE_ID | LWT_COL_NODE_GEOM; + LWT_ISO_NODE *nodes = lwt_be_getNodeByFace(topo, &face, + &numisonodes, fields, newface.mbr); + if (numisonodes == UINT64_MAX) + { + lwfree(signed_edge_ids); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -2; + } + if ( numisonodes ) { + LWT_ISO_NODE *updated_nodes = lwalloc(sizeof(LWT_ISO_NODE)*numisonodes); + int nodes_to_update = 0; + for (i=0; igeom->point, 0); + int contains = ptarray_contains_point(pa, pt) == LW_INSIDE; + LWDEBUGF(1, "Node %d is %scontained in new ring, newface is %s", + n->node_id, contains ? "" : "not ", + newface_outside ? "outside" : "inside" ); + if ( newface_outside ) + { + if ( contains ) + { + LWDEBUGF(1, "Node %d contained in an hole of the new face", + n->node_id); + continue; + } + } + else + { + if ( ! contains ) + { + LWDEBUGF(1, "Node %d not contained in the face shell", + n->node_id); + continue; + } + } + updated_nodes[nodes_to_update].node_id = n->node_id; + updated_nodes[nodes_to_update++].containing_face = + newface.face_id; + LWDEBUGF(1, "Node %d will be updated", n->node_id); + } + _lwt_release_nodes(nodes, numisonodes); + if ( nodes_to_update ) + { + int ret = lwt_be_updateNodesById(topo, updated_nodes, + nodes_to_update, + LWT_COL_NODE_CONTAINING_FACE); + if ( ret == -1 ) { + lwfree( signed_edge_ids ); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -2; + } + } + lwfree(updated_nodes); + } + + lwfree(signed_edge_ids); + lwpoly_free(shell); + + return newface.face_id; +} + +/** + * @param modFace can be + * 0 - have two new faces replace a splitted face + * 1 - modify a splitted face, adding a new one + * -1 - do not check at all for face splitting + * + */ +static LWT_ELEMID +_lwt_AddEdge( LWT_TOPOLOGY* topo, + LWT_ELEMID start_node, LWT_ELEMID end_node, + LWLINE *geom, int skipChecks, int modFace ) +{ + LWT_ISO_EDGE newedge; + LWGEOM *cleangeom; + edgeend span; /* start point analisys */ + edgeend epan; /* end point analisys */ + POINT2D p1, pn, p2; + POINTARRAY *pa; + LWT_ELEMID node_ids[2]; + const LWPOINT *start_node_geom = NULL; + const LWPOINT *end_node_geom = NULL; + uint64_t num_nodes; + LWT_ISO_NODE *endpoints; + uint64_t i; + int prev_left; + int prev_right; + LWT_ISO_EDGE seledge; + LWT_ISO_EDGE updedge; + + if ( ! skipChecks ) + { + /* curve must be simple */ + if ( ! lwgeom_is_simple(lwline_as_lwgeom(geom)) ) + { + lwerror("SQL/MM Spatial exception - curve not simple"); + return -1; + } + } + + newedge.start_node = start_node; + newedge.end_node = end_node; + newedge.geom = geom; + newedge.face_left = -1; + newedge.face_right = -1; + /* TODO: should do the repeated points removal in 2D space */ + cleangeom = lwgeom_remove_repeated_points( lwline_as_lwgeom(geom), 0 ); + + pa = lwgeom_as_lwline(cleangeom)->points; + if ( pa->npoints < 2 ) { + lwgeom_free(cleangeom); + lwerror("Invalid edge (no two distinct vertices exist)"); + return -1; + } + + /* Initialize endpoint info (some of that ) */ + span.cwFace = span.ccwFace = + epan.cwFace = epan.ccwFace = -1; + + /* Compute azimuth of first edge end on start node */ + getPoint2d_p(pa, 0, &p1); + if ( ! _lwt_FirstDistinctVertex2D(pa, &p1, 0, 1, &pn) ) + { + lwgeom_free(cleangeom); + lwerror("Invalid edge (no two distinct vertices exist)"); + return -1; + } + if ( ! azimuth_pt_pt(&p1, &pn, &span.myaz) ) { + lwgeom_free(cleangeom); + lwerror("error computing azimuth of first edgeend [%.15g %.15g,%.15g %.15g]", + p1.x, p1.y, pn.x, pn.y); + return -1; + } + LWDEBUGF(1, "edge's start node is %g,%g", p1.x, p1.y); + + /* Compute azimuth of last edge end on end node */ + getPoint2d_p(pa, pa->npoints-1, &p2); + if ( ! _lwt_FirstDistinctVertex2D(pa, &p2, pa->npoints-1, -1, &pn) ) + { + lwgeom_free(cleangeom); + /* This should never happen as we checked the edge while computing first edgend */ + lwerror("Invalid clean edge (no two distinct vertices exist) - should not happen"); + return -1; + } + lwgeom_free(cleangeom); + if ( ! azimuth_pt_pt(&p2, &pn, &epan.myaz) ) { + lwerror("error computing azimuth of last edgeend [%.15g %.15g,%.15g %.15g]", + p2.x, p2.y, pn.x, pn.y); + return -1; + } + LWDEBUGF(1, "edge's end node is %g,%g", p2.x, p2.y); + + /* + * Check endpoints existence, match with Curve geometry + * and get face information (if any) + */ + + if ( start_node != end_node ) { + num_nodes = 2; + node_ids[0] = start_node; + node_ids[1] = end_node; + } else { + num_nodes = 1; + node_ids[0] = start_node; + } + + endpoints = lwt_be_getNodeById( topo, node_ids, &num_nodes, LWT_COL_NODE_ALL ); + if (num_nodes == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + for ( i=0; icontaining_face != -1 ) + { + if ( newedge.face_left == -1 ) + { + newedge.face_left = newedge.face_right = node->containing_face; + } + else if ( newedge.face_left != node->containing_face ) + { + _lwt_release_nodes(endpoints, num_nodes); + lwerror("SQL/MM Spatial exception - geometry crosses an edge" + " (endnodes in faces %" LWTFMT_ELEMID " and %" LWTFMT_ELEMID ")", + newedge.face_left, node->containing_face); + } + } + + LWDEBUGF(1, "Node %d, with geom %p (looking for %d and %d)", + node->node_id, node->geom, start_node, end_node); + if ( node->node_id == start_node ) { + start_node_geom = node->geom; + } + if ( node->node_id == end_node ) { + end_node_geom = node->geom; + } + } + + if ( ! skipChecks ) + { + if ( ! start_node_geom ) + { + if ( num_nodes ) _lwt_release_nodes(endpoints, num_nodes); + lwerror("SQL/MM Spatial exception - non-existent node"); + return -1; + } + else + { + pa = start_node_geom->point; + getPoint2d_p(pa, 0, &pn); + if ( ! P2D_SAME_STRICT(&pn, &p1) ) + { + if ( num_nodes ) _lwt_release_nodes(endpoints, num_nodes); + lwerror("SQL/MM Spatial exception" + " - start node not geometry start point." + //" - start node not geometry start point (%g,%g != %g,%g).", pn.x, pn.y, p1.x, p1.y + ); + return -1; + } + } + + if ( ! end_node_geom ) + { + if ( num_nodes ) _lwt_release_nodes(endpoints, num_nodes); + lwerror("SQL/MM Spatial exception - non-existent node"); + return -1; + } + else + { + pa = end_node_geom->point; + getPoint2d_p(pa, 0, &pn); + if ( ! P2D_SAME_STRICT(&pn, &p2) ) + { + if ( num_nodes ) _lwt_release_nodes(endpoints, num_nodes); + lwerror("SQL/MM Spatial exception" + " - end node not geometry end point." + //" - end node not geometry end point (%g,%g != %g,%g).", pn.x, pn.y, p2.x, p2.y + ); + return -1; + } + } + + if ( num_nodes ) _lwt_release_nodes(endpoints, num_nodes); + + if ( _lwt_CheckEdgeCrossing( topo, start_node, end_node, geom, 0 ) ) + return -1; + + } /* ! skipChecks */ + + /* + * All checks passed, time to prepare the new edge + */ + + newedge.edge_id = lwt_be_getNextEdgeId( topo ); + if ( newedge.edge_id == -1 ) { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + /* Find adjacent edges to each endpoint */ + int isclosed = start_node == end_node; + int found; + found = _lwt_FindAdjacentEdges( topo, start_node, &span, + isclosed ? &epan : NULL, -1 ); + if ( found ) { + span.was_isolated = 0; + newedge.next_right = span.nextCW ? span.nextCW : -newedge.edge_id; + prev_left = span.nextCCW ? -span.nextCCW : newedge.edge_id; + LWDEBUGF(1, "New edge %d is connected on start node, " + "next_right is %d, prev_left is %d", + newedge.edge_id, newedge.next_right, prev_left); + if ( modFace != -1 ) + { + if ( newedge.face_right == -1 ) { + newedge.face_right = span.cwFace; + } + if ( newedge.face_left == -1 ) { + newedge.face_left = span.ccwFace; + } + } + } else { + span.was_isolated = 1; + newedge.next_right = isclosed ? -newedge.edge_id : newedge.edge_id; + prev_left = isclosed ? newedge.edge_id : -newedge.edge_id; + LWDEBUGF(1, "New edge %d is isolated on start node, " + "next_right is %d, prev_left is %d", + newedge.edge_id, newedge.next_right, prev_left); + } + + found = _lwt_FindAdjacentEdges( topo, end_node, &epan, + isclosed ? &span : NULL, -1 ); + if ( found ) { + epan.was_isolated = 0; + newedge.next_left = epan.nextCW ? epan.nextCW : newedge.edge_id; + prev_right = epan.nextCCW ? -epan.nextCCW : -newedge.edge_id; + LWDEBUGF(1, "New edge %d is connected on end node, " + "next_left is %d, prev_right is %d", + newedge.edge_id, newedge.next_left, prev_right); + if ( modFace != -1 ) + { + if ( newedge.face_right == -1 ) { + newedge.face_right = span.ccwFace; + } else if ( newedge.face_right != epan.ccwFace ) { + /* side-location conflict */ + lwerror("Side-location conflict: " + "new edge starts in face" + " %" LWTFMT_ELEMID " and ends in face" + " %" LWTFMT_ELEMID, + newedge.face_right, epan.ccwFace + ); + return -1; + } + if ( newedge.face_left == -1 ) { + newedge.face_left = span.cwFace; + } else if ( newedge.face_left != epan.cwFace ) { + /* side-location conflict */ + lwerror("Side-location conflict: " + "new edge starts in face" + " %" LWTFMT_ELEMID " and ends in face" + " %" LWTFMT_ELEMID, + newedge.face_left, epan.cwFace + ); + return -1; + } + } + } else { + epan.was_isolated = 1; + newedge.next_left = isclosed ? newedge.edge_id : -newedge.edge_id; + prev_right = isclosed ? -newedge.edge_id : newedge.edge_id; + LWDEBUGF(1, "New edge %d is isolated on end node, " + "next_left is %d, prev_right is %d", + newedge.edge_id, newedge.next_left, prev_right); + } + + /* + * If we don't have faces setup by now we must have encountered + * a malformed topology (no containing_face on isolated nodes, no + * left/right faces on adjacent edges or mismatching values) + */ + if ( modFace > -1 ) + { + if ( newedge.face_left != newedge.face_right ) + { + lwerror("Left(%" LWTFMT_ELEMID ")/right(%" LWTFMT_ELEMID ")" + " faces mismatch: invalid topology ?", + newedge.face_left, newedge.face_right); + return -1; + } + else if ( newedge.face_left == -1 ) + { + lwerror("Could not derive edge face from linked primitives:" + " invalid topology ?"); + return -1; + } + } + + /* + * Insert the new edge, and update all linking + */ + + int ret = lwt_be_insertEdges(topo, &newedge, 1); + if ( ret == -1 ) { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } else if ( ret == 0 ) { + lwerror("Insertion of split edge failed (no reason)"); + return -1; + } + + int updfields; + + /* Link prev_left to us + * (if it's not us already) */ + if ( llabs(prev_left) != newedge.edge_id ) + { + if ( prev_left > 0 ) + { + /* its next_left_edge is us */ + updfields = LWT_COL_EDGE_NEXT_LEFT; + updedge.next_left = newedge.edge_id; + seledge.edge_id = prev_left; + } + else + { + /* its next_right_edge is us */ + updfields = LWT_COL_EDGE_NEXT_RIGHT; + updedge.next_right = newedge.edge_id; + seledge.edge_id = -prev_left; + } + + ret = lwt_be_updateEdges(topo, + &seledge, LWT_COL_EDGE_EDGE_ID, + &updedge, updfields, + NULL, 0); + if ( ret == -1 ) { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + } + + /* Link prev_right to us + * (if it's not us already) */ + if ( llabs(prev_right) != newedge.edge_id ) + { + if ( prev_right > 0 ) + { + /* its next_left_edge is -us */ + updfields = LWT_COL_EDGE_NEXT_LEFT; + updedge.next_left = -newedge.edge_id; + seledge.edge_id = prev_right; + } + else + { + /* its next_right_edge is -us */ + updfields = LWT_COL_EDGE_NEXT_RIGHT; + updedge.next_right = -newedge.edge_id; + seledge.edge_id = -prev_right; + } + + ret = lwt_be_updateEdges(topo, + &seledge, LWT_COL_EDGE_EDGE_ID, + &updedge, updfields, + NULL, 0); + if ( ret == -1 ) { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + } + + /* NOT IN THE SPECS... + * set containing_face = null for start_node and end_node + * if they where isolated + * + */ + LWT_ISO_NODE updnode, selnode; + updnode.containing_face = -1; + if ( span.was_isolated ) + { + selnode.node_id = start_node; + ret = lwt_be_updateNodes(topo, + &selnode, LWT_COL_NODE_NODE_ID, + &updnode, LWT_COL_NODE_CONTAINING_FACE, + NULL, 0); + if ( ret == -1 ) { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + } + if ( epan.was_isolated ) + { + selnode.node_id = end_node; + ret = lwt_be_updateNodes(topo, + &selnode, LWT_COL_NODE_NODE_ID, + &updnode, LWT_COL_NODE_CONTAINING_FACE, + NULL, 0); + if ( ret == -1 ) { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + } + + /* Check face splitting, if required */ + + if ( modFace > -1 ) { + + if ( ! isclosed && ( epan.was_isolated || span.was_isolated ) ) + { + LWDEBUG(1, "New edge is dangling, so it cannot split any face"); + return newedge.edge_id; /* no split */ + } + + int newface1 = -1; + + /* IDEA: avoid building edge ring if input is closed, which means we + * know in advance it splits a face */ + + if ( ! modFace ) + { + newface1 = _lwt_AddFaceSplit( topo, -newedge.edge_id, newedge.face_left, 0 ); + if ( newface1 == 0 ) { + LWDEBUG(1, "New edge does not split any face"); + return newedge.edge_id; /* no split */ + } + } + + int newface = _lwt_AddFaceSplit( topo, newedge.edge_id, + newedge.face_left, 0 ); + if ( modFace ) + { + if ( newface == 0 ) { + LWDEBUG(1, "New edge does not split any face"); + return newedge.edge_id; /* no split */ + } + + if ( newface < 0 ) + { + /* face on the left is the universe face */ + /* must be forming a maximal ring in universal face */ + newface = _lwt_AddFaceSplit( topo, -newedge.edge_id, + newedge.face_left, 0 ); + if ( newface < 0 ) return newedge.edge_id; /* no split */ + } + else + { + _lwt_AddFaceSplit( topo, -newedge.edge_id, newedge.face_left, 1 ); + } + } + + /* + * Update topogeometries, if needed + */ + if ( newedge.face_left != 0 ) + { + ret = lwt_be_updateTopoGeomFaceSplit(topo, newedge.face_left, + newface, newface1); + if ( ret == 0 ) { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + if ( ! modFace ) + { + /* drop old face from the face table */ + ret = lwt_be_deleteFacesById(topo, &(newedge.face_left), 1); + if ( ret == -1 ) { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + } + } + + } // end of face split checking + + return newedge.edge_id; +} + +LWT_ELEMID +lwt_AddEdgeModFace( LWT_TOPOLOGY* topo, + LWT_ELEMID start_node, LWT_ELEMID end_node, + LWLINE *geom, int skipChecks ) +{ + return _lwt_AddEdge( topo, start_node, end_node, geom, skipChecks, 1 ); +} + +LWT_ELEMID +lwt_AddEdgeNewFaces( LWT_TOPOLOGY* topo, + LWT_ELEMID start_node, LWT_ELEMID end_node, + LWLINE *geom, int skipChecks ) +{ + return _lwt_AddEdge( topo, start_node, end_node, geom, skipChecks, 0 ); +} + +static LWGEOM * +_lwt_FaceByEdges(LWT_TOPOLOGY *topo, LWT_ISO_EDGE *edges, int numfaceedges) +{ + LWGEOM *outg; + LWCOLLECTION *bounds; + LWGEOM **geoms = lwalloc( sizeof(LWGEOM*) * numfaceedges ); + int i, validedges = 0; + + for ( i=0; isrid, topo->hasZ, 0) + ); + } + bounds = lwcollection_construct(MULTILINETYPE, + topo->srid, + NULL, /* gbox */ + validedges, + geoms); + outg = lwgeom_buildarea( lwcollection_as_lwgeom(bounds) ); + lwcollection_release(bounds); + lwfree(geoms); +#if 0 + { + size_t sz; + char *wkt = lwgeom_to_wkt(outg, WKT_EXTENDED, 2, &sz); + LWDEBUGF(1, "_lwt_FaceByEdges returning area: %s", wkt); + lwfree(wkt); + } +#endif + return outg; +} + +LWGEOM* +lwt_GetFaceGeometry(LWT_TOPOLOGY* topo, LWT_ELEMID faceid) +{ + uint64_t numfaceedges; + LWT_ISO_EDGE *edges; + LWT_ISO_FACE *face; + LWPOLY *out; + LWGEOM *outg; + uint64_t i, edgeid; + int fields; + + if (faceid == 0) + { + lwerror("SQL/MM Spatial exception - universal face has no geometry"); + return NULL; + } + + /* Construct the face geometry */ + numfaceedges = 1; + fields = LWT_COL_EDGE_GEOM | + LWT_COL_EDGE_EDGE_ID | + LWT_COL_EDGE_FACE_LEFT | + LWT_COL_EDGE_FACE_RIGHT + ; + edges = lwt_be_getEdgeByFace( topo, &faceid, &numfaceedges, fields, NULL ); + if (numfaceedges == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return NULL; + } + LWDEBUGF(1, "lwt_GetFaceGeometry: lwt_be_getEdgeByFace returned %d edges", numfaceedges); + + if ( numfaceedges == 0 ) + { + i = 1; + face = lwt_be_getFaceById(topo, &faceid, &i, LWT_COL_FACE_FACE_ID); + if (i == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return NULL; + } + if ( i == 0 ) { + lwerror("SQL/MM Spatial exception - non-existent face."); + return NULL; + } + lwfree( face ); + if ( i > 1 ) { + lwerror("Corrupted topology: multiple face records have face_id=%" + LWTFMT_ELEMID, faceid); + return NULL; + } + /* Face has no boundary edges, we'll return EMPTY, see + * https://trac.osgeo.org/postgis/ticket/3221 */ + lwnotice("Corrupted topology: face %" + LWTFMT_ELEMID " has no associated edges.", faceid); + out = lwpoly_construct_empty(topo->srid, topo->hasZ, 0); + return lwpoly_as_lwgeom(out); + } + edgeid = edges[0].edge_id; + + outg = _lwt_FaceByEdges( topo, edges, numfaceedges ); + _lwt_release_edges(edges, numfaceedges); + + if ( ! outg ) + { + /* Face did have edges but no polygon could be constructed + * with that material, sounds like a corrupted topology.. + * + * We'll return EMPTY, see + * https://trac.osgeo.org/postgis/ticket/3221 */ + lwnotice("Corrupted topology: face %" + LWTFMT_ELEMID " could not be constructed only from edges " + "knowing about it (like edge %" LWTFMT_ELEMID ").", + faceid, edgeid); + out = lwpoly_construct_empty(topo->srid, topo->hasZ, 0); + return lwpoly_as_lwgeom(out); + } + + return outg; +} + +/* Find which edge from the "edges" set defines the next + * portion of the given "ring". + * + * The edge might be either forward or backward. + * + * @param ring The ring to find definition of. + * It is assumed it does not contain duplicated vertices. + * @param from offset of the ring point to start looking from + * @param edges array of edges to search into + * @param numedges number of edges in the edges array + * + * @return index of the edge defining the next ring portion or + * -1 if no edge was found to be part of the ring + */ +static int +_lwt_FindNextRingEdge(const POINTARRAY *ring, int from, + const LWT_ISO_EDGE *edges, int numedges) +{ + int i; + POINT2D p1; + + /* Get starting ring point */ + getPoint2d_p(ring, from, &p1); + + LWDEBUGF(1, "Ring's 'from' point (%d) is %g,%g", from, p1.x, p1.y); + + /* find the edges defining the next portion of ring starting from + * vertex "from" */ + for ( i=0; igeom; + POINTARRAY *epa = edge->points; + POINT2D p2, pt; + int match = 0; + uint32_t j; + + /* Skip if the edge is a dangling one */ + if ( isoe->face_left == isoe->face_right ) + { + LWDEBUGF(3, "_lwt_FindNextRingEdge: edge %" LWTFMT_ELEMID + " has same face (%" LWTFMT_ELEMID + ") on both sides, skipping", + isoe->edge_id, isoe->face_left); + continue; + } + + if (epa->npoints < 2) + { + LWDEBUGF(3, "_lwt_FindNextRingEdge: edge %" LWTFMT_ELEMID + " has only %"PRIu32" points", + isoe->edge_id, epa->npoints); + continue; + } + +#if 0 + size_t sz; + LWDEBUGF(1, "Edge %" LWTFMT_ELEMID " is %s", + isoe->edge_id, + lwgeom_to_wkt(lwline_as_lwgeom(edge), WKT_EXTENDED, 2, &sz)); +#endif + + /* ptarray_remove_repeated_points ? */ + + getPoint2d_p(epa, 0, &p2); + LWDEBUGF(1, "Edge %" LWTFMT_ELEMID " 'first' point is %g,%g", + isoe->edge_id, p2.x, p2.y); + LWDEBUGF(1, "Rings's 'from' point is still %g,%g", p1.x, p1.y); + if ( P2D_SAME_STRICT(&p1, &p2) ) + { + LWDEBUG(1, "P2D_SAME_STRICT(p1,p2) returned true"); + LWDEBUGF(1, "First point of edge %" LWTFMT_ELEMID + " matches ring vertex %d", isoe->edge_id, from); + /* first point matches, let's check next non-equal one */ + for ( j=1; jnpoints; ++j ) + { + getPoint2d_p(epa, j, &p2); + LWDEBUGF(1, "Edge %" LWTFMT_ELEMID " 'next' point %d is %g,%g", + isoe->edge_id, j, p2.x, p2.y); + /* we won't check duplicated edge points */ + if ( P2D_SAME_STRICT(&p1, &p2) ) continue; + /* we assume there are no duplicated points in ring */ + getPoint2d_p(ring, from+1, &pt); + LWDEBUGF(1, "Ring's point %d is %g,%g", + from+1, pt.x, pt.y); + match = P2D_SAME_STRICT(&pt, &p2); + break; /* we want to check a single non-equal next vertex */ + } +#if POSTGIS_DEBUG_LEVEL > 0 + if ( match ) { + LWDEBUGF(1, "Prev point of edge %" LWTFMT_ELEMID + " matches ring vertex %d", isoe->edge_id, from+1); + } else { + LWDEBUGF(1, "Prev point of edge %" LWTFMT_ELEMID + " does not match ring vertex %d", isoe->edge_id, from+1); + } +#endif + } + + if ( ! match ) + { + LWDEBUGF(1, "Edge %" LWTFMT_ELEMID " did not match as forward", + isoe->edge_id); + getPoint2d_p(epa, epa->npoints-1, &p2); + LWDEBUGF(1, "Edge %" LWTFMT_ELEMID " 'last' point is %g,%g", + isoe->edge_id, p2.x, p2.y); + if ( P2D_SAME_STRICT(&p1, &p2) ) + { + LWDEBUGF(1, "Last point of edge %" LWTFMT_ELEMID + " matches ring vertex %d", isoe->edge_id, from); + /* last point matches, let's check next non-equal one */ + for ( j=2; j<=epa->npoints; j++ ) + { + getPoint2d_p(epa, epa->npoints - j, &p2); + LWDEBUGF(1, "Edge %" LWTFMT_ELEMID " 'prev' point %d is %g,%g", + isoe->edge_id, epa->npoints - j, p2.x, p2.y); + /* we won't check duplicated edge points */ + if ( P2D_SAME_STRICT(&p1, &p2) ) continue; + /* we assume there are no duplicated points in ring */ + getPoint2d_p(ring, from+1, &pt); + LWDEBUGF(1, "Ring's point %d is %g,%g", + from+1, pt.x, pt.y); + match = P2D_SAME_STRICT(&pt, &p2); + break; /* we want to check a single non-equal next vertex */ + } + } +#if POSTGIS_DEBUG_LEVEL > 0 + if ( match ) { + LWDEBUGF(1, "Prev point of edge %" LWTFMT_ELEMID + " matches ring vertex %d", isoe->edge_id, from+1); + } else { + LWDEBUGF(1, "Prev point of edge %" LWTFMT_ELEMID + " does not match ring vertex %d", isoe->edge_id, from+1); + } +#endif + } + + if ( match ) return i; + + } + + return -1; +} + +/* Reverse values in array between "from" (inclusive) + * and "to" (exclusive) indexes */ +static void +_lwt_ReverseElemidArray(LWT_ELEMID *ary, int from, int to) +{ + LWT_ELEMID t; + while (from < to) + { + t = ary[from]; + ary[from++] = ary[to]; + ary[to--] = t; + } +} + +/* Rotate values in array between "from" (inclusive) + * and "to" (exclusive) indexes, so that "rotidx" is + * the new value at "from" */ +static void +_lwt_RotateElemidArray(LWT_ELEMID *ary, int from, int to, int rotidx) +{ + _lwt_ReverseElemidArray(ary, from, rotidx-1); + _lwt_ReverseElemidArray(ary, rotidx, to-1); + _lwt_ReverseElemidArray(ary, from, to-1); +} + + +int +lwt_GetFaceEdges(LWT_TOPOLOGY* topo, LWT_ELEMID face_id, LWT_ELEMID **out ) +{ + LWGEOM *face; + LWPOLY *facepoly; + LWT_ISO_EDGE *edges; + uint64_t numfaceedges; + int fields; + uint32_t i; + int nseid = 0; /* number of signed edge ids */ + int prevseid; + LWT_ELEMID *seid; /* signed edge ids */ + + /* Get list of face edges */ + numfaceedges = 1; + fields = LWT_COL_EDGE_EDGE_ID | + LWT_COL_EDGE_GEOM | + LWT_COL_EDGE_FACE_LEFT | + LWT_COL_EDGE_FACE_RIGHT + ; + edges = lwt_be_getEdgeByFace( topo, &face_id, &numfaceedges, fields, NULL ); + if (numfaceedges == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if ( ! numfaceedges ) return 0; /* no edges in output */ + LWDEBUGF(1, "lwt_GetFaceEdges: lwt_be_getEdgeByFace returned %d edges", numfaceedges); + + /* order edges by occurrence in face */ + + face = _lwt_FaceByEdges(topo, edges, numfaceedges); + if ( ! face ) + { + /* _lwt_FaceByEdges should have already invoked lwerror in this case */ + _lwt_release_edges(edges, numfaceedges); + return -1; + } + + if ( lwgeom_is_empty(face) ) + { + /* no edges in output */ + _lwt_release_edges(edges, numfaceedges); + lwgeom_free(face); + return 0; + } + + /* force_lhr, if the face is not the universe */ + /* _lwt_FaceByEdges seems to guaranteed RHR */ + /* lwgeom_force_clockwise(face); */ + if ( face_id ) lwgeom_reverse_in_place(face); + +#if 0 + { + size_t sz; + char *wkt = lwgeom_to_wkt(face, WKT_EXTENDED, 6, &sz); + LWDEBUGF(1, "Geometry of face %" LWTFMT_ELEMID " is: %s", + face_id, wkt); + lwfree(wkt); + } +#endif + + facepoly = lwgeom_as_lwpoly(face); + if ( ! facepoly ) + { + _lwt_release_edges(edges, numfaceedges); + lwgeom_free(face); + lwerror("Geometry of face %" LWTFMT_ELEMID " is not a polygon", face_id); + return -1; + } + + nseid = prevseid = 0; + seid = lwalloc( sizeof(LWT_ELEMID) * numfaceedges ); + + /* for each ring of the face polygon... */ + for ( i=0; inrings; ++i ) + { + const POINTARRAY *ring = facepoly->rings[i]; + int32_t j = 0; + LWT_ISO_EDGE *nextedge; + LWLINE *nextline; + + LWDEBUGF(1, "Ring %d has %d points", i, ring->npoints); + + while ( j < (int32_t) ring->npoints-1 ) + { + LWDEBUGF(1, "Looking for edge covering ring %d from vertex %d", + i, j); + + int edgeno = _lwt_FindNextRingEdge(ring, j, edges, numfaceedges); + if ( edgeno == -1 ) + { + /* should never happen */ + _lwt_release_edges(edges, numfaceedges); + lwgeom_free(face); + lwfree(seid); + lwerror("No edge (among %d) found to be defining geometry of face %" + LWTFMT_ELEMID, numfaceedges, face_id); + return -1; + } + + nextedge = &(edges[edgeno]); + nextline = nextedge->geom; + + LWDEBUGF(1, "Edge %" LWTFMT_ELEMID + " covers ring %d from vertex %d to %d", + nextedge->edge_id, i, j, j + nextline->points->npoints - 1); + +#if 0 + { + size_t sz; + char *wkt = lwgeom_to_wkt(lwline_as_lwgeom(nextline), WKT_EXTENDED, 6, &sz); + LWDEBUGF(1, "Edge %" LWTFMT_ELEMID " is %s", + nextedge->edge_id, wkt); + lwfree(wkt); + } +#endif + + j += nextline->points->npoints - 1; + + /* Add next edge to the output array */ + seid[nseid++] = nextedge->face_left == face_id ? + nextedge->edge_id : + -nextedge->edge_id; + + /* avoid checking again on next time turn */ + nextedge->face_left = nextedge->face_right = -1; + } + + /* now "scroll" the list of edges so that the one + * with smaller absolute edge_id is first */ + /* Range is: [prevseid, nseid) -- [inclusive, exclusive) */ + if ( (nseid - prevseid) > 1 ) + {{ + LWT_ELEMID minid = 0; + int minidx = 0; + LWDEBUGF(1, "Looking for smallest id among the %d edges " + "composing ring %d", (nseid-prevseid), i); + for ( j=prevseid; jbe_iface)); + return -1; + } + else if ( i == 0 ) + { + lwerror("SQL/MM Spatial exception - non-existent edge %" + LWTFMT_ELEMID, edge_id); + return -1; + } + else + { + lwerror("Backend coding error: getEdgeById callback returned NULL " + "but numelements output parameter has value %d " + "(expected 0 or 1)", i); + return -1; + } + } + + LWDEBUGF(1, "lwt_ChangeEdgeGeom: " + "old edge has %d points, new edge has %d points", + oldedge->geom->points->npoints, geom->points->npoints); + + /* + * e) Check StartPoint consistency + */ + getPoint2d_p(oldedge->geom->points, 0, &p1); + getPoint2d_p(geom->points, 0, &pt); + if ( ! P2D_SAME_STRICT(&p1, &pt) ) + { + _lwt_release_edges(oldedge, 1); + lwerror("SQL/MM Spatial exception - " + "start node not geometry start point."); + return -1; + } + + /* + * f) Check EndPoint consistency + */ + if ( oldedge->geom->points->npoints < 2 ) + { + _lwt_release_edges(oldedge, 1); + lwerror("Corrupted topology: edge %" LWTFMT_ELEMID + " has less than 2 vertices", oldedge->edge_id); + return -1; + } + getPoint2d_p(oldedge->geom->points, oldedge->geom->points->npoints-1, &p2); + if ( geom->points->npoints < 2 ) + { + _lwt_release_edges(oldedge, 1); + lwerror("Invalid edge: less than 2 vertices"); + return -1; + } + getPoint2d_p(geom->points, geom->points->npoints-1, &pt); + if ( ! P2D_SAME_STRICT(&pt, &p2) ) + { + _lwt_release_edges(oldedge, 1); + lwerror("SQL/MM Spatial exception - " + "end node not geometry end point."); + return -1; + } + + /* Not in the specs: + * if the edge is closed, check we didn't change winding ! + * (should be part of isomorphism checking) + */ + if ( oldedge->start_node == oldedge->end_node ) + { + isclosed = 1; +#if 1 /* TODO: this is actually bogus as a test */ + /* check for valid edge (distinct vertices must exist) */ + if ( ! _lwt_GetInteriorEdgePoint(geom, &pt) ) + { + _lwt_release_edges(oldedge, 1); + lwerror("Invalid edge (no two distinct vertices exist)"); + return -1; + } +#endif + + if ( ptarray_isccw(oldedge->geom->points) != + ptarray_isccw(geom->points) ) + { + _lwt_release_edges(oldedge, 1); + lwerror("Edge twist at node POINT(%g %g)", p1.x, p1.y); + return -1; + } + } + + if ( _lwt_CheckEdgeCrossing(topo, oldedge->start_node, + oldedge->end_node, geom, edge_id ) ) + { + /* would have called lwerror already, leaking :( */ + _lwt_release_edges(oldedge, 1); + return -1; + } + + LWDEBUG(1, "lwt_ChangeEdgeGeom: " + "edge crossing check passed "); + + /* + * Not in the specs: + * Check topological isomorphism + */ + + /* Check that the "motion range" doesn't include any node */ + // 1. compute combined bbox of old and new edge + GBOX mbox; /* motion box */ + lwgeom_add_bbox((LWGEOM*)oldedge->geom); /* just in case */ + lwgeom_add_bbox((LWGEOM*)geom); /* just in case */ + gbox_union(oldedge->geom->bbox, geom->bbox, &mbox); + // 2. fetch all nodes in the combined box + LWT_ISO_NODE *nodes; + uint64_t numnodes; + nodes = lwt_be_getNodeWithinBox2D(topo, &mbox, &numnodes, + LWT_COL_NODE_ALL, 0); + LWDEBUGF(1, "lwt_be_getNodeWithinBox2D returned %d nodes", numnodes); + if (numnodes == UINT64_MAX) + { + _lwt_release_edges(oldedge, 1); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + // 3. if any node beside endnodes are found: + if ( numnodes > ( 1 + isclosed ? 0 : 1 ) ) + {{ + // 3.2. bail out if any node is in one and not the other + for (i=0; inode_id == oldedge->start_node ) continue; + if ( n->node_id == oldedge->end_node ) continue; + const POINT2D *pt = getPoint2d_cp(n->geom->point, 0); + int ocont = ptarray_contains_point_partial(oldedge->geom->points, pt, isclosed, NULL) == LW_INSIDE; + int ncont = ptarray_contains_point_partial(geom->points, pt, isclosed, NULL) == LW_INSIDE; + if (ocont != ncont) + { + size_t sz; + char *wkt = lwgeom_to_wkt(lwpoint_as_lwgeom(n->geom), WKT_ISO, 15, &sz); + _lwt_release_nodes(nodes, numnodes); + lwerror("Edge motion collision at %s", wkt); + lwfree(wkt); /* would not necessarely reach this point */ + return -1; + } + } + }} + if ( numnodes ) _lwt_release_nodes(nodes, numnodes); + + LWDEBUG(1, "nodes containment check passed"); + + /* + * Check edge adjacency before + * TODO: can be optimized to gather azimuths of all edge ends once + */ + + edgeend span_pre, epan_pre; + /* initialize span_pre.myaz and epan_pre.myaz with existing edge */ + int res = _lwt_InitEdgeEndByLine(&span_pre, &epan_pre, oldedge->geom, &p1, &p2); + if (res) + return -1; /* lwerror should have been raised */ + _lwt_FindAdjacentEdges( topo, oldedge->start_node, &span_pre, + isclosed ? &epan_pre : NULL, edge_id ); + _lwt_FindAdjacentEdges( topo, oldedge->end_node, &epan_pre, + isclosed ? &span_pre : NULL, edge_id ); + + LWDEBUGF(1, "edges adjacent to old edge are %" LWTFMT_ELEMID + " and %" LWTFMT_ELEMID " (first point), %" LWTFMT_ELEMID + " and %" LWTFMT_ELEMID " (last point)", + span_pre.nextCW, span_pre.nextCCW, + epan_pre.nextCW, epan_pre.nextCCW); + + /* update edge geometry */ + newedge.edge_id = edge_id; + newedge.geom = geom; + res = lwt_be_updateEdgesById(topo, &newedge, 1, LWT_COL_EDGE_GEOM); + if (res == -1) + { + _lwt_release_edges(oldedge, 1); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if (!res) + { + _lwt_release_edges(oldedge, 1); + lwerror("Unexpected error: %d edges updated when expecting 1", i); + return -1; + } + + /* + * Check edge adjacency after + */ + edgeend span_post, epan_post; + /* initialize epan_post.myaz and epan_post.myaz */ + res = _lwt_InitEdgeEndByLine(&span_post, &epan_post, geom, &p1, &p2); + if (res) + return -1; /* lwerror should have been raised */ + _lwt_FindAdjacentEdges( topo, oldedge->start_node, &span_post, + isclosed ? &epan_post : NULL, edge_id ); + _lwt_FindAdjacentEdges( topo, oldedge->end_node, &epan_post, + isclosed ? &span_post : NULL, edge_id ); + + LWDEBUGF(1, "edges adjacent to new edge are %" LWTFMT_ELEMID + " and %" LWTFMT_ELEMID " (first point), %" LWTFMT_ELEMID + " and %" LWTFMT_ELEMID " (last point)", + span_pre.nextCW, span_pre.nextCCW, + epan_pre.nextCW, epan_pre.nextCCW); + + + /* Bail out if next CW or CCW edge on start node changed */ + if ( span_pre.nextCW != span_post.nextCW || + span_pre.nextCCW != span_post.nextCCW ) + {{ + LWT_ELEMID nid = oldedge->start_node; + _lwt_release_edges(oldedge, 1); + lwerror("Edge changed disposition around start node %" + LWTFMT_ELEMID, nid); + return -1; + }} + + /* Bail out if next CW or CCW edge on end node changed */ + if ( epan_pre.nextCW != epan_post.nextCW || + epan_pre.nextCCW != epan_post.nextCCW ) + {{ + LWT_ELEMID nid = oldedge->end_node; + _lwt_release_edges(oldedge, 1); + lwerror("Edge changed disposition around end node %" + LWTFMT_ELEMID, nid); + return -1; + }} + + /* + -- Update faces MBR of left and right faces + -- TODO: think about ways to optimize this part, like see if + -- the old edge geometry participated in the definition + -- of the current MBR (for shrinking) or the new edge MBR + -- would be larger than the old face MBR... + -- + */ + const GBOX* oldbox = lwgeom_get_bbox(lwline_as_lwgeom(oldedge->geom)); + const GBOX* newbox = lwgeom_get_bbox(lwline_as_lwgeom(geom)); + if ( ! gbox_same(oldbox, newbox) ) + { + GBOX* updatedBox; + uint64_t facestoupdate = 0; + LWT_ISO_FACE faces[2]; + if ( oldedge->face_left > 0 ) + { + updatedBox = lwt_be_computeFaceMBR(topo, oldedge->face_left); + if ( ! updatedBox ) + { + lwerror("Corrupted topology: face %d, left of edge %d, has no bbox", + oldedge->face_left, edge_id); + return -1; + } + faces[facestoupdate].face_id = oldedge->face_left; + /* ownership transferred to faces[] */ + faces[facestoupdate++].mbr = updatedBox; + } + if ( oldedge->face_right > 0 + /* no need to update twice the same face.. */ + && oldedge->face_right != oldedge->face_left ) + { + updatedBox = lwt_be_computeFaceMBR(topo, oldedge->face_right); + if ( ! updatedBox ) + { + lwerror("Corrupted topology: face %d, right of edge %d, has no bbox", + oldedge->face_right, edge_id); + return -1; + } + faces[facestoupdate].face_id = oldedge->face_right; + /* ownership transferred to faces[] */ + faces[facestoupdate++].mbr = updatedBox; + } + LWDEBUGF(1, "%d faces to update", facestoupdate); + if ( facestoupdate ) + { + uint64_t updatedFaces = lwt_be_updateFacesById(topo, &(faces[0]), facestoupdate); + if (updatedFaces != facestoupdate) + { + while ( facestoupdate-- ) lwfree(faces[facestoupdate].mbr); + _lwt_release_edges(oldedge, 1); + if (updatedFaces == UINT64_MAX) + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + else + lwerror("Unexpected error: %d faces updated when expecting 1", updatedFaces); + return -1; + } + } + while ( facestoupdate-- ) lwfree(faces[facestoupdate].mbr); + } + else + { + LWDEBUG(1, "BBOX of changed edge did not change"); + } + + LWDEBUG(1, "all done, cleaning up edges"); + + _lwt_release_edges(oldedge, 1); + return 0; /* success */ +} + +/* Only return CONTAINING_FACE in the node object */ +static LWT_ISO_NODE * +_lwt_GetIsoNode(LWT_TOPOLOGY* topo, LWT_ELEMID nid) +{ + LWT_ISO_NODE *node; + uint64_t n = 1; + + node = lwt_be_getNodeById( topo, &nid, &n, LWT_COL_NODE_CONTAINING_FACE ); + if (n == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return 0; + } + if ( n < 1 ) { + lwerror("SQL/MM Spatial exception - non-existent node"); + return 0; + } + if ( node->containing_face == -1 ) + { + lwfree(node); + lwerror("SQL/MM Spatial exception - not isolated node"); + return 0; + } + + return node; +} + +int +lwt_MoveIsoNode(LWT_TOPOLOGY* topo, LWT_ELEMID nid, LWPOINT *pt) +{ + LWT_ISO_NODE *node; + int ret; + int newPointFace; + + node = _lwt_GetIsoNode( topo, nid ); + if ( ! node ) return -1; + + if ( lwt_be_ExistsCoincidentNode(topo, pt) ) + { + lwfree(node); + lwerror("SQL/MM Spatial exception - coincident node"); + return -1; + } + + if ( lwt_be_ExistsEdgeIntersectingPoint(topo, pt) ) + { + lwfree(node); + lwerror("SQL/MM Spatial exception - edge crosses node."); + return -1; + } + + /* Check that the new point is in the same containing face ! + * See https://trac.osgeo.org/postgis/ticket/3232 */ + newPointFace = lwt_GetFaceContainingPoint(topo, pt); + if ( newPointFace == -1 ) { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if ( node->containing_face != newPointFace ) + { + lwfree(node); + lwerror("Cannot move isolated node across faces"); + return -1; + } + + node->node_id = nid; + node->geom = pt; + ret = lwt_be_updateNodesById(topo, node, 1, + LWT_COL_NODE_GEOM); + if ( ret == -1 ) { + lwfree(node); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + lwfree(node); + return 0; +} + +int +lwt_RemoveIsoNode(LWT_TOPOLOGY* topo, LWT_ELEMID nid) +{ + LWT_ISO_NODE *node; + int n = 1; + + node = _lwt_GetIsoNode( topo, nid ); + if ( ! node ) return -1; + + n = lwt_be_deleteNodesById( topo, &nid, n ); + if ( n == -1 ) + { + lwfree(node); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if ( n != 1 ) + { + lwfree(node); + lwerror("Unexpected error: %d nodes deleted when expecting 1", n); + return -1; + } + + if ( ! lwt_be_checkTopoGeomRemIsoNode(topo, nid) ) + { + lwfree(node); + lwerror("%s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + lwfree(node); + return 0; /* success */ +} + +int +lwt_RemIsoEdge(LWT_TOPOLOGY* topo, LWT_ELEMID id) +{ + LWT_ISO_EDGE deledge; + LWT_ISO_EDGE *edge; + LWT_ELEMID nid[2]; + LWT_ISO_NODE upd_node[2]; + LWT_ELEMID containing_face; + uint64_t n = 1; + uint64_t i; + + edge = lwt_be_getEdgeById( topo, &id, &n, LWT_COL_EDGE_START_NODE| + LWT_COL_EDGE_END_NODE | + LWT_COL_EDGE_FACE_LEFT | + LWT_COL_EDGE_FACE_RIGHT ); + if ( ! edge ) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if ( ! n ) + { + lwerror("SQL/MM Spatial exception - non-existent edge"); + return -1; + } + if ( n > 1 ) + { + lwfree(edge); + lwerror("Corrupted topology: more than a single edge have id %" + LWTFMT_ELEMID, id); + return -1; + } + + if ( edge[0].face_left != edge[0].face_right ) + { + lwfree(edge); + lwerror("SQL/MM Spatial exception - not isolated edge"); + return -1; + } + containing_face = edge[0].face_left; + + nid[0] = edge[0].start_node; + nid[1] = edge[0].end_node; + lwfree(edge); + + n = 2; + edge = lwt_be_getEdgeByNode( topo, nid, &n, LWT_COL_EDGE_EDGE_ID ); + if ((n == UINT64_MAX) || (edge == NULL)) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + for (i = 0; i < n; ++i) + { + if (edge[i].edge_id != id) + { + lwfree(edge); + lwerror("SQL/MM Spatial exception - not isolated edge"); + return -1; + } + } + lwfree(edge); + + deledge.edge_id = id; + n = lwt_be_deleteEdges( topo, &deledge, LWT_COL_EDGE_EDGE_ID ); + if (n == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if ( n != 1 ) + { + lwerror("Unexpected error: %d edges deleted when expecting 1", n); + return -1; + } + + upd_node[0].node_id = nid[0]; + upd_node[0].containing_face = containing_face; + n = 1; + if ( nid[1] != nid[0] ) { + upd_node[1].node_id = nid[1]; + upd_node[1].containing_face = containing_face; + ++n; + } + n = lwt_be_updateNodesById(topo, upd_node, n, + LWT_COL_NODE_CONTAINING_FACE); + if (n == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + /* Check that the edge can be safely removed + * See https://trac.osgeo.org/postgis/ticket/3248 + */ + if ( ! lwt_be_checkTopoGeomRemIsoEdge(topo, id) ) + { + lwerror("%s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + return 0; /* success */ +} + +/* Used by _lwt_RemEdge to update edge face ref on healing + * + * @param of old face id (never 0 as you cannot remove face 0) + * @param nf new face id + * @return 0 on success, -1 on backend error + */ +static int +_lwt_UpdateEdgeFaceRef( LWT_TOPOLOGY *topo, LWT_ELEMID of, LWT_ELEMID nf) +{ + LWT_ISO_EDGE sel_edge, upd_edge; + int ret; + + assert( of != 0 ); + + /* Update face_left for all edges still referencing old face */ + sel_edge.face_left = of; + upd_edge.face_left = nf; + ret = lwt_be_updateEdges(topo, &sel_edge, LWT_COL_EDGE_FACE_LEFT, + &upd_edge, LWT_COL_EDGE_FACE_LEFT, + NULL, 0); + if ( ret == -1 ) return -1; + + /* Update face_right for all edges still referencing old face */ + sel_edge.face_right = of; + upd_edge.face_right = nf; + ret = lwt_be_updateEdges(topo, &sel_edge, LWT_COL_EDGE_FACE_RIGHT, + &upd_edge, LWT_COL_EDGE_FACE_RIGHT, + NULL, 0); + if ( ret == -1 ) return -1; + + return 0; +} + +/* Used by _lwt_RemEdge to update node face ref on healing + * + * @param of old face id (never 0 as you cannot remove face 0) + * @param nf new face id + * @return 0 on success, -1 on backend error + */ +static int +_lwt_UpdateNodeFaceRef( LWT_TOPOLOGY *topo, LWT_ELEMID of, LWT_ELEMID nf) +{ + LWT_ISO_NODE sel, upd; + int ret; + + assert( of != 0 ); + + /* Update face_left for all edges still referencing old face */ + sel.containing_face = of; + upd.containing_face = nf; + ret = lwt_be_updateNodes(topo, &sel, LWT_COL_NODE_CONTAINING_FACE, + &upd, LWT_COL_NODE_CONTAINING_FACE, + NULL, 0); + if ( ret == -1 ) return -1; + + return 0; +} + +/* Used by lwt_RemEdgeModFace and lwt_RemEdgeNewFaces + * + * Returns -1 on error, identifier of the face that takes up the space + * previously occupied by the removed edge if modFace is 1, identifier of + * the created face (0 if none) if modFace is 0. + */ +static LWT_ELEMID +_lwt_RemEdge( LWT_TOPOLOGY* topo, LWT_ELEMID edge_id, int modFace ) +{ + uint64_t i, nedges, nfaces, fields; + LWT_ISO_EDGE *edge = NULL; + LWT_ISO_EDGE *upd_edge = NULL; + LWT_ISO_EDGE upd_edge_left[2]; + int nedge_left = 0; + LWT_ISO_EDGE upd_edge_right[2]; + int nedge_right = 0; + LWT_ISO_NODE upd_node[2]; + int nnode = 0; + LWT_ISO_FACE *faces = NULL; + LWT_ISO_FACE newface; + LWT_ELEMID node_ids[2]; + LWT_ELEMID face_ids[2]; + int fnode_edges = 0; /* number of edges on the first node (excluded + * the one being removed ) */ + int lnode_edges = 0; /* number of edges on the last node (excluded + * the one being removed ) */ + + newface.face_id = 0; + + i = 1; + edge = lwt_be_getEdgeById(topo, &edge_id, &i, LWT_COL_EDGE_ALL); + if (!edge) + { + LWDEBUGF(1, "lwt_be_getEdgeById returned NULL and set i=%d", i); + if (i == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + else if (i == 0) + { + lwerror("SQL/MM Spatial exception - non-existent edge %" LWTFMT_ELEMID, edge_id); + return -1; + } + else + { + lwerror( + "Backend coding error: getEdgeById callback returned NULL " + "but numelements output parameter has value %d " + "(expected 0 or 1)", + i); + return -1; + } + } + + if ( ! lwt_be_checkTopoGeomRemEdge(topo, edge_id, + edge->face_left, edge->face_right) ) + { + lwerror("%s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + LWDEBUG(1, "Updating next_{right,left}_face of ring edges..."); + + /* Update edge linking */ + + nedges = 0; + node_ids[nedges++] = edge->start_node; + if ( edge->end_node != edge->start_node ) + { + node_ids[nedges++] = edge->end_node; + } + fields = LWT_COL_EDGE_EDGE_ID | LWT_COL_EDGE_START_NODE | + LWT_COL_EDGE_END_NODE | LWT_COL_EDGE_NEXT_LEFT | + LWT_COL_EDGE_NEXT_RIGHT; + upd_edge = lwt_be_getEdgeByNode( topo, &(node_ids[0]), &nedges, fields ); + if (nedges == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + nedge_left = nedge_right = 0; + for ( i=0; iedge_id == edge_id ) continue; + if ( e->start_node == edge->start_node || e->end_node == edge->start_node ) + { + ++fnode_edges; + } + if ( e->start_node == edge->end_node || e->end_node == edge->end_node ) + { + ++lnode_edges; + } + if ( e->next_left == -edge_id ) + { + upd_edge_left[nedge_left].edge_id = e->edge_id; + upd_edge_left[nedge_left++].next_left = + edge->next_left != edge_id ? edge->next_left : edge->next_right; + } + else if ( e->next_left == edge_id ) + { + upd_edge_left[nedge_left].edge_id = e->edge_id; + upd_edge_left[nedge_left++].next_left = + edge->next_right != -edge_id ? edge->next_right : edge->next_left; + } + + if ( e->next_right == -edge_id ) + { + upd_edge_right[nedge_right].edge_id = e->edge_id; + upd_edge_right[nedge_right++].next_right = + edge->next_left != edge_id ? edge->next_left : edge->next_right; + } + else if ( e->next_right == edge_id ) + { + upd_edge_right[nedge_right].edge_id = e->edge_id; + upd_edge_right[nedge_right++].next_right = + edge->next_right != -edge_id ? edge->next_right : edge->next_left; + } + } + + if ( nedge_left ) + { + LWDEBUGF(1, "updating %d 'next_left' edges", nedge_left); + /* update edges in upd_edge_left set next_left */ + int result = lwt_be_updateEdgesById(topo, &(upd_edge_left[0]), nedge_left, LWT_COL_EDGE_NEXT_LEFT); + if (result == -1) + { + _lwt_release_edges(edge, 1); + lwfree(upd_edge); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + } + if ( nedge_right ) + { + LWDEBUGF(1, "updating %d 'next_right' edges", nedge_right); + /* update edges in upd_edge_right set next_right */ + int result = lwt_be_updateEdgesById(topo, &(upd_edge_right[0]), nedge_right, LWT_COL_EDGE_NEXT_RIGHT); + if (result == -1) + { + _lwt_release_edges(edge, 1); + lwfree(upd_edge); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + } + LWDEBUGF(1, "releasing %d updateable edges in %p", nedges, upd_edge); + lwfree(upd_edge); + + /* Id of face that will take up all the space previously + * taken by left and right faces of the edge */ + LWT_ELEMID floodface; + + /* Find floodface, and update its mbr if != 0 */ + if ( edge->face_left == edge->face_right ) + { + floodface = edge->face_right; + } + else + { + /* Two faces healed */ + if ( edge->face_left == 0 || edge->face_right == 0 ) + { + floodface = 0; + LWDEBUG(1, "floodface is universe"); + } + else + { + /* we choose right face as the face that will remain + * to be symmetric with ST_AddEdgeModFace */ + floodface = edge->face_right; + LWDEBUGF(1, "floodface is %" LWTFMT_ELEMID, floodface); + /* update mbr of floodface as union of mbr of both faces */ + face_ids[0] = edge->face_left; + face_ids[1] = edge->face_right; + nfaces = 2; + fields = LWT_COL_FACE_ALL; + faces = lwt_be_getFaceById(topo, face_ids, &nfaces, fields); + if (nfaces == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + GBOX *box1=NULL; + GBOX *box2=NULL; + for ( i=0; iface_left ) + { + if ( ! box1 ) box1 = faces[i].mbr; + else + { + i = edge->face_left; + _lwt_release_edges(edge, 1); + _lwt_release_faces(faces, nfaces); + lwerror("corrupted topology: more than 1 face have face_id=%" + LWTFMT_ELEMID, i); + return -1; + } + } + else if ( faces[i].face_id == edge->face_right ) + { + if ( ! box2 ) box2 = faces[i].mbr; + else + { + i = edge->face_right; + _lwt_release_edges(edge, 1); + _lwt_release_faces(faces, nfaces); + lwerror("corrupted topology: more than 1 face have face_id=%" + LWTFMT_ELEMID, i); + return -1; + } + } + else + { + i = faces[i].face_id; + _lwt_release_edges(edge, 1); + _lwt_release_faces(faces, nfaces); + lwerror("Backend coding error: getFaceById returned face " + "with non-requested id %" LWTFMT_ELEMID, i); + return -1; + } + } + if ( ! box1 ) { + i = edge->face_left; + _lwt_release_edges(edge, 1); + if ( nfaces ) _lwt_release_faces(faces, nfaces); + lwerror("corrupted topology: no face have face_id=%" + LWTFMT_ELEMID " (left face for edge %" + LWTFMT_ELEMID ")", i, edge_id); + return -1; + } + if ( ! box2 ) { + i = edge->face_right; + _lwt_release_edges(edge, 1); + if ( nfaces ) _lwt_release_faces(faces, nfaces); + lwerror("corrupted topology: no face have face_id=%" + LWTFMT_ELEMID " (right face for edge %" + LWTFMT_ELEMID ")", i, edge_id); + return -1; + } + gbox_merge(box2, box1); /* box1 is now the union of the two */ + newface.mbr = box1; + if ( modFace ) + { + newface.face_id = floodface; + int result = lwt_be_updateFacesById(topo, &newface, 1); + _lwt_release_faces(faces, 2); + if (result == -1) + { + _lwt_release_edges(edge, 1); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if (result != 1) + { + _lwt_release_edges(edge, 1); + lwerror("Unexpected error: %d faces updated when expecting 1", i); + return -1; + } + } + else + { + /* New face replaces the old two faces */ + newface.face_id = -1; + int result = lwt_be_insertFaces(topo, &newface, 1); + _lwt_release_faces(faces, 2); + if (result == -1) + { + _lwt_release_edges(edge, 1); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if (result != 1) + { + _lwt_release_edges(edge, 1); + lwerror("Unexpected error: %d faces inserted when expecting 1", result); + return -1; + } + floodface = newface.face_id; + } + } + + /* Update face references for edges and nodes still referencing + * the removed face(s) */ + + if ( edge->face_left != floodface ) + { + if ( -1 == _lwt_UpdateEdgeFaceRef(topo, edge->face_left, floodface) ) + { + _lwt_release_edges(edge, 1); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if ( -1 == _lwt_UpdateNodeFaceRef(topo, edge->face_left, floodface) ) + { + _lwt_release_edges(edge, 1); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + } + + if ( edge->face_right != floodface ) + { + if ( -1 == _lwt_UpdateEdgeFaceRef(topo, edge->face_right, floodface) ) + { + _lwt_release_edges(edge, 1); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if ( -1 == _lwt_UpdateNodeFaceRef(topo, edge->face_right, floodface) ) + { + _lwt_release_edges(edge, 1); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + } + + /* Update topogeoms on heal */ + if ( ! lwt_be_updateTopoGeomFaceHeal(topo, + edge->face_right, edge->face_left, + floodface) ) + { + _lwt_release_edges(edge, 1); + lwerror("%s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + } /* two faces healed */ + + /* Delete the edge */ + int result = lwt_be_deleteEdges(topo, edge, LWT_COL_EDGE_EDGE_ID); + if (result == -1) + { + _lwt_release_edges(edge, 1); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + /* If any of the edge nodes remained isolated, set + * containing_face = floodface + */ + if ( ! fnode_edges ) + { + upd_node[nnode].node_id = edge->start_node; + upd_node[nnode].containing_face = floodface; + ++nnode; + } + if ( edge->end_node != edge->start_node && ! lnode_edges ) + { + upd_node[nnode].node_id = edge->end_node; + upd_node[nnode].containing_face = floodface; + ++nnode; + } + if ( nnode ) + { + int result = lwt_be_updateNodesById(topo, upd_node, nnode, LWT_COL_NODE_CONTAINING_FACE); + if (result == -1) + { + _lwt_release_edges(edge, 1); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + } + + if ( edge->face_left != edge->face_right ) + /* or there'd be no face to remove */ + { + LWT_ELEMID ids[2]; + int nids = 0; + if ( edge->face_right != floodface ) + ids[nids++] = edge->face_right; + if ( edge->face_left != floodface ) + ids[nids++] = edge->face_left; + int result = lwt_be_deleteFacesById(topo, ids, nids); + if (result == -1) + { + _lwt_release_edges(edge, 1); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + } + + _lwt_release_edges(edge, 1); + return modFace ? floodface : newface.face_id; +} + +LWT_ELEMID +lwt_RemEdgeModFace( LWT_TOPOLOGY* topo, LWT_ELEMID edge_id ) +{ + return _lwt_RemEdge( topo, edge_id, 1 ); +} + +LWT_ELEMID +lwt_RemEdgeNewFace( LWT_TOPOLOGY* topo, LWT_ELEMID edge_id ) +{ + return _lwt_RemEdge( topo, edge_id, 0 ); +} + +static LWT_ELEMID +_lwt_HealEdges( LWT_TOPOLOGY* topo, LWT_ELEMID eid1, LWT_ELEMID eid2, + int modEdge ) +{ + LWT_ELEMID ids[2]; + LWT_ELEMID commonnode = -1; + int caseno = 0; + LWT_ISO_EDGE *node_edges; + uint64_t num_node_edges; + LWT_ISO_EDGE *edges; + LWT_ISO_EDGE *e1 = NULL; + LWT_ISO_EDGE *e2 = NULL; + LWT_ISO_EDGE newedge, updedge, seledge; + uint64_t nedges, i; + int e1freenode; + int e2sign, e2freenode; + POINTARRAY *pa; + char buf[256]; + char *ptr; + size_t bufleft = 256; + + ptr = buf; + + /* NOT IN THE SPECS: see if the same edge is given twice.. */ + if ( eid1 == eid2 ) + { + lwerror("Cannot heal edge %" LWTFMT_ELEMID + " with itself, try with another", eid1); + return -1; + } + ids[0] = eid1; + ids[1] = eid2; + nedges = 2; + edges = lwt_be_getEdgeById(topo, ids, &nedges, LWT_COL_EDGE_ALL); + if ((nedges == UINT64_MAX) || (edges == NULL)) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + for ( i=0; istart_node == e1->end_node ) + { + _lwt_release_edges(edges, nedges); + lwerror("Edge %" LWTFMT_ELEMID " is closed, cannot heal to edge %" + LWTFMT_ELEMID, eid1, eid2); + return -1; + } + if ( e2->start_node == e2->end_node ) + { + _lwt_release_edges(edges, nedges); + lwerror("Edge %" LWTFMT_ELEMID " is closed, cannot heal to edge %" + LWTFMT_ELEMID, eid2, eid1); + return -1; + } + + /* Find common node */ + + if ( e1->end_node == e2->start_node ) + { + commonnode = e1->end_node; + caseno = 1; + } + else if ( e1->end_node == e2->end_node ) + { + commonnode = e1->end_node; + caseno = 2; + } + /* Check if any other edge is connected to the common node, if found */ + if ( commonnode != -1 ) + { + num_node_edges = 1; + node_edges = lwt_be_getEdgeByNode( topo, &commonnode, + &num_node_edges, LWT_COL_EDGE_EDGE_ID ); + if (num_node_edges == UINT64_MAX) + { + _lwt_release_edges(edges, nedges); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + for (i=0; i 0 ) { + r = snprintf(ptr, bufleft, "%s%" LWTFMT_ELEMID, + ( ptr==buf ? "" : "," ), node_edges[i].edge_id); + if ( r >= (int) bufleft ) + { + bufleft = 0; + buf[252] = '.'; + buf[253] = '.'; + buf[254] = '.'; + buf[255] = '\0'; + } + else + { + bufleft -= r; + ptr += r; + } + } + } + lwfree(node_edges); + } + + if ( commonnode == -1 ) + { + if ( e1->start_node == e2->start_node ) + { + commonnode = e1->start_node; + caseno = 3; + } + else if ( e1->start_node == e2->end_node ) + { + commonnode = e1->start_node; + caseno = 4; + } + /* Check if any other edge is connected to the common node, if found */ + if ( commonnode != -1 ) + { + num_node_edges = 1; + node_edges = lwt_be_getEdgeByNode( topo, &commonnode, + &num_node_edges, LWT_COL_EDGE_EDGE_ID ); + if (num_node_edges == UINT64_MAX) + { + _lwt_release_edges(edges, nedges); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + for (i=0; i 0 ) { + r = snprintf(ptr, bufleft, "%s%" LWTFMT_ELEMID, + ( ptr==buf ? "" : "," ), node_edges[i].edge_id); + if ( r >= (int) bufleft ) + { + bufleft = 0; + buf[252] = '.'; + buf[253] = '.'; + buf[254] = '.'; + buf[255] = '\0'; + } + else + { + bufleft -= r; + ptr += r; + } + } + } + if ( num_node_edges ) lwfree(node_edges); + } + } + + if ( commonnode == -1 ) + { + _lwt_release_edges(edges, nedges); + if ( ptr != buf ) + { + lwerror("SQL/MM Spatial exception - other edges connected (%s)", + buf); + } + else + { + lwerror("SQL/MM Spatial exception - non-connected edges"); + } + return -1; + } + + if ( ! lwt_be_checkTopoGeomRemNode(topo, commonnode, + eid1, eid2 ) ) + { + _lwt_release_edges(edges, nedges); + lwerror("%s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + /* Construct the geometry of the new edge */ + switch (caseno) + { + case 1: /* e1.end = e2.start */ + pa = ptarray_clone_deep(e1->geom->points); + //pa = ptarray_merge(pa, e2->geom->points); + ptarray_append_ptarray(pa, e2->geom->points, 0); + newedge.start_node = e1->start_node; + newedge.end_node = e2->end_node; + newedge.next_left = e2->next_left; + newedge.next_right = e1->next_right; + e1freenode = 1; + e2freenode = -1; + e2sign = 1; + break; + case 2: /* e1.end = e2.end */ + { + POINTARRAY *pa2; + pa2 = ptarray_clone_deep(e2->geom->points); + ptarray_reverse_in_place(pa2); + pa = ptarray_clone_deep(e1->geom->points); + //pa = ptarray_merge(e1->geom->points, pa); + ptarray_append_ptarray(pa, pa2, 0); + ptarray_free(pa2); + newedge.start_node = e1->start_node; + newedge.end_node = e2->start_node; + newedge.next_left = e2->next_right; + newedge.next_right = e1->next_right; + e1freenode = 1; + e2freenode = 1; + e2sign = -1; + break; + } + case 3: /* e1.start = e2.start */ + pa = ptarray_clone_deep(e2->geom->points); + ptarray_reverse_in_place(pa); + //pa = ptarray_merge(pa, e1->geom->points); + ptarray_append_ptarray(pa, e1->geom->points, 0); + newedge.end_node = e1->end_node; + newedge.start_node = e2->end_node; + newedge.next_left = e1->next_left; + newedge.next_right = e2->next_left; + e1freenode = -1; + e2freenode = -1; + e2sign = -1; + break; + case 4: /* e1.start = e2.end */ + pa = ptarray_clone_deep(e2->geom->points); + //pa = ptarray_merge(pa, e1->geom->points); + ptarray_append_ptarray(pa, e1->geom->points, 0); + newedge.end_node = e1->end_node; + newedge.start_node = e2->start_node; + newedge.next_left = e1->next_left; + newedge.next_right = e2->next_right; + e1freenode = -1; + e2freenode = 1; + e2sign = 1; + break; + default: + pa = NULL; + e1freenode = 0; + e2freenode = 0; + e2sign = 0; + _lwt_release_edges(edges, nedges); + lwerror("Coding error: caseno=%d should never happen", caseno); + return -1; + break; + } + newedge.geom = lwline_construct(topo->srid, NULL, pa); + + if ( modEdge ) + { + /* Update data of the first edge */ + newedge.edge_id = eid1; + int result = lwt_be_updateEdgesById(topo, + &newedge, + 1, + LWT_COL_EDGE_NEXT_LEFT | LWT_COL_EDGE_NEXT_RIGHT | LWT_COL_EDGE_START_NODE | + LWT_COL_EDGE_END_NODE | LWT_COL_EDGE_GEOM); + if (result == -1) + { + lwline_free(newedge.geom); + _lwt_release_edges(edges, nedges); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + else if (result != 1) + { + lwline_free(newedge.geom); + if ( edges ) _lwt_release_edges(edges, nedges); + lwerror("Unexpected error: %d edges updated when expecting 1", i); + return -1; + } + } + else + { + /* Add new edge */ + newedge.edge_id = -1; + newedge.face_left = e1->face_left; + newedge.face_right = e1->face_right; + int result = lwt_be_insertEdges(topo, &newedge, 1); + if (result == -1) + { + lwline_free(newedge.geom); + _lwt_release_edges(edges, nedges); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + else if (result == 0) + { + lwline_free(newedge.geom); + _lwt_release_edges(edges, nedges); + lwerror("Insertion of split edge failed (no reason)"); + return -1; + } + } + lwline_free(newedge.geom); + + /* + -- Update next_left_edge/next_right_edge for + -- any edge having them still pointing at the edge being removed + -- (eid2 only when modEdge, or both otherwise) + -- + -- NOTE: + -- e#freenode is 1 when edge# end node was the common node + -- and -1 otherwise. This gives the sign of possibly found references + -- to its "free" (non connected to other edge) endnode. + -- e2sign is -1 if edge1 direction is opposite to edge2 direction, + -- or 1 otherwise. + -- + */ + + /* update edges connected to e2's boundary from their end node */ + seledge.next_left = e2freenode * eid2; + updedge.next_left = e2freenode * newedge.edge_id * e2sign; + int result = lwt_be_updateEdges(topo, &seledge, LWT_COL_EDGE_NEXT_LEFT, &updedge, LWT_COL_EDGE_NEXT_LEFT, NULL, 0); + if (result == -1) + { + _lwt_release_edges(edges, nedges); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + /* update edges connected to e2's boundary from their start node */ + seledge.next_right = e2freenode * eid2; + updedge.next_right = e2freenode * newedge.edge_id * e2sign; + result = lwt_be_updateEdges(topo, &seledge, LWT_COL_EDGE_NEXT_RIGHT, &updedge, LWT_COL_EDGE_NEXT_RIGHT, NULL, 0); + if (result == -1) + { + _lwt_release_edges(edges, nedges); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + if ( ! modEdge ) + { + /* update edges connected to e1's boundary from their end node */ + seledge.next_left = e1freenode * eid1; + updedge.next_left = e1freenode * newedge.edge_id; + result = lwt_be_updateEdges(topo, &seledge, LWT_COL_EDGE_NEXT_LEFT, &updedge, LWT_COL_EDGE_NEXT_LEFT, NULL, 0); + if (result == -1) + { + _lwt_release_edges(edges, nedges); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + /* update edges connected to e1's boundary from their start node */ + seledge.next_right = e1freenode * eid1; + updedge.next_right = e1freenode * newedge.edge_id; + result = lwt_be_updateEdges(topo, &seledge, LWT_COL_EDGE_NEXT_RIGHT, &updedge, LWT_COL_EDGE_NEXT_RIGHT, NULL, 0); + if (result == -1) + { + _lwt_release_edges(edges, nedges); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + } + + /* delete the edges (only second on modEdge or both) */ + result = lwt_be_deleteEdges(topo, e2, LWT_COL_EDGE_EDGE_ID); + if (result == -1) + { + _lwt_release_edges(edges, nedges); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if ( ! modEdge ) { + i = lwt_be_deleteEdges(topo, e1, LWT_COL_EDGE_EDGE_ID); + if (result == -1) + { + _lwt_release_edges(edges, nedges); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + } + + _lwt_release_edges(edges, nedges); + + /* delete the common node */ + i = lwt_be_deleteNodesById( topo, &commonnode, 1 ); + if (result == -1) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + /* + -- + -- NOT IN THE SPECS: + -- Drop composition rows involving second + -- edge, as the first edge took its space, + -- and all affected TopoGeom have been previously checked + -- for being composed by both edges. + */ + if ( ! lwt_be_updateTopoGeomEdgeHeal(topo, + eid1, eid2, newedge.edge_id) ) + { + lwerror("%s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + return modEdge ? commonnode : newedge.edge_id; +} + +LWT_ELEMID +lwt_ModEdgeHeal( LWT_TOPOLOGY* topo, LWT_ELEMID e1, LWT_ELEMID e2 ) +{ + return _lwt_HealEdges( topo, e1, e2, 1 ); +} + +LWT_ELEMID +lwt_NewEdgeHeal( LWT_TOPOLOGY* topo, LWT_ELEMID e1, LWT_ELEMID e2 ) +{ + return _lwt_HealEdges( topo, e1, e2, 0 ); +} + +LWT_ELEMID +lwt_GetNodeByPoint(LWT_TOPOLOGY *topo, LWPOINT *pt, double tol) +{ + LWT_ISO_NODE *elem; + uint64_t num; + int flds = LWT_COL_NODE_NODE_ID|LWT_COL_NODE_GEOM; /* geom not needed */ + LWT_ELEMID id = 0; + POINT2D qp; /* query point */ + + if ( ! getPoint2d_p(pt->point, 0, &qp) ) + { + lwerror("Empty query point"); + return -1; + } + elem = lwt_be_getNodeWithinDistance2D(topo, pt, tol, &num, flds, 0); + if (num == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + else if ( num ) + { + if ( num > 1 ) + { + _lwt_release_nodes(elem, num); + lwerror("Two or more nodes found"); + return -1; + } + id = elem[0].node_id; + _lwt_release_nodes(elem, num); + } + + return id; +} + +LWT_ELEMID +lwt_GetEdgeByPoint(LWT_TOPOLOGY *topo, LWPOINT *pt, double tol) +{ + LWT_ISO_EDGE *elem; + uint64_t num, i; + int flds = LWT_COL_EDGE_EDGE_ID|LWT_COL_EDGE_GEOM; /* GEOM is not needed */ + LWT_ELEMID id = 0; + LWGEOM *qp = lwpoint_as_lwgeom(pt); /* query point */ + + if ( lwgeom_is_empty(qp) ) + { + lwerror("Empty query point"); + return -1; + } + elem = lwt_be_getEdgeWithinDistance2D(topo, pt, tol, &num, flds, 0); + if (num == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + for (i=0; igeom ) + { + _lwt_release_edges(elem, num); + lwnotice("Corrupted topology: edge %" LWTFMT_ELEMID + " has null geometry", e->edge_id); + continue; + } + + /* Should we check for intersection not being on an endpoint + * as documented ? */ + geom = lwline_as_lwgeom(e->geom); + dist = lwgeom_mindistance2d_tolerance(geom, qp, tol); + if ( dist > tol ) continue; +#endif + + if ( id ) + { + _lwt_release_edges(elem, num); + lwerror("Two or more edges found"); + return -1; + } + else id = e->edge_id; + } + + if ( num ) _lwt_release_edges(elem, num); + + return id; +} + +LWT_ELEMID +lwt_GetFaceByPoint(LWT_TOPOLOGY *topo, const LWPOINT *pt, double tol) +{ + LWT_ELEMID id = 0; + LWT_ISO_EDGE *elem; + uint64_t num, i; + int flds = LWT_COL_EDGE_EDGE_ID | + LWT_COL_EDGE_GEOM | + LWT_COL_EDGE_FACE_LEFT | + LWT_COL_EDGE_FACE_RIGHT; + LWGEOM *qp = lwpoint_as_lwgeom(pt); + + id = lwt_GetFaceContainingPoint(topo, pt); + if ( id == -1 ) { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + + if ( id > 0 ) + { + return id; + } + + if ( tol == 0 ) + { + return id; + } + + LWDEBUG(1, "No face properly contains query point," + " looking for edges"); + + /* Not in a face, may be in universe or on edge, let's check + * for distance */ + /* NOTE: we never pass a tolerance of 0 to avoid ever using + * ST_Within, which doesn't include endpoints matches */ + elem = lwt_be_getEdgeWithinDistance2D(topo, pt, tol?tol:1e-5, &num, flds, 0); + if (num == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + for (i=0; igeom ) + { + _lwt_release_edges(elem, num); + lwnotice("Corrupted topology: edge %" LWTFMT_ELEMID + " has null geometry", e->edge_id); + continue; + } + + /* don't consider dangling edges */ + if ( e->face_left == e->face_right ) + { + LWDEBUGF(1, "Edge %" LWTFMT_ELEMID + " is dangling, won't consider it", e->edge_id); + continue; + } + + geom = lwline_as_lwgeom(e->geom); + dist = lwgeom_mindistance2d_tolerance(geom, qp, tol); + + LWDEBUGF(1, "Distance from edge %" LWTFMT_ELEMID + " is %g (tol=%g)", e->edge_id, dist, tol); + + /* we won't consider edges too far */ + if ( dist > tol ) continue; + if ( e->face_left == 0 ) { + eface = e->face_right; + } + else if ( e->face_right == 0 ) { + eface = e->face_left; + } + else { + _lwt_release_edges(elem, num); + lwerror("Two or more faces found"); + return -1; + } + + if ( id && id != eface ) + { + _lwt_release_edges(elem, num); + lwerror("Two or more faces found" +#if 0 /* debugging */ + " (%" LWTFMT_ELEMID + " and %" LWTFMT_ELEMID ")", id, eface +#endif + ); + return -1; + } + else id = eface; + } + if ( num ) _lwt_release_edges(elem, num); + + return id; +} + +/* Return the smallest delta that can perturbate + * the given value */ +static inline double +_lwt_minToleranceDouble( double d ) +{ + double ret = 3.6 * pow(10, - ( 15 - log10(d?d:1.0) ) ); + return ret; +} + +/* Return the smallest delta that can perturbate + * the given point +static inline double +_lwt_minTolerancePoint2d( const POINT2D* p ) +{ + double max = FP_ABS(p->x); + if ( max < FP_ABS(p->y) ) max = FP_ABS(p->y); + return _lwt_minToleranceDouble(max); +} +*/ + +/* Return the smallest delta that can perturbate + * the maximum absolute value of a geometry ordinate + */ +static double +_lwt_minTolerance( LWGEOM *g ) +{ + const GBOX* gbox; + double max; + double ret; + + gbox = lwgeom_get_bbox(g); + if ( ! gbox ) return 0; /* empty */ + max = FP_ABS(gbox->xmin); + if ( max < FP_ABS(gbox->xmax) ) max = FP_ABS(gbox->xmax); + if ( max < FP_ABS(gbox->ymin) ) max = FP_ABS(gbox->ymin); + if ( max < FP_ABS(gbox->ymax) ) max = FP_ABS(gbox->ymax); + + ret = _lwt_minToleranceDouble(max); + + return ret; +} + +#define _LWT_MINTOLERANCE( topo, geom ) ( \ + topo->precision ? topo->precision : _lwt_minTolerance(geom) ) + +typedef struct scored_pointer_t { + void *ptr; + double score; +} scored_pointer; + +static int +compare_scored_pointer(const void *si1, const void *si2) +{ + double a = ((scored_pointer *)si1)->score; + double b = ((scored_pointer *)si2)->score; + if ( a < b ) + return -1; + else if ( a > b ) + return 1; + else + return 0; +} + +/* + * @param findFace if non-zero the code will determine which face + * contains the given point (unless it is known to be NOT + * isolated) + * @param moved if not-null will be set to 0 if the point was added + * w/out any snapping or 1 otherwise. + */ +static LWT_ELEMID +_lwt_AddPoint(LWT_TOPOLOGY* topo, LWPOINT* point, double tol, int + findFace, int *moved) +{ + uint64_t num, i; + double mindist = FLT_MAX; + LWT_ISO_NODE *nodes, *nodes2; + LWT_ISO_EDGE *edges, *edges2; + LWGEOM *pt = lwpoint_as_lwgeom(point); + int flds; + LWT_ELEMID id = 0; + scored_pointer *sorted; + + /* Get tolerance, if 0 was given */ + if (!tol) + tol = _LWT_MINTOLERANCE(topo, pt); + + LWDEBUGG(1, pt, "Adding point"); + + /* + -- 1. Check if any existing node is closer than the given precision + -- and if so pick the closest + TODO: use WithinBox2D + */ + flds = LWT_COL_NODE_NODE_ID | LWT_COL_NODE_GEOM; + nodes = lwt_be_getNodeWithinDistance2D(topo, point, tol, &num, flds, 0); + if (num == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if ( num ) + { + LWDEBUGF(1, "New point is within %.15g units of %d nodes", tol, num); + /* Order by distance if there are more than a single return */ + if ( num > 1 ) + {{ + sorted= lwalloc(sizeof(scored_pointer)*num); + for (i=0; inode_id, sorted[i].score); + } + qsort(sorted, num, sizeof(scored_pointer), compare_scored_pointer); + nodes2 = lwalloc(sizeof(LWT_ISO_NODE)*num); + for (i=0; igeom); + double dist = lwgeom_mindistance2d(g, pt); + /* TODO: move this check in the previous sort scan ... */ + /* must be closer than tolerated, unless distance is zero */ + if ( dist && dist >= tol ) continue; + if ( ! id || dist < mindist ) + { + id = n->node_id; + mindist = dist; + } + } + if ( id ) + { + /* found an existing node */ + if ( nodes ) _lwt_release_nodes(nodes, num); + if ( moved ) *moved = mindist == 0 ? 0 : 1; + return id; + } + } + + initGEOS(lwnotice, lwgeom_geos_error); + + /* + -- 2. Check if any existing edge falls within tolerance + -- and if so split it by a point projected on it + TODO: use WithinBox2D + */ + flds = LWT_COL_EDGE_EDGE_ID|LWT_COL_EDGE_GEOM; + edges = lwt_be_getEdgeWithinDistance2D(topo, point, tol, &num, flds, 0); + if (num == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if ( num ) + { + LWDEBUGF(1, "New point is within %.15g units of %d edges", tol, num); + + /* Order by distance if there are more than a single return */ + if ( num > 1 ) + {{ + int j; + sorted = lwalloc(sizeof(scored_pointer)*num); + for (i=0; iedge_id, sorted[i].score); + } + qsort(sorted, num, sizeof(scored_pointer), compare_scored_pointer); + edges2 = lwalloc(sizeof(LWT_ISO_EDGE)*num); + for (j=0, i=0; igeom); + } + } + num = j; + lwfree(sorted); + lwfree(edges); + edges = edges2; + }} + + for (i=0; igeom); + LWGEOM *prj; + int contains; + LWT_ELEMID edge_id = e->edge_id; + + LWDEBUGF(1, "Splitting edge %" LWTFMT_ELEMID, edge_id); + + /* project point to line, split edge by point */ + prj = lwgeom_closest_point(g, pt); + if ( moved ) *moved = lwgeom_same(prj,pt) ? 0 : 1; + if ( lwgeom_has_z(pt) ) + {{ + /* + -- This is a workaround for ClosestPoint lack of Z support: + -- http://trac.osgeo.org/postgis/ticket/2033 + */ + LWGEOM *tmp; + double z; + POINT4D p4d; + LWPOINT *prjpt; + /* add Z to "prj" */ + tmp = lwgeom_force_3dz(prj, 0); + prjpt = lwgeom_as_lwpoint(tmp); + getPoint4d_p(point->point, 0, &p4d); + z = p4d.z; + getPoint4d_p(prjpt->point, 0, &p4d); + p4d.z = z; + ptarray_set_point4d(prjpt->point, 0, &p4d); + lwgeom_free(prj); + prj = tmp; + }} + const POINT2D *pt = getPoint2d_cp(lwgeom_as_lwpoint(prj)->point, 0); + contains = ptarray_contains_point_partial(e->geom->points, pt, 0, NULL) == LW_BOUNDARY; + if ( ! contains ) + {{ + double snaptol; + LWGEOM *snapedge; + LWLINE *snapline; + POINT4D p1, p2; + + LWDEBUGF(1, "Edge %" LWTFMT_ELEMID + " does not contain projected point to it", + edge_id); + + /* In order to reduce the robustness issues, we'll pick + * an edge that contains the projected point, if possible */ + if ( i+1 < num ) + { + LWDEBUG(1, "But there's another to check"); + lwgeom_free(prj); + continue; + } + + /* + -- The tolerance must be big enough for snapping to happen + -- and small enough to snap only to the projected point. + -- Unfortunately ST_Distance returns 0 because it also uses + -- a projected point internally, so we need another way. + */ + snaptol = _lwt_minTolerance(prj); + snapedge = _lwt_toposnap(g, prj, snaptol); + snapline = lwgeom_as_lwline(snapedge); + + LWDEBUGF(1, "Edge snapped with tolerance %g", snaptol); + + /* TODO: check if snapping did anything ? */ +#if POSTGIS_DEBUG_LEVEL > 0 + { + size_t sz; + char *wkt1 = lwgeom_to_wkt(g, WKT_EXTENDED, 15, &sz); + char *wkt2 = lwgeom_to_wkt(snapedge, WKT_EXTENDED, 15, &sz); + LWDEBUGF(1, "Edge %s snapped became %s", wkt1, wkt2); + lwfree(wkt1); + lwfree(wkt2); + } +#endif + + + /* + -- Snapping currently snaps the first point below tolerance + -- so may possibly move first point. See ticket #1631 + */ + getPoint4d_p(e->geom->points, 0, &p1); + getPoint4d_p(snapline->points, 0, &p2); + LWDEBUGF(1, "Edge first point is %g %g, " + "snapline first point is %g %g", + p1.x, p1.y, p2.x, p2.y); + if ( p1.x != p2.x || p1.y != p2.y ) + { + LWDEBUG(1, "Snapping moved first point, re-adding it"); + if ( LW_SUCCESS != ptarray_insert_point(snapline->points, &p1, 0) ) + { + lwgeom_free(prj); + lwgeom_free(snapedge); + _lwt_release_edges(edges, num); + lwerror("GEOS exception on Contains: %s", lwgeom_geos_errmsg); + return -1; + } +#if POSTGIS_DEBUG_LEVEL > 0 + { + size_t sz; + char *wkt1 = lwgeom_to_wkt(g, WKT_EXTENDED, 15, &sz); + LWDEBUGF(1, "Tweaked snapline became %s", wkt1); + lwfree(wkt1); + } +#endif + } +#if POSTGIS_DEBUG_LEVEL > 0 + else { + LWDEBUG(1, "Snapping did not move first point"); + } +#endif + + if ( -1 == lwt_ChangeEdgeGeom( topo, edge_id, snapline ) ) + { + /* TODO: should have invoked lwerror already, leaking memory */ + lwgeom_free(prj); + lwgeom_free(snapedge); + _lwt_release_edges(edges, num); + lwerror("lwt_ChangeEdgeGeom failed"); + return -1; + } + lwgeom_free(snapedge); + }} +#if POSTGIS_DEBUG_LEVEL > 0 + else + {{ + size_t sz; + char *wkt1 = lwgeom_to_wkt(g, WKT_EXTENDED, 15, &sz); + char *wkt2 = lwgeom_to_wkt(prj, WKT_EXTENDED, 15, &sz); + LWDEBUGF(1, "Edge %s contains projected point %s", wkt1, wkt2); + lwfree(wkt1); + lwfree(wkt2); + }} +#endif + + /* TODO: pass 1 as last argument (skipChecks) ? */ + id = lwt_ModEdgeSplit( topo, edge_id, lwgeom_as_lwpoint(prj), 0 ); + if ( -1 == id ) + { + /* TODO: should have invoked lwerror already, leaking memory */ + lwgeom_free(prj); + _lwt_release_edges(edges, num); + lwerror("lwt_ModEdgeSplit failed"); + return -1; + } + + lwgeom_free(prj); + + /* + * TODO: decimate the two new edges with the given tolerance ? + * + * the edge identifiers to decimate would be: edge_id and "id" + * The problem here is that decimation of existing edges + * may introduce intersections or topological inconsistencies, + * for example: + * + * - A node may end up falling on the other side of the edge + * - The decimated edge might intersect another existing edge + * + */ + + break; /* we only want to snap a single edge */ + } + _lwt_release_edges(edges, num); + } + else + { + /* The point is isolated, add it as such */ + /* TODO: pass 1 as last argument (skipChecks) ? */ + id = _lwt_AddIsoNode(topo, -1, point, 0, findFace); + if ( moved ) *moved = 0; + if ( -1 == id ) + { + /* should have invoked lwerror already, leaking memory */ + lwerror("lwt_AddIsoNode failed"); + return -1; + } + } + + return id; +} + +LWT_ELEMID +lwt_AddPoint(LWT_TOPOLOGY* topo, LWPOINT* point, double tol) +{ + return _lwt_AddPoint(topo, point, tol, 1, NULL); +} + +/* Return identifier of an equal edge, 0 if none or -1 on error + * (and lwerror gets called on error) + * + * If an equal edge is found, specify, in "forward" variable whether + * the edge is also equal direction-wise + * + */ +static LWT_ELEMID +_lwt_GetEqualEdge( LWT_TOPOLOGY *topo, LWLINE *edge, int *forward ) +{ + LWT_ELEMID id; + LWT_ISO_EDGE *edges; + uint64_t num, i; + const GBOX *qbox = lwgeom_get_bbox( lwline_as_lwgeom(edge) ); + GEOSGeometry *edgeg; + const int flds = LWT_COL_EDGE_EDGE_ID|LWT_COL_EDGE_GEOM; + + edges = lwt_be_getEdgeWithinBox2D( topo, qbox, &num, flds, 0 ); + if (num == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if ( num ) + { + initGEOS(lwnotice, lwgeom_geos_error); + + edgeg = LWGEOM2GEOS( lwline_as_lwgeom(edge), 0 ); + if ( ! edgeg ) + { + _lwt_release_edges(edges, num); + lwerror("Could not convert edge geometry to GEOS: %s", lwgeom_geos_errmsg); + return -1; + } + for (i=0; igeom); + GEOSGeometry *gg; + int equals; + gg = LWGEOM2GEOS( g, 0 ); + if ( ! gg ) + { + GEOSGeom_destroy(edgeg); + _lwt_release_edges(edges, num); + lwerror("Could not convert edge geometry to GEOS: %s", lwgeom_geos_errmsg); + return -1; + } + equals = GEOSEquals(gg, edgeg); + GEOSGeom_destroy(gg); + if ( equals == 2 ) + { + GEOSGeom_destroy(edgeg); + _lwt_release_edges(edges, num); + lwerror("GEOSEquals exception: %s", lwgeom_geos_errmsg); + return -1; + } + if ( equals ) + { + id = e->edge_id; + /* Check if direction also matches */ + if ( forward ) + { + /* If input line is closed, we use winding order */ + if ( lwline_is_closed(edge) ) + { + if ( ptarray_isccw(edge->points) == ptarray_isccw(e->geom->points) ) + { + *forward = 1; + } + else + { + *forward = 0; + } + } + else + { + /* Input line is not closed, checking fist point is enough */ + if ( + memcmp( + getPoint_internal(edge->points, 0), + getPoint_internal(e->geom->points, 0), + sizeof(POINT2D) + ) == 0 + ) + { + *forward = 1; + } + else + { + *forward = 0; + } + } + } + GEOSGeom_destroy(edgeg); + _lwt_release_edges(edges, num); + return id; + } + } + GEOSGeom_destroy(edgeg); + _lwt_release_edges(edges, num); + } + + return 0; +} + +/* + * Add a pre-noded pre-split line edge. Used by lwt_AddLine + * Return edge id, 0 if none added (empty edge), -1 on error + * + * @param handleFaceSplit if non-zero the code will check + * if the newly added edge would split a face and if so + * would create new faces accordingly. Otherwise it will + * set left_face and right_face to null (-1) + * + * @param forward output parameter, will be populated if + * a pre-existing edge was found in the topology, + * in which case a value of 1 means the incoming + * line will have the same direction of the edge, + * and 0 that the incomine line has opposite direction + */ +static LWT_ELEMID +_lwt_AddLineEdge( LWT_TOPOLOGY* topo, LWLINE* edge, double tol, + int handleFaceSplit, int *forward ) +{ + LWCOLLECTION *col; + LWPOINT *start_point, *end_point; + LWGEOM *tmp = 0, *tmp2; + LWT_ISO_NODE *node; + LWT_ELEMID nid[2]; /* start_node, end_node */ + LWT_ELEMID id; /* edge id */ + POINT4D p4d; + uint64_t nn, i; + int moved=0, mm; + + LWDEBUGG(1, lwline_as_lwgeom(edge), "_lwtAddLineEdge"); + LWDEBUGF(1, "_lwtAddLineEdge with tolerance %g", tol); + + start_point = lwline_get_lwpoint(edge, 0); + if ( ! start_point ) + { + lwnotice("Empty component of noded line"); + return 0; /* must be empty */ + } + nid[0] = _lwt_AddPoint( topo, start_point, + _lwt_minTolerance(lwpoint_as_lwgeom(start_point)), + handleFaceSplit, &mm ); + lwpoint_free(start_point); /* too late if lwt_AddPoint calls lwerror */ + if ( nid[0] == -1 ) return -1; /* lwerror should have been called */ + moved += mm; + + + end_point = lwline_get_lwpoint(edge, edge->points->npoints-1); + if ( ! end_point ) + { + lwerror("could not get last point of line " + "after successfully getting first point !?"); + return -1; + } + nid[1] = _lwt_AddPoint( topo, end_point, + _lwt_minTolerance(lwpoint_as_lwgeom(end_point)), + handleFaceSplit, &mm ); + moved += mm; + lwpoint_free(end_point); /* too late if lwt_AddPoint calls lwerror */ + if ( nid[1] == -1 ) return -1; /* lwerror should have been called */ + + /* + -- Added endpoints may have drifted due to tolerance, so + -- we need to re-snap the edge to the new nodes before adding it + */ + if ( moved ) + { + + nn = nid[0] == nid[1] ? 1 : 2; + node = lwt_be_getNodeById( topo, nid, &nn, + LWT_COL_NODE_NODE_ID|LWT_COL_NODE_GEOM ); + if (nn == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + start_point = NULL; end_point = NULL; + for (i=0; ipoint, 0, &p4d ); + lwline_setPoint4d(edge, 0, &p4d); + + getPoint4d_p( end_point->point, 0, &p4d ); + lwline_setPoint4d(edge, edge->points->npoints-1, &p4d); + + if ( nn ) _lwt_release_nodes(node, nn); + + /* make valid, after snap (to handle collapses) */ + tmp = lwgeom_make_valid(lwline_as_lwgeom(edge)); + + col = lwgeom_as_lwcollection(tmp); + if ( col ) + {{ + + LWCOLLECTION *colex = lwcollection_extract(col, LINETYPE); + + /* Check if the so-snapped edge collapsed (see #1650) */ + if ( colex->ngeoms == 0 ) + { + lwcollection_free(colex); + lwgeom_free(tmp); + LWDEBUG(1, "Made-valid snapped edge collapsed"); + return 0; + } + + tmp2 = lwgeom_clone_deep(colex->geoms[0]); + lwgeom_free(tmp); + tmp = tmp2; + edge = lwgeom_as_lwline(tmp); + lwcollection_free(colex); + if ( ! edge ) + { + /* should never happen */ + lwerror("lwcollection_extract(LINETYPE) returned a non-line?"); + return -1; + } + }} + else + { + edge = lwgeom_as_lwline(tmp); + if ( ! edge ) + { + LWDEBUGF(1, "Made-valid snapped edge collapsed to %s", + lwtype_name(lwgeom_get_type(tmp))); + lwgeom_free(tmp); + return 0; + } + } + } + + /* check if the so-snapped edge _now_ exists */ + id = _lwt_GetEqualEdge ( topo, edge, forward ); + LWDEBUGF(1, "_lwt_GetEqualEdge returned %" LWTFMT_ELEMID, id); + if ( id == -1 ) + { + if ( tmp ) lwgeom_free(tmp); /* probably too late, due to internal lwerror */ + return -1; + } + if ( id ) + { + if ( tmp ) lwgeom_free(tmp); /* possibly takes "edge" down with it */ + return id; + } + + /* No previously existing edge was found, we'll add one */ + + /* Remove consecutive vertices below given tolerance + * on edge addition */ + if ( tol ) + {{ + tmp2 = lwline_remove_repeated_points(edge, tol); + LWDEBUGG(1, tmp2, "Repeated-point removed"); + edge = lwgeom_as_lwline(tmp2); + if ( tmp ) lwgeom_free(tmp); + tmp = tmp2; + + /* check if the so-decimated edge collapsed to single-point */ + if ( nid[0] == nid[1] && edge->points->npoints == 2 ) + { + lwgeom_free(tmp); + LWDEBUG(1, "Repeated-point removed edge collapsed"); + return 0; + } + + /* check if the so-decimated edge _now_ exists */ + id = _lwt_GetEqualEdge ( topo, edge, forward ); + LWDEBUGF(1, "_lwt_GetEqualEdge returned %" LWTFMT_ELEMID, id); + if ( id == -1 ) + { + lwgeom_free(tmp); /* probably too late, due to internal lwerror */ + return -1; + } + if ( id ) + { + lwgeom_free(tmp); /* takes "edge" down with it */ + return id; + } + }} + + + /* TODO: skip checks ? */ + id = _lwt_AddEdge( topo, nid[0], nid[1], edge, 0, handleFaceSplit ? 1 : -1 ); + LWDEBUGF(1, "lwt_AddEdgeModFace returned %" LWTFMT_ELEMID, id); + if ( id == -1 ) + { + lwgeom_free(tmp); /* probably too late, due to internal lwerror */ + return -1; + } + lwgeom_free(tmp); /* possibly takes "edge" down with it */ + + *forward = 1; + return id; +} + +/* Simulate split-loop as it was implemented in pl/pgsql version + * of TopoGeo_addLinestring */ +static LWGEOM * +_lwt_split_by_nodes(const LWGEOM *g, const LWGEOM *nodes) +{ + LWCOLLECTION *col = lwgeom_as_lwcollection(nodes); + uint32_t i; + LWGEOM *bg; + + bg = lwgeom_clone_deep(g); + if ( ! col->ngeoms ) return bg; + + for (i=0; ingeoms; ++i) + { + LWGEOM *g2; + g2 = lwgeom_split(bg, col->geoms[i]); + lwgeom_free(bg); + bg = g2; + } + bg->srid = nodes->srid; + + return bg; +} + +static LWT_ELEMID* +_lwt_AddLine(LWT_TOPOLOGY* topo, LWLINE* line, double tol, int* nedges, + int handleFaceSplit) +{ + LWGEOM *geomsbuf[1]; + LWGEOM **geoms; + uint32_t ngeoms; + LWGEOM *noded, *tmp; + LWCOLLECTION *col; + LWT_ELEMID *ids; + LWT_ISO_EDGE *edges; + LWT_ISO_NODE *nodes; + uint64_t num, numedges = 0, numnodes = 0; + uint64_t i; + GBOX qbox; + int forward; + int input_was_closed = 0; + POINT4D originalStartPoint; + + if ( lwline_is_closed(line) ) + { + input_was_closed = 1; + getPoint4d_p( line->points, 0, &originalStartPoint); + LWDEBUGF(1, "Input line is closed, original point is %g,%g", originalStartPoint.x, originalStartPoint.y); + } + + *nedges = -1; /* error condition, by default */ + + /* Get tolerance, if 0 was given */ + if ( ! tol ) tol = _LWT_MINTOLERANCE( topo, (LWGEOM*)line ); + LWDEBUGF(1, "Working tolerance:%.15g", tol); + LWDEBUGF(1, "Input line has srid=%d", line->srid); + + /* Remove consecutive vertices below given tolerance upfront */ + if ( tol ) + {{ + LWLINE *clean = lwgeom_as_lwline(lwline_remove_repeated_points(line, tol)); + tmp = lwline_as_lwgeom(clean); /* NOTE: might collapse to non-simple */ + LWDEBUGG(1, tmp, "Repeated-point removed"); + }} else tmp=(LWGEOM*)line; + + /* 1. Self-node */ + noded = lwgeom_node((LWGEOM*)tmp); + if ( tmp != (LWGEOM*)line ) lwgeom_free(tmp); + if ( ! noded ) return NULL; /* should have called lwerror already */ + LWDEBUGG(1, noded, "Noded"); + + qbox = *lwgeom_get_bbox( lwline_as_lwgeom(line) ); + LWDEBUGF(1, "Line BOX is %.15g %.15g, %.15g %.15g", qbox.xmin, qbox.ymin, + qbox.xmax, qbox.ymax); + gbox_expand(&qbox, tol); + LWDEBUGF(1, "BOX expanded by %g is %.15g %.15g, %.15g %.15g", + tol, qbox.xmin, qbox.ymin, qbox.xmax, qbox.ymax); + + LWGEOM **nearby = 0; + int nearbyindex = 0; + int nearbycount = 0; + + /* 2.0. Find edges falling within tol distance */ + edges = lwt_be_getEdgeWithinBox2D( topo, &qbox, &numedges, LWT_COL_EDGE_ALL, 0 ); + if (numedges == UINT64_MAX) + { + lwgeom_free(noded); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return NULL; + } + LWDEBUGF(1, "Line has %d points, its bbox intersects %d edges bboxes", + line->points->npoints, numedges); + if ( numedges ) + {{ + /* collect those whose distance from us is < tol */ + nearbycount += numedges; + nearby = lwalloc(numedges * sizeof(LWGEOM *)); + for (i=0; igeom); + LWDEBUGF(2, "Computing distance from edge %d having %d points", i, e->geom->points->npoints); + double dist = lwgeom_mindistance2d(g, noded); + /* must be closer than tolerated, unless distance is zero */ + if ( dist && dist >= tol ) continue; + nearby[nearbyindex++] = g; + } + LWDEBUGF(1, "Found %d edges closer than tolerance (%g)", nearbyindex, tol); + }} + int nearbyedgecount = nearbyindex; + + /* 2.1. Find isolated nodes falling within tol distance + * + * TODO: add backend-interface support for only getting isolated nodes + */ + nodes = lwt_be_getNodeWithinBox2D( topo, &qbox, &numnodes, LWT_COL_NODE_ALL, 0 ); + if (numnodes == UINT64_MAX) + { + lwgeom_free(noded); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return NULL; + } + LWDEBUGF(1, "Line bbox intersects %d nodes bboxes", numnodes); + if ( numnodes ) + {{ + /* collect those whose distance from us is < tol */ + nearbycount = nearbyedgecount + numnodes; + nearby = nearby ? + lwrealloc(nearby, nearbycount * sizeof(LWGEOM *)) + : + lwalloc(nearbycount * sizeof(LWGEOM *)) + ; + int nn = 0; + for (i=0; icontaining_face == -1 ) continue; /* skip not-isolated nodes */ + LWGEOM *g = lwpoint_as_lwgeom(n->geom); + double dist = lwgeom_mindistance2d(g, noded); + /* must be closer than tolerated, unless distance is zero */ + if ( dist && dist >= tol ) + { + LWDEBUGF(1, "Node %d is %g units away, we tolerate only %g", n->node_id, dist, tol); + continue; + } + nearby[nearbyindex++] = g; + nn = nn + 1; + } + LWDEBUGF(1, "Found %d isolated nodes closer than tolerance (%g)", nn, tol); + }} + int nearbynodecount = nearbyindex - nearbyedgecount; + nearbycount = nearbyindex; + + LWDEBUGF(1, "Number of nearby elements is %d", nearbycount); + + /* 2.2. Snap to nearby elements */ + if ( nearbycount ) + {{ + LWCOLLECTION *col; + LWGEOM *elems; + + col = lwcollection_construct(COLLECTIONTYPE, topo->srid, + NULL, nearbycount, nearby); + elems = lwcollection_as_lwgeom(col); + + LWDEBUGG(1, elems, "Collected nearby elements"); + + tmp = _lwt_toposnap(noded, elems, tol); + lwgeom_free(noded); + noded = tmp; + LWDEBUGG(1, noded, "Elements-snapped"); + if ( input_was_closed ) + {{ + /* Recompute start point in case it moved */ + LWLINE *scrolled = lwgeom_as_lwline(noded); + if (scrolled) + { + getPoint4d_p( scrolled->points, 0, &originalStartPoint); + LWDEBUGF(1, "Closed input line start point after snap %g,%g", originalStartPoint.x, originalStartPoint.y); + } + }} + + /* will not release the geoms array */ + lwcollection_release(col); + + /* + -- re-node to account for ST_Snap introduced self-intersections + -- See http://trac.osgeo.org/postgis/ticket/1714 + -- TODO: consider running UnaryUnion once after all noding + */ + tmp = lwgeom_unaryunion(noded); + lwgeom_free(noded); + noded = tmp; + LWDEBUGG(1, noded, "Unary-unioned"); + }} + + /* 2.3. Node with nearby edges */ + if ( nearbyedgecount ) + {{ + LWCOLLECTION *col; + LWGEOM *iedges; /* just an alias for col */ + LWGEOM *diff, *xset; + + LWDEBUGF(1, "Line intersects %d edges", nearbyedgecount); + + col = lwcollection_construct(COLLECTIONTYPE, topo->srid, + NULL, nearbyedgecount, nearby); + iedges = lwcollection_as_lwgeom(col); + LWDEBUGG(1, iedges, "Collected edges"); + + LWDEBUGF(1, "Diffing noded, with srid=%d " + "and interesecting edges, with srid=%d", + noded->srid, iedges->srid); + diff = lwgeom_difference(noded, iedges); + LWDEBUGG(1, diff, "Differenced"); + + LWDEBUGF(1, "Intersecting noded, with srid=%d " + "and interesecting edges, with srid=%d", + noded->srid, iedges->srid); + xset = lwgeom_intersection(noded, iedges); + LWDEBUGG(1, xset, "Intersected"); + lwgeom_free(noded); + + /* We linemerge here because INTERSECTION, as of GEOS 3.8, + * will result in shared segments being output as multiple + * lines rather than a single line. Example: + + INTERSECTION( + 'LINESTRING(0 0, 5 0, 8 0, 10 0,12 0)', + 'LINESTRING(5 0, 8 0, 10 0)' + ) + == + MULTILINESTRING((5 0,8 0),(8 0,10 0)) + + * We will re-split in a subsequent step, by splitting + * the final line with pre-existing nodes + */ + LWDEBUG(1, "Linemerging intersection"); + tmp = lwgeom_linemerge(xset); + LWDEBUGG(1, tmp, "Linemerged"); + lwgeom_free(xset); + xset = tmp; + + /* + * Here we union the (linemerged) intersection with + * the difference (new lines) + */ + LWDEBUG(1, "Unioning difference and (linemerged) intersection"); + noded = lwgeom_union(diff, xset); + LWDEBUGG(1, noded, "Diff-Xset Unioned"); + lwgeom_free(xset); + lwgeom_free(diff); + + /* will not release the geoms array */ + lwcollection_release(col); + + if ( input_was_closed ) + {{ + LWLINE *scrolled = lwgeom_as_lwline(noded); + if (scrolled) { + if ( lwline_is_closed(scrolled) ) { + ptarray_scroll_in_place(scrolled->points, &originalStartPoint); + } + else { + LWDEBUGG(1, lwline_as_lwgeom(scrolled), "Linemerged intersected input is not closed anymore"); + } + } + else { + LWDEBUGG(1, xset, "Linemerged intersected input is not a line anymore"); + } + }} + + + }} + + + /* 2.4. Split by pre-existing nodes + * + * Pre-existing nodes are isolated nodes AND endpoints + * of intersecting edges + */ + if ( nearbyedgecount ) + { + nearbycount += nearbyedgecount * 2; /* make space for endpoints */ + nearby = lwrealloc(nearby, nearbycount * sizeof(LWGEOM *)); + for (int i=0; ipoints->npoints-1); + /* TODO: only add if within distance to noded AND if not duplicated */ + nearby[nearbyindex++] = lwpoint_as_lwgeom(startNode); + nearbynodecount++; + nearby[nearbyindex++] = lwpoint_as_lwgeom(endNode); + nearbynodecount++; + } + } + if ( nearbynodecount ) + { + col = lwcollection_construct(MULTIPOINTTYPE, topo->srid, + NULL, nearbynodecount, + nearby + nearbyedgecount); + LWGEOM *inodes = lwcollection_as_lwgeom(col); + /* TODO: use lwgeom_split of lwgeom_union ... */ + tmp = _lwt_split_by_nodes(noded, inodes); + lwgeom_free(noded); + noded = tmp; + LWDEBUGG(1, noded, "Node-split"); + /* will not release the geoms array */ + lwcollection_release(col); + } + + + LWDEBUG(1, "Freeing up nearby elements"); + + /* TODO: free up endpoints of nearbyedges */ + if ( nearby ) lwfree(nearby); + if ( nodes ) _lwt_release_nodes(nodes, numnodes); + if ( edges ) _lwt_release_edges(edges, numedges); + + LWDEBUGG(1, noded, "Finally-noded"); + + /* 3. For each (now-noded) segment, insert an edge */ + col = lwgeom_as_lwcollection(noded); + if ( col ) + { + LWDEBUG(1, "Noded line was a collection"); + geoms = col->geoms; + ngeoms = col->ngeoms; + } + else + { + LWDEBUG(1, "Noded line was a single geom"); + geomsbuf[0] = noded; + geoms = geomsbuf; + ngeoms = 1; + } + + LWDEBUGF(1, "Line was split into %d edges", ngeoms); + + /* TODO: refactor to first add all nodes (re-snapping edges if + * needed) and then check all edges for existing already + * ( so to save a DB scan for each edge to be added ) + */ + ids = lwalloc(sizeof(LWT_ELEMID)*ngeoms); + num = 0; + for ( i=0; isrid = noded->srid; + +#if POSTGIS_DEBUG_LEVEL > 0 + { + size_t sz; + char *wkt1 = lwgeom_to_wkt(g, WKT_EXTENDED, 15, &sz); + LWDEBUGF(1, "Component %d of split line is: %s", i, wkt1); + lwfree(wkt1); + } +#endif + + id = _lwt_AddLineEdge( topo, lwgeom_as_lwline(g), tol, handleFaceSplit, &forward ); + LWDEBUGF(1, "_lwt_AddLineEdge returned %" LWTFMT_ELEMID, id); + if ( id < 0 ) + { + lwgeom_free(noded); + lwfree(ids); + return NULL; + } + if ( ! id ) + { + LWDEBUGF(1, "Component %d of split line collapsed", i); + continue; + } + + LWDEBUGF(1, "Component %d of split line is %s edge %" LWTFMT_ELEMID, + i, forward ? "forward" : "backward", id); + ids[num++] = forward ? id : -id; /* TODO: skip duplicates */ + } + + LWDEBUGG(1, noded, "Noded before free"); + lwgeom_free(noded); + + /* TODO: XXX remove duplicated ids if not done before */ + + *nedges = num; + return ids; +} + +LWT_ELEMID* +lwt_AddLine(LWT_TOPOLOGY* topo, LWLINE* line, double tol, int* nedges) +{ + return _lwt_AddLine(topo, line, tol, nedges, 1); +} + +LWT_ELEMID* +lwt_AddLineNoFace(LWT_TOPOLOGY* topo, LWLINE* line, double tol, int* nedges) +{ + return _lwt_AddLine(topo, line, tol, nedges, 0); +} + +LWT_ELEMID* +lwt_AddPolygon(LWT_TOPOLOGY* topo, LWPOLY* poly, double tol, int* nfaces) +{ + uint32_t i; + *nfaces = -1; /* error condition, by default */ + int num; + LWT_ISO_FACE *faces; + uint64_t nfacesinbox; + uint64_t j; + LWT_ELEMID *ids = NULL; + GBOX qbox; + const GEOSPreparedGeometry *ppoly; + GEOSGeometry *polyg; + + /* Get tolerance, if 0 was given */ + if ( ! tol ) tol = _LWT_MINTOLERANCE( topo, (LWGEOM*)poly ); + LWDEBUGF(1, "Working tolerance:%.15g", tol); + + /* Add each ring as an edge */ + for ( i=0; inrings; ++i ) + { + LWLINE *line; + POINTARRAY *pa; + LWT_ELEMID *eids; + int nedges; + + pa = ptarray_clone(poly->rings[i]); + line = lwline_construct(topo->srid, NULL, pa); + eids = lwt_AddLine( topo, line, tol, &nedges ); + if ( nedges < 0 ) { + /* probably too late as lwt_AddLine invoked lwerror */ + lwline_free(line); + lwerror("Error adding ring %d of polygon", i); + return NULL; + } + lwline_free(line); + lwfree(eids); + } + + /* + -- Find faces covered by input polygon + -- NOTE: potential snapping changed polygon edges + */ + qbox = *lwgeom_get_bbox( lwpoly_as_lwgeom(poly) ); + gbox_expand(&qbox, tol); + faces = lwt_be_getFaceWithinBox2D( topo, &qbox, &nfacesinbox, + LWT_COL_FACE_ALL, 0 ); + if (nfacesinbox == UINT64_MAX) + { + lwfree(ids); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return NULL; + } + + num = 0; + if ( nfacesinbox ) + { + polyg = LWGEOM2GEOS(lwpoly_as_lwgeom(poly), 0); + if ( ! polyg ) + { + _lwt_release_faces(faces, nfacesinbox); + lwerror("Could not convert poly geometry to GEOS: %s", lwgeom_geos_errmsg); + return NULL; + } + ppoly = GEOSPrepare(polyg); + ids = lwalloc(sizeof(LWT_ELEMID)*nfacesinbox); + for ( j=0; jface_id ); + if ( ! fg ) + { + j = f->face_id; /* so we can destroy faces */ + GEOSPreparedGeom_destroy(ppoly); + GEOSGeom_destroy(polyg); + lwfree(ids); + _lwt_release_faces(faces, nfacesinbox); + lwerror("Could not get geometry of face %" LWTFMT_ELEMID, j); + return NULL; + } + /* check if a point on this face's surface is covered by our polygon */ + fgg = LWGEOM2GEOS(fg, 0); + lwgeom_free(fg); + if ( ! fgg ) + { + GEOSPreparedGeom_destroy(ppoly); + GEOSGeom_destroy(polyg); + _lwt_release_faces(faces, nfacesinbox); + lwerror("Could not convert edge geometry to GEOS: %s", lwgeom_geos_errmsg); + return NULL; + } + sp = GEOSPointOnSurface(fgg); + GEOSGeom_destroy(fgg); + if ( ! sp ) + { + GEOSPreparedGeom_destroy(ppoly); + GEOSGeom_destroy(polyg); + _lwt_release_faces(faces, nfacesinbox); + lwerror("Could not find point on face surface: %s", lwgeom_geos_errmsg); + return NULL; + } + covers = GEOSPreparedCovers( ppoly, sp ); + GEOSGeom_destroy(sp); + if (covers == 2) + { + GEOSPreparedGeom_destroy(ppoly); + GEOSGeom_destroy(polyg); + _lwt_release_faces(faces, nfacesinbox); + lwerror("PreparedCovers error: %s", lwgeom_geos_errmsg); + return NULL; + } + if ( ! covers ) + { + continue; /* we're not composed by this face */ + } + + /* TODO: avoid duplicates ? */ + ids[num++] = f->face_id; + } + GEOSPreparedGeom_destroy(ppoly); + GEOSGeom_destroy(polyg); + _lwt_release_faces(faces, nfacesinbox); + } + + /* possibly 0 if non face's surface point was found + * to be covered by input polygon */ + *nfaces = num; + + return ids; +} + +/* + *---- polygonizer + */ + +/* An array of pointers to EDGERING structures */ +typedef struct LWT_ISO_EDGE_TABLE_T { + LWT_ISO_EDGE *edges; + int size; +} LWT_ISO_EDGE_TABLE; + +static int +compare_iso_edges_by_id(const void *si1, const void *si2) +{ + int a = ((LWT_ISO_EDGE *)si1)->edge_id; + int b = ((LWT_ISO_EDGE *)si2)->edge_id; + if ( a < b ) + return -1; + else if ( a > b ) + return 1; + else + return 0; +} + +static LWT_ISO_EDGE * +_lwt_getIsoEdgeById(LWT_ISO_EDGE_TABLE *tab, LWT_ELEMID id) +{ + LWT_ISO_EDGE key; + key.edge_id = id; + + void *match = bsearch( &key, tab->edges, tab->size, + sizeof(LWT_ISO_EDGE), + compare_iso_edges_by_id); + return match; +} + +typedef struct LWT_EDGERING_ELEM_T { + /* externally owned */ + LWT_ISO_EDGE *edge; + /* 0 if false, 1 if true */ + int left; +} LWT_EDGERING_ELEM; + +/* A ring of edges */ +typedef struct LWT_EDGERING_T { + /* Signed edge identifiers + * positive ones are walked in their direction, negative ones + * in the opposite direction */ + LWT_EDGERING_ELEM **elems; + /* Number of edges in the ring */ + int size; + int capacity; + /* Bounding box of the ring */ + GBOX *env; + /* Bounding box of the ring in GEOS format (for STRTree) */ + GEOSGeometry *genv; +} LWT_EDGERING; + +#define LWT_EDGERING_INIT(a) { \ + (a)->size = 0; \ + (a)->capacity = 1; \ + (a)->elems = lwalloc(sizeof(LWT_EDGERING_ELEM *) * (a)->capacity); \ + (a)->env = NULL; \ + (a)->genv = NULL; \ +} + +#define LWT_EDGERING_PUSH(a, r) { \ + if ( (a)->size + 1 > (a)->capacity ) { \ + (a)->capacity *= 2; \ + (a)->elems = lwrealloc((a)->elems, sizeof(LWT_EDGERING_ELEM *) * (a)->capacity); \ + } \ + /* lwdebug(1, "adding elem %d (%p) of edgering %p", (a)->size, (r), (a)); */ \ + (a)->elems[(a)->size++] = (r); \ +} + +#define LWT_EDGERING_CLEAN(a) { \ + int i; for (i=0; i<(a)->size; ++i) { \ + if ( (a)->elems[i] ) { \ + /* lwdebug(1, "freeing elem %d (%p) of edgering %p", i, (a)->elems[i], (a)); */ \ + lwfree((a)->elems[i]); \ + } \ + } \ + if ( (a)->elems ) { lwfree((a)->elems); (a)->elems = NULL; } \ + (a)->size = 0; \ + (a)->capacity = 0; \ + if ( (a)->env ) { lwfree((a)->env); (a)->env = NULL; } \ + if ( (a)->genv ) { GEOSGeom_destroy((a)->genv); (a)->genv = NULL; } \ +} + +/* An array of pointers to EDGERING structures */ +typedef struct LWT_EDGERING_ARRAY_T { + LWT_EDGERING **rings; + int size; + int capacity; + GEOSSTRtree* tree; +} LWT_EDGERING_ARRAY; + +#define LWT_EDGERING_ARRAY_INIT(a) { \ + (a)->size = 0; \ + (a)->capacity = 1; \ + (a)->rings = lwalloc(sizeof(LWT_EDGERING *) * (a)->capacity); \ + (a)->tree = NULL; \ +} + +/* WARNING: use of 'j' is intentional, not to clash with + * 'i' used in LWT_EDGERING_CLEAN */ +#define LWT_EDGERING_ARRAY_CLEAN(a) { \ + int j; for (j=0; j<(a)->size; ++j) { \ + LWT_EDGERING_CLEAN((a)->rings[j]); \ + } \ + if ( (a)->capacity ) lwfree((a)->rings); \ + if ( (a)->tree ) { \ + GEOSSTRtree_destroy( (a)->tree ); \ + (a)->tree = NULL; \ + } \ +} + +#define LWT_EDGERING_ARRAY_PUSH(a, r) { \ + if ( (a)->size + 1 > (a)->capacity ) { \ + (a)->capacity *= 2; \ + (a)->rings = lwrealloc((a)->rings, sizeof(LWT_EDGERING *) * (a)->capacity); \ + } \ + (a)->rings[(a)->size++] = (r); \ +} + +typedef struct LWT_EDGERING_POINT_ITERATOR_T { + LWT_EDGERING *ring; + LWT_EDGERING_ELEM *curelem; + int curelemidx; + int curidx; +} LWT_EDGERING_POINT_ITERATOR; + +static int +_lwt_EdgeRingIterator_next(LWT_EDGERING_POINT_ITERATOR *it, POINT2D *pt) +{ + LWT_EDGERING_ELEM *el = it->curelem; + POINTARRAY *pa; + + if ( ! el ) return 0; /* finished */ + + pa = el->edge->geom->points; + + int tonext = 0; + LWDEBUGF(3, "iterator fetching idx %d from pa of %d points", it->curidx, pa->npoints); + getPoint2d_p(pa, it->curidx, pt); + if ( el->left ) { + it->curidx++; + if ( it->curidx >= (int) pa->npoints ) tonext = 1; + } else { + it->curidx--; + if ( it->curidx < 0 ) tonext = 1; + } + + if ( tonext ) + { + LWDEBUG(3, "iterator moving to next element"); + it->curelemidx++; + if ( it->curelemidx < it->ring->size ) + { + el = it->curelem = it->ring->elems[it->curelemidx]; + it->curidx = el->left ? 0 : el->edge->geom->points->npoints - 1; + } + else + { + it->curelem = NULL; + } + } + + return 1; +} + +/* Release return with lwfree */ +static LWT_EDGERING_POINT_ITERATOR * +_lwt_EdgeRingIterator_begin(LWT_EDGERING *er) +{ + LWT_EDGERING_POINT_ITERATOR *ret = lwalloc(sizeof(LWT_EDGERING_POINT_ITERATOR)); + ret->ring = er; + if ( er->size ) ret->curelem = er->elems[0]; + else ret->curelem = NULL; + ret->curelemidx = 0; + ret->curidx = (ret->curelem == NULL || ret->curelem->left) ? 0 : ret->curelem->edge->geom->points->npoints - 1; + return ret; +} + +/* Identifier for a placeholder face that will be + * used to mark hole rings */ +#define LWT_HOLES_FACE_PLACEHOLDER INT32_MIN + +static int +_lwt_FetchNextUnvisitedEdge(__attribute__((__unused__)) LWT_TOPOLOGY *topo, LWT_ISO_EDGE_TABLE *etab, int from) +{ + while ( + from < etab->size && + etab->edges[from].face_left != -1 && + etab->edges[from].face_right != -1 + ) from++; + return from < etab->size ? from : -1; +} + +static LWT_ISO_EDGE * +_lwt_FetchAllEdges(LWT_TOPOLOGY *topo, int *numedges) +{ + LWT_ISO_EDGE *edge; + int fields = LWT_COL_EDGE_ALL; + uint64_t nelems = 1; + + edge = lwt_be_getEdgeWithinBox2D( topo, NULL, &nelems, fields, 0); + *numedges = nelems; + if (nelems == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return NULL; + } + return edge; +} + +/* Update the side face of given ring edges + * + * Edge identifiers are signed, those with negative identifier + * need to be updated their right_face, those with positive + * identifier need to be updated their left_face. + * + * @param face identifier of the face bound by the ring + * @return 0 on success, -1 on error + */ +static int +_lwt_UpdateEdgeRingSideFace(LWT_TOPOLOGY *topo, LWT_EDGERING *ring, + LWT_ELEMID face) +{ + LWT_ISO_EDGE *forward_edges = NULL; + int forward_edges_count = 0; + LWT_ISO_EDGE *backward_edges = NULL; + int backward_edges_count = 0; + int i, ret; + + /* Make a list of forward_edges and backward_edges */ + + forward_edges = lwalloc(sizeof(LWT_ISO_EDGE) * ring->size); + forward_edges_count = 0; + backward_edges = lwalloc(sizeof(LWT_ISO_EDGE) * ring->size); + backward_edges_count = 0; + + for ( i=0; isize; ++i ) + { + LWT_EDGERING_ELEM *elem = ring->elems[i]; + LWT_ISO_EDGE *edge = elem->edge; + LWT_ELEMID id = edge->edge_id; + if ( elem->left ) + { + LWDEBUGF(3, "Forward edge %d is %d", forward_edges_count, id); + forward_edges[forward_edges_count].edge_id = id; + forward_edges[forward_edges_count++].face_left = face; + edge->face_left = face; + } + else + { + LWDEBUGF(3, "Backward edge %d is %d", forward_edges_count, id); + backward_edges[backward_edges_count].edge_id = id; + backward_edges[backward_edges_count++].face_right = face; + edge->face_right = face; + } + } + + /* Update forward edges */ + if ( forward_edges_count ) + { + ret = lwt_be_updateEdgesById(topo, forward_edges, + forward_edges_count, + LWT_COL_EDGE_FACE_LEFT); + if ( ret == -1 ) + { + lwfree( forward_edges ); + lwfree( backward_edges ); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if ( ret != forward_edges_count ) + { + lwfree( forward_edges ); + lwfree( backward_edges ); + lwerror("Unexpected error: %d edges updated when expecting %d (forward)", + ret, forward_edges_count); + return -1; + } + } + + /* Update backward edges */ + if ( backward_edges_count ) + { + ret = lwt_be_updateEdgesById(topo, backward_edges, + backward_edges_count, + LWT_COL_EDGE_FACE_RIGHT); + if ( ret == -1 ) + { + lwfree( forward_edges ); + lwfree( backward_edges ); + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if ( ret != backward_edges_count ) + { + lwfree( forward_edges ); + lwfree( backward_edges ); + lwerror("Unexpected error: %d edges updated when expecting %d (backward)", + ret, backward_edges_count); + return -1; + } + } + + lwfree( forward_edges ); + lwfree( backward_edges ); + + return 0; +} + +/* + * @param side 1 for left side, -1 for right side + */ +static LWT_EDGERING * +_lwt_BuildEdgeRing(__attribute__((__unused__)) LWT_TOPOLOGY *topo, LWT_ISO_EDGE_TABLE *edges, + LWT_ISO_EDGE *edge, int side) +{ + LWT_EDGERING *ring; + LWT_EDGERING_ELEM *elem; + LWT_ISO_EDGE *cur; + int curside; + + ring = lwalloc(sizeof(LWT_EDGERING)); + LWT_EDGERING_INIT(ring); + + cur = edge; + curside = side; + + LWDEBUGF(2, "Building rings for edge %d (side %d)", cur->edge_id, side); + + do { + LWT_ELEMID next; + + elem = lwalloc(sizeof(LWT_EDGERING_ELEM)); + elem->edge = cur; + elem->left = ( curside == 1 ); + + /* Mark edge as "visited" */ + if ( elem->left ) cur->face_left = LWT_HOLES_FACE_PLACEHOLDER; + else cur->face_right = LWT_HOLES_FACE_PLACEHOLDER; + + LWT_EDGERING_PUSH(ring, elem); + next = elem->left ? cur->next_left : cur->next_right; + + LWDEBUGF(3, " next edge is %d", next); + + if ( next > 0 ) curside = 1; + else { curside = -1; next = -next; } + cur = _lwt_getIsoEdgeById(edges, next); + if ( ! cur ) + { + lwerror("Could not find edge with id %d", next); + break; + } + } while (cur != edge || curside != side); + + LWDEBUGF(1, "Ring for edge %d has %d elems", edge->edge_id*side, ring->size); + + return ring; +} + +static double +_lwt_EdgeRingSignedArea(LWT_EDGERING_POINT_ITERATOR *it) +{ + POINT2D P1; + POINT2D P2; + POINT2D P3; + double sum = 0.0; + double x0, x, y1, y2; + + if ( ! _lwt_EdgeRingIterator_next(it, &P1) ) return 0.0; + if ( ! _lwt_EdgeRingIterator_next(it, &P2) ) return 0.0; + + LWDEBUG(2, "_lwt_EdgeRingSignedArea"); + + x0 = P1.x; + while ( _lwt_EdgeRingIterator_next(it, &P3) ) + { + x = P2.x - x0; + y1 = P3.y; + y2 = P1.y; + sum += x * (y2-y1); + + /* Move forwards! */ + P1 = P2; + P2 = P3; + } + + return sum / 2.0; +} + + +/* Return 1 for true, 0 for false */ +static int +_lwt_EdgeRingIsCCW(LWT_EDGERING *ring) +{ + double sa; + + LWDEBUGF(2, "_lwt_EdgeRingIsCCW, ring has %d elems", ring->size); + LWT_EDGERING_POINT_ITERATOR *it = _lwt_EdgeRingIterator_begin(ring); + sa = _lwt_EdgeRingSignedArea(it); + LWDEBUGF(2, "_lwt_EdgeRingIsCCW, signed area is %g", sa); + lwfree(it); + if ( sa >= 0 ) return 0; + else return 1; +} + +static int +_lwt_EdgeRingCrossingCount(const POINT2D *p, LWT_EDGERING_POINT_ITERATOR *it) +{ + int cn = 0; /* the crossing number counter */ + POINT2D v1, v2; +#ifndef RELAX + POINT2D v0; +#endif + + if ( ! _lwt_EdgeRingIterator_next(it, &v1) ) return cn; + v0 = v1; + while ( _lwt_EdgeRingIterator_next(it, &v2) ) + { + double vt; + + /* edge from vertex i to vertex i+1 */ + if + ( + /* an upward crossing */ + ((v1.y <= p->y) && (v2.y > p->y)) + /* a downward crossing */ + || ((v1.y > p->y) && (v2.y <= p->y)) + ) + { + + vt = (double)(p->y - v1.y) / (v2.y - v1.y); + + /* P->x x < v1.x + vt * (v2.x - v1.x)) + { + /* a valid crossing of y=p->y right of p->x */ + ++cn; + } + } + v1 = v2; + } + + LWDEBUGF(3, "_lwt_EdgeRingCrossingCount returning %d", cn); + +#ifndef RELAX + if ( memcmp(&v1, &v0, sizeof(POINT2D)) ) + { + lwerror("_lwt_EdgeRingCrossingCount: V[n] != V[0] (%g %g != %g %g)", + v1.x, v1.y, v0.x, v0.y); + return -1; + } +#endif + + return cn; +} + +/* Return 1 for true, 0 for false */ +static int +_lwt_EdgeRingContainsPoint(LWT_EDGERING *ring, POINT2D *p) +{ + int cn = 0; + + LWT_EDGERING_POINT_ITERATOR *it = _lwt_EdgeRingIterator_begin(ring); + cn = _lwt_EdgeRingCrossingCount(p, it); + lwfree(it); + return (cn&1); /* 0 if even (out), and 1 if odd (in) */ +} + +static GBOX * +_lwt_EdgeRingGetBbox(LWT_EDGERING *ring) +{ + int i; + + if ( ! ring->env ) + { + LWDEBUGF(2, "Computing GBOX for ring %p", ring); + for (i=0; isize; ++i) + { + LWT_EDGERING_ELEM *elem = ring->elems[i]; + LWLINE *g = elem->edge->geom; + const GBOX *newbox = lwgeom_get_bbox(lwline_as_lwgeom(g)); + if ( ! i ) ring->env = gbox_clone( newbox ); + else gbox_merge( newbox, ring->env ); + } + } + + return ring->env; +} + +static LWT_ELEMID +_lwt_EdgeRingGetFace(LWT_EDGERING *ring) +{ + LWT_EDGERING_ELEM *el = ring->elems[0]; + return el->left ? el->edge->face_left : el->edge->face_right; +} + + +/* + * Register a face on an edge side + * + * Create and register face to shell (CCW) walks, + * register arbitrary negative face_id to CW rings. + * + * Push CCW rings to shells, CW rings to holes. + * + * The ownership of the "geom" and "ids" members of the + * LWT_EDGERING pushed to the given LWT_EDGERING_ARRAYS + * are transferred to caller. + * + * @param side 1 for left side, -1 for right side + * + * @param holes an array where holes will be pushed + * + * @param shells an array where shells will be pushed + * + * @param registered id of registered face. It will be a negative number + * for holes or isolated edge strips (still registered in the face + * table, but only temporary). + * + * @return 0 on success, -1 on error. + * + */ +static int +_lwt_RegisterFaceOnEdgeSide(LWT_TOPOLOGY *topo, LWT_ISO_EDGE *edge, + int side, LWT_ISO_EDGE_TABLE *edges, + LWT_EDGERING_ARRAY *holes, + LWT_EDGERING_ARRAY *shells, + LWT_ELEMID *registered) +{ + const LWT_BE_IFACE *iface = topo->be_iface; + /* this is arbitrary, could be taken as parameter */ + static const int placeholder_faceid = LWT_HOLES_FACE_PLACEHOLDER; + LWT_EDGERING *ring; + + /* Get edge ring */ + ring = _lwt_BuildEdgeRing(topo, edges, edge, side); + + LWDEBUG(2, "Ring built, calling EdgeRingIsCCW"); + + /* Compute winding (CW or CCW?) */ + int isccw = _lwt_EdgeRingIsCCW(ring); + + if ( isccw ) + { + /* Create new face */ + LWT_ISO_FACE newface; + + LWDEBUGF(1, "Ring of edge %d is a shell (shell %d)", edge->edge_id * side, shells->size); + + newface.mbr = _lwt_EdgeRingGetBbox(ring); + + newface.face_id = -1; + /* Insert the new face */ + int ret = lwt_be_insertFaces( topo, &newface, 1 ); + newface.mbr = NULL; + if ( ret == -1 ) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if ( ret != 1 ) + { + lwerror("Unexpected error: %d faces inserted when expecting 1", ret); + return -1; + } + /* return new face_id */ + *registered = newface.face_id; + LWT_EDGERING_ARRAY_PUSH(shells, ring); + + /* update ring edges set new face_id on resp. side to *registered */ + ret = _lwt_UpdateEdgeRingSideFace(topo, ring, *registered); + if ( ret ) + { + lwerror("Errors updating edgering side face: %s", + lwt_be_lastErrorMessage(iface)); + return -1; + } + + } + else /* cw, so is an hole */ + { + LWDEBUGF(1, "Ring of edge %d is a hole (hole %d)", edge->edge_id * side, holes->size); + *registered = placeholder_faceid; + LWT_EDGERING_ARRAY_PUSH(holes, ring); + } + + return 0; +} + +static void +_lwt_AccumulateCanditates(void* item, void* userdata) +{ + LWT_EDGERING_ARRAY *candidates = userdata; + LWT_EDGERING *sring = item; + LWT_EDGERING_ARRAY_PUSH(candidates, sring); +} + +static LWT_ELEMID +_lwt_FindFaceContainingRing(LWT_TOPOLOGY* topo, LWT_EDGERING *ring, + LWT_EDGERING_ARRAY *shells) +{ + LWT_ELEMID foundInFace = -1; + int i; + const GBOX *minenv = NULL; + POINT2D pt; + const GBOX *testbox; + GEOSGeometry *ghole; + + getPoint2d_p( ring->elems[0]->edge->geom->points, 0, &pt ); + + testbox = _lwt_EdgeRingGetBbox(ring); + + /* Create a GEOS Point from a vertex of the hole ring */ + { + LWPOINT *point = lwpoint_make2d(topo->srid, pt.x, pt.y); + ghole = LWGEOM2GEOS( lwpoint_as_lwgeom(point), 1 ); + lwpoint_free(point); + if ( ! ghole ) { + lwerror("Could not convert edge geometry to GEOS: %s", lwgeom_geos_errmsg); + return -1; + } + } + + /* Build STRtree of shell envelopes */ + if ( ! shells->tree ) + { + static const int STRTREE_NODE_CAPACITY = 10; + LWDEBUG(1, "Building STRtree"); + shells->tree = GEOSSTRtree_create(STRTREE_NODE_CAPACITY); + if (shells->tree == NULL) + { + lwerror("Could not create GEOS STRTree: %s", lwgeom_geos_errmsg); + return -1; + } + for (i=0; isize; ++i) + { + LWT_EDGERING *sring = shells->rings[i]; + const GBOX* shellbox = _lwt_EdgeRingGetBbox(sring); + LWDEBUGF(2, "GBOX of shell %p for edge %d is %g %g,%g %g", + sring, sring->elems[0]->edge->edge_id, shellbox->xmin, + shellbox->ymin, shellbox->xmax, shellbox->ymax); + POINTARRAY *pa = ptarray_construct(0, 0, 2); + POINT4D pt; + LWLINE *diag; + pt.x = shellbox->xmin; + pt.y = shellbox->ymin; + ptarray_set_point4d(pa, 0, &pt); + pt.x = shellbox->xmax; + pt.y = shellbox->ymax; + ptarray_set_point4d(pa, 1, &pt); + diag = lwline_construct(topo->srid, NULL, pa); + /* Record just envelope in ggeom */ + /* making valid, probably not needed */ + sring->genv = LWGEOM2GEOS( lwline_as_lwgeom(diag), 1 ); + lwline_free(diag); + GEOSSTRtree_insert(shells->tree, sring->genv, sring); + } + LWDEBUG(1, "STRtree build completed"); + } + + LWT_EDGERING_ARRAY candidates; + LWT_EDGERING_ARRAY_INIT(&candidates); + GEOSSTRtree_query(shells->tree, ghole, &_lwt_AccumulateCanditates, &candidates); + LWDEBUGF(1, "Found %d candidate shells containing first point of ring's originating edge %d", + candidates.size, ring->elems[0]->edge->edge_id * ( ring->elems[0]->left ? 1 : -1 ) ); + + /* TODO: sort candidates by bounding box size */ + + for (i=0; ielems[0]->edge->edge_id == ring->elems[0]->edge->edge_id ) + { + LWDEBUGF(1, "Shell %d is on other side of ring", + _lwt_EdgeRingGetFace(sring)); + continue; + } + + /* The hole envelope cannot equal the shell envelope */ + if ( gbox_same(shellbox, testbox) ) + { + LWDEBUGF(1, "Bbox of shell %d equals that of hole ring", + _lwt_EdgeRingGetFace(sring)); + continue; + } + + /* Skip if ring box is not in shell box */ + if ( ! gbox_contains_2d(shellbox, testbox) ) + { + LWDEBUGF(1, "Bbox of shell %d does not contain bbox of ring point", + _lwt_EdgeRingGetFace(sring)); + continue; + } + + /* Skip test if a containing shell was already found + * and this shell's bbox is not contained in the other */ + if ( minenv && ! gbox_contains_2d(minenv, shellbox) ) + { + LWDEBUGF(2, "Bbox of shell %d (face %d) not contained by bbox " + "of last shell found to contain the point", + i, _lwt_EdgeRingGetFace(sring)); + continue; + } + + contains = _lwt_EdgeRingContainsPoint(sring, &pt); + if ( contains ) + { + /* Continue until all shells are tested, as we want to + * use the one with the smallest bounding box */ + /* IDEA: sort shells by bbox size, stopping on first match */ + LWDEBUGF(1, "Shell %d contains hole of edge %d", + _lwt_EdgeRingGetFace(sring), + ring->elems[0]->edge->edge_id); + minenv = shellbox; + foundInFace = _lwt_EdgeRingGetFace(sring); + } + } + if ( foundInFace == -1 ) foundInFace = 0; + + candidates.size = 0; /* Avoid destroying the actual shell rings */ + LWT_EDGERING_ARRAY_CLEAN(&candidates); + + GEOSGeom_destroy(ghole); + + return foundInFace; +} + +/* + * @return -1 on error (and report error), + * 1 if faces beside the universal one exist + * 0 otherwise + */ +static int +_lwt_CheckFacesExist(LWT_TOPOLOGY *topo) +{ + LWT_ISO_FACE *faces; + int fields = LWT_COL_FACE_FACE_ID; + uint64_t nelems = 1; + GBOX qbox; + + qbox.xmin = qbox.ymin = -DBL_MAX; + qbox.xmax = qbox.ymax = DBL_MAX; + faces = lwt_be_getFaceWithinBox2D( topo, &qbox, &nelems, fields, 1); + if (nelems == UINT64_MAX) + { + lwerror("Backend error: %s", lwt_be_lastErrorMessage(topo->be_iface)); + return -1; + } + if ( faces ) _lwt_release_faces(faces, nelems); + return nelems; +} + +int +lwt_Polygonize(LWT_TOPOLOGY* topo) +{ + /* + Fetch all edges + Sort edges by edge_id + Mark all edges' left and right face as -1 + Iteratively: + Fetch next edge with left or right face == -1 + For each side with face == -1: + Find ring on its side + If ring is CCW: + create a new face, assign to the ring edges' appropriate side + If ring is CW (face needs to be same of external): + assign a negative face_id the ring edges' appropriate side + Now for each edge with a negative face_id on the side: + Find containing face (mbr cache and all) + Update with id of containing face + */ + + const LWT_BE_IFACE *iface = topo->be_iface; + LWT_ISO_EDGE *edge; + int numfaces = -1; + LWT_ISO_EDGE_TABLE edgetable; + LWT_EDGERING_ARRAY holes, shells; + int i; + int err = 0; + + LWT_EDGERING_ARRAY_INIT(&holes); + LWT_EDGERING_ARRAY_INIT(&shells); + + initGEOS(lwnotice, lwgeom_geos_error); + + /* + Check if Topology already contains some Face + (ignoring the Universal Face) + */ + numfaces = _lwt_CheckFacesExist(topo); + if ( numfaces != 0 ) { + if ( numfaces > 0 ) { + /* Faces exist */ + lwerror("Polygonize: face table is not empty."); + } + /* Backend error, message should have been printed already */ + return -1; + } + + + edgetable.edges = _lwt_FetchAllEdges(topo, &(edgetable.size)); + if ( ! edgetable.edges ) { + if (edgetable.size == 0) { + /* not an error: no Edges */ + return 0; + } + /* error should have been printed already */ + return -1; + } + + /* Sort edges by ID (to allow btree searches) */ + qsort(edgetable.edges, edgetable.size, sizeof(LWT_ISO_EDGE), compare_iso_edges_by_id); + + /* Mark all edges as unvisited */ + for (i=0; iedge_id, edge->face_left, edge->face_right); + if ( edge->face_left == -1 ) + { + err = _lwt_RegisterFaceOnEdgeSide(topo, edge, 1, &edgetable, + &holes, &shells, &newface); + if ( err ) break; + LWDEBUGF(1, "New face on the left of edge %d is %d", + edge->edge_id, newface); + edge->face_left = newface; + } + if ( edge->face_right == -1 ) + { + err = _lwt_RegisterFaceOnEdgeSide(topo, edge, -1, &edgetable, + &holes, &shells, &newface); + if ( err ) break; + LWDEBUGF(1, "New face on the right of edge %d is %d", + edge->edge_id, newface); + edge->face_right = newface; + } + } + + if ( err ) + { + _lwt_release_edges(edgetable.edges, edgetable.size); + LWT_EDGERING_ARRAY_CLEAN( &holes ); + LWT_EDGERING_ARRAY_CLEAN( &shells ); + lwerror("Errors fetching or registering face-missing edges: %s", + lwt_be_lastErrorMessage(iface)); + return -1; + } + + LWDEBUGF(1, "Found %d holes and %d shells", holes.size, shells.size); + + /* TODO: sort holes by pt.x, sort shells by bbox.xmin */ + + /* Assign shells to holes */ + for (i=0; ibe_iface)); + /* cberror(topo->be_data, "Error from cb_getClosestEdge"); */ + return -1; + } + if (numedges == 0) + { + /* If there are no edges the point is in the universal face */ + return 0; + } + + LWDEBUGGF(2, lwline_as_lwgeom(closestEdge->geom), "Closest edge %" LWTFMT_ELEMID, closestEdge->edge_id); + + /* Find closest segment of edge to the point */ + queryPoint = getPoint2d_cp(pt->point, 0); + closestSegmentIndex = ptarray_closest_segment_2d(closestEdge->geom->points, queryPoint, &dist); + LWDEBUGF(1, "Closest segment on edge %" LWTFMT_ELEMID " is %d (dist %g)", closestEdge->edge_id, closestSegmentIndex, dist); + closestSegmentP0 = getPoint2d_cp(closestEdge->geom->points, closestSegmentIndex); + closestSegmentP1 = getPoint2d_cp(closestEdge->geom->points, closestSegmentIndex + 1); + LWDEBUGF(1, "Closest segment on edge %" LWTFMT_ELEMID " is LINESTRING(%g %g, %g %g)", + closestEdge->edge_id, + closestSegmentP0->x, + closestSegmentP0->y, + closestSegmentP1->x, + closestSegmentP1->y + ); + + /* + * We use comp.graphics.algorithms Frequently Asked Questions method + * + * (1) AC dot AB + * r = ---------- + * ||AB||^2 + * r has the following meaning: + * r=0 P = A + * r=1 P = B + * r<0 P is on the backward extension of AB + * r>1 P is on the forward extension of AB + * 0x-A->x) * (B->x-A->x) + (p->y-A->y) * (B->y-A->y) )/( (B->x-A->x)*(B->x-A->x) +(B->y-A->y)*(B->y-A->y) ); + if ( r <= 0 ) + { + closestPointOnEdge = A; + closestPointVertex = closestSegmentIndex; + if ( closestSegmentIndex == 0 ) + { + closestNode = closestEdge->start_node; + } + } + else if (r >= 1 ) + { + closestPointOnEdge = B; + closestPointVertex = closestSegmentIndex + 1; + if ( closestSegmentIndex + 2 == closestEdge->geom->points->npoints ) + { + closestNode = closestEdge->end_node; + } + } + else + { + closestPointVertex = closestEdge->geom->points->npoints; + } + + if ( closestNode != 0 ) + { + LWDEBUGF(1, "Closest point is node %d", closestNode); + if ( dist == 0 ) + { + LWDEBUGF(1, "Query point is node %d", closestNode); + /* Query point is the node + * + * If all edges incident to the node are + * dangling, we can return their common + * side face, otherwise the point will be + * on multiple face boundaries + */ + if ( closestEdge->face_left != closestEdge->face_right ) + { + _lwt_release_edges(closestEdge, 1); + lwerror("Two or more faces found"); + return -1; + } + containingFace = closestEdge->face_left; + + /* Check other incident edges */ + numedges = 1; + edges = lwt_be_getEdgeByNode( topo, &closestNode, &numedges, LWT_COL_EDGE_FACE_LEFT|LWT_COL_EDGE_FACE_RIGHT ); + if (numedges == UINT64_MAX) + { + lwerror("Backend error from getEdgeByNode: %s", lwt_be_lastErrorMessage(topo->be_iface)); + /* cberror(topo->be_data, "Error from cb_getClosestEdge"); */ + _lwt_release_edges(closestEdge, 1); + return -1; + } + for (i=0; iedge_id); + _lwt_release_edges(edges, numedges); + _lwt_release_edges(closestEdge, 1); + return -1; + } + LWDEBUGF(1, "lwt_be_getEdgeByNode returned %d edges", numedges); + _lwt_release_edges(edges, numedges); + _lwt_release_edges(closestEdge, 1); + return containingFace; + } + + /* Closest point is a node, but query point is NOT on the node */ + + /* let's do azimuth computation */ + edgeend ee; + if ( ! azimuth_pt_pt(closestPointOnEdge, queryPoint, &ee.myaz) ) { + lwerror("error computing azimuth of query point [%.15g %.15g,%.15g %.15g]", + closestPointOnEdge->x, closestPointOnEdge->y, + queryPoint->x, queryPoint->y); + _lwt_release_edges(closestEdge, 1); + return -1; + } + + LWDEBUGF(1, "Query point azimuth is %g", ee.myaz); + + int found = _lwt_FindAdjacentEdges( topo, closestNode, &ee, NULL, -1 ); + if ( ! found ) { + lwerror("Unexpected backend return: _lwt_FindAdjacentEdges(%d) found no edges when we previously found edge %d ending on that node", + closestNode, closestEdge->edge_id); + _lwt_release_edges(closestEdge, 1); + return -1; + } + + _lwt_release_edges(closestEdge, 1); + return ee.cwFace; + + } + + LWDEBUG(1, "Closest point is NOT a node"); + + /* If this edge has the same face on the left and right sides + * we found the face containing our query point */ + if ( closestEdge->face_left == closestEdge->face_right ) + { + containingFace = closestEdge->face_left; + _lwt_release_edges(closestEdge, 1); + return containingFace; + } + + if ( dist == 0 ) + { + /* We checked the dangling case above */ + _lwt_release_edges(closestEdge, 1); + lwerror("Two or more faces found"); + return -1; + } + + /* Find on which side of the segment the query point lays */ + if ( closestPointVertex != closestEdge->geom->points->npoints ) + { + /* Closest point is a vertex of the closest segment */ + LWDEBUGF(1, "Closest point is vertex %d of closest segment", closestPointVertex); + + /* + * We need to check if rotating clockwise the line + * from previous vertex to closest vertex clockwise + * around the closest vertex encounters + * the line to query point first (which means it's on the left + * of the closest edge) or the line to next vertex first (which + * means the query point is on the right) + */ + + uint32_t prevVertexIndex = closestPointVertex > 0 ? + closestPointVertex - 1u : + closestEdge->geom->points->npoints - 2u; /* last vertex would be == first vertex, this being a closed edge */ + + const POINT2D *prevVertex = getPoint2d_cp( + closestEdge->geom->points, + prevVertexIndex + ); + + LWDEBUGF(1, "Previous vertex %u is POINT(%.15g %.15g)", + prevVertexIndex, + prevVertex->x, + prevVertex->y + ); + + uint32_t nextVertexIndex = closestPointVertex == closestEdge->geom->points->npoints - 1u ? + 1u : /* first point would be == last point, this being a closed edge */ + closestPointVertex + 1u; + + const POINT2D *nextVertex = getPoint2d_cp( + closestEdge->geom->points, + nextVertexIndex + ); + + LWDEBUGF(1, "Next vertex %u is POINT(%.15g %.15g)", + nextVertexIndex, + nextVertex->x, + nextVertex->y + ); + + + double azS0; /* azimuth from previous vertex to closestPointVertex */ + double azS1; /* azimuth from closestPointVertex to next vertex */ + double azSL; /* azimuth from closestPointVertex to query point */ + + if ( ! azimuth_pt_pt(closestPointOnEdge, prevVertex, &azS0)) { + lwerror("error computing azimuth of segment to closest point [%.15g %.15g,%.15g %.15g]", + closestPointOnEdge->x, closestPointOnEdge->y, + prevVertex->x, prevVertex->y); + _lwt_release_edges(closestEdge, 1); + return -1; + } + if ( ! azimuth_pt_pt(closestPointOnEdge, nextVertex, &azS1)) { + lwerror("error computing azimuth of segment from closest point [%.15g %.15g,%.15g %.15g]", + closestPointOnEdge->x, closestPointOnEdge->y, + nextVertex->x, nextVertex->y); + _lwt_release_edges(closestEdge, 1); + return -1; + } + if ( ! azimuth_pt_pt(closestPointOnEdge, queryPoint, &azSL) ) { + lwerror("error computing azimuth of queryPoint [%.15g %.15g,%.15g %.15g]", + closestPointOnEdge->x, closestPointOnEdge->y, + queryPoint->x, queryPoint->y); + _lwt_release_edges(closestEdge, 1); + return -1; + } + + double angle_S0_S1 = azS1 - azS0; + if ( angle_S0_S1 < 0 ) angle_S0_S1 += 2 * M_PI; + + double angle_S0_SL = azSL - azS0; + if ( angle_S0_SL < 0 ) angle_S0_SL += 2 * M_PI; + + LWDEBUGF(1, "Azimuths from closest (vertex) point: P:%g, N:%g (+%g), Q:%g (+%g)", + azS0, + azS1, angle_S0_S1, + azSL, angle_S0_SL + ); + if ( angle_S0_SL < angle_S0_S1 ) + { + /* line to query point was encountered first, is on the left */ + containingFace = closestEdge->face_left; + } + else + { + /* line to query point was NOT encountered first, is on the right */ + containingFace = closestEdge->face_right; + } + } + else + { + /* Closest point is internal to closest segment, we can use + * lw_segment_side */ + + LWDEBUGF(1, "Closest point is internal to closest segment, calling lw_segment_side((%g,%g),(%g,%g),(%g,%g)", + closestSegmentP0->x, + closestSegmentP0->y, + closestSegmentP1->x, + closestSegmentP1->y, + queryPoint->x, + queryPoint->y + ); + + closestSegmentSide = lw_segment_side(closestSegmentP0, closestSegmentP1, queryPoint); + LWDEBUGF(1, "Side of closest segment query point falls on: %d", closestSegmentSide); + + if ( closestSegmentSide == -1 ) /* left */ + { + containingFace = closestEdge->face_left; + } + else if ( closestSegmentSide == 1 ) /* right */ + { + containingFace = closestEdge->face_right; + } + else + { + lwerror("Unexpected collinearity reported from lw_segment_side"); + _lwt_release_edges(closestEdge, 1); + return -1; + } + + } + + _lwt_release_edges(closestEdge, 1); + return containingFace; +} diff --git a/mgist-postgis/liblwgeom/lwgeom_transform.c b/mgist-postgis/liblwgeom/lwgeom_transform.c new file mode 100644 index 0000000..e5b4180 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeom_transform.c @@ -0,0 +1,385 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2001-2003 Refractions Research Inc. + * + **********************************************************************/ + + +// #include "../postgis_config.h" +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" +#include + +/** convert decimal degress to radians */ +static void +to_rad(POINT4D *pt) +{ + pt->x *= M_PI/180.0; + pt->y *= M_PI/180.0; +} + +/** convert radians to decimal degress */ +static void +to_dec(POINT4D *pt) +{ + pt->x *= 180.0/M_PI; + pt->y *= 180.0/M_PI; +} + +/***************************************************************************/ + +LWPROJ * +lwproj_from_str(const char* str_in, const char* str_out) +{ + uint8_t source_is_latlong = LW_FALSE; + double semi_major_metre = DBL_MAX, semi_minor_metre = DBL_MAX; + + /* Usable inputs? */ + if (! (str_in && str_out)) + return NULL; + + PJ* pj = proj_create_crs_to_crs(PJ_DEFAULT_CTX, str_in, str_out, NULL); + if (!pj) + return NULL; + + /* Fill in geodetic parameter information when a null-transform */ + /* is passed, because that's how we signal we want to store */ + /* that info in the cache */ + if (strcmp(str_in, str_out) == 0) + { + PJ *pj_source_crs = proj_get_source_crs(PJ_DEFAULT_CTX, pj); + PJ *pj_ellps; + PJ_TYPE pj_type = proj_get_type(pj_source_crs); + if (pj_type == PJ_TYPE_UNKNOWN) + { + proj_destroy(pj); + lwerror("%s: unable to access source crs type", __func__); + return NULL; + } + source_is_latlong = (pj_type == PJ_TYPE_GEOGRAPHIC_2D_CRS) || (pj_type == PJ_TYPE_GEOGRAPHIC_3D_CRS); + + pj_ellps = proj_get_ellipsoid(PJ_DEFAULT_CTX, pj_source_crs); + proj_destroy(pj_source_crs); + if (!pj_ellps) + { + proj_destroy(pj); + lwerror("%s: unable to access source crs ellipsoid", __func__); + return NULL; + } + if (!proj_ellipsoid_get_parameters(PJ_DEFAULT_CTX, + pj_ellps, + &semi_major_metre, + &semi_minor_metre, + NULL, + NULL)) + { + proj_destroy(pj_ellps); + proj_destroy(pj); + lwerror("%s: unable to access source crs ellipsoid parameters", __func__); + return NULL; + } + proj_destroy(pj_ellps); + } + + /* Add in an axis swap if necessary */ + PJ* pj_norm = proj_normalize_for_visualization(PJ_DEFAULT_CTX, pj); + /* Swap failed for some reason? Fall back to coordinate operation */ + if (!pj_norm) + pj_norm = pj; + /* Swap is not a copy of input? Clean up input */ + else if (pj != pj_norm) + proj_destroy(pj); + + /* Allocate and populate return value */ + LWPROJ *lp = lwalloc(sizeof(LWPROJ)); + lp->pj = pj_norm; /* Caller is going to have to explicitly proj_destroy this */ + lp->pipeline_is_forward = true; + lp->source_is_latlong = source_is_latlong; + lp->source_semi_major_metre = semi_major_metre; + lp->source_semi_minor_metre = semi_minor_metre; + return lp; +} + +LWPROJ * +lwproj_from_str_pipeline(const char* str_pipeline, bool is_forward) +{ + /* Usable inputs? */ + if (!str_pipeline) + return NULL; + + PJ* pj = proj_create(PJ_DEFAULT_CTX, str_pipeline); + if (!pj) + return NULL; + + /* check we have a transform, not a crs */ + if (proj_is_crs(pj)) + return NULL; + + /* Add in an axis swap if necessary */ + PJ* pj_norm = proj_normalize_for_visualization(PJ_DEFAULT_CTX, pj); + if (!pj_norm) + pj_norm = pj; + /* Swap is not a copy of input? Clean up input */ + else if (pj != pj_norm) + proj_destroy(pj); + + /* Allocate and populate return value */ + LWPROJ *lp = lwalloc(sizeof(LWPROJ)); + lp->pj = pj_norm; /* Caller is going to have to explicitly proj_destroy this */ + lp->pipeline_is_forward = is_forward; + + /* this is stuff for geography calculations; doesn't matter here */ + lp->source_is_latlong = LW_FALSE; + lp->source_semi_major_metre = DBL_MAX; + lp->source_semi_minor_metre = DBL_MAX; + return lp; +} + +int +lwgeom_transform_from_str(LWGEOM *geom, const char* instr, const char* outstr) +{ + LWPROJ *lp = lwproj_from_str(instr, outstr); + if (!lp) + { + PJ *pj_in = proj_create(PJ_DEFAULT_CTX, instr); + if (!pj_in) + { + proj_errno_reset(NULL); + lwerror("could not parse proj string '%s'", instr); + } + proj_destroy(pj_in); + + PJ *pj_out = proj_create(PJ_DEFAULT_CTX, outstr); + if (!pj_out) + { + proj_errno_reset(NULL); + lwerror("could not parse proj string '%s'", outstr); + } + proj_destroy(pj_out); + lwerror("%s: Failed to transform", __func__); + return LW_FAILURE; + } + int ret = lwgeom_transform(geom, lp); + proj_destroy(lp->pj); + lwfree(lp); + return ret; +} + +int +lwgeom_transform_pipeline(LWGEOM *geom, const char* pipelinestr, bool is_forward) +{ + LWPROJ *lp = lwproj_from_str_pipeline(pipelinestr, is_forward); + if (!lp) + { + PJ *pj_in = proj_create(PJ_DEFAULT_CTX, pipelinestr); + if (!pj_in) + { + proj_errno_reset(NULL); + lwerror("could not parse coordinate operation '%s'", pipelinestr); + } + proj_destroy(pj_in); + lwerror("%s: Failed to transform", __func__); + return LW_FAILURE; + } + int ret = lwgeom_transform(geom, lp); + proj_destroy(lp->pj); + lwfree(lp); + return ret; +} + +int +box3d_transform(GBOX *gbox, LWPROJ *pj) +{ + POINT4D pt; + POINTARRAY *pa = ptarray_construct(0, 0, 4); + pt = (POINT4D){gbox->xmin, gbox->ymin, 0, 0}; + ptarray_set_point4d(pa, 0, &pt); + + pt = (POINT4D){gbox->xmax, gbox->ymin, 0, 0}; + ptarray_set_point4d(pa, 1, &pt); + + pt = (POINT4D){gbox->xmax, gbox->ymax, 0, 0}; + ptarray_set_point4d(pa, 2, &pt); + + pt = (POINT4D){gbox->xmin, gbox->ymax, 0, 0}; + ptarray_set_point4d(pa, 3, &pt); + + ptarray_transform(pa, pj); + return ptarray_calculate_gbox_cartesian(pa, gbox); +} + +int +ptarray_transform(POINTARRAY *pa, LWPROJ *pj) +{ + uint32_t i; + POINT4D p; + size_t n_converted; + size_t n_points = pa->npoints; + size_t point_size = ptarray_point_size(pa); + int has_z = ptarray_has_z(pa); + double *pa_double = (double*)(pa->serialized_pointlist); + + PJ_DIRECTION direction = pj->pipeline_is_forward ? PJ_FWD : PJ_INV; + + /* Convert to radians if necessary */ + if (proj_angular_input(pj->pj, direction)) + { + for (i = 0; i < pa->npoints; i++) + { + getPoint4d_p(pa, i, &p); + to_rad(&p); + ptarray_set_point4d(pa, i, &p); + } + } + + if (n_points == 1) + { + /* For single points it's faster to call proj_trans */ + PJ_XYZT v = {pa_double[0], pa_double[1], has_z ? pa_double[2] : 0.0, 0.0}; + PJ_COORD c; + c.xyzt = v; + PJ_COORD t = proj_trans(pj->pj, direction, c); + + int pj_errno_val = proj_errno_reset(pj->pj); + if (pj_errno_val) + { + lwerror("transform: %s (%d)", proj_errno_string(pj_errno_val), pj_errno_val); + return LW_FAILURE; + } + pa_double[0] = (t.xyzt).x; + pa_double[1] = (t.xyzt).y; + if (has_z) + pa_double[2] = (t.xyzt).z; + } + else + { + /* + * size_t proj_trans_generic(PJ *P, PJ_DIRECTION direction, + * double *x, size_t sx, size_t nx, + * double *y, size_t sy, size_t ny, + * double *z, size_t sz, size_t nz, + * double *t, size_t st, size_t nt) + */ + + n_converted = proj_trans_generic(pj->pj, + direction, + pa_double, + point_size, + n_points, /* X */ + pa_double + 1, + point_size, + n_points, /* Y */ + has_z ? pa_double + 2 : NULL, + has_z ? point_size : 0, + has_z ? n_points : 0, /* Z */ + NULL, + 0, + 0 /* M */ + ); + + if (n_converted != n_points) + { + lwerror("ptarray_transform: converted (%d) != input (%d)", n_converted, n_points); + return LW_FAILURE; + } + + int pj_errno_val = proj_errno_reset(pj->pj); + if (pj_errno_val) + { + lwerror("transform: %s (%d)", proj_errno_string(pj_errno_val), pj_errno_val); + return LW_FAILURE; + } + } + + /* Convert radians to degrees if necessary */ + if (proj_angular_output(pj->pj, direction)) + { + for (i = 0; i < pa->npoints; i++) + { + getPoint4d_p(pa, i, &p); + to_dec(&p); + ptarray_set_point4d(pa, i, &p); + } + } + + return LW_SUCCESS; +} + +/** + * Transform given LWGEOM geometry + * from inpj projection to outpj projection + */ +int +lwgeom_transform(LWGEOM *geom, LWPROJ *pj) +{ + uint32_t i; + + /* No points to transform in an empty! */ + if ( lwgeom_is_empty(geom) ) + return LW_SUCCESS; + + switch(geom->type) + { + case POINTTYPE: + case LINETYPE: + case CIRCSTRINGTYPE: + case TRIANGLETYPE: + { + LWLINE *g = (LWLINE*)geom; + if ( ! ptarray_transform(g->points, pj) ) return LW_FAILURE; + break; + } + case POLYGONTYPE: + { + LWPOLY *g = (LWPOLY*)geom; + for ( i = 0; i < g->nrings; i++ ) + { + if ( ! ptarray_transform(g->rings[i], pj) ) return LW_FAILURE; + } + break; + } + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + case COMPOUNDTYPE: + case CURVEPOLYTYPE: + case MULTICURVETYPE: + case MULTISURFACETYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + { + LWCOLLECTION *g = (LWCOLLECTION*)geom; + for ( i = 0; i < g->ngeoms; i++ ) + { + if ( ! lwgeom_transform(g->geoms[i], pj) ) return LW_FAILURE; + } + break; + } + default: + { + lwerror("lwgeom_transform: Cannot handle type '%s'", + lwtype_name(geom->type)); + return LW_FAILURE; + } + } + return LW_SUCCESS; +} diff --git a/mgist-postgis/liblwgeom/lwgeom_wrapx.c b/mgist-postgis/liblwgeom/lwgeom_wrapx.c new file mode 100644 index 0000000..194672e --- /dev/null +++ b/mgist-postgis/liblwgeom/lwgeom_wrapx.c @@ -0,0 +1,223 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2016 Sandro Santilli + * + **********************************************************************/ + +// #include "../postgis_config.h" +/*#define POSTGIS_DEBUG_LEVEL 4*/ +#include "lwgeom_geos.h" +#include "liblwgeom_internal.h" + +#include +#include + +LWGEOM* lwgeom_wrapx(const LWGEOM* lwgeom_in, double cutx, double amount); +static LWCOLLECTION* lwcollection_wrapx(const LWCOLLECTION* lwcoll_in, double cutx, double amount); + +static LWGEOM* +lwgeom_split_wrapx(const LWGEOM* geom_in, double cutx, double amount) +{ + LWGEOM *blade, *split; + POINTARRAY *bladepa; + POINT4D pt; + const GBOX *box_in; + AFFINE affine = { + 1, 0, 0, + 0, 1, 0, + 0, 0, 1, + amount, 0, 0, + }; + + /* Extract box */ + /* TODO: check if the bbox should be force-recomputed */ + box_in = lwgeom_get_bbox(geom_in); + if ( ! box_in ) { + /* must be empty */ + return lwgeom_clone_deep(geom_in); + } + + LWDEBUGF(2, "BOX X range is %g..%g, cutx:%g, amount:%g", box_in->xmin, box_in->xmax, cutx, amount); + + /* Check if geometry is fully on the side needing shift */ + if ( ( amount < 0 && box_in->xmin >= cutx ) || ( amount > 0 && box_in->xmax <= cutx ) ) + { + split = lwgeom_clone_deep(geom_in); + lwgeom_affine(split, &affine); + LWDEBUGG(2, split, "returning the translated geometry"); + return split; + } + + /* Check if geometry is fully on the side needing no shift */ + if ( ( amount < 0 && box_in->xmax <= cutx ) || ( amount > 0 && box_in->xmin >= cutx ) ) + { + split = lwgeom_clone_deep(geom_in); + LWDEBUGG(2, split, "returning the cloned geometry"); + return split; + } + + /* We need splitting here */ + + /* construct blade */ + bladepa = ptarray_construct(0, 0, 2); + pt.x = cutx; + pt.y = box_in->ymin - 1; + ptarray_set_point4d(bladepa, 0, &pt); + pt.y = box_in->ymax + 1; + ptarray_set_point4d(bladepa, 1, &pt); + blade = lwline_as_lwgeom(lwline_construct(geom_in->srid, NULL, bladepa)); + + LWDEBUG(2, "splitting the geometry"); + + /* split by blade */ + split = lwgeom_split(geom_in, blade); + lwgeom_free(blade); + if ( ! split ) { + lwerror("%s:%d - lwgeom_split_wrapx: %s", __FILE__, __LINE__, lwgeom_geos_errmsg); + return NULL; + } + LWDEBUGG(2, split, "split geometry"); + + + /* iterate over components, translate if needed */ + const LWCOLLECTION *col = lwgeom_as_lwcollection(split); + if ( ! col ) { + /* not split, this is unexpected */ + lwnotice("WARNING: unexpected lack of split in lwgeom_split_wrapx"); + return lwgeom_clone_deep(geom_in); + } + LWCOLLECTION *col_out = lwcollection_wrapx(col, cutx, amount); + lwgeom_free(split); + + /* unary-union the result (homogenize too ?) */ + LWGEOM* out = lwgeom_unaryunion(lwcollection_as_lwgeom(col_out)); + LWDEBUGF(2, "col_out:%p, unaryunion_out:%p", col_out, out); + LWDEBUGG(2, out, "unary-unioned"); + + lwcollection_free(col_out); + + return out; +} + +static LWCOLLECTION* +lwcollection_wrapx(const LWCOLLECTION* lwcoll_in, double cutx, double amount) +{ + LWGEOM** wrap_geoms; + LWCOLLECTION* out; + uint32_t i; + int outtype = lwcoll_in->type; + + wrap_geoms = lwalloc(lwcoll_in->ngeoms * sizeof(LWGEOM*)); + if ( ! wrap_geoms ) + { + lwerror("Out of virtual memory"); + return NULL; + } + + for (i=0; ingeoms; ++i) + { + LWDEBUGF(3, "Wrapping collection element %d", i); + wrap_geoms[i] = lwgeom_wrapx(lwcoll_in->geoms[i], cutx, amount); + /* an exception should prevent this from ever returning NULL */ + if ( ! wrap_geoms[i] ) { + uint32_t j; + lwnotice("Error wrapping geometry, cleaning up"); + for (j = 0; j < i; j++) + { + lwnotice("cleaning geometry %d (%p)", j, wrap_geoms[j]); + lwgeom_free(wrap_geoms[j]); + } + lwfree(wrap_geoms); + lwnotice("cleanup complete"); + return NULL; + } + if ( outtype != COLLECTIONTYPE ) { + if ( MULTITYPE[wrap_geoms[i]->type] != outtype ) + { + outtype = COLLECTIONTYPE; + } + } + } + + /* Now wrap_geoms has wrap_geoms_size geometries */ + out = lwcollection_construct(outtype, lwcoll_in->srid, NULL, + lwcoll_in->ngeoms, wrap_geoms); + + return out; +} + +/* exported */ +LWGEOM* +lwgeom_wrapx(const LWGEOM* lwgeom_in, double cutx, double amount) +{ + /* Nothing to wrap in an empty geom */ + if ( lwgeom_is_empty(lwgeom_in) ) + { + LWDEBUG(2, "geom is empty, cloning"); + return lwgeom_clone_deep(lwgeom_in); + } + + /* Nothing to wrap if shift amount is zero */ + if ( amount == 0 ) + { + LWDEBUG(2, "amount is zero, cloning"); + return lwgeom_clone_deep(lwgeom_in); + } + + switch (lwgeom_in->type) + { + case LINETYPE: + case POLYGONTYPE: + LWDEBUG(2, "split-wrapping line or polygon"); + return lwgeom_split_wrapx(lwgeom_in, cutx, amount); + + case POINTTYPE: + { + const LWPOINT *pt = lwgeom_as_lwpoint(lwgeom_clone_deep(lwgeom_in)); + POINT4D pt4d; + getPoint4d_p(pt->point, 0, &pt4d); + + LWDEBUGF(2, "POINT X is %g, cutx:%g, amount:%g", pt4d.x, cutx, amount); + + if ( ( amount < 0 && pt4d.x > cutx ) || ( amount > 0 && pt4d.x < cutx ) ) + { + pt4d.x += amount; + ptarray_set_point4d(pt->point, 0, &pt4d); + } + return lwpoint_as_lwgeom(pt); + } + + case MULTIPOINTTYPE: + case MULTIPOLYGONTYPE: + case MULTILINETYPE: + case COLLECTIONTYPE: + LWDEBUG(2, "collection-wrapping multi"); + return lwcollection_as_lwgeom( + lwcollection_wrapx((const LWCOLLECTION*)lwgeom_in, cutx, amount) + ); + + default: + lwerror("Wrapping of %s geometries is unsupported", + lwtype_name(lwgeom_in->type)); + return NULL; + } + +} diff --git a/mgist-postgis/liblwgeom/lwhomogenize.c b/mgist-postgis/liblwgeom/lwhomogenize.c new file mode 100644 index 0000000..1d52ed9 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwhomogenize.c @@ -0,0 +1,271 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2010 Olivier Courtin + * + **********************************************************************/ + + +#include +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + + +typedef struct { + int cnt[NUMTYPES]; + LWCOLLECTION* buf[NUMTYPES]; +} HomogenizeBuffer; + +static void +init_homogenizebuffer(HomogenizeBuffer *buffer) +{ + int i; + for ( i = 0; i < NUMTYPES; i++ ) + { + buffer->cnt[i] = 0; + buffer->buf[i] = NULL; + } +} + +/* +static void +free_homogenizebuffer(HomogenizeBuffer *buffer) +{ + int i; + for ( i = 0; i < NUMTYPES; i++ ) + { + if ( buffer->buf[i] ) + { + lwcollection_free(buffer->buf[i]); + } + } +} +*/ + +/* +** Given a generic collection, return the "simplest" form. +** +** eg: GEOMETRYCOLLECTION(MULTILINESTRING()) => MULTILINESTRING() +** +** GEOMETRYCOLLECTION(MULTILINESTRING(), MULTILINESTRING(), POINT()) +** => GEOMETRYCOLLECTION(MULTILINESTRING(), POINT()) +** +** In general, if the subcomponents are homogeneous, return a properly +** typed collection. +** Otherwise, return a generic collection, with the subtypes in minimal +** typed collections. +*/ +static void +lwcollection_build_buffer(const LWCOLLECTION *col, HomogenizeBuffer *buffer) +{ + uint32_t i; + + if (!col || lwcollection_is_empty(col)) + return; + + for (i = 0; i < col->ngeoms; i++) + { + LWGEOM *geom = col->geoms[i]; + switch (geom->type) + { + case POINTTYPE: + case LINETYPE: + case CIRCSTRINGTYPE: + case COMPOUNDTYPE: + case TRIANGLETYPE: + case CURVEPOLYTYPE: + case POLYGONTYPE: + /* Init if necessary */ + if (!buffer->buf[geom->type]) + { + LWCOLLECTION *bufcol = lwcollection_construct_empty( + COLLECTIONTYPE, col->srid, FLAGS_GET_Z(col->flags), FLAGS_GET_M(col->flags)); + bufcol->type = lwtype_get_collectiontype(geom->type); + buffer->buf[geom->type] = bufcol; + } + /* Add sub-geom to buffer */ + lwcollection_add_lwgeom(buffer->buf[geom->type], lwgeom_clone_deep(geom)); + /* Increment count for this singleton type */ + buffer->cnt[geom->type]++; + break; + default: + lwcollection_build_buffer(lwgeom_as_lwcollection(geom), buffer); + break; + } + } +} + +static LWGEOM* +lwcollection_homogenize(const LWCOLLECTION *col) +{ + int i; + int ntypes = 0; + int type = 0; + LWGEOM *outgeom = NULL; + + HomogenizeBuffer buffer; + + /* Sort all the parts into a buffer */ + init_homogenizebuffer(&buffer); + lwcollection_build_buffer(col, &buffer); + + /* Check for homogeneity */ + for ( i = 0; i < NUMTYPES; i++ ) + { + if ( buffer.cnt[i] > 0 ) + { + ntypes++; + type = i; + } + } + + /* No types? Huh. Return empty. */ + if ( ntypes == 0 ) + { + LWCOLLECTION *outcol; + outcol = lwcollection_construct_empty(COLLECTIONTYPE, col->srid, FLAGS_GET_Z(col->flags), FLAGS_GET_M(col->flags)); + outgeom = lwcollection_as_lwgeom(outcol); + } + /* One type, return homogeneous collection */ + else if ( ntypes == 1 ) + { + LWCOLLECTION *outcol; + outcol = buffer.buf[type]; + if ( outcol->ngeoms == 1 ) + { + outgeom = outcol->geoms[0]; + outcol->ngeoms=0; lwcollection_free(outcol); + } + else + { + outgeom = lwcollection_as_lwgeom(outcol); + } + outgeom->srid = col->srid; + } + /* Bah, more than out type, return anonymous collection */ + else if ( ntypes > 1 ) + { + int j; + LWCOLLECTION *outcol; + outcol = lwcollection_construct_empty(COLLECTIONTYPE, col->srid, FLAGS_GET_Z(col->flags), FLAGS_GET_M(col->flags)); + for ( j = 0; j < NUMTYPES; j++ ) + { + if ( buffer.buf[j] ) + { + LWCOLLECTION *bcol = buffer.buf[j]; + if ( bcol->ngeoms == 1 ) + { + lwcollection_add_lwgeom(outcol, bcol->geoms[0]); + bcol->ngeoms=0; lwcollection_free(bcol); + } + else + { + lwcollection_add_lwgeom(outcol, lwcollection_as_lwgeom(bcol)); + } + } + } + outgeom = lwcollection_as_lwgeom(outcol); + } + + return outgeom; +} + + + + + +/* +** Given a generic geometry, return the "simplest" form. +** +** eg: +** LINESTRING() => LINESTRING() +** +** MULTILINESTRING(with a single line) => LINESTRING() +** +** GEOMETRYCOLLECTION(MULTILINESTRING()) => MULTILINESTRING() +** +** GEOMETRYCOLLECTION(MULTILINESTRING(), MULTILINESTRING(), POINT()) +** => GEOMETRYCOLLECTION(MULTILINESTRING(), POINT()) +*/ +LWGEOM * +lwgeom_homogenize(const LWGEOM *geom) +{ + LWGEOM *hgeom; + + /* EMPTY Geometry */ + if (lwgeom_is_empty(geom)) + { + if( lwgeom_is_collection(geom) ) + { + return lwcollection_as_lwgeom(lwcollection_construct_empty(geom->type, geom->srid, lwgeom_has_z(geom), lwgeom_has_m(geom))); + } + + return lwgeom_clone_deep(geom); + } + + switch (geom->type) + { + + /* Return simple geometries untouched */ + case POINTTYPE: + case LINETYPE: + case CIRCSTRINGTYPE: + case COMPOUNDTYPE: + case TRIANGLETYPE: + case CURVEPOLYTYPE: + case POLYGONTYPE: + return lwgeom_clone_deep(geom); + + /* Process homogeneous geometries lightly */ + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case MULTICURVETYPE: + case MULTISURFACETYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + { + LWCOLLECTION *col = (LWCOLLECTION*)geom; + + /* Strip single-entry multi-geometries down to singletons */ + if ( col->ngeoms == 1 ) + { + hgeom = lwgeom_clone_deep((LWGEOM *)(col->geoms[0])); + hgeom->srid = geom->srid; + if (geom->bbox) + hgeom->bbox = gbox_copy(geom->bbox); + return hgeom; + } + + /* Return proper multigeometry untouched */ + return lwgeom_clone_deep(geom); + } + + /* Work on anonymous collections separately */ + case COLLECTIONTYPE: + return lwcollection_homogenize((LWCOLLECTION *) geom); + } + + /* Unknown type */ + lwerror("lwgeom_homogenize: Geometry Type not supported (%i)", + lwtype_name(geom->type)); + + return NULL; /* Never get here! */ +} diff --git a/mgist-postgis/liblwgeom/lwin_encoded_polyline.c b/mgist-postgis/liblwgeom/lwin_encoded_polyline.c new file mode 100644 index 0000000..050e80d --- /dev/null +++ b/mgist-postgis/liblwgeom/lwin_encoded_polyline.c @@ -0,0 +1,81 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * +* Copyright 2014 Kashif Rasul and + * + **********************************************************************/ + + +#include +#include +#include + +#include "liblwgeom.h" +// #include "../postgis_config.h" + +LWGEOM* +lwgeom_from_encoded_polyline(const char *encodedpolyline, int precision) +{ + LWGEOM *geom = NULL; + POINTARRAY *pa = NULL; + int length = strlen(encodedpolyline); + int idx = 0; + double scale = pow(10,precision); + + float latitude = 0.0f; + float longitude = 0.0f; + + pa = ptarray_construct_empty(LW_FALSE, LW_FALSE, 1); + + while (idx < length) { + POINT4D pt; + char byte = 0; + + int res = 0; + char shift = 0; + do { + byte = encodedpolyline[idx++] - 63; + res |= (byte & 0x1F) << shift; + shift += 5; + } while (byte >= 0x20); + float deltaLat = ((res & 1) ? ~(res >> 1) : (res >> 1)); + latitude += deltaLat; + + shift = 0; + res = 0; + do { + byte = encodedpolyline[idx++] - 63; + res |= (byte & 0x1F) << shift; + shift += 5; + } while (byte >= 0x20); + float deltaLon = ((res & 1) ? ~(res >> 1) : (res >> 1)); + longitude += deltaLon; + + pt.x = longitude/scale; + pt.y = latitude/scale; + pt.m = pt.z = 0.0; + ptarray_append_point(pa, &pt, LW_FALSE); + } + + geom = (LWGEOM *)lwline_construct(4326, NULL, pa); + lwgeom_add_bbox(geom); + + return geom; +} diff --git a/mgist-postgis/liblwgeom/lwin_geojson.c b/mgist-postgis/liblwgeom/lwin_geojson.c new file mode 100644 index 0000000..f2bd396 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwin_geojson.c @@ -0,0 +1,472 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2019 Darafei Praliaskouski + * Copyright 2013 Sandro Santilli + * Copyright 2011 Kashif Rasul + * + **********************************************************************/ + +#include "liblwgeom.h" +#include "lwgeom_log.h" +// #include "../postgis_config.h" + +#if defined(HAVE_LIBJSON) + +#define JSON_C_VERSION_013 (13 << 8) + +#include + +#if !defined(JSON_C_VERSION_NUM) || JSON_C_VERSION_NUM < JSON_C_VERSION_013 +#include +#endif + +#ifndef JSON_C_VERSION +/* Adds support for libjson < 0.10 */ +#define json_tokener_error_desc(x) json_tokener_errors[(x)] +#endif + +#include + +/* Prototype */ +static LWGEOM *parse_geojson(json_object *geojson, int *hasz); + +static inline json_object * +findMemberByName(json_object *poObj, const char *pszName) +{ + json_object *poTmp; + json_object_iter it; + + poTmp = poObj; + + if (!pszName || !poObj) + return NULL; + + it.key = NULL; + it.val = NULL; + it.entry = NULL; + + if (json_object_get_object(poTmp)) + { + if (!json_object_get_object(poTmp)->head) + { + lwerror("invalid GeoJSON representation"); + return NULL; + } + + for (it.entry = json_object_get_object(poTmp)->head; + (it.entry ? (it.key = (char *)it.entry->k, it.val = (json_object *)it.entry->v, it.entry) : 0); + it.entry = it.entry->next) + { + if (strcasecmp((char *)it.key, pszName) == 0) + return it.val; + } + } + + return NULL; +} + +static inline json_object * +parse_coordinates(json_object *geojson) +{ + json_object *coordinates = findMemberByName(geojson, "coordinates"); + if (!coordinates) + { + lwerror("Unable to find 'coordinates' in GeoJSON string"); + return NULL; + } + + if (json_type_array != json_object_get_type(coordinates)) + { + lwerror("The 'coordinates' in GeoJSON are not an array"); + return NULL; + } + return coordinates; +} + + +static inline int +parse_geojson_coord(json_object *poObj, int *hasz, POINTARRAY *pa) +{ + POINT4D pt = {0, 0, 0, 0}; + + if (json_object_get_type(poObj) == json_type_array) + { + json_object *poObjCoord = NULL; + const int nSize = json_object_array_length(poObj); + if (nSize == 0) + return LW_TRUE; + if (nSize < 2) + { + lwerror("Too few ordinates in GeoJSON"); + return LW_FAILURE; + } + + /* Read X coordinate */ + poObjCoord = json_object_array_get_idx(poObj, 0); + pt.x = json_object_get_double(poObjCoord); + + /* Read Y coordinate */ + poObjCoord = json_object_array_get_idx(poObj, 1); + pt.y = json_object_get_double(poObjCoord); + + if (nSize > 2) /* should this be >= 3 ? */ + { + /* Read Z coordinate */ + poObjCoord = json_object_array_get_idx(poObj, 2); + pt.z = json_object_get_double(poObjCoord); + *hasz = LW_TRUE; + } + } + else + { + /* If it's not an array, just don't handle it */ + lwerror("The 'coordinates' in GeoJSON are not sufficiently nested"); + return LW_FAILURE; + } + + return ptarray_append_point(pa, &pt, LW_TRUE); +} + +static inline LWGEOM * +parse_geojson_point(json_object *geojson, int *hasz) +{ + json_object *coords = parse_coordinates(geojson); + if (!coords) + return NULL; + POINTARRAY *pa = ptarray_construct_empty(1, 0, 1); + parse_geojson_coord(coords, hasz, pa); + return (LWGEOM *)lwpoint_construct(0, NULL, pa); +} + +static inline LWGEOM * +parse_geojson_linestring(json_object *geojson, int *hasz) +{ + json_object *points = parse_coordinates(geojson); + if (!points) + return NULL; + POINTARRAY *pa = ptarray_construct_empty(1, 0, 1); + const int nPoints = json_object_array_length(points); + for (int i = 0; i < nPoints; i++) + { + json_object *coords = json_object_array_get_idx(points, i); + parse_geojson_coord(coords, hasz, pa); + } + return (LWGEOM *)lwline_construct(0, NULL, pa); +} + +static inline LWPOLY * +parse_geojson_poly_rings(json_object *rings, int *hasz) +{ + if (!rings || json_object_get_type(rings) != json_type_array) + return NULL; + + int nRings = json_object_array_length(rings); + + /* No rings => POLYGON EMPTY */ + if (!nRings) + return lwpoly_construct_empty(0, 1, 0); + + /* Expecting up to nRings otherwise */ + POINTARRAY **ppa = (POINTARRAY **)lwalloc(sizeof(POINTARRAY *) * nRings); + int o = 0; + + for (int i = 0; i < nRings; i++) + { + json_object *points = json_object_array_get_idx(rings, i); + if (!points || json_object_get_type(points) != json_type_array) + { + for (int k = 0; k < o; k++) + ptarray_free(ppa[k]); + lwfree(ppa); + lwerror("The 'coordinates' in GeoJSON ring are not an array"); + return NULL; + } + int nPoints = json_object_array_length(points); + + /* Skip empty rings */ + if (!nPoints) + { + /* Empty outer? Don't promote first hole to outer, holes don't matter. */ + if (!i) + break; + else + continue; + } + + ppa[o] = ptarray_construct_empty(1, 0, 1); + for (int j = 0; j < nPoints; j++) + { + json_object *coords = NULL; + coords = json_object_array_get_idx(points, j); + if (LW_FAILURE == parse_geojson_coord(coords, hasz, ppa[o])) + { + for (int k = 0; k <= o; k++) + ptarray_free(ppa[k]); + lwfree(ppa); + lwerror("The 'coordinates' in GeoJSON are not sufficiently nested"); + return NULL; + } + } + o++; + } + + /* All the rings were empty! */ + if (!o) + { + lwfree(ppa); + return lwpoly_construct_empty(0, 1, 0); + } + + return lwpoly_construct(0, NULL, o, ppa); +} + +static inline LWGEOM * +parse_geojson_polygon(json_object *geojson, int *hasz) +{ + return (LWGEOM *)parse_geojson_poly_rings(parse_coordinates(geojson), hasz); +} + +static inline LWGEOM * +parse_geojson_multipoint(json_object *geojson, int *hasz) +{ + json_object *points = parse_coordinates(geojson); + if (!points) + return NULL; + LWMPOINT *geom = (LWMPOINT *)lwcollection_construct_empty(MULTIPOINTTYPE, 0, 1, 0); + + const int nPoints = json_object_array_length(points); + for (int i = 0; i < nPoints; ++i) + { + POINTARRAY *pa = ptarray_construct_empty(1, 0, 1); + json_object *coord = json_object_array_get_idx(points, i); + if (parse_geojson_coord(coord, hasz, pa)) + geom = lwmpoint_add_lwpoint(geom, lwpoint_construct(0, NULL, pa)); + else + { + lwmpoint_free(geom); + ptarray_free(pa); + return NULL; + } + } + + return (LWGEOM *)geom; +} + +static inline LWGEOM * +parse_geojson_multilinestring(json_object *geojson, int *hasz) +{ + json_object *mls = parse_coordinates(geojson); + if (!mls) + return NULL; + LWMLINE *geom = (LWMLINE *)lwcollection_construct_empty(MULTILINETYPE, 0, 1, 0); + const int nLines = json_object_array_length(mls); + for (int i = 0; i < nLines; ++i) + { + POINTARRAY *pa = ptarray_construct_empty(1, 0, 1); + json_object *coords = json_object_array_get_idx(mls, i); + + if (json_type_array == json_object_get_type(coords)) + { + const int nPoints = json_object_array_length(coords); + for (int j = 0; j < nPoints; ++j) + { + json_object *coord = json_object_array_get_idx(coords, j); + if (!parse_geojson_coord(coord, hasz, pa)) + { + lwmline_free(geom); + ptarray_free(pa); + return NULL; + } + } + geom = lwmline_add_lwline(geom, lwline_construct(0, NULL, pa)); + } + else + { + lwmline_free(geom); + ptarray_free(pa); + return NULL; + } + } + return (LWGEOM *)geom; +} + +static inline LWGEOM * +parse_geojson_multipolygon(json_object *geojson, int *hasz) +{ + json_object *polys = parse_coordinates(geojson); + if (!polys) + return NULL; + LWGEOM *geom = (LWGEOM *)lwcollection_construct_empty(MULTIPOLYGONTYPE, 0, 1, 0); + int nPolys = json_object_array_length(polys); + + for (int i = 0; i < nPolys; ++i) + { + json_object *rings = json_object_array_get_idx(polys, i); + LWPOLY *poly = parse_geojson_poly_rings(rings, hasz); + if (poly) + geom = (LWGEOM *)lwmpoly_add_lwpoly((LWMPOLY *)geom, poly); + } + + return geom; +} + +static inline LWGEOM * +parse_geojson_geometrycollection(json_object *geojson, int *hasz) +{ + json_object *poObjGeoms = findMemberByName(geojson, "geometries"); + if (!poObjGeoms) + { + lwerror("Unable to find 'geometries' in GeoJSON string"); + return NULL; + } + LWGEOM *geom = (LWGEOM *)lwcollection_construct_empty(COLLECTIONTYPE, 0, 1, 0); + + if (json_type_array == json_object_get_type(poObjGeoms)) + { + const int nGeoms = json_object_array_length(poObjGeoms); + for (int i = 0; i < nGeoms; ++i) + { + json_object *poObjGeom = json_object_array_get_idx(poObjGeoms, i); + LWGEOM *t = parse_geojson(poObjGeom, hasz); + if (t) + geom = (LWGEOM *)lwcollection_add_lwgeom((LWCOLLECTION *)geom, t); + else + { + lwgeom_free(geom); + return NULL; + } + } + } + + return geom; +} + +static inline LWGEOM * +parse_geojson(json_object *geojson, int *hasz) +{ + json_object *type = NULL; + const char *name; + + if (!geojson) + { + lwerror("invalid GeoJSON representation"); + return NULL; + } + + type = findMemberByName(geojson, "type"); + if (!type) + { + lwerror("unknown GeoJSON type"); + return NULL; + } + + name = json_object_get_string(type); + + if (strcasecmp(name, "Point") == 0) + return parse_geojson_point(geojson, hasz); + + if (strcasecmp(name, "LineString") == 0) + return parse_geojson_linestring(geojson, hasz); + + if (strcasecmp(name, "Polygon") == 0) + return parse_geojson_polygon(geojson, hasz); + + if (strcasecmp(name, "MultiPoint") == 0) + return parse_geojson_multipoint(geojson, hasz); + + if (strcasecmp(name, "MultiLineString") == 0) + return parse_geojson_multilinestring(geojson, hasz); + + if (strcasecmp(name, "MultiPolygon") == 0) + return parse_geojson_multipolygon(geojson, hasz); + + if (strcasecmp(name, "GeometryCollection") == 0) + return parse_geojson_geometrycollection(geojson, hasz); + + lwerror("invalid GeoJson representation"); + return NULL; /* Never reach */ +} + +#endif /* HAVE_LIBJSON */ + +LWGEOM * +lwgeom_from_geojson(const char *geojson, char **srs) +{ +#ifndef HAVE_LIBJSON + *srs = NULL; + lwerror("You need JSON-C for lwgeom_from_geojson"); + return NULL; +#else /* HAVE_LIBJSON */ + + /* Begin to Parse json */ + json_tokener *jstok = json_tokener_new(); + json_object *poObj = json_tokener_parse_ex(jstok, geojson, -1); + if (jstok->err != json_tokener_success) + { + char err[256]; + snprintf(err, 256, "%s (at offset %d)", json_tokener_error_desc(jstok->err), jstok->char_offset); + json_tokener_free(jstok); + json_object_put(poObj); + lwerror(err); + return NULL; + } + json_tokener_free(jstok); + + *srs = NULL; + json_object *poObjSrs = findMemberByName(poObj, "crs"); + if (poObjSrs != NULL) + { + json_object *poObjSrsType = findMemberByName(poObjSrs, "type"); + if (poObjSrsType != NULL) + { + json_object *poObjSrsProps = findMemberByName(poObjSrs, "properties"); + if (poObjSrsProps) + { + json_object *poNameURL = findMemberByName(poObjSrsProps, "name"); + if (poNameURL) + { + const char *pszName = json_object_get_string(poNameURL); + if (pszName) + { + *srs = lwalloc(strlen(pszName) + 1); + strcpy(*srs, pszName); + } + } + } + } + } + + int hasz = LW_FALSE; + LWGEOM *lwgeom = parse_geojson(poObj, &hasz); + json_object_put(poObj); + if (!lwgeom) + return NULL; + + if (!hasz) + { + LWGEOM *tmp = lwgeom_force_2d(lwgeom); + lwgeom_free(lwgeom); + lwgeom = tmp; + } + lwgeom_add_bbox(lwgeom); + return lwgeom; +#endif /* HAVE_LIBJSON */ +} diff --git a/mgist-postgis/liblwgeom/lwin_twkb.c b/mgist-postgis/liblwgeom/lwin_twkb.c new file mode 100644 index 0000000..445b4a9 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwin_twkb.c @@ -0,0 +1,673 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2014 Nicklas Avén + * + **********************************************************************/ + + +#include +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" +#include "varint.h" + +#define TWKB_IN_MAXCOORDS 4 + +/** +* Used for passing the parse state between the parsing functions. +*/ +typedef struct +{ + /* Pointers to the bytes */ + const uint8_t *twkb; /* Points to start of TWKB */ + const uint8_t *twkb_end; /* Points to end of TWKB */ + const uint8_t *pos; /* Current read position */ + + uint32_t check; /* Simple validity checks on geometries */ + uint32_t lwtype; /* Current type we are handling */ + + uint8_t has_bbox; + uint8_t has_size; + uint8_t has_idlist; + uint8_t has_z; + uint8_t has_m; + uint8_t is_empty; + + /* Precision factors to convert ints to double */ + double factor; + double factor_z; + double factor_m; + + uint64_t size; + + /* Info about current geometry */ + uint8_t magic_byte; /* the magic byte contain info about if twkb contain id, size info, bboxes and precision */ + + int ndims; /* Number of dimensions */ + + int64_t *coords; /* An array to keep delta values from 4 dimensions */ + +} twkb_parse_state; + + +/** +* Internal function declarations. +*/ +LWGEOM* lwgeom_from_twkb_state(twkb_parse_state *s); + + +/**********************************************************************/ + +/** +* Check that we are not about to read off the end of the WKB +* array. +*/ +static inline void twkb_parse_state_advance(twkb_parse_state *s, size_t next) +{ + if( (s->pos + next) > s->twkb_end) + { + lwerror("%s: TWKB structure does not match expected size!", __func__); + // lwnotice("TWKB structure does not match expected size!"); + } + + s->pos += next; +} + +static inline int64_t twkb_parse_state_varint(twkb_parse_state *s) +{ + size_t size; + int64_t val = varint_s64_decode(s->pos, s->twkb_end, &size); + twkb_parse_state_advance(s, size); + return val; +} + +static inline uint64_t twkb_parse_state_uvarint(twkb_parse_state *s) +{ + size_t size; + uint64_t val = varint_u64_decode(s->pos, s->twkb_end, &size); + twkb_parse_state_advance(s, size); + return val; +} + +static inline double twkb_parse_state_double(twkb_parse_state *s, double factor) +{ + size_t size; + int64_t val = varint_s64_decode(s->pos, s->twkb_end, &size); + twkb_parse_state_advance(s, size); + return val / factor; +} + +static inline void twkb_parse_state_varint_skip(twkb_parse_state *s) +{ + size_t size = varint_size(s->pos, s->twkb_end); + + if ( ! size ) + lwerror("%s: no varint to skip", __func__); + + twkb_parse_state_advance(s, size); + return; +} + + + +static uint32_t lwtype_from_twkb_type(uint8_t twkb_type) +{ + switch (twkb_type) + { + case 1: + return POINTTYPE; + case 2: + return LINETYPE; + case 3: + return POLYGONTYPE; + case 4: + return MULTIPOINTTYPE; + case 5: + return MULTILINETYPE; + case 6: + return MULTIPOLYGONTYPE; + case 7: + return COLLECTIONTYPE; + + default: /* Error! */ + lwerror("Unknown WKB type"); + return 0; + } + return 0; +} + +/** +* Byte +* Read a byte and advance the parse state forward. +*/ +static uint8_t byte_from_twkb_state(twkb_parse_state *s) +{ + uint8_t val = *(s->pos); + twkb_parse_state_advance(s, WKB_BYTE_SIZE); + return val; +} + + +/** +* POINTARRAY +* Read a dynamically sized point array and advance the parse state forward. +*/ +static POINTARRAY* ptarray_from_twkb_state(twkb_parse_state *s, uint32_t npoints) +{ + POINTARRAY *pa = NULL; + uint32_t ndims = s->ndims; + uint32_t i; + double *dlist; + + LWDEBUG(2,"Entering ptarray_from_twkb_state"); + LWDEBUGF(4,"Pointarray has %d points", npoints); + + /* Empty! */ + if( npoints == 0 ) + return ptarray_construct_empty(s->has_z, s->has_m, 0); + + pa = ptarray_construct(s->has_z, s->has_m, npoints); + dlist = (double*)(pa->serialized_pointlist); + for( i = 0; i < npoints; i++ ) + { + int j = 0; + /* X */ + s->coords[j] += twkb_parse_state_varint(s); + dlist[ndims*i + j] = s->coords[j] / s->factor; + j++; + /* Y */ + s->coords[j] += twkb_parse_state_varint(s); + dlist[ndims*i + j] = s->coords[j] / s->factor; + j++; + /* Z */ + if ( s->has_z ) + { + s->coords[j] += twkb_parse_state_varint(s); + dlist[ndims*i + j] = s->coords[j] / s->factor_z; + j++; + } + /* M */ + if ( s->has_m ) + { + s->coords[j] += twkb_parse_state_varint(s); + dlist[ndims*i + j] = s->coords[j] / s->factor_m; + j++; + } + } + + return pa; +} + +/** +* POINT +*/ +static LWPOINT* lwpoint_from_twkb_state(twkb_parse_state *s) +{ + static uint32_t npoints = 1; + POINTARRAY *pa; + + LWDEBUG(2,"Entering lwpoint_from_twkb_state"); + + if ( s->is_empty ) + return lwpoint_construct_empty(SRID_UNKNOWN, s->has_z, s->has_m); + + pa = ptarray_from_twkb_state(s, npoints); + return lwpoint_construct(SRID_UNKNOWN, NULL, pa); +} + +/** +* LINESTRING +*/ +static LWLINE* lwline_from_twkb_state(twkb_parse_state *s) +{ + uint32_t npoints; + POINTARRAY *pa; + + LWDEBUG(2,"Entering lwline_from_twkb_state"); + + if ( s->is_empty ) + return lwline_construct_empty(SRID_UNKNOWN, s->has_z, s->has_m); + + /* Read number of points */ + npoints = twkb_parse_state_uvarint(s); + + if ( npoints == 0 ) + return lwline_construct_empty(SRID_UNKNOWN, s->has_z, s->has_m); + + /* Read coordinates */ + pa = ptarray_from_twkb_state(s, npoints); + + if( pa == NULL ) + return lwline_construct_empty(SRID_UNKNOWN, s->has_z, s->has_m); + + if( s->check & LW_PARSER_CHECK_MINPOINTS && pa->npoints < 2 ) + { + lwerror("%s must have at least two points", lwtype_name(s->lwtype)); + return NULL; + } + + return lwline_construct(SRID_UNKNOWN, NULL, pa); +} + +/** +* POLYGON +*/ +static LWPOLY* lwpoly_from_twkb_state(twkb_parse_state *s) +{ + uint32_t nrings; + uint32_t i; + LWPOLY *poly; + + LWDEBUG(2,"Entering lwpoly_from_twkb_state"); + + if ( s->is_empty ) + return lwpoly_construct_empty(SRID_UNKNOWN, s->has_z, s->has_m); + + /* Read number of rings */ + nrings = twkb_parse_state_uvarint(s); + + /* Start w/ empty polygon */ + poly = lwpoly_construct_empty(SRID_UNKNOWN, s->has_z, s->has_m); + + LWDEBUGF(4,"Polygon has %d rings", nrings); + + /* Empty polygon? */ + if( nrings == 0 ) + return poly; + + for( i = 0; i < nrings; i++ ) + { + /* Ret number of points */ + uint32_t npoints = twkb_parse_state_uvarint(s); + POINTARRAY *pa = ptarray_from_twkb_state(s, npoints); + + /* Skip empty rings */ + if( pa == NULL ) + continue; + + /* Force first and last points to be the same. */ + if( ! ptarray_is_closed_2d(pa) ) + { + POINT4D pt; + getPoint4d_p(pa, 0, &pt); + ptarray_append_point(pa, &pt, LW_FALSE); + } + + /* Check for at least four points. */ + if( s->check & LW_PARSER_CHECK_MINPOINTS && pa->npoints < 4 ) + { + LWDEBUGF(2, "%s must have at least four points in each ring", lwtype_name(s->lwtype)); + lwerror("%s must have at least four points in each ring", lwtype_name(s->lwtype)); + return NULL; + } + + /* Add ring to polygon */ + if ( lwpoly_add_ring(poly, pa) == LW_FAILURE ) + { + LWDEBUG(2, "Unable to add ring to polygon"); + lwerror("Unable to add ring to polygon"); + } + + } + return poly; +} + + +/** +* MULTIPOINT +*/ +static LWCOLLECTION* lwmultipoint_from_twkb_state(twkb_parse_state *s) +{ + int ngeoms, i; + LWGEOM *geom = NULL; + LWCOLLECTION *col = lwcollection_construct_empty(s->lwtype, SRID_UNKNOWN, s->has_z, s->has_m); + + LWDEBUG(2,"Entering lwmultipoint_from_twkb_state"); + + if ( s->is_empty ) + return col; + + /* Read number of geometries */ + ngeoms = twkb_parse_state_uvarint(s); + LWDEBUGF(4,"Number of geometries %d", ngeoms); + + /* It has an idlist, we need to skip that */ + if ( s->has_idlist ) + { + for ( i = 0; i < ngeoms; i++ ) + twkb_parse_state_varint_skip(s); + } + + for ( i = 0; i < ngeoms; i++ ) + { + geom = lwpoint_as_lwgeom(lwpoint_from_twkb_state(s)); + if ( lwcollection_add_lwgeom(col, geom) == NULL ) + { + lwerror("Unable to add geometry (%p) to collection (%p)", geom, col); + return NULL; + } + } + + return col; +} + +/** +* MULTILINESTRING +*/ +static LWCOLLECTION* lwmultiline_from_twkb_state(twkb_parse_state *s) +{ + int ngeoms, i; + LWGEOM *geom = NULL; + LWCOLLECTION *col = lwcollection_construct_empty(s->lwtype, SRID_UNKNOWN, s->has_z, s->has_m); + + LWDEBUG(2,"Entering lwmultilinestring_from_twkb_state"); + + if ( s->is_empty ) + return col; + + /* Read number of geometries */ + ngeoms = twkb_parse_state_uvarint(s); + + LWDEBUGF(4,"Number of geometries %d",ngeoms); + + /* It has an idlist, we need to skip that */ + if ( s->has_idlist ) + { + for ( i = 0; i < ngeoms; i++ ) + twkb_parse_state_varint_skip(s); + } + + for ( i = 0; i < ngeoms; i++ ) + { + geom = lwline_as_lwgeom(lwline_from_twkb_state(s)); + if ( lwcollection_add_lwgeom(col, geom) == NULL ) + { + lwerror("Unable to add geometry (%p) to collection (%p)", geom, col); + return NULL; + } + } + + return col; +} + +/** +* MULTIPOLYGON +*/ +static LWCOLLECTION* lwmultipoly_from_twkb_state(twkb_parse_state *s) +{ + int ngeoms, i; + LWGEOM *geom = NULL; + LWCOLLECTION *col = lwcollection_construct_empty(s->lwtype, SRID_UNKNOWN, s->has_z, s->has_m); + + LWDEBUG(2,"Entering lwmultipolygon_from_twkb_state"); + + if ( s->is_empty ) + return col; + + /* Read number of geometries */ + ngeoms = twkb_parse_state_uvarint(s); + LWDEBUGF(4,"Number of geometries %d",ngeoms); + + /* It has an idlist, we need to skip that */ + if ( s->has_idlist ) + { + for ( i = 0; i < ngeoms; i++ ) + twkb_parse_state_varint_skip(s); + } + + for ( i = 0; i < ngeoms; i++ ) + { + geom = lwpoly_as_lwgeom(lwpoly_from_twkb_state(s)); + if ( lwcollection_add_lwgeom(col, geom) == NULL ) + { + lwerror("Unable to add geometry (%p) to collection (%p)", geom, col); + return NULL; + } + } + + return col; +} + + +/** +* COLLECTION, MULTIPOINTTYPE, MULTILINETYPE, MULTIPOLYGONTYPE +**/ +static LWCOLLECTION* lwcollection_from_twkb_state(twkb_parse_state *s) +{ + int ngeoms, i; + LWGEOM *geom = NULL; + LWCOLLECTION *col = lwcollection_construct_empty(s->lwtype, SRID_UNKNOWN, s->has_z, s->has_m); + + LWDEBUG(2,"Entering lwcollection_from_twkb_state"); + + if ( s->is_empty ) + return col; + + /* Read number of geometries */ + ngeoms = twkb_parse_state_uvarint(s); + + LWDEBUGF(4,"Number of geometries %d",ngeoms); + + /* It has an idlist, we need to skip that */ + if ( s->has_idlist ) + { + for ( i = 0; i < ngeoms; i++ ) + twkb_parse_state_varint_skip(s); + } + + for ( i = 0; i < ngeoms; i++ ) + { + geom = lwgeom_from_twkb_state(s); + if ( lwcollection_add_lwgeom(col, geom) == NULL ) + { + lwerror("Unable to add geometry (%p) to collection (%p)", geom, col); + return NULL; + } + } + + + return col; +} + + +static void header_from_twkb_state(twkb_parse_state *s) +{ + LWDEBUG(2,"Entering magicbyte_from_twkb_state"); + + uint8_t extended_dims; + + /* Read the first two bytes */ + uint8_t type_precision = byte_from_twkb_state(s); + uint8_t metadata = byte_from_twkb_state(s); + + /* Strip type and precision out of first byte */ + uint8_t type = type_precision & 0x0F; + int8_t precision = unzigzag8((type_precision & 0xF0) >> 4); + + /* Convert TWKB type to internal type */ + s->lwtype = lwtype_from_twkb_type(type); + + /* Convert the precision into factor */ + s->factor = pow(10, (double)precision); + + /* Strip metadata flags out of second byte */ + s->has_bbox = metadata & 0x01; + s->has_size = (metadata & 0x02) >> 1; + s->has_idlist = (metadata & 0x04) >> 2; + extended_dims = (metadata & 0x08) >> 3; + s->is_empty = (metadata & 0x10) >> 4; + + /* Flag for higher dims means read a third byte */ + if ( extended_dims ) + { + int8_t precision_z, precision_m; + + extended_dims = byte_from_twkb_state(s); + + /* Strip Z/M presence and precision from ext byte */ + s->has_z = (extended_dims & 0x01); + s->has_m = (extended_dims & 0x02) >> 1; + precision_z = (extended_dims & 0x1C) >> 2; + precision_m = (extended_dims & 0xE0) >> 5; + + /* Convert the precision into factor */ + s->factor_z = pow(10, (double)precision_z); + s->factor_m = pow(10, (double)precision_m); + } + else + { + s->has_z = 0; + s->has_m = 0; + s->factor_z = 0; + s->factor_m = 0; + } + + /* Read the size, if there is one */ + if ( s->has_size ) + { + s->size = twkb_parse_state_uvarint(s); + } + + /* Calculate the number of dimensions */ + s->ndims = 2 + s->has_z + s->has_m; + + return; +} + + + +/** +* Generic handling for TWKB geometries. The front of every TWKB geometry +* (including those embedded in collections) is a type byte and metadata byte, +* then optional size, bbox, etc. Read those, then switch to particular type +* handling code. +*/ +LWGEOM* lwgeom_from_twkb_state(twkb_parse_state *s) +{ + GBOX bbox; + LWGEOM *geom = NULL; + uint32_t has_bbox = LW_FALSE; + int i; + + /* Read the first two bytes, and optional */ + /* extended precision info and optional size info */ + header_from_twkb_state(s); + + /* Just experienced a geometry header, so now we */ + /* need to reset our coordinate deltas */ + for ( i = 0; i < TWKB_IN_MAXCOORDS; i++ ) + { + s->coords[i] = 0.0; + } + + /* Read the bounding box, is there is one */ + if ( s->has_bbox ) + { + /* Initialize */ + has_bbox = s->has_bbox; + memset(&bbox, 0, sizeof(GBOX)); + bbox.flags = lwflags(s->has_z, s->has_m, 0); + + /* X */ + bbox.xmin = twkb_parse_state_double(s, s->factor); + bbox.xmax = bbox.xmin + twkb_parse_state_double(s, s->factor); + /* Y */ + bbox.ymin = twkb_parse_state_double(s, s->factor); + bbox.ymax = bbox.ymin + twkb_parse_state_double(s, s->factor); + /* Z */ + if ( s->has_z ) + { + bbox.zmin = twkb_parse_state_double(s, s->factor_z); + bbox.zmax = bbox.zmin + twkb_parse_state_double(s, s->factor_z); + } + /* M */ + if ( s->has_m ) + { + bbox.mmin = twkb_parse_state_double(s, s->factor_m); + bbox.mmax = bbox.mmin + twkb_parse_state_double(s, s->factor_m); + } + } + + /* Switch to code for the particular type we're dealing with */ + switch( s->lwtype ) + { + case POINTTYPE: + geom = lwpoint_as_lwgeom(lwpoint_from_twkb_state(s)); + break; + case LINETYPE: + geom = lwline_as_lwgeom(lwline_from_twkb_state(s)); + break; + case POLYGONTYPE: + geom = lwpoly_as_lwgeom(lwpoly_from_twkb_state(s)); + break; + case MULTIPOINTTYPE: + geom = lwcollection_as_lwgeom(lwmultipoint_from_twkb_state(s)); + break; + case MULTILINETYPE: + geom = lwcollection_as_lwgeom(lwmultiline_from_twkb_state(s)); + break; + case MULTIPOLYGONTYPE: + geom = lwcollection_as_lwgeom(lwmultipoly_from_twkb_state(s)); + break; + case COLLECTIONTYPE: + geom = lwcollection_as_lwgeom(lwcollection_from_twkb_state(s)); + break; + /* Unknown type! */ + default: + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(s->lwtype)); + break; + } + + if ( has_bbox ) + geom->bbox = gbox_clone(&bbox); + + return geom; +} + + +/** +* WKB inputs *must* have a declared size, to prevent malformed WKB from reading +* off the end of the memory segment (this stops a malevolent user from declaring +* a one-ring polygon to have 10 rings, causing the WKB reader to walk off the +* end of the memory). +* +* Check is a bitmask of: LW_PARSER_CHECK_MINPOINTS, LW_PARSER_CHECK_ODD, +* LW_PARSER_CHECK_CLOSURE, LW_PARSER_CHECK_NONE, LW_PARSER_CHECK_ALL +*/ +LWGEOM* lwgeom_from_twkb(const uint8_t *twkb, size_t twkb_size, char check) +{ + int64_t coords[TWKB_IN_MAXCOORDS] = {0, 0, 0, 0}; + twkb_parse_state s; + + LWDEBUG(2,"Entering lwgeom_from_twkb"); + LWDEBUGF(4,"twkb_size: %d",(int) twkb_size); + + /* Zero out the state */ + memset(&s, 0, sizeof(twkb_parse_state)); + + /* Initialize the state appropriately */ + s.twkb = s.pos = twkb; + s.twkb_end = twkb + twkb_size; + s.check = check; + s.coords = coords; + + /* Read the rest of the geometry */ + return lwgeom_from_twkb_state(&s); +} diff --git a/mgist-postgis/liblwgeom/lwin_wkb.c b/mgist-postgis/liblwgeom/lwin_wkb.c new file mode 100644 index 0000000..438828f --- /dev/null +++ b/mgist-postgis/liblwgeom/lwin_wkb.c @@ -0,0 +1,875 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2009 Paul Ramsey + * + **********************************************************************/ + + +// #include "../postgis_config.h" +/*#define POSTGIS_DEBUG_LEVEL 4*/ +#include "liblwgeom_internal.h" /* NOTE: includes lwgeom_log.h */ +#include "lwgeom_log.h" +#include +#include + +/** Max depth in a geometry. Matches the default YYINITDEPTH for WKT */ +#define LW_PARSER_MAX_DEPTH 200 + +/** +* Used for passing the parse state between the parsing functions. +*/ +typedef struct +{ + const uint8_t *wkb; /* Points to start of WKB */ + int32_t srid; /* Current SRID we are handling */ + size_t wkb_size; /* Expected size of WKB */ + int8_t swap_bytes; /* Do an endian flip? */ + int8_t check; /* Simple validity checks on geometries */ + int8_t lwtype; /* Current type we are handling */ + int8_t has_z; /* Z? */ + int8_t has_m; /* M? */ + int8_t has_srid; /* SRID? */ + int8_t error; /* An error was found (not enough bytes to read) */ + uint8_t depth; /* Current recursion level (to prevent stack overflows). Maxes at LW_PARSER_MAX_DEPTH */ + const uint8_t *pos; /* Current parse position */ +} wkb_parse_state; + + +/** +* Internal function declarations. +*/ +LWGEOM* lwgeom_from_wkb_state(wkb_parse_state *s); + + + +/**********************************************************************/ + +/* Our static character->number map. Anything > 15 is invalid */ +static uint8_t hex2char[256] = { + /* not Hex characters */ + 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20, + 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20, + 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20, + /* 0-9 */ + 0,1,2,3,4,5,6,7,8,9,20,20,20,20,20,20, + /* A-F */ + 20,10,11,12,13,14,15,20,20,20,20,20,20,20,20,20, + /* not Hex characters */ + 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20, + /* a-f */ + 20,10,11,12,13,14,15,20,20,20,20,20,20,20,20,20, + 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20, + /* not Hex characters (upper 128 characters) */ + 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20, + 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20, + 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20, + 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20, + 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20, + 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20, + 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20, + 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20 + }; + + +uint8_t* bytes_from_hexbytes(const char *hexbuf, size_t hexsize) +{ + uint8_t *buf = NULL; + register uint8_t h1, h2; + uint32_t i; + + if( hexsize % 2 ) + lwerror("Invalid hex string, length (%d) has to be a multiple of two!", hexsize); + + buf = lwalloc(hexsize/2); + + if( ! buf ) + lwerror("Unable to allocate memory buffer."); + + for( i = 0; i < hexsize/2; i++ ) + { + h1 = hex2char[(int)hexbuf[2*i]]; + h2 = hex2char[(int)hexbuf[2*i+1]]; + if( h1 > 15 ) + lwerror("Invalid hex character (%c) encountered", hexbuf[2*i]); + if( h2 > 15 ) + lwerror("Invalid hex character (%c) encountered", hexbuf[2*i+1]); + /* First character is high bits, second is low bits */ + buf[i] = ((h1 & 0x0F) << 4) | (h2 & 0x0F); + } + return buf; +} + + +/**********************************************************************/ + + + + + +/** +* Check that we are not about to read off the end of the WKB +* array. +*/ +static inline void wkb_parse_state_check(wkb_parse_state *s, size_t next) +{ + if( (s->pos + next) > (s->wkb + s->wkb_size) ) + { + lwerror("WKB structure does not match expected size!"); + s->error = LW_TRUE; + } +} + +/** +* Take in an unknown kind of wkb type number and ensure it comes out +* as an extended WKB type number (with Z/M/SRID flags masked onto the +* high bits). +*/ +static void lwtype_from_wkb_state(wkb_parse_state *s, uint32_t wkb_type) +{ + uint32_t wkb_simple_type; + + LWDEBUG(4, "Entered function"); + + s->has_z = LW_FALSE; + s->has_m = LW_FALSE; + s->has_srid = LW_FALSE; + + /* If any of the higher bits are set, this is probably an extended type. */ + if( wkb_type & 0xF0000000 ) + { + if( wkb_type & WKBZOFFSET ) s->has_z = LW_TRUE; + if( wkb_type & WKBMOFFSET ) s->has_m = LW_TRUE; + if( wkb_type & WKBSRIDFLAG ) s->has_srid = LW_TRUE; + LWDEBUGF(4, "Extended type: has_z=%d has_m=%d has_srid=%d", s->has_z, s->has_m, s->has_srid); + } + + /* Mask off the flags */ + wkb_type = wkb_type & 0x0FFFFFFF; + + /* Catch strange Oracle WKB type numbers */ + if ( wkb_type >= 4000 ) { + lwerror("Unknown WKB type (%d)!", wkb_type); + return; + } + + /* Strip out just the type number (1-12) from the ISO number (eg 3001-3012) */ + wkb_simple_type = wkb_type % 1000; + + /* Extract the Z/M information from ISO style numbers */ + if( wkb_type >= 3000 && wkb_type < 4000 ) + { + s->has_z = LW_TRUE; + s->has_m = LW_TRUE; + } + else if ( wkb_type >= 2000 && wkb_type < 3000 ) + { + s->has_m = LW_TRUE; + } + else if ( wkb_type >= 1000 && wkb_type < 2000 ) + { + s->has_z = LW_TRUE; + } + + switch (wkb_simple_type) + { + case WKB_POINT_TYPE: + s->lwtype = POINTTYPE; + break; + case WKB_LINESTRING_TYPE: + s->lwtype = LINETYPE; + break; + case WKB_POLYGON_TYPE: + s->lwtype = POLYGONTYPE; + break; + case WKB_MULTIPOINT_TYPE: + s->lwtype = MULTIPOINTTYPE; + break; + case WKB_MULTILINESTRING_TYPE: + s->lwtype = MULTILINETYPE; + break; + case WKB_MULTIPOLYGON_TYPE: + s->lwtype = MULTIPOLYGONTYPE; + break; + case WKB_GEOMETRYCOLLECTION_TYPE: + s->lwtype = COLLECTIONTYPE; + break; + case WKB_CIRCULARSTRING_TYPE: + s->lwtype = CIRCSTRINGTYPE; + break; + case WKB_COMPOUNDCURVE_TYPE: + s->lwtype = COMPOUNDTYPE; + break; + case WKB_CURVEPOLYGON_TYPE: + s->lwtype = CURVEPOLYTYPE; + break; + case WKB_MULTICURVE_TYPE: + s->lwtype = MULTICURVETYPE; + break; + case WKB_MULTISURFACE_TYPE: + s->lwtype = MULTISURFACETYPE; + break; + case WKB_POLYHEDRALSURFACE_TYPE: + s->lwtype = POLYHEDRALSURFACETYPE; + break; + case WKB_TIN_TYPE: + s->lwtype = TINTYPE; + break; + case WKB_TRIANGLE_TYPE: + s->lwtype = TRIANGLETYPE; + break; + + /* PostGIS 1.5 emits 13, 14 for CurvePolygon, MultiCurve */ + /* These numbers aren't SQL/MM (numbers currently only */ + /* go up to 12. We can handle the old data here (for now??) */ + /* converting them into the lwtypes that are intended. */ + case WKB_CURVE_TYPE: + s->lwtype = CURVEPOLYTYPE; + break; + case WKB_SURFACE_TYPE: + s->lwtype = MULTICURVETYPE; + break; + + default: /* Error! */ + lwerror("Unknown WKB type (%d)! Full WKB type number was (%d).", wkb_simple_type, wkb_type); + break; + } + + LWDEBUGF(4,"Got lwtype %s (%u)", lwtype_name(s->lwtype), s->lwtype); + + return; +} + +/** +* Byte +* Read a byte and advance the parse state forward. +*/ +static char byte_from_wkb_state(wkb_parse_state *s) +{ + char char_value = 0; + LWDEBUG(4, "Entered function"); + + wkb_parse_state_check(s, WKB_BYTE_SIZE); + if (s->error) + return 0; + LWDEBUG(4, "Passed state check"); + + char_value = s->pos[0]; + LWDEBUGF(4, "Read byte value: %x", char_value); + s->pos += WKB_BYTE_SIZE; + + return char_value; +} + +/** +* Int32 +* Read 4-byte integer and advance the parse state forward. +*/ +static uint32_t integer_from_wkb_state(wkb_parse_state *s) +{ + uint32_t i = 0; + + wkb_parse_state_check(s, WKB_INT_SIZE); + if (s->error) + return 0; + + memcpy(&i, s->pos, WKB_INT_SIZE); + + /* Swap? Copy into a stack-allocated integer. */ + if( s->swap_bytes ) + { + int j = 0; + uint8_t tmp; + + for( j = 0; j < WKB_INT_SIZE/2; j++ ) + { + tmp = ((uint8_t*)(&i))[j]; + ((uint8_t*)(&i))[j] = ((uint8_t*)(&i))[WKB_INT_SIZE - j - 1]; + ((uint8_t*)(&i))[WKB_INT_SIZE - j - 1] = tmp; + } + } + + s->pos += WKB_INT_SIZE; + return i; +} + +/** +* Double +* Read an 8-byte double and advance the parse state forward. +*/ +static double double_from_wkb_state(wkb_parse_state *s) +{ + double d = 0; + + memcpy(&d, s->pos, WKB_DOUBLE_SIZE); + + /* Swap? Copy into a stack-allocated integer. */ + if( s->swap_bytes ) + { + int i = 0; + uint8_t tmp; + + for( i = 0; i < WKB_DOUBLE_SIZE/2; i++ ) + { + tmp = ((uint8_t*)(&d))[i]; + ((uint8_t*)(&d))[i] = ((uint8_t*)(&d))[WKB_DOUBLE_SIZE - i - 1]; + ((uint8_t*)(&d))[WKB_DOUBLE_SIZE - i - 1] = tmp; + } + + } + + s->pos += WKB_DOUBLE_SIZE; + return d; +} + +/** +* POINTARRAY +* Read a dynamically sized point array and advance the parse state forward. +* First read the number of points, then read the points. +*/ +static POINTARRAY* ptarray_from_wkb_state(wkb_parse_state *s) +{ + POINTARRAY *pa = NULL; + size_t pa_size; + uint32_t ndims = 2; + uint32_t npoints = 0; + static uint32_t maxpoints = UINT_MAX / WKB_DOUBLE_SIZE / 4; + + /* Calculate the size of this point array. */ + npoints = integer_from_wkb_state(s); + if (s->error) + return NULL; + + if (npoints > maxpoints) + { + s->error = LW_TRUE; + lwerror("Pointarray length (%d) is too large", npoints); + return NULL; + } + + LWDEBUGF(4,"Pointarray has %d points", npoints); + + if( s->has_z ) ndims++; + if( s->has_m ) ndims++; + pa_size = npoints * ndims * WKB_DOUBLE_SIZE; + + /* Empty! */ + if( npoints == 0 ) + return ptarray_construct(s->has_z, s->has_m, npoints); + + /* Does the data we want to read exist? */ + wkb_parse_state_check(s, pa_size); + if (s->error) + return NULL; + + /* If we're in a native endianness, we can just copy the data directly! */ + if( ! s->swap_bytes ) + { + pa = ptarray_construct_copy_data(s->has_z, s->has_m, npoints, (uint8_t*)s->pos); + s->pos += pa_size; + } + /* Otherwise we have to read each double, separately. */ + else + { + uint32_t i = 0; + double *dlist; + pa = ptarray_construct(s->has_z, s->has_m, npoints); + dlist = (double*)(pa->serialized_pointlist); + for( i = 0; i < npoints * ndims; i++ ) + { + dlist[i] = double_from_wkb_state(s); + } + } + + return pa; +} + +/** +* POINT +* Read a WKB point, starting just after the endian byte, +* type number and optional srid number. +* Advance the parse state forward appropriately. +* WKB point has just a set of doubles, with the quantity depending on the +* dimension of the point, so this looks like a special case of the above +* with only one point. +*/ +static LWPOINT* lwpoint_from_wkb_state(wkb_parse_state *s) +{ + static uint32_t npoints = 1; + POINTARRAY *pa = NULL; + size_t pa_size; + uint32_t ndims = 2; + const POINT2D *pt; + + /* Count the dimensions. */ + if( s->has_z ) ndims++; + if( s->has_m ) ndims++; + pa_size = ndims * WKB_DOUBLE_SIZE; + + /* Does the data we want to read exist? */ + wkb_parse_state_check(s, pa_size); + if (s->error) + return NULL; + + /* If we're in a native endianness, we can just copy the data directly! */ + if( ! s->swap_bytes ) + { + pa = ptarray_construct_copy_data(s->has_z, s->has_m, npoints, (uint8_t*)s->pos); + s->pos += pa_size; + } + /* Otherwise we have to read each double, separately */ + else + { + uint32_t i = 0; + double *dlist; + pa = ptarray_construct(s->has_z, s->has_m, npoints); + dlist = (double*)(pa->serialized_pointlist); + for( i = 0; i < ndims; i++ ) + { + dlist[i] = double_from_wkb_state(s); + } + } + + /* Check for POINT(NaN NaN) ==> POINT EMPTY */ + pt = getPoint2d_cp(pa, 0); + if ( isnan(pt->x) && isnan(pt->y) ) + { + ptarray_free(pa); + return lwpoint_construct_empty(s->srid, s->has_z, s->has_m); + } + else + { + return lwpoint_construct(s->srid, NULL, pa); + } +} + +/** +* LINESTRING +* Read a WKB linestring, starting just after the endian byte, +* type number and optional srid number. Advance the parse state +* forward appropriately. +* There is only one pointarray in a linestring. Optionally +* check for minimal following of rules (two point minimum). +*/ +static LWLINE* lwline_from_wkb_state(wkb_parse_state *s) +{ + POINTARRAY *pa = ptarray_from_wkb_state(s); + if (s->error) + return NULL; + + if( pa == NULL || pa->npoints == 0 ) + { + if (pa) + ptarray_free(pa); + return lwline_construct_empty(s->srid, s->has_z, s->has_m); + } + + if( s->check & LW_PARSER_CHECK_MINPOINTS && pa->npoints < 2 ) + { + lwerror("%s must have at least two points", lwtype_name(s->lwtype)); + return NULL; + } + + return lwline_construct(s->srid, NULL, pa); +} + +/** +* CIRCULARSTRING +* Read a WKB circularstring, starting just after the endian byte, +* type number and optional srid number. Advance the parse state +* forward appropriately. +* There is only one pointarray in a linestring. Optionally +* check for minimal following of rules (three point minimum, +* odd number of points). +*/ +static LWCIRCSTRING* lwcircstring_from_wkb_state(wkb_parse_state *s) +{ + POINTARRAY *pa = ptarray_from_wkb_state(s); + if (s->error) + return NULL; + + if( pa == NULL || pa->npoints == 0 ) + { + if (pa) + ptarray_free(pa); + return lwcircstring_construct_empty(s->srid, s->has_z, s->has_m); + } + + if( s->check & LW_PARSER_CHECK_MINPOINTS && pa->npoints < 3 ) + { + lwerror("%s must have at least three points", lwtype_name(s->lwtype)); + return NULL; + } + + if( s->check & LW_PARSER_CHECK_ODD && ! (pa->npoints % 2) ) + { + lwerror("%s must have an odd number of points", lwtype_name(s->lwtype)); + return NULL; + } + + return lwcircstring_construct(s->srid, NULL, pa); +} + +/** +* POLYGON +* Read a WKB polygon, starting just after the endian byte, +* type number and optional srid number. Advance the parse state +* forward appropriately. +* First read the number of rings, then read each ring +* (which are structured as point arrays) +*/ +static LWPOLY* lwpoly_from_wkb_state(wkb_parse_state *s) +{ + uint32_t nrings = integer_from_wkb_state(s); + if (s->error) + return NULL; + uint32_t i = 0; + LWPOLY *poly = lwpoly_construct_empty(s->srid, s->has_z, s->has_m); + + LWDEBUGF(4,"Polygon has %d rings", nrings); + + /* Empty polygon? */ + if( nrings == 0 ) + return poly; + + for( i = 0; i < nrings; i++ ) + { + POINTARRAY *pa = ptarray_from_wkb_state(s); + if (pa == NULL) + { + lwpoly_free(poly); + return NULL; + } + + /* Check for at least four points. */ + if (s->check & LW_PARSER_CHECK_MINPOINTS && pa->npoints < 4) + { + lwpoly_free(poly); + ptarray_free(pa); + LWDEBUGF(2, "%s must have at least four points in each ring", lwtype_name(s->lwtype)); + lwerror("%s must have at least four points in each ring", lwtype_name(s->lwtype)); + return NULL; + } + + /* Check that first and last points are the same. */ + if( s->check & LW_PARSER_CHECK_CLOSURE && ! ptarray_is_closed_2d(pa) ) + { + lwpoly_free(poly); + ptarray_free(pa); + LWDEBUGF(2, "%s must have closed rings", lwtype_name(s->lwtype)); + lwerror("%s must have closed rings", lwtype_name(s->lwtype)); + return NULL; + } + + /* Add ring to polygon */ + if ( lwpoly_add_ring(poly, pa) == LW_FAILURE ) + { + lwpoly_free(poly); + ptarray_free(pa); + LWDEBUG(2, "Unable to add ring to polygon"); + lwerror("Unable to add ring to polygon"); + return NULL; + } + + } + return poly; +} + +/** +* TRIANGLE +* Read a WKB triangle, starting just after the endian byte, +* type number and optional srid number. Advance the parse state +* forward appropriately. +* Triangles are encoded like polygons in WKB, but more like linestrings +* as lwgeometries. +*/ +static LWTRIANGLE* lwtriangle_from_wkb_state(wkb_parse_state *s) +{ + uint32_t nrings = integer_from_wkb_state(s); + if (s->error) + return NULL; + + /* Empty triangle? */ + if( nrings == 0 ) + return lwtriangle_construct_empty(s->srid, s->has_z, s->has_m); + + /* Should be only one ring. */ + if (nrings != 1) + { + lwerror("Triangle has wrong number of rings: %d", nrings); + } + + /* There's only one ring, we hope? */ + POINTARRAY *pa = ptarray_from_wkb_state(s); + + /* If there's no points, return an empty triangle. */ + if (pa == NULL) + return lwtriangle_construct_empty(s->srid, s->has_z, s->has_m); + + /* Check for at least four points. */ + if (s->check & LW_PARSER_CHECK_MINPOINTS && pa->npoints < 4) + { + ptarray_free(pa); + lwerror("%s must have at least four points", lwtype_name(s->lwtype)); + return NULL; + } + + if (s->check & LW_PARSER_CHECK_ZCLOSURE && !ptarray_is_closed_z(pa)) + { + ptarray_free(pa); + lwerror("%s must have closed rings", lwtype_name(s->lwtype)); + return NULL; + } + + /* Empty TRIANGLE starts w/ empty POINTARRAY, free it first */ + return lwtriangle_construct(s->srid, NULL, pa); +} + +/** +* CURVEPOLYTYPE +*/ +static LWCURVEPOLY* lwcurvepoly_from_wkb_state(wkb_parse_state *s) +{ + uint32_t ngeoms = integer_from_wkb_state(s); + if (s->error) + return NULL; + LWCURVEPOLY *cp = lwcurvepoly_construct_empty(s->srid, s->has_z, s->has_m); + LWGEOM *geom = NULL; + uint32_t i; + + /* Empty collection? */ + if ( ngeoms == 0 ) + return cp; + + for ( i = 0; i < ngeoms; i++ ) + { + geom = lwgeom_from_wkb_state(s); + if ( lwcurvepoly_add_ring(cp, geom) == LW_FAILURE ) + { + lwgeom_free(geom); + lwgeom_free((LWGEOM *)cp); + lwerror("Unable to add geometry (%p) to curvepoly (%p)", geom, cp); + return NULL; + } + } + + return cp; +} + +/** +* POLYHEDRALSURFACETYPE +*/ + +/** +* COLLECTION, MULTIPOINTTYPE, MULTILINETYPE, MULTIPOLYGONTYPE, COMPOUNDTYPE, +* MULTICURVETYPE, MULTISURFACETYPE, +* TINTYPE +*/ +static LWCOLLECTION* lwcollection_from_wkb_state(wkb_parse_state *s) +{ + uint32_t ngeoms = integer_from_wkb_state(s); + if (s->error) + return NULL; + LWCOLLECTION *col = lwcollection_construct_empty(s->lwtype, s->srid, s->has_z, s->has_m); + LWGEOM *geom = NULL; + uint32_t i; + + LWDEBUGF(4,"Collection has %d components", ngeoms); + + /* Empty collection? */ + if ( ngeoms == 0 ) + return col; + + /* Be strict in polyhedral surface closures */ + if ( s->lwtype == POLYHEDRALSURFACETYPE ) + s->check |= LW_PARSER_CHECK_ZCLOSURE; + + s->depth++; + if (s->depth >= LW_PARSER_MAX_DEPTH) + { + lwcollection_free(col); + lwerror("Geometry has too many chained collections"); + return NULL; + } + for ( i = 0; i < ngeoms; i++ ) + { + geom = lwgeom_from_wkb_state(s); + if ( lwcollection_add_lwgeom(col, geom) == NULL ) + { + lwgeom_free(geom); + lwgeom_free((LWGEOM *)col); + lwerror("Unable to add geometry (%p) to collection (%p)", geom, col); + return NULL; + } + } + s->depth--; + + return col; +} + + +/** +* GEOMETRY +* Generic handling for WKB geometries. The front of every WKB geometry +* (including those embedded in collections) is an endian byte, a type +* number and an optional srid number. We handle all those here, then pass +* to the appropriate handler for the specific type. +*/ +LWGEOM* lwgeom_from_wkb_state(wkb_parse_state *s) +{ + char wkb_little_endian; + uint32_t wkb_type; + + LWDEBUG(4,"Entered function"); + + /* Fail when handed incorrect starting byte */ + wkb_little_endian = byte_from_wkb_state(s); + if (s->error) + return NULL; + if( wkb_little_endian != 1 && wkb_little_endian != 0 ) + { + LWDEBUG(4,"Leaving due to bad first byte!"); + lwerror("Invalid endian flag value encountered."); + return NULL; + } + + /* Check the endianness of our input */ + s->swap_bytes = LW_FALSE; + + /* Machine arch is big endian, request is for little */ + if (IS_BIG_ENDIAN && wkb_little_endian) + s->swap_bytes = LW_TRUE; + /* Machine arch is little endian, request is for big */ + else if ((!IS_BIG_ENDIAN) && (!wkb_little_endian)) + s->swap_bytes = LW_TRUE; + + /* Read the type number */ + wkb_type = integer_from_wkb_state(s); + if (s->error) + return NULL; + LWDEBUGF(4,"Got WKB type number: 0x%X", wkb_type); + lwtype_from_wkb_state(s, wkb_type); + + /* Read the SRID, if necessary */ + if( s->has_srid ) + { + s->srid = clamp_srid(integer_from_wkb_state(s)); + if (s->error) + return NULL; + /* TODO: warn on explicit UNKNOWN srid ? */ + LWDEBUGF(4,"Got SRID: %u", s->srid); + } + + /* Do the right thing */ + switch( s->lwtype ) + { + case POINTTYPE: + return (LWGEOM*)lwpoint_from_wkb_state(s); + break; + case LINETYPE: + return (LWGEOM*)lwline_from_wkb_state(s); + break; + case CIRCSTRINGTYPE: + return (LWGEOM*)lwcircstring_from_wkb_state(s); + break; + case POLYGONTYPE: + return (LWGEOM*)lwpoly_from_wkb_state(s); + break; + case TRIANGLETYPE: + return (LWGEOM*)lwtriangle_from_wkb_state(s); + break; + case CURVEPOLYTYPE: + return (LWGEOM*)lwcurvepoly_from_wkb_state(s); + break; + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COMPOUNDTYPE: + case MULTICURVETYPE: + case MULTISURFACETYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + return (LWGEOM*)lwcollection_from_wkb_state(s); + break; + + /* Unknown type! */ + default: + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(s->lwtype)); + } + + /* Return value to keep compiler happy. */ + return NULL; + +} + +/* TODO add check for SRID consistency */ + +/** +* WKB inputs *must* have a declared size, to prevent malformed WKB from reading +* off the end of the memory segment (this stops a malevolent user from declaring +* a one-ring polygon to have 10 rings, causing the WKB reader to walk off the +* end of the memory). +* +* Check is a bitmask of: LW_PARSER_CHECK_MINPOINTS, LW_PARSER_CHECK_ODD, +* LW_PARSER_CHECK_CLOSURE, LW_PARSER_CHECK_NONE, LW_PARSER_CHECK_ALL +*/ +LWGEOM* lwgeom_from_wkb(const uint8_t *wkb, const size_t wkb_size, const char check) +{ + wkb_parse_state s; + + /* Initialize the state appropriately */ + s.wkb = wkb; + s.wkb_size = wkb_size; + s.swap_bytes = LW_FALSE; + s.check = check; + s.lwtype = 0; + s.srid = SRID_UNKNOWN; + s.has_z = LW_FALSE; + s.has_m = LW_FALSE; + s.has_srid = LW_FALSE; + s.error = LW_FALSE; + s.pos = wkb; + s.depth = 1; + + if (!wkb || !wkb_size) + return NULL; + + return lwgeom_from_wkb_state(&s); +} + +LWGEOM* lwgeom_from_hexwkb(const char *hexwkb, const char check) +{ + int hexwkb_len; + uint8_t *wkb; + LWGEOM *lwgeom; + + if ( ! hexwkb ) + { + lwerror("lwgeom_from_hexwkb: null input"); + return NULL; + } + + hexwkb_len = strlen(hexwkb); + wkb = bytes_from_hexbytes(hexwkb, hexwkb_len); + lwgeom = lwgeom_from_wkb(wkb, hexwkb_len/2, check); + lwfree(wkb); + return lwgeom; +} diff --git a/mgist-postgis/liblwgeom/lwin_wkt.c b/mgist-postgis/liblwgeom/lwin_wkt.c new file mode 100644 index 0000000..d911663 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwin_wkt.c @@ -0,0 +1,918 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2010 Paul Ramsey + * + **********************************************************************/ + + +#include +#include /* for isspace */ + +#include "lwin_wkt.h" +#include "lwin_wkt_parse.h" +#include "lwgeom_log.h" + + +/* +* Error messages for failures in the parser. +*/ +const char *parser_error_messages[] = +{ + "", + "geometry requires more points", + "geometry must have an odd number of points", + "geometry contains non-closed rings", + "can not mix dimensionality in a geometry", + "parse error - invalid geometry", + "invalid WKB type", + "incontinuous compound curve", + "triangle must have exactly 4 points", + "geometry has too many points", + "parse error - invalid geometry" +}; + +#define SET_PARSER_ERROR(errno) { \ + global_parser_result.message = parser_error_messages[(errno)]; \ + global_parser_result.errcode = (errno); \ + global_parser_result.errlocation = wkt_yylloc.last_column; \ + } + +/** +* Read the SRID number from an SRID=<> string +*/ +int wkt_lexer_read_srid(char *str) +{ + char *c = str; + long i = 0; + int32_t srid; + + if( ! str ) return SRID_UNKNOWN; + c += 5; /* Advance past "SRID=" */ + i = strtol(c, NULL, 10); + srid = clamp_srid((int32_t)i); + /* TODO: warn on explicit UNKNOWN srid ? */ + return srid; +} + +static lwflags_t wkt_dimensionality(char *dimensionality) +{ + size_t i = 0; + lwflags_t flags = 0; + + if( ! dimensionality ) + return flags; + + /* If there's an explicit dimensionality, we use that */ + for( i = 0; i < strlen(dimensionality); i++ ) + { + if( (dimensionality[i] == 'Z') || (dimensionality[i] == 'z') ) + FLAGS_SET_Z(flags,1); + else if( (dimensionality[i] == 'M') || (dimensionality[i] == 'm') ) + FLAGS_SET_M(flags,1); + /* only a space is accepted in between */ + else if( ! isspace(dimensionality[i]) ) break; + } + return flags; +} + + +/** +* Force the dimensionality of a geometry to match the dimensionality +* of a set of flags (usually derived from a ZM WKT tag). +*/ +static int wkt_parser_set_dims(LWGEOM *geom, lwflags_t flags) +{ + int hasz = FLAGS_GET_Z(flags); + int hasm = FLAGS_GET_M(flags); + uint32_t i = 0; + + /* Error on junk */ + if( ! geom ) + return LW_FAILURE; + + FLAGS_SET_Z(geom->flags, hasz); + FLAGS_SET_M(geom->flags, hasm); + + switch( geom->type ) + { + case POINTTYPE: + { + LWPOINT *pt = (LWPOINT*)geom; + if ( pt->point ) + { + FLAGS_SET_Z(pt->point->flags, hasz); + FLAGS_SET_M(pt->point->flags, hasm); + } + break; + } + case TRIANGLETYPE: + case CIRCSTRINGTYPE: + case LINETYPE: + { + LWLINE *ln = (LWLINE*)geom; + if ( ln->points ) + { + FLAGS_SET_Z(ln->points->flags, hasz); + FLAGS_SET_M(ln->points->flags, hasm); + } + break; + } + case POLYGONTYPE: + { + LWPOLY *poly = (LWPOLY*)geom; + for ( i = 0; i < poly->nrings; i++ ) + { + if( poly->rings[i] ) + { + FLAGS_SET_Z(poly->rings[i]->flags, hasz); + FLAGS_SET_M(poly->rings[i]->flags, hasm); + } + } + break; + } + case CURVEPOLYTYPE: + { + LWCURVEPOLY *poly = (LWCURVEPOLY*)geom; + for ( i = 0; i < poly->nrings; i++ ) + wkt_parser_set_dims(poly->rings[i], flags); + break; + } + default: + { + if ( lwtype_is_collection(geom->type) ) + { + LWCOLLECTION *col = (LWCOLLECTION*)geom; + for ( i = 0; i < col->ngeoms; i++ ) + wkt_parser_set_dims(col->geoms[i], flags); + return LW_SUCCESS; + } + else + { + LWDEBUGF(2,"Unknown geometry type: %d", geom->type); + return LW_FAILURE; + } + } + } + + return LW_SUCCESS; +} + +/** +* Read the dimensionality from a flag, if provided. Then check that the +* dimensionality matches that of the pointarray. If the dimension counts +* match, ensure the pointarray is using the right "Z" or "M". +*/ +static int wkt_pointarray_dimensionality(POINTARRAY *pa, lwflags_t flags) +{ + int hasz = FLAGS_GET_Z(flags); + int hasm = FLAGS_GET_M(flags); + int ndims = 2 + hasz + hasm; + + /* No dimensionality or array means we go with what we have */ + if( ! (flags && pa) ) + return LW_TRUE; + + LWDEBUGF(5,"dimensionality ndims == %d", ndims); + LWDEBUGF(5,"FLAGS_NDIMS(pa->flags) == %d", FLAGS_NDIMS(pa->flags)); + + /* + * ndims > 2 implies that the flags have something useful to add, + * that there is a 'Z' or an 'M' or both. + */ + if( ndims > 2 ) + { + /* Mismatch implies a problem */ + if ( FLAGS_NDIMS(pa->flags) != ndims ) + return LW_FALSE; + /* Match means use the explicit dimensionality */ + else + { + FLAGS_SET_Z(pa->flags, hasz); + FLAGS_SET_M(pa->flags, hasm); + } + } + + return LW_TRUE; +} + + + +/** +* Build a 2d coordinate. +*/ +POINT wkt_parser_coord_2(double c1, double c2) +{ + POINT p; + p.flags = 0; + p.x = c1; + p.y = c2; + p.z = p.m = 0.0; + FLAGS_SET_Z(p.flags, 0); + FLAGS_SET_M(p.flags, 0); + return p; +} + +/** +* Note, if this is an XYM coordinate we'll have to fix it later when we build +* the object itself and have access to the dimensionality token. +*/ +POINT wkt_parser_coord_3(double c1, double c2, double c3) +{ + POINT p; + p.flags = 0; + p.x = c1; + p.y = c2; + p.z = c3; + p.m = 0; + FLAGS_SET_Z(p.flags, 1); + FLAGS_SET_M(p.flags, 0); + return p; +} + +/** +*/ +POINT wkt_parser_coord_4(double c1, double c2, double c3, double c4) +{ + POINT p; + p.flags = 0; + p.x = c1; + p.y = c2; + p.z = c3; + p.m = c4; + FLAGS_SET_Z(p.flags, 1); + FLAGS_SET_M(p.flags, 1); + return p; +} + +POINTARRAY* wkt_parser_ptarray_add_coord(POINTARRAY *pa, POINT p) +{ + POINT4D pt; + LWDEBUG(4,"entered"); + + /* Error on trouble */ + if( ! pa ) + { + SET_PARSER_ERROR(PARSER_ERROR_OTHER); + return NULL; + } + + /* Check that the coordinate has the same dimesionality as the array */ + if( FLAGS_NDIMS(p.flags) != FLAGS_NDIMS(pa->flags) ) + { + ptarray_free(pa); + SET_PARSER_ERROR(PARSER_ERROR_MIXDIMS); + return NULL; + } + + /* While parsing the point arrays, XYM and XMZ points are both treated as XYZ */ + pt.x = p.x; + pt.y = p.y; + if( FLAGS_GET_Z(pa->flags) ) + pt.z = p.z; + if( FLAGS_GET_M(pa->flags) ) + pt.m = p.m; + /* If the destination is XYM, we'll write the third coordinate to m */ + if( FLAGS_GET_M(pa->flags) && ! FLAGS_GET_Z(pa->flags) ) + pt.m = p.z; + + ptarray_append_point(pa, &pt, LW_TRUE); /* Allow duplicate points in array */ + return pa; +} + +/** +* Start a point array from the first coordinate. +*/ +POINTARRAY* wkt_parser_ptarray_new(POINT p) +{ + int ndims = FLAGS_NDIMS(p.flags); + POINTARRAY *pa = ptarray_construct_empty((ndims>2), (ndims>3), 4); + LWDEBUG(4,"entered"); + if ( ! pa ) + { + SET_PARSER_ERROR(PARSER_ERROR_OTHER); + return NULL; + } + return wkt_parser_ptarray_add_coord(pa, p); +} + +/** +* Create a new point. Null point array implies empty. Null dimensionality +* implies no specified dimensionality in the WKT. +*/ +LWGEOM* wkt_parser_point_new(POINTARRAY *pa, char *dimensionality) +{ + lwflags_t flags = wkt_dimensionality(dimensionality); + LWDEBUG(4,"entered"); + + /* No pointarray means it is empty */ + if( ! pa ) + return lwpoint_as_lwgeom(lwpoint_construct_empty(SRID_UNKNOWN, FLAGS_GET_Z(flags), FLAGS_GET_M(flags))); + + /* If the number of dimensions is not consistent, we have a problem. */ + if( wkt_pointarray_dimensionality(pa, flags) == LW_FALSE ) + { + ptarray_free(pa); + SET_PARSER_ERROR(PARSER_ERROR_MIXDIMS); + return NULL; + } + + /* Only one point allowed in our point array! */ + if( pa->npoints != 1 ) + { + ptarray_free(pa); + SET_PARSER_ERROR(PARSER_ERROR_LESSPOINTS); + return NULL; + } + + return lwpoint_as_lwgeom(lwpoint_construct(SRID_UNKNOWN, NULL, pa)); +} + + +/** +* Create a new linestring. Null point array implies empty. Null dimensionality +* implies no specified dimensionality in the WKT. Check for numpoints >= 2 if +* requested. +*/ +LWGEOM* wkt_parser_linestring_new(POINTARRAY *pa, char *dimensionality) +{ + lwflags_t flags = wkt_dimensionality(dimensionality); + LWDEBUG(4,"entered"); + + /* No pointarray means it is empty */ + if( ! pa ) + return lwline_as_lwgeom(lwline_construct_empty(SRID_UNKNOWN, FLAGS_GET_Z(flags), FLAGS_GET_M(flags))); + + /* If the number of dimensions is not consistent, we have a problem. */ + if( wkt_pointarray_dimensionality(pa, flags) == LW_FALSE ) + { + ptarray_free(pa); + SET_PARSER_ERROR(PARSER_ERROR_MIXDIMS); + return NULL; + } + + /* Apply check for not enough points, if requested. */ + if( (global_parser_result.parser_check_flags & LW_PARSER_CHECK_MINPOINTS) && (pa->npoints < 2) ) + { + ptarray_free(pa); + SET_PARSER_ERROR(PARSER_ERROR_MOREPOINTS); + return NULL; + } + + return lwline_as_lwgeom(lwline_construct(SRID_UNKNOWN, NULL, pa)); +} + +/** +* Create a new circularstring. Null point array implies empty. Null dimensionality +* implies no specified dimensionality in the WKT. +* Circular strings are just like linestrings, except with slighty different +* validity rules (minpoint == 3, numpoints % 2 == 1). +*/ +LWGEOM* wkt_parser_circularstring_new(POINTARRAY *pa, char *dimensionality) +{ + lwflags_t flags = wkt_dimensionality(dimensionality); + LWDEBUG(4,"entered"); + + /* No pointarray means it is empty */ + if( ! pa ) + return lwcircstring_as_lwgeom(lwcircstring_construct_empty(SRID_UNKNOWN, FLAGS_GET_Z(flags), FLAGS_GET_M(flags))); + + /* If the number of dimensions is not consistent, we have a problem. */ + if( wkt_pointarray_dimensionality(pa, flags) == LW_FALSE ) + { + ptarray_free(pa); + SET_PARSER_ERROR(PARSER_ERROR_MIXDIMS); + return NULL; + } + + /* Apply check for not enough points, if requested. */ + if( (global_parser_result.parser_check_flags & LW_PARSER_CHECK_MINPOINTS) && (pa->npoints < 3) ) + { + ptarray_free(pa); + SET_PARSER_ERROR(PARSER_ERROR_MOREPOINTS); + return NULL; + } + + /* Apply check for odd number of points, if requested. */ + if( (global_parser_result.parser_check_flags & LW_PARSER_CHECK_ODD) && ((pa->npoints % 2) == 0) ) + { + ptarray_free(pa); + SET_PARSER_ERROR(PARSER_ERROR_ODDPOINTS); + return NULL; + } + + return lwcircstring_as_lwgeom(lwcircstring_construct(SRID_UNKNOWN, NULL, pa)); +} + +LWGEOM* wkt_parser_triangle_new(POINTARRAY *pa, char *dimensionality) +{ + lwflags_t flags = wkt_dimensionality(dimensionality); + LWDEBUG(4,"entered"); + + /* No pointarray means it is empty */ + if( ! pa ) + return lwtriangle_as_lwgeom(lwtriangle_construct_empty(SRID_UNKNOWN, FLAGS_GET_Z(flags), FLAGS_GET_M(flags))); + + /* If the number of dimensions is not consistent, we have a problem. */ + if( wkt_pointarray_dimensionality(pa, flags) == LW_FALSE ) + { + ptarray_free(pa); + SET_PARSER_ERROR(PARSER_ERROR_MIXDIMS); + return NULL; + } + + /* Triangles need four points. */ + if( (pa->npoints != 4) ) + { + ptarray_free(pa); + SET_PARSER_ERROR(PARSER_ERROR_TRIANGLEPOINTS); + return NULL; + } + + /* Triangles need closure. */ + if( ! ptarray_is_closed_z(pa) ) + { + ptarray_free(pa); + SET_PARSER_ERROR(PARSER_ERROR_UNCLOSED); + return NULL; + } + + return lwtriangle_as_lwgeom(lwtriangle_construct(SRID_UNKNOWN, NULL, pa)); +} + +LWGEOM* wkt_parser_polygon_new(POINTARRAY *pa, char dimcheck) +{ + LWPOLY *poly = NULL; + LWDEBUG(4,"entered"); + + /* No pointarray is a problem */ + if( ! pa ) + { + SET_PARSER_ERROR(PARSER_ERROR_OTHER); + return NULL; + } + + poly = lwpoly_construct_empty(SRID_UNKNOWN, FLAGS_GET_Z(pa->flags), FLAGS_GET_M(pa->flags)); + + /* Error out if we can't build this polygon. */ + if( ! poly ) + { + SET_PARSER_ERROR(PARSER_ERROR_OTHER); + return NULL; + } + + wkt_parser_polygon_add_ring(lwpoly_as_lwgeom(poly), pa, dimcheck); + return lwpoly_as_lwgeom(poly); +} + +LWGEOM* wkt_parser_polygon_add_ring(LWGEOM *poly, POINTARRAY *pa, char dimcheck) +{ + LWDEBUG(4,"entered"); + + /* Bad inputs are a problem */ + if( ! (pa && poly) ) + { + SET_PARSER_ERROR(PARSER_ERROR_OTHER); + return NULL; + } + + /* Rings must agree on dimensionality */ + if( FLAGS_NDIMS(poly->flags) != FLAGS_NDIMS(pa->flags) ) + { + ptarray_free(pa); + lwgeom_free(poly); + SET_PARSER_ERROR(PARSER_ERROR_MIXDIMS); + return NULL; + } + + /* Apply check for minimum number of points, if requested. */ + if( (global_parser_result.parser_check_flags & LW_PARSER_CHECK_MINPOINTS) && (pa->npoints < 4) ) + { + ptarray_free(pa); + lwgeom_free(poly); + SET_PARSER_ERROR(PARSER_ERROR_MOREPOINTS); + return NULL; + } + + /* Apply check for not closed rings, if requested. */ + if( (global_parser_result.parser_check_flags & LW_PARSER_CHECK_CLOSURE) && + ! (dimcheck == 'Z' ? ptarray_is_closed_z(pa) : ptarray_is_closed_2d(pa)) ) + { + ptarray_free(pa); + lwgeom_free(poly); + SET_PARSER_ERROR(PARSER_ERROR_UNCLOSED); + return NULL; + } + + /* If something goes wrong adding a ring, error out. */ + if ( LW_FAILURE == lwpoly_add_ring(lwgeom_as_lwpoly(poly), pa) ) + { + ptarray_free(pa); + lwgeom_free(poly); + SET_PARSER_ERROR(PARSER_ERROR_OTHER); + return NULL; + } + return poly; +} + +LWGEOM* wkt_parser_polygon_finalize(LWGEOM *poly, char *dimensionality) +{ + lwflags_t flags = wkt_dimensionality(dimensionality); + int flagdims = FLAGS_NDIMS(flags); + LWDEBUG(4,"entered"); + + /* Null input implies empty return */ + if( ! poly ) + return lwpoly_as_lwgeom(lwpoly_construct_empty(SRID_UNKNOWN, FLAGS_GET_Z(flags), FLAGS_GET_M(flags))); + + /* If the number of dimensions are not consistent, we have a problem. */ + if( flagdims > 2 ) + { + if ( flagdims != FLAGS_NDIMS(poly->flags) ) + { + lwgeom_free(poly); + SET_PARSER_ERROR(PARSER_ERROR_MIXDIMS); + return NULL; + } + + /* Harmonize the flags in the sub-components with the wkt flags */ + if( LW_FAILURE == wkt_parser_set_dims(poly, flags) ) + { + lwgeom_free(poly); + SET_PARSER_ERROR(PARSER_ERROR_OTHER); + return NULL; + } + } + + return poly; +} + +LWGEOM* wkt_parser_curvepolygon_new(LWGEOM *ring) +{ + LWGEOM *poly; + LWDEBUG(4,"entered"); + + /* Toss error on null geometry input */ + if( ! ring ) + { + SET_PARSER_ERROR(PARSER_ERROR_OTHER); + return NULL; + } + + /* Construct poly and add the ring. */ + poly = lwcurvepoly_as_lwgeom(lwcurvepoly_construct_empty(SRID_UNKNOWN, FLAGS_GET_Z(ring->flags), FLAGS_GET_M(ring->flags))); + /* Return the result. */ + return wkt_parser_curvepolygon_add_ring(poly,ring); +} + +LWGEOM* wkt_parser_curvepolygon_add_ring(LWGEOM *poly, LWGEOM *ring) +{ + LWDEBUG(4,"entered"); + + /* Toss error on null input */ + if( ! (ring && poly) ) + { + SET_PARSER_ERROR(PARSER_ERROR_OTHER); + LWDEBUG(4,"inputs are null"); + return NULL; + } + + /* All the elements must agree on dimensionality */ + if( FLAGS_NDIMS(poly->flags) != FLAGS_NDIMS(ring->flags) ) + { + LWDEBUG(4,"dimensionality does not match"); + lwgeom_free(ring); + lwgeom_free(poly); + SET_PARSER_ERROR(PARSER_ERROR_MIXDIMS); + return NULL; + } + + /* Apply check for minimum number of points, if requested. */ + if( (global_parser_result.parser_check_flags & LW_PARSER_CHECK_MINPOINTS) ) + { + uint32_t vertices_needed = 3; + + if ( ring->type == LINETYPE ) + vertices_needed = 4; + + if (lwgeom_count_vertices(ring) < vertices_needed) + { + LWDEBUG(4,"number of points is incorrect"); + lwgeom_free(ring); + lwgeom_free(poly); + SET_PARSER_ERROR(PARSER_ERROR_MOREPOINTS); + return NULL; + } + } + + /* Apply check for not closed rings, if requested. */ + if( (global_parser_result.parser_check_flags & LW_PARSER_CHECK_CLOSURE) ) + { + int is_closed = 1; + LWDEBUG(4,"checking ring closure"); + switch ( ring->type ) + { + case LINETYPE: + is_closed = lwline_is_closed(lwgeom_as_lwline(ring)); + break; + + case CIRCSTRINGTYPE: + is_closed = lwcircstring_is_closed(lwgeom_as_lwcircstring(ring)); + break; + + case COMPOUNDTYPE: + is_closed = lwcompound_is_closed(lwgeom_as_lwcompound(ring)); + break; + } + if ( ! is_closed ) + { + LWDEBUG(4,"ring is not closed"); + lwgeom_free(ring); + lwgeom_free(poly); + SET_PARSER_ERROR(PARSER_ERROR_UNCLOSED); + return NULL; + } + } + + if( LW_FAILURE == lwcurvepoly_add_ring(lwgeom_as_lwcurvepoly(poly), ring) ) + { + LWDEBUG(4,"failed to add ring"); + lwgeom_free(ring); + lwgeom_free(poly); + SET_PARSER_ERROR(PARSER_ERROR_OTHER); + return NULL; + } + + return poly; +} + +LWGEOM* wkt_parser_curvepolygon_finalize(LWGEOM *poly, char *dimensionality) +{ + lwflags_t flags = wkt_dimensionality(dimensionality); + int flagdims = FLAGS_NDIMS(flags); + LWDEBUG(4,"entered"); + + /* Null input implies empty return */ + if( ! poly ) + return lwcurvepoly_as_lwgeom(lwcurvepoly_construct_empty(SRID_UNKNOWN, FLAGS_GET_Z(flags), FLAGS_GET_M(flags))); + + if ( flagdims > 2 ) + { + /* If the number of dimensions are not consistent, we have a problem. */ + if( flagdims != FLAGS_NDIMS(poly->flags) ) + { + lwgeom_free(poly); + SET_PARSER_ERROR(PARSER_ERROR_MIXDIMS); + return NULL; + } + + /* Harmonize the flags in the sub-components with the wkt flags */ + if( LW_FAILURE == wkt_parser_set_dims(poly, flags) ) + { + lwgeom_free(poly); + SET_PARSER_ERROR(PARSER_ERROR_OTHER); + return NULL; + } + } + + return poly; +} + +LWGEOM* wkt_parser_collection_new(LWGEOM *geom) +{ + LWCOLLECTION *col; + LWGEOM **geoms; + static int ngeoms = 1; + LWDEBUG(4,"entered"); + + /* Toss error on null geometry input */ + if( ! geom ) + { + SET_PARSER_ERROR(PARSER_ERROR_OTHER); + return NULL; + } + + /* Create our geometry array */ + geoms = lwalloc(sizeof(LWGEOM*) * ngeoms); + geoms[0] = geom; + + /* Make a new collection */ + col = lwcollection_construct(COLLECTIONTYPE, SRID_UNKNOWN, NULL, ngeoms, geoms); + + /* Return the result. */ + return lwcollection_as_lwgeom(col); +} + + +LWGEOM* wkt_parser_compound_new(LWGEOM *geom) +{ + LWCOLLECTION *col; + LWGEOM **geoms; + static int ngeoms = 1; + LWDEBUG(4,"entered"); + + /* Toss error on null geometry input */ + if( ! geom ) + { + SET_PARSER_ERROR(PARSER_ERROR_OTHER); + return NULL; + } + + /* Elements of a compoundcurve cannot be empty, because */ + /* empty things can't join up and form a ring */ + if ( lwgeom_is_empty(geom) ) + { + lwgeom_free(geom); + SET_PARSER_ERROR(PARSER_ERROR_INCONTINUOUS); + return NULL; + } + + /* Create our geometry array */ + geoms = lwalloc(sizeof(LWGEOM*) * ngeoms); + geoms[0] = geom; + + /* Make a new collection */ + col = lwcollection_construct(COLLECTIONTYPE, SRID_UNKNOWN, NULL, ngeoms, geoms); + + /* Return the result. */ + return lwcollection_as_lwgeom(col); +} + + +LWGEOM* wkt_parser_compound_add_geom(LWGEOM *col, LWGEOM *geom) +{ + LWDEBUG(4,"entered"); + + /* Toss error on null geometry input */ + if( ! (geom && col) ) + { + SET_PARSER_ERROR(PARSER_ERROR_OTHER); + return NULL; + } + + /* All the elements must agree on dimensionality */ + if( FLAGS_NDIMS(col->flags) != FLAGS_NDIMS(geom->flags) ) + { + lwgeom_free(col); + lwgeom_free(geom); + SET_PARSER_ERROR(PARSER_ERROR_MIXDIMS); + return NULL; + } + + if( LW_FAILURE == lwcompound_add_lwgeom((LWCOMPOUND*)col, geom) ) + { + lwgeom_free(col); + lwgeom_free(geom); + SET_PARSER_ERROR(PARSER_ERROR_INCONTINUOUS); + return NULL; + } + + return col; +} + + +LWGEOM* wkt_parser_collection_add_geom(LWGEOM *col, LWGEOM *geom) +{ + LWDEBUG(4,"entered"); + + /* Toss error on null geometry input */ + if( ! (geom && col) ) + { + SET_PARSER_ERROR(PARSER_ERROR_OTHER); + return NULL; + } + + return lwcollection_as_lwgeom(lwcollection_add_lwgeom(lwgeom_as_lwcollection(col), geom)); +} + +LWGEOM* wkt_parser_collection_finalize(int lwtype, LWGEOM *geom, char *dimensionality) +{ + lwflags_t flags = wkt_dimensionality(dimensionality); + int flagdims = FLAGS_NDIMS(flags); + + /* No geometry means it is empty */ + if( ! geom ) + { + return lwcollection_as_lwgeom(lwcollection_construct_empty(lwtype, SRID_UNKNOWN, FLAGS_GET_Z(flags), FLAGS_GET_M(flags))); + } + + /* There are 'Z' or 'M' tokens in the signature */ + if ( flagdims > 2 ) + { + LWCOLLECTION *col = lwgeom_as_lwcollection(geom); + uint32_t i; + + for ( i = 0 ; i < col->ngeoms; i++ ) + { + LWGEOM *subgeom = col->geoms[i]; + if ( FLAGS_NDIMS(flags) != FLAGS_NDIMS(subgeom->flags) && + ! lwgeom_is_empty(subgeom) ) + { + lwgeom_free(geom); + SET_PARSER_ERROR(PARSER_ERROR_MIXDIMS); + return NULL; + } + + if ( lwtype == COLLECTIONTYPE && + ( (FLAGS_GET_Z(flags) != FLAGS_GET_Z(subgeom->flags)) || + (FLAGS_GET_M(flags) != FLAGS_GET_M(subgeom->flags)) ) && + ! lwgeom_is_empty(subgeom) ) + { + lwgeom_free(geom); + SET_PARSER_ERROR(PARSER_ERROR_MIXDIMS); + return NULL; + } + } + + /* Harmonize the collection dimensionality */ + if( LW_FAILURE == wkt_parser_set_dims(geom, flags) ) + { + lwgeom_free(geom); + SET_PARSER_ERROR(PARSER_ERROR_OTHER); + return NULL; + } + } + + /* Set the collection type */ + geom->type = lwtype; + + return geom; +} + +void +wkt_parser_geometry_new(LWGEOM *geom, int32_t srid) +{ + LWDEBUG(4,"entered"); + LWDEBUGF(4,"geom %p",geom); + LWDEBUGF(4,"srid %d",srid); + + if ( geom == NULL ) + { + lwerror("Parsed geometry is null!"); + return; + } + + if ( srid != SRID_UNKNOWN && srid <= SRID_MAXIMUM ) + lwgeom_set_srid(geom, srid); + else + lwgeom_set_srid(geom, SRID_UNKNOWN); + + global_parser_result.geom = geom; +} + +void lwgeom_parser_result_init(LWGEOM_PARSER_RESULT *parser_result) +{ + memset(parser_result, 0, sizeof(LWGEOM_PARSER_RESULT)); +} + + +void lwgeom_parser_result_free(LWGEOM_PARSER_RESULT *parser_result) +{ + if ( parser_result->geom ) + { + lwgeom_free(parser_result->geom); + parser_result->geom = 0; + } + if ( parser_result->serialized_lwgeom ) + { + lwfree(parser_result->serialized_lwgeom ); + parser_result->serialized_lwgeom = 0; + } + /* We don't free parser_result->message because + it is a const *char */ +} + +/* +* Public function used for easy access to the parser. +*/ +LWGEOM *lwgeom_from_wkt(const char *wkt, const char check) +{ + LWGEOM_PARSER_RESULT r; + + if( LW_FAILURE == lwgeom_parse_wkt(&r, (char*)wkt, check) ) + { + lwerror(r.message); + return NULL; + } + + return r.geom; +} + + diff --git a/mgist-postgis/liblwgeom/lwin_wkt.h b/mgist-postgis/liblwgeom/lwin_wkt.h new file mode 100644 index 0000000..1fdce31 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwin_wkt.h @@ -0,0 +1,80 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2010-2015 Paul Ramsey + * Copyright (C) 2011 Sandro Santilli + * + **********************************************************************/ + +#include "liblwgeom_internal.h" + +/* +* Coordinate object to hold information about last coordinate temporarily. +* We need to know how many dimensions there are at any given time. +*/ +typedef struct +{ + lwflags_t flags; + double x; + double y; + double z; + double m; +} +POINT; + +/* +* Global that holds the final output geometry for the WKT parser. +*/ +extern LWGEOM_PARSER_RESULT global_parser_result; +extern const char *parser_error_messages[]; + +/* +* Prototypes for the lexer +*/ +extern void wkt_lexer_init(char *str); +extern void wkt_lexer_close(void); +extern int wkt_yylex_destroy(void); + + +/* +* Functions called from within the bison parser to construct geometries. +*/ +int32_t wkt_lexer_read_srid(char *str); +POINT wkt_parser_coord_2(double c1, double c2); +POINT wkt_parser_coord_3(double c1, double c2, double c3); +POINT wkt_parser_coord_4(double c1, double c2, double c3, double c4); +POINTARRAY* wkt_parser_ptarray_add_coord(POINTARRAY *pa, POINT p); +POINTARRAY* wkt_parser_ptarray_new(POINT p); +LWGEOM* wkt_parser_point_new(POINTARRAY *pa, char *dimensionality); +LWGEOM* wkt_parser_linestring_new(POINTARRAY *pa, char *dimensionality); +LWGEOM* wkt_parser_circularstring_new(POINTARRAY *pa, char *dimensionality); +LWGEOM* wkt_parser_triangle_new(POINTARRAY *pa, char *dimensionality); +LWGEOM* wkt_parser_polygon_new(POINTARRAY *pa, char dimcheck); +LWGEOM* wkt_parser_polygon_add_ring(LWGEOM *poly, POINTARRAY *pa, char dimcheck); +LWGEOM* wkt_parser_polygon_finalize(LWGEOM *poly, char *dimensionality); +LWGEOM* wkt_parser_curvepolygon_new(LWGEOM *ring); +LWGEOM* wkt_parser_curvepolygon_add_ring(LWGEOM *poly, LWGEOM *ring); +LWGEOM* wkt_parser_curvepolygon_finalize(LWGEOM *poly, char *dimensionality); +LWGEOM* wkt_parser_compound_new(LWGEOM *element); +LWGEOM* wkt_parser_compound_add_geom(LWGEOM *col, LWGEOM *geom); +LWGEOM* wkt_parser_collection_new(LWGEOM *geom); +LWGEOM* wkt_parser_collection_add_geom(LWGEOM *col, LWGEOM *geom); +LWGEOM* wkt_parser_collection_finalize(int lwtype, LWGEOM *col, char *dimensionality); +void wkt_parser_geometry_new(LWGEOM *geom, int32_t srid); diff --git a/mgist-postgis/liblwgeom/lwin_wkt_lex.c b/mgist-postgis/liblwgeom/lwin_wkt_lex.c new file mode 100644 index 0000000..f194112 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwin_wkt_lex.c @@ -0,0 +1,2321 @@ +#line 1 "lwin_wkt_lex.c" + +#line 3 "lwin_wkt_lex.c" + +#define YY_INT_ALIGNED short int + +/* A lexical scanner generated by flex */ + +#define yy_create_buffer wkt_yy_create_buffer +#define yy_delete_buffer wkt_yy_delete_buffer +#define yy_scan_buffer wkt_yy_scan_buffer +#define yy_scan_string wkt_yy_scan_string +#define yy_scan_bytes wkt_yy_scan_bytes +#define yy_init_buffer wkt_yy_init_buffer +#define yy_flush_buffer wkt_yy_flush_buffer +#define yy_load_buffer_state wkt_yy_load_buffer_state +#define yy_switch_to_buffer wkt_yy_switch_to_buffer +#define yypush_buffer_state wkt_yypush_buffer_state +#define yypop_buffer_state wkt_yypop_buffer_state +#define yyensure_buffer_stack wkt_yyensure_buffer_stack +#define yy_flex_debug wkt_yy_flex_debug +#define yyin wkt_yyin +#define yyleng wkt_yyleng +#define yylex wkt_yylex +#define yylineno wkt_yylineno +#define yyout wkt_yyout +#define yyrestart wkt_yyrestart +#define yytext wkt_yytext +#define yywrap wkt_yywrap +#define yyalloc wkt_yyalloc +#define yyrealloc wkt_yyrealloc +#define yyfree wkt_yyfree + +#define FLEX_SCANNER +#define YY_FLEX_MAJOR_VERSION 2 +#define YY_FLEX_MINOR_VERSION 6 +#define YY_FLEX_SUBMINOR_VERSION 4 +#if YY_FLEX_SUBMINOR_VERSION > 0 +#define FLEX_BETA +#endif + +#ifdef yy_create_buffer +#define wkt_yy_create_buffer_ALREADY_DEFINED +#else +#define yy_create_buffer wkt_yy_create_buffer +#endif + +#ifdef yy_delete_buffer +#define wkt_yy_delete_buffer_ALREADY_DEFINED +#else +#define yy_delete_buffer wkt_yy_delete_buffer +#endif + +#ifdef yy_scan_buffer +#define wkt_yy_scan_buffer_ALREADY_DEFINED +#else +#define yy_scan_buffer wkt_yy_scan_buffer +#endif + +#ifdef yy_scan_string +#define wkt_yy_scan_string_ALREADY_DEFINED +#else +#define yy_scan_string wkt_yy_scan_string +#endif + +#ifdef yy_scan_bytes +#define wkt_yy_scan_bytes_ALREADY_DEFINED +#else +#define yy_scan_bytes wkt_yy_scan_bytes +#endif + +#ifdef yy_init_buffer +#define wkt_yy_init_buffer_ALREADY_DEFINED +#else +#define yy_init_buffer wkt_yy_init_buffer +#endif + +#ifdef yy_flush_buffer +#define wkt_yy_flush_buffer_ALREADY_DEFINED +#else +#define yy_flush_buffer wkt_yy_flush_buffer +#endif + +#ifdef yy_load_buffer_state +#define wkt_yy_load_buffer_state_ALREADY_DEFINED +#else +#define yy_load_buffer_state wkt_yy_load_buffer_state +#endif + +#ifdef yy_switch_to_buffer +#define wkt_yy_switch_to_buffer_ALREADY_DEFINED +#else +#define yy_switch_to_buffer wkt_yy_switch_to_buffer +#endif + +#ifdef yypush_buffer_state +#define wkt_yypush_buffer_state_ALREADY_DEFINED +#else +#define yypush_buffer_state wkt_yypush_buffer_state +#endif + +#ifdef yypop_buffer_state +#define wkt_yypop_buffer_state_ALREADY_DEFINED +#else +#define yypop_buffer_state wkt_yypop_buffer_state +#endif + +#ifdef yyensure_buffer_stack +#define wkt_yyensure_buffer_stack_ALREADY_DEFINED +#else +#define yyensure_buffer_stack wkt_yyensure_buffer_stack +#endif + +#ifdef yylex +#define wkt_yylex_ALREADY_DEFINED +#else +#define yylex wkt_yylex +#endif + +#ifdef yyrestart +#define wkt_yyrestart_ALREADY_DEFINED +#else +#define yyrestart wkt_yyrestart +#endif + +#ifdef yylex_init +#define wkt_yylex_init_ALREADY_DEFINED +#else +#define yylex_init wkt_yylex_init +#endif + +#ifdef yylex_init_extra +#define wkt_yylex_init_extra_ALREADY_DEFINED +#else +#define yylex_init_extra wkt_yylex_init_extra +#endif + +#ifdef yylex_destroy +#define wkt_yylex_destroy_ALREADY_DEFINED +#else +#define yylex_destroy wkt_yylex_destroy +#endif + +#ifdef yyget_debug +#define wkt_yyget_debug_ALREADY_DEFINED +#else +#define yyget_debug wkt_yyget_debug +#endif + +#ifdef yyset_debug +#define wkt_yyset_debug_ALREADY_DEFINED +#else +#define yyset_debug wkt_yyset_debug +#endif + +#ifdef yyget_extra +#define wkt_yyget_extra_ALREADY_DEFINED +#else +#define yyget_extra wkt_yyget_extra +#endif + +#ifdef yyset_extra +#define wkt_yyset_extra_ALREADY_DEFINED +#else +#define yyset_extra wkt_yyset_extra +#endif + +#ifdef yyget_in +#define wkt_yyget_in_ALREADY_DEFINED +#else +#define yyget_in wkt_yyget_in +#endif + +#ifdef yyset_in +#define wkt_yyset_in_ALREADY_DEFINED +#else +#define yyset_in wkt_yyset_in +#endif + +#ifdef yyget_out +#define wkt_yyget_out_ALREADY_DEFINED +#else +#define yyget_out wkt_yyget_out +#endif + +#ifdef yyset_out +#define wkt_yyset_out_ALREADY_DEFINED +#else +#define yyset_out wkt_yyset_out +#endif + +#ifdef yyget_leng +#define wkt_yyget_leng_ALREADY_DEFINED +#else +#define yyget_leng wkt_yyget_leng +#endif + +#ifdef yyget_text +#define wkt_yyget_text_ALREADY_DEFINED +#else +#define yyget_text wkt_yyget_text +#endif + +#ifdef yyget_lineno +#define wkt_yyget_lineno_ALREADY_DEFINED +#else +#define yyget_lineno wkt_yyget_lineno +#endif + +#ifdef yyset_lineno +#define wkt_yyset_lineno_ALREADY_DEFINED +#else +#define yyset_lineno wkt_yyset_lineno +#endif + +#ifdef yywrap +#define wkt_yywrap_ALREADY_DEFINED +#else +#define yywrap wkt_yywrap +#endif + +#ifdef yyalloc +#define wkt_yyalloc_ALREADY_DEFINED +#else +#define yyalloc wkt_yyalloc +#endif + +#ifdef yyrealloc +#define wkt_yyrealloc_ALREADY_DEFINED +#else +#define yyrealloc wkt_yyrealloc +#endif + +#ifdef yyfree +#define wkt_yyfree_ALREADY_DEFINED +#else +#define yyfree wkt_yyfree +#endif + +#ifdef yytext +#define wkt_yytext_ALREADY_DEFINED +#else +#define yytext wkt_yytext +#endif + +#ifdef yyleng +#define wkt_yyleng_ALREADY_DEFINED +#else +#define yyleng wkt_yyleng +#endif + +#ifdef yyin +#define wkt_yyin_ALREADY_DEFINED +#else +#define yyin wkt_yyin +#endif + +#ifdef yyout +#define wkt_yyout_ALREADY_DEFINED +#else +#define yyout wkt_yyout +#endif + +#ifdef yy_flex_debug +#define wkt_yy_flex_debug_ALREADY_DEFINED +#else +#define yy_flex_debug wkt_yy_flex_debug +#endif + +#ifdef yylineno +#define wkt_yylineno_ALREADY_DEFINED +#else +#define yylineno wkt_yylineno +#endif + +/* First, we deal with platform-specific or compiler-specific issues. */ + +/* begin standard C headers. */ +#include +#include +#include +#include + +/* end standard C headers. */ + +/* flex integer type definitions */ + +#ifndef FLEXINT_H +#define FLEXINT_H + +/* C99 systems have . Non-C99 systems may or may not. */ + +#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L + +/* C99 says to define __STDC_LIMIT_MACROS before including stdint.h, + * if you want the limit (max/min) macros for int types. + */ +#ifndef __STDC_LIMIT_MACROS +#define __STDC_LIMIT_MACROS 1 +#endif + +#include +typedef int8_t flex_int8_t; +typedef uint8_t flex_uint8_t; +typedef int16_t flex_int16_t; +typedef uint16_t flex_uint16_t; +typedef int32_t flex_int32_t; +typedef uint32_t flex_uint32_t; +#else +typedef signed char flex_int8_t; +typedef short int flex_int16_t; +typedef int flex_int32_t; +typedef unsigned char flex_uint8_t; +typedef unsigned short int flex_uint16_t; +typedef unsigned int flex_uint32_t; + +/* Limits of integral types. */ +#ifndef INT8_MIN +#define INT8_MIN (-128) +#endif +#ifndef INT16_MIN +#define INT16_MIN (-32767-1) +#endif +#ifndef INT32_MIN +#define INT32_MIN (-2147483647-1) +#endif +#ifndef INT8_MAX +#define INT8_MAX (127) +#endif +#ifndef INT16_MAX +#define INT16_MAX (32767) +#endif +#ifndef INT32_MAX +#define INT32_MAX (2147483647) +#endif +#ifndef UINT8_MAX +#define UINT8_MAX (255U) +#endif +#ifndef UINT16_MAX +#define UINT16_MAX (65535U) +#endif +#ifndef UINT32_MAX +#define UINT32_MAX (4294967295U) +#endif + +#ifndef SIZE_MAX +#define SIZE_MAX (~(size_t)0) +#endif + +#endif /* ! C99 */ + +#endif /* ! FLEXINT_H */ + +/* begin standard C++ headers. */ + +/* TODO: this is always defined, so inline it */ +#define yyconst const + +#if defined(__GNUC__) && __GNUC__ >= 3 +#define yynoreturn __attribute__((__noreturn__)) +#else +#define yynoreturn +#endif + +/* Returned upon end-of-file. */ +#define YY_NULL 0 + +/* Promotes a possibly negative, possibly signed char to an + * integer in range [0..255] for use as an array index. + */ +#define YY_SC_TO_UI(c) ((YY_CHAR) (c)) + +/* Enter a start condition. This macro really ought to take a parameter, + * but we do it the disgusting crufty way forced on us by the ()-less + * definition of BEGIN. + */ +#define BEGIN (yy_start) = 1 + 2 * +/* Translate the current start state into a value that can be later handed + * to BEGIN to return to the state. The YYSTATE alias is for lex + * compatibility. + */ +#define YY_START (((yy_start) - 1) / 2) +#define YYSTATE YY_START +/* Action number for EOF rule of a given start state. */ +#define YY_STATE_EOF(state) (YY_END_OF_BUFFER + state + 1) +/* Special action meaning "start processing a new file". */ +#define YY_NEW_FILE yyrestart( yyin ) +#define YY_END_OF_BUFFER_CHAR 0 + +/* Size of default input buffer. */ +#ifndef YY_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k. + * Moreover, YY_BUF_SIZE is 2*YY_READ_BUF_SIZE in the general case. + * Ditto for the __ia64__ case accordingly. + */ +#define YY_BUF_SIZE 32768 +#else +#define YY_BUF_SIZE 16384 +#endif /* __ia64__ */ +#endif + +/* The state buf must be large enough to hold one state per character in the main buffer. + */ +#define YY_STATE_BUF_SIZE ((YY_BUF_SIZE + 2) * sizeof(yy_state_type)) + +#ifndef YY_TYPEDEF_YY_BUFFER_STATE +#define YY_TYPEDEF_YY_BUFFER_STATE +typedef struct yy_buffer_state *YY_BUFFER_STATE; +#endif + +#ifndef YY_TYPEDEF_YY_SIZE_T +#define YY_TYPEDEF_YY_SIZE_T +typedef size_t yy_size_t; +#endif + +extern int yyleng; + +extern FILE *yyin, *yyout; + +#define EOB_ACT_CONTINUE_SCAN 0 +#define EOB_ACT_END_OF_FILE 1 +#define EOB_ACT_LAST_MATCH 2 + + #define YY_LESS_LINENO(n) + #define YY_LINENO_REWIND_TO(ptr) + +/* Return all but the first "n" matched characters back to the input stream. */ +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + *yy_cp = (yy_hold_char); \ + YY_RESTORE_YY_MORE_OFFSET \ + (yy_c_buf_p) = yy_cp = yy_bp + yyless_macro_arg - YY_MORE_ADJ; \ + YY_DO_BEFORE_ACTION; /* set up yytext again */ \ + } \ + while ( 0 ) +#define unput(c) yyunput( c, (yytext_ptr) ) + +#ifndef YY_STRUCT_YY_BUFFER_STATE +#define YY_STRUCT_YY_BUFFER_STATE +struct yy_buffer_state + { + FILE *yy_input_file; + + char *yy_ch_buf; /* input buffer */ + char *yy_buf_pos; /* current position in input buffer */ + + /* Size of input buffer in bytes, not including room for EOB + * characters. + */ + int yy_buf_size; + + /* Number of characters read into yy_ch_buf, not including EOB + * characters. + */ + int yy_n_chars; + + /* Whether we "own" the buffer - i.e., we know we created it, + * and can realloc() it to grow it, and should free() it to + * delete it. + */ + int yy_is_our_buffer; + + /* Whether this is an "interactive" input source; if so, and + * if we're using stdio for input, then we want to use getc() + * instead of fread(), to make sure we stop fetching input after + * each newline. + */ + int yy_is_interactive; + + /* Whether we're considered to be at the beginning of a line. + * If so, '^' rules will be active on the next match, otherwise + * not. + */ + int yy_at_bol; + + int yy_bs_lineno; /**< The line count. */ + int yy_bs_column; /**< The column count. */ + + /* Whether to try to fill the input buffer when we reach the + * end of it. + */ + int yy_fill_buffer; + + int yy_buffer_status; + +#define YY_BUFFER_NEW 0 +#define YY_BUFFER_NORMAL 1 + /* When an EOF's been seen but there's still some text to process + * then we mark the buffer as YY_EOF_PENDING, to indicate that we + * shouldn't try reading from the input source any more. We might + * still have a bunch of tokens to match, though, because of + * possible backing-up. + * + * When we actually see the EOF, we change the status to "new" + * (via yyrestart()), so that the user can continue scanning by + * just pointing yyin at a new input file. + */ +#define YY_BUFFER_EOF_PENDING 2 + + }; +#endif /* !YY_STRUCT_YY_BUFFER_STATE */ + +/* Stack of input buffers. */ +static size_t yy_buffer_stack_top = 0; /**< index of top of stack. */ +static size_t yy_buffer_stack_max = 0; /**< capacity of stack. */ +static YY_BUFFER_STATE * yy_buffer_stack = NULL; /**< Stack as an array. */ + +/* We provide macros for accessing buffer states in case in the + * future we want to put the buffer states in a more general + * "scanner state". + * + * Returns the top of the stack, or NULL. + */ +#define YY_CURRENT_BUFFER ( (yy_buffer_stack) \ + ? (yy_buffer_stack)[(yy_buffer_stack_top)] \ + : NULL) +/* Same as previous macro, but useful when we know that the buffer stack is not + * NULL or when we need an lvalue. For internal use only. + */ +#define YY_CURRENT_BUFFER_LVALUE (yy_buffer_stack)[(yy_buffer_stack_top)] + +/* yy_hold_char holds the character lost when yytext is formed. */ +static char yy_hold_char; +static int yy_n_chars; /* number of characters read into yy_ch_buf */ +int yyleng; + +/* Points to current character in buffer. */ +static char *yy_c_buf_p = NULL; +static int yy_init = 0; /* whether we need to initialize */ +static int yy_start = 0; /* start state number */ + +/* Flag which is used to allow yywrap()'s to do buffer switches + * instead of setting up a fresh yyin. A bit of a hack ... + */ +static int yy_did_buffer_switch_on_eof; + +void yyrestart ( FILE *input_file ); +void yy_switch_to_buffer ( YY_BUFFER_STATE new_buffer ); +YY_BUFFER_STATE yy_create_buffer ( FILE *file, int size ); +void yy_delete_buffer ( YY_BUFFER_STATE b ); +void yy_flush_buffer ( YY_BUFFER_STATE b ); +void yypush_buffer_state ( YY_BUFFER_STATE new_buffer ); +void yypop_buffer_state ( void ); + +static void yyensure_buffer_stack ( void ); +static void yy_load_buffer_state ( void ); +static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file ); +#define YY_FLUSH_BUFFER yy_flush_buffer( YY_CURRENT_BUFFER ) + +YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size ); +YY_BUFFER_STATE yy_scan_string ( const char *yy_str ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len ); + +void *yyalloc ( yy_size_t ); +void *yyrealloc ( void *, yy_size_t ); +void yyfree ( void * ); + +#define yy_new_buffer yy_create_buffer +#define yy_set_interactive(is_interactive) \ + { \ + if ( ! YY_CURRENT_BUFFER ){ \ + yyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_is_interactive = is_interactive; \ + } +#define yy_set_bol(at_bol) \ + { \ + if ( ! YY_CURRENT_BUFFER ){\ + yyensure_buffer_stack (); \ + YY_CURRENT_BUFFER_LVALUE = \ + yy_create_buffer( yyin, YY_BUF_SIZE ); \ + } \ + YY_CURRENT_BUFFER_LVALUE->yy_at_bol = at_bol; \ + } +#define YY_AT_BOL() (YY_CURRENT_BUFFER_LVALUE->yy_at_bol) + +/* Begin user sect3 */ + +#define wkt_yywrap() (/*CONSTCOND*/1) +#define YY_SKIP_YYWRAP +typedef flex_uint8_t YY_CHAR; + +FILE *yyin = NULL, *yyout = NULL; + +typedef int yy_state_type; + +extern int yylineno; +int yylineno = 1; + +extern char *yytext; +#ifdef yytext_ptr +#undef yytext_ptr +#endif +#define yytext_ptr yytext + +static yy_state_type yy_get_previous_state ( void ); +static yy_state_type yy_try_NUL_trans ( yy_state_type current_state ); +static int yy_get_next_buffer ( void ); +static void yynoreturn yy_fatal_error ( const char* msg ); + +/* Done after the current pattern has been matched and before the + * corresponding action - sets up yytext. + */ +#define YY_DO_BEFORE_ACTION \ + (yytext_ptr) = yy_bp; \ + yyleng = (int) (yy_cp - yy_bp); \ + (yy_hold_char) = *yy_cp; \ + *yy_cp = '\0'; \ + (yy_c_buf_p) = yy_cp; +#define YY_NUM_RULES 27 +#define YY_END_OF_BUFFER 28 +/* This struct is not used in this scanner, + but its presence is necessary. */ +struct yy_trans_info + { + flex_int32_t yy_verify; + flex_int32_t yy_nxt; + }; +static const flex_int16_t yy_accept[177] = + { 0, + 0, 0, 28, 26, 25, 25, 21, 22, 23, 26, + 26, 26, 24, 26, 26, 26, 26, 20, 26, 26, + 26, 26, 20, 25, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, + 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, + 0, 0, 0, 19, 0, 0, 0, 18, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, + + 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 14, 7, 0, 9, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 10, 0, 0, 6, 5, 0, 0, 12, + 0, 0, 0, 13, 0, 0, 0, 0, 8, 0, + 0, 0, 0, 15, 4, 0 + } ; + +static const YY_CHAR yy_ec[256] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, + 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 2, 1, 1, 1, 1, 1, 1, 1, 4, + 5, 1, 6, 7, 8, 9, 1, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 1, 11, 1, + 12, 1, 1, 1, 13, 1, 14, 15, 16, 17, + 18, 19, 20, 1, 1, 21, 22, 23, 24, 25, + 1, 26, 27, 28, 29, 30, 1, 1, 31, 32, + 1, 1, 1, 1, 1, 1, 33, 1, 34, 35, + + 36, 37, 38, 39, 40, 1, 1, 41, 42, 43, + 44, 45, 1, 46, 47, 48, 49, 50, 1, 1, + 51, 52, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1 + } ; + +static const YY_CHAR yy_meta[53] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1 + } ; + +static const flex_int16_t yy_base[177] = + { 0, + 0, 0, 374, 391, 51, 53, 391, 391, 391, 48, + 362, 57, 391, 41, 41, 52, 49, 42, 59, 50, + 49, 56, 55, 76, 361, 0, 96, 391, 106, 109, + 54, 62, 61, 80, 86, 91, 97, 97, 101, 103, + 101, 106, 391, 193, 126, 113, 110, 107, 111, 123, + 132, 121, 149, 127, 122, 143, 391, 147, 132, 138, + 147, 133, 150, 140, 151, 391, 144, 156, 189, 153, + 156, 150, 160, 391, 160, 161, 196, 391, 169, 182, + 192, 186, 193, 184, 187, 186, 188, 186, 196, 195, + 191, 199, 210, 180, 76, 207, 203, 218, 217, 213, + + 226, 221, 227, 231, 228, 391, 229, 240, 230, 245, + 229, 247, 239, 233, 249, 245, 238, 256, 265, 391, + 256, 257, 269, 266, 273, 276, 267, 267, 278, 284, + 278, 274, 275, 278, 282, 391, 391, 277, 391, 284, + 295, 286, 298, 294, 303, 306, 304, 308, 316, 305, + 312, 320, 391, 321, 319, 391, 391, 314, 323, 391, + 328, 320, 328, 391, 320, 331, 340, 338, 391, 350, + 342, 351, 347, 391, 391, 391 + } ; + +static const flex_int16_t yy_def[177] = + { 0, + 176, 1, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 12, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 0 + } ; + +static const flex_int16_t yy_nxt[444] = + { 0, + 4, 5, 6, 7, 8, 4, 9, 10, 11, 12, + 13, 4, 4, 14, 4, 15, 4, 16, 4, 4, + 17, 18, 19, 4, 20, 4, 21, 22, 4, 4, + 4, 23, 4, 14, 4, 15, 4, 16, 4, 4, + 17, 18, 19, 4, 20, 4, 21, 22, 4, 4, + 4, 23, 24, 24, 24, 24, 25, 26, 28, 28, + 31, 28, 34, 28, 32, 29, 26, 35, 36, 33, + 37, 38, 30, 39, 40, 41, 43, 24, 24, 46, + 31, 42, 34, 47, 32, 95, 48, 35, 36, 33, + 37, 38, 30, 39, 40, 41, 43, 28, 28, 46, + + 28, 42, 28, 47, 49, 27, 48, 28, 28, 50, + 28, 30, 28, 51, 44, 27, 44, 52, 45, 53, + 54, 55, 56, 57, 49, 58, 59, 28, 28, 50, + 28, 30, 28, 51, 60, 45, 61, 52, 62, 53, + 54, 55, 56, 57, 63, 58, 59, 64, 65, 67, + 66, 66, 68, 66, 60, 66, 61, 69, 62, 70, + 71, 72, 73, 74, 63, 75, 76, 64, 65, 67, + 77, 78, 68, 79, 80, 82, 83, 69, 84, 70, + 71, 72, 73, 74, 85, 75, 76, 86, 87, 95, + 77, 78, 92, 79, 80, 82, 83, 93, 84, 94, + + 81, 95, 45, 96, 85, 97, 98, 86, 87, 88, + 99, 100, 92, 101, 102, 103, 89, 93, 104, 105, + 90, 106, 91, 96, 107, 97, 98, 108, 109, 88, + 99, 100, 110, 101, 102, 103, 89, 111, 104, 105, + 90, 106, 91, 112, 107, 113, 114, 108, 109, 115, + 116, 117, 110, 118, 119, 120, 121, 111, 122, 123, + 124, 125, 126, 112, 127, 113, 114, 128, 129, 115, + 116, 117, 130, 118, 119, 120, 121, 131, 122, 123, + 124, 125, 126, 132, 127, 133, 134, 128, 129, 135, + 136, 137, 130, 138, 139, 140, 141, 131, 142, 143, + + 144, 145, 146, 132, 147, 133, 134, 148, 149, 135, + 136, 137, 150, 138, 139, 140, 141, 151, 142, 143, + 144, 145, 146, 152, 147, 153, 154, 148, 149, 155, + 156, 157, 150, 158, 159, 160, 161, 151, 162, 163, + 164, 165, 166, 152, 167, 153, 154, 168, 169, 155, + 156, 157, 170, 158, 159, 160, 161, 171, 162, 163, + 164, 165, 166, 172, 167, 173, 174, 168, 169, 175, + 27, 27, 170, 176, 176, 176, 176, 171, 176, 176, + 176, 176, 176, 172, 176, 173, 174, 176, 176, 175, + 3, 176, 176, 176, 176, 176, 176, 176, 176, 176, + + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176 + } ; + +static const flex_int16_t yy_chk[444] = + { 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 5, 5, 6, 6, 10, 10, 12, 12, + 14, 12, 15, 12, 14, 12, 12, 16, 17, 14, + 18, 19, 12, 20, 21, 22, 23, 24, 24, 31, + 14, 22, 15, 32, 14, 95, 33, 16, 17, 14, + 18, 19, 12, 20, 21, 22, 23, 27, 27, 31, + + 27, 22, 27, 32, 34, 27, 33, 29, 29, 35, + 29, 27, 29, 36, 30, 29, 30, 37, 30, 38, + 39, 39, 40, 41, 34, 42, 46, 45, 45, 35, + 45, 27, 45, 36, 47, 45, 48, 37, 49, 38, + 39, 39, 40, 41, 50, 42, 46, 51, 52, 54, + 53, 53, 55, 53, 47, 53, 48, 56, 49, 58, + 59, 60, 61, 62, 50, 63, 64, 51, 52, 54, + 65, 67, 55, 68, 68, 70, 71, 56, 72, 58, + 59, 60, 61, 62, 73, 63, 64, 75, 76, 94, + 65, 67, 79, 68, 68, 70, 71, 80, 72, 81, + + 69, 81, 44, 82, 73, 83, 84, 75, 76, 77, + 85, 86, 79, 87, 88, 89, 77, 80, 90, 91, + 77, 92, 77, 82, 93, 83, 84, 96, 97, 77, + 85, 86, 98, 87, 88, 89, 77, 99, 90, 91, + 77, 92, 77, 100, 93, 101, 102, 96, 97, 103, + 104, 104, 98, 105, 107, 108, 109, 99, 110, 111, + 112, 113, 114, 100, 115, 101, 102, 116, 117, 103, + 104, 104, 118, 105, 107, 108, 109, 119, 110, 111, + 112, 113, 114, 121, 115, 122, 123, 116, 117, 124, + 125, 126, 118, 127, 128, 129, 130, 119, 131, 132, + + 133, 134, 135, 121, 138, 122, 123, 140, 141, 124, + 125, 126, 142, 127, 128, 129, 130, 143, 131, 132, + 133, 134, 135, 144, 138, 145, 146, 140, 141, 147, + 148, 149, 142, 150, 151, 152, 154, 143, 155, 158, + 159, 161, 162, 144, 163, 145, 146, 165, 166, 147, + 148, 149, 167, 150, 151, 152, 154, 168, 155, 158, + 159, 161, 162, 170, 163, 171, 172, 165, 166, 173, + 25, 11, 167, 3, 0, 0, 0, 168, 0, 0, + 0, 0, 0, 170, 0, 171, 172, 0, 0, 173, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176, 176, 176, 176, 176, 176, 176, 176, + 176, 176, 176 + } ; + +static yy_state_type yy_last_accepting_state; +static char *yy_last_accepting_cpos; + +extern int yy_flex_debug; +int yy_flex_debug = 0; + +/* The intent behind this definition is that it'll catch + * any uses of REJECT which flex missed. + */ +#define REJECT reject_used_but_not_detected +#define yymore() yymore_used_but_not_detected +#define YY_MORE_ADJ 0 +#define YY_RESTORE_YY_MORE_OFFSET +char *yytext; +#line 1 "lwin_wkt_lex.l" +#line 2 "lwin_wkt_lex.l" + +/* The lexer */ + +#include +#include +#include "lwin_wkt.h" +#include "lwin_wkt_parse.h" +#include "lwgeom_log.h" + +static YY_BUFFER_STATE wkt_yy_buf_state; + +/* +* Handle errors due to unexpected junk in WKT strings. +*/ +static void wkt_lexer_unknown() +{ + /* Set the global error state */ + global_parser_result.errcode = PARSER_ERROR_OTHER; + global_parser_result.message = parser_error_messages[PARSER_ERROR_OTHER]; + global_parser_result.errlocation = wkt_yylloc.last_column; +} + +/* +* This macro is magically run after a rule is found but before the main +* action is run. We use it to update the parse location information +* so we can report on where things fail. Also optionally to dump +* debugging info. +*/ +#define YY_USER_ACTION do { \ + wkt_yylloc.first_line = wkt_yylloc.last_line = yylineno; \ + wkt_yylloc.first_column = wkt_yylloc.last_column; \ + wkt_yylloc.last_column += yyleng; \ + LWDEBUGF(5,"lex: %s", wkt_yytext); \ + } while (0); + +/* +* Ensure we have a definition of NAN to use when encountering +* NAN tokens. +*/ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#ifndef NAN +#define NAN 0.0/0.0 +#endif + +#line 906 "lwin_wkt_lex.c" +#define YY_NO_INPUT 1 +/* Suppress the default implementations. */ +#line 909 "lwin_wkt_lex.c" + +#define INITIAL 0 + +#ifndef YY_NO_UNISTD_H +/* Special case for "unistd.h", since it is non-ANSI. We include it way + * down here because we want the user's section 1 to have been scanned first. + * The user has a chance to override it with an option. + */ +#include +#endif + +#ifndef YY_EXTRA_TYPE +#define YY_EXTRA_TYPE void * +#endif + +static int yy_init_globals ( void ); + +/* Accessor methods to globals. + These are made visible to non-reentrant scanners for convenience. */ + +int yylex_destroy ( void ); + +int yyget_debug ( void ); + +void yyset_debug ( int debug_flag ); + +YY_EXTRA_TYPE yyget_extra ( void ); + +void yyset_extra ( YY_EXTRA_TYPE user_defined ); + +FILE *yyget_in ( void ); + +void yyset_in ( FILE * _in_str ); + +FILE *yyget_out ( void ); + +void yyset_out ( FILE * _out_str ); + + int yyget_leng ( void ); + +char *yyget_text ( void ); + +int yyget_lineno ( void ); + +void yyset_lineno ( int _line_number ); + +/* Macros after this point can all be overridden by user definitions in + * section 1. + */ + +#ifndef YY_SKIP_YYWRAP +#ifdef __cplusplus +extern "C" int yywrap ( void ); +#else +extern int yywrap ( void ); +#endif +#endif + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef yytext_ptr +static void yy_flex_strncpy ( char *, const char *, int ); +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen ( const char * ); +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus +static int yyinput ( void ); +#else +static int input ( void ); +#endif + +#endif + +/* Amount of stuff to slurp up with each read. */ +#ifndef YY_READ_BUF_SIZE +#ifdef __ia64__ +/* On IA-64, the buffer size is 16k, not 8k */ +#define YY_READ_BUF_SIZE 16384 +#else +#define YY_READ_BUF_SIZE 8192 +#endif /* __ia64__ */ +#endif + +/* Copy whatever the last rule matched to the standard output. */ +#ifndef ECHO +/* This used to be an fputs(), but since the string might contain NUL's, + * we now use fwrite(). + */ +#define ECHO do { if (fwrite( yytext, (size_t) yyleng, 1, yyout )) {} } while (0) +#endif + +/* Gets input and stuffs it into "buf". number of characters read, or YY_NULL, + * is returned in "result". + */ +#ifndef YY_INPUT +#define YY_INPUT(buf,result,max_size) \ + if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ + { \ + int c = '*'; \ + int n; \ + for ( n = 0; n < max_size && \ + (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ + buf[n] = (char) c; \ + if ( c == '\n' ) \ + buf[n++] = (char) c; \ + if ( c == EOF && ferror( yyin ) ) \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + result = n; \ + } \ + else \ + { \ + errno=0; \ + while ( (result = (int) fread(buf, 1, (yy_size_t) max_size, yyin)) == 0 && ferror(yyin)) \ + { \ + if( errno != EINTR) \ + { \ + YY_FATAL_ERROR( "input in flex scanner failed" ); \ + break; \ + } \ + errno=0; \ + clearerr(yyin); \ + } \ + }\ +\ + +#endif + +/* No semi-colon after return; correct usage is to write "yyterminate();" - + * we don't want an extra ';' after the "return" because that will cause + * some compilers to complain about unreachable statements. + */ +#ifndef yyterminate +#define yyterminate() return YY_NULL +#endif + +/* Number of entries by which start-condition stack grows. */ +#ifndef YY_START_STACK_INCR +#define YY_START_STACK_INCR 25 +#endif + +/* Report a fatal error. */ +#ifndef YY_FATAL_ERROR +#define YY_FATAL_ERROR(msg) yy_fatal_error( msg ) +#endif + +/* end tables serialization structures and prototypes */ + +/* Default declaration of generated scanner - a define so the user can + * easily add parameters. + */ +#ifndef YY_DECL +#define YY_DECL_IS_OURS 1 + +extern int yylex (void); + +#define YY_DECL int yylex (void) +#endif /* !YY_DECL */ + +/* Code executed at the beginning of each rule, after yytext and yyleng + * have been set up. + */ +#ifndef YY_USER_ACTION +#define YY_USER_ACTION +#endif + +/* Code executed at the end of each rule. */ +#ifndef YY_BREAK +#define YY_BREAK /*LINTED*/break; +#endif + +#define YY_RULE_SETUP \ + YY_USER_ACTION + +/** The main scanner function which does all the work. + */ +YY_DECL +{ + yy_state_type yy_current_state; + char *yy_cp, *yy_bp; + int yy_act; + + if ( !(yy_init) ) + { + (yy_init) = 1; + +#ifdef YY_USER_INIT + YY_USER_INIT; +#endif + + if ( ! (yy_start) ) + (yy_start) = 1; /* first start state */ + + if ( ! yyin ) + yyin = stdin; + + if ( ! yyout ) + yyout = stdout; + + if ( ! YY_CURRENT_BUFFER ) { + yyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE ); + } + + yy_load_buffer_state( ); + } + + { +#line 60 "lwin_wkt_lex.l" + + +#line 1127 "lwin_wkt_lex.c" + + while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */ + { + yy_cp = (yy_c_buf_p); + + /* Support of yytext. */ + *yy_cp = (yy_hold_char); + + /* yy_bp points to the position in yy_ch_buf of the start of + * the current run. + */ + yy_bp = yy_cp; + + yy_current_state = (yy_start); +yy_match: + do + { + YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)] ; + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 177 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + ++yy_cp; + } + while ( yy_current_state != 176 ); + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + +yy_find_action: + yy_act = yy_accept[yy_current_state]; + + YY_DO_BEFORE_ACTION; + +do_action: /* This label is used only to access EOF actions. */ + + switch ( yy_act ) + { /* beginning of action switch */ + case 0: /* must back up */ + /* undo the effects of YY_DO_BEFORE_ACTION */ + *yy_cp = (yy_hold_char); + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + goto yy_find_action; + +case 1: +/* rule 1 can match eol */ +YY_RULE_SETUP +#line 62 "lwin_wkt_lex.l" +{ + LWDEBUG(5,"DOUBLE"); + wkt_yylval.doublevalue = atof(wkt_yytext); + yyless(wkt_yyleng-1); + return DOUBLE_TOK; + } + YY_BREAK +case 2: +/* rule 2 can match eol */ +YY_RULE_SETUP +#line 69 "lwin_wkt_lex.l" +{ + LWDEBUG(5,"DOUBLE NAN"); + wkt_yylval.doublevalue = NAN; + yyless(wkt_yyleng-1); + return DOUBLE_TOK; +} + YY_BREAK +case 3: +YY_RULE_SETUP +#line 76 "lwin_wkt_lex.l" +{ + LWDEBUG(5,"SRID"); + wkt_yylval.integervalue = wkt_lexer_read_srid(wkt_yytext); + return SRID_TOK; + } + YY_BREAK +case 4: +YY_RULE_SETUP +#line 82 "lwin_wkt_lex.l" +{ return COLLECTION_TOK; } + YY_BREAK +case 5: +YY_RULE_SETUP +#line 83 "lwin_wkt_lex.l" +{ return MSURFACE_TOK; } + YY_BREAK +case 6: +YY_RULE_SETUP +#line 84 "lwin_wkt_lex.l" +{ return MPOLYGON_TOK; } + YY_BREAK +case 7: +YY_RULE_SETUP +#line 85 "lwin_wkt_lex.l" +{ return MCURVE_TOK; } + YY_BREAK +case 8: +YY_RULE_SETUP +#line 86 "lwin_wkt_lex.l" +{ return MLINESTRING_TOK; } + YY_BREAK +case 9: +YY_RULE_SETUP +#line 87 "lwin_wkt_lex.l" +{ return MPOINT_TOK; } + YY_BREAK +case 10: +YY_RULE_SETUP +#line 88 "lwin_wkt_lex.l" +{ return CURVEPOLYGON_TOK; } + YY_BREAK +case 11: +YY_RULE_SETUP +#line 89 "lwin_wkt_lex.l" +{ return POLYGON_TOK; } + YY_BREAK +case 12: +YY_RULE_SETUP +#line 90 "lwin_wkt_lex.l" +{ return COMPOUNDCURVE_TOK; } + YY_BREAK +case 13: +YY_RULE_SETUP +#line 91 "lwin_wkt_lex.l" +{ return CIRCULARSTRING_TOK; } + YY_BREAK +case 14: +YY_RULE_SETUP +#line 92 "lwin_wkt_lex.l" +{ return LINESTRING_TOK; } + YY_BREAK +case 15: +YY_RULE_SETUP +#line 93 "lwin_wkt_lex.l" +{ return POLYHEDRALSURFACE_TOK; } + YY_BREAK +case 16: +YY_RULE_SETUP +#line 94 "lwin_wkt_lex.l" +{ return TRIANGLE_TOK; } + YY_BREAK +case 17: +YY_RULE_SETUP +#line 95 "lwin_wkt_lex.l" +{ return TIN_TOK; } + YY_BREAK +case 18: +YY_RULE_SETUP +#line 96 "lwin_wkt_lex.l" +{ return POINT_TOK; } + YY_BREAK +case 19: +YY_RULE_SETUP +#line 97 "lwin_wkt_lex.l" +{ return EMPTY_TOK; } + YY_BREAK +case 20: +YY_RULE_SETUP +#line 99 "lwin_wkt_lex.l" +{ + LWDEBUG(5,"DIMENSIONALITY"); + wkt_yylval.stringvalue = wkt_yytext; + return DIMENSIONALITY_TOK; + } + YY_BREAK +case 21: +YY_RULE_SETUP +#line 105 "lwin_wkt_lex.l" +{ LWDEBUG(5,"LBRACKET"); return LBRACKET_TOK; } + YY_BREAK +case 22: +YY_RULE_SETUP +#line 106 "lwin_wkt_lex.l" +{ LWDEBUG(5,"RBRACKET"); return RBRACKET_TOK; } + YY_BREAK +case 23: +YY_RULE_SETUP +#line 107 "lwin_wkt_lex.l" +{ LWDEBUG(5,"COMMA"); return COMMA_TOK; } + YY_BREAK +case 24: +YY_RULE_SETUP +#line 108 "lwin_wkt_lex.l" +{ LWDEBUG(5,"SEMICOLON"); return SEMICOLON_TOK; } + YY_BREAK +case 25: +/* rule 25 can match eol */ +YY_RULE_SETUP +#line 110 "lwin_wkt_lex.l" +{ /* ignore whitespace */ LWDEBUG(5,"WHITESPACE"); } + YY_BREAK +case 26: +YY_RULE_SETUP +#line 112 "lwin_wkt_lex.l" +{ /* Error out and stop parsing on unknown/unexpected characters */ + LWDEBUG(5,"UNKNOWN"); + wkt_lexer_unknown(); + yyterminate(); + } + YY_BREAK +case 27: +YY_RULE_SETUP +#line 118 "lwin_wkt_lex.l" +ECHO; + YY_BREAK +#line 1340 "lwin_wkt_lex.c" +case YY_STATE_EOF(INITIAL): + yyterminate(); + + case YY_END_OF_BUFFER: + { + /* Amount of text matched not including the EOB char. */ + int yy_amount_of_matched_text = (int) (yy_cp - (yytext_ptr)) - 1; + + /* Undo the effects of YY_DO_BEFORE_ACTION. */ + *yy_cp = (yy_hold_char); + YY_RESTORE_YY_MORE_OFFSET + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_NEW ) + { + /* We're scanning a new file or input source. It's + * possible that this happened because the user + * just pointed yyin at a new source and called + * yylex(). If so, then we have to assure + * consistency between YY_CURRENT_BUFFER and our + * globals. Here is the right place to do so, because + * this is the first action (other than possibly a + * back-up) that will match for the new input source. + */ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + YY_CURRENT_BUFFER_LVALUE->yy_input_file = yyin; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = YY_BUFFER_NORMAL; + } + + /* Note that here we test for yy_c_buf_p "<=" to the position + * of the first EOB in the buffer, since yy_c_buf_p will + * already have been incremented past the NUL character + * (since all states make transitions on EOB to the + * end-of-buffer state). Contrast this with the test + * in input(). + */ + if ( (yy_c_buf_p) <= &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + { /* This was really a NUL. */ + yy_state_type yy_next_state; + + (yy_c_buf_p) = (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + /* Okay, we're now positioned to make the NUL + * transition. We couldn't have + * yy_get_previous_state() go ahead and do it + * for us because it doesn't know how to deal + * with the possibility of jamming (and we don't + * want to build jamming into it because then it + * will run more slowly). + */ + + yy_next_state = yy_try_NUL_trans( yy_current_state ); + + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + + if ( yy_next_state ) + { + /* Consume the NUL. */ + yy_cp = ++(yy_c_buf_p); + yy_current_state = yy_next_state; + goto yy_match; + } + + else + { + yy_cp = (yy_last_accepting_cpos); + yy_current_state = (yy_last_accepting_state); + goto yy_find_action; + } + } + + else switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_END_OF_FILE: + { + (yy_did_buffer_switch_on_eof) = 0; + + if ( yywrap( ) ) + { + /* Note: because we've taken care in + * yy_get_next_buffer() to have set up + * yytext, we can now set up + * yy_c_buf_p so that if some total + * hoser (like flex itself) wants to + * call the scanner after we return the + * YY_NULL, it'll still work - another + * YY_NULL will get returned. + */ + (yy_c_buf_p) = (yytext_ptr) + YY_MORE_ADJ; + + yy_act = YY_STATE_EOF(YY_START); + goto do_action; + } + + else + { + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; + } + break; + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = + (yytext_ptr) + yy_amount_of_matched_text; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_match; + + case EOB_ACT_LAST_MATCH: + (yy_c_buf_p) = + &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)]; + + yy_current_state = yy_get_previous_state( ); + + yy_cp = (yy_c_buf_p); + yy_bp = (yytext_ptr) + YY_MORE_ADJ; + goto yy_find_action; + } + break; + } + + default: + YY_FATAL_ERROR( + "fatal flex scanner internal error--no action found" ); + } /* end of action switch */ + } /* end of scanning one token */ + } /* end of user's declarations */ +} /* end of yylex */ + +/* yy_get_next_buffer - try to read in a new buffer + * + * Returns a code representing an action: + * EOB_ACT_LAST_MATCH - + * EOB_ACT_CONTINUE_SCAN - continue scanning from current position + * EOB_ACT_END_OF_FILE - end of file + */ +static int yy_get_next_buffer (void) +{ + char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf; + char *source = (yytext_ptr); + int number_to_move, i; + int ret_val; + + if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] ) + YY_FATAL_ERROR( + "fatal flex scanner internal error--end of buffer missed" ); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_fill_buffer == 0 ) + { /* Don't try to fill the buffer, so this is an EOF. */ + if ( (yy_c_buf_p) - (yytext_ptr) - YY_MORE_ADJ == 1 ) + { + /* We matched a single character, the EOB, so + * treat this as a final EOF. + */ + return EOB_ACT_END_OF_FILE; + } + + else + { + /* We matched some text prior to the EOB, first + * process it. + */ + return EOB_ACT_LAST_MATCH; + } + } + + /* Try to read more data. */ + + /* First move last chars to start of buffer. */ + number_to_move = (int) ((yy_c_buf_p) - (yytext_ptr) - 1); + + for ( i = 0; i < number_to_move; ++i ) + *(dest++) = *(source++); + + if ( YY_CURRENT_BUFFER_LVALUE->yy_buffer_status == YY_BUFFER_EOF_PENDING ) + /* don't do the read, it's not guaranteed to return an EOF, + * just force an EOF + */ + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars) = 0; + + else + { + int num_to_read = + YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; + + while ( num_to_read <= 0 ) + { /* Not enough room in the buffer - grow it. */ + + /* just a shorter name for the current buffer */ + YY_BUFFER_STATE b = YY_CURRENT_BUFFER_LVALUE; + + int yy_c_buf_p_offset = + (int) ((yy_c_buf_p) - b->yy_ch_buf); + + if ( b->yy_is_our_buffer ) + { + int new_size = b->yy_buf_size * 2; + + if ( new_size <= 0 ) + b->yy_buf_size += b->yy_buf_size / 8; + else + b->yy_buf_size *= 2; + + b->yy_ch_buf = (char *) + /* Include room in for 2 EOB chars. */ + yyrealloc( (void *) b->yy_ch_buf, + (yy_size_t) (b->yy_buf_size + 2) ); + } + else + /* Can't grow it, we don't own it. */ + b->yy_ch_buf = NULL; + + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( + "fatal error - scanner input buffer overflow" ); + + (yy_c_buf_p) = &b->yy_ch_buf[yy_c_buf_p_offset]; + + num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - + number_to_move - 1; + + } + + if ( num_to_read > YY_READ_BUF_SIZE ) + num_to_read = YY_READ_BUF_SIZE; + + /* Read in more data. */ + YY_INPUT( (&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move]), + (yy_n_chars), num_to_read ); + + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + if ( (yy_n_chars) == 0 ) + { + if ( number_to_move == YY_MORE_ADJ ) + { + ret_val = EOB_ACT_END_OF_FILE; + yyrestart( yyin ); + } + + else + { + ret_val = EOB_ACT_LAST_MATCH; + YY_CURRENT_BUFFER_LVALUE->yy_buffer_status = + YY_BUFFER_EOF_PENDING; + } + } + + else + ret_val = EOB_ACT_CONTINUE_SCAN; + + if (((yy_n_chars) + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { + /* Extend the array by 50%, plus the number we really need. */ + int new_size = (yy_n_chars) + number_to_move + ((yy_n_chars) >> 1); + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc( + (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size ); + if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_get_next_buffer()" ); + /* "- 2" to take care of EOB's */ + YY_CURRENT_BUFFER_LVALUE->yy_buf_size = (int) (new_size - 2); + } + + (yy_n_chars) += number_to_move; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] = YY_END_OF_BUFFER_CHAR; + YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] = YY_END_OF_BUFFER_CHAR; + + (yytext_ptr) = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[0]; + + return ret_val; +} + +/* yy_get_previous_state - get the state just before the EOB char was reached */ + + static yy_state_type yy_get_previous_state (void) +{ + yy_state_type yy_current_state; + char *yy_cp; + + yy_current_state = (yy_start); + + for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp ) + { + YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1); + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 177 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + } + + return yy_current_state; +} + +/* yy_try_NUL_trans - try to make a transition on the NUL character + * + * synopsis + * next_state = yy_try_NUL_trans( current_state ); + */ + static yy_state_type yy_try_NUL_trans (yy_state_type yy_current_state ) +{ + int yy_is_jam; + char *yy_cp = (yy_c_buf_p); + + YY_CHAR yy_c = 1; + if ( yy_accept[yy_current_state] ) + { + (yy_last_accepting_state) = yy_current_state; + (yy_last_accepting_cpos) = yy_cp; + } + while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state ) + { + yy_current_state = (int) yy_def[yy_current_state]; + if ( yy_current_state >= 177 ) + yy_c = yy_meta[yy_c]; + } + yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c]; + yy_is_jam = (yy_current_state == 176); + + return yy_is_jam ? 0 : yy_current_state; +} + +#ifndef YY_NO_UNPUT + +#endif + +#ifndef YY_NO_INPUT +#ifdef __cplusplus + static int yyinput (void) +#else + static int input (void) +#endif + +{ + int c; + + *(yy_c_buf_p) = (yy_hold_char); + + if ( *(yy_c_buf_p) == YY_END_OF_BUFFER_CHAR ) + { + /* yy_c_buf_p now points to the character we want to return. + * If this occurs *before* the EOB characters, then it's a + * valid NUL; if not, then we've hit the end of the buffer. + */ + if ( (yy_c_buf_p) < &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars)] ) + /* This was really a NUL. */ + *(yy_c_buf_p) = '\0'; + + else + { /* need more input */ + int offset = (int) ((yy_c_buf_p) - (yytext_ptr)); + ++(yy_c_buf_p); + + switch ( yy_get_next_buffer( ) ) + { + case EOB_ACT_LAST_MATCH: + /* This happens because yy_g_n_b() + * sees that we've accumulated a + * token and flags that we need to + * try matching the token before + * proceeding. But for input(), + * there's no matching to consider. + * So convert the EOB_ACT_LAST_MATCH + * to EOB_ACT_END_OF_FILE. + */ + + /* Reset buffer status. */ + yyrestart( yyin ); + + /*FALLTHROUGH*/ + + case EOB_ACT_END_OF_FILE: + { + if ( yywrap( ) ) + return 0; + + if ( ! (yy_did_buffer_switch_on_eof) ) + YY_NEW_FILE; +#ifdef __cplusplus + return yyinput(); +#else + return input(); +#endif + } + + case EOB_ACT_CONTINUE_SCAN: + (yy_c_buf_p) = (yytext_ptr) + offset; + break; + } + } + } + + c = *(unsigned char *) (yy_c_buf_p); /* cast for 8-bit char's */ + *(yy_c_buf_p) = '\0'; /* preserve yytext */ + (yy_hold_char) = *++(yy_c_buf_p); + + return c; +} +#endif /* ifndef YY_NO_INPUT */ + +/** Immediately switch to a different input stream. + * @param input_file A readable stream. + * + * @note This function does not reset the start condition to @c INITIAL . + */ + void yyrestart (FILE * input_file ) +{ + + if ( ! YY_CURRENT_BUFFER ){ + yyensure_buffer_stack (); + YY_CURRENT_BUFFER_LVALUE = + yy_create_buffer( yyin, YY_BUF_SIZE ); + } + + yy_init_buffer( YY_CURRENT_BUFFER, input_file ); + yy_load_buffer_state( ); +} + +/** Switch to a different input buffer. + * @param new_buffer The new input buffer. + * + */ + void yy_switch_to_buffer (YY_BUFFER_STATE new_buffer ) +{ + + /* TODO. We should be able to replace this entire function body + * with + * yypop_buffer_state(); + * yypush_buffer_state(new_buffer); + */ + yyensure_buffer_stack (); + if ( YY_CURRENT_BUFFER == new_buffer ) + return; + + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + YY_CURRENT_BUFFER_LVALUE = new_buffer; + yy_load_buffer_state( ); + + /* We don't actually know whether we did this switch during + * EOF (yywrap()) processing, but the only time this flag + * is looked at is after yywrap() is called, so it's safe + * to go ahead and always set it. + */ + (yy_did_buffer_switch_on_eof) = 1; +} + +static void yy_load_buffer_state (void) +{ + (yy_n_chars) = YY_CURRENT_BUFFER_LVALUE->yy_n_chars; + (yytext_ptr) = (yy_c_buf_p) = YY_CURRENT_BUFFER_LVALUE->yy_buf_pos; + yyin = YY_CURRENT_BUFFER_LVALUE->yy_input_file; + (yy_hold_char) = *(yy_c_buf_p); +} + +/** Allocate and initialize an input buffer state. + * @param file A readable stream. + * @param size The character buffer size in bytes. When in doubt, use @c YY_BUF_SIZE. + * + * @return the allocated buffer state. + */ + YY_BUFFER_STATE yy_create_buffer (FILE * file, int size ) +{ + YY_BUFFER_STATE b; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_buf_size = size; + + /* yy_ch_buf has to be 2 characters longer than the size given because + * we need to put in 2 end-of-buffer characters. + */ + b->yy_ch_buf = (char *) yyalloc( (yy_size_t) (b->yy_buf_size + 2) ); + if ( ! b->yy_ch_buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_create_buffer()" ); + + b->yy_is_our_buffer = 1; + + yy_init_buffer( b, file ); + + return b; +} + +/** Destroy the buffer. + * @param b a buffer created with yy_create_buffer() + * + */ + void yy_delete_buffer (YY_BUFFER_STATE b ) +{ + + if ( ! b ) + return; + + if ( b == YY_CURRENT_BUFFER ) /* Not sure if we should pop here. */ + YY_CURRENT_BUFFER_LVALUE = (YY_BUFFER_STATE) 0; + + if ( b->yy_is_our_buffer ) + yyfree( (void *) b->yy_ch_buf ); + + yyfree( (void *) b ); +} + +/* Initializes or reinitializes a buffer. + * This function is sometimes called more than once on the same buffer, + * such as during a yyrestart() or at EOF. + */ + static void yy_init_buffer (YY_BUFFER_STATE b, FILE * file ) + +{ + int oerrno = errno; + + yy_flush_buffer( b ); + + b->yy_input_file = file; + b->yy_fill_buffer = 1; + + /* If b is the current buffer, then yy_init_buffer was _probably_ + * called from yyrestart() or through yy_get_next_buffer. + * In that case, we don't want to reset the lineno or column. + */ + if (b != YY_CURRENT_BUFFER){ + b->yy_bs_lineno = 1; + b->yy_bs_column = 0; + } + + b->yy_is_interactive = 0; + + errno = oerrno; +} + +/** Discard all buffered characters. On the next scan, YY_INPUT will be called. + * @param b the buffer state to be flushed, usually @c YY_CURRENT_BUFFER. + * + */ + void yy_flush_buffer (YY_BUFFER_STATE b ) +{ + if ( ! b ) + return; + + b->yy_n_chars = 0; + + /* We always need two end-of-buffer characters. The first causes + * a transition to the end-of-buffer state. The second causes + * a jam in that state. + */ + b->yy_ch_buf[0] = YY_END_OF_BUFFER_CHAR; + b->yy_ch_buf[1] = YY_END_OF_BUFFER_CHAR; + + b->yy_buf_pos = &b->yy_ch_buf[0]; + + b->yy_at_bol = 1; + b->yy_buffer_status = YY_BUFFER_NEW; + + if ( b == YY_CURRENT_BUFFER ) + yy_load_buffer_state( ); +} + +/** Pushes the new state onto the stack. The new state becomes + * the current state. This function will allocate the stack + * if necessary. + * @param new_buffer The new state. + * + */ +void yypush_buffer_state (YY_BUFFER_STATE new_buffer ) +{ + if (new_buffer == NULL) + return; + + yyensure_buffer_stack(); + + /* This block is copied from yy_switch_to_buffer. */ + if ( YY_CURRENT_BUFFER ) + { + /* Flush out information for old buffer. */ + *(yy_c_buf_p) = (yy_hold_char); + YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = (yy_c_buf_p); + YY_CURRENT_BUFFER_LVALUE->yy_n_chars = (yy_n_chars); + } + + /* Only push if top exists. Otherwise, replace top. */ + if (YY_CURRENT_BUFFER) + (yy_buffer_stack_top)++; + YY_CURRENT_BUFFER_LVALUE = new_buffer; + + /* copied from yy_switch_to_buffer. */ + yy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; +} + +/** Removes and deletes the top of the stack, if present. + * The next element becomes the new top. + * + */ +void yypop_buffer_state (void) +{ + if (!YY_CURRENT_BUFFER) + return; + + yy_delete_buffer(YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + if ((yy_buffer_stack_top) > 0) + --(yy_buffer_stack_top); + + if (YY_CURRENT_BUFFER) { + yy_load_buffer_state( ); + (yy_did_buffer_switch_on_eof) = 1; + } +} + +/* Allocates the stack if it does not exist. + * Guarantees space for at least one push. + */ +static void yyensure_buffer_stack (void) +{ + yy_size_t num_to_alloc; + + if (!(yy_buffer_stack)) { + + /* First allocation is just for 2 elements, since we don't know if this + * scanner will even need a stack. We use 2 instead of 1 to avoid an + * immediate realloc on the next call. + */ + num_to_alloc = 1; /* After all that talk, this was set to 1 anyways... */ + (yy_buffer_stack) = (struct yy_buffer_state**)yyalloc + (num_to_alloc * sizeof(struct yy_buffer_state*) + ); + if ( ! (yy_buffer_stack) ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + memset((yy_buffer_stack), 0, num_to_alloc * sizeof(struct yy_buffer_state*)); + + (yy_buffer_stack_max) = num_to_alloc; + (yy_buffer_stack_top) = 0; + return; + } + + if ((yy_buffer_stack_top) >= ((yy_buffer_stack_max)) - 1){ + + /* Increase the buffer to prepare for a possible push. */ + yy_size_t grow_size = 8 /* arbitrary grow size */; + + num_to_alloc = (yy_buffer_stack_max) + grow_size; + (yy_buffer_stack) = (struct yy_buffer_state**)yyrealloc + ((yy_buffer_stack), + num_to_alloc * sizeof(struct yy_buffer_state*) + ); + if ( ! (yy_buffer_stack) ) + YY_FATAL_ERROR( "out of dynamic memory in yyensure_buffer_stack()" ); + + /* zero only the new slots.*/ + memset((yy_buffer_stack) + (yy_buffer_stack_max), 0, grow_size * sizeof(struct yy_buffer_state*)); + (yy_buffer_stack_max) = num_to_alloc; + } +} + +/** Setup the input buffer state to scan directly from a user-specified character buffer. + * @param base the character buffer + * @param size the size in bytes of the character buffer + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_buffer (char * base, yy_size_t size ) +{ + YY_BUFFER_STATE b; + + if ( size < 2 || + base[size-2] != YY_END_OF_BUFFER_CHAR || + base[size-1] != YY_END_OF_BUFFER_CHAR ) + /* They forgot to leave room for the EOB's. */ + return NULL; + + b = (YY_BUFFER_STATE) yyalloc( sizeof( struct yy_buffer_state ) ); + if ( ! b ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_buffer()" ); + + b->yy_buf_size = (int) (size - 2); /* "- 2" to take care of EOB's */ + b->yy_buf_pos = b->yy_ch_buf = base; + b->yy_is_our_buffer = 0; + b->yy_input_file = NULL; + b->yy_n_chars = b->yy_buf_size; + b->yy_is_interactive = 0; + b->yy_at_bol = 1; + b->yy_fill_buffer = 0; + b->yy_buffer_status = YY_BUFFER_NEW; + + yy_switch_to_buffer( b ); + + return b; +} + +/** Setup the input buffer state to scan a string. The next call to yylex() will + * scan from a @e copy of @a str. + * @param yystr a NUL-terminated string to scan + * + * @return the newly allocated buffer state object. + * @note If you want to scan bytes that may contain NUL values, then use + * yy_scan_bytes() instead. + */ +YY_BUFFER_STATE yy_scan_string (const char * yystr ) +{ + + return yy_scan_bytes( yystr, (int) strlen(yystr) ); +} + +/** Setup the input buffer state to scan the given bytes. The next call to yylex() will + * scan from a @e copy of @a bytes. + * @param yybytes the byte buffer to scan + * @param _yybytes_len the number of bytes in the buffer pointed to by @a bytes. + * + * @return the newly allocated buffer state object. + */ +YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len ) +{ + YY_BUFFER_STATE b; + char *buf; + yy_size_t n; + int i; + + /* Get memory for full buffer, including space for trailing EOB's. */ + n = (yy_size_t) (_yybytes_len + 2); + buf = (char *) yyalloc( n ); + if ( ! buf ) + YY_FATAL_ERROR( "out of dynamic memory in yy_scan_bytes()" ); + + for ( i = 0; i < _yybytes_len; ++i ) + buf[i] = yybytes[i]; + + buf[_yybytes_len] = buf[_yybytes_len+1] = YY_END_OF_BUFFER_CHAR; + + b = yy_scan_buffer( buf, n ); + if ( ! b ) + YY_FATAL_ERROR( "bad buffer in yy_scan_bytes()" ); + + /* It's okay to grow etc. this buffer, and we should throw it + * away when we're done. + */ + b->yy_is_our_buffer = 1; + + return b; +} + +#ifndef YY_EXIT_FAILURE +#define YY_EXIT_FAILURE 2 +#endif + +static void yynoreturn yy_fatal_error (const char* msg ) +{ + fprintf( stderr, "%s\n", msg ); + exit( YY_EXIT_FAILURE ); +} + +/* Redefine yyless() so it works in section 3 code. */ + +#undef yyless +#define yyless(n) \ + do \ + { \ + /* Undo effects of setting up yytext. */ \ + int yyless_macro_arg = (n); \ + YY_LESS_LINENO(yyless_macro_arg);\ + yytext[yyleng] = (yy_hold_char); \ + (yy_c_buf_p) = yytext + yyless_macro_arg; \ + (yy_hold_char) = *(yy_c_buf_p); \ + *(yy_c_buf_p) = '\0'; \ + yyleng = yyless_macro_arg; \ + } \ + while ( 0 ) + +/* Accessor methods (get/set functions) to struct members. */ + +/** Get the current line number. + * + */ +int yyget_lineno (void) +{ + + return yylineno; +} + +/** Get the input stream. + * + */ +FILE *yyget_in (void) +{ + return yyin; +} + +/** Get the output stream. + * + */ +FILE *yyget_out (void) +{ + return yyout; +} + +/** Get the length of the current token. + * + */ +int yyget_leng (void) +{ + return yyleng; +} + +/** Get the current token. + * + */ + +char *yyget_text (void) +{ + return yytext; +} + +/** Set the current line number. + * @param _line_number line number + * + */ +void yyset_lineno (int _line_number ) +{ + + yylineno = _line_number; +} + +/** Set the input stream. This does not discard the current + * input buffer. + * @param _in_str A readable stream. + * + * @see yy_switch_to_buffer + */ +void yyset_in (FILE * _in_str ) +{ + yyin = _in_str ; +} + +void yyset_out (FILE * _out_str ) +{ + yyout = _out_str ; +} + +int yyget_debug (void) +{ + return yy_flex_debug; +} + +void yyset_debug (int _bdebug ) +{ + yy_flex_debug = _bdebug ; +} + +static int yy_init_globals (void) +{ + /* Initialization is the same as for the non-reentrant scanner. + * This function is called from yylex_destroy(), so don't allocate here. + */ + + (yy_buffer_stack) = NULL; + (yy_buffer_stack_top) = 0; + (yy_buffer_stack_max) = 0; + (yy_c_buf_p) = NULL; + (yy_init) = 0; + (yy_start) = 0; + +/* Defined in main.c */ +#ifdef YY_STDINIT + yyin = stdin; + yyout = stdout; +#else + yyin = NULL; + yyout = NULL; +#endif + + /* For future reference: Set errno on error, since we are called by + * yylex_init() + */ + return 0; +} + +/* yylex_destroy is for both reentrant and non-reentrant scanners. */ +int yylex_destroy (void) +{ + + /* Pop the buffer stack, destroying each element. */ + while(YY_CURRENT_BUFFER){ + yy_delete_buffer( YY_CURRENT_BUFFER ); + YY_CURRENT_BUFFER_LVALUE = NULL; + yypop_buffer_state(); + } + + /* Destroy the stack itself. */ + yyfree((yy_buffer_stack) ); + (yy_buffer_stack) = NULL; + + /* Reset the globals. This is important in a non-reentrant scanner so the next time + * yylex() is called, initialization will occur. */ + yy_init_globals( ); + + return 0; +} + +/* + * Internal utility routines. + */ + +#ifndef yytext_ptr +static void yy_flex_strncpy (char* s1, const char * s2, int n ) +{ + + int i; + for ( i = 0; i < n; ++i ) + s1[i] = s2[i]; +} +#endif + +#ifdef YY_NEED_STRLEN +static int yy_flex_strlen (const char * s ) +{ + int n; + for ( n = 0; s[n]; ++n ) + ; + + return n; +} +#endif + +#define YYTABLES_NAME "yytables" + +#line 118 "lwin_wkt_lex.l" + + +void *wkt_yyalloc (yy_size_t size ) +{ + return (void *) lwalloc( size ); +} + +void *wkt_yyrealloc (void * ptr, yy_size_t size ) +{ + return (void *) lwrealloc( (char *) ptr, size ); +} + +void wkt_yyfree (void * ptr ) +{ + lwfree( (char *) ptr ); /* see wkt_yyrealloc() for (char *) cast */ +} + +/* +* Set up the lexer! +*/ +void wkt_lexer_init(char *src) +{ + yy_init_globals(); + wkt_yy_buf_state = wkt_yy_scan_string(src); +} + +/* +* Clean up the lexer! +*/ +void wkt_lexer_close() +{ + wkt_yy_delete_buffer(wkt_yy_buf_state); +} + + diff --git a/mgist-postgis/liblwgeom/lwin_wkt_lex.l b/mgist-postgis/liblwgeom/lwin_wkt_lex.l new file mode 100644 index 0000000..d301a22 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwin_wkt_lex.l @@ -0,0 +1,151 @@ +%{ + +/* The lexer */ + +#include +#include +#include "lwin_wkt.h" +#include "lwin_wkt_parse.h" +#include "lwgeom_log.h" + +static YY_BUFFER_STATE wkt_yy_buf_state; + +/* +* Handle errors due to unexpected junk in WKT strings. +*/ +static void wkt_lexer_unknown() +{ + /* Set the global error state */ + global_parser_result.errcode = PARSER_ERROR_OTHER; + global_parser_result.message = parser_error_messages[PARSER_ERROR_OTHER]; + global_parser_result.errlocation = wkt_yylloc.last_column; +} + +/* +* This macro is magically run after a rule is found but before the main +* action is run. We use it to update the parse location information +* so we can report on where things fail. Also optionally to dump +* debugging info. +*/ +#define YY_USER_ACTION do { \ + wkt_yylloc.first_line = wkt_yylloc.last_line = yylineno; \ + wkt_yylloc.first_column = wkt_yylloc.last_column; \ + wkt_yylloc.last_column += yyleng; \ + LWDEBUGF(5,"lex: %s", wkt_yytext); \ + } while (0); + +/* +* Ensure we have a definition of NAN to use when encountering +* NAN tokens. +*/ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#ifndef NAN +#define NAN 0.0/0.0 +#endif + +%} + +%option prefix="wkt_yy" +%option nounput +%option noinput +%option never-interactive +%option outfile="lwin_wkt_lex.c" +%option noyywrap +/* Suppress the default implementations. */ +%option noyyalloc noyyrealloc noyyfree + +%% + +-?(([0-9]+\.?)|([0-9]*\.?[0-9]+)([eE][-+]?[0-9]+)?)[ \,\)\t\n\r] { + LWDEBUG(5,"DOUBLE"); + wkt_yylval.doublevalue = atof(wkt_yytext); + yyless(wkt_yyleng-1); + return DOUBLE_TOK; + } + +([Nn][Aa][Nn])[ \,\)\t\n\r] { + LWDEBUG(5,"DOUBLE NAN"); + wkt_yylval.doublevalue = NAN; + yyless(wkt_yyleng-1); + return DOUBLE_TOK; +} + +SRID=-?[0-9]+ { + LWDEBUG(5,"SRID"); + wkt_yylval.integervalue = wkt_lexer_read_srid(wkt_yytext); + return SRID_TOK; + } + +GEOMETRYCOLLECTION { return COLLECTION_TOK; } +MULTISURFACE { return MSURFACE_TOK; } +MULTIPOLYGON { return MPOLYGON_TOK; } +MULTICURVE { return MCURVE_TOK; } +MULTILINESTRING { return MLINESTRING_TOK; } +MULTIPOINT { return MPOINT_TOK; } +CURVEPOLYGON { return CURVEPOLYGON_TOK; } +POLYGON { return POLYGON_TOK; } +COMPOUNDCURVE { return COMPOUNDCURVE_TOK; } +CIRCULARSTRING { return CIRCULARSTRING_TOK; } +LINESTRING { return LINESTRING_TOK; } +POLYHEDRALSURFACE { return POLYHEDRALSURFACE_TOK; } +TRIANGLE { return TRIANGLE_TOK; } +TIN { return TIN_TOK; } +POINT { return POINT_TOK; } +EMPTY { return EMPTY_TOK; } + +Z|M|ZM { + LWDEBUG(5,"DIMENSIONALITY"); + wkt_yylval.stringvalue = wkt_yytext; + return DIMENSIONALITY_TOK; + } + +\( { LWDEBUG(5,"LBRACKET"); return LBRACKET_TOK; } +\) { LWDEBUG(5,"RBRACKET"); return RBRACKET_TOK; } +, { LWDEBUG(5,"COMMA"); return COMMA_TOK; } +\; { LWDEBUG(5,"SEMICOLON"); return SEMICOLON_TOK; } + +[ \t\n\r]+ { /* ignore whitespace */ LWDEBUG(5,"WHITESPACE"); } + +. { /* Error out and stop parsing on unknown/unexpected characters */ + LWDEBUG(5,"UNKNOWN"); + wkt_lexer_unknown(); + yyterminate(); + } + +%% + +void *wkt_yyalloc (yy_size_t size ) +{ + return (void *) lwalloc( size ); +} + +void *wkt_yyrealloc (void * ptr, yy_size_t size ) +{ + return (void *) lwrealloc( (char *) ptr, size ); +} + +void wkt_yyfree (void * ptr ) +{ + lwfree( (char *) ptr ); /* see wkt_yyrealloc() for (char *) cast */ +} + +/* +* Set up the lexer! +*/ +void wkt_lexer_init(char *src) +{ + yy_init_globals(); + wkt_yy_buf_state = wkt_yy_scan_string(src); +} + +/* +* Clean up the lexer! +*/ +void wkt_lexer_close() +{ + wkt_yy_delete_buffer(wkt_yy_buf_state); +} + diff --git a/mgist-postgis/liblwgeom/lwin_wkt_parse.c b/mgist-postgis/liblwgeom/lwin_wkt_parse.c new file mode 100644 index 0000000..122d4ce --- /dev/null +++ b/mgist-postgis/liblwgeom/lwin_wkt_parse.c @@ -0,0 +1,2920 @@ +/* A Bison parser, made by GNU Bison 3.0.4. */ + +/* Bison implementation for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +/* C LALR(1) parser skeleton written by Richard Stallman, by + simplifying the original so-called "semantic" parser. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +/* Identify Bison output. */ +#define YYBISON 1 + +/* Bison version. */ +#define YYBISON_VERSION "3.0.4" + +/* Skeleton name. */ +#define YYSKELETON_NAME "yacc.c" + +/* Pure parsers. */ +#define YYPURE 0 + +/* Push parsers. */ +#define YYPUSH 0 + +/* Pull parsers. */ +#define YYPULL 1 + + +/* Substitute the variable and function names. */ +#define yyparse wkt_yyparse +#define yylex wkt_yylex +#define yyerror wkt_yyerror +#define yydebug wkt_yydebug +#define yynerrs wkt_yynerrs + +#define yylval wkt_yylval +#define yychar wkt_yychar +#define yylloc wkt_yylloc + +/* Copy the first part of user declarations. */ +#line 1 "lwin_wkt_parse.y" /* yacc.c:339 */ + + +/* WKT Parser */ +#include +#include +#include +#include "lwin_wkt.h" +#include "lwin_wkt_parse.h" +#include "lwgeom_log.h" + + +/* Prototypes to quiet the compiler */ +int wkt_yyparse(void); +void wkt_yyerror(const char *str); +int wkt_yylex(void); + + +/* Declare the global parser variable */ +LWGEOM_PARSER_RESULT global_parser_result; + +/* Turn on/off verbose parsing (turn off for production) */ +int wkt_yydebug = 0; + +/* +* Error handler called by the bison parser. Mostly we will be +* catching our own errors and filling out the message and errlocation +* from WKT_ERROR in the grammar, but we keep this one +* around just in case. +*/ +void wkt_yyerror(__attribute__((__unused__)) const char *str) +{ + /* If we haven't already set a message and location, let's set one now. */ + if ( ! global_parser_result.message ) + { + global_parser_result.message = parser_error_messages[PARSER_ERROR_OTHER]; + global_parser_result.errcode = PARSER_ERROR_OTHER; + global_parser_result.errlocation = wkt_yylloc.last_column; + } + LWDEBUGF(4,"%s", str); +} + +/** +* Parse a WKT geometry string into an LWGEOM structure. Note that this +* process uses globals and is not re-entrant, so don't call it within itself +* (eg, from within other functions in lwin_wkt.c) or from a threaded program. +* Note that parser_result.wkinput picks up a reference to wktstr. +*/ +int lwgeom_parse_wkt(LWGEOM_PARSER_RESULT *parser_result, char *wktstr, int parser_check_flags) +{ + int parse_rv = 0; + + /* Clean up our global parser result. */ + lwgeom_parser_result_init(&global_parser_result); + /* Work-around possible bug in GNU Bison 3.0.2 resulting in wkt_yylloc + * members not being initialized on yyparse() as documented here: + * https://www.gnu.org/software/bison/manual/html_node/Location-Type.html + * See discussion here: + * http://lists.osgeo.org/pipermail/postgis-devel/2014-September/024506.html + */ + wkt_yylloc.last_column = wkt_yylloc.last_line = \ + wkt_yylloc.first_column = wkt_yylloc.first_line = 1; + + /* Set the input text string, and parse checks. */ + global_parser_result.wkinput = wktstr; + global_parser_result.parser_check_flags = parser_check_flags; + + wkt_lexer_init(wktstr); /* Lexer ready */ + parse_rv = wkt_yyparse(); /* Run the parse */ + LWDEBUGF(4,"wkt_yyparse returned %d", parse_rv); + wkt_lexer_close(); /* Clean up lexer */ + + /* A non-zero parser return is an error. */ + if ( parse_rv || global_parser_result.errcode ) + { + if( ! global_parser_result.errcode ) + { + global_parser_result.errcode = PARSER_ERROR_OTHER; + global_parser_result.message = parser_error_messages[PARSER_ERROR_OTHER]; + global_parser_result.errlocation = wkt_yylloc.last_column; + } + else if (global_parser_result.geom) + { + lwgeom_free(global_parser_result.geom); + global_parser_result.geom = NULL; + } + + LWDEBUGF(5, "error returned by wkt_yyparse() @ %d: [%d] '%s'", + global_parser_result.errlocation, + global_parser_result.errcode, + global_parser_result.message); + + /* Copy the global values into the return pointer */ + *parser_result = global_parser_result; + wkt_yylex_destroy(); + return LW_FAILURE; + } + + /* Copy the global value into the return pointer */ + *parser_result = global_parser_result; + wkt_yylex_destroy(); + return LW_SUCCESS; +} + +#define WKT_ERROR() { if ( global_parser_result.errcode != 0 ) { YYERROR; } } + + + +#line 183 "lwin_wkt_parse.c" /* yacc.c:339 */ + +# ifndef YY_NULLPTR +# if defined __cplusplus && 201103L <= __cplusplus +# define YY_NULLPTR nullptr +# else +# define YY_NULLPTR 0 +# endif +# endif + +/* Enabling verbose error messages. */ +#ifdef YYERROR_VERBOSE +# undef YYERROR_VERBOSE +# define YYERROR_VERBOSE 1 +#else +# define YYERROR_VERBOSE 1 +#endif + +/* In a future release of Bison, this section will be replaced + by #include "lwin_wkt_parse.h". */ +#ifndef YY_WKT_YY_LWIN_WKT_PARSE_H_INCLUDED +# define YY_WKT_YY_LWIN_WKT_PARSE_H_INCLUDED +/* Debug traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif +#if YYDEBUG +extern int wkt_yydebug; +#endif + +/* Token type. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + enum yytokentype + { + POINT_TOK = 258, + LINESTRING_TOK = 259, + POLYGON_TOK = 260, + MPOINT_TOK = 261, + MLINESTRING_TOK = 262, + MPOLYGON_TOK = 263, + MSURFACE_TOK = 264, + MCURVE_TOK = 265, + CURVEPOLYGON_TOK = 266, + COMPOUNDCURVE_TOK = 267, + CIRCULARSTRING_TOK = 268, + COLLECTION_TOK = 269, + RBRACKET_TOK = 270, + LBRACKET_TOK = 271, + COMMA_TOK = 272, + EMPTY_TOK = 273, + SEMICOLON_TOK = 274, + TRIANGLE_TOK = 275, + TIN_TOK = 276, + POLYHEDRALSURFACE_TOK = 277, + DOUBLE_TOK = 278, + DIMENSIONALITY_TOK = 279, + SRID_TOK = 280 + }; +#endif + +/* Value type. */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED + +union YYSTYPE +{ +#line 112 "lwin_wkt_parse.y" /* yacc.c:355 */ + + int integervalue; + double doublevalue; + char *stringvalue; + LWGEOM *geometryvalue; + POINT coordinatevalue; + POINTARRAY *ptarrayvalue; + +#line 258 "lwin_wkt_parse.c" /* yacc.c:355 */ +}; + +typedef union YYSTYPE YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define YYSTYPE_IS_DECLARED 1 +#endif + +/* Location type. */ +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE YYLTYPE; +struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +}; +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + +extern YYSTYPE wkt_yylval; +extern YYLTYPE wkt_yylloc; +int wkt_yyparse (void); + +#endif /* !YY_WKT_YY_LWIN_WKT_PARSE_H_INCLUDED */ + +/* Copy the second part of user declarations. */ + +#line 289 "lwin_wkt_parse.c" /* yacc.c:358 */ + +#ifdef short +# undef short +#endif + +#ifdef YYTYPE_UINT8 +typedef YYTYPE_UINT8 yytype_uint8; +#else +typedef unsigned char yytype_uint8; +#endif + +#ifdef YYTYPE_INT8 +typedef YYTYPE_INT8 yytype_int8; +#else +typedef signed char yytype_int8; +#endif + +#ifdef YYTYPE_UINT16 +typedef YYTYPE_UINT16 yytype_uint16; +#else +typedef unsigned short int yytype_uint16; +#endif + +#ifdef YYTYPE_INT16 +typedef YYTYPE_INT16 yytype_int16; +#else +typedef short int yytype_int16; +#endif + +#ifndef YYSIZE_T +# ifdef __SIZE_TYPE__ +# define YYSIZE_T __SIZE_TYPE__ +# elif defined size_t +# define YYSIZE_T size_t +# elif ! defined YYSIZE_T +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# else +# define YYSIZE_T unsigned int +# endif +#endif + +#define YYSIZE_MAXIMUM ((YYSIZE_T) -1) + +#ifndef YY_ +# if defined YYENABLE_NLS && YYENABLE_NLS +# if ENABLE_NLS +# include /* INFRINGES ON USER NAME SPACE */ +# define YY_(Msgid) dgettext ("bison-runtime", Msgid) +# endif +# endif +# ifndef YY_ +# define YY_(Msgid) Msgid +# endif +#endif + +#ifndef YY_ATTRIBUTE +# if (defined __GNUC__ \ + && (2 < __GNUC__ || (__GNUC__ == 2 && 96 <= __GNUC_MINOR__))) \ + || defined __SUNPRO_C && 0x5110 <= __SUNPRO_C +# define YY_ATTRIBUTE(Spec) __attribute__(Spec) +# else +# define YY_ATTRIBUTE(Spec) /* empty */ +# endif +#endif + +#ifndef YY_ATTRIBUTE_PURE +# define YY_ATTRIBUTE_PURE YY_ATTRIBUTE ((__pure__)) +#endif + +#ifndef YY_ATTRIBUTE_UNUSED +# define YY_ATTRIBUTE_UNUSED YY_ATTRIBUTE ((__unused__)) +#endif + +#if !defined _Noreturn \ + && (!defined __STDC_VERSION__ || __STDC_VERSION__ < 201112) +# if defined _MSC_VER && 1200 <= _MSC_VER +# define _Noreturn __declspec (noreturn) +# else +# define _Noreturn YY_ATTRIBUTE ((__noreturn__)) +# endif +#endif + +/* Suppress unused-variable warnings by "using" E. */ +#if ! defined lint || defined __GNUC__ +# define YYUSE(E) ((void) (E)) +#else +# define YYUSE(E) /* empty */ +#endif + +#if defined __GNUC__ && 407 <= __GNUC__ * 100 + __GNUC_MINOR__ +/* Suppress an incorrect diagnostic about yylval being uninitialized. */ +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \ + _Pragma ("GCC diagnostic push") \ + _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"")\ + _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") +# define YY_IGNORE_MAYBE_UNINITIALIZED_END \ + _Pragma ("GCC diagnostic pop") +#else +# define YY_INITIAL_VALUE(Value) Value +#endif +#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN +# define YY_IGNORE_MAYBE_UNINITIALIZED_END +#endif +#ifndef YY_INITIAL_VALUE +# define YY_INITIAL_VALUE(Value) /* Nothing. */ +#endif + + +#if ! defined yyoverflow || YYERROR_VERBOSE + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# ifdef YYSTACK_USE_ALLOCA +# if YYSTACK_USE_ALLOCA +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# elif defined __BUILTIN_VA_ARG_INCR +# include /* INFRINGES ON USER NAME SPACE */ +# elif defined _AIX +# define YYSTACK_ALLOC __alloca +# elif defined _MSC_VER +# include /* INFRINGES ON USER NAME SPACE */ +# define alloca _alloca +# else +# define YYSTACK_ALLOC alloca +# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS +# include /* INFRINGES ON USER NAME SPACE */ + /* Use EXIT_SUCCESS as a witness for stdlib.h. */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's 'empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) +# ifndef YYSTACK_ALLOC_MAXIMUM + /* The OS might guarantee only one guard page at the bottom of the stack, + and a page size can be as small as 4096 bytes. So we cannot safely + invoke alloca (N) if N exceeds 4096. Use a slightly smaller number + to allow for a few compiler-allocated temporary stack slots. */ +# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */ +# endif +# else +# define YYSTACK_ALLOC YYMALLOC +# define YYSTACK_FREE YYFREE +# ifndef YYSTACK_ALLOC_MAXIMUM +# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM +# endif +# if (defined __cplusplus && ! defined EXIT_SUCCESS \ + && ! ((defined YYMALLOC || defined malloc) \ + && (defined YYFREE || defined free))) +# include /* INFRINGES ON USER NAME SPACE */ +# ifndef EXIT_SUCCESS +# define EXIT_SUCCESS 0 +# endif +# endif +# ifndef YYMALLOC +# define YYMALLOC malloc +# if ! defined malloc && ! defined EXIT_SUCCESS +void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# ifndef YYFREE +# define YYFREE free +# if ! defined free && ! defined EXIT_SUCCESS +void free (void *); /* INFRINGES ON USER NAME SPACE */ +# endif +# endif +# endif +#endif /* ! defined yyoverflow || YYERROR_VERBOSE */ + + +#if (! defined yyoverflow \ + && (! defined __cplusplus \ + || (defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL \ + && defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + yytype_int16 yyss_alloc; + YYSTYPE yyvs_alloc; + YYLTYPE yyls_alloc; +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAXIMUM (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE) + sizeof (YYLTYPE)) \ + + 2 * YYSTACK_GAP_MAXIMUM) + +# define YYCOPY_NEEDED 1 + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack_alloc, Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \ + Stack = &yyptr->Stack_alloc; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (0) + +#endif + +#if defined YYCOPY_NEEDED && YYCOPY_NEEDED +/* Copy COUNT objects from SRC to DST. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if defined __GNUC__ && 1 < __GNUC__ +# define YYCOPY(Dst, Src, Count) \ + __builtin_memcpy (Dst, Src, (Count) * sizeof (*(Src))) +# else +# define YYCOPY(Dst, Src, Count) \ + do \ + { \ + YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (Dst)[yyi] = (Src)[yyi]; \ + } \ + while (0) +# endif +# endif +#endif /* !YYCOPY_NEEDED */ + +/* YYFINAL -- State number of the termination state. */ +#define YYFINAL 80 +/* YYLAST -- Last index in YYTABLE. */ +#define YYLAST 294 + +/* YYNTOKENS -- Number of terminals. */ +#define YYNTOKENS 26 +/* YYNNTS -- Number of nonterminals. */ +#define YYNNTS 40 +/* YYNRULES -- Number of rules. */ +#define YYNRULES 136 +/* YYNSTATES -- Number of states. */ +#define YYNSTATES 264 + +/* YYTRANSLATE[YYX] -- Symbol number corresponding to YYX as returned + by yylex, with out-of-bounds checking. */ +#define YYUNDEFTOK 2 +#define YYMAXUTOK 280 + +#define YYTRANSLATE(YYX) \ + ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK) + +/* YYTRANSLATE[TOKEN-NUM] -- Symbol number corresponding to TOKEN-NUM + as returned by yylex, without out-of-bounds checking. */ +static const yytype_uint8 yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25 +}; + +#if YYDEBUG + /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */ +static const yytype_uint16 yyrline[] = +{ + 0, 217, 217, 219, 223, 224, 225, 226, 227, 228, + 229, 230, 231, 232, 233, 234, 235, 236, 237, 240, + 242, 244, 246, 250, 252, 256, 258, 260, 262, 266, + 268, 270, 272, 274, 276, 280, 282, 284, 286, 290, + 292, 294, 296, 300, 302, 304, 306, 310, 312, 316, + 318, 322, 324, 326, 328, 332, 334, 338, 341, 343, + 345, 347, 351, 353, 357, 358, 359, 360, 363, 365, + 369, 371, 375, 378, 381, 383, 385, 387, 391, 393, + 395, 397, 399, 401, 405, 407, 409, 411, 415, 417, + 419, 421, 423, 425, 427, 429, 433, 435, 437, 439, + 443, 445, 449, 451, 453, 455, 459, 461, 463, 465, + 469, 471, 475, 477, 481, 483, 485, 487, 491, 495, + 497, 499, 501, 505, 507, 511, 513, 515, 519, 521, + 523, 525, 529, 531, 535, 537, 539 +}; +#endif + +#if YYDEBUG || YYERROR_VERBOSE || 1 +/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM. + First, the terminals, then, starting at YYNTOKENS, nonterminals. */ +static const char *const yytname[] = +{ + "$end", "error", "$undefined", "POINT_TOK", "LINESTRING_TOK", + "POLYGON_TOK", "MPOINT_TOK", "MLINESTRING_TOK", "MPOLYGON_TOK", + "MSURFACE_TOK", "MCURVE_TOK", "CURVEPOLYGON_TOK", "COMPOUNDCURVE_TOK", + "CIRCULARSTRING_TOK", "COLLECTION_TOK", "RBRACKET_TOK", "LBRACKET_TOK", + "COMMA_TOK", "EMPTY_TOK", "SEMICOLON_TOK", "TRIANGLE_TOK", "TIN_TOK", + "POLYHEDRALSURFACE_TOK", "DOUBLE_TOK", "DIMENSIONALITY_TOK", "SRID_TOK", + "$accept", "geometry", "geometry_no_srid", "geometrycollection", + "geometry_list", "multisurface", "surface_list", "tin", + "polyhedralsurface", "multipolygon", "polygon_list", "patch_list", + "polygon", "polygon_untagged", "patch", "curvepolygon", "curvering_list", + "curvering", "patchring_list", "ring_list", "patchring", "ring", + "compoundcurve", "compound_list", "multicurve", "curve_list", + "multilinestring", "linestring_list", "circularstring", "linestring", + "linestring_untagged", "triangle_list", "triangle", "triangle_untagged", + "multipoint", "point_list", "point_untagged", "point", "ptarray", + "coordinate", YY_NULLPTR +}; +#endif + +# ifdef YYPRINT +/* YYTOKNUM[NUM] -- (External) token number corresponding to the + (internal) symbol number NUM (which must be that of a token). */ +static const yytype_uint16 yytoknum[] = +{ + 0, 256, 257, 258, 259, 260, 261, 262, 263, 264, + 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, + 275, 276, 277, 278, 279, 280 +}; +# endif + +#define YYPACT_NINF -90 + +#define yypact_value_is_default(Yystate) \ + (!!((Yystate) == (-90))) + +#define YYTABLE_NINF -1 + +#define yytable_value_is_error(Yytable_value) \ + 0 + + /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing + STATE-NUM. */ +static const yytype_int16 yypact[] = +{ + 109, -2, 16, 23, 26, 36, 39, 40, 52, 53, + 74, 79, 83, 84, 108, 137, 7, 46, -90, -90, + -90, -90, -90, -90, -90, -90, -90, -90, -90, -90, + -90, -90, -90, -90, 43, -90, 27, 43, -90, 88, + 33, -90, 144, 140, -90, 167, 175, -90, 176, 179, + -90, 183, 20, -90, 184, 11, -90, 187, 11, -90, + 188, 17, -90, 191, 43, -90, 192, 168, -90, 195, + 51, -90, 196, 56, -90, 199, 70, -90, 200, 168, + -90, 68, 110, -90, 43, -90, 169, 43, -90, 43, + 204, -90, 33, -90, 43, -90, 205, -90, -90, 140, + -90, 43, -90, 208, -90, 175, -90, 33, -90, 209, + -90, 179, -90, 212, -90, -90, -90, 20, -90, -90, + 213, -90, -90, -90, 11, -90, 216, -90, -90, -90, + -90, -90, 11, -90, 217, -90, -90, -90, 17, -90, + 220, 43, -90, -90, 221, 168, -90, 43, 80, -90, + 93, 224, -90, 56, -90, 94, 225, -90, 70, -90, + -90, 105, -90, 43, 228, -90, 229, 232, -90, 33, + 233, 44, -90, 140, 236, 237, -90, 175, 240, 241, + -90, 179, 244, -90, 20, 245, -90, 11, 248, -90, + 11, 249, -90, 17, 252, -90, 253, -90, 168, 256, + 257, 43, 43, -90, 56, 260, 43, 261, -90, -90, + 70, 264, 112, -90, -90, -90, -90, -90, -90, -90, + -90, -90, -90, -90, -90, -90, -90, -90, -90, -90, + -90, -90, -90, -90, -90, -90, -90, -90, -90, -90, + -90, -90, -90, -90, -90, -90, 47, 265, 268, -90, + -90, 269, -90, 94, -90, -90, -90, -90, 131, 132, + -90, -90, -90, -90 +}; + + /* YYDEFACT[STATE-NUM] -- Default reduction number in state STATE-NUM. + Performed when YYTABLE does not specify something else to do. Zero + means the default is an error. */ +static const yytype_uint8 yydefact[] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 2, 18, + 13, 15, 16, 12, 8, 9, 7, 14, 11, 6, + 5, 17, 10, 4, 0, 131, 0, 0, 109, 0, + 0, 54, 0, 0, 122, 0, 0, 99, 0, 0, + 46, 0, 0, 28, 0, 0, 87, 0, 0, 61, + 0, 0, 77, 0, 0, 105, 0, 0, 22, 0, + 0, 117, 0, 0, 38, 0, 0, 42, 0, 0, + 1, 0, 0, 133, 0, 130, 0, 0, 108, 0, + 0, 71, 0, 53, 0, 127, 0, 124, 125, 0, + 121, 0, 111, 0, 101, 0, 98, 0, 56, 0, + 48, 0, 45, 0, 32, 34, 33, 0, 27, 93, + 0, 92, 94, 95, 0, 86, 0, 63, 66, 67, + 65, 64, 0, 60, 0, 81, 82, 83, 0, 76, + 0, 0, 104, 24, 0, 0, 21, 0, 0, 116, + 0, 0, 113, 0, 37, 0, 0, 50, 0, 41, + 3, 134, 128, 0, 0, 106, 0, 0, 51, 0, + 0, 0, 119, 0, 0, 0, 96, 0, 0, 0, + 43, 0, 0, 25, 0, 0, 84, 0, 0, 58, + 0, 0, 74, 0, 0, 102, 0, 19, 0, 0, + 0, 0, 0, 35, 0, 0, 0, 0, 69, 39, + 0, 0, 135, 132, 129, 107, 73, 70, 52, 126, + 123, 120, 110, 100, 97, 55, 47, 44, 29, 31, + 30, 26, 89, 88, 90, 91, 85, 62, 59, 78, + 79, 80, 75, 103, 23, 20, 0, 0, 0, 112, + 36, 0, 57, 0, 49, 40, 136, 114, 0, 0, + 72, 68, 115, 118 +}; + + /* YYPGOTO[NTERM-NUM]. */ +static const yytype_int16 yypgoto[] = +{ + -90, -90, 0, -90, 5, -90, 37, -90, -90, -90, + 48, 6, -39, -33, -42, -32, 55, -21, -90, -89, + -57, 118, -50, 150, -90, 165, -90, 185, -51, -49, + -44, 138, -90, 89, -90, 193, 121, -90, -36, -6 +}; + + /* YYDEFGOTO[NTERM-NUM]. */ +static const yytype_int16 yydefgoto[] = +{ + -1, 17, 143, 19, 144, 20, 113, 21, 22, 23, + 109, 156, 24, 110, 157, 25, 126, 127, 207, 90, + 208, 91, 26, 134, 27, 120, 28, 103, 29, 30, + 131, 151, 31, 152, 32, 96, 97, 33, 82, 83 +}; + + /* YYTABLE[YYPACT[STATE-NUM]] -- What to do in state STATE-NUM. If + positive, shift that token. If negative, reduce the rule whose + number is the opposite. If YYTABLE_NINF, syntax error. */ +static const yytype_uint16 yytable[] = +{ + 18, 86, 104, 170, 121, 119, 122, 129, 128, 130, + 135, 123, 136, 114, 34, 2, 35, 137, 179, 115, + 116, 2, 36, 10, 11, 3, 79, 101, 140, 102, + 11, 9, 37, 101, 38, 102, 107, 98, 108, 40, + 39, 41, 43, 84, 44, 85, 80, 42, 164, 89, + 45, 166, 46, 167, 47, 49, 52, 50, 53, 219, + 48, 104, 257, 51, 54, 175, 81, 147, 55, 58, + 56, 59, 150, 121, 119, 122, 57, 60, 114, 160, + 123, 129, 128, 130, 115, 116, 155, 135, 171, 136, + 61, 161, 62, 98, 137, 64, 201, 65, 63, 67, + 70, 68, 71, 66, 87, 196, 88, 69, 72, 202, + 206, 200, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 73, 162, 74, 163, 212, 13, + 14, 15, 75, 223, 16, 256, 233, 232, 234, 129, + 128, 130, 239, 235, 240, 228, 262, 263, 226, 241, + 199, 229, 230, 76, 185, 77, 94, 213, 95, 182, + 92, 78, 93, 81, 211, 247, 248, 98, 254, 237, + 251, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 99, 165, 100, 163, 191, 13, 14, + 15, 101, 105, 102, 106, 107, 261, 108, 244, 111, + 117, 112, 118, 124, 132, 125, 133, 138, 141, 139, + 142, 145, 148, 146, 149, 153, 158, 154, 159, 168, + 172, 169, 173, 176, 180, 177, 181, 183, 186, 184, + 187, 189, 192, 190, 193, 195, 197, 163, 198, 203, + 209, 204, 210, 214, 215, 163, 163, 216, 218, 163, + 169, 221, 222, 173, 163, 224, 225, 177, 169, 227, + 231, 181, 184, 236, 238, 187, 190, 242, 243, 193, + 163, 245, 246, 198, 163, 250, 252, 204, 253, 255, + 258, 210, 163, 259, 260, 163, 163, 217, 194, 188, + 178, 205, 174, 249, 220 +}; + +static const yytype_uint8 yycheck[] = +{ + 0, 37, 46, 92, 55, 55, 55, 58, 58, 58, + 61, 55, 61, 52, 16, 4, 18, 61, 107, 52, + 52, 4, 24, 12, 13, 5, 19, 16, 64, 18, + 13, 11, 16, 16, 18, 18, 16, 43, 18, 16, + 24, 18, 16, 16, 18, 18, 0, 24, 84, 16, + 24, 87, 16, 89, 18, 16, 16, 18, 18, 15, + 24, 105, 15, 24, 24, 101, 23, 16, 16, 16, + 18, 18, 16, 124, 124, 124, 24, 24, 117, 79, + 124, 132, 132, 132, 117, 117, 16, 138, 94, 138, + 16, 23, 18, 99, 138, 16, 16, 18, 24, 16, + 16, 18, 18, 24, 16, 141, 18, 24, 24, 16, + 16, 147, 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 16, 15, 18, 17, 23, 20, + 21, 22, 24, 177, 25, 23, 187, 187, 187, 190, + 190, 190, 193, 187, 193, 184, 15, 15, 181, 193, + 145, 184, 184, 16, 117, 18, 16, 163, 18, 111, + 16, 24, 18, 23, 158, 201, 202, 173, 210, 190, + 206, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 16, 15, 18, 17, 132, 20, 21, + 22, 16, 16, 18, 18, 16, 253, 18, 198, 16, + 16, 18, 18, 16, 16, 18, 18, 16, 16, 18, + 18, 16, 16, 18, 18, 16, 16, 18, 18, 15, + 15, 17, 17, 15, 15, 17, 17, 15, 15, 17, + 17, 15, 15, 17, 17, 15, 15, 17, 17, 15, + 15, 17, 17, 15, 15, 17, 17, 15, 15, 17, + 17, 15, 15, 17, 17, 15, 15, 17, 17, 15, + 15, 17, 17, 15, 15, 17, 17, 15, 15, 17, + 17, 15, 15, 17, 17, 15, 15, 17, 17, 15, + 15, 17, 17, 15, 15, 17, 17, 169, 138, 124, + 105, 153, 99, 204, 173 +}; + + /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing + symbol of state STATE-NUM. */ +static const yytype_uint8 yystos[] = +{ + 0, 3, 4, 5, 6, 7, 8, 9, 10, 11, + 12, 13, 14, 20, 21, 22, 25, 27, 28, 29, + 31, 33, 34, 35, 38, 41, 48, 50, 52, 54, + 55, 58, 60, 63, 16, 18, 24, 16, 18, 24, + 16, 18, 24, 16, 18, 24, 16, 18, 24, 16, + 18, 24, 16, 18, 24, 16, 18, 24, 16, 18, + 24, 16, 18, 24, 16, 18, 24, 16, 18, 24, + 16, 18, 24, 16, 18, 24, 16, 18, 24, 19, + 0, 23, 64, 65, 16, 18, 64, 16, 18, 16, + 45, 47, 16, 18, 16, 18, 61, 62, 65, 16, + 18, 16, 18, 53, 56, 16, 18, 16, 18, 36, + 39, 16, 18, 32, 38, 39, 41, 16, 18, 48, + 51, 54, 55, 56, 16, 18, 42, 43, 48, 54, + 55, 56, 16, 18, 49, 54, 55, 56, 16, 18, + 64, 16, 18, 28, 30, 16, 18, 16, 16, 18, + 16, 57, 59, 16, 18, 16, 37, 40, 16, 18, + 28, 23, 15, 17, 64, 15, 64, 64, 15, 17, + 45, 65, 15, 17, 61, 64, 15, 17, 53, 45, + 15, 17, 36, 15, 17, 32, 15, 17, 51, 15, + 17, 42, 15, 17, 49, 15, 64, 15, 17, 30, + 64, 16, 16, 15, 17, 57, 16, 44, 46, 15, + 17, 37, 23, 65, 15, 15, 15, 47, 15, 15, + 62, 15, 15, 56, 15, 15, 39, 15, 38, 39, + 41, 15, 48, 54, 55, 56, 15, 43, 15, 54, + 55, 56, 15, 15, 28, 15, 15, 64, 64, 59, + 15, 64, 15, 17, 40, 15, 23, 15, 15, 15, + 15, 46, 15, 15 +}; + + /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const yytype_uint8 yyr1[] = +{ + 0, 26, 27, 27, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 29, + 29, 29, 29, 30, 30, 31, 31, 31, 31, 32, + 32, 32, 32, 32, 32, 33, 33, 33, 33, 34, + 34, 34, 34, 35, 35, 35, 35, 36, 36, 37, + 37, 38, 38, 38, 38, 39, 39, 40, 41, 41, + 41, 41, 42, 42, 43, 43, 43, 43, 44, 44, + 45, 45, 46, 47, 48, 48, 48, 48, 49, 49, + 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, + 51, 51, 51, 51, 51, 51, 52, 52, 52, 52, + 53, 53, 54, 54, 54, 54, 55, 55, 55, 55, + 56, 56, 57, 57, 58, 58, 58, 58, 59, 60, + 60, 60, 60, 61, 61, 62, 62, 62, 63, 63, + 63, 63, 64, 64, 65, 65, 65 +}; + + /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */ +static const yytype_uint8 yyr2[] = +{ + 0, 2, 1, 3, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 4, + 5, 3, 2, 3, 1, 4, 5, 3, 2, 3, + 3, 3, 1, 1, 1, 4, 5, 3, 2, 4, + 5, 3, 2, 4, 5, 3, 2, 3, 1, 3, + 1, 4, 5, 3, 2, 3, 1, 3, 4, 5, + 3, 2, 3, 1, 1, 1, 1, 1, 3, 1, + 3, 1, 3, 3, 4, 5, 3, 2, 3, 3, + 3, 1, 1, 1, 4, 5, 3, 2, 3, 3, + 3, 3, 1, 1, 1, 1, 4, 5, 3, 2, + 3, 1, 4, 5, 3, 2, 4, 5, 3, 2, + 3, 1, 3, 1, 6, 7, 3, 2, 5, 4, + 5, 3, 2, 3, 1, 1, 3, 1, 4, 5, + 3, 2, 3, 1, 2, 3, 4 +}; + + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY (-2) +#define YYEOF 0 + +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrorlab + + +#define YYRECOVERING() (!!yyerrstatus) + +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + YYPOPSTACK (yylen); \ + yystate = *yyssp; \ + goto yybackup; \ + } \ + else \ + { \ + yyerror (YY_("syntax error: cannot back up")); \ + YYERROR; \ + } \ +while (0) + +/* Error token number */ +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N]. + If N is 0, then set CURRENT to the empty location which ends + the previous symbol: RHS[0] (always defined). */ + +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + do \ + if (N) \ + { \ + (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \ + (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \ + (Current).last_line = YYRHSLOC (Rhs, N).last_line; \ + (Current).last_column = YYRHSLOC (Rhs, N).last_column; \ + } \ + else \ + { \ + (Current).first_line = (Current).last_line = \ + YYRHSLOC (Rhs, 0).last_line; \ + (Current).first_column = (Current).last_column = \ + YYRHSLOC (Rhs, 0).last_column; \ + } \ + while (0) +#endif + +#define YYRHSLOC(Rhs, K) ((Rhs)[K]) + + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (0) + + +/* YY_LOCATION_PRINT -- Print the location on the stream. + This macro was not mandated originally: define only if we know + we won't break user code: when these are the locations we know. */ + +#ifndef YY_LOCATION_PRINT +# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL + +/* Print *YYLOCP on YYO. Private, do not rely on its existence. */ + +YY_ATTRIBUTE_UNUSED +static unsigned +yy_location_print_ (FILE *yyo, YYLTYPE const * const yylocp) +{ + unsigned res = 0; + int end_col = 0 != yylocp->last_column ? yylocp->last_column - 1 : 0; + if (0 <= yylocp->first_line) + { + res += YYFPRINTF (yyo, "%d", yylocp->first_line); + if (0 <= yylocp->first_column) + res += YYFPRINTF (yyo, ".%d", yylocp->first_column); + } + if (0 <= yylocp->last_line) + { + if (yylocp->first_line < yylocp->last_line) + { + res += YYFPRINTF (yyo, "-%d", yylocp->last_line); + if (0 <= end_col) + res += YYFPRINTF (yyo, ".%d", end_col); + } + else if (0 <= end_col && yylocp->first_column < end_col) + res += YYFPRINTF (yyo, "-%d", end_col); + } + return res; + } + +# define YY_LOCATION_PRINT(File, Loc) \ + yy_location_print_ (File, &(Loc)) + +# else +# define YY_LOCATION_PRINT(File, Loc) ((void) 0) +# endif +#endif + + +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) \ +do { \ + if (yydebug) \ + { \ + YYFPRINTF (stderr, "%s ", Title); \ + yy_symbol_print (stderr, \ + Type, Value, Location); \ + YYFPRINTF (stderr, "\n"); \ + } \ +} while (0) + + +/*----------------------------------------. +| Print this symbol's value on YYOUTPUT. | +`----------------------------------------*/ + +static void +yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp) +{ + FILE *yyo = yyoutput; + YYUSE (yyo); + YYUSE (yylocationp); + if (!yyvaluep) + return; +# ifdef YYPRINT + if (yytype < YYNTOKENS) + YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep); +# endif + YYUSE (yytype); +} + + +/*--------------------------------. +| Print this symbol on YYOUTPUT. | +`--------------------------------*/ + +static void +yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep, YYLTYPE const * const yylocationp) +{ + YYFPRINTF (yyoutput, "%s %s (", + yytype < YYNTOKENS ? "token" : "nterm", yytname[yytype]); + + YY_LOCATION_PRINT (yyoutput, *yylocationp); + YYFPRINTF (yyoutput, ": "); + yy_symbol_value_print (yyoutput, yytype, yyvaluep, yylocationp); + YYFPRINTF (yyoutput, ")"); +} + +/*------------------------------------------------------------------. +| yy_stack_print -- Print the state stack from its BOTTOM up to its | +| TOP (included). | +`------------------------------------------------------------------*/ + +static void +yy_stack_print (yytype_int16 *yybottom, yytype_int16 *yytop) +{ + YYFPRINTF (stderr, "Stack now"); + for (; yybottom <= yytop; yybottom++) + { + int yybot = *yybottom; + YYFPRINTF (stderr, " %d", yybot); + } + YYFPRINTF (stderr, "\n"); +} + +# define YY_STACK_PRINT(Bottom, Top) \ +do { \ + if (yydebug) \ + yy_stack_print ((Bottom), (Top)); \ +} while (0) + + +/*------------------------------------------------. +| Report that the YYRULE is going to be reduced. | +`------------------------------------------------*/ + +static void +yy_reduce_print (yytype_int16 *yyssp, YYSTYPE *yyvsp, YYLTYPE *yylsp, int yyrule) +{ + unsigned long int yylno = yyrline[yyrule]; + int yynrhs = yyr2[yyrule]; + int yyi; + YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n", + yyrule - 1, yylno); + /* The symbols being reduced. */ + for (yyi = 0; yyi < yynrhs; yyi++) + { + YYFPRINTF (stderr, " $%d = ", yyi + 1); + yy_symbol_print (stderr, + yystos[yyssp[yyi + 1 - yynrhs]], + &(yyvsp[(yyi + 1) - (yynrhs)]) + , &(yylsp[(yyi + 1) - (yynrhs)]) ); + YYFPRINTF (stderr, "\n"); + } +} + +# define YY_REDUCE_PRINT(Rule) \ +do { \ + if (yydebug) \ + yy_reduce_print (yyssp, yyvsp, yylsp, Rule); \ +} while (0) + +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +# define YY_SYMBOL_PRINT(Title, Type, Value, Location) +# define YY_STACK_PRINT(Bottom, Top) +# define YY_REDUCE_PRINT(Rule) +#endif /* !YYDEBUG */ + + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + + +#if YYERROR_VERBOSE + +# ifndef yystrlen +# if defined __GLIBC__ && defined _STRING_H +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +static YYSIZE_T +yystrlen (const char *yystr) +{ + YYSIZE_T yylen; + for (yylen = 0; yystr[yylen]; yylen++) + continue; + return yylen; +} +# endif +# endif + +# ifndef yystpcpy +# if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +static char * +yystpcpy (char *yydest, const char *yysrc) +{ + char *yyd = yydest; + const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif + +# ifndef yytnamerr +/* Copy to YYRES the contents of YYSTR after stripping away unnecessary + quotes and backslashes, so that it's suitable for yyerror. The + heuristic is that double-quoting is unnecessary unless the string + contains an apostrophe, a comma, or backslash (other than + backslash-backslash). YYSTR is taken from yytname. If YYRES is + null, do not copy; instead, return the length of what the result + would have been. */ +static YYSIZE_T +yytnamerr (char *yyres, const char *yystr) +{ + if (*yystr == '"') + { + YYSIZE_T yyn = 0; + char const *yyp = yystr; + + for (;;) + switch (*++yyp) + { + case '\'': + case ',': + goto do_not_strip_quotes; + + case '\\': + if (*++yyp != '\\') + goto do_not_strip_quotes; + /* Fall through. */ + default: + if (yyres) + yyres[yyn] = *yyp; + yyn++; + break; + + case '"': + if (yyres) + yyres[yyn] = '\0'; + return yyn; + } + do_not_strip_quotes: ; + } + + if (! yyres) + return yystrlen (yystr); + + return yystpcpy (yyres, yystr) - yyres; +} +# endif + +/* Copy into *YYMSG, which is of size *YYMSG_ALLOC, an error message + about the unexpected token YYTOKEN for the state stack whose top is + YYSSP. + + Return 0 if *YYMSG was successfully written. Return 1 if *YYMSG is + not large enough to hold the message. In that case, also set + *YYMSG_ALLOC to the required number of bytes. Return 2 if the + required number of bytes is too large to store. */ +static int +yysyntax_error (YYSIZE_T *yymsg_alloc, char **yymsg, + yytype_int16 *yyssp, int yytoken) +{ + YYSIZE_T yysize0 = yytnamerr (YY_NULLPTR, yytname[yytoken]); + YYSIZE_T yysize = yysize0; + enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 }; + /* Internationalized format string. */ + const char *yyformat = YY_NULLPTR; + /* Arguments of yyformat. */ + char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM]; + /* Number of reported tokens (one for the "unexpected", one per + "expected"). */ + int yycount = 0; + + /* There are many possibilities here to consider: + - If this state is a consistent state with a default action, then + the only way this function was invoked is if the default action + is an error action. In that case, don't check for expected + tokens because there are none. + - The only way there can be no lookahead present (in yychar) is if + this state is a consistent state with a default action. Thus, + detecting the absence of a lookahead is sufficient to determine + that there is no unexpected or expected token to report. In that + case, just report a simple "syntax error". + - Don't assume there isn't a lookahead just because this state is a + consistent state with a default action. There might have been a + previous inconsistent state, consistent state with a non-default + action, or user semantic action that manipulated yychar. + - Of course, the expected token list depends on states to have + correct lookahead information, and it depends on the parser not + to perform extra reductions after fetching a lookahead from the + scanner and before detecting a syntax error. Thus, state merging + (from LALR or IELR) and default reductions corrupt the expected + token list. However, the list is correct for canonical LR with + one exception: it will still contain any token that will not be + accepted due to an error action in a later state. + */ + if (yytoken != YYEMPTY) + { + int yyn = yypact[*yyssp]; + yyarg[yycount++] = yytname[yytoken]; + if (!yypact_value_is_default (yyn)) + { + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. In other words, skip the first -YYN actions for + this state because they are default actions. */ + int yyxbegin = yyn < 0 ? -yyn : 0; + /* Stay within bounds of both yycheck and yytname. */ + int yychecklim = YYLAST - yyn + 1; + int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS; + int yyx; + + for (yyx = yyxbegin; yyx < yyxend; ++yyx) + if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR + && !yytable_value_is_error (yytable[yyx + yyn])) + { + if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM) + { + yycount = 1; + yysize = yysize0; + break; + } + yyarg[yycount++] = yytname[yyx]; + { + YYSIZE_T yysize1 = yysize + yytnamerr (YY_NULLPTR, yytname[yyx]); + if (! (yysize <= yysize1 + && yysize1 <= YYSTACK_ALLOC_MAXIMUM)) + return 2; + yysize = yysize1; + } + } + } + } + + switch (yycount) + { +# define YYCASE_(N, S) \ + case N: \ + yyformat = S; \ + break + YYCASE_(0, YY_("syntax error")); + YYCASE_(1, YY_("syntax error, unexpected %s")); + YYCASE_(2, YY_("syntax error, unexpected %s, expecting %s")); + YYCASE_(3, YY_("syntax error, unexpected %s, expecting %s or %s")); + YYCASE_(4, YY_("syntax error, unexpected %s, expecting %s or %s or %s")); + YYCASE_(5, YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s")); +# undef YYCASE_ + } + + { + YYSIZE_T yysize1 = yysize + yystrlen (yyformat); + if (! (yysize <= yysize1 && yysize1 <= YYSTACK_ALLOC_MAXIMUM)) + return 2; + yysize = yysize1; + } + + if (*yymsg_alloc < yysize) + { + *yymsg_alloc = 2 * yysize; + if (! (yysize <= *yymsg_alloc + && *yymsg_alloc <= YYSTACK_ALLOC_MAXIMUM)) + *yymsg_alloc = YYSTACK_ALLOC_MAXIMUM; + return 1; + } + + /* Avoid sprintf, as that infringes on the user's name space. + Don't have undefined behavior even if the translation + produced a string with the wrong number of "%s"s. */ + { + char *yyp = *yymsg; + int yyi = 0; + while ((*yyp = *yyformat) != '\0') + if (*yyp == '%' && yyformat[1] == 's' && yyi < yycount) + { + yyp += yytnamerr (yyp, yyarg[yyi++]); + yyformat += 2; + } + else + { + yyp++; + yyformat++; + } + } + return 0; +} +#endif /* YYERROR_VERBOSE */ + +/*-----------------------------------------------. +| Release the memory associated to this symbol. | +`-----------------------------------------------*/ + +static void +yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep, YYLTYPE *yylocationp) +{ + YYUSE (yyvaluep); + YYUSE (yylocationp); + if (!yymsg) + yymsg = "Deleting"; + YY_SYMBOL_PRINT (yymsg, yytype, yyvaluep, yylocationp); + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + switch (yytype) + { + case 28: /* geometry_no_srid */ +#line 194 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1372 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 29: /* geometrycollection */ +#line 195 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1378 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 30: /* geometry_list */ +#line 196 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1384 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 31: /* multisurface */ +#line 203 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1390 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 32: /* surface_list */ +#line 181 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1396 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 33: /* tin */ +#line 210 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1402 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 34: /* polyhedralsurface */ +#line 209 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1408 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 35: /* multipolygon */ +#line 202 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1414 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 36: /* polygon_list */ +#line 182 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1420 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 37: /* patch_list */ +#line 183 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1426 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 38: /* polygon */ +#line 206 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1432 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 39: /* polygon_untagged */ +#line 208 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1438 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 40: /* patch */ +#line 207 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1444 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 41: /* curvepolygon */ +#line 192 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1450 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 42: /* curvering_list */ +#line 179 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1456 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 43: /* curvering */ +#line 193 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1462 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 44: /* patchring_list */ +#line 189 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1468 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 45: /* ring_list */ +#line 188 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1474 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 46: /* patchring */ +#line 178 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { ptarray_free(((*yyvaluep).ptarrayvalue)); } +#line 1480 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 47: /* ring */ +#line 177 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { ptarray_free(((*yyvaluep).ptarrayvalue)); } +#line 1486 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 48: /* compoundcurve */ +#line 191 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1492 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 49: /* compound_list */ +#line 187 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1498 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 50: /* multicurve */ +#line 199 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1504 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 51: /* curve_list */ +#line 186 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1510 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 52: /* multilinestring */ +#line 200 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1516 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 53: /* linestring_list */ +#line 185 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1522 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 54: /* circularstring */ +#line 190 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1528 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 55: /* linestring */ +#line 197 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1534 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 56: /* linestring_untagged */ +#line 198 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1540 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 57: /* triangle_list */ +#line 180 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1546 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 58: /* triangle */ +#line 211 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1552 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 59: /* triangle_untagged */ +#line 212 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1558 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 60: /* multipoint */ +#line 201 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1564 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 61: /* point_list */ +#line 184 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1570 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 62: /* point_untagged */ +#line 205 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1576 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 63: /* point */ +#line 204 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { lwgeom_free(((*yyvaluep).geometryvalue)); } +#line 1582 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + case 64: /* ptarray */ +#line 176 "lwin_wkt_parse.y" /* yacc.c:1257 */ + { ptarray_free(((*yyvaluep).ptarrayvalue)); } +#line 1588 "lwin_wkt_parse.c" /* yacc.c:1257 */ + break; + + + default: + break; + } + YY_IGNORE_MAYBE_UNINITIALIZED_END +} + + + + +/* The lookahead symbol. */ +int yychar; + +/* The semantic value of the lookahead symbol. */ +YYSTYPE yylval; +/* Location data for the lookahead symbol. */ +YYLTYPE yylloc +# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL + = { 1, 1, 1, 1 } +# endif +; +/* Number of syntax errors so far. */ +int yynerrs; + + +/*----------. +| yyparse. | +`----------*/ + +int +yyparse (void) +{ + int yystate; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + + /* The stacks and their tools: + 'yyss': related to states. + 'yyvs': related to semantic values. + 'yyls': related to locations. + + Refer to the stacks through separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + yytype_int16 yyssa[YYINITDEPTH]; + yytype_int16 *yyss; + yytype_int16 *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs; + YYSTYPE *yyvsp; + + /* The location stack. */ + YYLTYPE yylsa[YYINITDEPTH]; + YYLTYPE *yyls; + YYLTYPE *yylsp; + + /* The locations where the error started and ended. */ + YYLTYPE yyerror_range[3]; + + YYSIZE_T yystacksize; + + int yyn; + int yyresult; + /* Lookahead token as an internal (translated) token number. */ + int yytoken = 0; + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; + YYLTYPE yyloc; + +#if YYERROR_VERBOSE + /* Buffer for error messages, and its allocated size. */ + char yymsgbuf[128]; + char *yymsg = yymsgbuf; + YYSIZE_T yymsg_alloc = sizeof yymsgbuf; +#endif + +#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N), yylsp -= (N)) + + /* The number of symbols on the RHS of the reduced rule. + Keep to zero when no symbol should be popped. */ + int yylen = 0; + + yyssp = yyss = yyssa; + yyvsp = yyvs = yyvsa; + yylsp = yyls = yylsa; + yystacksize = YYINITDEPTH; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + yylsp[0] = yylloc; + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. So pushing a state here evens the stacks. */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyss + yystacksize - 1 <= yyssp) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + yytype_int16 *yyss1 = yyss; + YYLTYPE *yyls1 = yyls; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. This used to be a + conditional around just the two extra args, but that might + be undefined if yyoverflow is a macro. */ + yyoverflow (YY_("memory exhausted"), + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yyls1, yysize * sizeof (*yylsp), + &yystacksize); + + yyls = yyls1; + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyexhaustedlab; +# else + /* Extend the stack our own way. */ + if (YYMAXDEPTH <= yystacksize) + goto yyexhaustedlab; + yystacksize *= 2; + if (YYMAXDEPTH < yystacksize) + yystacksize = YYMAXDEPTH; + + { + yytype_int16 *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyexhaustedlab; + YYSTACK_RELOCATE (yyss_alloc, yyss); + YYSTACK_RELOCATE (yyvs_alloc, yyvs); + YYSTACK_RELOCATE (yyls_alloc, yyls); +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; + yylsp = yyls + yysize - 1; + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyss + yystacksize - 1 <= yyssp) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + if (yystate == YYFINAL) + YYACCEPT; + + goto yybackup; + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + + /* Do appropriate processing given the current state. Read a + lookahead token if we need one and don't already have one. */ + + /* First try to decide what to do without reference to lookahead token. */ + yyn = yypact[yystate]; + if (yypact_value_is_default (yyn)) + goto yydefault; + + /* Not known => get a lookahead token if don't already have one. */ + + /* YYCHAR is either YYEMPTY or YYEOF or a valid lookahead symbol. */ + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = yylex (); + } + + if (yychar <= YYEOF) + { + yychar = yytoken = YYEOF; + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yytoken = YYTRANSLATE (yychar); + YY_SYMBOL_PRINT ("Next token is", yytoken, &yylval, &yylloc); + } + + /* If the proper action on seeing token YYTOKEN is to reduce or to + detect an error, take that action. */ + yyn += yytoken; + if (yyn < 0 || YYLAST < yyn || yycheck[yyn] != yytoken) + goto yydefault; + yyn = yytable[yyn]; + if (yyn <= 0) + { + if (yytable_value_is_error (yyn)) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + /* Shift the lookahead token. */ + YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc); + + /* Discard the shifted token. */ + yychar = YYEMPTY; + + yystate = yyn; + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + *++yylsp = yylloc; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + '$$ = $1'. + + Otherwise, the following line sets YYVAL to garbage. + This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + + /* Default location. */ + YYLLOC_DEFAULT (yyloc, (yylsp - yylen), yylen); + YY_REDUCE_PRINT (yyn); + switch (yyn) + { + case 2: +#line 218 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { wkt_parser_geometry_new((yyvsp[0].geometryvalue), SRID_UNKNOWN); WKT_ERROR(); } +#line 1876 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 3: +#line 220 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { wkt_parser_geometry_new((yyvsp[0].geometryvalue), (yyvsp[-2].integervalue)); WKT_ERROR(); } +#line 1882 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 4: +#line 223 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[0].geometryvalue); } +#line 1888 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 5: +#line 224 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[0].geometryvalue); } +#line 1894 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 6: +#line 225 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[0].geometryvalue); } +#line 1900 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 7: +#line 226 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[0].geometryvalue); } +#line 1906 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 8: +#line 227 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[0].geometryvalue); } +#line 1912 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 9: +#line 228 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[0].geometryvalue); } +#line 1918 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 10: +#line 229 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[0].geometryvalue); } +#line 1924 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 11: +#line 230 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[0].geometryvalue); } +#line 1930 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 12: +#line 231 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[0].geometryvalue); } +#line 1936 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 13: +#line 232 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[0].geometryvalue); } +#line 1942 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 14: +#line 233 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[0].geometryvalue); } +#line 1948 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 15: +#line 234 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[0].geometryvalue); } +#line 1954 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 16: +#line 235 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[0].geometryvalue); } +#line 1960 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 17: +#line 236 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[0].geometryvalue); } +#line 1966 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 18: +#line 237 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[0].geometryvalue); } +#line 1972 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 19: +#line 241 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(COLLECTIONTYPE, (yyvsp[-1].geometryvalue), NULL); WKT_ERROR(); } +#line 1978 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 20: +#line 243 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(COLLECTIONTYPE, (yyvsp[-1].geometryvalue), (yyvsp[-3].stringvalue)); WKT_ERROR(); } +#line 1984 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 21: +#line 245 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(COLLECTIONTYPE, NULL, (yyvsp[-1].stringvalue)); WKT_ERROR(); } +#line 1990 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 22: +#line 247 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(COLLECTIONTYPE, NULL, NULL); WKT_ERROR(); } +#line 1996 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 23: +#line 251 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_add_geom((yyvsp[-2].geometryvalue),(yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2002 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 24: +#line 253 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_new((yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2008 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 25: +#line 257 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTISURFACETYPE, (yyvsp[-1].geometryvalue), NULL); WKT_ERROR(); } +#line 2014 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 26: +#line 259 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTISURFACETYPE, (yyvsp[-1].geometryvalue), (yyvsp[-3].stringvalue)); WKT_ERROR(); } +#line 2020 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 27: +#line 261 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTISURFACETYPE, NULL, (yyvsp[-1].stringvalue)); WKT_ERROR(); } +#line 2026 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 28: +#line 263 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTISURFACETYPE, NULL, NULL); WKT_ERROR(); } +#line 2032 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 29: +#line 267 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_add_geom((yyvsp[-2].geometryvalue),(yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2038 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 30: +#line 269 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_add_geom((yyvsp[-2].geometryvalue),(yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2044 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 31: +#line 271 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_add_geom((yyvsp[-2].geometryvalue),(yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2050 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 32: +#line 273 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_new((yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2056 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 33: +#line 275 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_new((yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2062 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 34: +#line 277 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_new((yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2068 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 35: +#line 281 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(TINTYPE, (yyvsp[-1].geometryvalue), NULL); WKT_ERROR(); } +#line 2074 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 36: +#line 283 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(TINTYPE, (yyvsp[-1].geometryvalue), (yyvsp[-3].stringvalue)); WKT_ERROR(); } +#line 2080 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 37: +#line 285 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(TINTYPE, NULL, (yyvsp[-1].stringvalue)); WKT_ERROR(); } +#line 2086 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 38: +#line 287 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(TINTYPE, NULL, NULL); WKT_ERROR(); } +#line 2092 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 39: +#line 291 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(POLYHEDRALSURFACETYPE, (yyvsp[-1].geometryvalue), NULL); WKT_ERROR(); } +#line 2098 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 40: +#line 293 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(POLYHEDRALSURFACETYPE, (yyvsp[-1].geometryvalue), (yyvsp[-3].stringvalue)); WKT_ERROR(); } +#line 2104 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 41: +#line 295 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(POLYHEDRALSURFACETYPE, NULL, (yyvsp[-1].stringvalue)); WKT_ERROR(); } +#line 2110 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 42: +#line 297 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(POLYHEDRALSURFACETYPE, NULL, NULL); WKT_ERROR(); } +#line 2116 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 43: +#line 301 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTIPOLYGONTYPE, (yyvsp[-1].geometryvalue), NULL); WKT_ERROR(); } +#line 2122 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 44: +#line 303 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTIPOLYGONTYPE, (yyvsp[-1].geometryvalue), (yyvsp[-3].stringvalue)); WKT_ERROR(); } +#line 2128 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 45: +#line 305 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTIPOLYGONTYPE, NULL, (yyvsp[-1].stringvalue)); WKT_ERROR(); } +#line 2134 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 46: +#line 307 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTIPOLYGONTYPE, NULL, NULL); WKT_ERROR(); } +#line 2140 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 47: +#line 311 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_add_geom((yyvsp[-2].geometryvalue),(yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2146 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 48: +#line 313 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_new((yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2152 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 49: +#line 317 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_add_geom((yyvsp[-2].geometryvalue),(yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2158 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 50: +#line 319 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_new((yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2164 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 51: +#line 323 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_polygon_finalize((yyvsp[-1].geometryvalue), NULL); WKT_ERROR(); } +#line 2170 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 52: +#line 325 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_polygon_finalize((yyvsp[-1].geometryvalue), (yyvsp[-3].stringvalue)); WKT_ERROR(); } +#line 2176 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 53: +#line 327 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_polygon_finalize(NULL, (yyvsp[-1].stringvalue)); WKT_ERROR(); } +#line 2182 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 54: +#line 329 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_polygon_finalize(NULL, NULL); WKT_ERROR(); } +#line 2188 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 55: +#line 333 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[-1].geometryvalue); } +#line 2194 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 56: +#line 335 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_polygon_finalize(NULL, NULL); WKT_ERROR(); } +#line 2200 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 57: +#line 338 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[-1].geometryvalue); } +#line 2206 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 58: +#line 342 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_curvepolygon_finalize((yyvsp[-1].geometryvalue), NULL); WKT_ERROR(); } +#line 2212 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 59: +#line 344 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_curvepolygon_finalize((yyvsp[-1].geometryvalue), (yyvsp[-3].stringvalue)); WKT_ERROR(); } +#line 2218 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 60: +#line 346 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_curvepolygon_finalize(NULL, (yyvsp[-1].stringvalue)); WKT_ERROR(); } +#line 2224 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 61: +#line 348 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_curvepolygon_finalize(NULL, NULL); WKT_ERROR(); } +#line 2230 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 62: +#line 352 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_curvepolygon_add_ring((yyvsp[-2].geometryvalue),(yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2236 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 63: +#line 354 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_curvepolygon_new((yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2242 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 64: +#line 357 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[0].geometryvalue); } +#line 2248 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 65: +#line 358 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[0].geometryvalue); } +#line 2254 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 66: +#line 359 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[0].geometryvalue); } +#line 2260 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 67: +#line 360 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = (yyvsp[0].geometryvalue); } +#line 2266 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 68: +#line 364 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_polygon_add_ring((yyvsp[-2].geometryvalue),(yyvsp[0].ptarrayvalue),'Z'); WKT_ERROR(); } +#line 2272 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 69: +#line 366 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_polygon_new((yyvsp[0].ptarrayvalue),'Z'); WKT_ERROR(); } +#line 2278 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 70: +#line 370 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_polygon_add_ring((yyvsp[-2].geometryvalue),(yyvsp[0].ptarrayvalue),'2'); WKT_ERROR(); } +#line 2284 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 71: +#line 372 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_polygon_new((yyvsp[0].ptarrayvalue),'2'); WKT_ERROR(); } +#line 2290 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 72: +#line 375 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.ptarrayvalue) = (yyvsp[-1].ptarrayvalue); } +#line 2296 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 73: +#line 378 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.ptarrayvalue) = (yyvsp[-1].ptarrayvalue); } +#line 2302 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 74: +#line 382 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(COMPOUNDTYPE, (yyvsp[-1].geometryvalue), NULL); WKT_ERROR(); } +#line 2308 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 75: +#line 384 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(COMPOUNDTYPE, (yyvsp[-1].geometryvalue), (yyvsp[-3].stringvalue)); WKT_ERROR(); } +#line 2314 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 76: +#line 386 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(COMPOUNDTYPE, NULL, (yyvsp[-1].stringvalue)); WKT_ERROR(); } +#line 2320 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 77: +#line 388 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(COMPOUNDTYPE, NULL, NULL); WKT_ERROR(); } +#line 2326 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 78: +#line 392 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_compound_add_geom((yyvsp[-2].geometryvalue),(yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2332 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 79: +#line 394 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_compound_add_geom((yyvsp[-2].geometryvalue),(yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2338 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 80: +#line 396 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_compound_add_geom((yyvsp[-2].geometryvalue),(yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2344 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 81: +#line 398 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_compound_new((yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2350 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 82: +#line 400 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_compound_new((yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2356 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 83: +#line 402 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_compound_new((yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2362 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 84: +#line 406 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTICURVETYPE, (yyvsp[-1].geometryvalue), NULL); WKT_ERROR(); } +#line 2368 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 85: +#line 408 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTICURVETYPE, (yyvsp[-1].geometryvalue), (yyvsp[-3].stringvalue)); WKT_ERROR(); } +#line 2374 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 86: +#line 410 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTICURVETYPE, NULL, (yyvsp[-1].stringvalue)); WKT_ERROR(); } +#line 2380 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 87: +#line 412 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTICURVETYPE, NULL, NULL); WKT_ERROR(); } +#line 2386 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 88: +#line 416 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_add_geom((yyvsp[-2].geometryvalue),(yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2392 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 89: +#line 418 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_add_geom((yyvsp[-2].geometryvalue),(yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2398 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 90: +#line 420 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_add_geom((yyvsp[-2].geometryvalue),(yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2404 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 91: +#line 422 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_add_geom((yyvsp[-2].geometryvalue),(yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2410 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 92: +#line 424 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_new((yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2416 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 93: +#line 426 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_new((yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2422 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 94: +#line 428 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_new((yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2428 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 95: +#line 430 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_new((yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2434 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 96: +#line 434 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTILINETYPE, (yyvsp[-1].geometryvalue), NULL); WKT_ERROR(); } +#line 2440 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 97: +#line 436 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTILINETYPE, (yyvsp[-1].geometryvalue), (yyvsp[-3].stringvalue)); WKT_ERROR(); } +#line 2446 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 98: +#line 438 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTILINETYPE, NULL, (yyvsp[-1].stringvalue)); WKT_ERROR(); } +#line 2452 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 99: +#line 440 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTILINETYPE, NULL, NULL); WKT_ERROR(); } +#line 2458 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 100: +#line 444 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_add_geom((yyvsp[-2].geometryvalue),(yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2464 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 101: +#line 446 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_new((yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2470 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 102: +#line 450 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_circularstring_new((yyvsp[-1].ptarrayvalue), NULL); WKT_ERROR(); } +#line 2476 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 103: +#line 452 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_circularstring_new((yyvsp[-1].ptarrayvalue), (yyvsp[-3].stringvalue)); WKT_ERROR(); } +#line 2482 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 104: +#line 454 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_circularstring_new(NULL, (yyvsp[-1].stringvalue)); WKT_ERROR(); } +#line 2488 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 105: +#line 456 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_circularstring_new(NULL, NULL); WKT_ERROR(); } +#line 2494 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 106: +#line 460 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_linestring_new((yyvsp[-1].ptarrayvalue), NULL); WKT_ERROR(); } +#line 2500 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 107: +#line 462 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_linestring_new((yyvsp[-1].ptarrayvalue), (yyvsp[-3].stringvalue)); WKT_ERROR(); } +#line 2506 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 108: +#line 464 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_linestring_new(NULL, (yyvsp[-1].stringvalue)); WKT_ERROR(); } +#line 2512 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 109: +#line 466 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_linestring_new(NULL, NULL); WKT_ERROR(); } +#line 2518 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 110: +#line 470 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_linestring_new((yyvsp[-1].ptarrayvalue), NULL); WKT_ERROR(); } +#line 2524 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 111: +#line 472 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_linestring_new(NULL, NULL); WKT_ERROR(); } +#line 2530 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 112: +#line 476 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_add_geom((yyvsp[-2].geometryvalue),(yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2536 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 113: +#line 478 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_new((yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2542 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 114: +#line 482 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_triangle_new((yyvsp[-2].ptarrayvalue), NULL); WKT_ERROR(); } +#line 2548 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 115: +#line 484 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_triangle_new((yyvsp[-2].ptarrayvalue), (yyvsp[-5].stringvalue)); WKT_ERROR(); } +#line 2554 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 116: +#line 486 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_triangle_new(NULL, (yyvsp[-1].stringvalue)); WKT_ERROR(); } +#line 2560 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 117: +#line 488 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_triangle_new(NULL, NULL); WKT_ERROR(); } +#line 2566 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 118: +#line 492 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_triangle_new((yyvsp[-2].ptarrayvalue), NULL); WKT_ERROR(); } +#line 2572 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 119: +#line 496 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTIPOINTTYPE, (yyvsp[-1].geometryvalue), NULL); WKT_ERROR(); } +#line 2578 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 120: +#line 498 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTIPOINTTYPE, (yyvsp[-1].geometryvalue), (yyvsp[-3].stringvalue)); WKT_ERROR(); } +#line 2584 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 121: +#line 500 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTIPOINTTYPE, NULL, (yyvsp[-1].stringvalue)); WKT_ERROR(); } +#line 2590 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 122: +#line 502 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_finalize(MULTIPOINTTYPE, NULL, NULL); WKT_ERROR(); } +#line 2596 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 123: +#line 506 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_add_geom((yyvsp[-2].geometryvalue),(yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2602 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 124: +#line 508 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_collection_new((yyvsp[0].geometryvalue)); WKT_ERROR(); } +#line 2608 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 125: +#line 512 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_point_new(wkt_parser_ptarray_new((yyvsp[0].coordinatevalue)),NULL); WKT_ERROR(); } +#line 2614 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 126: +#line 514 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_point_new(wkt_parser_ptarray_new((yyvsp[-1].coordinatevalue)),NULL); WKT_ERROR(); } +#line 2620 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 127: +#line 516 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_point_new(NULL, NULL); WKT_ERROR(); } +#line 2626 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 128: +#line 520 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_point_new((yyvsp[-1].ptarrayvalue), NULL); WKT_ERROR(); } +#line 2632 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 129: +#line 522 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_point_new((yyvsp[-1].ptarrayvalue), (yyvsp[-3].stringvalue)); WKT_ERROR(); } +#line 2638 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 130: +#line 524 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_point_new(NULL, (yyvsp[-1].stringvalue)); WKT_ERROR(); } +#line 2644 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 131: +#line 526 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.geometryvalue) = wkt_parser_point_new(NULL,NULL); WKT_ERROR(); } +#line 2650 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 132: +#line 530 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.ptarrayvalue) = wkt_parser_ptarray_add_coord((yyvsp[-2].ptarrayvalue), (yyvsp[0].coordinatevalue)); WKT_ERROR(); } +#line 2656 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 133: +#line 532 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.ptarrayvalue) = wkt_parser_ptarray_new((yyvsp[0].coordinatevalue)); WKT_ERROR(); } +#line 2662 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 134: +#line 536 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.coordinatevalue) = wkt_parser_coord_2((yyvsp[-1].doublevalue), (yyvsp[0].doublevalue)); WKT_ERROR(); } +#line 2668 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 135: +#line 538 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.coordinatevalue) = wkt_parser_coord_3((yyvsp[-2].doublevalue), (yyvsp[-1].doublevalue), (yyvsp[0].doublevalue)); WKT_ERROR(); } +#line 2674 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + case 136: +#line 540 "lwin_wkt_parse.y" /* yacc.c:1646 */ + { (yyval.coordinatevalue) = wkt_parser_coord_4((yyvsp[-3].doublevalue), (yyvsp[-2].doublevalue), (yyvsp[-1].doublevalue), (yyvsp[0].doublevalue)); WKT_ERROR(); } +#line 2680 "lwin_wkt_parse.c" /* yacc.c:1646 */ + break; + + +#line 2684 "lwin_wkt_parse.c" /* yacc.c:1646 */ + default: break; + } + /* User semantic actions sometimes alter yychar, and that requires + that yytoken be updated with the new translation. We take the + approach of translating immediately before every use of yytoken. + One alternative is translating here after every semantic action, + but that translation would be missed if the semantic action invokes + YYABORT, YYACCEPT, or YYERROR immediately after altering yychar or + if it invokes YYBACKUP. In the case of YYABORT or YYACCEPT, an + incorrect destructor might then be invoked immediately. In the + case of YYERROR or YYBACKUP, subsequent parser actions might lead + to an incorrect destructor call or verbose syntax error message + before the lookahead is translated. */ + YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); + + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + + *++yyvsp = yyval; + *++yylsp = yyloc; + + /* Now 'shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTOKENS] + *yyssp; + if (0 <= yystate && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTOKENS]; + + goto yynewstate; + + +/*--------------------------------------. +| yyerrlab -- here on detecting error. | +`--------------------------------------*/ +yyerrlab: + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = yychar == YYEMPTY ? YYEMPTY : YYTRANSLATE (yychar); + + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; +#if ! YYERROR_VERBOSE + yyerror (YY_("syntax error")); +#else +# define YYSYNTAX_ERROR yysyntax_error (&yymsg_alloc, &yymsg, \ + yyssp, yytoken) + { + char const *yymsgp = YY_("syntax error"); + int yysyntax_error_status; + yysyntax_error_status = YYSYNTAX_ERROR; + if (yysyntax_error_status == 0) + yymsgp = yymsg; + else if (yysyntax_error_status == 1) + { + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); + yymsg = (char *) YYSTACK_ALLOC (yymsg_alloc); + if (!yymsg) + { + yymsg = yymsgbuf; + yymsg_alloc = sizeof yymsgbuf; + yysyntax_error_status = 2; + } + else + { + yysyntax_error_status = YYSYNTAX_ERROR; + yymsgp = yymsg; + } + } + yyerror (yymsgp); + if (yysyntax_error_status == 2) + goto yyexhaustedlab; + } +# undef YYSYNTAX_ERROR +#endif + } + + yyerror_range[1] = yylloc; + + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse lookahead token after an + error, discard it. */ + + if (yychar <= YYEOF) + { + /* Return failure if at end of input. */ + if (yychar == YYEOF) + YYABORT; + } + else + { + yydestruct ("Error: discarding", + yytoken, &yylval, &yylloc); + yychar = YYEMPTY; + } + } + + /* Else will try to reuse lookahead token after shifting the error + token. */ + goto yyerrlab1; + + +/*---------------------------------------------------. +| yyerrorlab -- error raised explicitly by YYERROR. | +`---------------------------------------------------*/ +yyerrorlab: + + /* Pacify compilers like GCC when the user code never invokes + YYERROR and the label yyerrorlab therefore never appears in user + code. */ + if (/*CONSTCOND*/ 0) + goto yyerrorlab; + + yyerror_range[1] = yylsp[1-yylen]; + /* Do not reclaim the symbols of the rule whose action triggered + this YYERROR. */ + YYPOPSTACK (yylen); + yylen = 0; + YY_STACK_PRINT (yyss, yyssp); + yystate = *yyssp; + goto yyerrlab1; + + +/*-------------------------------------------------------------. +| yyerrlab1 -- common code for both syntax error and YYERROR. | +`-------------------------------------------------------------*/ +yyerrlab1: + yyerrstatus = 3; /* Each real token shifted decrements this. */ + + for (;;) + { + yyn = yypact[yystate]; + if (!yypact_value_is_default (yyn)) + { + yyn += YYTERROR; + if (0 <= yyn && yyn <= YYLAST && yycheck[yyn] == YYTERROR) + { + yyn = yytable[yyn]; + if (0 < yyn) + break; + } + } + + /* Pop the current state because it cannot handle the error token. */ + if (yyssp == yyss) + YYABORT; + + yyerror_range[1] = *yylsp; + yydestruct ("Error: popping", + yystos[yystate], yyvsp, yylsp); + YYPOPSTACK (1); + yystate = *yyssp; + YY_STACK_PRINT (yyss, yyssp); + } + + YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN + *++yyvsp = yylval; + YY_IGNORE_MAYBE_UNINITIALIZED_END + + yyerror_range[2] = yylloc; + /* Using YYLLOC is tempting, but would change the location of + the lookahead. YYLOC is available though. */ + YYLLOC_DEFAULT (yyloc, yyerror_range, 2); + *++yylsp = yyloc; + + /* Shift the error token. */ + YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp); + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +#if !defined yyoverflow || YYERROR_VERBOSE +/*-------------------------------------------------. +| yyexhaustedlab -- memory exhaustion comes here. | +`-------------------------------------------------*/ +yyexhaustedlab: + yyerror (YY_("memory exhausted")); + yyresult = 2; + /* Fall through. */ +#endif + +yyreturn: + if (yychar != YYEMPTY) + { + /* Make sure we have latest lookahead translation. See comments at + user semantic actions for why this is necessary. */ + yytoken = YYTRANSLATE (yychar); + yydestruct ("Cleanup: discarding lookahead", + yytoken, &yylval, &yylloc); + } + /* Do not reclaim the symbols of the rule whose action triggered + this YYABORT or YYACCEPT. */ + YYPOPSTACK (yylen); + YY_STACK_PRINT (yyss, yyssp); + while (yyssp != yyss) + { + yydestruct ("Cleanup: popping", + yystos[*yyssp], yyvsp, yylsp); + YYPOPSTACK (1); + } +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif +#if YYERROR_VERBOSE + if (yymsg != yymsgbuf) + YYSTACK_FREE (yymsg); +#endif + return yyresult; +} +#line 542 "lwin_wkt_parse.y" /* yacc.c:1906 */ + + diff --git a/mgist-postgis/liblwgeom/lwin_wkt_parse.h b/mgist-postgis/liblwgeom/lwin_wkt_parse.h new file mode 100644 index 0000000..eeb89b0 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwin_wkt_parse.h @@ -0,0 +1,115 @@ +/* A Bison parser, made by GNU Bison 3.0.4. */ + +/* Bison interface for Yacc-like parsers in C + + Copyright (C) 1984, 1989-1990, 2000-2015 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* As a special exception, you may create a larger work that contains + part or all of the Bison parser skeleton and distribute that work + under terms of your choice, so long as that work isn't itself a + parser generator using the skeleton or a modified version thereof + as a parser skeleton. Alternatively, if you modify or redistribute + the parser skeleton itself, you may (at your option) remove this + special exception, which will cause the skeleton and the resulting + Bison output files to be licensed under the GNU General Public + License without this special exception. + + This special exception was added by the Free Software Foundation in + version 2.2 of Bison. */ + +#ifndef YY_WKT_YY_LWIN_WKT_PARSE_H_INCLUDED +# define YY_WKT_YY_LWIN_WKT_PARSE_H_INCLUDED +/* Debug traces. */ +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif +#if YYDEBUG +extern int wkt_yydebug; +#endif + +/* Token type. */ +#ifndef YYTOKENTYPE +# define YYTOKENTYPE + enum yytokentype + { + POINT_TOK = 258, + LINESTRING_TOK = 259, + POLYGON_TOK = 260, + MPOINT_TOK = 261, + MLINESTRING_TOK = 262, + MPOLYGON_TOK = 263, + MSURFACE_TOK = 264, + MCURVE_TOK = 265, + CURVEPOLYGON_TOK = 266, + COMPOUNDCURVE_TOK = 267, + CIRCULARSTRING_TOK = 268, + COLLECTION_TOK = 269, + RBRACKET_TOK = 270, + LBRACKET_TOK = 271, + COMMA_TOK = 272, + EMPTY_TOK = 273, + SEMICOLON_TOK = 274, + TRIANGLE_TOK = 275, + TIN_TOK = 276, + POLYHEDRALSURFACE_TOK = 277, + DOUBLE_TOK = 278, + DIMENSIONALITY_TOK = 279, + SRID_TOK = 280 + }; +#endif + +/* Value type. */ +#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED + +union YYSTYPE +{ +#line 112 "lwin_wkt_parse.y" /* yacc.c:1909 */ + + int integervalue; + double doublevalue; + char *stringvalue; + LWGEOM *geometryvalue; + POINT coordinatevalue; + POINTARRAY *ptarrayvalue; + +#line 89 "lwin_wkt_parse.h" /* yacc.c:1909 */ +}; + +typedef union YYSTYPE YYSTYPE; +# define YYSTYPE_IS_TRIVIAL 1 +# define YYSTYPE_IS_DECLARED 1 +#endif + +/* Location type. */ +#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED +typedef struct YYLTYPE YYLTYPE; +struct YYLTYPE +{ + int first_line; + int first_column; + int last_line; + int last_column; +}; +# define YYLTYPE_IS_DECLARED 1 +# define YYLTYPE_IS_TRIVIAL 1 +#endif + + +extern YYSTYPE wkt_yylval; +extern YYLTYPE wkt_yylloc; +int wkt_yyparse (void); + +#endif /* !YY_WKT_YY_LWIN_WKT_PARSE_H_INCLUDED */ diff --git a/mgist-postgis/liblwgeom/lwin_wkt_parse.y b/mgist-postgis/liblwgeom/lwin_wkt_parse.y new file mode 100644 index 0000000..5dfe5bd --- /dev/null +++ b/mgist-postgis/liblwgeom/lwin_wkt_parse.y @@ -0,0 +1,543 @@ +%{ + +/* WKT Parser */ +#include +#include +#include +#include "lwin_wkt.h" +#include "lwin_wkt_parse.h" +#include "lwgeom_log.h" + + +/* Prototypes to quiet the compiler */ +int wkt_yyparse(void); +void wkt_yyerror(const char *str); +int wkt_yylex(void); + + +/* Declare the global parser variable */ +LWGEOM_PARSER_RESULT global_parser_result; + +/* Turn on/off verbose parsing (turn off for production) */ +int wkt_yydebug = 0; + +/* +* Error handler called by the bison parser. Mostly we will be +* catching our own errors and filling out the message and errlocation +* from WKT_ERROR in the grammar, but we keep this one +* around just in case. +*/ +void wkt_yyerror(__attribute__((__unused__)) const char *str) +{ + /* If we haven't already set a message and location, let's set one now. */ + if ( ! global_parser_result.message ) + { + global_parser_result.message = parser_error_messages[PARSER_ERROR_OTHER]; + global_parser_result.errcode = PARSER_ERROR_OTHER; + global_parser_result.errlocation = wkt_yylloc.last_column; + } + LWDEBUGF(4,"%s", str); +} + +/** +* Parse a WKT geometry string into an LWGEOM structure. Note that this +* process uses globals and is not re-entrant, so don't call it within itself +* (eg, from within other functions in lwin_wkt.c) or from a threaded program. +* Note that parser_result.wkinput picks up a reference to wktstr. +*/ +int lwgeom_parse_wkt(LWGEOM_PARSER_RESULT *parser_result, char *wktstr, int parser_check_flags) +{ + int parse_rv = 0; + + /* Clean up our global parser result. */ + lwgeom_parser_result_init(&global_parser_result); + /* Work-around possible bug in GNU Bison 3.0.2 resulting in wkt_yylloc + * members not being initialized on yyparse() as documented here: + * https://www.gnu.org/software/bison/manual/html_node/Location-Type.html + * See discussion here: + * http://lists.osgeo.org/pipermail/postgis-devel/2014-September/024506.html + */ + wkt_yylloc.last_column = wkt_yylloc.last_line = \ + wkt_yylloc.first_column = wkt_yylloc.first_line = 1; + + /* Set the input text string, and parse checks. */ + global_parser_result.wkinput = wktstr; + global_parser_result.parser_check_flags = parser_check_flags; + + wkt_lexer_init(wktstr); /* Lexer ready */ + parse_rv = wkt_yyparse(); /* Run the parse */ + LWDEBUGF(4,"wkt_yyparse returned %d", parse_rv); + wkt_lexer_close(); /* Clean up lexer */ + + /* A non-zero parser return is an error. */ + if ( parse_rv || global_parser_result.errcode ) + { + if( ! global_parser_result.errcode ) + { + global_parser_result.errcode = PARSER_ERROR_OTHER; + global_parser_result.message = parser_error_messages[PARSER_ERROR_OTHER]; + global_parser_result.errlocation = wkt_yylloc.last_column; + } + else if (global_parser_result.geom) + { + lwgeom_free(global_parser_result.geom); + global_parser_result.geom = NULL; + } + + LWDEBUGF(5, "error returned by wkt_yyparse() @ %d: [%d] '%s'", + global_parser_result.errlocation, + global_parser_result.errcode, + global_parser_result.message); + + /* Copy the global values into the return pointer */ + *parser_result = global_parser_result; + wkt_yylex_destroy(); + return LW_FAILURE; + } + + /* Copy the global value into the return pointer */ + *parser_result = global_parser_result; + wkt_yylex_destroy(); + return LW_SUCCESS; +} + +#define WKT_ERROR() { if ( global_parser_result.errcode != 0 ) { YYERROR; } } + + +%} + +%locations +%define parse.error verbose + +%union { + int integervalue; + double doublevalue; + char *stringvalue; + LWGEOM *geometryvalue; + POINT coordinatevalue; + POINTARRAY *ptarrayvalue; +} + +%token POINT_TOK LINESTRING_TOK POLYGON_TOK +%token MPOINT_TOK MLINESTRING_TOK MPOLYGON_TOK +%token MSURFACE_TOK MCURVE_TOK CURVEPOLYGON_TOK COMPOUNDCURVE_TOK CIRCULARSTRING_TOK +%token COLLECTION_TOK +%token RBRACKET_TOK LBRACKET_TOK COMMA_TOK EMPTY_TOK +%token SEMICOLON_TOK +%token TRIANGLE_TOK TIN_TOK +%token POLYHEDRALSURFACE_TOK + +%token DOUBLE_TOK +%token DIMENSIONALITY_TOK +%token SRID_TOK + +%type ring +%type patchring +%type ptarray +%type coordinate +%type circularstring +%type compoundcurve +%type compound_list +%type curve_list +%type curvepolygon +%type curvering +%type curvering_list +%type geometry +%type geometry_no_srid +%type geometry_list +%type geometrycollection +%type linestring +%type linestring_list +%type linestring_untagged +%type multicurve +%type multilinestring +%type multipoint +%type multipolygon +%type multisurface +%type patch +%type patch_list +%type patchring_list +%type point +%type point_list +%type point_untagged +%type polygon +%type polygon_list +%type polygon_untagged +%type polyhedralsurface +%type ring_list +%type surface_list +%type tin +%type triangle +%type triangle_list +%type triangle_untagged + + +/* These clean up memory on errors and parser aborts. */ +%destructor { ptarray_free($$); } ptarray +%destructor { ptarray_free($$); } ring +%destructor { ptarray_free($$); } patchring +%destructor { lwgeom_free($$); } curvering_list +%destructor { lwgeom_free($$); } triangle_list +%destructor { lwgeom_free($$); } surface_list +%destructor { lwgeom_free($$); } polygon_list +%destructor { lwgeom_free($$); } patch_list +%destructor { lwgeom_free($$); } point_list +%destructor { lwgeom_free($$); } linestring_list +%destructor { lwgeom_free($$); } curve_list +%destructor { lwgeom_free($$); } compound_list +%destructor { lwgeom_free($$); } ring_list +%destructor { lwgeom_free($$); } patchring_list +%destructor { lwgeom_free($$); } circularstring +%destructor { lwgeom_free($$); } compoundcurve +%destructor { lwgeom_free($$); } curvepolygon +%destructor { lwgeom_free($$); } curvering +%destructor { lwgeom_free($$); } geometry_no_srid +%destructor { lwgeom_free($$); } geometrycollection +%destructor { lwgeom_free($$); } geometry_list +%destructor { lwgeom_free($$); } linestring +%destructor { lwgeom_free($$); } linestring_untagged +%destructor { lwgeom_free($$); } multicurve +%destructor { lwgeom_free($$); } multilinestring +%destructor { lwgeom_free($$); } multipoint +%destructor { lwgeom_free($$); } multipolygon +%destructor { lwgeom_free($$); } multisurface +%destructor { lwgeom_free($$); } point +%destructor { lwgeom_free($$); } point_untagged +%destructor { lwgeom_free($$); } polygon +%destructor { lwgeom_free($$); } patch +%destructor { lwgeom_free($$); } polygon_untagged +%destructor { lwgeom_free($$); } polyhedralsurface +%destructor { lwgeom_free($$); } tin +%destructor { lwgeom_free($$); } triangle +%destructor { lwgeom_free($$); } triangle_untagged + +%% + +geometry: + geometry_no_srid + { wkt_parser_geometry_new($1, SRID_UNKNOWN); WKT_ERROR(); } | + SRID_TOK SEMICOLON_TOK geometry_no_srid + { wkt_parser_geometry_new($3, $1); WKT_ERROR(); } ; + +geometry_no_srid : + point { $$ = $1; } | + linestring { $$ = $1; } | + circularstring { $$ = $1; } | + compoundcurve { $$ = $1; } | + polygon { $$ = $1; } | + curvepolygon { $$ = $1; } | + multipoint { $$ = $1; } | + multilinestring { $$ = $1; } | + multipolygon { $$ = $1; } | + multisurface { $$ = $1; } | + multicurve { $$ = $1; } | + tin { $$ = $1; } | + polyhedralsurface { $$ = $1; } | + triangle { $$ = $1; } | + geometrycollection { $$ = $1; } ; + +geometrycollection : + COLLECTION_TOK LBRACKET_TOK geometry_list RBRACKET_TOK + { $$ = wkt_parser_collection_finalize(COLLECTIONTYPE, $3, NULL); WKT_ERROR(); } | + COLLECTION_TOK DIMENSIONALITY_TOK LBRACKET_TOK geometry_list RBRACKET_TOK + { $$ = wkt_parser_collection_finalize(COLLECTIONTYPE, $4, $2); WKT_ERROR(); } | + COLLECTION_TOK DIMENSIONALITY_TOK EMPTY_TOK + { $$ = wkt_parser_collection_finalize(COLLECTIONTYPE, NULL, $2); WKT_ERROR(); } | + COLLECTION_TOK EMPTY_TOK + { $$ = wkt_parser_collection_finalize(COLLECTIONTYPE, NULL, NULL); WKT_ERROR(); } ; + +geometry_list : + geometry_list COMMA_TOK geometry_no_srid + { $$ = wkt_parser_collection_add_geom($1,$3); WKT_ERROR(); } | + geometry_no_srid + { $$ = wkt_parser_collection_new($1); WKT_ERROR(); } ; + +multisurface : + MSURFACE_TOK LBRACKET_TOK surface_list RBRACKET_TOK + { $$ = wkt_parser_collection_finalize(MULTISURFACETYPE, $3, NULL); WKT_ERROR(); } | + MSURFACE_TOK DIMENSIONALITY_TOK LBRACKET_TOK surface_list RBRACKET_TOK + { $$ = wkt_parser_collection_finalize(MULTISURFACETYPE, $4, $2); WKT_ERROR(); } | + MSURFACE_TOK DIMENSIONALITY_TOK EMPTY_TOK + { $$ = wkt_parser_collection_finalize(MULTISURFACETYPE, NULL, $2); WKT_ERROR(); } | + MSURFACE_TOK EMPTY_TOK + { $$ = wkt_parser_collection_finalize(MULTISURFACETYPE, NULL, NULL); WKT_ERROR(); } ; + +surface_list : + surface_list COMMA_TOK polygon + { $$ = wkt_parser_collection_add_geom($1,$3); WKT_ERROR(); } | + surface_list COMMA_TOK curvepolygon + { $$ = wkt_parser_collection_add_geom($1,$3); WKT_ERROR(); } | + surface_list COMMA_TOK polygon_untagged + { $$ = wkt_parser_collection_add_geom($1,$3); WKT_ERROR(); } | + polygon + { $$ = wkt_parser_collection_new($1); WKT_ERROR(); } | + curvepolygon + { $$ = wkt_parser_collection_new($1); WKT_ERROR(); } | + polygon_untagged + { $$ = wkt_parser_collection_new($1); WKT_ERROR(); } ; + +tin : + TIN_TOK LBRACKET_TOK triangle_list RBRACKET_TOK + { $$ = wkt_parser_collection_finalize(TINTYPE, $3, NULL); WKT_ERROR(); } | + TIN_TOK DIMENSIONALITY_TOK LBRACKET_TOK triangle_list RBRACKET_TOK + { $$ = wkt_parser_collection_finalize(TINTYPE, $4, $2); WKT_ERROR(); } | + TIN_TOK DIMENSIONALITY_TOK EMPTY_TOK + { $$ = wkt_parser_collection_finalize(TINTYPE, NULL, $2); WKT_ERROR(); } | + TIN_TOK EMPTY_TOK + { $$ = wkt_parser_collection_finalize(TINTYPE, NULL, NULL); WKT_ERROR(); } ; + +polyhedralsurface : + POLYHEDRALSURFACE_TOK LBRACKET_TOK patch_list RBRACKET_TOK + { $$ = wkt_parser_collection_finalize(POLYHEDRALSURFACETYPE, $3, NULL); WKT_ERROR(); } | + POLYHEDRALSURFACE_TOK DIMENSIONALITY_TOK LBRACKET_TOK patch_list RBRACKET_TOK + { $$ = wkt_parser_collection_finalize(POLYHEDRALSURFACETYPE, $4, $2); WKT_ERROR(); } | + POLYHEDRALSURFACE_TOK DIMENSIONALITY_TOK EMPTY_TOK + { $$ = wkt_parser_collection_finalize(POLYHEDRALSURFACETYPE, NULL, $2); WKT_ERROR(); } | + POLYHEDRALSURFACE_TOK EMPTY_TOK + { $$ = wkt_parser_collection_finalize(POLYHEDRALSURFACETYPE, NULL, NULL); WKT_ERROR(); } ; + +multipolygon : + MPOLYGON_TOK LBRACKET_TOK polygon_list RBRACKET_TOK + { $$ = wkt_parser_collection_finalize(MULTIPOLYGONTYPE, $3, NULL); WKT_ERROR(); } | + MPOLYGON_TOK DIMENSIONALITY_TOK LBRACKET_TOK polygon_list RBRACKET_TOK + { $$ = wkt_parser_collection_finalize(MULTIPOLYGONTYPE, $4, $2); WKT_ERROR(); } | + MPOLYGON_TOK DIMENSIONALITY_TOK EMPTY_TOK + { $$ = wkt_parser_collection_finalize(MULTIPOLYGONTYPE, NULL, $2); WKT_ERROR(); } | + MPOLYGON_TOK EMPTY_TOK + { $$ = wkt_parser_collection_finalize(MULTIPOLYGONTYPE, NULL, NULL); WKT_ERROR(); } ; + +polygon_list : + polygon_list COMMA_TOK polygon_untagged + { $$ = wkt_parser_collection_add_geom($1,$3); WKT_ERROR(); } | + polygon_untagged + { $$ = wkt_parser_collection_new($1); WKT_ERROR(); } ; + +patch_list : + patch_list COMMA_TOK patch + { $$ = wkt_parser_collection_add_geom($1,$3); WKT_ERROR(); } | + patch + { $$ = wkt_parser_collection_new($1); WKT_ERROR(); } ; + +polygon : + POLYGON_TOK LBRACKET_TOK ring_list RBRACKET_TOK + { $$ = wkt_parser_polygon_finalize($3, NULL); WKT_ERROR(); } | + POLYGON_TOK DIMENSIONALITY_TOK LBRACKET_TOK ring_list RBRACKET_TOK + { $$ = wkt_parser_polygon_finalize($4, $2); WKT_ERROR(); } | + POLYGON_TOK DIMENSIONALITY_TOK EMPTY_TOK + { $$ = wkt_parser_polygon_finalize(NULL, $2); WKT_ERROR(); } | + POLYGON_TOK EMPTY_TOK + { $$ = wkt_parser_polygon_finalize(NULL, NULL); WKT_ERROR(); } ; + +polygon_untagged : + LBRACKET_TOK ring_list RBRACKET_TOK + { $$ = $2; } | + EMPTY_TOK + { $$ = wkt_parser_polygon_finalize(NULL, NULL); WKT_ERROR(); }; + +patch : + LBRACKET_TOK patchring_list RBRACKET_TOK { $$ = $2; } ; + +curvepolygon : + CURVEPOLYGON_TOK LBRACKET_TOK curvering_list RBRACKET_TOK + { $$ = wkt_parser_curvepolygon_finalize($3, NULL); WKT_ERROR(); } | + CURVEPOLYGON_TOK DIMENSIONALITY_TOK LBRACKET_TOK curvering_list RBRACKET_TOK + { $$ = wkt_parser_curvepolygon_finalize($4, $2); WKT_ERROR(); } | + CURVEPOLYGON_TOK DIMENSIONALITY_TOK EMPTY_TOK + { $$ = wkt_parser_curvepolygon_finalize(NULL, $2); WKT_ERROR(); } | + CURVEPOLYGON_TOK EMPTY_TOK + { $$ = wkt_parser_curvepolygon_finalize(NULL, NULL); WKT_ERROR(); } ; + +curvering_list : + curvering_list COMMA_TOK curvering + { $$ = wkt_parser_curvepolygon_add_ring($1,$3); WKT_ERROR(); } | + curvering + { $$ = wkt_parser_curvepolygon_new($1); WKT_ERROR(); } ; + +curvering : + linestring_untagged { $$ = $1; } | + linestring { $$ = $1; } | + compoundcurve { $$ = $1; } | + circularstring { $$ = $1; } ; + +patchring_list : + patchring_list COMMA_TOK patchring + { $$ = wkt_parser_polygon_add_ring($1,$3,'Z'); WKT_ERROR(); } | + patchring + { $$ = wkt_parser_polygon_new($1,'Z'); WKT_ERROR(); } ; + +ring_list : + ring_list COMMA_TOK ring + { $$ = wkt_parser_polygon_add_ring($1,$3,'2'); WKT_ERROR(); } | + ring + { $$ = wkt_parser_polygon_new($1,'2'); WKT_ERROR(); } ; + +patchring : + LBRACKET_TOK ptarray RBRACKET_TOK { $$ = $2; } ; + +ring : + LBRACKET_TOK ptarray RBRACKET_TOK { $$ = $2; } ; + +compoundcurve : + COMPOUNDCURVE_TOK LBRACKET_TOK compound_list RBRACKET_TOK + { $$ = wkt_parser_collection_finalize(COMPOUNDTYPE, $3, NULL); WKT_ERROR(); } | + COMPOUNDCURVE_TOK DIMENSIONALITY_TOK LBRACKET_TOK compound_list RBRACKET_TOK + { $$ = wkt_parser_collection_finalize(COMPOUNDTYPE, $4, $2); WKT_ERROR(); } | + COMPOUNDCURVE_TOK DIMENSIONALITY_TOK EMPTY_TOK + { $$ = wkt_parser_collection_finalize(COMPOUNDTYPE, NULL, $2); WKT_ERROR(); } | + COMPOUNDCURVE_TOK EMPTY_TOK + { $$ = wkt_parser_collection_finalize(COMPOUNDTYPE, NULL, NULL); WKT_ERROR(); } ; + +compound_list : + compound_list COMMA_TOK circularstring + { $$ = wkt_parser_compound_add_geom($1,$3); WKT_ERROR(); } | + compound_list COMMA_TOK linestring + { $$ = wkt_parser_compound_add_geom($1,$3); WKT_ERROR(); } | + compound_list COMMA_TOK linestring_untagged + { $$ = wkt_parser_compound_add_geom($1,$3); WKT_ERROR(); } | + circularstring + { $$ = wkt_parser_compound_new($1); WKT_ERROR(); } | + linestring + { $$ = wkt_parser_compound_new($1); WKT_ERROR(); } | + linestring_untagged + { $$ = wkt_parser_compound_new($1); WKT_ERROR(); } ; + +multicurve : + MCURVE_TOK LBRACKET_TOK curve_list RBRACKET_TOK + { $$ = wkt_parser_collection_finalize(MULTICURVETYPE, $3, NULL); WKT_ERROR(); } | + MCURVE_TOK DIMENSIONALITY_TOK LBRACKET_TOK curve_list RBRACKET_TOK + { $$ = wkt_parser_collection_finalize(MULTICURVETYPE, $4, $2); WKT_ERROR(); } | + MCURVE_TOK DIMENSIONALITY_TOK EMPTY_TOK + { $$ = wkt_parser_collection_finalize(MULTICURVETYPE, NULL, $2); WKT_ERROR(); } | + MCURVE_TOK EMPTY_TOK + { $$ = wkt_parser_collection_finalize(MULTICURVETYPE, NULL, NULL); WKT_ERROR(); } ; + +curve_list : + curve_list COMMA_TOK circularstring + { $$ = wkt_parser_collection_add_geom($1,$3); WKT_ERROR(); } | + curve_list COMMA_TOK compoundcurve + { $$ = wkt_parser_collection_add_geom($1,$3); WKT_ERROR(); } | + curve_list COMMA_TOK linestring + { $$ = wkt_parser_collection_add_geom($1,$3); WKT_ERROR(); } | + curve_list COMMA_TOK linestring_untagged + { $$ = wkt_parser_collection_add_geom($1,$3); WKT_ERROR(); } | + circularstring + { $$ = wkt_parser_collection_new($1); WKT_ERROR(); } | + compoundcurve + { $$ = wkt_parser_collection_new($1); WKT_ERROR(); } | + linestring + { $$ = wkt_parser_collection_new($1); WKT_ERROR(); } | + linestring_untagged + { $$ = wkt_parser_collection_new($1); WKT_ERROR(); } ; + +multilinestring : + MLINESTRING_TOK LBRACKET_TOK linestring_list RBRACKET_TOK + { $$ = wkt_parser_collection_finalize(MULTILINETYPE, $3, NULL); WKT_ERROR(); } | + MLINESTRING_TOK DIMENSIONALITY_TOK LBRACKET_TOK linestring_list RBRACKET_TOK + { $$ = wkt_parser_collection_finalize(MULTILINETYPE, $4, $2); WKT_ERROR(); } | + MLINESTRING_TOK DIMENSIONALITY_TOK EMPTY_TOK + { $$ = wkt_parser_collection_finalize(MULTILINETYPE, NULL, $2); WKT_ERROR(); } | + MLINESTRING_TOK EMPTY_TOK + { $$ = wkt_parser_collection_finalize(MULTILINETYPE, NULL, NULL); WKT_ERROR(); } ; + +linestring_list : + linestring_list COMMA_TOK linestring_untagged + { $$ = wkt_parser_collection_add_geom($1,$3); WKT_ERROR(); } | + linestring_untagged + { $$ = wkt_parser_collection_new($1); WKT_ERROR(); } ; + +circularstring : + CIRCULARSTRING_TOK LBRACKET_TOK ptarray RBRACKET_TOK + { $$ = wkt_parser_circularstring_new($3, NULL); WKT_ERROR(); } | + CIRCULARSTRING_TOK DIMENSIONALITY_TOK LBRACKET_TOK ptarray RBRACKET_TOK + { $$ = wkt_parser_circularstring_new($4, $2); WKT_ERROR(); } | + CIRCULARSTRING_TOK DIMENSIONALITY_TOK EMPTY_TOK + { $$ = wkt_parser_circularstring_new(NULL, $2); WKT_ERROR(); } | + CIRCULARSTRING_TOK EMPTY_TOK + { $$ = wkt_parser_circularstring_new(NULL, NULL); WKT_ERROR(); } ; + +linestring : + LINESTRING_TOK LBRACKET_TOK ptarray RBRACKET_TOK + { $$ = wkt_parser_linestring_new($3, NULL); WKT_ERROR(); } | + LINESTRING_TOK DIMENSIONALITY_TOK LBRACKET_TOK ptarray RBRACKET_TOK + { $$ = wkt_parser_linestring_new($4, $2); WKT_ERROR(); } | + LINESTRING_TOK DIMENSIONALITY_TOK EMPTY_TOK + { $$ = wkt_parser_linestring_new(NULL, $2); WKT_ERROR(); } | + LINESTRING_TOK EMPTY_TOK + { $$ = wkt_parser_linestring_new(NULL, NULL); WKT_ERROR(); } ; + +linestring_untagged : + LBRACKET_TOK ptarray RBRACKET_TOK + { $$ = wkt_parser_linestring_new($2, NULL); WKT_ERROR(); } | + EMPTY_TOK + { $$ = wkt_parser_linestring_new(NULL, NULL); WKT_ERROR(); }; + +triangle_list : + triangle_list COMMA_TOK triangle_untagged + { $$ = wkt_parser_collection_add_geom($1,$3); WKT_ERROR(); } | + triangle_untagged + { $$ = wkt_parser_collection_new($1); WKT_ERROR(); } ; + +triangle : + TRIANGLE_TOK LBRACKET_TOK LBRACKET_TOK ptarray RBRACKET_TOK RBRACKET_TOK + { $$ = wkt_parser_triangle_new($4, NULL); WKT_ERROR(); } | + TRIANGLE_TOK DIMENSIONALITY_TOK LBRACKET_TOK LBRACKET_TOK ptarray RBRACKET_TOK RBRACKET_TOK + { $$ = wkt_parser_triangle_new($5, $2); WKT_ERROR(); } | + TRIANGLE_TOK DIMENSIONALITY_TOK EMPTY_TOK + { $$ = wkt_parser_triangle_new(NULL, $2); WKT_ERROR(); } | + TRIANGLE_TOK EMPTY_TOK + { $$ = wkt_parser_triangle_new(NULL, NULL); WKT_ERROR(); } ; + +triangle_untagged : + LBRACKET_TOK LBRACKET_TOK ptarray RBRACKET_TOK RBRACKET_TOK + { $$ = wkt_parser_triangle_new($3, NULL); WKT_ERROR(); } ; + +multipoint : + MPOINT_TOK LBRACKET_TOK point_list RBRACKET_TOK + { $$ = wkt_parser_collection_finalize(MULTIPOINTTYPE, $3, NULL); WKT_ERROR(); } | + MPOINT_TOK DIMENSIONALITY_TOK LBRACKET_TOK point_list RBRACKET_TOK + { $$ = wkt_parser_collection_finalize(MULTIPOINTTYPE, $4, $2); WKT_ERROR(); } | + MPOINT_TOK DIMENSIONALITY_TOK EMPTY_TOK + { $$ = wkt_parser_collection_finalize(MULTIPOINTTYPE, NULL, $2); WKT_ERROR(); } | + MPOINT_TOK EMPTY_TOK + { $$ = wkt_parser_collection_finalize(MULTIPOINTTYPE, NULL, NULL); WKT_ERROR(); } ; + +point_list : + point_list COMMA_TOK point_untagged + { $$ = wkt_parser_collection_add_geom($1,$3); WKT_ERROR(); } | + point_untagged + { $$ = wkt_parser_collection_new($1); WKT_ERROR(); } ; + +point_untagged : + coordinate + { $$ = wkt_parser_point_new(wkt_parser_ptarray_new($1),NULL); WKT_ERROR(); } | + LBRACKET_TOK coordinate RBRACKET_TOK + { $$ = wkt_parser_point_new(wkt_parser_ptarray_new($2),NULL); WKT_ERROR(); } | + EMPTY_TOK + { $$ = wkt_parser_point_new(NULL, NULL); WKT_ERROR(); }; + +point : + POINT_TOK LBRACKET_TOK ptarray RBRACKET_TOK + { $$ = wkt_parser_point_new($3, NULL); WKT_ERROR(); } | + POINT_TOK DIMENSIONALITY_TOK LBRACKET_TOK ptarray RBRACKET_TOK + { $$ = wkt_parser_point_new($4, $2); WKT_ERROR(); } | + POINT_TOK DIMENSIONALITY_TOK EMPTY_TOK + { $$ = wkt_parser_point_new(NULL, $2); WKT_ERROR(); } | + POINT_TOK EMPTY_TOK + { $$ = wkt_parser_point_new(NULL,NULL); WKT_ERROR(); } ; + +ptarray : + ptarray COMMA_TOK coordinate + { $$ = wkt_parser_ptarray_add_coord($1, $3); WKT_ERROR(); } | + coordinate + { $$ = wkt_parser_ptarray_new($1); WKT_ERROR(); } ; + +coordinate : + DOUBLE_TOK DOUBLE_TOK + { $$ = wkt_parser_coord_2($1, $2); WKT_ERROR(); } | + DOUBLE_TOK DOUBLE_TOK DOUBLE_TOK + { $$ = wkt_parser_coord_3($1, $2, $3); WKT_ERROR(); } | + DOUBLE_TOK DOUBLE_TOK DOUBLE_TOK DOUBLE_TOK + { $$ = wkt_parser_coord_4($1, $2, $3, $4); WKT_ERROR(); } ; + +%% + diff --git a/mgist-postgis/liblwgeom/lwinline.h b/mgist-postgis/liblwgeom/lwinline.h new file mode 100644 index 0000000..47e6aae --- /dev/null +++ b/mgist-postgis/liblwgeom/lwinline.h @@ -0,0 +1,349 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2018 Darafei Praliaskouski + * Copyright 2017-2018 Daniel Baston + * Copyright 2011 Sandro Santilli + * Copyright 2011 Paul Ramsey + * Copyright 2007-2008 Mark Cave-Ayland + * Copyright 2001-2006 Refractions Research Inc. + * + **********************************************************************/ + +#if PARANOIA_LEVEL > 0 +#include +#endif + +inline static double +distance2d_sqr_pt_pt(const POINT2D *p1, const POINT2D *p2) +{ + double hside = p2->x - p1->x; + double vside = p2->y - p1->y; + + return hside * hside + vside * vside; +} + +inline static double +distance3d_sqr_pt_pt(const POINT3D *p1, const POINT3D *p2) +{ + double hside = p2->x - p1->x; + double vside = p2->y - p1->y; + double zside = p2->z - p1->z; + + return hside * hside + vside * vside + zside * zside; +} + +/* + * Size of point represeneted in the POINTARRAY + * 16 for 2d, 24 for 3d, 32 for 4d + */ +static inline size_t +ptarray_point_size(const POINTARRAY *pa) +{ + return sizeof(double) * FLAGS_NDIMS(pa->flags); +} + +/* + * Get a pointer to Nth point of a POINTARRAY + * You'll need to cast it to appropriate dimensioned point. + * Note that if you cast to a higher dimensional point you'll + * possibly corrupt the POINTARRAY. + * + * Casting to returned pointer to POINT2D* should be safe, + * as gserialized format always keeps the POINTARRAY pointer + * aligned to double boundary. + * + * WARNING: Don't cast this to a POINT! + * it would not be reliable due to memory alignment constraints + */ +static inline uint8_t * +getPoint_internal(const POINTARRAY *pa, uint32_t n) +{ + size_t size; + uint8_t *ptr; + +#if PARANOIA_LEVEL > 0 + assert(pa); + assert(n <= pa->npoints); + assert(n <= pa->maxpoints); +#endif + + size = ptarray_point_size(pa); + ptr = pa->serialized_pointlist + size * n; + + return ptr; +} + +/** + * Returns a POINT2D pointer into the POINTARRAY serialized_ptlist, + * suitable for reading from. This is very high performance + * and declared const because you aren't allowed to muck with the + * values, only read them. + */ +static inline const POINT2D * +getPoint2d_cp(const POINTARRAY *pa, uint32_t n) +{ + return (const POINT2D *)getPoint_internal(pa, n); +} + +/** + * Returns a POINT3D pointer into the POINTARRAY serialized_ptlist, + * suitable for reading from. This is very high performance + * and declared const because you aren't allowed to muck with the + * values, only read them. + */ +static inline const POINT3D * +getPoint3d_cp(const POINTARRAY *pa, uint32_t n) +{ + return (const POINT3D *)getPoint_internal(pa, n); +} + +/** + * Returns a POINT4D pointer into the POINTARRAY serialized_ptlist, + * suitable for reading from. This is very high performance + * and declared const because you aren't allowed to muck with the + * values, only read them. + */ +static inline const POINT4D * +getPoint4d_cp(const POINTARRAY *pa, uint32_t n) +{ + return (const POINT4D *)getPoint_internal(pa, n); +} + +static inline LWPOINT * +lwgeom_as_lwpoint(const LWGEOM *lwgeom) +{ + if (!lwgeom) + return NULL; + if (lwgeom->type == POINTTYPE) + return (LWPOINT *)lwgeom; + else + return NULL; +} + +/** + * Return LWTYPE number + */ +static inline uint32_t +lwgeom_get_type(const LWGEOM *geom) +{ + if (!geom) + return 0; + return geom->type; +} + +static inline int +lwpoint_is_empty(const LWPOINT *point) +{ + return !point->point || point->point->npoints < 1; +} + +static inline int +lwline_is_empty(const LWLINE *line) +{ + return !line->points || line->points->npoints < 1; +} + +static inline int +lwcircstring_is_empty(const LWCIRCSTRING *circ) +{ + return !circ->points || circ->points->npoints < 1; +} + +static inline int +lwpoly_is_empty(const LWPOLY *poly) +{ + return poly->nrings < 1 || !poly->rings || !poly->rings[0] || poly->rings[0]->npoints < 1; +} + +static inline int +lwtriangle_is_empty(const LWTRIANGLE *triangle) +{ + return !triangle->points || triangle->points->npoints < 1; +} + +static inline int lwgeom_is_empty(const LWGEOM *geom); + +static inline int +lwcollection_is_empty(const LWCOLLECTION *col) +{ + uint32_t i; + if (col->ngeoms == 0 || !col->geoms) + return LW_TRUE; + for (i = 0; i < col->ngeoms; i++) + { + if (!lwgeom_is_empty(col->geoms[i])) + return LW_FALSE; + } + return LW_TRUE; +} + +/** + * Return true or false depending on whether a geometry is an "empty" + * geometry (no vertices members) + */ +static inline int +lwgeom_is_empty(const LWGEOM *geom) +{ + switch (geom->type) + { + case POINTTYPE: + return lwpoint_is_empty((LWPOINT *)geom); + break; + case LINETYPE: + return lwline_is_empty((LWLINE *)geom); + break; + case CIRCSTRINGTYPE: + return lwcircstring_is_empty((LWCIRCSTRING *)geom); + break; + case POLYGONTYPE: + return lwpoly_is_empty((LWPOLY *)geom); + break; + case TRIANGLETYPE: + return lwtriangle_is_empty((LWTRIANGLE *)geom); + break; + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COMPOUNDTYPE: + case CURVEPOLYTYPE: + case MULTICURVETYPE: + case MULTISURFACETYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + return lwcollection_is_empty((LWCOLLECTION *)geom); + break; + default: + return LW_FALSE; + break; + } +} + +inline static uint64_t +uint64_interleave_2(uint64_t x, uint64_t y) +{ + x = (x | (x << 16)) & 0x0000FFFF0000FFFFULL; + x = (x | (x << 8)) & 0x00FF00FF00FF00FFULL; + x = (x | (x << 4)) & 0x0F0F0F0F0F0F0F0FULL; + x = (x | (x << 2)) & 0x3333333333333333ULL; + x = (x | (x << 1)) & 0x5555555555555555ULL; + + y = (y | (y << 16)) & 0x0000FFFF0000FFFFULL; + y = (y | (y << 8)) & 0x00FF00FF00FF00FFULL; + y = (y | (y << 4)) & 0x0F0F0F0F0F0F0F0FULL; + y = (y | (y << 2)) & 0x3333333333333333ULL; + y = (y | (y << 1)) & 0x5555555555555555ULL; + + return x | (y << 1); +} + +/* Based on https://github.com/rawrunprotected/hilbert_curves Public Domain code */ +inline static uint64_t +uint32_hilbert(uint32_t px, uint32_t py) +{ + uint64_t x = px; + uint64_t y = py; + + uint64_t A, B, C, D; + uint64_t a, b, c, d; + uint64_t i0, i1; + + // Initial prefix scan round, prime with x and y + { + a = x ^ y; + b = 0xFFFFFFFFULL ^ a; + c = 0xFFFFFFFFULL ^ (x | y); + d = x & (y ^ 0xFFFFFFFFULL); + + A = a | (b >> 1); + B = (a >> 1) ^ a; + C = ((c >> 1) ^ (b & (d >> 1))) ^ c; + D = ((a & (c >> 1)) ^ (d >> 1)) ^ d; + } + + { + a = A; + b = B; + c = C; + d = D; + + A = ((a & (a >> 2)) ^ (b & (b >> 2))); + B = ((a & (b >> 2)) ^ (b & ((a ^ b) >> 2))); + C ^= ((a & (c >> 2)) ^ (b & (d >> 2))); + D ^= ((b & (c >> 2)) ^ ((a ^ b) & (d >> 2))); + } + + { + a = A; + b = B; + c = C; + d = D; + + A = ((a & (a >> 4)) ^ (b & (b >> 4))); + B = ((a & (b >> 4)) ^ (b & ((a ^ b) >> 4))); + C ^= ((a & (c >> 4)) ^ (b & (d >> 4))); + D ^= ((b & (c >> 4)) ^ ((a ^ b) & (d >> 4))); + } + + { + a = A; + b = B; + c = C; + d = D; + + A = ((a & (a >> 8)) ^ (b & (b >> 8))); + B = ((a & (b >> 8)) ^ (b & ((a ^ b) >> 8))); + C ^= ((a & (c >> 8)) ^ (b & (d >> 8))); + D ^= ((b & (c >> 8)) ^ ((a ^ b) & (d >> 8))); + } + + { + a = A; + b = B; + c = C; + d = D; + + C ^= ((a & (c >> 16)) ^ (b & (d >> 16))); + D ^= ((b & (c >> 16)) ^ ((a ^ b) & (d >> 16))); + } + + // Undo transformation prefix scan + a = C ^ (C >> 1); + b = D ^ (D >> 1); + + // Recover index bits + i0 = x ^ y; + i1 = b | (0xFFFFFFFFULL ^ (i0 | a)); + + return uint64_interleave_2(i0, i1); +} + +/* + * This macro is based on PG_FREE_IF_COPY, except that it accepts two pointers. + * See PG_FREE_IF_COPY comment in src/include/fmgr.h in postgres source code + * for more details. + */ +#define POSTGIS_FREE_IF_COPY_P(ptrsrc, ptrori) \ + do \ + { \ + if ((Pointer)(ptrsrc) != (Pointer)(ptrori)) \ + pfree(ptrsrc); \ + } while (0) diff --git a/mgist-postgis/liblwgeom/lwiterator.c b/mgist-postgis/liblwgeom/lwiterator.c new file mode 100644 index 0000000..a825239 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwiterator.c @@ -0,0 +1,280 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2015 Daniel Baston + * + **********************************************************************/ + + +#include "liblwgeom.h" +#include "lwgeom_log.h" + +struct LISTNODE +{ + struct LISTNODE* next; + void* item; +}; +typedef struct LISTNODE LISTNODE; + +/* The LWPOINTITERATOR consists of two stacks of items to process: a stack + * of geometries, and a stack of POINTARRAYs extracted from those geometries. + * The index "i" refers to the "next" point, which is found at the top of the + * pointarrays stack. + * + * When the pointarrays stack is depleted, we pull a geometry from the geometry + * stack to replenish it. + */ +struct LWPOINTITERATOR +{ + LISTNODE* geoms; + LISTNODE* pointarrays; + uint32_t i; + char allow_modification; +}; + +static LISTNODE* +prepend_node(void* g, LISTNODE* front) +{ + LISTNODE* n = lwalloc(sizeof(LISTNODE)); + n->item = g; + n->next = front; + + return n; +} + +static LISTNODE* +pop_node(LISTNODE* i) +{ + LISTNODE* next = i->next; + lwfree(i); + return next; +} + +static int +add_lwgeom_to_stack(LWPOINTITERATOR* s, LWGEOM* g) +{ + if (lwgeom_is_empty(g)) + return LW_FAILURE; + + s->geoms = prepend_node(g, s->geoms); + return LW_SUCCESS; +} + +/** Return a pointer to the first of one or more LISTNODEs holding the POINTARRAYs + * of a geometry. Will not handle GeometryCollections. + */ +static LISTNODE* +extract_pointarrays_from_lwgeom(LWGEOM* g) +{ + switch(lwgeom_get_type(g)) + { + case POINTTYPE: + return prepend_node(lwgeom_as_lwpoint(g)->point, NULL); + case LINETYPE: + return prepend_node(lwgeom_as_lwline(g)->points, NULL); + case TRIANGLETYPE: + return prepend_node(lwgeom_as_lwtriangle(g)->points, NULL); + case CIRCSTRINGTYPE: + return prepend_node(lwgeom_as_lwcircstring(g)->points, NULL); + case POLYGONTYPE: + { + LISTNODE* n = NULL; + + LWPOLY* p = lwgeom_as_lwpoly(g); + int i; + for (i = p->nrings - 1; i >= 0; i--) + n = prepend_node(p->rings[i], n); + + return n; + } + default: + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(g->type)); + } + + return NULL; +} + +/** Remove an LWCOLLECTION from the iterator stack, and add the components of the + * LWCOLLECTIONs to the stack. + */ +static void +unroll_collection(LWPOINTITERATOR* s) +{ + int i; + LWCOLLECTION* c; + + if (!s->geoms) + { + return; + } + + c = (LWCOLLECTION*) s->geoms->item; + s->geoms = pop_node(s->geoms); + + for (i = c->ngeoms - 1; i >= 0; i--) + { + LWGEOM* g = lwcollection_getsubgeom(c, i); + + add_lwgeom_to_stack(s, g); + } +} + +/** Unroll LWCOLLECTIONs from the top of the stack, as necessary, until the element at the + * top of the stack is not a LWCOLLECTION. + */ +static void +unroll_collections(LWPOINTITERATOR* s) +{ + while(s->geoms && lwgeom_is_collection(s->geoms->item)) + { + unroll_collection(s); + } +} + +static int +lwpointiterator_advance(LWPOINTITERATOR* s) +{ + s->i += 1; + + /* We've reached the end of our current POINTARRAY. Try to see if there + * are any more POINTARRAYS on the stack. */ + if (s->pointarrays && s->i >= ((POINTARRAY*) s->pointarrays->item)->npoints) + { + s->pointarrays = pop_node(s->pointarrays); + s->i = 0; + } + + /* We don't have a current POINTARRAY. Pull a geometry from the stack, and + * decompose it into its POINTARRARYs. */ + if (!s->pointarrays) + { + LWGEOM* g; + unroll_collections(s); + + if (!s->geoms) + { + return LW_FAILURE; + } + + s->i = 0; + g = s->geoms->item; + s->pointarrays = extract_pointarrays_from_lwgeom(g); + + s->geoms = pop_node(s->geoms); + } + + if (!s->pointarrays) + { + return LW_FAILURE; + } + return LW_SUCCESS; +} + +/* Public API implementation */ + +int +lwpointiterator_peek(LWPOINTITERATOR* s, POINT4D* p) +{ + if (!lwpointiterator_has_next(s)) + return LW_FAILURE; + + return getPoint4d_p(s->pointarrays->item, s->i, p); +} + +int +lwpointiterator_has_next(LWPOINTITERATOR* s) +{ + if (s->pointarrays && s->i < ((POINTARRAY*) s->pointarrays->item)->npoints) + return LW_TRUE; + return LW_FALSE; +} + +int +lwpointiterator_next(LWPOINTITERATOR* s, POINT4D* p) +{ + if (!lwpointiterator_has_next(s)) + return LW_FAILURE; + + /* If p is NULL, just advance without reading */ + if (p && !lwpointiterator_peek(s, p)) + return LW_FAILURE; + + lwpointiterator_advance(s); + return LW_SUCCESS; +} + +int +lwpointiterator_modify_next(LWPOINTITERATOR* s, const POINT4D* p) +{ + if (!lwpointiterator_has_next(s)) + return LW_FAILURE; + + if (!s->allow_modification) + { + lwerror("Cannot write to read-only iterator"); + return LW_FAILURE; + } + + ptarray_set_point4d(s->pointarrays->item, s->i, p); + + lwpointiterator_advance(s); + return LW_SUCCESS; +} + +LWPOINTITERATOR* +lwpointiterator_create(const LWGEOM* g) +{ + LWPOINTITERATOR* it = lwpointiterator_create_rw((LWGEOM*) g); + it->allow_modification = LW_FALSE; + + return it; +} + +LWPOINTITERATOR* +lwpointiterator_create_rw(LWGEOM* g) +{ + LWPOINTITERATOR* it = lwalloc(sizeof(LWPOINTITERATOR)); + + it->geoms = NULL; + it->pointarrays = NULL; + it->i = 0; + it->allow_modification = LW_TRUE; + + add_lwgeom_to_stack(it, g); + lwpointiterator_advance(it); + + return it; +} + +void +lwpointiterator_destroy(LWPOINTITERATOR* s) +{ + while (s->geoms != NULL) + { + s->geoms = pop_node(s->geoms); + } + + while (s->pointarrays != NULL) + { + s->pointarrays = pop_node(s->pointarrays); + } + + lwfree(s); +} diff --git a/mgist-postgis/liblwgeom/lwkmeans.c b/mgist-postgis/liblwgeom/lwkmeans.c new file mode 100644 index 0000000..88319b5 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwkmeans.c @@ -0,0 +1,453 @@ +/*------------------------------------------------------------------------- + * + * Copyright (c) 2018-2021, Darafei Praliaskouski + * Copyright (c) 2016, Paul Ramsey + * + *------------------------------------------------------------------------*/ + +#include "liblwgeom_internal.h" + +/* + * When clustering lists with NULL or EMPTY elements, they will get this as + * their cluster number. (All the other clusters will be non-negative) + */ +#define KMEANS_NULL_CLUSTER -1 + +/* + * If the algorithm doesn't converge within this number of iterations, + * it will return with a failure error code. + */ +#define KMEANS_MAX_ITERATIONS 1000 + +static uint32_t kmeans(POINT4D *objs, + uint32_t *clusters, + uint32_t n, + POINT4D *centers, + double *radii, + uint32_t min_k, + double max_radius); + +inline static double +distance3d_sqr_pt4d_pt4d(const POINT4D *p1, const POINT4D *p2) +{ + double hside = p2->x - p1->x; + double vside = p2->y - p1->y; + double zside = p2->z - p1->z; + + return hside * hside + vside * vside + zside * zside; +} + +/* Split the clusters that need to be split */ +static uint32_t +improve_structure(POINT4D *objs, + uint32_t *clusters, + uint32_t n, + POINT4D *centers, + double *radii, + uint32_t k, + double max_radius) +{ + /* Input check: radius limit should be measurable */ + if (max_radius <= 0) + return k; + + double max_radius_sq = max_radius * max_radius; + + /* Do we have the big clusters to split at all? */ + uint32_t first_cluster_to_split = 0; + for (; first_cluster_to_split < k; first_cluster_to_split++) + if (radii[first_cluster_to_split] > max_radius_sq) + break; + if (first_cluster_to_split == k) + return k; + + POINT4D *temp_objs = lwalloc(sizeof(POINT4D) * n); + uint32_t *temp_clusters = lwalloc(sizeof(uint32_t) * n); + double *temp_radii = lwalloc(sizeof(double) * n); + POINT4D *temp_centers = lwalloc(sizeof(POINT4D) * n); + + uint32_t new_k = k; + + for (uint32_t cluster = first_cluster_to_split; cluster < k; cluster++) + { + if (radii[cluster] <= max_radius_sq) + continue; + + /* copy cluster alone */ + uint32_t cluster_size = 0; + for (uint32_t i = 0; i < n; i++) + if (clusters[i] == cluster) + temp_objs[cluster_size++] = objs[i]; + if (cluster_size <= 1) + continue; + + /* run 2-means on the cluster */ + kmeans(temp_objs, temp_clusters, cluster_size, temp_centers, temp_radii, 2, 0); + + /* replace cluster with split */ + uint32_t d = 0; + for (uint32_t i = 0; i < n; i++) + if (clusters[i] == cluster) + if (temp_clusters[d++]) + clusters[i] = new_k; + + centers[cluster] = temp_centers[0]; + centers[new_k] = temp_centers[1]; + radii[cluster] = temp_radii[0]; + radii[new_k] = temp_radii[1]; + new_k++; + } + lwfree(temp_centers); + lwfree(temp_radii); + lwfree(temp_clusters); + lwfree(temp_objs); + return new_k; +} + +/* Refresh mapping of point to closest cluster */ +static uint8_t +update_r(POINT4D *objs, uint32_t *clusters, uint32_t n, POINT4D *centers, double *radii, uint32_t k) +{ + uint8_t converged = LW_TRUE; + if (radii) + memset(radii, 0, sizeof(double) * k); + + for (uint32_t i = 0; i < n; i++) + { + POINT4D obj = objs[i]; + + /* Initialize with distance to first cluster */ + double curr_distance = distance3d_sqr_pt4d_pt4d(&obj, ¢ers[0]); + uint32_t curr_cluster = 0; + + /* Check all other cluster centers and find the nearest */ + for (uint32_t cluster = 1; cluster < k; cluster++) + { + double distance = distance3d_sqr_pt4d_pt4d(&obj, ¢ers[cluster]); + if (distance < curr_distance) + { + curr_distance = distance; + curr_cluster = cluster; + } + } + + /* Store the nearest cluster this object is in */ + if (clusters[i] != curr_cluster) + { + converged = LW_FALSE; + clusters[i] = curr_cluster; + } + if (radii) + if (radii[curr_cluster] < curr_distance) + radii[curr_cluster] = curr_distance; + } + return converged; +} + +/* Refresh cluster centroids based on all of their objects */ +static void +update_means(POINT4D *objs, uint32_t *clusters, uint32_t n, POINT4D *centers, uint32_t k) +{ + memset(centers, 0, sizeof(POINT4D) * k); + /* calculate weighted sum */ + for (uint32_t i = 0; i < n; i++) + { + uint32_t cluster = clusters[i]; + centers[cluster].x += objs[i].x * objs[i].m; + centers[cluster].y += objs[i].y * objs[i].m; + centers[cluster].z += objs[i].z * objs[i].m; + centers[cluster].m += objs[i].m; + } + /* divide by weight to get average */ + for (uint32_t i = 0; i < k; i++) + { + if (centers[i].m) + { + centers[i].x /= centers[i].m; + centers[i].y /= centers[i].m; + centers[i].z /= centers[i].m; + } + } +} + +/* Assign initial clusters centroids heuristically */ +static void +kmeans_init(POINT4D *objs, uint32_t n, POINT4D *centers, uint32_t k) +{ + double *distances; + uint32_t p1 = 0, p2 = 0; + uint32_t duplicate_count = 1; /* a point is a duplicate of itself */ + double max_dst = -1; + + /* k=0, k=1: any point will do */ + assert(n > 0); + if (k < 2) + { + centers[0] = objs[0]; + return; + } + + /* k >= 2: find two distant points greedily */ + for (uint32_t i = 1; i < n; i++) + { + /* if we found a larger distance, replace our choice */ + double dst_p1 = distance3d_sqr_pt4d_pt4d(&objs[i], &objs[p1]); + double dst_p2 = distance3d_sqr_pt4d_pt4d(&objs[i], &objs[p2]); + if ((dst_p1 > max_dst) || (dst_p2 > max_dst)) + { + if (dst_p1 > dst_p2) + { + max_dst = dst_p1; + p2 = i; + } + else + { + max_dst = dst_p2; + p1 = i; + } + } + if ((dst_p1 == 0) || (dst_p2 == 0)) + duplicate_count++; + } + if (duplicate_count > 1) + lwnotice( + "%s: there are at least %u duplicate inputs, number of output clusters may be less than you requested", + __func__, + duplicate_count); + + /* by now two points should be found and non-same */ + assert(p1 != p2 && max_dst >= 0); + + /* accept these two points */ + centers[0] = objs[p1]; + centers[1] = objs[p2]; + + if (k > 2) + { + /* array of minimum distance to a point from accepted cluster centers */ + distances = lwalloc(sizeof(double) * n); + + /* initialize array with distance to first object */ + for (uint32_t j = 0; j < n; j++) + distances[j] = distance3d_sqr_pt4d_pt4d(&objs[j], ¢ers[0]); + distances[p1] = -1; + distances[p2] = -1; + + /* loop i on clusters, skip 0 and 1 as found already */ + for (uint32_t i = 2; i < k; i++) + { + uint32_t candidate_center = 0; + double max_distance = -DBL_MAX; + + /* loop j on objs */ + for (uint32_t j = 0; j < n; j++) + { + /* empty objs and accepted clusters are already marked with distance = -1 */ + if (distances[j] < 0) + continue; + + /* update minimal distance with previosuly accepted cluster */ + double current_distance = distance3d_sqr_pt4d_pt4d(&objs[j], ¢ers[i - 1]); + if (current_distance < distances[j]) + distances[j] = current_distance; + + /* greedily take a point that's farthest from any of accepted clusters */ + if (distances[j] > max_distance) + { + candidate_center = j; + max_distance = distances[j]; + } + } + + /* Checked earlier by counting entries on input, just in case */ + assert(max_distance >= 0); + + /* accept candidate to centers */ + distances[candidate_center] = -1; + /* Copy the point coordinates into the initial centers array + * Centers array is an array of pointers to points, not an array of points */ + centers[i] = objs[candidate_center]; + } + lwfree(distances); + } +} + +static uint32_t +kmeans(POINT4D *objs, + uint32_t *clusters, + uint32_t n, + POINT4D *centers, + double *radii, + uint32_t min_k, + double max_radius) +{ + uint8_t converged = LW_FALSE; + uint32_t cur_k = min_k; + + kmeans_init(objs, n, centers, cur_k); + /* One iteration of kmeans needs to happen without shortcuts to fully initialize structures */ + update_r(objs, clusters, n, centers, radii, cur_k); + update_means(objs, clusters, n, centers, cur_k); + for (uint32_t t = 0; t < KMEANS_MAX_ITERATIONS; t++) + { + /* Standard KMeans loop */ + for (uint32_t i = 0; i < KMEANS_MAX_ITERATIONS; i++) + { + LW_ON_INTERRUPT(break); + converged = update_r(objs, clusters, n, centers, radii, cur_k); + if (converged) + break; + update_means(objs, clusters, n, centers, cur_k); + } + if (!converged || !max_radius) + break; + + /* XMeans-inspired improve_structure pass to split clusters bigger than limit into 2 */ + uint32_t new_k = improve_structure(objs, clusters, n, centers, radii, cur_k, max_radius); + if (new_k == cur_k) + break; + cur_k = new_k; + } + + if (!converged) + { + lwerror("%s did not converge after %d iterations", __func__, KMEANS_MAX_ITERATIONS); + return 0; + } + return cur_k; +} + +int * +lwgeom_cluster_kmeans(const LWGEOM **geoms, uint32_t n, uint32_t k, double max_radius) +{ + uint32_t num_non_empty = 0; + + assert(k > 0); + assert(n > 0); + assert(max_radius >= 0); + assert(geoms); + + if (n < k) + { + lwerror( + "%s: number of geometries is less than the number of clusters requested, not all clusters will get data", + __func__); + k = n; + } + + /* An array of objects to be analyzed. */ + POINT4D *objs_dense = lwalloc(sizeof(POINT4D) * n); + + /* Array to mark unclusterable objects. Will be returned as KMEANS_NULL_CLUSTER. */ + uint8_t *geom_valid = lwalloc(sizeof(uint8_t) * n); + memset(geom_valid, 0, sizeof(uint8_t) * n); + + /* Array to fill in with cluster numbers. */ + int *clusters = lwalloc(sizeof(int) * n); + for (uint32_t i = 0; i < n; i++) + clusters[i] = KMEANS_NULL_CLUSTER; + + /* An array of clusters centers for the algorithm. */ + POINT4D *centers = lwalloc(sizeof(POINT4D) * n); + memset(centers, 0, sizeof(POINT4D) * n); + + /* An array of clusters radii for the algorithm. */ + double *radii = lwalloc(sizeof(double) * n); + memset(radii, 0, sizeof(double) * n); + + /* Prepare the list of object pointers for K-means */ + for (uint32_t i = 0; i < n; i++) + { + const LWGEOM *geom = geoms[i]; + /* Unset M values will be 1 */ + POINT4D out = {0, 0, 0, 1}; + + /* Null/empty geometries get geom_valid=LW_FALSE set earlier with memset */ + if ((!geom) || lwgeom_is_empty(geom)) + continue; + + /* If the input is a point, use its coordinates */ + if (lwgeom_get_type(geom) == POINTTYPE) + { + out.x = lwpoint_get_x(lwgeom_as_lwpoint(geom)); + out.y = lwpoint_get_y(lwgeom_as_lwpoint(geom)); + if (lwgeom_has_z(geom)) + out.z = lwpoint_get_z(lwgeom_as_lwpoint(geom)); + if (lwgeom_has_m(geom)) + { + out.m = lwpoint_get_m(lwgeom_as_lwpoint(geom)); + if (out.m <= 0) + lwerror("%s has an input point geometry with weight in M less or equal to 0", + __func__); + } + } + else if (!lwgeom_has_z(geom)) + { + /* For 2D, we can take a centroid */ + LWGEOM *centroid = lwgeom_centroid(geom); + if (!centroid) + continue; + if (lwgeom_is_empty(centroid)) + { + lwgeom_free(centroid); + continue; + } + out.x = lwpoint_get_x(lwgeom_as_lwpoint(centroid)); + out.y = lwpoint_get_y(lwgeom_as_lwpoint(centroid)); + lwgeom_free(centroid); + } + else + { + /* For 3D non-point, we can have a box center */ + const GBOX *box = lwgeom_get_bbox(geom); + if (!gbox_is_valid(box)) + continue; + out.x = (box->xmax + box->xmin) / 2; + out.y = (box->ymax + box->ymin) / 2; + out.z = (box->zmax + box->zmin) / 2; + } + geom_valid[i] = LW_TRUE; + objs_dense[num_non_empty++] = out; + } + + if (num_non_empty < k) + { + lwnotice( + "%s: number of non-empty geometries (%d) is less than the number of clusters (%d) requested, not all clusters will get data", + __func__, + num_non_empty, + k); + k = num_non_empty; + } + + uint8_t converged = LW_TRUE; + + if (num_non_empty > 0) + { + uint32_t *clusters_dense = lwalloc(sizeof(uint32_t) * num_non_empty); + memset(clusters_dense, 0, sizeof(uint32_t) * num_non_empty); + uint32_t output_cluster_count = kmeans(objs_dense, clusters_dense, num_non_empty, centers, radii, k, max_radius); + + uint32_t d = 0; + for (uint32_t i = 0; i < n; i++) + if (geom_valid[i]) + clusters[i] = (int)clusters_dense[d++]; + + converged = output_cluster_count > 0; + lwfree(clusters_dense); + } + + /* Before error handling, might as well clean up all the inputs */ + lwfree(objs_dense); + lwfree(centers); + lwfree(geom_valid); + lwfree(radii); + + /* Good result */ + if (converged) + return clusters; + + /* Bad result, not going to need the answer */ + lwfree(clusters); + return NULL; +} diff --git a/mgist-postgis/liblwgeom/lwline.c b/mgist-postgis/liblwgeom/lwline.c new file mode 100644 index 0000000..936d950 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwline.c @@ -0,0 +1,739 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2012 Sandro Santilli + * Copyright (C) 2001-2006 Refractions Research Inc. + * + **********************************************************************/ + + +/* basic LWLINE functions */ + +#include +#include +#include +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + + + +/* + * Construct a new LWLINE. points will *NOT* be copied + * use SRID=SRID_UNKNOWN for unknown SRID (will have 8bit type's S = 0) + */ +LWLINE * +lwline_construct(int32_t srid, GBOX *bbox, POINTARRAY *points) +{ + LWLINE *result = (LWLINE *)lwalloc(sizeof(LWLINE)); + result->type = LINETYPE; + result->flags = points->flags; + FLAGS_SET_BBOX(result->flags, bbox?1:0); + result->srid = srid; + result->points = points; + result->bbox = bbox; + return result; +} + +LWLINE * +lwline_construct_empty(int32_t srid, char hasz, char hasm) +{ + LWLINE *result = lwalloc(sizeof(LWLINE)); + result->type = LINETYPE; + result->flags = lwflags(hasz,hasm,0); + result->srid = srid; + result->points = ptarray_construct_empty(hasz, hasm, 1); + result->bbox = NULL; + return result; +} + + +void lwline_free (LWLINE *line) +{ + if ( ! line ) return; + + if ( line->bbox ) + lwfree(line->bbox); + if ( line->points ) + ptarray_free(line->points); + lwfree(line); +} + + +void printLWLINE(LWLINE *line) +{ + lwnotice("LWLINE {"); + lwnotice(" ndims = %i", (int)FLAGS_NDIMS(line->flags)); + lwnotice(" srid = %i", (int)line->srid); + printPA(line->points); + lwnotice("}"); +} + +/* @brief Clone LWLINE object. Serialized point lists are not copied. + * + * @see ptarray_clone + */ +LWLINE * +lwline_clone(const LWLINE *g) +{ + LWLINE *ret = lwalloc(sizeof(LWLINE)); + + LWDEBUGF(2, "lwline_clone called with %p", g); + + memcpy(ret, g, sizeof(LWLINE)); + + ret->points = ptarray_clone(g->points); + + if ( g->bbox ) ret->bbox = gbox_copy(g->bbox); + return ret; +} + +/* Deep clone LWLINE object. POINTARRAY *is* copied. */ +LWLINE * +lwline_clone_deep(const LWLINE *g) +{ + LWLINE *ret = lwalloc(sizeof(LWLINE)); + + LWDEBUGF(2, "lwline_clone_deep called with %p", g); + memcpy(ret, g, sizeof(LWLINE)); + + if ( g->bbox ) ret->bbox = gbox_copy(g->bbox); + if ( g->points ) ret->points = ptarray_clone_deep(g->points); + FLAGS_SET_READONLY(ret->flags,0); + + return ret; +} + + +void +lwline_release(LWLINE *lwline) +{ + lwgeom_release(lwline_as_lwgeom(lwline)); +} + + +LWLINE * +lwline_segmentize2d(const LWLINE *line, double dist) +{ + POINTARRAY *segmentized = ptarray_segmentize2d(line->points, dist); + if ( ! segmentized ) return NULL; + return lwline_construct(line->srid, NULL, segmentized); +} + +/* check coordinate equality */ +char +lwline_same(const LWLINE *l1, const LWLINE *l2) +{ + return ptarray_same(l1->points, l2->points); +} + +/* + * Construct a LWLINE from an array of point and line geometries + * LWLINE dimensions are large enough to host all input dimensions. + */ +LWLINE * +lwline_from_lwgeom_array(int32_t srid, uint32_t ngeoms, LWGEOM **geoms) +{ + uint32_t i; + int hasz = LW_FALSE; + int hasm = LW_FALSE; + POINTARRAY *pa; + LWLINE *line; + POINT4D pt; + LWPOINTITERATOR* it; + + /* + * Find output dimensions, check integrity + */ + for (i=0; iflags) ) hasz = LW_TRUE; + if ( FLAGS_GET_M(geoms[i]->flags) ) hasm = LW_TRUE; + if ( hasz && hasm ) break; /* Nothing more to learn! */ + } + + /* + * ngeoms should be a guess about how many points we have in input. + * It's an underestimate for lines and multipoints */ + pa = ptarray_construct_empty(hasz, hasm, ngeoms); + + for ( i=0; i < ngeoms; i++ ) + { + LWGEOM *g = geoms[i]; + + if ( lwgeom_is_empty(g) ) continue; + + if ( g->type == POINTTYPE ) + { + lwpoint_getPoint4d_p((LWPOINT*)g, &pt); + ptarray_append_point(pa, &pt, LW_TRUE); + } + else if ( g->type == LINETYPE ) + { + /* + * Append the new line points, de-duplicating against the previous points. + * Duplicated points internal to the linestring are untouched. + */ + ptarray_append_ptarray(pa, ((LWLINE*)g)->points, -1); + } + else if ( g->type == MULTIPOINTTYPE ) + { + it = lwpointiterator_create(g); + while(lwpointiterator_next(it, &pt)) + { + ptarray_append_point(pa, &pt, LW_TRUE); + } + lwpointiterator_destroy(it); + } + else + { + ptarray_free(pa); + lwerror("lwline_from_ptarray: invalid input type: %s", lwtype_name(g->type)); + return NULL; + } + } + + if ( pa->npoints > 0 ) + line = lwline_construct(srid, NULL, pa); + else { + /* Is this really any different from the above ? */ + ptarray_free(pa); + line = lwline_construct_empty(srid, hasz, hasm); + } + + return line; +} + +/* + * Construct a LWLINE from an array of LWPOINTs + * LWLINE dimensions are large enough to host all input dimensions. + */ +LWLINE * +lwline_from_ptarray(int32_t srid, uint32_t npoints, LWPOINT **points) +{ + uint32_t i; + int hasz = LW_FALSE; + int hasm = LW_FALSE; + POINTARRAY *pa; + LWLINE *line; + POINT4D pt; + + /* + * Find output dimensions, check integrity + */ + for (i=0; itype != POINTTYPE ) + { + lwerror("lwline_from_ptarray: invalid input type: %s", lwtype_name(points[i]->type)); + return NULL; + } + if ( FLAGS_GET_Z(points[i]->flags) ) hasz = LW_TRUE; + if ( FLAGS_GET_M(points[i]->flags) ) hasm = LW_TRUE; + if ( hasz && hasm ) break; /* Nothing more to learn! */ + } + + pa = ptarray_construct_empty(hasz, hasm, npoints); + + for ( i=0; i < npoints; i++ ) + { + if ( ! lwpoint_is_empty(points[i]) ) + { + lwpoint_getPoint4d_p(points[i], &pt); + ptarray_append_point(pa, &pt, LW_TRUE); + } + } + + if ( pa->npoints > 0 ) + line = lwline_construct(srid, NULL, pa); + else + line = lwline_construct_empty(srid, hasz, hasm); + + return line; +} + +/* + * Construct a LWLINE from a LWMPOINT + */ +LWLINE * +lwline_from_lwmpoint(int32_t srid, const LWMPOINT *mpoint) +{ + uint32_t i; + POINTARRAY *pa = NULL; + LWGEOM *lwgeom = (LWGEOM*)mpoint; + POINT4D pt; + + char hasz = lwgeom_has_z(lwgeom); + char hasm = lwgeom_has_m(lwgeom); + uint32_t npoints = mpoint->ngeoms; + + if ( lwgeom_is_empty(lwgeom) ) + { + return lwline_construct_empty(srid, hasz, hasm); + } + + pa = ptarray_construct(hasz, hasm, npoints); + + for (i=0; i < npoints; i++) + { + getPoint4d_p(mpoint->geoms[i]->point, 0, &pt); + ptarray_set_point4d(pa, i, &pt); + } + + LWDEBUGF(3, "lwline_from_lwmpoint: constructed pointarray for %d points", mpoint->ngeoms); + + return lwline_construct(srid, NULL, pa); +} + +/** +* Returns freshly allocated #LWPOINT that corresponds to the index where. +* Returns NULL if the geometry is empty or the index invalid. +*/ +LWPOINT* +lwline_get_lwpoint(const LWLINE *line, uint32_t where) +{ + POINT4D pt; + LWPOINT *lwpoint; + POINTARRAY *pa; + + if ( lwline_is_empty(line) || where >= line->points->npoints ) + return NULL; + + pa = ptarray_construct_empty(FLAGS_GET_Z(line->flags), FLAGS_GET_M(line->flags), 1); + pt = getPoint4d(line->points, where); + ptarray_append_point(pa, &pt, LW_TRUE); + lwpoint = lwpoint_construct(line->srid, NULL, pa); + return lwpoint; +} + + +int +lwline_add_lwpoint(LWLINE *line, LWPOINT *point, uint32_t where) +{ + POINT4D pt; + getPoint4d_p(point->point, 0, &pt); + + if ( ptarray_insert_point(line->points, &pt, where) != LW_SUCCESS ) + return LW_FAILURE; + + /* Update the bounding box */ + if ( line->bbox ) + { + lwgeom_refresh_bbox((LWGEOM*)line); + } + + return LW_SUCCESS; +} + + + +LWLINE * +lwline_removepoint(LWLINE *line, uint32_t index) +{ + POINTARRAY *newpa; + LWLINE *ret; + + newpa = ptarray_removePoint(line->points, index); + + ret = lwline_construct(line->srid, NULL, newpa); + lwgeom_add_bbox((LWGEOM *) ret); + + return ret; +} + +/* + * Note: input will be changed, make sure you have permissions for this. + */ +void +lwline_setPoint4d(LWLINE *line, uint32_t index, POINT4D *newpoint) +{ + ptarray_set_point4d(line->points, index, newpoint); + /* Update the box, if there is one to update */ + if ( line->bbox ) + { + lwgeom_refresh_bbox((LWGEOM*)line); + } +} + +/** +* Re-write the measure ordinate (or add one, if it isn't already there) interpolating +* the measure between the supplied start and end values. +*/ +LWLINE* +lwline_measured_from_lwline(const LWLINE *lwline, double m_start, double m_end) +{ + int i = 0; + int hasm = 0, hasz = 0; + int npoints = 0; + double length = 0.0; + double length_so_far = 0.0; + double m_range = m_end - m_start; + double m; + POINTARRAY *pa = NULL; + POINT3DZ p1, p2; + + if ( lwline->type != LINETYPE ) + { + lwerror("lwline_construct_from_lwline: only line types supported"); + return NULL; + } + + hasz = FLAGS_GET_Z(lwline->flags); + hasm = 1; + + /* Null points or npoints == 0 will result in empty return geometry */ + if ( lwline->points ) + { + npoints = lwline->points->npoints; + length = ptarray_length_2d(lwline->points); + getPoint3dz_p(lwline->points, 0, &p1); + } + + pa = ptarray_construct(hasz, hasm, npoints); + + for ( i = 0; i < npoints; i++ ) + { + POINT4D q; + POINT2D a, b; + getPoint3dz_p(lwline->points, i, &p2); + a.x = p1.x; + a.y = p1.y; + b.x = p2.x; + b.y = p2.y; + length_so_far += distance2d_pt_pt(&a, &b); + if ( length > 0.0 ) + m = m_start + m_range * length_so_far / length; + /* #3172, support (valid) zero-length inputs */ + else if ( length == 0.0 && npoints > 1 ) + m = m_start + m_range * i / (npoints-1); + else + m = 0.0; + q.x = p2.x; + q.y = p2.y; + q.z = p2.z; + q.m = m; + ptarray_set_point4d(pa, i, &q); + p1 = p2; + } + + return lwline_construct(lwline->srid, NULL, pa); +} + +LWGEOM* +lwline_remove_repeated_points(const LWLINE *lwline, double tolerance) +{ + return lwgeom_remove_repeated_points((LWGEOM*)lwline, tolerance); +} + +int +lwline_is_closed(const LWLINE *line) +{ + if (FLAGS_GET_Z(line->flags)) + return ptarray_is_closed_3d(line->points); + + return ptarray_is_closed_2d(line->points); +} + +int +lwline_is_trajectory(const LWLINE *line) +{ + if (!FLAGS_GET_M(line->flags)) + { + lwnotice("Line does not have M dimension"); + return LW_FALSE; + } + + uint32_t n = line->points->npoints; + + if (n < 2) + return LW_TRUE; /* empty or single-point are "good" */ + + double m = -1 * FLT_MAX; + for (uint32_t i = 0; i < n; ++i) + { + POINT3DM p; + if (!getPoint3dm_p(line->points, i, &p)) + return LW_FALSE; + if (p.m <= m) + { + lwnotice( + "Measure of vertex %d (%g) not bigger than measure of vertex %d (%g)", i, p.m, i - 1, m); + return LW_FALSE; + } + m = p.m; + } + + return LW_TRUE; +} + +LWLINE* +lwline_force_dims(const LWLINE *line, int hasz, int hasm, double zval, double mval) +{ + POINTARRAY *pdims = NULL; + LWLINE *lineout; + + /* Return 2D empty */ + if( lwline_is_empty(line) ) + { + lineout = lwline_construct_empty(line->srid, hasz, hasm); + } + else + { + pdims = ptarray_force_dims(line->points, hasz, hasm, zval, mval); + lineout = lwline_construct(line->srid, NULL, pdims); + } + lineout->type = line->type; + return lineout; +} + +uint32_t lwline_count_vertices(const LWLINE *line) +{ + assert(line); + if ( ! line->points ) + return 0; + return line->points->npoints; +} + +double lwline_length(const LWLINE *line) +{ + if ( lwline_is_empty(line) ) + return 0.0; + return ptarray_length(line->points); +} + +double lwline_length_2d(const LWLINE *line) +{ + if ( lwline_is_empty(line) ) + return 0.0; + return ptarray_length_2d(line->points); +} + + +POINTARRAY* lwline_interpolate_points(const LWLINE *line, double length_fraction, char repeat) { + POINT4D pt; + uint32_t i; + uint32_t points_to_interpolate; + uint32_t points_found = 0; + double length; + double length_fraction_increment = length_fraction; + double length_fraction_consumed = 0; + char has_z = (char) lwgeom_has_z(lwline_as_lwgeom(line)); + char has_m = (char) lwgeom_has_m(lwline_as_lwgeom(line)); + const POINTARRAY* ipa = line->points; + POINTARRAY* opa; + + /* Empty.InterpolatePoint == Point Empty */ + if ( lwline_is_empty(line) ) + { + return ptarray_construct_empty(has_z, has_m, 0); + } + + /* If distance is one of the two extremes, return the point on that + * end rather than doing any computations + */ + if ( length_fraction == 0.0 || length_fraction == 1.0 ) + { + if ( length_fraction == 0.0 ) + getPoint4d_p(ipa, 0, &pt); + else + getPoint4d_p(ipa, ipa->npoints-1, &pt); + + opa = ptarray_construct(has_z, has_m, 1); + ptarray_set_point4d(opa, 0, &pt); + + return opa; + } + + /* Interpolate points along the line */ + length = ptarray_length_2d(ipa); + points_to_interpolate = repeat ? (uint32_t) floor(1 / length_fraction) : 1; + opa = ptarray_construct(has_z, has_m, points_to_interpolate); + + const POINT2D* p1 = getPoint2d_cp(ipa, 0); + for ( i = 0; i < ipa->npoints - 1 && points_found < points_to_interpolate; i++ ) + { + const POINT2D* p2 = getPoint2d_cp(ipa, i+1); + double segment_length_frac = distance2d_pt_pt(p1, p2) / length; + + /* If our target distance is before the total length we've seen + * so far. create a new point some distance down the current + * segment. + */ + while ( length_fraction < length_fraction_consumed + segment_length_frac && points_found < points_to_interpolate ) + { + POINT4D p1_4d = getPoint4d(ipa, i); + POINT4D p2_4d = getPoint4d(ipa, i+1); + + double segment_fraction = (length_fraction - length_fraction_consumed) / segment_length_frac; + interpolate_point4d(&p1_4d, &p2_4d, &pt, segment_fraction); + ptarray_set_point4d(opa, points_found++, &pt); + length_fraction += length_fraction_increment; + } + + length_fraction_consumed += segment_length_frac; + + p1 = p2; + } + + /* Return the last point on the line. This shouldn't happen, but + * could if there's some floating point rounding errors. */ + if (points_found < points_to_interpolate) { + getPoint4d_p(ipa, ipa->npoints - 1, &pt); + ptarray_set_point4d(opa, points_found, &pt); + } + + return opa; +} + +extern LWPOINT * +lwline_interpolate_point_3d(const LWLINE *line, double distance) +{ + double length, slength, tlength; + POINTARRAY *ipa; + POINT4D pt; + int nsegs, i; + LWGEOM *geom = lwline_as_lwgeom(line); + int has_z = lwgeom_has_z(geom); + int has_m = lwgeom_has_m(geom); + ipa = line->points; + + /* Empty.InterpolatePoint == Point Empty */ + if (lwline_is_empty(line)) + { + return lwpoint_construct_empty(line->srid, has_z, has_m); + } + + /* If distance is one of the two extremes, return the point on that + * end rather than doing any expensive computations + */ + if (distance == 0.0 || distance == 1.0) + { + if (distance == 0.0) + getPoint4d_p(ipa, 0, &pt); + else + getPoint4d_p(ipa, ipa->npoints - 1, &pt); + + return lwpoint_make(line->srid, has_z, has_m, &pt); + } + + /* Interpolate a point on the line */ + nsegs = ipa->npoints - 1; + length = ptarray_length(ipa); + tlength = 0; + for (i = 0; i < nsegs; i++) + { + POINT4D p1, p2; + POINT4D *p1ptr = &p1, *p2ptr = &p2; /* don't break + * strict-aliasing rules + */ + + getPoint4d_p(ipa, i, &p1); + getPoint4d_p(ipa, i + 1, &p2); + + /* Find the relative length of this segment */ + slength = distance3d_pt_pt((POINT3D *)p1ptr, (POINT3D *)p2ptr) / length; + + /* If our target distance is before the total length we've seen + * so far. create a new point some distance down the current + * segment. + */ + if (distance < tlength + slength) + { + double dseg = (distance - tlength) / slength; + interpolate_point4d(&p1, &p2, &pt, dseg); + return lwpoint_make(line->srid, has_z, has_m, &pt); + } + tlength += slength; + } + + /* Return the last point on the line. This shouldn't happen, but + * could if there's some floating point rounding errors. */ + getPoint4d_p(ipa, ipa->npoints - 1, &pt); + return lwpoint_make(line->srid, has_z, has_m, &pt); +} + +extern LWLINE * +lwline_extend(const LWLINE *line, double distance_forward, double distance_backward) +{ + POINTARRAY *pa, *opa; + POINT4D p00, p01, p10, p11; + POINT4D p_start, p_end; + uint32_t i; + bool forward = false, backward = false; + + if (distance_forward < 0 || distance_backward < 0) + lwerror("%s: distances must be non-negative", __func__); + + if (!line || lwline_is_empty(line) || lwline_count_vertices(line) < 2) + { + lwerror("%s: line must have at least two points", __func__); + } + + pa = line->points; + if (distance_backward > 0.0) + { + i = 0; + /* Get two distinct points at start of pointarray */ + getPoint4d_p(pa, i++, &p00); + getPoint4d_p(pa, i, &p01); + while(p4d_same(&p00, &p01)) + { + if (i == pa->npoints - 1) + { + lwerror("%s: line must have at least two distinct points", __func__); + } + i++; + getPoint4d_p(pa, i, &p01); + } + project_pt_pt(&p01, &p00, distance_backward, &p_start); + backward = true; + } + + if (distance_forward > 0.0) + { + i = pa->npoints - 1; + /* Get two distinct points at end of pointarray */ + getPoint4d_p(pa, i--, &p10); + getPoint4d_p(pa, i, &p11); + while(p4d_same(&p10, &p11)) + { + if (i == 0) + { + lwerror("%s: line must have at least two distinct points", __func__); + } + i--; + getPoint4d_p(pa, i, &p11); + } + project_pt_pt(&p11, &p10, distance_forward, &p_end); + forward = true; + } + + opa = ptarray_construct_empty(ptarray_has_z(pa), ptarray_has_m(pa), pa->npoints + 2); + + if (backward) + { + ptarray_append_point(opa, &p_start, true); + } + ptarray_append_ptarray(opa, pa, -1.0); + if (forward) + { + ptarray_append_point(opa, &p_end, true); + } + return lwline_construct(line->srid, NULL, opa); +} diff --git a/mgist-postgis/liblwgeom/lwlinearreferencing.c b/mgist-postgis/liblwgeom/lwlinearreferencing.c new file mode 100644 index 0000000..35628bf --- /dev/null +++ b/mgist-postgis/liblwgeom/lwlinearreferencing.c @@ -0,0 +1,1472 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2015 Sandro Santilli + * Copyright (C) 2011 Paul Ramsey + * + **********************************************************************/ + +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" +#include "measures3d.h" + +static int +segment_locate_along(const POINT4D *p1, const POINT4D *p2, double m, double offset, POINT4D *pn) +{ + double m1 = p1->m; + double m2 = p2->m; + double mprop; + + /* M is out of range, no new point generated. */ + if ((m < FP_MIN(m1, m2)) || (m > FP_MAX(m1, m2))) + { + return LW_FALSE; + } + + if (m1 == m2) + { + /* Degenerate case: same M on both points. + If they are the same point we just return one of them. */ + if (p4d_same(p1, p2)) + { + *pn = *p1; + return LW_TRUE; + } + /* If the points are different we split the difference */ + mprop = 0.5; + } + else + { + mprop = (m - m1) / (m2 - m1); + } + + /* M is in range, new point to be generated. */ + pn->x = p1->x + (p2->x - p1->x) * mprop; + pn->y = p1->y + (p2->y - p1->y) * mprop; + pn->z = p1->z + (p2->z - p1->z) * mprop; + pn->m = m; + + /* Offset to the left or right, if necessary. */ + if (offset != 0.0) + { + double theta = atan2(p2->y - p1->y, p2->x - p1->x); + pn->x -= sin(theta) * offset; + pn->y += cos(theta) * offset; + } + + return LW_TRUE; +} + +static POINTARRAY * +ptarray_locate_along(const POINTARRAY *pa, double m, double offset) +{ + uint32_t i; + POINT4D p1, p2, pn; + POINTARRAY *dpa = NULL; + + /* Can't do anything with degenerate point arrays */ + if (!pa || pa->npoints < 2) + return NULL; + + /* Walk through each segment in the point array */ + for (i = 1; i < pa->npoints; i++) + { + getPoint4d_p(pa, i - 1, &p1); + getPoint4d_p(pa, i, &p2); + + /* No derived point? Move to next segment. */ + if (segment_locate_along(&p1, &p2, m, offset, &pn) == LW_FALSE) + continue; + + /* No pointarray, make a fresh one */ + if (dpa == NULL) + dpa = ptarray_construct_empty(ptarray_has_z(pa), ptarray_has_m(pa), 8); + + /* Add our new point to the array */ + ptarray_append_point(dpa, &pn, 0); + } + + return dpa; +} + +static LWMPOINT * +lwline_locate_along(const LWLINE *lwline, double m, double offset) +{ + POINTARRAY *opa = NULL; + LWMPOINT *mp = NULL; + LWGEOM *lwg = lwline_as_lwgeom(lwline); + int hasz, hasm, srid; + + /* Return degenerates upwards */ + if (!lwline) + return NULL; + + /* Create empty return shell */ + srid = lwgeom_get_srid(lwg); + hasz = lwgeom_has_z(lwg); + hasm = lwgeom_has_m(lwg); + + if (hasm) + { + /* Find points along */ + opa = ptarray_locate_along(lwline->points, m, offset); + } + else + { + LWLINE *lwline_measured = lwline_measured_from_lwline(lwline, 0.0, 1.0); + opa = ptarray_locate_along(lwline_measured->points, m, offset); + lwline_free(lwline_measured); + } + + /* Return NULL as EMPTY */ + if (!opa) + return lwmpoint_construct_empty(srid, hasz, hasm); + + /* Convert pointarray into a multipoint */ + mp = lwmpoint_construct(srid, opa); + ptarray_free(opa); + return mp; +} + +static LWMPOINT * +lwmline_locate_along(const LWMLINE *lwmline, double m, double offset) +{ + LWMPOINT *lwmpoint = NULL; + LWGEOM *lwg = lwmline_as_lwgeom(lwmline); + uint32_t i, j; + + /* Return degenerates upwards */ + if ((!lwmline) || (lwmline->ngeoms < 1)) + return NULL; + + /* Construct return */ + lwmpoint = lwmpoint_construct_empty(lwgeom_get_srid(lwg), lwgeom_has_z(lwg), lwgeom_has_m(lwg)); + + /* Locate along each sub-line */ + for (i = 0; i < lwmline->ngeoms; i++) + { + LWMPOINT *along = lwline_locate_along(lwmline->geoms[i], m, offset); + if (along) + { + if (!lwgeom_is_empty((LWGEOM *)along)) + { + for (j = 0; j < along->ngeoms; j++) + { + lwmpoint_add_lwpoint(lwmpoint, along->geoms[j]); + } + } + /* Free the containing geometry, but leave the sub-geometries around */ + along->ngeoms = 0; + lwmpoint_free(along); + } + } + return lwmpoint; +} + +static LWMPOINT * +lwpoint_locate_along(const LWPOINT *lwpoint, double m, __attribute__((__unused__)) double offset) +{ + double point_m = lwpoint_get_m(lwpoint); + LWGEOM *lwg = lwpoint_as_lwgeom(lwpoint); + LWMPOINT *r = lwmpoint_construct_empty(lwgeom_get_srid(lwg), lwgeom_has_z(lwg), lwgeom_has_m(lwg)); + if (FP_EQUALS(m, point_m)) + { + lwmpoint_add_lwpoint(r, lwpoint_clone(lwpoint)); + } + return r; +} + +static LWMPOINT * +lwmpoint_locate_along(const LWMPOINT *lwin, double m, __attribute__((__unused__)) double offset) +{ + LWGEOM *lwg = lwmpoint_as_lwgeom(lwin); + LWMPOINT *lwout = NULL; + uint32_t i; + + /* Construct return */ + lwout = lwmpoint_construct_empty(lwgeom_get_srid(lwg), lwgeom_has_z(lwg), lwgeom_has_m(lwg)); + + for (i = 0; i < lwin->ngeoms; i++) + { + double point_m = lwpoint_get_m(lwin->geoms[i]); + if (FP_EQUALS(m, point_m)) + { + lwmpoint_add_lwpoint(lwout, lwpoint_clone(lwin->geoms[i])); + } + } + + return lwout; +} + +LWGEOM * +lwgeom_locate_along(const LWGEOM *lwin, double m, double offset) +{ + if (!lwin) + return NULL; + + if (!lwgeom_has_m(lwin)) + lwerror("Input geometry does not have a measure dimension"); + + switch (lwin->type) + { + case POINTTYPE: + return (LWGEOM *)lwpoint_locate_along((LWPOINT *)lwin, m, offset); + case MULTIPOINTTYPE: + return (LWGEOM *)lwmpoint_locate_along((LWMPOINT *)lwin, m, offset); + case LINETYPE: + return (LWGEOM *)lwline_locate_along((LWLINE *)lwin, m, offset); + case MULTILINETYPE: + return (LWGEOM *)lwmline_locate_along((LWMLINE *)lwin, m, offset); + /* Only line types supported right now */ + /* TO DO: CurveString, CompoundCurve, MultiCurve */ + /* TO DO: Point, MultiPoint */ + default: + lwerror("Only linear geometries are supported, %s provided.", lwtype_name(lwin->type)); + return NULL; + } + return NULL; +} + +/** + * Given a POINT4D and an ordinate number, return + * the value of the ordinate. + * @param p input point + * @param ordinate number (1=x, 2=y, 3=z, 4=m) + * @return d value at that ordinate + */ +inline double +lwpoint_get_ordinate(const POINT4D *p, char ordinate) +{ + if (!p) + { + lwerror("Null input geometry."); + return 0.0; + } + + switch (ordinate) + { + case 'X': + return p->x; + case 'Y': + return p->y; + case 'Z': + return p->z; + case 'M': + return p->m; + } + lwerror("Cannot extract %c ordinate.", ordinate); + return 0.0; +} + +/** + * Given a point, ordinate number and value, set that ordinate on the + * point. + */ +inline void +lwpoint_set_ordinate(POINT4D *p, char ordinate, double value) +{ + if (!p) + { + lwerror("Null input geometry."); + return; + } + + switch (ordinate) + { + case 'X': + p->x = value; + return; + case 'Y': + p->y = value; + return; + case 'Z': + p->z = value; + return; + case 'M': + p->m = value; + return; + } + lwerror("Cannot set %c ordinate.", ordinate); + return; +} + +/** + * Given two points, a dimensionality, an ordinate, and an interpolation value + * generate a new point that is proportionally between the input points, + * using the values in the provided dimension as the scaling factors. + */ +inline int +point_interpolate(const POINT4D *p1, + const POINT4D *p2, + POINT4D *p, + int hasz, + int hasm, + char ordinate, + double interpolation_value) +{ + static char *dims = "XYZM"; + double p1_value = lwpoint_get_ordinate(p1, ordinate); + double p2_value = lwpoint_get_ordinate(p2, ordinate); + double proportion; + int i = 0; + +#if PARANOIA_LEVEL > 0 + if (!(ordinate == 'X' || ordinate == 'Y' || ordinate == 'Z' || ordinate == 'M')) + { + lwerror("Cannot interpolate over %c ordinate.", ordinate); + return LW_FAILURE; + } + + if (FP_MIN(p1_value, p2_value) > interpolation_value || FP_MAX(p1_value, p2_value) < interpolation_value) + { + lwerror("Cannot interpolate to a value (%g) not between the input points (%g, %g).", + interpolation_value, + p1_value, + p2_value); + return LW_FAILURE; + } +#endif + + proportion = (interpolation_value - p1_value) / (p2_value - p1_value); + + for (i = 0; i < 4; i++) + { + if (dims[i] == 'Z' && !hasz) + continue; + if (dims[i] == 'M' && !hasm) + continue; + if (dims[i] == ordinate) + lwpoint_set_ordinate(p, dims[i], interpolation_value); + else + { + double newordinate = 0.0; + p1_value = lwpoint_get_ordinate(p1, dims[i]); + p2_value = lwpoint_get_ordinate(p2, dims[i]); + newordinate = p1_value + proportion * (p2_value - p1_value); + lwpoint_set_ordinate(p, dims[i], newordinate); + } + } + + return LW_SUCCESS; +} + +/** + * Clip an input POINT between two values, on any ordinate input. + */ +static inline LWCOLLECTION * +lwpoint_clip_to_ordinate_range(const LWPOINT *point, char ordinate, double from, double to) +{ + LWCOLLECTION *lwgeom_out = NULL; + char hasz, hasm; + POINT4D p4d; + double ordinate_value; + + /* Read Z/M info */ + hasz = lwgeom_has_z(lwpoint_as_lwgeom(point)); + hasm = lwgeom_has_m(lwpoint_as_lwgeom(point)); + + /* Prepare return object */ + lwgeom_out = lwcollection_construct_empty(MULTIPOINTTYPE, point->srid, hasz, hasm); + + /* Test if ordinate is in range */ + lwpoint_getPoint4d_p(point, &p4d); + ordinate_value = lwpoint_get_ordinate(&p4d, ordinate); + if (from <= ordinate_value && to >= ordinate_value) + { + LWPOINT *lwp = lwpoint_clone(point); + lwcollection_add_lwgeom(lwgeom_out, lwpoint_as_lwgeom(lwp)); + } + + return lwgeom_out; +} + +/** + * Clip an input MULTIPOINT between two values, on any ordinate input. + */ +static inline LWCOLLECTION * +lwmpoint_clip_to_ordinate_range(const LWMPOINT *mpoint, char ordinate, double from, double to) +{ + LWCOLLECTION *lwgeom_out = NULL; + char hasz, hasm; + uint32_t i; + + /* Read Z/M info */ + hasz = lwgeom_has_z(lwmpoint_as_lwgeom(mpoint)); + hasm = lwgeom_has_m(lwmpoint_as_lwgeom(mpoint)); + + /* Prepare return object */ + lwgeom_out = lwcollection_construct_empty(MULTIPOINTTYPE, mpoint->srid, hasz, hasm); + + /* For each point, is its ordinate value between from and to? */ + for (i = 0; i < mpoint->ngeoms; i++) + { + POINT4D p4d; + double ordinate_value; + + lwpoint_getPoint4d_p(mpoint->geoms[i], &p4d); + ordinate_value = lwpoint_get_ordinate(&p4d, ordinate); + + if (from <= ordinate_value && to >= ordinate_value) + { + LWPOINT *lwp = lwpoint_clone(mpoint->geoms[i]); + lwcollection_add_lwgeom(lwgeom_out, lwpoint_as_lwgeom(lwp)); + } + } + + /* Set the bbox, if necessary */ + if (mpoint->bbox) + lwgeom_refresh_bbox((LWGEOM *)lwgeom_out); + + return lwgeom_out; +} + +static inline POINTARRAY * +ptarray_clamp_to_ordinate_range(const POINTARRAY *ipa, char ordinate, double from, double to, uint8_t is_closed) +{ + POINT4D p1, p2; + POINTARRAY *opa; + double ovp1, ovp2; + POINT4D *t; + int8_t p1out, p2out; /* -1 - smaller than from, 0 - in range, 1 - larger than to */ + uint32_t i; + uint8_t hasz = FLAGS_GET_Z(ipa->flags); + uint8_t hasm = FLAGS_GET_M(ipa->flags); + + assert(from <= to); + + t = lwalloc(sizeof(POINT4D)); + + /* Initial storage */ + opa = ptarray_construct_empty(hasz, hasm, ipa->npoints); + + /* Add first point */ + getPoint4d_p(ipa, 0, &p1); + ovp1 = lwpoint_get_ordinate(&p1, ordinate); + + p1out = (ovp1 < from) ? -1 : ((ovp1 > to) ? 1 : 0); + + if (from <= ovp1 && ovp1 <= to) + ptarray_append_point(opa, &p1, LW_FALSE); + + /* Loop on all other input points */ + for (i = 1; i < ipa->npoints; i++) + { + getPoint4d_p(ipa, i, &p2); + ovp2 = lwpoint_get_ordinate(&p2, ordinate); + p2out = (ovp2 < from) ? -1 : ((ovp2 > to) ? 1 : 0); + + if (p1out == 0 && p2out == 0) /* both visible */ + { + ptarray_append_point(opa, &p2, LW_FALSE); + } + else if (p1out == p2out && p1out != 0) /* both invisible on the same side */ + { + /* skip */ + } + else if (p1out == -1 && p2out == 0) + { + point_interpolate(&p1, &p2, t, hasz, hasm, ordinate, from); + ptarray_append_point(opa, t, LW_FALSE); + ptarray_append_point(opa, &p2, LW_FALSE); + } + else if (p1out == -1 && p2out == 1) + { + point_interpolate(&p1, &p2, t, hasz, hasm, ordinate, from); + ptarray_append_point(opa, t, LW_FALSE); + point_interpolate(&p1, &p2, t, hasz, hasm, ordinate, to); + ptarray_append_point(opa, t, LW_FALSE); + } + else if (p1out == 0 && p2out == -1) + { + point_interpolate(&p1, &p2, t, hasz, hasm, ordinate, from); + ptarray_append_point(opa, t, LW_FALSE); + } + else if (p1out == 0 && p2out == 1) + { + point_interpolate(&p1, &p2, t, hasz, hasm, ordinate, to); + ptarray_append_point(opa, t, LW_FALSE); + } + else if (p1out == 1 && p2out == -1) + { + point_interpolate(&p1, &p2, t, hasz, hasm, ordinate, to); + ptarray_append_point(opa, t, LW_FALSE); + point_interpolate(&p1, &p2, t, hasz, hasm, ordinate, from); + ptarray_append_point(opa, t, LW_FALSE); + } + else if (p1out == 1 && p2out == 0) + { + point_interpolate(&p1, &p2, t, hasz, hasm, ordinate, to); + ptarray_append_point(opa, t, LW_FALSE); + ptarray_append_point(opa, &p2, LW_FALSE); + } + + p1 = p2; + p1out = p2out; + LW_ON_INTERRUPT(ptarray_free(opa); return NULL); + } + + if (is_closed && opa->npoints > 2) + { + getPoint4d_p(opa, 0, &p1); + ptarray_append_point(opa, &p1, LW_FALSE); + } + lwfree(t); + + return opa; +} + +/** + * Take in a LINESTRING and return a MULTILINESTRING of those portions of the + * LINESTRING between the from/to range for the specified ordinate (XYZM) + */ +static inline LWCOLLECTION * +lwline_clip_to_ordinate_range(const LWLINE *line, char ordinate, double from, double to) +{ + POINTARRAY *pa_in = NULL; + LWCOLLECTION *lwgeom_out = NULL; + POINTARRAY *dp = NULL; + uint32_t i; + int added_last_point = 0; + POINT4D *p = NULL, *q = NULL, *r = NULL; + double ordinate_value_p = 0.0, ordinate_value_q = 0.0; + char hasz, hasm; + char dims; + + /* Null input, nothing we can do. */ + assert(line); + hasz = lwgeom_has_z(lwline_as_lwgeom(line)); + hasm = lwgeom_has_m(lwline_as_lwgeom(line)); + dims = FLAGS_NDIMS(line->flags); + + /* Asking for an ordinate we don't have. Error. */ + if ((ordinate == 'Z' && !hasz) || (ordinate == 'M' && !hasm)) + { + lwerror("Cannot clip on ordinate %d in a %d-d geometry.", ordinate, dims); + return NULL; + } + + /* Prepare our working point objects. */ + p = lwalloc(sizeof(POINT4D)); + q = lwalloc(sizeof(POINT4D)); + r = lwalloc(sizeof(POINT4D)); + + /* Construct a collection to hold our outputs. */ + lwgeom_out = lwcollection_construct_empty(MULTILINETYPE, line->srid, hasz, hasm); + + /* Get our input point array */ + pa_in = line->points; + + for (i = 0; i < pa_in->npoints; i++) + { + if (i > 0) + { + *q = *p; + ordinate_value_q = ordinate_value_p; + } + getPoint4d_p(pa_in, i, p); + ordinate_value_p = lwpoint_get_ordinate(p, ordinate); + + /* Is this point inside the ordinate range? Yes. */ + if (ordinate_value_p >= from && ordinate_value_p <= to) + { + + if (!added_last_point) + { + /* We didn't add the previous point, so this is a new segment. + * Make a new point array. */ + dp = ptarray_construct_empty(hasz, hasm, 32); + + /* We're transiting into the range so add an interpolated + * point at the range boundary. + * If we're on a boundary and crossing from the far side, + * we also need an interpolated point. */ + if (i > 0 && + (/* Don't try to interpolate if this is the first point */ + (ordinate_value_p > from && ordinate_value_p < to) || /* Inside */ + (ordinate_value_p == from && ordinate_value_q > to) || /* Hopping from above */ + (ordinate_value_p == to && ordinate_value_q < from))) /* Hopping from below */ + { + double interpolation_value; + (ordinate_value_q > to) ? (interpolation_value = to) + : (interpolation_value = from); + point_interpolate(q, p, r, hasz, hasm, ordinate, interpolation_value); + ptarray_append_point(dp, r, LW_FALSE); + } + } + /* Add the current vertex to the point array. */ + ptarray_append_point(dp, p, LW_FALSE); + if (ordinate_value_p == from || ordinate_value_p == to) + added_last_point = 2; /* Added on boundary. */ + else + added_last_point = 1; /* Added inside range. */ + } + /* Is this point inside the ordinate range? No. */ + else + { + if (added_last_point == 1) + { + /* We're transiting out of the range, so add an interpolated point + * to the point array at the range boundary. */ + double interpolation_value; + (ordinate_value_p > to) ? (interpolation_value = to) : (interpolation_value = from); + point_interpolate(q, p, r, hasz, hasm, ordinate, interpolation_value); + ptarray_append_point(dp, r, LW_FALSE); + } + else if (added_last_point == 2) + { + /* We're out and the last point was on the boundary. + * If the last point was the near boundary, nothing to do. + * If it was the far boundary, we need an interpolated point. */ + if (from != to && ((ordinate_value_q == from && ordinate_value_p > from) || + (ordinate_value_q == to && ordinate_value_p < to))) + { + double interpolation_value; + (ordinate_value_p > to) ? (interpolation_value = to) + : (interpolation_value = from); + point_interpolate(q, p, r, hasz, hasm, ordinate, interpolation_value); + ptarray_append_point(dp, r, LW_FALSE); + } + } + else if (i && ordinate_value_q < from && ordinate_value_p > to) + { + /* We just hopped over the whole range, from bottom to top, + * so we need to add *two* interpolated points! */ + dp = ptarray_construct(hasz, hasm, 2); + /* Interpolate lower point. */ + point_interpolate(p, q, r, hasz, hasm, ordinate, from); + ptarray_set_point4d(dp, 0, r); + /* Interpolate upper point. */ + point_interpolate(p, q, r, hasz, hasm, ordinate, to); + ptarray_set_point4d(dp, 1, r); + } + else if (i && ordinate_value_q > to && ordinate_value_p < from) + { + /* We just hopped over the whole range, from top to bottom, + * so we need to add *two* interpolated points! */ + dp = ptarray_construct(hasz, hasm, 2); + /* Interpolate upper point. */ + point_interpolate(p, q, r, hasz, hasm, ordinate, to); + ptarray_set_point4d(dp, 0, r); + /* Interpolate lower point. */ + point_interpolate(p, q, r, hasz, hasm, ordinate, from); + ptarray_set_point4d(dp, 1, r); + } + /* We have an extant point-array, save it out to a multi-line. */ + if (dp) + { + /* Only one point, so we have to make an lwpoint to hold this + * and set the overall output type to a generic collection. */ + if (dp->npoints == 1) + { + LWPOINT *opoint = lwpoint_construct(line->srid, NULL, dp); + lwgeom_out->type = COLLECTIONTYPE; + lwgeom_out = lwcollection_add_lwgeom(lwgeom_out, lwpoint_as_lwgeom(opoint)); + } + else + { + LWLINE *oline = lwline_construct(line->srid, NULL, dp); + lwgeom_out = lwcollection_add_lwgeom(lwgeom_out, lwline_as_lwgeom(oline)); + } + + /* Pointarray is now owned by lwgeom_out, so drop reference to it */ + dp = NULL; + } + added_last_point = 0; + } + } + + /* Still some points left to be saved out. */ + if (dp) + { + if (dp->npoints == 1) + { + LWPOINT *opoint = lwpoint_construct(line->srid, NULL, dp); + lwgeom_out->type = COLLECTIONTYPE; + lwgeom_out = lwcollection_add_lwgeom(lwgeom_out, lwpoint_as_lwgeom(opoint)); + } + else if (dp->npoints > 1) + { + LWLINE *oline = lwline_construct(line->srid, NULL, dp); + lwgeom_out = lwcollection_add_lwgeom(lwgeom_out, lwline_as_lwgeom(oline)); + } + else + ptarray_free(dp); + } + + lwfree(p); + lwfree(q); + lwfree(r); + + if (line->bbox && lwgeom_out->ngeoms > 0) + lwgeom_refresh_bbox((LWGEOM *)lwgeom_out); + + return lwgeom_out; +} + +/** + * Clip an input LWPOLY between two values, on any ordinate input. + */ +static inline LWCOLLECTION * +lwpoly_clip_to_ordinate_range(const LWPOLY *poly, char ordinate, double from, double to) +{ + assert(poly); + char hasz = FLAGS_GET_Z(poly->flags), hasm = FLAGS_GET_M(poly->flags); + LWPOLY *poly_res = lwpoly_construct_empty(poly->srid, hasz, hasm); + LWCOLLECTION *lwgeom_out = lwcollection_construct_empty(MULTIPOLYGONTYPE, poly->srid, hasz, hasm); + + for (uint32_t i = 0; i < poly->nrings; i++) + { + /* Ret number of points */ + POINTARRAY *pa = ptarray_clamp_to_ordinate_range(poly->rings[i], ordinate, from, to, LW_TRUE); + + if (!pa) + return NULL; + + if (pa->npoints >= 4) + lwpoly_add_ring(poly_res, pa); + else + { + ptarray_free(pa); + if (i == 0) + break; + } + } + if (poly_res->nrings > 0) + lwgeom_out = lwcollection_add_lwgeom(lwgeom_out, (LWGEOM *)poly_res); + else + lwpoly_free(poly_res); + + return lwgeom_out; +} + +/** + * Clip an input LWTRIANGLE between two values, on any ordinate input. + */ +static inline LWCOLLECTION * +lwtriangle_clip_to_ordinate_range(const LWTRIANGLE *tri, char ordinate, double from, double to) +{ + assert(tri); + char hasz = FLAGS_GET_Z(tri->flags), hasm = FLAGS_GET_M(tri->flags); + LWCOLLECTION *lwgeom_out = lwcollection_construct_empty(TINTYPE, tri->srid, hasz, hasm); + POINTARRAY *pa = ptarray_clamp_to_ordinate_range(tri->points, ordinate, from, to, LW_TRUE); + + if (!pa) + return NULL; + + if (pa->npoints >= 4) + { + POINT4D first = getPoint4d(pa, 0); + for (uint32_t i = 1; i < pa->npoints - 2; i++) + { + POINT4D p; + POINTARRAY *tpa = ptarray_construct_empty(hasz, hasm, 4); + ptarray_append_point(tpa, &first, LW_TRUE); + getPoint4d_p(pa, i, &p); + ptarray_append_point(tpa, &p, LW_TRUE); + getPoint4d_p(pa, i + 1, &p); + ptarray_append_point(tpa, &p, LW_TRUE); + ptarray_append_point(tpa, &first, LW_TRUE); + LWTRIANGLE *otri = lwtriangle_construct(tri->srid, NULL, tpa); + lwgeom_out = lwcollection_add_lwgeom(lwgeom_out, (LWGEOM *)otri); + } + } + ptarray_free(pa); + return lwgeom_out; +} + +/** + * Clip an input COLLECTION between two values, on any ordinate input. + */ +static inline LWCOLLECTION * +lwcollection_clip_to_ordinate_range(const LWCOLLECTION *icol, char ordinate, double from, double to) +{ + LWCOLLECTION *lwgeom_out; + + assert(icol); + if (icol->ngeoms == 1) + lwgeom_out = lwgeom_clip_to_ordinate_range(icol->geoms[0], ordinate, from, to, 0); + else + { + LWCOLLECTION *col; + char hasz = lwgeom_has_z(lwcollection_as_lwgeom(icol)); + char hasm = lwgeom_has_m(lwcollection_as_lwgeom(icol)); + uint32_t i; + lwgeom_out = lwcollection_construct_empty(icol->type, icol->srid, hasz, hasm); + FLAGS_SET_Z(lwgeom_out->flags, hasz); + FLAGS_SET_M(lwgeom_out->flags, hasm); + for (i = 0; i < icol->ngeoms; i++) + { + col = lwgeom_clip_to_ordinate_range(icol->geoms[i], ordinate, from, to, 0); + if (col) + { + if (col->type != icol->type) + lwgeom_out->type = COLLECTIONTYPE; + lwgeom_out = lwcollection_concat_in_place(lwgeom_out, col); + lwfree(col->geoms); + lwcollection_release(col); + } + } + } + + if (icol->bbox) + lwgeom_refresh_bbox((LWGEOM *)lwgeom_out); + + return lwgeom_out; +} + +LWCOLLECTION * +lwgeom_clip_to_ordinate_range(const LWGEOM *lwin, char ordinate, double from, double to, double offset) +{ + LWCOLLECTION *out_col; + LWCOLLECTION *out_offset; + uint32_t i; + + /* Ensure 'from' is less than 'to'. */ + if (to < from) + { + double t = from; + from = to; + to = t; + } + + if (!lwin) + lwerror("lwgeom_clip_to_ordinate_range: null input geometry!"); + + switch (lwin->type) + { + case LINETYPE: + out_col = lwline_clip_to_ordinate_range((LWLINE *)lwin, ordinate, from, to); + break; + case MULTIPOINTTYPE: + out_col = lwmpoint_clip_to_ordinate_range((LWMPOINT *)lwin, ordinate, from, to); + break; + case POINTTYPE: + out_col = lwpoint_clip_to_ordinate_range((LWPOINT *)lwin, ordinate, from, to); + break; + case POLYGONTYPE: + out_col = lwpoly_clip_to_ordinate_range((LWPOLY *)lwin, ordinate, from, to); + break; + case TRIANGLETYPE: + out_col = lwtriangle_clip_to_ordinate_range((LWTRIANGLE *)lwin, ordinate, from, to); + break; + case TINTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + case POLYHEDRALSURFACETYPE: + out_col = lwcollection_clip_to_ordinate_range((LWCOLLECTION *)lwin, ordinate, from, to); + break; + default: + lwerror("This function does not accept %s geometries.", lwtype_name(lwin->type)); + return NULL; + } + + /* Stop if result is NULL */ + if (!out_col) + lwerror("lwgeom_clip_to_ordinate_range clipping routine returned NULL"); + + /* Return if we aren't going to offset the result */ + if (FP_IS_ZERO(offset) || lwgeom_is_empty(lwcollection_as_lwgeom(out_col))) + return out_col; + + /* Construct a collection to hold our outputs. */ + /* Things get ugly: GEOS offset drops Z's and M's so we have to drop ours */ + out_offset = lwcollection_construct_empty(MULTILINETYPE, lwin->srid, 0, 0); + + /* Try and offset the linear portions of the return value */ + for (i = 0; i < out_col->ngeoms; i++) + { + int type = out_col->geoms[i]->type; + if (type == POINTTYPE) + { + lwnotice("lwgeom_clip_to_ordinate_range cannot offset a clipped point"); + continue; + } + else if (type == LINETYPE) + { + /* lwgeom_offsetcurve(line, offset, quadsegs, joinstyle (round), mitrelimit) */ + LWGEOM *lwoff = lwgeom_offsetcurve(out_col->geoms[i], offset, 8, 1, 5.0); + if (!lwoff) + { + lwerror("lwgeom_offsetcurve returned null"); + } + lwcollection_add_lwgeom(out_offset, lwoff); + } + else + { + lwerror("lwgeom_clip_to_ordinate_range found an unexpected type (%s) in the offset routine", + lwtype_name(type)); + } + } + + return out_offset; +} + +LWCOLLECTION * +lwgeom_locate_between(const LWGEOM *lwin, double from, double to, double offset) +{ + if (!lwgeom_has_m(lwin)) + lwerror("Input geometry does not have a measure dimension"); + + return lwgeom_clip_to_ordinate_range(lwin, 'M', from, to, offset); +} + +double +lwgeom_interpolate_point(const LWGEOM *lwin, const LWPOINT *lwpt) +{ + POINT4D p, p_proj; + double ret = 0.0; + + if (!lwin) + lwerror("lwgeom_interpolate_point: null input geometry!"); + + if (!lwgeom_has_m(lwin)) + lwerror("Input geometry does not have a measure dimension"); + + if (lwgeom_is_empty(lwin) || lwpoint_is_empty(lwpt)) + lwerror("Input geometry is empty"); + + switch (lwin->type) + { + case LINETYPE: + { + LWLINE *lwline = lwgeom_as_lwline(lwin); + lwpoint_getPoint4d_p(lwpt, &p); + ret = ptarray_locate_point(lwline->points, &p, NULL, &p_proj); + ret = p_proj.m; + break; + } + default: + lwerror("This function does not accept %s geometries.", lwtype_name(lwin->type)); + } + return ret; +} + +/* + * Time of closest point of approach + * + * Given two vectors (p1-p2 and q1-q2) and + * a time range (t1-t2) return the time in which + * a point p is closest to a point q on their + * respective vectors, and the actual points + * + * Here we use algorithm from softsurfer.com + * that can be found here + * http://softsurfer.com/Archive/algorithm_0106/algorithm_0106.htm + * + * @param p0 start of first segment, will be set to actual + * closest point of approach on segment. + * @param p1 end of first segment + * @param q0 start of second segment, will be set to actual + * closest point of approach on segment. + * @param q1 end of second segment + * @param t0 start of travel time + * @param t1 end of travel time + * + * @return time of closest point of approach + * + */ +static double +segments_tcpa(POINT4D *p0, const POINT4D *p1, POINT4D *q0, const POINT4D *q1, double t0, double t1) +{ + POINT3DZ pv; /* velocity of p, aka u */ + POINT3DZ qv; /* velocity of q, aka v */ + POINT3DZ dv; /* velocity difference */ + POINT3DZ w0; /* vector between first points */ + + /* + lwnotice("FROM %g,%g,%g,%g -- %g,%g,%g,%g", + p0->x, p0->y, p0->z, p0->m, + p1->x, p1->y, p1->z, p1->m); + lwnotice(" TO %g,%g,%g,%g -- %g,%g,%g,%g", + q0->x, q0->y, q0->z, q0->m, + q1->x, q1->y, q1->z, q1->m); + */ + + /* PV aka U */ + pv.x = (p1->x - p0->x); + pv.y = (p1->y - p0->y); + pv.z = (p1->z - p0->z); + /*lwnotice("PV: %g, %g, %g", pv.x, pv.y, pv.z);*/ + + /* QV aka V */ + qv.x = (q1->x - q0->x); + qv.y = (q1->y - q0->y); + qv.z = (q1->z - q0->z); + /*lwnotice("QV: %g, %g, %g", qv.x, qv.y, qv.z);*/ + + dv.x = pv.x - qv.x; + dv.y = pv.y - qv.y; + dv.z = pv.z - qv.z; + /*lwnotice("DV: %g, %g, %g", dv.x, dv.y, dv.z);*/ + + double dv2 = DOT(dv, dv); + /*lwnotice("DOT: %g", dv2);*/ + + if (dv2 == 0.0) + { + /* Distance is the same at any time, we pick the earliest */ + return t0; + } + + /* Distance at any given time, with t0 */ + w0.x = (p0->x - q0->x); + w0.y = (p0->y - q0->y); + w0.z = (p0->z - q0->z); + + /*lwnotice("W0: %g, %g, %g", w0.x, w0.y, w0.z);*/ + + /* Check that at distance dt w0 is distance */ + + /* This is the fraction of measure difference */ + double t = -DOT(w0, dv) / dv2; + /*lwnotice("CLOSEST TIME (fraction): %g", t);*/ + + if (t > 1.0) + { + /* Getting closer as we move to the end */ + /*lwnotice("Converging");*/ + t = 1; + } + else if (t < 0.0) + { + /*lwnotice("Diverging");*/ + t = 0; + } + + /* Interpolate the actual points now */ + + p0->x += pv.x * t; + p0->y += pv.y * t; + p0->z += pv.z * t; + + q0->x += qv.x * t; + q0->y += qv.y * t; + q0->z += qv.z * t; + + t = t0 + (t1 - t0) * t; + /*lwnotice("CLOSEST TIME (real): %g", t);*/ + + return t; +} + +static int +ptarray_collect_mvals(const POINTARRAY *pa, double tmin, double tmax, double *mvals) +{ + POINT4D pbuf; + uint32_t i, n = 0; + for (i = 0; i < pa->npoints; ++i) + { + getPoint4d_p(pa, i, &pbuf); /* could be optimized */ + if (pbuf.m >= tmin && pbuf.m <= tmax) + mvals[n++] = pbuf.m; + } + return n; +} + +static int +compare_double(const void *pa, const void *pb) +{ + double a = *((double *)pa); + double b = *((double *)pb); + if (a < b) + return -1; + else if (a > b) + return 1; + else + return 0; +} + +/* Return number of elements in unique array */ +static int +uniq(double *vals, int nvals) +{ + int i, last = 0; + for (i = 1; i < nvals; ++i) + { + // lwnotice("(I%d):%g", i, vals[i]); + if (vals[i] != vals[last]) + { + vals[++last] = vals[i]; + // lwnotice("(O%d):%g", last, vals[last]); + } + } + return last + 1; +} + +/* + * Find point at a given measure + * + * The function assumes measures are linear so that always a single point + * is returned for a single measure. + * + * @param pa the point array to perform search on + * @param m the measure to search for + * @param p the point to write result into + * @param from the segment number to start from + * + * @return the segment number the point was found into + * or -1 if given measure was out of the known range. + */ +static int +ptarray_locate_along_linear(const POINTARRAY *pa, double m, POINT4D *p, uint32_t from) +{ + uint32_t i = from; + POINT4D p1, p2; + + /* Walk through each segment in the point array */ + getPoint4d_p(pa, i, &p1); + for (i = from + 1; i < pa->npoints; i++) + { + getPoint4d_p(pa, i, &p2); + + if (segment_locate_along(&p1, &p2, m, 0, p) == LW_TRUE) + return i - 1; /* found */ + + p1 = p2; + } + + return -1; /* not found */ +} + +double +lwgeom_tcpa(const LWGEOM *g1, const LWGEOM *g2, double *mindist) +{ + LWLINE *l1, *l2; + int i; + GBOX gbox1, gbox2; + double tmin, tmax; + double *mvals; + int nmvals = 0; + double mintime; + double mindist2 = FLT_MAX; /* minimum distance, squared */ + + if (!lwgeom_has_m(g1) || !lwgeom_has_m(g2)) + { + lwerror("Both input geometries must have a measure dimension"); + return -1; + } + + l1 = lwgeom_as_lwline(g1); + l2 = lwgeom_as_lwline(g2); + + if (!l1 || !l2) + { + lwerror("Both input geometries must be linestrings"); + return -1; + } + + if (l1->points->npoints < 2 || l2->points->npoints < 2) + { + lwerror("Both input lines must have at least 2 points"); + return -1; + } + + /* We use lwgeom_calculate_gbox() instead of lwgeom_get_gbox() */ + /* because we cannot afford the float rounding inaccuracy when */ + /* we compare the ranges for overlap below */ + lwgeom_calculate_gbox(g1, &gbox1); + lwgeom_calculate_gbox(g2, &gbox2); + + /* + * Find overlapping M range + * WARNING: may be larger than the real one + */ + + tmin = FP_MAX(gbox1.mmin, gbox2.mmin); + tmax = FP_MIN(gbox1.mmax, gbox2.mmax); + + if (tmax < tmin) + { + LWDEBUG(1, "Inputs never exist at the same time"); + return -2; + } + + // lwnotice("Min:%g, Max:%g", tmin, tmax); + + /* + * Collect M values in common time range from inputs + */ + + mvals = lwalloc(sizeof(double) * (l1->points->npoints + l2->points->npoints)); + + /* TODO: also clip the lines ? */ + nmvals = ptarray_collect_mvals(l1->points, tmin, tmax, mvals); + nmvals += ptarray_collect_mvals(l2->points, tmin, tmax, mvals + nmvals); + + /* Sort values in ascending order */ + qsort(mvals, nmvals, sizeof(double), compare_double); + + /* Remove duplicated values */ + nmvals = uniq(mvals, nmvals); + + if (nmvals < 2) + { + { + /* there's a single time, must be that one... */ + double t0 = mvals[0]; + POINT4D p0, p1; + LWDEBUGF(1, "Inputs only exist both at a single time (%g)", t0); + if (mindist) + { + if (-1 == ptarray_locate_along_linear(l1->points, t0, &p0, 0)) + { + lwfree(mvals); + lwerror("Could not find point with M=%g on first geom", t0); + return -1; + } + if (-1 == ptarray_locate_along_linear(l2->points, t0, &p1, 0)) + { + lwfree(mvals); + lwerror("Could not find point with M=%g on second geom", t0); + return -1; + } + *mindist = distance3d_pt_pt((POINT3D *)&p0, (POINT3D *)&p1); + } + lwfree(mvals); + return t0; + } + } + + /* + * For each consecutive pair of measures, compute time of closest point + * approach and actual distance between points at that time + */ + mintime = tmin; + for (i = 1; i < nmvals; ++i) + { + double t0 = mvals[i - 1]; + double t1 = mvals[i]; + double t; + POINT4D p0, p1, q0, q1; + int seg; + double dist2; + + // lwnotice("T %g-%g", t0, t1); + + seg = ptarray_locate_along_linear(l1->points, t0, &p0, 0); + if (-1 == seg) + continue; /* possible, if GBOX is approximated */ + // lwnotice("Measure %g on segment %d of line 1: %g, %g, %g", t0, seg, p0.x, p0.y, p0.z); + + seg = ptarray_locate_along_linear(l1->points, t1, &p1, seg); + if (-1 == seg) + continue; /* possible, if GBOX is approximated */ + // lwnotice("Measure %g on segment %d of line 1: %g, %g, %g", t1, seg, p1.x, p1.y, p1.z); + + seg = ptarray_locate_along_linear(l2->points, t0, &q0, 0); + if (-1 == seg) + continue; /* possible, if GBOX is approximated */ + // lwnotice("Measure %g on segment %d of line 2: %g, %g, %g", t0, seg, q0.x, q0.y, q0.z); + + seg = ptarray_locate_along_linear(l2->points, t1, &q1, seg); + if (-1 == seg) + continue; /* possible, if GBOX is approximated */ + // lwnotice("Measure %g on segment %d of line 2: %g, %g, %g", t1, seg, q1.x, q1.y, q1.z); + + t = segments_tcpa(&p0, &p1, &q0, &q1, t0, t1); + + /* + lwnotice("Closest points: %g,%g,%g and %g,%g,%g at time %g", + p0.x, p0.y, p0.z, + q0.x, q0.y, q0.z, t); + */ + + dist2 = (q0.x - p0.x) * (q0.x - p0.x) + (q0.y - p0.y) * (q0.y - p0.y) + (q0.z - p0.z) * (q0.z - p0.z); + if (dist2 < mindist2) + { + mindist2 = dist2; + mintime = t; + // lwnotice("MINTIME: %g", mintime); + } + } + + /* + * Release memory + */ + + lwfree(mvals); + + if (mindist) + { + *mindist = sqrt(mindist2); + } + /*lwnotice("MINDIST: %g", sqrt(mindist2));*/ + + return mintime; +} + +int +lwgeom_cpa_within(const LWGEOM *g1, const LWGEOM *g2, double maxdist) +{ + LWLINE *l1, *l2; + int i; + GBOX gbox1, gbox2; + double tmin, tmax; + double *mvals; + int nmvals = 0; + double maxdist2 = maxdist * maxdist; + int within = LW_FALSE; + + if (!lwgeom_has_m(g1) || !lwgeom_has_m(g2)) + { + lwerror("Both input geometries must have a measure dimension"); + return LW_FALSE; + } + + l1 = lwgeom_as_lwline(g1); + l2 = lwgeom_as_lwline(g2); + + if (!l1 || !l2) + { + lwerror("Both input geometries must be linestrings"); + return LW_FALSE; + } + + if (l1->points->npoints < 2 || l2->points->npoints < 2) + { + /* TODO: return distance between these two points */ + lwerror("Both input lines must have at least 2 points"); + return LW_FALSE; + } + + /* We use lwgeom_calculate_gbox() instead of lwgeom_get_gbox() */ + /* because we cannot afford the float rounding inaccuracy when */ + /* we compare the ranges for overlap below */ + lwgeom_calculate_gbox(g1, &gbox1); + lwgeom_calculate_gbox(g2, &gbox2); + + /* + * Find overlapping M range + * WARNING: may be larger than the real one + */ + + tmin = FP_MAX(gbox1.mmin, gbox2.mmin); + tmax = FP_MIN(gbox1.mmax, gbox2.mmax); + + if (tmax < tmin) + { + LWDEBUG(1, "Inputs never exist at the same time"); + return LW_FALSE; + } + + /* + * Collect M values in common time range from inputs + */ + + mvals = lwalloc(sizeof(double) * (l1->points->npoints + l2->points->npoints)); + + /* TODO: also clip the lines ? */ + nmvals = ptarray_collect_mvals(l1->points, tmin, tmax, mvals); + nmvals += ptarray_collect_mvals(l2->points, tmin, tmax, mvals + nmvals); + + /* Sort values in ascending order */ + qsort(mvals, nmvals, sizeof(double), compare_double); + + /* Remove duplicated values */ + nmvals = uniq(mvals, nmvals); + + if (nmvals < 2) + { + /* there's a single time, must be that one... */ + double t0 = mvals[0]; + POINT4D p0, p1; + LWDEBUGF(1, "Inputs only exist both at a single time (%g)", t0); + if (-1 == ptarray_locate_along_linear(l1->points, t0, &p0, 0)) + { + lwnotice("Could not find point with M=%g on first geom", t0); + return LW_FALSE; + } + if (-1 == ptarray_locate_along_linear(l2->points, t0, &p1, 0)) + { + lwnotice("Could not find point with M=%g on second geom", t0); + return LW_FALSE; + } + if (distance3d_pt_pt((POINT3D *)&p0, (POINT3D *)&p1) <= maxdist) + within = LW_TRUE; + lwfree(mvals); + return within; + } + + /* + * For each consecutive pair of measures, compute time of closest point + * approach and actual distance between points at that time + */ + for (i = 1; i < nmvals; ++i) + { + double t0 = mvals[i - 1]; + double t1 = mvals[i]; +#if POSTGIS_DEBUG_LEVEL >= 1 + double t; +#endif + POINT4D p0, p1, q0, q1; + int seg; + double dist2; + + // lwnotice("T %g-%g", t0, t1); + + seg = ptarray_locate_along_linear(l1->points, t0, &p0, 0); + if (-1 == seg) + continue; /* possible, if GBOX is approximated */ + // lwnotice("Measure %g on segment %d of line 1: %g, %g, %g", t0, seg, p0.x, p0.y, p0.z); + + seg = ptarray_locate_along_linear(l1->points, t1, &p1, seg); + if (-1 == seg) + continue; /* possible, if GBOX is approximated */ + // lwnotice("Measure %g on segment %d of line 1: %g, %g, %g", t1, seg, p1.x, p1.y, p1.z); + + seg = ptarray_locate_along_linear(l2->points, t0, &q0, 0); + if (-1 == seg) + continue; /* possible, if GBOX is approximated */ + // lwnotice("Measure %g on segment %d of line 2: %g, %g, %g", t0, seg, q0.x, q0.y, q0.z); + + seg = ptarray_locate_along_linear(l2->points, t1, &q1, seg); + if (-1 == seg) + continue; /* possible, if GBOX is approximated */ + // lwnotice("Measure %g on segment %d of line 2: %g, %g, %g", t1, seg, q1.x, q1.y, q1.z); + +#if POSTGIS_DEBUG_LEVEL >= 1 + t = +#endif + segments_tcpa(&p0, &p1, &q0, &q1, t0, t1); + + /* + lwnotice("Closest points: %g,%g,%g and %g,%g,%g at time %g", + p0.x, p0.y, p0.z, + q0.x, q0.y, q0.z, t); + */ + + dist2 = (q0.x - p0.x) * (q0.x - p0.x) + (q0.y - p0.y) * (q0.y - p0.y) + (q0.z - p0.z) * (q0.z - p0.z); + if (dist2 <= maxdist2) + { + LWDEBUGF(1, "Within distance %g at time %g, breaking", sqrt(dist2), t); + within = LW_TRUE; + break; + } + } + + /* + * Release memory + */ + + lwfree(mvals); + + return within; +} diff --git a/mgist-postgis/liblwgeom/lwmcurve.c b/mgist-postgis/liblwgeom/lwmcurve.c new file mode 100644 index 0000000..e797992 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwmcurve.c @@ -0,0 +1,33 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2001-2006 Refractions Research Inc. + * + **********************************************************************/ + + +#include +#include +#include +#include "liblwgeom_internal.h" + + + + diff --git a/mgist-postgis/liblwgeom/lwmline.c b/mgist-postgis/liblwgeom/lwmline.c new file mode 100644 index 0000000..ec430b1 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwmline.c @@ -0,0 +1,129 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2001-2006 Refractions Research Inc. + * + **********************************************************************/ + + +#include +#include +#include +#include "liblwgeom_internal.h" + +void +lwmline_release(LWMLINE *lwmline) +{ + lwgeom_release(lwmline_as_lwgeom(lwmline)); +} + +LWMLINE * +lwmline_construct_empty(int32_t srid, char hasz, char hasm) +{ + LWMLINE *ret = (LWMLINE*)lwcollection_construct_empty(MULTILINETYPE, srid, hasz, hasm); + return ret; +} + + + +LWMLINE* lwmline_add_lwline(LWMLINE *mobj, const LWLINE *obj) +{ + return (LWMLINE*)lwcollection_add_lwgeom((LWCOLLECTION*)mobj, (LWGEOM*)obj); +} + +/** +* Re-write the measure ordinate (or add one, if it isn't already there) interpolating +* the measure between the supplied start and end values. +*/ +LWMLINE* +lwmline_measured_from_lwmline(const LWMLINE *lwmline, double m_start, double m_end) +{ + uint32_t i = 0; + int hasm = 0, hasz = 0; + double length = 0.0, length_so_far = 0.0; + double m_range = m_end - m_start; + LWGEOM **geoms = NULL; + + if ( lwmline->type != MULTILINETYPE ) + { + lwerror("lwmline_measured_from_lmwline: only multiline types supported"); + return NULL; + } + + hasz = FLAGS_GET_Z(lwmline->flags); + hasm = 1; + + /* Calculate the total length of the mline */ + for ( i = 0; i < lwmline->ngeoms; i++ ) + { + LWLINE *lwline = (LWLINE*)lwmline->geoms[i]; + if ( lwline->points && lwline->points->npoints > 1 ) + { + length += ptarray_length_2d(lwline->points); + } + } + + if ( lwgeom_is_empty((LWGEOM*)lwmline) ) + { + return (LWMLINE*)lwcollection_construct_empty(MULTILINETYPE, lwmline->srid, hasz, hasm); + } + + geoms = lwalloc(sizeof(LWGEOM*) * lwmline->ngeoms); + + for ( i = 0; i < lwmline->ngeoms; i++ ) + { + double sub_m_start, sub_m_end; + double sub_length = 0.0; + LWLINE *lwline = (LWLINE*)lwmline->geoms[i]; + + if ( lwline->points && lwline->points->npoints > 1 ) + { + sub_length = ptarray_length_2d(lwline->points); + } + + sub_m_start = (m_start + m_range * length_so_far / length); + sub_m_end = (m_start + m_range * (length_so_far + sub_length) / length); + + geoms[i] = (LWGEOM*)lwline_measured_from_lwline(lwline, sub_m_start, sub_m_end); + + length_so_far += sub_length; + } + + return (LWMLINE*)lwcollection_construct(lwmline->type, lwmline->srid, NULL, lwmline->ngeoms, geoms); +} + +void lwmline_free(LWMLINE *mline) +{ + if (!mline) + return; + + if (mline->bbox) + lwfree(mline->bbox); + + if (mline->geoms) + { + for (uint32_t i = 0; i < mline->ngeoms; i++) + if (mline->geoms[i]) + lwline_free(mline->geoms[i]); + lwfree(mline->geoms); + } + + lwfree(mline); +} diff --git a/mgist-postgis/liblwgeom/lwmpoint.c b/mgist-postgis/liblwgeom/lwmpoint.c new file mode 100644 index 0000000..9f687f6 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwmpoint.c @@ -0,0 +1,108 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2001-2006 Refractions Research Inc. + * + **********************************************************************/ + + +#include +#include +#include +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + +void +lwmpoint_release(LWMPOINT *lwmpoint) +{ + lwgeom_release(lwmpoint_as_lwgeom(lwmpoint)); +} + +LWMPOINT * +lwmpoint_construct_empty(int32_t srid, char hasz, char hasm) +{ + LWMPOINT *ret = (LWMPOINT*)lwcollection_construct_empty(MULTIPOINTTYPE, srid, hasz, hasm); + return ret; +} + +LWMPOINT* lwmpoint_add_lwpoint(LWMPOINT *mobj, const LWPOINT *obj) +{ + LWDEBUG(4, "Called"); + return (LWMPOINT*)lwcollection_add_lwgeom((LWCOLLECTION*)mobj, (LWGEOM*)obj); +} + +LWMPOINT * +lwmpoint_construct(int32_t srid, const POINTARRAY *pa) +{ + uint32_t i; + int hasz = ptarray_has_z(pa); + int hasm = ptarray_has_m(pa); + LWMPOINT *ret = (LWMPOINT*)lwcollection_construct_empty(MULTIPOINTTYPE, srid, hasz, hasm); + + for ( i = 0; i < pa->npoints; i++ ) + { + LWPOINT *lwp; + POINT4D p; + getPoint4d_p(pa, i, &p); + lwp = lwpoint_make(srid, hasz, hasm, &p); + lwmpoint_add_lwpoint(ret, lwp); + } + + return ret; +} + + +void lwmpoint_free(LWMPOINT *mpt) +{ + uint32_t i; + + if ( ! mpt ) return; + + if ( mpt->bbox ) + lwfree(mpt->bbox); + + for ( i = 0; i < mpt->ngeoms; i++ ) + if ( mpt->geoms && mpt->geoms[i] ) + lwpoint_free(mpt->geoms[i]); + + if ( mpt->geoms ) + lwfree(mpt->geoms); + + lwfree(mpt); +} + + +LWMPOINT* +lwmpoint_from_lwgeom(const LWGEOM *g) +{ + LWPOINTITERATOR* it = lwpointiterator_create(g); + int has_z = lwgeom_has_z(g); + int has_m = lwgeom_has_m(g); + LWMPOINT* result = lwmpoint_construct_empty(g->srid, has_z, has_m); + POINT4D p; + + while(lwpointiterator_next(it, &p)) { + LWPOINT* lwp = lwpoint_make(g->srid, has_z, has_m, &p); + lwmpoint_add_lwpoint(result, lwp); + } + + lwpointiterator_destroy(it); + return result; +} diff --git a/mgist-postgis/liblwgeom/lwmpoly.c b/mgist-postgis/liblwgeom/lwmpoly.c new file mode 100644 index 0000000..6dc95ce --- /dev/null +++ b/mgist-postgis/liblwgeom/lwmpoly.c @@ -0,0 +1,69 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2001-2006 Refractions Research Inc. + * + **********************************************************************/ + + +#include +#include +#include +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + + +void +lwmpoly_release(LWMPOLY *lwmpoly) +{ + lwgeom_release(lwmpoly_as_lwgeom(lwmpoly)); +} + +LWMPOLY * +lwmpoly_construct_empty(int32_t srid, char hasz, char hasm) +{ + LWMPOLY *ret = (LWMPOLY*)lwcollection_construct_empty(MULTIPOLYGONTYPE, srid, hasz, hasm); + return ret; +} + + +LWMPOLY* lwmpoly_add_lwpoly(LWMPOLY *mobj, const LWPOLY *obj) +{ + return (LWMPOLY*)lwcollection_add_lwgeom((LWCOLLECTION*)mobj, (LWGEOM*)obj); +} + + +void lwmpoly_free(LWMPOLY *mpoly) +{ + uint32_t i; + if ( ! mpoly ) return; + if ( mpoly->bbox ) + lwfree(mpoly->bbox); + + for ( i = 0; i < mpoly->ngeoms; i++ ) + if ( mpoly->geoms && mpoly->geoms[i] ) + lwpoly_free(mpoly->geoms[i]); + + if ( mpoly->geoms ) + lwfree(mpoly->geoms); + + lwfree(mpoly); +} + diff --git a/mgist-postgis/liblwgeom/lwmsurface.c b/mgist-postgis/liblwgeom/lwmsurface.c new file mode 100644 index 0000000..1d31c1f --- /dev/null +++ b/mgist-postgis/liblwgeom/lwmsurface.c @@ -0,0 +1,33 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2001-2006 Refractions Research Inc. + * + **********************************************************************/ + + +#include +#include +#include +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + + + diff --git a/mgist-postgis/liblwgeom/lwmval.c b/mgist-postgis/liblwgeom/lwmval.c new file mode 100644 index 0000000..6e8f88f --- /dev/null +++ b/mgist-postgis/liblwgeom/lwmval.c @@ -0,0 +1,269 @@ + +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2018 Nicklas Avén + * + **********************************************************************/ + + +#include "liblwgeom_internal.h" + +static LWGEOM* lwgeom_filter_m_ignore_null(LWGEOM *geom,double min,double max, int returnm); + +static POINTARRAY* ptarray_filterm(POINTARRAY *pa,double min, double max, int returnm) +{ + LWDEBUGF(2, "Entered %s", __func__); + + /*Check if M exists + * This should already be tested earlier so we don't handle it properly. + * If this happens it is because the function is used in another context than filterM + and we throw an error*/ + if(!FLAGS_GET_M(pa->flags)) + lwerror("missing m-value in function %s\n",__func__); + + /*Dimensions in input geometry*/ + int ndims = FLAGS_NDIMS(pa->flags); + + /*Dimensions in result*/ + int res_ndims; + if(returnm) + res_ndims = ndims; + else + res_ndims = ndims-1; + + int pointsize = res_ndims * sizeof(double); + + double m_val; + + //M-value will always be the last dimension + int m_pos = ndims-1; + + uint32_t i, counter=0; + for(i=0;inpoints;i++) + { + m_val = *((double*)pa->serialized_pointlist + i*ndims + m_pos); + if(m_val >= min && m_val <= max) + counter++; + } + + POINTARRAY *pa_res = ptarray_construct(FLAGS_GET_Z(pa->flags),returnm * FLAGS_GET_M(pa->flags), counter); + + double *res_cursor = (double*) pa_res->serialized_pointlist; + for(i=0;inpoints;i++) + { + m_val = *((double*)pa->serialized_pointlist + i*ndims + m_pos); + if(m_val >= min && m_val <= max) + { + memcpy(res_cursor, (double*) pa->serialized_pointlist + i*ndims, pointsize); + res_cursor+=res_ndims; + } + } + + return pa_res; + +} +static LWPOINT* lwpoint_filterm(LWPOINT *pt,double min,double max, int returnm) +{ + LWDEBUGF(2, "Entered %s", __func__); + + POINTARRAY *pa; + + pa = ptarray_filterm(pt->point, min, max,returnm); + if(pa->npoints < 1) + { + ptarray_free(pa); + return NULL; + } + return lwpoint_construct(pt->srid, NULL, pa); + +} + +static LWLINE* lwline_filterm(LWLINE *line,double min,double max, int returnm) +{ + LWDEBUGF(2, "Entered %s", __func__); + + POINTARRAY *pa; + pa = ptarray_filterm(line->points, min, max,returnm); + + if(pa->npoints < 2 ) + { + ptarray_free(pa); + return NULL; + } + + return lwline_construct(line->srid, NULL, pa); +} + +static LWPOLY* lwpoly_filterm(LWPOLY *poly,double min,double max, int returnm) +{ + LWDEBUGF(2, "Entered %s", __func__); + int i, nrings; + LWPOLY *poly_res = lwpoly_construct_empty(poly->srid, FLAGS_GET_Z(poly->flags),returnm * FLAGS_GET_M(poly->flags)); + + nrings = poly->nrings; + for( i = 0; i < nrings; i++ ) + { + /* Ret number of points */ + POINTARRAY *pa = ptarray_filterm(poly->rings[i], min, max,returnm); + + /* Skip empty rings */ + if( pa == NULL ) + continue; + + if(pa->npoints>=4) + { + if (lwpoly_add_ring(poly_res, pa) == LW_FAILURE ) + { + LWDEBUG(2, "Unable to add ring to polygon"); + lwerror("Unable to add ring to polygon"); + } + } + else if (i==0) + { + ptarray_free(pa); + lwpoly_free(poly_res); + return NULL; + } + else + ptarray_free(pa); + } + return poly_res; +} + + + +static LWCOLLECTION* lwcollection_filterm(const LWCOLLECTION *igeom,double min,double max, int returnm) +{ + LWDEBUGF(2, "Entered %s",__func__); + + uint32_t i; + + LWCOLLECTION *out = lwcollection_construct_empty(igeom->type, igeom->srid, FLAGS_GET_Z(igeom->flags),returnm * FLAGS_GET_M(igeom->flags)); + + if( lwcollection_is_empty(igeom) ) + return out; + + for( i = 0; i < igeom->ngeoms; i++ ) + { + LWGEOM *ngeom = lwgeom_filter_m_ignore_null(igeom->geoms[i],min, max,returnm); + if ( ngeom ) out = lwcollection_add_lwgeom(out, ngeom); + } + + return out; +} + +static LWGEOM* lwgeom_filter_m_ignore_null(LWGEOM *geom,double min,double max, int returnm) +{ + LWDEBUGF(2, "Entered %s",__func__); + + LWGEOM *geom_out = NULL; + + if(!FLAGS_GET_M(geom->flags)) + return geom; + switch ( geom->type ) + { + case POINTTYPE: + { + LWDEBUGF(4,"Type found is Point, %d", geom->type); + geom_out = lwpoint_as_lwgeom(lwpoint_filterm((LWPOINT*) geom, min, max,returnm)); + break; + } + case LINETYPE: + { + LWDEBUGF(4,"Type found is Linestring, %d", geom->type); + geom_out = lwline_as_lwgeom(lwline_filterm((LWLINE*) geom, min,max,returnm)); + break; + } + /* Polygon has 'nrings' and 'rings' elements */ + case POLYGONTYPE: + { + LWDEBUGF(4,"Type found is Polygon, %d", geom->type); + geom_out = lwpoly_as_lwgeom(lwpoly_filterm((LWPOLY*)geom, min, max,returnm)); + break; + } + + /* All these Collection types have 'ngeoms' and 'geoms' elements */ + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + { + LWDEBUGF(4,"Type found is collection, %d", geom->type); + geom_out = (LWGEOM*) lwcollection_filterm((LWCOLLECTION*) geom, min, max,returnm); + break; + } + /* Unknown type! */ + default: + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(geom->type)); + } + return geom_out; + +} + +LWGEOM* lwgeom_filter_m(LWGEOM *geom,double min,double max, int returnm) +{ + LWDEBUGF(2, "Entered %s",__func__); + + LWGEOM *ngeom = lwgeom_filter_m_ignore_null(geom,min, max,returnm); + + if(ngeom) + return ngeom; + else + { + switch ( geom->type ) + { + case POINTTYPE: + { + return (LWGEOM*) lwpoint_construct_empty(geom->srid, FLAGS_GET_Z(geom->flags), returnm * FLAGS_GET_M(geom->flags)); + break; + } + case LINETYPE: + { + return (LWGEOM*) lwline_construct_empty(geom->srid, FLAGS_GET_Z(geom->flags), returnm * FLAGS_GET_M(geom->flags)); + break; + } + /* Polygon has 'nrings' and 'rings' elements */ + case POLYGONTYPE: + { + return (LWGEOM*) lwpoly_construct_empty(geom->srid, FLAGS_GET_Z(geom->flags), returnm * FLAGS_GET_M(geom->flags)); + break; + } + + /* All these Collection types have 'ngeoms' and 'geoms' elements */ + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + { + return (LWGEOM*) lwcollection_construct_empty(geom->type, geom->srid, FLAGS_GET_Z(geom->flags), returnm * FLAGS_GET_M(geom->flags)); + break; + } + /* Unknown type! */ + default: + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(geom->type)); + } + } + /*Shouldn't be possible*/ + return NULL; +} + + + diff --git a/mgist-postgis/liblwgeom/lwout_encoded_polyline.c b/mgist-postgis/liblwgeom/lwout_encoded_polyline.c new file mode 100644 index 0000000..e5afdd4 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwout_encoded_polyline.c @@ -0,0 +1,139 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2014 Kashif Rasul and + * Shoaib Burq + * + **********************************************************************/ + +#include "stringbuffer.h" +#include "liblwgeom_internal.h" + +static lwvarlena_t *lwline_to_encoded_polyline(const LWLINE *, int precision); +static lwvarlena_t *lwmmpoint_to_encoded_polyline(const LWMPOINT *, int precision); +static lwvarlena_t *pointarray_to_encoded_polyline(const POINTARRAY *, int precision); + +/* takes a GEOMETRY and returns an Encoded Polyline representation */ +extern lwvarlena_t * +lwgeom_to_encoded_polyline(const LWGEOM *geom, int precision) +{ + int type = geom->type; + switch (type) + { + case LINETYPE: + return lwline_to_encoded_polyline((LWLINE*)geom, precision); + case MULTIPOINTTYPE: + return lwmmpoint_to_encoded_polyline((LWMPOINT*)geom, precision); + default: + lwerror("lwgeom_to_encoded_polyline: '%s' geometry type not supported", + lwtype_name(type)); + return NULL; + } +} + +static lwvarlena_t * +lwline_to_encoded_polyline(const LWLINE *line, int precision) +{ + return pointarray_to_encoded_polyline(line->points, precision); +} + +static lwvarlena_t * +lwmmpoint_to_encoded_polyline(const LWMPOINT *mpoint, int precision) +{ + LWLINE* line = lwline_from_lwmpoint(mpoint->srid, mpoint); + lwvarlena_t *encoded_polyline = lwline_to_encoded_polyline(line, precision); + + lwline_free(line); + return encoded_polyline; +} + +static lwvarlena_t * +pointarray_to_encoded_polyline(const POINTARRAY *pa, int precision) +{ + uint32_t i; + const POINT2D* prevPoint; + int* delta; + stringbuffer_t* sb; + double scale = pow(10, precision); + + /* Empty input is empty string */ + if (pa->npoints == 0) + { + lwvarlena_t *v = lwalloc(LWVARHDRSZ); + LWSIZE_SET(v->size, LWVARHDRSZ); + return v; + } + + delta = lwalloc(2 * sizeof(int) * pa->npoints); + + /* Take the double value and multiply it by 1x10^precision, rounding the + * result */ + prevPoint = getPoint2d_cp(pa, 0); + delta[0] = round(prevPoint->y * scale); + delta[1] = round(prevPoint->x * scale); + + /* Points only include the offset from the previous point */ + for (i = 1; i < pa->npoints; i++) + { + const POINT2D* point = getPoint2d_cp(pa, i); + delta[2 * i] = round(point->y * scale) - round(prevPoint->y * scale); + delta[(2 * i) + 1] = + round(point->x * scale) - round(prevPoint->x * scale); + prevPoint = point; + } + + /* value to binary: a negative value must be calculated using its two's + * complement */ + for (i = 0; i < pa->npoints * 2; i++) + { + /* Multiply by 2 for a signed left shift */ + delta[i] *= 2; + /* if value is negative, invert this encoding */ + if (delta[i] < 0) { + delta[i] = ~(delta[i]); + } + } + + sb = stringbuffer_create(); + for (i = 0; i < pa->npoints * 2; i++) + { + int numberToEncode = delta[i]; + + while (numberToEncode >= 0x20) + { + /* Place the 5-bit chunks into reverse order or + each value with 0x20 if another bit chunk follows and add 63*/ + int nextValue = (0x20 | (numberToEncode & 0x1f)) + 63; + stringbuffer_aprintf(sb, "%c", (char)nextValue); + + /* Break the binary value out into 5-bit chunks */ + numberToEncode >>= 5; + } + + numberToEncode += 63; + stringbuffer_aprintf(sb, "%c", (char)numberToEncode); + } + + lwfree(delta); + lwvarlena_t *v = stringbuffer_getvarlenacopy(sb); + stringbuffer_destroy(sb); + + return v; +} diff --git a/mgist-postgis/liblwgeom/lwout_geojson.c b/mgist-postgis/liblwgeom/lwout_geojson.c new file mode 100644 index 0000000..aa14748 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwout_geojson.c @@ -0,0 +1,407 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2001-2003 Refractions Research Inc. + * Copyright 2009-2010 Olivier Courtin + * + **********************************************************************/ + + +#include "liblwgeom_internal.h" +#include "stringbuffer.h" +#include /* strlen */ +#include + + +typedef struct geojson_opts { + const char *srs; + GBOX *bbox; + int precision; + int hasz; + int isCollectionElement; +} geojson_opts; + +enum { + geojson_tagged, + geojson_untagged +}; + +static void asgeojson_geometry(stringbuffer_t *sb, const LWGEOM *geom, const geojson_opts *opts); + + + +static void +coordinate_to_geojson(stringbuffer_t *sb, const POINTARRAY *pa, uint32_t i, const geojson_opts *opts) +{ + if (!FLAGS_GET_Z(pa->flags)) + { + const POINT2D *pt = getPoint2d_cp(pa, i); + stringbuffer_append_char(sb, '['); + stringbuffer_append_double(sb, pt->x, opts->precision); + stringbuffer_append_char(sb, ','); + stringbuffer_append_double(sb, pt->y, opts->precision); + stringbuffer_append_char(sb, ']'); + } + else + { + const POINT3D *pt = getPoint3d_cp(pa, i); + stringbuffer_append_char(sb, '['); + stringbuffer_append_double(sb, pt->x, opts->precision); + stringbuffer_append_char(sb, ','); + stringbuffer_append_double(sb, pt->y, opts->precision); + stringbuffer_append_char(sb, ','); + stringbuffer_append_double(sb, pt->z, opts->precision); + stringbuffer_append_char(sb, ']'); + } +} + +static void +pointArray_to_geojson(stringbuffer_t *sb, const POINTARRAY *pa, const geojson_opts *opts) +{ + if (!pa || pa->npoints == 0) + { + stringbuffer_append_len(sb, "[]", 2); + return; + } + + stringbuffer_append_char(sb, '['); + for (uint32_t i = 0; i < pa->npoints; i++) + { + if (i) stringbuffer_append_char(sb, ','); + coordinate_to_geojson(sb, pa, i, opts); + } + stringbuffer_append_char(sb, ']'); + return; +} + +static void +asgeojson_srs(stringbuffer_t *sb, const geojson_opts *opts) +{ + if (!opts->srs) return; + stringbuffer_append_len(sb, "\"crs\":{\"type\":\"name\",", 21); + stringbuffer_aprintf(sb, "\"properties\":{\"name\":\"%s\"}},", opts->srs); + return; +} + + +static void +asgeojson_bbox(stringbuffer_t *sb, const geojson_opts *opts) +{ + if (!opts->bbox) return; + if (!opts->hasz) + stringbuffer_aprintf(sb, "\"bbox\":[%.*f,%.*f,%.*f,%.*f],", + opts->precision, opts->bbox->xmin, + opts->precision, opts->bbox->ymin, + opts->precision, opts->bbox->xmax, + opts->precision, opts->bbox->ymax); + else + stringbuffer_aprintf(sb, "\"bbox\":[%.*f,%.*f,%.*f,%.*f,%.*f,%.*f],", + opts->precision, opts->bbox->xmin, + opts->precision, opts->bbox->ymin, + opts->precision, opts->bbox->zmin, + opts->precision, opts->bbox->xmax, + opts->precision, opts->bbox->ymax, + opts->precision, opts->bbox->zmax); + return; +} + + +/** + * Point Geometry + */ +static void +asgeojson_point_coords(stringbuffer_t *sb, const LWPOINT *point, const geojson_opts *opts, int tagged) +{ + if (tagged == geojson_tagged) stringbuffer_append_len(sb, "\"coordinates\":", 14); + if (lwgeom_is_empty((LWGEOM*)point)) + stringbuffer_append_len(sb, "[]", 2); + else + coordinate_to_geojson(sb, point->point, 0, opts); + return; +} + +static void +asgeojson_line_coords(stringbuffer_t *sb, const LWLINE *line, const geojson_opts *opts, int tagged) +{ + if (tagged == geojson_tagged) stringbuffer_append_len(sb, "\"coordinates\":", 14); + if (lwgeom_is_empty((LWGEOM*)line)) + stringbuffer_append_len(sb, "[]", 2); + else + pointArray_to_geojson(sb, line->points, opts); + return; +} + +static void +asgeojson_poly_coords(stringbuffer_t *sb, const LWPOLY *poly, const geojson_opts *opts, int tagged) +{ + uint32_t i; + if (tagged == geojson_tagged) stringbuffer_append_len(sb, "\"coordinates\":", 14); + if (lwgeom_is_empty((LWGEOM*)poly)) + stringbuffer_append_len(sb, "[]", 2); + else + { + stringbuffer_append_char(sb, '['); + for (i = 0; i < poly->nrings; i++) + { + if (i) stringbuffer_append_char(sb, ','); + pointArray_to_geojson(sb, poly->rings[i], opts); + } + stringbuffer_append_char(sb, ']'); + } + return; +} + +/** + * Point Geometry + */ +static void +asgeojson_point(stringbuffer_t *sb, const LWPOINT *point, const geojson_opts *opts) +{ + stringbuffer_append_len(sb, "{\"type\":\"Point\",", 16); + asgeojson_srs(sb, opts); + asgeojson_bbox(sb, opts); + asgeojson_point_coords(sb, point, opts, geojson_tagged); + stringbuffer_append_char(sb, '}'); + return; +} + +/** + * Triangle Geometry + */ +static void +asgeojson_triangle(stringbuffer_t *sb, const LWTRIANGLE *tri, const geojson_opts *opts) +{ + stringbuffer_append_len(sb, "{\"type\":\"Polygon\",", 18); + asgeojson_srs(sb, opts); + asgeojson_bbox(sb, opts); + stringbuffer_append_len(sb, "\"coordinates\":[", 15); + if (lwtriangle_is_empty(tri)) + stringbuffer_append_len(sb, "[]", 2); + else + pointArray_to_geojson(sb, tri->points, opts); + stringbuffer_append_len(sb, "]}", 2); + return; +} + +/** + * Line Geometry + */ +static void +asgeojson_line(stringbuffer_t *sb, const LWLINE *line, const geojson_opts *opts) +{ + const char tmpl[] = "{\"type\":\"LineString\","; + stringbuffer_append_len(sb, tmpl, sizeof(tmpl)-1); + asgeojson_srs(sb, opts); + asgeojson_bbox(sb, opts); + asgeojson_line_coords(sb, line, opts, geojson_tagged); + stringbuffer_append_char(sb, '}'); + return; +} + +/** + * Polygon Geometry + */ +static void +asgeojson_poly(stringbuffer_t *sb, const LWPOLY *poly, const geojson_opts *opts) +{ + stringbuffer_append_len(sb, "{\"type\":\"Polygon\",", 18); + asgeojson_srs(sb, opts); + asgeojson_bbox(sb, opts); + asgeojson_poly_coords(sb, poly, opts, geojson_tagged); + stringbuffer_append_char(sb, '}'); + return; +} + +/** + * Multipoint Geometry + */ +static void +asgeojson_multipoint(stringbuffer_t *sb, const LWMPOINT *mpoint, const geojson_opts *opts) +{ + uint32_t i, ngeoms = mpoint->ngeoms; + stringbuffer_append_len(sb, "{\"type\":\"MultiPoint\",", 21); + asgeojson_srs(sb, opts); + asgeojson_bbox(sb, opts); + stringbuffer_append_len(sb, "\"coordinates\":[", 15); + + if (lwgeom_is_empty((LWGEOM*)mpoint)) + ngeoms = 0; + + for (i=0; i < ngeoms; i++) + { + if (i) stringbuffer_append_char(sb, ','); + asgeojson_point_coords(sb, mpoint->geoms[i], opts, geojson_untagged); + } + stringbuffer_append_len(sb, "]}", 2); + return; +} + + +/** + * Multipoint Geometry + */ +static void +asgeojson_multiline(stringbuffer_t *sb, const LWMLINE *mline, const geojson_opts *opts) +{ + uint32_t i, ngeoms = mline->ngeoms; + stringbuffer_append_len(sb, "{\"type\":\"MultiLineString\",", 26); + asgeojson_srs(sb, opts); + asgeojson_bbox(sb, opts); + stringbuffer_append_len(sb, "\"coordinates\":[", 15); + + if (lwgeom_is_empty((LWGEOM*)mline)) + ngeoms = 0; + + for (i=0; i < ngeoms; i++) + { + if (i) stringbuffer_append_char(sb, ','); + asgeojson_line_coords(sb, mline->geoms[i], opts, geojson_untagged); + } + stringbuffer_append_len(sb, "]}", 2); + return; +} + + +static void +asgeojson_multipolygon(stringbuffer_t *sb, const LWMPOLY *mpoly, const geojson_opts *opts) +{ + uint32_t i, ngeoms = mpoly->ngeoms; + + stringbuffer_append_len(sb, "{\"type\":\"MultiPolygon\",", 23); + asgeojson_srs(sb, opts); + asgeojson_bbox(sb, opts); + stringbuffer_append_len(sb, "\"coordinates\":[", 15); + + if (lwgeom_is_empty((LWGEOM*)mpoly)) + ngeoms = 0; + + for (i=0; i < ngeoms; i++) + { + if (i) stringbuffer_append_char(sb, ','); + asgeojson_poly_coords(sb, mpoly->geoms[i], opts, geojson_untagged); + } + stringbuffer_append_len(sb, "]}", 2); + return; +} + +/** + * Collection Geometry + */ +static void +asgeojson_collection(stringbuffer_t *sb, const LWCOLLECTION *col, const geojson_opts *opts) +{ + uint32_t i, ngeoms = col->ngeoms; + + /* subgeometries don't get boxes or srs */ + geojson_opts subopts = *opts; + subopts.bbox = NULL; + subopts.srs = NULL; + subopts.isCollectionElement = LW_TRUE; + + stringbuffer_append_len(sb, "{\"type\":\"GeometryCollection\",", 29); + asgeojson_srs(sb, opts); + if (col->ngeoms) asgeojson_bbox(sb, opts); + stringbuffer_append_len(sb, "\"geometries\":[", 14); + + if (lwgeom_is_empty((LWGEOM*)col)) + ngeoms = 0; + + for (i=0; igeoms[i], &subopts); + } + + stringbuffer_append_len(sb, "]}", 2); + return; +} + +static void +asgeojson_geometry(stringbuffer_t *sb, const LWGEOM *geom, const geojson_opts *opts) +{ + switch (geom->type) + { + case POINTTYPE: + asgeojson_point(sb, (LWPOINT*)geom, opts); + break; + case LINETYPE: + asgeojson_line(sb, (LWLINE*)geom, opts); + break; + case POLYGONTYPE: + asgeojson_poly(sb, (LWPOLY*)geom, opts); + break; + case MULTIPOINTTYPE: + asgeojson_multipoint(sb, (LWMPOINT*)geom, opts); + break; + case MULTILINETYPE: + asgeojson_multiline(sb, (LWMLINE*)geom, opts); + break; + case MULTIPOLYGONTYPE: + asgeojson_multipolygon(sb, (LWMPOLY*)geom, opts); + break; + case TRIANGLETYPE: + asgeojson_triangle(sb, (LWTRIANGLE *)geom, opts); + break; + case TINTYPE: + case COLLECTIONTYPE: + if (opts->isCollectionElement) { + lwerror("GeoJson: geometry not supported."); + } + asgeojson_collection(sb, (LWCOLLECTION*)geom, opts); + break; + default: + lwerror("lwgeom_to_geojson: '%s' geometry type not supported", lwtype_name(geom->type)); + } +} + +/** + * Takes a GEOMETRY and returns a GeoJson representation + */ +lwvarlena_t * +lwgeom_to_geojson(const LWGEOM *geom, const char *srs, int precision, int has_bbox) +{ + GBOX static_bbox = {0}; + geojson_opts opts; + stringbuffer_t sb; + + memset(&opts, 0, sizeof(opts)); + opts.precision = precision; + opts.hasz = FLAGS_GET_Z(geom->flags); + opts.srs = srs; + + if (has_bbox) + { + /* Whether these are geography or geometry, + the GeoJSON expects a cartesian bounding box */ + lwgeom_calculate_gbox_cartesian(geom, &static_bbox); + opts.bbox = &static_bbox; + } + + /* To avoid taking a copy of the output, we make */ + /* space for the VARLENA header before starting to */ + /* serialize the geom */ + stringbuffer_init_varlena(&sb); + /* Now serialize the geometry */ + asgeojson_geometry(&sb, geom, &opts); + /* Leave the initially allocated buffer in place */ + /* and write the varlena_t metadata into the slot we */ + /* left at the start */ + return stringbuffer_getvarlena(&sb); +} diff --git a/mgist-postgis/liblwgeom/lwout_gml.c b/mgist-postgis/liblwgeom/lwout_gml.c new file mode 100644 index 0000000..a50d05d --- /dev/null +++ b/mgist-postgis/liblwgeom/lwout_gml.c @@ -0,0 +1,1097 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2011 Sandro Santilli + * Copyright 2010-2012 Oslandia + * Copyright 2001-2003 Refractions Research Inc. + * + **********************************************************************/ + + +/** +* @file GML output routines. +* +**********************************************************************/ + + +#include +#include "liblwgeom_internal.h" +#include "liblwgeom.h" +#include "stringbuffer.h" + +typedef struct +{ + const char *srs; + int precision; + int opts; + int is_patch; + const char *prefix; + const char *id; +} GML_Options; + + +static void +asgml2_ptarray(stringbuffer_t* sb, const POINTARRAY *pa, const GML_Options* opts) +{ + uint32_t i; + if ( ! FLAGS_GET_Z(pa->flags) ) + { + for (i=0; inpoints; i++) + { + const POINT2D *pt = getPoint2d_cp(pa, i); + if (i) stringbuffer_append_char(sb, ' '); + stringbuffer_append_double(sb, pt->x, opts->precision); + stringbuffer_append_char(sb, ','); + stringbuffer_append_double(sb, pt->y, opts->precision); + } + } + else + { + for (i=0; inpoints; i++) + { + const POINT3D *pt = getPoint3d_cp(pa, i); + if (i) stringbuffer_append_char(sb, ' '); + stringbuffer_append_double(sb, pt->x, opts->precision); + stringbuffer_append_char(sb, ','); + stringbuffer_append_double(sb, pt->y, opts->precision); + stringbuffer_append_char(sb, ','); + stringbuffer_append_double(sb, pt->z, opts->precision); + } + } +} + + +static void +asgml2_gbox(stringbuffer_t* sb, const GBOX *bbox, const GML_Options* opts) +{ + if (!bbox) + { + stringbuffer_aprintf(sb, "<%sBox", opts->prefix); + if (opts->srs) stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + stringbuffer_append(sb, "/>"); + return; + } + else + { + POINT4D pt = { bbox->xmin, bbox->ymin, bbox->zmin, 0.0 }; + POINTARRAY *pa = ptarray_construct_empty(FLAGS_GET_Z(bbox->flags), 0, 2); + ptarray_append_point(pa, &pt, LW_TRUE); + pt.x = bbox->xmax; pt.y = bbox->ymax; pt.z = bbox->zmax; + ptarray_append_point(pa, &pt, LW_TRUE); + + if (opts->srs) stringbuffer_aprintf(sb, "<%sBox srsName=\"%s\">", opts->prefix, opts->srs); + else stringbuffer_aprintf(sb, "<%sBox>", opts->prefix); + + stringbuffer_aprintf(sb, "<%scoordinates>", opts->prefix); + asgml2_ptarray(sb, pa, opts); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + + ptarray_free(pa); + } +} + +static void +asgml3_ptarray(stringbuffer_t* sb, const POINTARRAY *pa, const GML_Options* opts) +{ + uint32_t i; + if ( ! FLAGS_GET_Z(pa->flags) ) + { + for (i=0; inpoints; i++) + { + const POINT2D *pt = getPoint2d_cp(pa, i); + if (i) stringbuffer_append_char(sb, ' '); + if (IS_DEGREE(opts->opts)) + { + stringbuffer_append_double(sb, pt->y, opts->precision); + stringbuffer_append_char(sb, ' '); + stringbuffer_append_double(sb, pt->x, opts->precision); + } + else + { + stringbuffer_append_double(sb, pt->x, opts->precision); + stringbuffer_append_char(sb, ' '); + stringbuffer_append_double(sb, pt->y, opts->precision); + } + } + } + else + { + for (i=0; inpoints; i++) + { + const POINT3D *pt = getPoint3d_cp(pa, i); + if (i) stringbuffer_append_char(sb, ' '); + if (IS_DEGREE(opts->opts)) + { + stringbuffer_append_double(sb, pt->y, opts->precision); + stringbuffer_append_char(sb, ' '); + stringbuffer_append_double(sb, pt->x, opts->precision); + stringbuffer_append_char(sb, ' '); + stringbuffer_append_double(sb, pt->z, opts->precision); + } + else + { + stringbuffer_append_double(sb, pt->x, opts->precision); + stringbuffer_append_char(sb, ' '); + stringbuffer_append_double(sb, pt->y, opts->precision); + stringbuffer_append_char(sb, ' '); + stringbuffer_append_double(sb, pt->z, opts->precision); + } + } + } +} + + +static void +asgml3_gbox(stringbuffer_t* sb, const GBOX *bbox, const GML_Options* opts) +{ + if (!bbox) + { + stringbuffer_aprintf(sb, "<%sEnvelope", opts->prefix); + if (opts->srs) stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + stringbuffer_append(sb, "/>"); + return; + } + else + { + int dimension = FLAGS_GET_Z(bbox->flags) ? 3 : 2; + + POINTARRAY *pa = ptarray_construct_empty(FLAGS_GET_Z(bbox->flags), 0, 1); + POINT4D pt = { bbox->xmin, bbox->ymin, bbox->zmin, 0.0 }; + ptarray_append_point(pa, &pt, LW_TRUE); + + stringbuffer_aprintf(sb, "<%sEnvelope", opts->prefix); + if (opts->srs) stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + if (IS_DIMS(opts->opts)) stringbuffer_aprintf(sb, " srsDimension=\"%d\"", dimension); + stringbuffer_append(sb, ">"); + + stringbuffer_aprintf(sb, "<%slowerCorner>", opts->prefix); + asgml3_ptarray(sb, pa, opts); + stringbuffer_aprintf(sb, "", opts->prefix); + + pt.x = bbox->xmax; pt.y = bbox->ymax; pt.z =bbox->zmax; + ptarray_remove_point(pa, 0); + ptarray_append_point(pa, &pt, LW_TRUE); + + stringbuffer_aprintf(sb, "<%supperCorner>", opts->prefix); + asgml3_ptarray(sb, pa, opts); + stringbuffer_aprintf(sb, "", opts->prefix); + + stringbuffer_aprintf(sb, "", opts->prefix); + ptarray_free(pa); + } +} + +static void +asgml2_point(stringbuffer_t* sb, const LWPOINT *point, const GML_Options* opts) +{ + + stringbuffer_aprintf(sb, "<%sPoint", opts->prefix); + if (opts->srs) stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + if (lwpoint_is_empty(point)) + { + stringbuffer_append(sb, "/>"); + return; + } + stringbuffer_append(sb, ">"); + stringbuffer_aprintf(sb, "<%scoordinates>", opts->prefix); + asgml2_ptarray(sb, point->point, opts); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); +} + +static void +asgml2_line(stringbuffer_t* sb, const LWLINE *line, const GML_Options* opts) +{ + stringbuffer_aprintf(sb, "<%sLineString", opts->prefix); + if (opts->srs) stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + + if (lwline_is_empty(line)) + { + stringbuffer_append(sb, "/>"); + return; + } + stringbuffer_append(sb, ">"); + + stringbuffer_aprintf(sb, "<%scoordinates>", opts->prefix); + asgml2_ptarray(sb, line->points, opts); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); +} + +static void +asgml2_poly(stringbuffer_t* sb, const LWPOLY *poly, const GML_Options* opts) +{ + uint32_t i; + + stringbuffer_aprintf(sb, "<%sPolygon", opts->prefix); + if (opts->srs) stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + if (lwpoly_is_empty(poly)) + { + stringbuffer_append(sb, "/>"); + return; + } + stringbuffer_append(sb, ">"); + stringbuffer_aprintf(sb, "<%souterBoundaryIs>", opts->prefix); + stringbuffer_aprintf(sb, "<%sLinearRing>", opts->prefix); + stringbuffer_aprintf(sb, "<%scoordinates>", opts->prefix); + asgml2_ptarray(sb, poly->rings[0], opts); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + for (i=1; inrings; i++) + { + stringbuffer_aprintf(sb, "<%sinnerBoundaryIs>", opts->prefix); + stringbuffer_aprintf(sb, "<%sLinearRing>", opts->prefix); + stringbuffer_aprintf(sb, "<%scoordinates>", opts->prefix); + asgml2_ptarray(sb, poly->rings[i], opts); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + } + stringbuffer_aprintf(sb, "", opts->prefix); +} + +static void +asgml2_multi(stringbuffer_t* sb, const LWCOLLECTION *col, const GML_Options* opts) +{ + uint32_t i; + const char* gmltype = ""; + int type = col->type; + + /* Subgeoms don't get an SRS */ + GML_Options subopts = *opts; + subopts.srs = 0; + + if (type == MULTIPOINTTYPE) gmltype = "MultiPoint"; + else if (type == MULTILINETYPE) gmltype = "MultiLineString"; + else if (type == MULTIPOLYGONTYPE) gmltype = "MultiPolygon"; + + /* Open outmost tag */ + stringbuffer_aprintf(sb, "<%s%s", opts->prefix, gmltype); + if (opts->srs) stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + + if (!col->ngeoms) + { + stringbuffer_append(sb, "/>"); + return; + } + stringbuffer_append(sb, ">"); + + for (i=0; ingeoms; i++) + { + LWGEOM* subgeom = col->geoms[i]; + if (subgeom->type == POINTTYPE) + { + stringbuffer_aprintf(sb, "<%spointMember>", opts->prefix); + asgml2_point(sb, (LWPOINT*)subgeom, &subopts); + stringbuffer_aprintf(sb, "", opts->prefix); + } + else if (subgeom->type == LINETYPE) + { + stringbuffer_aprintf(sb, "<%slineStringMember>", opts->prefix); + asgml2_line(sb, (LWLINE*)subgeom, &subopts); + stringbuffer_aprintf(sb, "", opts->prefix); + } + else if (subgeom->type == POLYGONTYPE) + { + stringbuffer_aprintf(sb, "<%spolygonMember>", opts->prefix); + asgml2_poly(sb, (LWPOLY*)subgeom, &subopts); + stringbuffer_aprintf(sb, "", opts->prefix); + } + } + + /* Close outmost tag */ + stringbuffer_aprintf(sb, "", opts->prefix, gmltype); +} + +static void +asgml2_collection(stringbuffer_t* sb, const LWCOLLECTION *col, const GML_Options* opts) +{ + uint32_t i; + LWGEOM *subgeom; + + /* Subgeoms don't get an SRS */ + GML_Options subopts = *opts; + subopts.srs = 0; + + /* Open outmost tag */ + stringbuffer_aprintf(sb, "<%sMultiGeometry", opts->prefix); + if (opts->srs) stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + + if (!col->ngeoms) + { + stringbuffer_append(sb, "/>"); + return; + } + stringbuffer_append(sb, ">"); + + for (i=0; ingeoms; i++) + { + subgeom = col->geoms[i]; + stringbuffer_aprintf(sb, "<%sgeometryMember>", opts->prefix); + switch (subgeom->type) + { + case POINTTYPE: + asgml2_point(sb, (LWPOINT*)subgeom, &subopts); + break; + case LINETYPE: + asgml2_line(sb, (LWLINE*)subgeom, &subopts);; + break; + case POLYGONTYPE: + asgml2_poly(sb, (LWPOLY*)subgeom, &subopts); + break; + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + asgml2_multi(sb, (LWCOLLECTION*)subgeom, &subopts); + break; + case COLLECTIONTYPE: + asgml2_collection(sb, (LWCOLLECTION*)subgeom, &subopts); + break; + } + stringbuffer_aprintf(sb, "", opts->prefix); + } + stringbuffer_aprintf(sb, "", opts->prefix); +} + + + +static void +asgml3_point(stringbuffer_t* sb, const LWPOINT *point, const GML_Options* opts) +{ + int dimension = FLAGS_GET_Z(point->flags) ? 3 : 2; + + stringbuffer_aprintf(sb, "<%sPoint", opts->prefix); + if (opts->srs) stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + if (opts->id) stringbuffer_aprintf(sb, " %sid=\"%s\"", opts->prefix, opts->id); + if (lwpoint_is_empty(point)) + { + stringbuffer_append(sb, "/>"); + return; + } + + stringbuffer_append(sb, ">"); + if (IS_DIMS(opts->opts)) + stringbuffer_aprintf(sb, "<%spos srsDimension=\"%d\">", opts->prefix, dimension); + else + stringbuffer_aprintf(sb, "<%spos>", opts->prefix); + asgml3_ptarray(sb, point->point, opts); + stringbuffer_aprintf(sb, "", opts->prefix, opts->prefix); +} + +static void +asgml3_line(stringbuffer_t* sb, const LWLINE *line, const GML_Options* opts) +{ + int dimension = FLAGS_GET_Z(line->flags) ? 3 : 2; + int shortline = (opts->opts & LW_GML_SHORTLINE); + + if (shortline) + { + stringbuffer_aprintf(sb, "<%sLineString", opts->prefix); + } + else + { + stringbuffer_aprintf(sb, "<%sCurve", opts->prefix); + } + + if (opts->srs) stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + if (opts->id) stringbuffer_aprintf(sb, " %sid=\"%s\"", opts->prefix, opts->id); + + if (lwline_is_empty(line)) + { + stringbuffer_append(sb, "/>"); + return; + } + stringbuffer_append(sb, ">"); + + if (!shortline) + { + stringbuffer_aprintf(sb, "<%ssegments>", opts->prefix); + stringbuffer_aprintf(sb, "<%sLineStringSegment>", opts->prefix); + } + + if (IS_DIMS(opts->opts)) + { + stringbuffer_aprintf(sb, "<%sposList srsDimension=\"%d\">", opts->prefix, dimension); + } + else + { + stringbuffer_aprintf(sb, "<%sposList>", opts->prefix); + } + + asgml3_ptarray(sb, line->points, opts); + stringbuffer_aprintf(sb, "", opts->prefix); + + if (shortline) + { + stringbuffer_aprintf(sb, "", opts->prefix); + } + else + { + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + } +} + +static void +asgml3_poly(stringbuffer_t* sb, const LWPOLY *poly, const GML_Options* opts) +{ + uint32_t i; + int dimension = FLAGS_GET_Z(poly->flags) ? 3 : 2; + + stringbuffer_aprintf(sb, + opts->is_patch ? "<%sPolygonPatch" : "<%sPolygon", + opts->prefix); + + if (opts->srs) stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + if (opts->id) stringbuffer_aprintf(sb, " %sid=\"%s\"", opts->prefix, opts->id); + + if (lwpoly_is_empty(poly)) + { + stringbuffer_append(sb, "/>"); + return; + } + stringbuffer_append(sb, ">"); + + stringbuffer_aprintf(sb, "<%sexterior>", opts->prefix); + stringbuffer_aprintf(sb, "<%sLinearRing>", opts->prefix); + if (IS_DIMS(opts->opts)) + stringbuffer_aprintf(sb, "<%sposList srsDimension=\"%d\">", opts->prefix, dimension); + else + stringbuffer_aprintf(sb, "<%sposList>", opts->prefix); + + asgml3_ptarray(sb, poly->rings[0], opts); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + for (i=1; inrings; i++) + { + stringbuffer_aprintf(sb, "<%sinterior>", opts->prefix); + stringbuffer_aprintf(sb, "<%sLinearRing>", opts->prefix); + if (IS_DIMS(opts->opts)) + stringbuffer_aprintf(sb, "<%sposList srsDimension=\"%d\">", opts->prefix, dimension); + else + stringbuffer_aprintf(sb, "<%sposList>", opts->prefix); + asgml3_ptarray(sb, poly->rings[i], opts); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + } + + stringbuffer_aprintf(sb, + opts->is_patch ? "" : "", + opts->prefix); +} + + +static void +asgml3_circstring(stringbuffer_t* sb, const LWCIRCSTRING *circ, const GML_Options* opts) +{ + int dimension = FLAGS_GET_Z(circ->flags) ? 3 : 2; + + stringbuffer_aprintf(sb, "<%sCurve", opts->prefix); + if (opts->srs) + { + stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + } + if (opts->id) + { + stringbuffer_aprintf(sb, " %sid=\"%s\"", opts->prefix, opts->id); + } + stringbuffer_append(sb, ">"); + stringbuffer_aprintf(sb, "<%ssegments>", opts->prefix); + stringbuffer_aprintf(sb, "<%sArcString>", opts->prefix); + stringbuffer_aprintf(sb, "<%sposList", opts->prefix); + + if (IS_DIMS(opts->opts)) + { + stringbuffer_aprintf(sb, " srsDimension=\"%d\"", dimension); + } + stringbuffer_append(sb, ">"); + + asgml3_ptarray(sb, circ->points, opts); + + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); +} + +static void +asgml3_compound(stringbuffer_t* sb, const LWCOMPOUND *col, const GML_Options* opts) +{ + LWGEOM *subgeom; + uint32_t i; + int dimension = FLAGS_GET_Z(col->flags) ? 3 : 2; + + stringbuffer_aprintf(sb, "<%sCurve", opts->prefix); + if (opts->srs) stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + if (opts->id) stringbuffer_aprintf(sb, " %sid=\"%s\"", opts->prefix, opts->id); + stringbuffer_append(sb, ">"); + stringbuffer_aprintf(sb, "<%ssegments>", opts->prefix); + + for (i = 0; i < col->ngeoms; ++i) + { + subgeom = col->geoms[i]; + + if (subgeom->type != LINETYPE && subgeom->type != CIRCSTRINGTYPE) + continue; + + if (subgeom->type == LINETYPE) + { + stringbuffer_aprintf(sb, "<%sLineStringSegment>", opts->prefix); + stringbuffer_aprintf(sb, "<%sposList", opts->prefix); + if (IS_DIMS(opts->opts)) + stringbuffer_aprintf(sb, " srsDimension=\"%d\"", dimension); + + stringbuffer_append(sb, ">"); + asgml3_ptarray(sb, ((LWCIRCSTRING*)subgeom)->points, opts); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + } + else if( subgeom->type == CIRCSTRINGTYPE ) + { + stringbuffer_aprintf(sb, "<%sArcString>", opts->prefix); + stringbuffer_aprintf(sb, "<%sposList", opts->prefix); + if (IS_DIMS(opts->opts)) + { + stringbuffer_aprintf(sb, " srsDimension=\"%d\"", dimension); + } + stringbuffer_append(sb, ">"); + asgml3_ptarray(sb, ((LWLINE*)subgeom)->points, opts); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + } + } + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); +} + +static void +asgml3_curvepoly(stringbuffer_t* sb, const LWCURVEPOLY* poly, const GML_Options* opts) +{ + uint32_t i; + LWGEOM* subgeom; + int dimension = FLAGS_GET_Z(poly->flags) ? 3 : 2; + + /* Subgeoms don't get an SRS */ + GML_Options subopts = *opts; + subopts.srs = 0; + + stringbuffer_aprintf(sb, "<%sPolygon", opts->prefix); + if (opts->srs) + { + stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + } + if (opts->id) + { + stringbuffer_aprintf(sb, " %sid=\"%s\"", opts->prefix, opts->id); + } + stringbuffer_append(sb, ">"); + + for (i = 0; i < poly->nrings; ++i) + { + stringbuffer_aprintf(sb, + i ? "<%sinterior>" : "<%sexterior>", + opts->prefix); + + subgeom = poly->rings[i]; + if (subgeom->type == LINETYPE) + { + stringbuffer_aprintf(sb, "<%sLinearRing>", opts->prefix); + stringbuffer_aprintf(sb, "<%sposList", opts->prefix); + if (IS_DIMS(opts->opts)) + { + stringbuffer_aprintf(sb, " srsDimension=\"%d\"", dimension); + } + stringbuffer_append(sb, ">"); + asgml3_ptarray(sb, ((LWLINE*)subgeom)->points, opts); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + } + else if (subgeom->type == CIRCSTRINGTYPE) + { + stringbuffer_aprintf(sb, "<%sRing>", opts->prefix); + stringbuffer_aprintf(sb, "<%scurveMember>", opts->prefix); + asgml3_circstring(sb, (LWCIRCSTRING*)subgeom, &subopts); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + } + else if (subgeom->type == COMPOUNDTYPE) + { + stringbuffer_aprintf(sb, "<%sRing>", opts->prefix); + stringbuffer_aprintf(sb, "<%scurveMember>", opts->prefix); + asgml3_compound(sb, (LWCOMPOUND*)subgeom, &subopts); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + } + + stringbuffer_aprintf(sb, + i ? "" : "", + opts->prefix); + } + stringbuffer_aprintf(sb, "", opts->prefix); +} + +static void +asgml3_triangle(stringbuffer_t* sb, const LWTRIANGLE *triangle, const GML_Options* opts) +{ + int dimension = FLAGS_GET_Z(triangle->flags) ? 3 : 2; + + stringbuffer_aprintf(sb, "<%sTriangle", opts->prefix); + if (opts->srs) stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + if (opts->id) stringbuffer_aprintf(sb, " %sid=\"%s\"", opts->prefix, opts->id); + stringbuffer_append(sb, ">"); + + stringbuffer_aprintf(sb, "<%sexterior>", opts->prefix); + stringbuffer_aprintf(sb, "<%sLinearRing>", opts->prefix); + if (IS_DIMS(opts->opts)) + stringbuffer_aprintf(sb, "<%sposList srsDimension=\"%d\">", opts->prefix, dimension); + else + stringbuffer_aprintf(sb, "<%sposList>", opts->prefix); + + asgml3_ptarray(sb, triangle->points, opts); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); + + stringbuffer_aprintf(sb, "", opts->prefix); +} + + +static void +asgml3_multi(stringbuffer_t* sb, const LWCOLLECTION *col, const GML_Options* opts) +{ + int type = col->type; + uint32_t i; + LWGEOM *subgeom; + /* Subgeoms don't get an SRS */ + GML_Options subopts = *opts; + subopts.srs = 0; + + const char* gmltype = ""; + + if (type == MULTIPOINTTYPE) gmltype = "MultiPoint"; + else if (type == MULTILINETYPE) gmltype = "MultiCurve"; + else if (type == MULTIPOLYGONTYPE) gmltype = "MultiSurface"; + + /* Open outmost tag */ + stringbuffer_aprintf(sb, "<%s%s", opts->prefix, gmltype); + if (opts->srs) stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + if (opts->id) stringbuffer_aprintf(sb, " %sid=\"%s\"", opts->prefix, opts->id); + + if (!col->ngeoms) + { + stringbuffer_append(sb, "/>"); + return; + } + stringbuffer_append(sb, ">"); + + for (i=0; ingeoms; i++) + { + subgeom = col->geoms[i]; + if (subgeom->type == POINTTYPE) + { + stringbuffer_aprintf(sb, "<%spointMember>", opts->prefix); + asgml3_point(sb, (LWPOINT*)subgeom, &subopts); + stringbuffer_aprintf(sb, "", opts->prefix); + } + else if (subgeom->type == LINETYPE) + { + stringbuffer_aprintf(sb, "<%scurveMember>", opts->prefix); + asgml3_line(sb, (LWLINE*)subgeom, &subopts); + stringbuffer_aprintf(sb, "", opts->prefix); + } + else if (subgeom->type == POLYGONTYPE) + { + stringbuffer_aprintf(sb, "<%ssurfaceMember>", opts->prefix); + asgml3_poly(sb, (LWPOLY*)subgeom, &subopts); + stringbuffer_aprintf(sb, "", opts->prefix); + } + } + + /* Close outmost tag */ + stringbuffer_aprintf(sb, "", opts->prefix, gmltype); +} + +/* + * Don't call this with single-geoms inspected! + */ +static void +asgml3_tin(stringbuffer_t* sb, const LWTIN *tin, const GML_Options* opts) +{ + uint32_t i; + + /* Subgeoms don't get an SRS */ + GML_Options subopts = *opts; + subopts.srs = 0; + + /* Open outmost tag */ + stringbuffer_aprintf(sb, "<%sTin", opts->prefix); + if (opts->srs) + stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + if (opts->id) + stringbuffer_aprintf(sb, " %sid=\"%s\"", opts->prefix, opts->id); + stringbuffer_append(sb, ">"); + + stringbuffer_aprintf(sb, "<%strianglePatches>", opts->prefix); + for (i=0; ingeoms; i++) + { + asgml3_triangle(sb, tin->geoms[i], &subopts); + } + + /* Close outmost tag */ + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); +} + +static void +asgml3_psurface(stringbuffer_t* sb, const LWPSURFACE *psur, const GML_Options* opts) +{ + uint32_t i; + + /* Subgeoms don't get an SRS */ + GML_Options subopts = *opts; + subopts.srs = 0; + subopts.is_patch = 1; + + /* Open outmost tag */ + stringbuffer_aprintf(sb, "<%sPolyhedralSurface", opts->prefix); + if (opts->srs) + stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + if (opts->id) + stringbuffer_aprintf(sb, " %sid=\"%s\"", opts->prefix, opts->id); + stringbuffer_append(sb, ">"); + stringbuffer_aprintf(sb, "<%spolygonPatches>", opts->prefix); + + for (i=0; ingeoms; i++) + { + asgml3_poly(sb, psur->geoms[i], &subopts); + } + + /* Close outmost tag */ + stringbuffer_aprintf(sb, "", opts->prefix); + stringbuffer_aprintf(sb, "", opts->prefix); +} + + +static void +asgml3_collection(stringbuffer_t* sb, const LWCOLLECTION *col, const GML_Options* opts) +{ + uint32_t i; + LWGEOM *subgeom; + + /* Subgeoms don't get an SRS */ + GML_Options subopts = *opts; + subopts.srs = 0; + + /* Open outmost tag */ + stringbuffer_aprintf(sb, "<%sMultiGeometry", opts->prefix); + if (opts->srs) stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + if (opts->id) stringbuffer_aprintf(sb, " %sid=\"%s\"", opts->prefix, opts->id); + + if (!col->ngeoms) + { + stringbuffer_append(sb, "/>"); + return; + } + stringbuffer_append(sb, ">"); + + for (i=0; ingeoms; i++) + { + subgeom = col->geoms[i]; + stringbuffer_aprintf(sb, "<%sgeometryMember>", opts->prefix); + + switch (subgeom->type) + { + case POINTTYPE: + asgml3_point(sb, (LWPOINT*)subgeom, &subopts); + break; + case LINETYPE: + asgml3_line(sb, (LWLINE*)subgeom, &subopts); + break; + case POLYGONTYPE: + asgml3_poly(sb, (LWPOLY*)subgeom, &subopts); + break; + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + asgml3_multi(sb, (LWCOLLECTION*)subgeom, &subopts); + break; + case COLLECTIONTYPE: + asgml3_collection(sb, (LWCOLLECTION*)subgeom, &subopts); + break; + default: + lwerror("asgml3_collection: unknown geometry type"); + } + stringbuffer_aprintf(sb, "", opts->prefix); + } + + /* Close outmost tag */ + stringbuffer_aprintf(sb, "", opts->prefix); +} + +static void +asgml3_multicurve(stringbuffer_t*sb, const LWMCURVE* cur, const GML_Options* opts) +{ + LWGEOM* subgeom; + uint32_t i; + + stringbuffer_aprintf(sb, "<%sMultiCurve", opts->prefix); + if (opts->srs) + { + stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + } + if (opts->id) + { + stringbuffer_aprintf(sb, " %sid=\"%s\"", opts->prefix, opts->id); + } + stringbuffer_append(sb, ">"); + + for (i = 0; i < cur->ngeoms; ++i) + { + stringbuffer_aprintf(sb, "<%scurveMember>", opts->prefix); + subgeom = cur->geoms[i]; + if (subgeom->type == LINETYPE) + { + asgml3_line(sb, (LWLINE*)subgeom, opts); + } + else if (subgeom->type == CIRCSTRINGTYPE) + { + asgml3_circstring(sb, (LWCIRCSTRING*)subgeom, opts); + } + else if (subgeom->type == COMPOUNDTYPE) + { + asgml3_compound(sb, (LWCOMPOUND*)subgeom, opts); + } + stringbuffer_aprintf(sb, "", opts->prefix); + } + stringbuffer_aprintf(sb, "", opts->prefix); +} + + +static void +asgml3_multisurface(stringbuffer_t* sb, const LWMSURFACE *sur, const GML_Options* opts) +{ + uint32_t i; + LWGEOM* subgeom; + + stringbuffer_aprintf(sb, "<%sMultiSurface", opts->prefix); + if (opts->srs) stringbuffer_aprintf(sb, " srsName=\"%s\"", opts->srs); + if (opts->id) stringbuffer_aprintf(sb, " %sid=\"%s\"", opts->prefix, opts->id); + + stringbuffer_append(sb, ">"); + + for (i = 0; i < sur->ngeoms; ++i) + { + subgeom = sur->geoms[i]; + if (subgeom->type == POLYGONTYPE) + { + asgml3_poly(sb, (LWPOLY*)sur->geoms[i], opts); + } + else if (subgeom->type == CURVEPOLYTYPE) + { + asgml3_curvepoly(sb, (LWCURVEPOLY*)sur->geoms[i], opts); + } + } + stringbuffer_aprintf(sb, "", opts->prefix); +} + +extern lwvarlena_t * +lwgeom_to_gml2(const LWGEOM *geom, const char *srs, int precision, const char *prefix) +{ + stringbuffer_t sb; + + /* Initialize options */ + GML_Options gmlopts; + memset(&gmlopts, 0, sizeof(gmlopts)); + gmlopts.srs = srs; + gmlopts.precision = precision; + gmlopts.prefix = prefix; + + /* Return null for empty (#1377) */ + if (lwgeom_is_empty(geom)) + return NULL; + + stringbuffer_init_varlena(&sb); + + switch (geom->type) + { + case POINTTYPE: + asgml2_point(&sb, (LWPOINT*)geom, &gmlopts); + break; + + case LINETYPE: + asgml2_line(&sb, (LWLINE*)geom, &gmlopts); + break; + + case POLYGONTYPE: + asgml2_poly(&sb, (LWPOLY*)geom, &gmlopts); + break; + + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + asgml2_multi(&sb, (LWCOLLECTION*)geom, &gmlopts); + break; + + case COLLECTIONTYPE: + asgml2_collection(&sb, (LWCOLLECTION*)geom, &gmlopts); + break; + + case TRIANGLETYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + lwerror("Cannot convert %s to GML2. Try ST_AsGML(3, ) to generate GML3.", lwtype_name(geom->type)); + stringbuffer_release(&sb); + return NULL; + + default: + lwerror("lwgeom_to_gml2: '%s' geometry type not supported", lwtype_name(geom->type)); + stringbuffer_release(&sb); + return NULL; + } + + return stringbuffer_getvarlena(&sb); +} + +extern lwvarlena_t * +lwgeom_to_gml3(const LWGEOM *geom, const char *srs, int precision, int opts, const char *prefix, const char *id) +{ + stringbuffer_t sb; + + /* Initialize options */ + GML_Options gmlopts; + memset(&gmlopts, 0, sizeof(gmlopts)); + gmlopts.srs = srs; + gmlopts.precision = precision; + gmlopts.opts = opts; + gmlopts.prefix = prefix; + gmlopts.id = id; + + /* Return null for empty (#1377) */ + if (lwgeom_is_empty(geom)) + return NULL; + + stringbuffer_init_varlena(&sb); + + switch (geom->type) + { + case POINTTYPE: + asgml3_point(&sb, (LWPOINT*)geom, &gmlopts); + break; + + case LINETYPE: + asgml3_line(&sb, (LWLINE*)geom, &gmlopts); + break; + + case CIRCSTRINGTYPE: + asgml3_circstring(&sb, (LWCIRCSTRING*)geom, &gmlopts ); + break; + + case POLYGONTYPE: + asgml3_poly(&sb, (LWPOLY*)geom, &gmlopts); + break; + + case CURVEPOLYTYPE: + asgml3_curvepoly(&sb, (LWCURVEPOLY*)geom, &gmlopts); + break; + + case TRIANGLETYPE: + asgml3_triangle(&sb, (LWTRIANGLE*)geom, &gmlopts); + break; + + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + asgml3_multi(&sb, (LWCOLLECTION*)geom, &gmlopts); + break; + + case POLYHEDRALSURFACETYPE: + asgml3_psurface(&sb, (LWPSURFACE*)geom, &gmlopts); + break; + + case TINTYPE: + asgml3_tin(&sb, (LWTIN*)geom, &gmlopts); + break; + + case COLLECTIONTYPE: + asgml3_collection(&sb, (LWCOLLECTION*)geom, &gmlopts); + break; + + case COMPOUNDTYPE: + asgml3_compound(&sb, (LWCOMPOUND*)geom, &gmlopts ); + break; + + case MULTICURVETYPE: + asgml3_multicurve(&sb, (LWMCURVE*)geom, &gmlopts ); + break; + + case MULTISURFACETYPE: + asgml3_multisurface(&sb, (LWMSURFACE*)geom, &gmlopts ); + break; + + default: + lwerror("lwgeom_to_gml3: '%s' geometry type not supported", lwtype_name(geom->type)); + stringbuffer_release(&sb); + return NULL; + } + + return stringbuffer_getvarlena(&sb); +} + +extern lwvarlena_t * +lwgeom_extent_to_gml2(const LWGEOM *geom, const char *srs, int precision, const char *prefix) +{ + const GBOX* bbox = lwgeom_get_bbox(geom); + stringbuffer_t sb; + + /* Initialize options */ + GML_Options gmlopts; + memset(&gmlopts, 0, sizeof(gmlopts)); + gmlopts.srs = srs; + gmlopts.precision = precision; + gmlopts.prefix = prefix; + + stringbuffer_init_varlena(&sb); + asgml2_gbox(&sb, bbox, &gmlopts); + return stringbuffer_getvarlena(&sb); +} + +extern lwvarlena_t * +lwgeom_extent_to_gml3(const LWGEOM *geom, const char *srs, int precision, int opts, const char *prefix) +{ + const GBOX* bbox = lwgeom_get_bbox(geom); + stringbuffer_t sb; + + /* Initialize options */ + GML_Options gmlopts; + memset(&gmlopts, 0, sizeof(gmlopts)); + gmlopts.srs = srs; + gmlopts.precision = precision; + gmlopts.opts = opts; + gmlopts.prefix = prefix; + + stringbuffer_init_varlena(&sb); + asgml3_gbox(&sb, bbox, &gmlopts); + return stringbuffer_getvarlena(&sb); +} diff --git a/mgist-postgis/liblwgeom/lwout_kml.c b/mgist-postgis/liblwgeom/lwout_kml.c new file mode 100644 index 0000000..45ff523 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwout_kml.c @@ -0,0 +1,214 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2006 Corporacion Autonoma Regional de Santander + * Copyright 2010 Paul Ramsey + * + **********************************************************************/ + + +#include "liblwgeom_internal.h" +#include "stringbuffer.h" + +static int lwgeom_to_kml2_sb(const LWGEOM *geom, int precision, const char *prefix, stringbuffer_t *sb); +static int lwpoint_to_kml2_sb(const LWPOINT *point, int precision, const char *prefix, stringbuffer_t *sb); +static int lwline_to_kml2_sb(const LWLINE *line, int precision, const char *prefix, stringbuffer_t *sb); +static int lwtriangle_to_kml2_sb(const LWTRIANGLE *tri, int precision, const char *prefix, stringbuffer_t *sb); +static int lwpoly_to_kml2_sb(const LWPOLY *poly, int precision, const char *prefix, stringbuffer_t *sb); +static int lwcollection_to_kml2_sb(const LWCOLLECTION *col, int precision, const char *prefix, stringbuffer_t *sb); +static int ptarray_to_kml2_sb(const POINTARRAY *pa, int precision, stringbuffer_t *sb); + +/* +* KML 2.2.0 +*/ + +/* takes a GEOMETRY and returns a KML representation */ +lwvarlena_t * +lwgeom_to_kml2(const LWGEOM *geom, int precision, const char *prefix) +{ + stringbuffer_t *sb; + int rv; + + /* Can't do anything with empty */ + if( lwgeom_is_empty(geom) ) + return NULL; + + sb = stringbuffer_create(); + rv = lwgeom_to_kml2_sb(geom, precision, prefix, sb); + + if ( rv == LW_FAILURE ) + { + stringbuffer_destroy(sb); + return NULL; + } + + lwvarlena_t *v = stringbuffer_getvarlenacopy(sb); + stringbuffer_destroy(sb); + + return v; +} + +static int +lwgeom_to_kml2_sb(const LWGEOM *geom, int precision, const char *prefix, stringbuffer_t *sb) +{ + switch (geom->type) + { + case POINTTYPE: + return lwpoint_to_kml2_sb((LWPOINT*)geom, precision, prefix, sb); + + case LINETYPE: + return lwline_to_kml2_sb((LWLINE*)geom, precision, prefix, sb); + + case TRIANGLETYPE: + return lwtriangle_to_kml2_sb((LWTRIANGLE *)geom, precision, prefix, sb); + + case POLYGONTYPE: + return lwpoly_to_kml2_sb((LWPOLY*)geom, precision, prefix, sb); + + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case TINTYPE: + return lwcollection_to_kml2_sb((LWCOLLECTION*)geom, precision, prefix, sb); + + default: + lwerror("lwgeom_to_kml2: '%s' geometry type not supported", lwtype_name(geom->type)); + return LW_FAILURE; + } +} + +static int +ptarray_to_kml2_sb(const POINTARRAY *pa, int precision, stringbuffer_t *sb) +{ + uint32_t i, j; + uint32_t dims = FLAGS_GET_Z(pa->flags) ? 3 : 2; + POINT4D pt; + double *d; + + for ( i = 0; i < pa->npoints; i++ ) + { + getPoint4d_p(pa, i, &pt); + d = (double*)(&pt); + if ( i ) stringbuffer_append_len(sb," ",1); + for (j = 0; j < dims; j++) + { + if ( j ) stringbuffer_append_len(sb,",",1); + stringbuffer_append_double(sb, d[j], precision); + } + } + return LW_SUCCESS; +} + + +static int +lwpoint_to_kml2_sb(const LWPOINT *point, int precision, const char *prefix, stringbuffer_t *sb) +{ + /* Open point */ + if ( stringbuffer_aprintf(sb, "<%sPoint><%scoordinates>", prefix, prefix) < 0 ) return LW_FAILURE; + /* Coordinate array */ + if ( ptarray_to_kml2_sb(point->point, precision, sb) == LW_FAILURE ) return LW_FAILURE; + /* Close point */ + if ( stringbuffer_aprintf(sb, "", prefix, prefix) < 0 ) return LW_FAILURE; + return LW_SUCCESS; +} + +static int +lwline_to_kml2_sb(const LWLINE *line, int precision, const char *prefix, stringbuffer_t *sb) +{ + /* Open linestring */ + if ( stringbuffer_aprintf(sb, "<%sLineString><%scoordinates>", prefix, prefix) < 0 ) return LW_FAILURE; + /* Coordinate array */ + if ( ptarray_to_kml2_sb(line->points, precision, sb) == LW_FAILURE ) return LW_FAILURE; + /* Close linestring */ + if ( stringbuffer_aprintf(sb, "", prefix, prefix) < 0 ) return LW_FAILURE; + + return LW_SUCCESS; +} + +static int +lwtriangle_to_kml2_sb(const LWTRIANGLE *tri, int precision, const char *prefix, stringbuffer_t *sb) +{ + /* Open polygon */ + if (stringbuffer_aprintf( + sb, "<%sPolygon><%souterBoundaryIs><%sLinearRing><%scoordinates>", prefix, prefix, prefix, prefix) < 0) + return LW_FAILURE; + /* Coordinate array */ + if (ptarray_to_kml2_sb(tri->points, precision, sb) == LW_FAILURE) + return LW_FAILURE; + /* Close polygon */ + if (stringbuffer_aprintf( + sb, "", prefix, prefix, prefix, prefix) < + 0) + return LW_FAILURE; + + return LW_SUCCESS; +} + +static int +lwpoly_to_kml2_sb(const LWPOLY *poly, int precision, const char *prefix, stringbuffer_t *sb) +{ + uint32_t i; + int rv; + + /* Open polygon */ + if ( stringbuffer_aprintf(sb, "<%sPolygon>", prefix) < 0 ) return LW_FAILURE; + for ( i = 0; i < poly->nrings; i++ ) + { + /* Inner or outer ring opening tags */ + if( i ) + rv = stringbuffer_aprintf(sb, "<%sinnerBoundaryIs><%sLinearRing><%scoordinates>", prefix, prefix, prefix); + else + rv = stringbuffer_aprintf(sb, "<%souterBoundaryIs><%sLinearRing><%scoordinates>", prefix, prefix, prefix); + if ( rv < 0 ) return LW_FAILURE; + + /* Coordinate array */ + if ( ptarray_to_kml2_sb(poly->rings[i], precision, sb) == LW_FAILURE ) return LW_FAILURE; + + /* Inner or outer ring closing tags */ + if( i ) + rv = stringbuffer_aprintf(sb, "", prefix, prefix, prefix); + else + rv = stringbuffer_aprintf(sb, "", prefix, prefix, prefix); + if ( rv < 0 ) return LW_FAILURE; + } + /* Close polygon */ + if ( stringbuffer_aprintf(sb, "", prefix) < 0 ) return LW_FAILURE; + + return LW_SUCCESS; +} + +static int +lwcollection_to_kml2_sb(const LWCOLLECTION *col, int precision, const char *prefix, stringbuffer_t *sb) +{ + uint32_t i; + int rv; + + /* Open geometry */ + if ( stringbuffer_aprintf(sb, "<%sMultiGeometry>", prefix) < 0 ) return LW_FAILURE; + for ( i = 0; i < col->ngeoms; i++ ) + { + rv = lwgeom_to_kml2_sb(col->geoms[i], precision, prefix, sb); + if ( rv == LW_FAILURE ) return LW_FAILURE; + } + /* Close geometry */ + if ( stringbuffer_aprintf(sb, "", prefix) < 0 ) return LW_FAILURE; + + return LW_SUCCESS; +} diff --git a/mgist-postgis/liblwgeom/lwout_svg.c b/mgist-postgis/liblwgeom/lwout_svg.c new file mode 100644 index 0000000..c05efc3 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwout_svg.c @@ -0,0 +1,619 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2001-2003 Refractions Research Inc. + * + **********************************************************************/ + + +/** @file +* +* SVG output routines. +* Originally written by: Klaus F�rster +* Refactored by: Olivier Courtin (Camptocamp) +* +* BNF SVG Path: +**********************************************************************/ + +#include "liblwgeom_internal.h" +#include "stringbuffer.h" + + +static void +assvg_geom(stringbuffer_t* sb, const LWGEOM *geom, int relative, int precision); + + +static void +pointArray_svg_rel(stringbuffer_t* sb, const POINTARRAY *pa, int close_ring, int precision, int start_at_index) +{ + int i, end; + const POINT2D *pt; + + double f = 1.0; + double dx, dy, x, y, accum_x, accum_y; + + char sx[OUT_DOUBLE_BUFFER_SIZE]; + char sy[OUT_DOUBLE_BUFFER_SIZE]; + + if (precision >= 0) + { + f = pow(10, precision); + } + + end = close_ring ? pa->npoints : pa->npoints - 1; + + /* Starting point */ + pt = getPoint2d_cp(pa, start_at_index); + + x = round(pt->x*f)/f; + y = round(pt->y*f)/f; + + lwprint_double(x, precision, sx); + lwprint_double(-y, precision, sy); + + stringbuffer_aprintf(sb, "%s %s l", sx, sy); + + /* accum */ + accum_x = x; + accum_y = y; + + /* All the following ones */ + for (i = (start_at_index + 1); i < end; i++) + { + pt = getPoint2d_cp(pa, i); + + x = round(pt->x*f)/f; + y = round(pt->y*f)/f; + + dx = x - accum_x; + dy = y - accum_y; + + accum_x += dx; + accum_y += dy; + + lwprint_double(dx, precision, sx); + lwprint_double(-dy, precision, sy); + stringbuffer_aprintf(sb, " %s %s", sx, sy); + } +} + + +/** + * Returns maximum size of rendered pointarray in bytes. + */ +static void +pointArray_svg_abs(stringbuffer_t* sb, const POINTARRAY *pa, int close_ring, int precision, int start_at_index) +{ + int i, end; + const POINT2D* pt; + char sx[OUT_DOUBLE_BUFFER_SIZE]; + char sy[OUT_DOUBLE_BUFFER_SIZE]; + + end = close_ring ? pa->npoints : pa->npoints - 1; + + for (i = start_at_index; i < end; i++) + { + pt = getPoint2d_cp(pa, i); + + if (i == 1) + { + if (start_at_index > 0 ){ + stringbuffer_append(sb, "L "); + } + else { + stringbuffer_append(sb, " L "); + } + } + else if (i) stringbuffer_append(sb, " "); + + lwprint_double(pt->x, precision, sx); + lwprint_double(-(pt->y), precision, sy); + + stringbuffer_aprintf(sb, "%s %s", sx, sy); + } +} + + +static void +assvg_point(stringbuffer_t* sb, const LWPOINT *point, int circle, int precision) +{ + char sx[OUT_DOUBLE_BUFFER_SIZE]; + char sy[OUT_DOUBLE_BUFFER_SIZE]; + if ( !lwgeom_is_empty((LWGEOM*)point) ){ + const POINT2D* pt = getPoint2d_cp(point->point, 0); + lwprint_double(pt->x, precision, sx); + lwprint_double(-(pt->y), precision, sy); + + stringbuffer_aprintf(sb, + circle ? "x=\"%s\" y=\"%s\"" : "cx=\"%s\" cy=\"%s\"", + sx, sy); + } +} + + +static void +assvg_line(stringbuffer_t* sb, const LWLINE *line, int relative, int precision) +{ + /* Start path with SVG MoveTo */ + stringbuffer_append(sb, "M "); + if (relative) + pointArray_svg_rel(sb, line->points, 1, precision, 0); + else + pointArray_svg_abs(sb, line->points, 1, precision, 0); +} + +static void pointArray_svg_arc(stringbuffer_t* sb, const POINTARRAY *pa, int close_ring, int relative, int precision) +{ + uint32_t i; //, end; + char sx[OUT_DOUBLE_BUFFER_SIZE]; + char sy[OUT_DOUBLE_BUFFER_SIZE]; + + LWDEBUG(2, "pointArray_svg_arc called."); + + for (i = 2; i < pa->npoints; i+=2) + { + LWDEBUGF(3, "assvg_circstring: arc ending at point %d", i); + int largeArcFlag, sweepFlag, clockwise; + int is_circle = LW_FALSE; + double a1, a3;/**angles**/ + double radius; /* Arc radius */ + double total_angle; + POINT2D center; + const POINT2D *t1; + const POINT2D *t2; + const POINT2D *t3; + int p2_side = 0; + t1 = getPoint2d_cp(pa, i - 2); + t2 = getPoint2d_cp(pa, i - 1); + t3 = getPoint2d_cp(pa, i); + radius = lw_arc_center(t1, t2, t3, ¢er); + if ( t1->x == t3->x && t1->y == t3->y ){ + is_circle = LW_TRUE; + } + p2_side = lw_segment_side(t1, t3, t2); + if ( p2_side == -1 ) + clockwise = LW_TRUE; + else + clockwise = LW_FALSE; + /* Angles of each point that defines the arc section */ + a1 = atan2(t1->y - center.y, t1->x - center.x)*180/M_PI; + //a2 = atan2(t2->y - center.y, t2->x - center.x)*180/M_PI; + a3 = atan2(t3->y - center.y, t3->x - center.x)*180/M_PI; + + LWDEBUGF(2, " center is POINT(%.15g %.15g) - radius:%g", center.x, center.y, radius); + + total_angle = clockwise ? a1 - a3 : a3 - a1; + if (total_angle < 0 ){ + total_angle += 360; + } + + //stringbuffer_aprintf(sb, "angles (a1 a2 a3): %g %g %g is_circle: %d, total_angle: %g, t1.x: %f, t3.x: %f, t1.y: %f, t3.y: %f ", a1, a2, a3, is_circle, total_angle, t1->x, t3->x, t1->y, t3->y); + + /** endAngle - startAngle <= 180 ? "0" : "1" **/ + largeArcFlag = (total_angle <= 180)? 0 : 1; + /* The side of the t1/t3 line that t2 falls on dictates the sweep + direction from t1 to t3. */ + sweepFlag = (p2_side == -1) ? 1 : 0; + if ( (i == 2) && !is_circle ){ + /** add MoveTo first point **/ + lwprint_double(t1->x, precision, sx); + lwprint_double(-(t1->y), precision, sy); + stringbuffer_aprintf(sb, "%s %s", sx, sy); + } + /** is circle: need to start at center of circle **/ + if ( (i == 2) && is_circle){ + /** add MoveTo center of circle **/ + lwprint_double(center.x, precision, sx); + lwprint_double(-(center.y), precision, sy); + stringbuffer_aprintf(sb, "%s %s", sx, sy); + } + lwprint_double(radius, precision, sx); + lwprint_double(0, precision, sy); + /** is circle need to handle differently **/ + if (is_circle){ + //https://stackoverflow.com/questions/5737975/circle-drawing-with-svgs-arc-path + lwprint_double(radius*2, precision, sy); + stringbuffer_aprintf(sb, " m %s 0 a %s %s 0 1 0 -%s 0", sx, sx, sx, sy); + stringbuffer_aprintf(sb, " a %s %s 0 1 0 %s 0", sx, sx, sy); + } + else { + /* Arc radius radius 0 arcflag swipeflag */ + if (relative){ + stringbuffer_aprintf(sb, " a %s %s 0 %d %d ", sx, sx, largeArcFlag, sweepFlag); + } + else { + stringbuffer_aprintf(sb, " A %s %s 0 %d %d ", sx, sx, largeArcFlag, sweepFlag); + } + lwprint_double(t3->x, precision, sx); + lwprint_double(-(t3->y), precision, sy); + /* end point */ + stringbuffer_aprintf(sb, "%s %s", sx, sy); + } + } +} + +static void +assvg_circstring(stringbuffer_t* sb, const LWCIRCSTRING *icurve, int relative, int precision) +{ + /* Start path with SVG MoveTo */ + stringbuffer_append(sb, "M "); + pointArray_svg_arc(sb, icurve->points, 1, relative, precision); +} + +static void +assvg_compound(stringbuffer_t* sb, const LWCOMPOUND *icompound, int relative, int precision) +{ + uint32_t i; + LWGEOM *geom; + LWCIRCSTRING *tmpc = NULL; + LWLINE *tmpl = NULL; + /* Start path with SVG MoveTo */ + stringbuffer_append(sb, "M "); + + for (i = 0; i < icompound->ngeoms; i++) + { + if (i) stringbuffer_append(sb, " "); /* SVG whitespace Separator */ + geom = icompound->geoms[i]; + + switch (geom->type) + { + case CIRCSTRINGTYPE: + tmpc = (LWCIRCSTRING *)geom; + pointArray_svg_arc(sb, tmpc->points, 1, relative, precision); + break; + + case LINETYPE: + tmpl = (LWLINE *)geom; + + if (i){ + /** if the compound curve does not start with a line, + * we need to skip the first point since it is the same as previous + * point of previous curve **/ + if (relative) + pointArray_svg_rel(sb, tmpl->points, 1, precision, 1); + else + pointArray_svg_abs(sb, tmpl->points, 1, precision, 1); + } + else { + if (relative) + pointArray_svg_rel(sb, tmpl->points, 1, precision, 0); + else + pointArray_svg_abs(sb, tmpl->points, 1, precision, 0); + } + break; + + default: + break; /** in theory this should never happen **/ + } + } +} + +static void +assvg_polygon(stringbuffer_t* sb, const LWPOLY *poly, int relative, int precision) +{ + uint32_t i; + + for (i = 0; inrings; i++) + { + if (i) stringbuffer_append(sb, " "); /* Space beetween each ring */ + stringbuffer_append(sb, "M "); /* Start path with SVG MoveTo */ + + if (relative) + { + pointArray_svg_rel(sb, poly->rings[i], 0, precision, 0); + stringbuffer_append(sb, " z"); /* SVG closepath */ + } + else + { + pointArray_svg_abs(sb, poly->rings[i], 0, precision, 0); + stringbuffer_append(sb, " Z"); /* SVG closepath */ + } + } +} + +static void +assvg_multipoint(stringbuffer_t* sb, const LWMPOINT *mpoint, int relative, int precision) +{ + const LWPOINT *point; + uint32_t i; + + for (i = 0; ingeoms; i++) + { + if (i) stringbuffer_append(sb, ","); /* Arbitrary comma separator */ + point = mpoint->geoms[i]; + assvg_point(sb, point, relative, precision); + } +} + + +static void +assvg_multiline(stringbuffer_t* sb, const LWMLINE *mline, int relative, int precision) +{ + const LWLINE *line; + uint32_t i; + + for (i = 0; ingeoms; i++) + { + if (i) stringbuffer_append(sb, " "); /* SVG whitespace Separator */ + line = mline->geoms[i]; + assvg_line(sb, line, relative, precision); + } +} + +static void +assvg_multipolygon(stringbuffer_t* sb, const LWMPOLY *mpoly, int relative, int precision) +{ + const LWPOLY *poly; + uint32_t i; + + for (i = 0; ingeoms; i++) + { + if (i) stringbuffer_append(sb, " "); /* SVG whitespace Separator */ + poly = mpoly->geoms[i]; + assvg_polygon(sb, poly, relative, precision); + } +} + +static void +assvg_multicurve(stringbuffer_t* sb, const LWMCURVE *mcurve, int relative, int precision) +{ + uint32_t i; + LWGEOM *geom; + const LWCIRCSTRING *tmpc = NULL; + const LWLINE *tmpl = NULL; + + for (i = 0; i < mcurve->ngeoms; i++) + { + if (i) stringbuffer_append(sb, " "); /* SVG whitespace Separator */ + geom = mcurve->geoms[i]; + + switch (geom->type) + { + case CIRCSTRINGTYPE: + tmpc = (LWCIRCSTRING *)geom; + assvg_circstring(sb, tmpc, relative, precision); + break; + + case LINETYPE: + tmpl = (LWLINE *)geom; + assvg_line(sb, tmpl, relative, precision); + break; + + default: + break; /** in theory this should never happen **/ + } + } +} + +static void +assvg_curvepoly(stringbuffer_t* sb, const LWCURVEPOLY *curvepoly, int relative, int precision) +{ + uint32_t i; + LWGEOM *tmp; + + for (i = 0; i < curvepoly->nrings; i++) + { + if (i) stringbuffer_append(sb, " "); /* Space between each ring */ + tmp = curvepoly->rings[i]; + switch (tmp->type) + { + case CIRCSTRINGTYPE: + assvg_circstring(sb, (LWCIRCSTRING *)tmp, relative, precision); + break; + + case LINETYPE: + assvg_line(sb, (LWLINE *)tmp, relative, precision); + break; + + case COMPOUNDTYPE: + assvg_compound(sb, (LWCOMPOUND *)tmp, relative, precision); + break; + + default: + break; /** in theory this should never happen **/ + } + if (relative) + { + stringbuffer_append(sb, " z"); /* SVG closepath */ + } + else + { + stringbuffer_append(sb, " Z"); /* SVG closepath */ + } + } +} + +static void +assvg_multisurface(stringbuffer_t* sb, const LWMSURFACE *msurface, int relative, int precision) +{ + LWGEOM *geom; + uint32_t i; + const LWPOLY *poly; + const LWCURVEPOLY *curvepoly; + + for (i = 0; i < msurface->ngeoms; i++) + { + if (i) stringbuffer_append(sb, " "); /* SVG whitespace Separator */ + geom = msurface->geoms[i]; + switch (geom->type) + { + case CURVEPOLYTYPE: + curvepoly = (LWCURVEPOLY *) geom; + assvg_curvepoly(sb, curvepoly, relative, precision); + break; + case POLYGONTYPE: + poly = (LWPOLY *) geom; + assvg_polygon(sb, poly, relative, precision); + break; + default: + break; /** in theory this should never happen **/ + } + } +} + +static void +assvg_collection(stringbuffer_t* sb, const LWCOLLECTION *col, int relative, int precision) +{ + uint32_t i; uint32_t j = 0; + const LWGEOM *subgeom; + + /* EMPTY GEOMETRYCOLLECTION */ + if (col->ngeoms == 0) return; + + for (i = 0; ingeoms; i++) + { + subgeom = col->geoms[i]; + if (!lwgeom_is_empty(subgeom) ){ + /** Note the j is to prevent adding a ; + * if the first geometry is empty, but subsequent aren't + **/ + if (j) stringbuffer_append(sb, ";"); + j++; + assvg_geom(sb, subgeom, relative, precision); + } + } + +} + + + +static void +assvg_geom(stringbuffer_t* sb, const LWGEOM *geom, int relative, int precision) +{ + int type = geom->type; + + switch (type) + { + case POINTTYPE: + assvg_point(sb, (LWPOINT*)geom, relative, precision); + break; + + case LINETYPE: + assvg_line(sb, (LWLINE*)geom, relative, precision); + break; + + case POLYGONTYPE: + assvg_polygon(sb, (LWPOLY*)geom, relative, precision); + break; + + case MULTIPOINTTYPE: + assvg_multipoint(sb, (LWMPOINT*)geom, relative, precision); + break; + + case MULTILINETYPE: + assvg_multiline(sb, (LWMLINE*)geom, relative, precision); + break; + + case MULTIPOLYGONTYPE: + assvg_multipolygon(sb, (LWMPOLY*)geom, relative, precision); + break; + + case CIRCSTRINGTYPE: + assvg_circstring(sb, (LWCIRCSTRING*)geom, relative, precision); + break; + + case COMPOUNDTYPE: + assvg_compound(sb, (LWCOMPOUND*)geom, relative, precision); + break; + + case CURVEPOLYTYPE: + assvg_curvepoly(sb, (LWCURVEPOLY*)geom, relative, precision); + break; + + case MULTICURVETYPE: + assvg_multicurve(sb, (LWMCURVE*)geom, relative, precision); + break; + + case MULTISURFACETYPE: + assvg_multisurface(sb, (LWMSURFACE*)geom, relative, precision); + break; + + default: + lwerror("assvg_geom_buf: '%s' geometry type not supported.", + lwtype_name(type)); + } + +} + +/** + * Takes a GEOMETRY and returns a SVG representation + */ +lwvarlena_t * +lwgeom_to_svg(const LWGEOM *geom, int precision, int relative) +{ + stringbuffer_t sb; + int type = geom->type; + + /* Empty varlena for empties */ + if(lwgeom_is_empty(geom)) + { + lwvarlena_t *v = lwalloc(LWVARHDRSZ); + LWSIZE_SET(v->size, LWVARHDRSZ); + return v; + } + + stringbuffer_init_varlena(&sb); + + switch (type) + { + case POINTTYPE: + assvg_point(&sb, (LWPOINT*)geom, relative, precision); + break; + case LINETYPE: + assvg_line(&sb, (LWLINE*)geom, relative, precision); + break; + case POLYGONTYPE: + assvg_polygon(&sb, (LWPOLY*)geom, relative, precision); + break; + case CIRCSTRINGTYPE: + assvg_circstring(&sb, (LWCIRCSTRING*)geom, relative, precision); + break; + case COMPOUNDTYPE: + assvg_compound(&sb, (LWCOMPOUND*)geom, relative, precision); + break; + case CURVEPOLYTYPE: + assvg_curvepoly(&sb, (LWCURVEPOLY*)geom, relative, precision); + break; + case MULTIPOINTTYPE: + assvg_multipoint(&sb, (LWMPOINT*)geom, relative, precision); + break; + case MULTILINETYPE: + assvg_multiline(&sb, (LWMLINE*)geom, relative, precision); + break; + case MULTICURVETYPE: + assvg_multicurve(&sb, (LWMCURVE*)geom, relative, precision); + break; + case MULTIPOLYGONTYPE: + assvg_multipolygon(&sb, (LWMPOLY*)geom, relative, precision); + break; + case MULTISURFACETYPE: + assvg_multisurface(&sb, (LWMSURFACE*)geom, relative, precision); + break; + case COLLECTIONTYPE: + assvg_collection(&sb, (LWCOLLECTION*)geom, relative, precision); + break; + + default: + lwerror("lwgeom_to_svg: '%s' geometry type not supported", lwtype_name(type)); + } + + return stringbuffer_getvarlena(&sb); +} + diff --git a/mgist-postgis/liblwgeom/lwout_twkb.c b/mgist-postgis/liblwgeom/lwout_twkb.c new file mode 100644 index 0000000..6d099f6 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwout_twkb.c @@ -0,0 +1,641 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2013 Nicklas Avén + * + **********************************************************************/ + + +#include "lwout_twkb.h" + +/* +* GeometryType, and dimensions +*/ +static uint8_t lwgeom_twkb_type(const LWGEOM *geom) +{ + uint8_t twkb_type = 0; + + LWDEBUGF(2, "Entered lwgeom_twkb_type",0); + + switch ( geom->type ) + { + case POINTTYPE: + twkb_type = WKB_POINT_TYPE; + break; + case LINETYPE: + twkb_type = WKB_LINESTRING_TYPE; + break; + case TRIANGLETYPE: + case POLYGONTYPE: + twkb_type = WKB_POLYGON_TYPE; + break; + case MULTIPOINTTYPE: + twkb_type = WKB_MULTIPOINT_TYPE; + break; + case MULTILINETYPE: + twkb_type = WKB_MULTILINESTRING_TYPE; + break; + case MULTIPOLYGONTYPE: + twkb_type = WKB_MULTIPOLYGON_TYPE; + break; + case TINTYPE: + case COLLECTIONTYPE: + twkb_type = WKB_GEOMETRYCOLLECTION_TYPE; + break; + default: + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(geom->type)); + } + return twkb_type; +} + + +/** +* Calculates the size of the bbox in varints in the form: +* xmin, xdelta, ymin, ydelta +*/ +static size_t sizeof_bbox(TWKB_STATE *ts, int ndims) +{ + int i; + uint8_t buf[16]; + size_t size = 0; + LWDEBUGF(2, "Entered %s", __func__); + for ( i = 0; i < ndims; i++ ) + { + size += varint_s64_encode_buf(ts->bbox_min[i], buf); + size += varint_s64_encode_buf((ts->bbox_max[i] - ts->bbox_min[i]), buf); + } + return size; +} +/** +* Writes the bbox in varints in the form: +* xmin, xdelta, ymin, ydelta +*/ +static void write_bbox(TWKB_STATE *ts, int ndims) +{ + int i; + LWDEBUGF(2, "Entered %s", __func__); + for ( i = 0; i < ndims; i++ ) + { + bytebuffer_append_varint(ts->header_buf, ts->bbox_min[i]); + bytebuffer_append_varint(ts->header_buf, (ts->bbox_max[i] - ts->bbox_min[i])); + } +} + + +/** +* Stores a pointarray as varints in the buffer +* @register_npoints, controls whether an npoints entry is added to the buffer (used to skip npoints for point types) +* @dimension, states the dimensionality of object this array is part of (0 = point, 1 = linear, 2 = areal) +*/ +static int ptarray_to_twkb_buf(const POINTARRAY *pa, TWKB_GLOBALS *globals, TWKB_STATE *ts, int register_npoints, uint32_t minpoints) +{ + uint32_t ndims = FLAGS_NDIMS(pa->flags); + uint32_t i, j; + bytebuffer_t b; + bytebuffer_t *b_p; + int64_t nextdelta[MAX_N_DIMS]; + int npoints = 0; + size_t npoints_offset = 0; + uint32_t max_points_left = pa->npoints; + + LWDEBUGF(2, "Entered %s", __func__); + + /* Dispense with the empty case right away */ + if ( pa->npoints == 0 && register_npoints ) + { + LWDEBUGF(4, "Register npoints:%d", pa->npoints); + bytebuffer_append_uvarint(ts->geom_buf, pa->npoints); + return 0; + } + + /* If npoints is more than 127 it is unpredictable how many bytes npoints will need */ + /* Then we have to store the deltas in a temp buffer to later add them after npoints */ + /* If noints is below 128 we know 1 byte will be needed */ + /* Then we can make room for that 1 byte at once and write to */ + /* ordinary buffer */ + if( pa->npoints > 127 ) + { + /* Independent buffer to hold the coordinates, so we can put the npoints */ + /* into the stream once we know how many points we actually have */ + bytebuffer_init_with_size(&b, 3 * ndims * pa->npoints); + b_p = &b; + } + else + { + /* We give an alias to our ordinary buffer */ + b_p = ts->geom_buf; + if ( register_npoints ) + { + /* We do not store a pointer to the place where we want the npoints value */ + /* Instead we store how far from the beginning of the buffer we want the value */ + /* That is because we otherwise will get in trouble if the buffer is reallocated */ + npoints_offset = b_p->writecursor - b_p->buf_start; + + /* We just move the cursor 1 step to make room for npoints byte */ + /* We use the function append_byte even if we have no value yet, */ + /* since that gives us the check for big enough buffer and moves the cursor */ + bytebuffer_append_byte(b_p, 0); + } + } + + for ( i = 0; i < pa->npoints; i++ ) + { + double *dbl_ptr = (double*)getPoint_internal(pa, i); + int64_t diff = 0; + + /* Write this coordinate to the buffer as a varint */ + for ( j = 0; j < ndims; j++ ) + { + /* To get the relative coordinate we don't get the distance */ + /* from the last point but instead the distance from our */ + /* last accumulated point. This is important to not build up an */ + /* accumulated error when rounding the coordinates */ + nextdelta[j] = (int64_t) llround(globals->factor[j] * dbl_ptr[j]) - ts->accum_rels[j]; + LWDEBUGF(4, "deltavalue: %d, ", nextdelta[j]); + diff += llabs(nextdelta[j]); + } + + /* Skipping the first point is not allowed */ + /* If the sum(abs()) of all the deltas was zero, */ + /* then this was a duplicate point, so we can ignore it */ + if ( i > 0 && diff == 0 && max_points_left > minpoints ) + { + max_points_left--; + continue; + } + + /* We really added a point, so... */ + npoints++; + + /* Write this vertex to the temporary buffer as varints */ + for ( j = 0; j < ndims; j++ ) + { + ts->accum_rels[j] += nextdelta[j]; + bytebuffer_append_varint(b_p, nextdelta[j]); + } + + /* See if this coordinate expands the bounding box */ + if( globals->variant & TWKB_BBOX ) + { + for ( j = 0; j < ndims; j++ ) + { + if( ts->accum_rels[j] > ts->bbox_max[j] ) + ts->bbox_max[j] = ts->accum_rels[j]; + + if( ts->accum_rels[j] < ts->bbox_min[j] ) + ts->bbox_min[j] = ts->accum_rels[j]; + } + } + + } + + if ( pa->npoints > 127 ) + { + /* Now write the temporary results into the main buffer */ + /* First the npoints */ + if ( register_npoints ) + bytebuffer_append_uvarint(ts->geom_buf, npoints); + /* Now the coordinates */ + bytebuffer_append_bytebuffer(ts->geom_buf, b_p); + + /* Clear our temporary buffer */ + bytebuffer_destroy_buffer(&b); + } + else + { + /* If we didn't use a temp buffer, we just write that npoints value */ + /* to where it belongs*/ + if ( register_npoints ) + varint_u64_encode_buf(npoints, b_p->buf_start + npoints_offset); + } + + return 0; +} + +/****************************************************************** +* POINTS +*******************************************************************/ + +static int lwpoint_to_twkb_buf(const LWPOINT *pt, TWKB_GLOBALS *globals, TWKB_STATE *ts) +{ + LWDEBUGF(2, "Entered %s", __func__); + + /* Set the coordinates (don't write npoints) */ + ptarray_to_twkb_buf(pt->point, globals, ts, 0, 1); + return 0; +} + +/****************************************************************** +* LINESTRINGS +*******************************************************************/ + +static int lwline_to_twkb_buf(const LWLINE *line, TWKB_GLOBALS *globals, TWKB_STATE *ts) +{ + LWDEBUGF(2, "Entered %s", __func__); + + /* Set the coordinates (do write npoints) */ + ptarray_to_twkb_buf(line->points, globals, ts, 1, 2); + return 0; +} + +static int +lwtriangle_to_twkb_buf(const LWTRIANGLE *tri, TWKB_GLOBALS *globals, TWKB_STATE *ts) +{ + LWDEBUGF(2, "Entered %s", __func__); + bytebuffer_append_uvarint(ts->geom_buf, (uint64_t)1); + + /* Set the coordinates (do write npoints) */ + ptarray_to_twkb_buf(tri->points, globals, ts, 1, 2); + return 0; +} + +/****************************************************************** +* POLYGONS +*******************************************************************/ + +static int lwpoly_to_twkb_buf(const LWPOLY *poly, TWKB_GLOBALS *globals, TWKB_STATE *ts) +{ + uint32_t i; + + /* Set the number of rings */ + bytebuffer_append_uvarint(ts->geom_buf, (uint64_t) poly->nrings); + + for ( i = 0; i < poly->nrings; i++ ) + { + /* Set the coordinates (do write npoints) */ + ptarray_to_twkb_buf(poly->rings[i], globals, ts, 1, 4); + } + + return 0; +} + + + +/****************************************************************** +* MULTI-GEOMETRYS (MultiPoint, MultiLinestring, MultiPolygon) +*******************************************************************/ + +static int lwmulti_to_twkb_buf(const LWCOLLECTION *col, TWKB_GLOBALS *globals, TWKB_STATE *ts) +{ + uint32_t i; + int nempty = 0; + + LWDEBUGF(2, "Entered %s", __func__); + LWDEBUGF(4, "Number of geometries in multi is %d", col->ngeoms); + + /* Deal with special case for MULTIPOINT: skip any empty points */ + if ( col->type == MULTIPOINTTYPE ) + { + for ( i = 0; i < col->ngeoms; i++ ) + if ( lwgeom_is_empty(col->geoms[i]) ) + nempty++; + } + + /* Set the number of geometries */ + bytebuffer_append_uvarint(ts->geom_buf, (uint64_t) (col->ngeoms - nempty)); + + /* We've been handed an idlist, so write it in */ + if ( ts->idlist ) + { + for ( i = 0; i < col->ngeoms; i++ ) + { + /* Skip empty points in multipoints, we can't represent them */ + if ( col->type == MULTIPOINTTYPE && lwgeom_is_empty(col->geoms[i]) ) + continue; + + bytebuffer_append_varint(ts->geom_buf, ts->idlist[i]); + } + + /* Empty it out to nobody else uses it now */ + ts->idlist = NULL; + } + + for ( i = 0; i < col->ngeoms; i++ ) + { + /* Skip empty points in multipoints, we can't represent them */ + if ( col->type == MULTIPOINTTYPE && lwgeom_is_empty(col->geoms[i]) ) + continue; + + lwgeom_to_twkb_buf(col->geoms[i], globals, ts); + } + return 0; +} + +/****************************************************************** +* GEOMETRYCOLLECTIONS +*******************************************************************/ + +static int lwcollection_to_twkb_buf(const LWCOLLECTION *col, TWKB_GLOBALS *globals, TWKB_STATE *ts) +{ + uint32_t i; + + LWDEBUGF(2, "Entered %s", __func__); + LWDEBUGF(4, "Number of geometries in collection is %d", col->ngeoms); + + /* Set the number of geometries */ + bytebuffer_append_uvarint(ts->geom_buf, (uint64_t) col->ngeoms); + + /* We've been handed an idlist, so write it in */ + if ( ts->idlist ) + { + for ( i = 0; i < col->ngeoms; i++ ) + bytebuffer_append_varint(ts->geom_buf, ts->idlist[i]); + + /* Empty it out to nobody else uses it now */ + ts->idlist = NULL; + } + + /* Write in the sub-geometries */ + for ( i = 0; i < col->ngeoms; i++ ) + { + lwgeom_write_to_buffer(col->geoms[i], globals, ts); + } + return 0; +} + + +/****************************************************************** +* Handle whole TWKB +*******************************************************************/ + +static int lwgeom_to_twkb_buf(const LWGEOM *geom, TWKB_GLOBALS *globals, TWKB_STATE *ts) +{ + LWDEBUGF(2, "Entered %s", __func__); + + switch ( geom->type ) + { + case POINTTYPE: + { + LWDEBUGF(4,"Type found is Point, %d", geom->type); + return lwpoint_to_twkb_buf((LWPOINT*) geom, globals, ts); + } + case LINETYPE: + { + LWDEBUGF(4,"Type found is Linestring, %d", geom->type); + return lwline_to_twkb_buf((LWLINE*) geom, globals, ts); + } + case TRIANGLETYPE: + { + LWDEBUGF(4, "Type found is Triangle, %d", geom->type); + return lwtriangle_to_twkb_buf((LWTRIANGLE *)geom, globals, ts); + } + /* Polygon has 'nrings' and 'rings' elements */ + case POLYGONTYPE: + { + LWDEBUGF(4,"Type found is Polygon, %d", geom->type); + return lwpoly_to_twkb_buf((LWPOLY*)geom, globals, ts); + } + + /* All these Collection types have 'ngeoms' and 'geoms' elements */ + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + { + LWDEBUGF(4,"Type found is Multi, %d", geom->type); + return lwmulti_to_twkb_buf((LWCOLLECTION*)geom, globals, ts); + } + case COLLECTIONTYPE: + case TINTYPE: + { + LWDEBUGF(4,"Type found is collection, %d", geom->type); + return lwcollection_to_twkb_buf((LWCOLLECTION*) geom, globals, ts); + } + /* Unknown type! */ + default: + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(geom->type)); + } + + return 0; +} + + +static int lwgeom_write_to_buffer(const LWGEOM *geom, TWKB_GLOBALS *globals, TWKB_STATE *parent_state) +{ + int i, is_empty, has_z = 0, has_m = 0, ndims; + size_t bbox_size = 0, optional_precision_byte = 0; + uint8_t flag = 0, type_prec = 0; + bytebuffer_t header_bytebuffer, geom_bytebuffer; + + TWKB_STATE child_state; + memset(&child_state, 0, sizeof(TWKB_STATE)); + child_state.header_buf = &header_bytebuffer; + child_state.geom_buf = &geom_bytebuffer; + child_state.idlist = parent_state->idlist; + + bytebuffer_init_with_size(child_state.header_buf, 16); + bytebuffer_init_with_size(child_state.geom_buf, 64); + + /* Read dimensionality from input */ + ndims = lwgeom_ndims(geom); + is_empty = lwgeom_is_empty(geom); + if ( ndims > 2 ) + { + has_z = lwgeom_has_z(geom); + has_m = lwgeom_has_m(geom); + } + + /* Do we need extended precision? If we have a Z or M we do. */ + optional_precision_byte = (has_z || has_m); + + /* Both X and Y dimension use the same precision */ + globals->factor[0] = pow(10, globals->prec_xy); + globals->factor[1] = globals->factor[0]; + + /* Z and M dimensions have their own precisions */ + if ( has_z ) + globals->factor[2] = pow(10, globals->prec_z); + if ( has_m ) + globals->factor[2 + has_z] = pow(10, globals->prec_m); + + /* Reset stats */ + for ( i = 0; i < MAX_N_DIMS; i++ ) + { + /* Reset bbox calculation */ + child_state.bbox_max[i] = INT64_MIN; + child_state.bbox_min[i] = INT64_MAX; + /* Reset acumulated delta values to get absolute values on next point */ + child_state.accum_rels[i] = 0; + } + + /* TYPE/PRECISION BYTE */ + if ( abs(globals->prec_xy) > 7 ) + lwerror("%s: X/Z precision cannot be greater than 7 or less than -7", __func__); + + /* Read the TWKB type number from the geometry */ + TYPE_PREC_SET_TYPE(type_prec, lwgeom_twkb_type(geom)); + /* Zig-zag the precision value before encoding it since it is a signed value */ + TYPE_PREC_SET_PREC(type_prec, zigzag8(globals->prec_xy)); + /* Write the type and precision byte */ + bytebuffer_append_byte(child_state.header_buf, type_prec); + + /* METADATA BYTE */ + /* Set first bit if we are going to store bboxes */ + FIRST_BYTE_SET_BBOXES(flag, (globals->variant & TWKB_BBOX) && ! is_empty); + /* Set second bit if we are going to store resulting size */ + FIRST_BYTE_SET_SIZES(flag, globals->variant & TWKB_SIZE); + /* There will be no ID-list (for now) */ + FIRST_BYTE_SET_IDLIST(flag, parent_state->idlist && ! is_empty); + /* Are there higher dimensions */ + FIRST_BYTE_SET_EXTENDED(flag, optional_precision_byte); + /* Empty? */ + FIRST_BYTE_SET_EMPTY(flag, is_empty); + /* Write the header byte */ + bytebuffer_append_byte(child_state.header_buf, flag); + + /* EXTENDED PRECISION BYTE (OPTIONAL) */ + /* If needed, write the extended dim byte */ + if( optional_precision_byte ) + { + uint8_t flag = 0; + + if ( has_z && ( globals->prec_z > 7 || globals->prec_z < 0 ) ) + lwerror("%s: Z precision cannot be negative or greater than 7", __func__); + + if ( has_m && ( globals->prec_m > 7 || globals->prec_m < 0 ) ) + lwerror("%s: M precision cannot be negative or greater than 7", __func__); + + HIGHER_DIM_SET_HASZ(flag, has_z); + HIGHER_DIM_SET_HASM(flag, has_m); + HIGHER_DIM_SET_PRECZ(flag, globals->prec_z); + HIGHER_DIM_SET_PRECM(flag, globals->prec_m); + bytebuffer_append_byte(child_state.header_buf, flag); + } + + /* It the geometry is empty, we're almost done */ + if ( is_empty ) + { + /* If this output is sized, write the size of */ + /* all following content, which is zero because */ + /* there is none */ + if ( globals->variant & TWKB_SIZE ) + bytebuffer_append_byte(child_state.header_buf, 0); + + bytebuffer_append_bytebuffer(parent_state->geom_buf, child_state.header_buf); + bytebuffer_destroy_buffer(child_state.header_buf); + bytebuffer_destroy_buffer(child_state.geom_buf); + return 0; + } + + /* Write the TWKB into the output buffer */ + lwgeom_to_twkb_buf(geom, globals, &child_state); + + /*If we have a header_buf, we know that this function is called inside a collection*/ + /*and then we have to merge the bboxes of the included geometries*/ + /*and put the result to the parent (the collection)*/ + if( (globals->variant & TWKB_BBOX) && parent_state->header_buf ) + { + LWDEBUG(4,"Merge bboxes"); + for ( i = 0; i < ndims; i++ ) + { + if(child_state.bbox_min[i]bbox_min[i]) + parent_state->bbox_min[i] = child_state.bbox_min[i]; + if(child_state.bbox_max[i]>parent_state->bbox_max[i]) + parent_state->bbox_max[i] = child_state.bbox_max[i]; + } + } + + /* Did we have a box? If so, how big? */ + bbox_size = 0; + if( globals->variant & TWKB_BBOX ) + { + LWDEBUG(4,"We want boxes and will calculate required size"); + bbox_size = sizeof_bbox(&child_state, ndims); + } + + /* Write the size if wanted */ + if( globals->variant & TWKB_SIZE ) + { + /* Here we have to add what we know will be written to header */ + /* buffer after size value is written */ + size_t size_to_register = bytebuffer_getlength(child_state.geom_buf); + size_to_register += bbox_size; + bytebuffer_append_uvarint(child_state.header_buf, size_to_register); + } + + if( globals->variant & TWKB_BBOX ) + write_bbox(&child_state, ndims); + + bytebuffer_append_bytebuffer(parent_state->geom_buf,child_state.header_buf); + bytebuffer_append_bytebuffer(parent_state->geom_buf,child_state.geom_buf); + + bytebuffer_destroy_buffer(child_state.header_buf); + bytebuffer_destroy_buffer(child_state.geom_buf); + return 0; +} + + +/** +* Convert LWGEOM to a char* in TWKB format. Caller is responsible for freeing +* the returned array. +*/ +lwvarlena_t * +lwgeom_to_twkb_with_idlist(const LWGEOM *geom, + int64_t *idlist, + uint8_t variant, + int8_t precision_xy, + int8_t precision_z, + int8_t precision_m) +{ + LWDEBUGF(2, "Entered %s", __func__); + LWDEBUGF(2, "variant value %x", variant); + + TWKB_GLOBALS tg; + TWKB_STATE ts; + bytebuffer_t geom_bytebuffer; + + memset(&ts, 0, sizeof(TWKB_STATE)); + memset(&tg, 0, sizeof(TWKB_GLOBALS)); + + tg.variant = variant; + tg.prec_xy = precision_xy; + tg.prec_z = precision_z; + tg.prec_m = precision_m; + + if ( idlist && ! lwgeom_is_collection(geom) ) + { + lwerror("Only collections can support ID lists"); + return NULL; + } + + if ( ! geom ) + { + LWDEBUG(4,"Cannot convert NULL into TWKB."); + lwerror("Cannot convert NULL into TWKB"); + return NULL; + } + + ts.idlist = idlist; + ts.header_buf = NULL; + ts.geom_buf = &geom_bytebuffer; + bytebuffer_init_with_size(ts.geom_buf, 512); + lwgeom_write_to_buffer(geom, &tg, &ts); + + lwvarlena_t *v = bytebuffer_get_buffer_varlena(ts.geom_buf); + bytebuffer_destroy_buffer(ts.geom_buf); + return v; +} + +lwvarlena_t * +lwgeom_to_twkb(const LWGEOM *geom, uint8_t variant, int8_t precision_xy, int8_t precision_z, int8_t precision_m) +{ + return lwgeom_to_twkb_with_idlist(geom, NULL, variant, precision_xy, precision_z, precision_m); +} + + diff --git a/mgist-postgis/liblwgeom/lwout_twkb.h b/mgist-postgis/liblwgeom/lwout_twkb.h new file mode 100644 index 0000000..b9e9c3d --- /dev/null +++ b/mgist-postgis/liblwgeom/lwout_twkb.h @@ -0,0 +1,105 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2013 Nicklas Avén + * + **********************************************************************/ + + +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2013 Nicklas Avén + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" +#include +#include "bytebuffer.h" + +/* Maximum number of geometry dimmensions that internal arrays can hold */ +#define MAX_N_DIMS 4 + +#define MAX_BBOX_SIZE 64 +#define MAX_SIZE_SIZE 8 + + +/** +* Header true/false flags +*/ + +#define FIRST_BYTE_SET_BBOXES(flag, bool) ((flag) = ((bool) ? (flag) | 0x01 : (flag) & (~0x01))) +#define FIRST_BYTE_SET_SIZES(flag, bool) ((flag) = ((bool) ? (flag) | 0x02 : (flag) & (~0x02))) +#define FIRST_BYTE_SET_IDLIST(flag, bool) ((flag) = ((bool) ? (flag) | 0x04 : (flag) & (~0x04))) +#define FIRST_BYTE_SET_EXTENDED(flag, bool) ((flag) = ((bool) ? (flag) | 0x08 : (flag) & (~0x08))) +#define FIRST_BYTE_SET_EMPTY(flag, bool) ((flag) = ((bool) ? (flag) | 0x10 : (flag) & (~0x10))) + + +/** +* Macros for manipulating the 'type_precision' int. An int8_t used as follows: +* Type 4 bits +* Precision 4 bits +*/ + +#define TYPE_PREC_SET_TYPE(flag, type) ((flag) = ((flag) & 0xF0) | (((type) & 0x0F))) +#define TYPE_PREC_SET_PREC(flag, prec) ((flag) = ((flag) & 0x0F) | (((prec) & 0x0F) << 4)) + +#define HIGHER_DIM_SET_HASZ(flag, bool) ((flag) = ((bool) ? (flag) | 0x01 : (flag) & (~0x01))) +#define HIGHER_DIM_SET_HASM(flag, bool) ((flag) = ((bool) ? (flag) | 0x02 : (flag) & (~0x02))) + +#define HIGHER_DIM_SET_PRECZ(flag, prec) ((flag) = ((flag) & 0xE3) | (((prec) & 0x07) << 2)) +#define HIGHER_DIM_SET_PRECM(flag, prec) ((flag) = ((flag) & 0x1F) | (((prec) & 0x07) << 5)) + +typedef struct +{ + /* Options defined at start */ + uint8_t variant; + int8_t prec_xy; + int8_t prec_z; + int8_t prec_m; + float factor[4]; /*What factor to multiply the coordiinates with to get the requested precision*/ +} TWKB_GLOBALS; + +typedef struct +{ + uint8_t variant; /*options that change at runtime*/ + bytebuffer_t *header_buf; + bytebuffer_t *geom_buf; + int hasz; + int hasm; + const int64_t *idlist; + int64_t bbox_min[MAX_N_DIMS]; + int64_t bbox_max[MAX_N_DIMS]; + int64_t accum_rels[MAX_N_DIMS]; /*Holds the acculmulated relative values*/ +} TWKB_STATE; + +static int lwgeom_to_twkb_buf(const LWGEOM *geom, TWKB_GLOBALS *global_values, TWKB_STATE *ts); + +static int lwpoint_to_twkb_buf(const LWPOINT *line, TWKB_GLOBALS *global_values, TWKB_STATE *ts); +static int lwline_to_twkb_buf(const LWLINE *line, TWKB_GLOBALS *global_values, TWKB_STATE *ts); +static int lwpoly_to_twkb_buf(const LWPOLY *poly, TWKB_GLOBALS *global_values, TWKB_STATE *ts); +static int lwcollection_to_twkb_buf(const LWCOLLECTION *col, TWKB_GLOBALS *global_values, TWKB_STATE *ts); +static int lwgeom_write_to_buffer(const LWGEOM *geom, TWKB_GLOBALS *global_values, TWKB_STATE *parent_state); + diff --git a/mgist-postgis/liblwgeom/lwout_wkb.c b/mgist-postgis/liblwgeom/lwout_wkb.c new file mode 100644 index 0000000..3de8b5b --- /dev/null +++ b/mgist-postgis/liblwgeom/lwout_wkb.c @@ -0,0 +1,878 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2009 Paul Ramsey + * + **********************************************************************/ + + +#include +#include // for ptrdiff_t + +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + +static uint8_t* lwgeom_to_wkb_buf(const LWGEOM *geom, uint8_t *buf, uint8_t variant); +static size_t lwgeom_to_wkb_size(const LWGEOM *geom, uint8_t variant); + +/* +* Look-up table for hex writer +*/ +static char *hexchr = "0123456789ABCDEF"; + +char* hexbytes_from_bytes(const uint8_t *bytes, size_t size) +{ + char *hex; + uint32_t i; + if ( ! bytes || ! size ) + { + lwerror("hexbutes_from_bytes: invalid input"); + return NULL; + } + hex = lwalloc(size * 2 + 1); + hex[2*size] = '\0'; + for( i = 0; i < size; i++ ) + { + /* Top four bits to 0-F */ + hex[2*i] = hexchr[bytes[i] >> 4]; + /* Bottom four bits to 0-F */ + hex[2*i+1] = hexchr[bytes[i] & 0x0F]; + } + return hex; +} + +/* +* Optional SRID +*/ +static int lwgeom_wkb_needs_srid(const LWGEOM *geom, uint8_t variant) +{ + /* Sub-components of collections inherit their SRID from the parent. + We force that behavior with the WKB_NO_SRID flag */ + if ( variant & WKB_NO_SRID ) + return LW_FALSE; + + /* We can only add an SRID if the geometry has one, and the + WKB form is extended */ + if ( (variant & WKB_EXTENDED) && lwgeom_has_srid(geom) ) + return LW_TRUE; + + /* Everything else doesn't get an SRID */ + return LW_FALSE; +} + +/* +* GeometryType +*/ +static uint32_t lwgeom_wkb_type(const LWGEOM *geom, uint8_t variant) +{ + uint32_t wkb_type = 0; + + switch ( geom->type ) + { + case POINTTYPE: + wkb_type = WKB_POINT_TYPE; + break; + case LINETYPE: + wkb_type = WKB_LINESTRING_TYPE; + break; + case POLYGONTYPE: + wkb_type = WKB_POLYGON_TYPE; + break; + case MULTIPOINTTYPE: + wkb_type = WKB_MULTIPOINT_TYPE; + break; + case MULTILINETYPE: + wkb_type = WKB_MULTILINESTRING_TYPE; + break; + case MULTIPOLYGONTYPE: + wkb_type = WKB_MULTIPOLYGON_TYPE; + break; + case COLLECTIONTYPE: + wkb_type = WKB_GEOMETRYCOLLECTION_TYPE; + break; + case CIRCSTRINGTYPE: + wkb_type = WKB_CIRCULARSTRING_TYPE; + break; + case COMPOUNDTYPE: + wkb_type = WKB_COMPOUNDCURVE_TYPE; + break; + case CURVEPOLYTYPE: + wkb_type = WKB_CURVEPOLYGON_TYPE; + break; + case MULTICURVETYPE: + wkb_type = WKB_MULTICURVE_TYPE; + break; + case MULTISURFACETYPE: + wkb_type = WKB_MULTISURFACE_TYPE; + break; + case POLYHEDRALSURFACETYPE: + wkb_type = WKB_POLYHEDRALSURFACE_TYPE; + break; + case TINTYPE: + wkb_type = WKB_TIN_TYPE; + break; + case TRIANGLETYPE: + wkb_type = WKB_TRIANGLE_TYPE; + break; + default: + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(geom->type)); + } + + if ( variant & WKB_EXTENDED ) + { + if ( FLAGS_GET_Z(geom->flags) ) + wkb_type |= WKBZOFFSET; + if ( FLAGS_GET_M(geom->flags) ) + wkb_type |= WKBMOFFSET; +/* if ( geom->srid != SRID_UNKNOWN && ! (variant & WKB_NO_SRID) ) */ + if ( lwgeom_wkb_needs_srid(geom, variant) ) + wkb_type |= WKBSRIDFLAG; + } + else if ( variant & WKB_ISO ) + { + /* Z types are in the 1000 range */ + if ( FLAGS_GET_Z(geom->flags) ) + wkb_type += 1000; + /* M types are in the 2000 range */ + if ( FLAGS_GET_M(geom->flags) ) + wkb_type += 2000; + /* ZM types are in the 1000 + 2000 = 3000 range, see above */ + } + return wkb_type; +} + +/* +* Endian +*/ +static uint8_t* endian_to_wkb_buf(uint8_t *buf, uint8_t variant) +{ + if ( variant & WKB_HEX ) + { + buf[0] = '0'; + buf[1] = ((variant & WKB_NDR) ? '1' : '0'); + return buf + 2; + } + else + { + buf[0] = ((variant & WKB_NDR) ? 1 : 0); + return buf + 1; + } +} + +/* +* SwapBytes? +*/ +static inline int wkb_swap_bytes(uint8_t variant) +{ + /* If requested variant matches machine arch, we don't have to swap! */ + if (((variant & WKB_NDR) && !IS_BIG_ENDIAN) || + ((!(variant & WKB_NDR)) && IS_BIG_ENDIAN)) + { + return LW_FALSE; + } + return LW_TRUE; +} + +/* +* Integer32 +*/ +static uint8_t * +integer_to_wkb_buf(const uint32_t ival, uint8_t *buf, uint8_t variant) +{ + uint8_t *iptr = (uint8_t *)(&ival); + int i = 0; + + if ( sizeof(int) != WKB_INT_SIZE ) + { + lwerror("Machine int size is not %d bytes!", WKB_INT_SIZE); + } + LWDEBUGF(4, "Writing value '%u'", ival); + if ( variant & WKB_HEX ) + { + int swap = wkb_swap_bytes(variant); + /* Machine/request arch mismatch, so flip byte order */ + for ( i = 0; i < WKB_INT_SIZE; i++ ) + { + int j = (swap ? WKB_INT_SIZE - 1 - i : i); + uint8_t b = iptr[j]; + /* Top four bits to 0-F */ + buf[2*i] = hexchr[b >> 4]; + /* Bottom four bits to 0-F */ + buf[2*i+1] = hexchr[b & 0x0F]; + } + return buf + (2 * WKB_INT_SIZE); + } + else + { + /* Machine/request arch mismatch, so flip byte order */ + if ( wkb_swap_bytes(variant) ) + { + for ( i = 0; i < WKB_INT_SIZE; i++ ) + { + buf[i] = iptr[WKB_INT_SIZE - 1 - i]; + } + } + /* If machine arch and requested arch match, don't flip byte order */ + else + { + memcpy(buf, iptr, WKB_INT_SIZE); + } + return buf + WKB_INT_SIZE; + } +} + +static uint8_t* double_nan_to_wkb_buf(uint8_t *buf, uint8_t variant) +{ +#define NAN_SIZE 8 + const uint8_t ndr_nan[NAN_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x7f}; + const uint8_t xdr_nan[NAN_SIZE] = {0x7f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + if ( variant & WKB_HEX ) + { + for (int i = 0; i < NAN_SIZE; i++) + { + uint8_t b = (variant & WKB_NDR) ? ndr_nan[i] : xdr_nan[i]; + /* Top four bits to 0-F */ + buf[2*i] = hexchr[b >> 4]; + /* Bottom four bits to 0-F */ + buf[2*i + 1] = hexchr[b & 0x0F]; + } + return buf + (2 * NAN_SIZE); + } + else + { + for (int i = 0; i < NAN_SIZE; i++) + { + buf[i] = (variant & WKB_NDR) ? ndr_nan[i] : xdr_nan[i];; + } + return buf + NAN_SIZE; + } +} + +/* +* Float64 +*/ +static uint8_t* double_to_wkb_buf(const double d, uint8_t *buf, uint8_t variant) +{ + uint8_t *dptr = (uint8_t *)(&d); + int i = 0; + + if ( sizeof(double) != WKB_DOUBLE_SIZE ) + { + lwerror("Machine double size is not %d bytes!", WKB_DOUBLE_SIZE); + } + + if ( variant & WKB_HEX ) + { + int swap = wkb_swap_bytes(variant); + /* Machine/request arch mismatch, so flip byte order */ + for ( i = 0; i < WKB_DOUBLE_SIZE; i++ ) + { + int j = (swap ? WKB_DOUBLE_SIZE - 1 - i : i); + uint8_t b = dptr[j]; + /* Top four bits to 0-F */ + buf[2*i] = hexchr[b >> 4]; + /* Bottom four bits to 0-F */ + buf[2*i+1] = hexchr[b & 0x0F]; + } + return buf + (2 * WKB_DOUBLE_SIZE); + } + else + { + /* Machine/request arch mismatch, so flip byte order */ + if ( wkb_swap_bytes(variant) ) + { + for ( i = 0; i < WKB_DOUBLE_SIZE; i++ ) + { + buf[i] = dptr[WKB_DOUBLE_SIZE - 1 - i]; + } + } + /* If machine arch and requested arch match, don't flip byte order */ + else + { + memcpy(buf, dptr, WKB_DOUBLE_SIZE); + } + return buf + WKB_DOUBLE_SIZE; + } +} + + +/* +* Empty +*/ +static size_t empty_to_wkb_size(const LWGEOM *geom, uint8_t variant) +{ + /* endian byte + type integer */ + size_t size = WKB_BYTE_SIZE + WKB_INT_SIZE; + + /* optional srid integer */ + if ( lwgeom_wkb_needs_srid(geom, variant) ) + size += WKB_INT_SIZE; + + /* Represent POINT EMPTY as POINT(NaN NaN) */ + if ( geom->type == POINTTYPE ) + { + const LWPOINT *pt = (LWPOINT*)geom; + size += WKB_DOUBLE_SIZE * FLAGS_NDIMS(pt->point->flags); + } + /* num-elements */ + else + { + size += WKB_INT_SIZE; + } + + return size; +} + +static uint8_t* empty_to_wkb_buf(const LWGEOM *geom, uint8_t *buf, uint8_t variant) +{ + uint32_t wkb_type = lwgeom_wkb_type(geom, variant); + + /* Set the endian flag */ + buf = endian_to_wkb_buf(buf, variant); + + /* Set the geometry type */ + buf = integer_to_wkb_buf(wkb_type, buf, variant); + + /* Set the SRID if necessary */ + if ( lwgeom_wkb_needs_srid(geom, variant) ) + buf = integer_to_wkb_buf(geom->srid, buf, variant); + + /* Represent POINT EMPTY as POINT(NaN NaN) */ + if ( geom->type == POINTTYPE ) + { + const LWPOINT *pt = (LWPOINT*)geom; + for (int i = 0; i < FLAGS_NDIMS(pt->point->flags); i++) + { + buf = double_nan_to_wkb_buf(buf, variant); + } + } + /* Everything else is flagged as empty using num-elements == 0 */ + else + { + /* Set nrings/npoints/ngeoms to zero */ + buf = integer_to_wkb_buf(0, buf, variant); + } + + return buf; +} + +/* +* POINTARRAY +*/ +static size_t ptarray_to_wkb_size(const POINTARRAY *pa, uint8_t variant) +{ + int dims = 2; + size_t size = 0; + + if ( variant & (WKB_ISO | WKB_EXTENDED) ) + dims = FLAGS_NDIMS(pa->flags); + + /* Include the npoints if it's not a POINT type) */ + if ( ! ( variant & WKB_NO_NPOINTS ) ) + size += WKB_INT_SIZE; + + /* size of the double list */ + size += pa->npoints * dims * WKB_DOUBLE_SIZE; + + return size; +} + +static uint8_t* ptarray_to_wkb_buf(const POINTARRAY *pa, uint8_t *buf, uint8_t variant) +{ + uint32_t dims = 2; + uint32_t pa_dims = FLAGS_NDIMS(pa->flags); + uint32_t i, j; + double *dbl_ptr; + + /* SFSQL is always 2-d. Extended and ISO use all available dimensions */ + if ( (variant & WKB_ISO) || (variant & WKB_EXTENDED) ) + dims = pa_dims; + + /* Set the number of points (if it's not a POINT type) */ + if ( ! ( variant & WKB_NO_NPOINTS ) ) + buf = integer_to_wkb_buf(pa->npoints, buf, variant); + + /* Bulk copy the coordinates when: dimensionality matches, output format */ + /* is not hex, and output endian matches internal endian. */ + if ( pa->npoints && (dims == pa_dims) && ! wkb_swap_bytes(variant) && ! (variant & WKB_HEX) ) + { + size_t size = pa->npoints * dims * WKB_DOUBLE_SIZE; + memcpy(buf, getPoint_internal(pa, 0), size); + buf += size; + } + /* Copy coordinates one-by-one otherwise */ + else + { + for ( i = 0; i < pa->npoints; i++ ) + { + LWDEBUGF(4, "Writing point #%d", i); + dbl_ptr = (double*)getPoint_internal(pa, i); + for ( j = 0; j < dims; j++ ) + { + LWDEBUGF(4, "Writing dimension #%d (buf = %p)", j, buf); + buf = double_to_wkb_buf(dbl_ptr[j], buf, variant); + } + } + } + LWDEBUGF(4, "Done (buf = %p)", buf); + return buf; +} + +/* +* POINT +*/ +static size_t lwpoint_to_wkb_size(const LWPOINT *pt, uint8_t variant) +{ + /* Endian flag + type number */ + size_t size = WKB_BYTE_SIZE + WKB_INT_SIZE; + + /* Only process empty at this level in the EXTENDED case */ + if ( (variant & WKB_EXTENDED) && lwgeom_is_empty((LWGEOM*)pt) ) + return empty_to_wkb_size((LWGEOM*)pt, variant); + + /* Extended WKB needs space for optional SRID integer */ + if ( lwgeom_wkb_needs_srid((LWGEOM*)pt, variant) ) + size += WKB_INT_SIZE; + + /* Points */ + size += ptarray_to_wkb_size(pt->point, variant | WKB_NO_NPOINTS); + return size; +} + +static uint8_t* lwpoint_to_wkb_buf(const LWPOINT *pt, uint8_t *buf, uint8_t variant) +{ + /* Only process empty at this level in the EXTENDED case */ + if ( (variant & WKB_EXTENDED) && lwgeom_is_empty((LWGEOM*)pt) ) + return empty_to_wkb_buf((LWGEOM*)pt, buf, variant); + + /* Set the endian flag */ + LWDEBUGF(4, "Entering function, buf = %p", buf); + buf = endian_to_wkb_buf(buf, variant); + LWDEBUGF(4, "Endian set, buf = %p", buf); + /* Set the geometry type */ + buf = integer_to_wkb_buf(lwgeom_wkb_type((LWGEOM*)pt, variant), buf, variant); + LWDEBUGF(4, "Type set, buf = %p", buf); + /* Set the optional SRID for extended variant */ + if ( lwgeom_wkb_needs_srid((LWGEOM*)pt, variant) ) + { + buf = integer_to_wkb_buf(pt->srid, buf, variant); + LWDEBUGF(4, "SRID set, buf = %p", buf); + } + /* Set the coordinates */ + buf = ptarray_to_wkb_buf(pt->point, buf, variant | WKB_NO_NPOINTS); + LWDEBUGF(4, "Pointarray set, buf = %p", buf); + return buf; +} + +/* +* LINESTRING, CIRCULARSTRING +*/ +static size_t lwline_to_wkb_size(const LWLINE *line, uint8_t variant) +{ + /* Endian flag + type number */ + size_t size = WKB_BYTE_SIZE + WKB_INT_SIZE; + + /* Only process empty at this level in the EXTENDED case */ + if ( (variant & WKB_EXTENDED) && lwgeom_is_empty((LWGEOM*)line) ) + return empty_to_wkb_size((LWGEOM*)line, variant); + + /* Extended WKB needs space for optional SRID integer */ + if ( lwgeom_wkb_needs_srid((LWGEOM*)line, variant) ) + size += WKB_INT_SIZE; + + /* Size of point array */ + size += ptarray_to_wkb_size(line->points, variant); + return size; +} + +static uint8_t* lwline_to_wkb_buf(const LWLINE *line, uint8_t *buf, uint8_t variant) +{ + /* Only process empty at this level in the EXTENDED case */ + if ( (variant & WKB_EXTENDED) && lwgeom_is_empty((LWGEOM*)line) ) + return empty_to_wkb_buf((LWGEOM*)line, buf, variant); + + /* Set the endian flag */ + buf = endian_to_wkb_buf(buf, variant); + /* Set the geometry type */ + buf = integer_to_wkb_buf(lwgeom_wkb_type((LWGEOM*)line, variant), buf, variant); + /* Set the optional SRID for extended variant */ + if ( lwgeom_wkb_needs_srid((LWGEOM*)line, variant) ) + buf = integer_to_wkb_buf(line->srid, buf, variant); + /* Set the coordinates */ + buf = ptarray_to_wkb_buf(line->points, buf, variant); + return buf; +} + +/* +* TRIANGLE +*/ +static size_t lwtriangle_to_wkb_size(const LWTRIANGLE *tri, uint8_t variant) +{ + /* endian flag + type number + number of rings */ + size_t size = WKB_BYTE_SIZE + WKB_INT_SIZE + WKB_INT_SIZE; + + /* Only process empty at this level in the EXTENDED case */ + if ( (variant & WKB_EXTENDED) && lwgeom_is_empty((LWGEOM*)tri) ) + return empty_to_wkb_size((LWGEOM*)tri, variant); + + /* Extended WKB needs space for optional SRID integer */ + if ( lwgeom_wkb_needs_srid((LWGEOM*)tri, variant) ) + size += WKB_INT_SIZE; + + /* How big is this point array? */ + size += ptarray_to_wkb_size(tri->points, variant); + + return size; +} + +static uint8_t* lwtriangle_to_wkb_buf(const LWTRIANGLE *tri, uint8_t *buf, uint8_t variant) +{ + /* Only process empty at this level in the EXTENDED case */ + if ( (variant & WKB_EXTENDED) && lwgeom_is_empty((LWGEOM*)tri) ) + return empty_to_wkb_buf((LWGEOM*)tri, buf, variant); + + /* Set the endian flag */ + buf = endian_to_wkb_buf(buf, variant); + + /* Set the geometry type */ + buf = integer_to_wkb_buf(lwgeom_wkb_type((LWGEOM*)tri, variant), buf, variant); + + /* Set the optional SRID for extended variant */ + if ( lwgeom_wkb_needs_srid((LWGEOM*)tri, variant) ) + buf = integer_to_wkb_buf(tri->srid, buf, variant); + + /* Set the number of rings (only one, it's a triangle, buddy) */ + buf = integer_to_wkb_buf(1, buf, variant); + + /* Write that ring */ + buf = ptarray_to_wkb_buf(tri->points, buf, variant); + + return buf; +} + +/* +* POLYGON +*/ +static size_t lwpoly_to_wkb_size(const LWPOLY *poly, uint8_t variant) +{ + /* endian flag + type number + number of rings */ + size_t size = WKB_BYTE_SIZE + WKB_INT_SIZE + WKB_INT_SIZE; + uint32_t i = 0; + + /* Only process empty at this level in the EXTENDED case */ + if ( (variant & WKB_EXTENDED) && lwgeom_is_empty((LWGEOM*)poly) ) + return empty_to_wkb_size((LWGEOM*)poly, variant); + + /* Extended WKB needs space for optional SRID integer */ + if ( lwgeom_wkb_needs_srid((LWGEOM*)poly, variant) ) + size += WKB_INT_SIZE; + + for ( i = 0; i < poly->nrings; i++ ) + { + /* Size of ring point array */ + size += ptarray_to_wkb_size(poly->rings[i], variant); + } + + return size; +} + +static uint8_t* lwpoly_to_wkb_buf(const LWPOLY *poly, uint8_t *buf, uint8_t variant) +{ + uint32_t i; + + /* Only process empty at this level in the EXTENDED case */ + if ( (variant & WKB_EXTENDED) && lwgeom_is_empty((LWGEOM*)poly) ) + return empty_to_wkb_buf((LWGEOM*)poly, buf, variant); + + /* Set the endian flag */ + buf = endian_to_wkb_buf(buf, variant); + /* Set the geometry type */ + buf = integer_to_wkb_buf(lwgeom_wkb_type((LWGEOM*)poly, variant), buf, variant); + /* Set the optional SRID for extended variant */ + if ( lwgeom_wkb_needs_srid((LWGEOM*)poly, variant) ) + buf = integer_to_wkb_buf(poly->srid, buf, variant); + /* Set the number of rings */ + buf = integer_to_wkb_buf(poly->nrings, buf, variant); + + for ( i = 0; i < poly->nrings; i++ ) + { + buf = ptarray_to_wkb_buf(poly->rings[i], buf, variant); + } + + return buf; +} + + +/* +* MULTIPOINT, MULTILINESTRING, MULTIPOLYGON, GEOMETRYCOLLECTION +* MULTICURVE, COMPOUNDCURVE, MULTISURFACE, CURVEPOLYGON, TIN, +* POLYHEDRALSURFACE +*/ +static size_t lwcollection_to_wkb_size(const LWCOLLECTION *col, uint8_t variant) +{ + /* Endian flag + type number + number of subgeoms */ + size_t size = WKB_BYTE_SIZE + WKB_INT_SIZE + WKB_INT_SIZE; + uint32_t i = 0; + + /* Extended WKB needs space for optional SRID integer */ + if ( lwgeom_wkb_needs_srid((LWGEOM*)col, variant) ) + size += WKB_INT_SIZE; + + for ( i = 0; i < col->ngeoms; i++ ) + { + /* size of subgeom */ + size += lwgeom_to_wkb_size((LWGEOM*)col->geoms[i], variant | WKB_NO_SRID); + } + + return size; +} + +static uint8_t* lwcollection_to_wkb_buf(const LWCOLLECTION *col, uint8_t *buf, uint8_t variant) +{ + uint32_t i; + + /* Set the endian flag */ + buf = endian_to_wkb_buf(buf, variant); + /* Set the geometry type */ + buf = integer_to_wkb_buf(lwgeom_wkb_type((LWGEOM*)col, variant), buf, variant); + /* Set the optional SRID for extended variant */ + if ( lwgeom_wkb_needs_srid((LWGEOM*)col, variant) ) + buf = integer_to_wkb_buf(col->srid, buf, variant); + /* Set the number of sub-geometries */ + buf = integer_to_wkb_buf(col->ngeoms, buf, variant); + + /* Write the sub-geometries. Sub-geometries do not get SRIDs, they + inherit from their parents. */ + for ( i = 0; i < col->ngeoms; i++ ) + { + buf = lwgeom_to_wkb_buf(col->geoms[i], buf, variant | WKB_NO_SRID); + } + + return buf; +} + +/* +* GEOMETRY +*/ +static size_t +lwgeom_to_wkb_size(const LWGEOM *geom, uint8_t variant) +{ + size_t size = 0; + + if (geom == NULL) + { + LWDEBUG(4, "Cannot convert NULL into WKB."); + lwerror("Cannot convert NULL into WKB."); + return 0; + } + + /* Short circuit out empty geometries */ + if ( (!(variant & WKB_EXTENDED)) && lwgeom_is_empty(geom) ) + { + return empty_to_wkb_size(geom, variant); + } + + switch ( geom->type ) + { + case POINTTYPE: + size += lwpoint_to_wkb_size((LWPOINT*)geom, variant); + break; + + /* LineString and CircularString both have points elements */ + case CIRCSTRINGTYPE: + case LINETYPE: + size += lwline_to_wkb_size((LWLINE*)geom, variant); + break; + + /* Polygon has nrings and rings elements */ + case POLYGONTYPE: + size += lwpoly_to_wkb_size((LWPOLY*)geom, variant); + break; + + /* Triangle has one ring of three points */ + case TRIANGLETYPE: + size += lwtriangle_to_wkb_size((LWTRIANGLE*)geom, variant); + break; + + /* All these Collection types have ngeoms and geoms elements */ + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COMPOUNDTYPE: + case CURVEPOLYTYPE: + case MULTICURVETYPE: + case MULTISURFACETYPE: + case COLLECTIONTYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + size += lwcollection_to_wkb_size((LWCOLLECTION*)geom, variant); + break; + + /* Unknown type! */ + default: + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(geom->type)); + } + + return size; +} + +/* TODO handle the TRIANGLE type properly */ + +static uint8_t* lwgeom_to_wkb_buf(const LWGEOM *geom, uint8_t *buf, uint8_t variant) +{ + + /* Do not simplify empties when outputting to canonical form */ + if (lwgeom_is_empty(geom) && !(variant & WKB_EXTENDED)) + return empty_to_wkb_buf(geom, buf, variant); + + switch ( geom->type ) + { + case POINTTYPE: + return lwpoint_to_wkb_buf((LWPOINT*)geom, buf, variant); + + /* LineString and CircularString both have 'points' elements */ + case CIRCSTRINGTYPE: + case LINETYPE: + return lwline_to_wkb_buf((LWLINE*)geom, buf, variant); + + /* Polygon has 'nrings' and 'rings' elements */ + case POLYGONTYPE: + return lwpoly_to_wkb_buf((LWPOLY*)geom, buf, variant); + + /* Triangle has one ring of three points */ + case TRIANGLETYPE: + return lwtriangle_to_wkb_buf((LWTRIANGLE*)geom, buf, variant); + + /* All these Collection types have 'ngeoms' and 'geoms' elements */ + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COMPOUNDTYPE: + case CURVEPOLYTYPE: + case MULTICURVETYPE: + case MULTISURFACETYPE: + case COLLECTIONTYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + return lwcollection_to_wkb_buf((LWCOLLECTION*)geom, buf, variant); + + /* Unknown type! */ + default: + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(geom->type)); + } + /* Return value to keep compiler happy. */ + return 0; +} + +/** +* Convert LWGEOM to a char* in WKB format. Caller is responsible for freeing +* the returned array. +* +* @param variant. Unsigned bitmask value. Accepts one of: WKB_ISO, WKB_EXTENDED, WKB_SFSQL. +* Accepts any of: WKB_NDR, WKB_HEX. For example: Variant = ( WKB_ISO | WKB_NDR ) would +* return the little-endian ISO form of WKB. For Example: Variant = ( WKB_EXTENDED | WKB_HEX ) +* would return the big-endian extended form of WKB, as hex-encoded ASCII (the "canonical form"). +* @param size_out If supplied, will return the size of the returned memory segment, +* including the null terminator in the case of ASCII. +*/ +static ptrdiff_t +lwgeom_to_wkb_write_buf(const LWGEOM *geom, uint8_t variant, uint8_t *buffer) +{ + /* If neither or both variants are specified, choose the native order */ + if (!(variant & WKB_NDR || variant & WKB_XDR) || (variant & WKB_NDR && variant & WKB_XDR)) + { + if (IS_BIG_ENDIAN) + variant = variant | WKB_XDR; + else + variant = variant | WKB_NDR; + } + + /* Write the WKB into the output buffer */ + int written_bytes = (lwgeom_to_wkb_buf(geom, buffer, variant) - buffer); + + return written_bytes; +} + +uint8_t * +lwgeom_to_wkb_buffer(const LWGEOM *geom, uint8_t variant) +{ + size_t b_size = lwgeom_to_wkb_size(geom, variant); + /* Hex string takes twice as much space as binary + a null character */ + if (variant & WKB_HEX) + { + b_size = 2 * b_size + 1; + } + + uint8_t *buffer = (uint8_t *)lwalloc(b_size); + ptrdiff_t written_size = lwgeom_to_wkb_write_buf(geom, variant, buffer); + if (variant & WKB_HEX) + { + buffer[written_size] = '\0'; + written_size++; + } + + if (written_size != (ptrdiff_t)b_size) + { + char *wkt = lwgeom_to_wkt(geom, WKT_EXTENDED, 15, NULL); + lwerror("Output WKB is not the same size as the allocated buffer. Variant: %u, Geom: %s", variant, wkt); + lwfree(wkt); + lwfree(buffer); + return NULL; + } + + return buffer; +} + +char * +lwgeom_to_hexwkb_buffer(const LWGEOM *geom, uint8_t variant) +{ + return (char *)lwgeom_to_wkb_buffer(geom, variant | WKB_HEX); +} + +lwvarlena_t * +lwgeom_to_wkb_varlena(const LWGEOM *geom, uint8_t variant) +{ + size_t b_size = lwgeom_to_wkb_size(geom, variant); + /* Hex string takes twice as much space as binary, but No NULL ending in varlena */ + if (variant & WKB_HEX) + { + b_size = 2 * b_size; + } + + lwvarlena_t *buffer = (lwvarlena_t *)lwalloc(b_size + LWVARHDRSZ); + int written_size = lwgeom_to_wkb_write_buf(geom, variant, (uint8_t *)buffer->data); + if (written_size != (ptrdiff_t)b_size) + { + char *wkt = lwgeom_to_wkt(geom, WKT_EXTENDED, 15, NULL); + lwerror("Output WKB is not the same size as the allocated buffer. Variant: %u, Geom: %s", variant, wkt); + lwfree(wkt); + lwfree(buffer); + return NULL; + } + LWSIZE_SET(buffer->size, written_size + LWVARHDRSZ); + return buffer; +} + +lwvarlena_t * +lwgeom_to_hexwkb_varlena(const LWGEOM *geom, uint8_t variant) +{ + return lwgeom_to_wkb_varlena(geom, variant | WKB_HEX); +} diff --git a/mgist-postgis/liblwgeom/lwout_wkt.c b/mgist-postgis/liblwgeom/lwout_wkt.c new file mode 100644 index 0000000..d99d385 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwout_wkt.c @@ -0,0 +1,729 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2009 Paul Ramsey + * + **********************************************************************/ + + +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" +#include "stringbuffer.h" + +static void lwgeom_to_wkt_sb(const LWGEOM *geom, stringbuffer_t *sb, int precision, uint8_t variant); + +#define buffer_size 128 + +/* +* ISO format uses both Z and M qualifiers. +* Extended format only uses an M qualifier for 3DM variants, where it is not +* clear what the third dimension represents. +* SFSQL format never has more than two dimensions, so no qualifiers. +*/ +static void dimension_qualifiers_to_wkt_sb(const LWGEOM *geom, stringbuffer_t *sb, uint8_t variant) +{ + + /* Extended WKT: POINTM(0 0 0) */ +#if 0 + if ( (variant & WKT_EXTENDED) && ! (variant & WKT_IS_CHILD) && FLAGS_GET_M(geom->flags) && (!FLAGS_GET_Z(geom->flags)) ) +#else + if ( (variant & WKT_EXTENDED) && FLAGS_GET_M(geom->flags) && (!FLAGS_GET_Z(geom->flags)) ) +#endif + { + stringbuffer_append_len(sb, "M", 1); /* "M" */ + return; + } + + /* ISO WKT: POINT ZM (0 0 0 0) */ + if ( (variant & WKT_ISO) && (FLAGS_NDIMS(geom->flags) > 2) ) + { + stringbuffer_append_len(sb, " ", 1); + if ( FLAGS_GET_Z(geom->flags) ) + stringbuffer_append_len(sb, "Z", 1); + if ( FLAGS_GET_M(geom->flags) ) + stringbuffer_append_len(sb, "M", 1); + stringbuffer_append_len(sb, " ", 1); + } +} + +/* +* Write an empty token out, padding with a space if +* necessary. +*/ +static void empty_to_wkt_sb(stringbuffer_t *sb) +{ + if ( ! strchr(" ,(", stringbuffer_lastchar(sb)) ) /* "EMPTY" */ + { + stringbuffer_append_len(sb, " ", 1); + } + stringbuffer_append_len(sb, "EMPTY", 5); +} + +inline static void +coordinate_to_wkt_sb(double *coords, stringbuffer_t *sb, uint32_t dimensions, int precision) +{ + uint32_t d = 0; + stringbuffer_append_double(sb, coords[d], precision); + + for (d = 1; d < dimensions; d++) + { + stringbuffer_append_len(sb, " ", 1); + stringbuffer_append_double(sb, coords[d], precision); + } +} + +/* +* Point array is a list of coordinates. Depending on output mode, +* we may suppress some dimensions. ISO and Extended formats include +* all dimensions. Standard OGC output only includes X/Y coordinates. +*/ +static void ptarray_to_wkt_sb(const POINTARRAY *ptarray, stringbuffer_t *sb, int precision, uint8_t variant) +{ + /* OGC only includes X/Y */ + uint32_t dimensions = 2; + + /* ISO and extended formats include all dimensions */ + if ( variant & ( WKT_ISO | WKT_EXTENDED ) ) + dimensions = FLAGS_NDIMS(ptarray->flags); + + stringbuffer_makeroom(sb, 2 + ((OUT_MAX_BYTES_DOUBLE + 1) * dimensions * ptarray->npoints)); + /* Opening paren? */ + if ( ! (variant & WKT_NO_PARENS) ) + stringbuffer_append_len(sb, "(", 1); + + /* Digits and commas */ + if (ptarray->npoints) + { + uint32_t i = 0; + + double *dbl_ptr = (double *)getPoint_internal(ptarray, i); + coordinate_to_wkt_sb(dbl_ptr, sb, dimensions, precision); + + for (i = 1; i < ptarray->npoints; i++) + { + stringbuffer_append_len(sb, ",", 1); + dbl_ptr = (double *)getPoint_internal(ptarray, i); + coordinate_to_wkt_sb(dbl_ptr, sb, dimensions, precision); + } + } + + /* Closing paren? */ + if ( ! (variant & WKT_NO_PARENS) ) + stringbuffer_append_len(sb, ")", 1); +} + +/* +* A four-dimensional point will have different outputs depending on variant. +* ISO: POINT ZM (0 0 0 0) +* Extended: POINT(0 0 0 0) +* OGC: POINT(0 0) +* A three-dimensional m-point will have different outputs too. +* ISO: POINT M (0 0 0) +* Extended: POINTM(0 0 0) +* OGC: POINT(0 0) +*/ +static void lwpoint_to_wkt_sb(const LWPOINT *pt, stringbuffer_t *sb, int precision, uint8_t variant) +{ + if ( ! (variant & WKT_NO_TYPE) ) + { + stringbuffer_append_len(sb, "POINT", 5); /* "POINT" */ + dimension_qualifiers_to_wkt_sb((LWGEOM*)pt, sb, variant); + } + + if ( lwpoint_is_empty(pt) ) + { + empty_to_wkt_sb(sb); + return; + } + + ptarray_to_wkt_sb(pt->point, sb, precision, variant); +} + +/* +* LINESTRING(0 0 0, 1 1 1) +*/ +static void lwline_to_wkt_sb(const LWLINE *line, stringbuffer_t *sb, int precision, uint8_t variant) +{ + if ( ! (variant & WKT_NO_TYPE) ) + { + stringbuffer_append_len(sb, "LINESTRING", 10); /* "LINESTRING" */ + dimension_qualifiers_to_wkt_sb((LWGEOM*)line, sb, variant); + } + if ( lwline_is_empty(line) ) + { + empty_to_wkt_sb(sb); + return; + } + + ptarray_to_wkt_sb(line->points, sb, precision, variant); +} + +/* +* POLYGON(0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1) +*/ +static void lwpoly_to_wkt_sb(const LWPOLY *poly, stringbuffer_t *sb, int precision, uint8_t variant) +{ + uint32_t i = 0; + if ( ! (variant & WKT_NO_TYPE) ) + { + stringbuffer_append_len(sb, "POLYGON", 7); /* "POLYGON" */ + dimension_qualifiers_to_wkt_sb((LWGEOM*)poly, sb, variant); + } + if ( lwpoly_is_empty(poly) ) + { + empty_to_wkt_sb(sb); + return; + } + + stringbuffer_append_len(sb, "(", 1); + for ( i = 0; i < poly->nrings; i++ ) + { + if ( i > 0 ) + stringbuffer_append_len(sb, ",", 1); + ptarray_to_wkt_sb(poly->rings[i], sb, precision, variant); + } + stringbuffer_append_len(sb, ")", 1); +} + +/* +* CIRCULARSTRING +*/ +static void lwcircstring_to_wkt_sb(const LWCIRCSTRING *circ, stringbuffer_t *sb, int precision, uint8_t variant) +{ + if ( ! (variant & WKT_NO_TYPE) ) + { + stringbuffer_append_len(sb, "CIRCULARSTRING", 14); /* "CIRCULARSTRING" */ + dimension_qualifiers_to_wkt_sb((LWGEOM*)circ, sb, variant); + } + if ( lwcircstring_is_empty(circ) ) + { + empty_to_wkt_sb(sb); + return; + } + ptarray_to_wkt_sb(circ->points, sb, precision, variant); +} + + +/* +* Multi-points, in non-ISO format, do not wrap their sub-members in parens, unlike other multi-geometries. +* MULTPOINT(0 0, 1 1) instead of MULTIPOINT((0 0),(1 1)) +* Strictly speaking, the SFA spec also mandates use of parens in sub-members, but +* use the old non-parens interpretation for WKT_SFSQL +*/ +static void lwmpoint_to_wkt_sb(const LWMPOINT *mpoint, stringbuffer_t *sb, int precision, uint8_t variant) +{ + uint32_t i = 0; + if ( ! (variant & WKT_NO_TYPE) ) + { + stringbuffer_append_len(sb, "MULTIPOINT", 10); /* "MULTIPOINT" */ + dimension_qualifiers_to_wkt_sb((LWGEOM*)mpoint, sb, variant); + } + if ( mpoint->ngeoms < 1 ) + { + empty_to_wkt_sb(sb); + return; + } + stringbuffer_append_len(sb, "(", 1); + variant = variant | WKT_IS_CHILD | WKT_NO_TYPE; /* Inform the sub-geometries they are children */ + if ( !(variant & WKT_ISO) ) + variant = variant | WKT_NO_PARENS; + for ( i = 0; i < mpoint->ngeoms; i++ ) + { + if ( i > 0 ) + stringbuffer_append_len(sb, ",", 1); + /* We don't want type strings or parens on our subgeoms */ + lwpoint_to_wkt_sb(mpoint->geoms[i], sb, precision, variant); + } + stringbuffer_append_len(sb, ")", 1); +} + +/* +* MULTILINESTRING +*/ +static void lwmline_to_wkt_sb(const LWMLINE *mline, stringbuffer_t *sb, int precision, uint8_t variant) +{ + uint32_t i = 0; + + if ( ! (variant & WKT_NO_TYPE) ) + { + stringbuffer_append_len(sb, "MULTILINESTRING", 15); /* "MULTILINESTRING" */ + dimension_qualifiers_to_wkt_sb((LWGEOM*)mline, sb, variant); + } + if ( mline->ngeoms < 1 ) + { + empty_to_wkt_sb(sb); + return; + } + + stringbuffer_append_len(sb, "(", 1); + variant = variant | WKT_IS_CHILD; /* Inform the sub-geometries they are childre */ + for ( i = 0; i < mline->ngeoms; i++ ) + { + if ( i > 0 ) + stringbuffer_append_len(sb, ",", 1); + /* We don't want type strings on our subgeoms */ + lwline_to_wkt_sb(mline->geoms[i], sb, precision, variant | WKT_NO_TYPE ); + } + stringbuffer_append_len(sb, ")", 1); +} + +/* +* MULTIPOLYGON +*/ +static void lwmpoly_to_wkt_sb(const LWMPOLY *mpoly, stringbuffer_t *sb, int precision, uint8_t variant) +{ + uint32_t i = 0; + + if ( ! (variant & WKT_NO_TYPE) ) + { + stringbuffer_append_len(sb, "MULTIPOLYGON", 12); /* "MULTIPOLYGON" */ + dimension_qualifiers_to_wkt_sb((LWGEOM*)mpoly, sb, variant); + } + if ( mpoly->ngeoms < 1 ) + { + empty_to_wkt_sb(sb); + return; + } + + stringbuffer_append_len(sb, "(", 1); + variant = variant | WKT_IS_CHILD; /* Inform the sub-geometries they are childre */ + for ( i = 0; i < mpoly->ngeoms; i++ ) + { + if ( i > 0 ) + stringbuffer_append_len(sb, ",", 1); + /* We don't want type strings on our subgeoms */ + lwpoly_to_wkt_sb(mpoly->geoms[i], sb, precision, variant | WKT_NO_TYPE ); + } + stringbuffer_append_len(sb, ")", 1); +} + +/* +* Compound curves provide type information for their curved sub-geometries +* but not their linestring sub-geometries. +* COMPOUNDCURVE((0 0, 1 1), CURVESTRING(1 1, 2 2, 3 3)) +*/ +static void lwcompound_to_wkt_sb(const LWCOMPOUND *comp, stringbuffer_t *sb, int precision, uint8_t variant) +{ + uint32_t i = 0; + + if ( ! (variant & WKT_NO_TYPE) ) + { + stringbuffer_append_len(sb, "COMPOUNDCURVE", 13); /* "COMPOUNDCURVE" */ + dimension_qualifiers_to_wkt_sb((LWGEOM*)comp, sb, variant); + } + if ( comp->ngeoms < 1 ) + { + empty_to_wkt_sb(sb); + return; + } + + stringbuffer_append_len(sb, "(", 1); + variant = variant | WKT_IS_CHILD; /* Inform the sub-geometries they are childre */ + for ( i = 0; i < comp->ngeoms; i++ ) + { + int type = comp->geoms[i]->type; + if ( i > 0 ) + stringbuffer_append_len(sb, ",", 1); + /* Linestring subgeoms don't get type identifiers */ + if ( type == LINETYPE ) + { + lwline_to_wkt_sb((LWLINE*)comp->geoms[i], sb, precision, variant | WKT_NO_TYPE ); + } + /* But circstring subgeoms *do* get type identifiers */ + else if ( type == CIRCSTRINGTYPE ) + { + lwcircstring_to_wkt_sb((LWCIRCSTRING*)comp->geoms[i], sb, precision, variant ); + } + else + { + lwerror("lwcompound_to_wkt_sb: Unknown type received %d - %s", type, lwtype_name(type)); + } + } + stringbuffer_append_len(sb, ")", 1); +} + +/* +* Curve polygons provide type information for their curved rings +* but not their linestring rings. +* CURVEPOLYGON((0 0, 1 1, 0 1, 0 0), CURVESTRING(0 0, 1 1, 0 1, 0.5 1, 0 0)) +*/ +static void lwcurvepoly_to_wkt_sb(const LWCURVEPOLY *cpoly, stringbuffer_t *sb, int precision, uint8_t variant) +{ + uint32_t i = 0; + + if ( ! (variant & WKT_NO_TYPE) ) + { + stringbuffer_append_len(sb, "CURVEPOLYGON", 12); /* "CURVEPOLYGON" */ + dimension_qualifiers_to_wkt_sb((LWGEOM*)cpoly, sb, variant); + } + if ( cpoly->nrings < 1 ) + { + empty_to_wkt_sb(sb); + return; + } + stringbuffer_append_len(sb, "(", 1); + variant = variant | WKT_IS_CHILD; /* Inform the sub-geometries they are childre */ + for ( i = 0; i < cpoly->nrings; i++ ) + { + int type = cpoly->rings[i]->type; + if ( i > 0 ) + stringbuffer_append_len(sb, ",", 1); + switch (type) + { + case LINETYPE: + /* Linestring subgeoms don't get type identifiers */ + lwline_to_wkt_sb((LWLINE*)cpoly->rings[i], sb, precision, variant | WKT_NO_TYPE ); + break; + case CIRCSTRINGTYPE: + /* But circstring subgeoms *do* get type identifiers */ + lwcircstring_to_wkt_sb((LWCIRCSTRING*)cpoly->rings[i], sb, precision, variant ); + break; + case COMPOUNDTYPE: + /* And compoundcurve subgeoms *do* get type identifiers */ + lwcompound_to_wkt_sb((LWCOMPOUND*)cpoly->rings[i], sb, precision, variant ); + break; + default: + lwerror("lwcurvepoly_to_wkt_sb: Unknown type received %d - %s", type, lwtype_name(type)); + } + } + stringbuffer_append_len(sb, ")", 1); +} + + +/* +* Multi-curves provide type information for their curved sub-geometries +* but not their linear sub-geometries. +* MULTICURVE((0 0, 1 1), CURVESTRING(0 0, 1 1, 2 2)) +*/ +static void lwmcurve_to_wkt_sb(const LWMCURVE *mcurv, stringbuffer_t *sb, int precision, uint8_t variant) +{ + uint32_t i = 0; + + if ( ! (variant & WKT_NO_TYPE) ) + { + stringbuffer_append_len(sb, "MULTICURVE", 10); /* "MULTICURVE" */ + dimension_qualifiers_to_wkt_sb((LWGEOM*)mcurv, sb, variant); + } + if ( mcurv->ngeoms < 1 ) + { + empty_to_wkt_sb(sb); + return; + } + stringbuffer_append_len(sb, "(", 1); + variant = variant | WKT_IS_CHILD; /* Inform the sub-geometries they are childre */ + for ( i = 0; i < mcurv->ngeoms; i++ ) + { + int type = mcurv->geoms[i]->type; + if ( i > 0 ) + stringbuffer_append_len(sb, ",", 1); + switch (type) + { + case LINETYPE: + /* Linestring subgeoms don't get type identifiers */ + lwline_to_wkt_sb((LWLINE*)mcurv->geoms[i], sb, precision, variant | WKT_NO_TYPE ); + break; + case CIRCSTRINGTYPE: + /* But circstring subgeoms *do* get type identifiers */ + lwcircstring_to_wkt_sb((LWCIRCSTRING*)mcurv->geoms[i], sb, precision, variant ); + break; + case COMPOUNDTYPE: + /* And compoundcurve subgeoms *do* get type identifiers */ + lwcompound_to_wkt_sb((LWCOMPOUND*)mcurv->geoms[i], sb, precision, variant ); + break; + default: + lwerror("lwmcurve_to_wkt_sb: Unknown type received %d - %s", type, lwtype_name(type)); + } + } + stringbuffer_append_len(sb, ")", 1); +} + + +/* +* Multi-surfaces provide type information for their curved sub-geometries +* but not their linear sub-geometries. +* MULTISURFACE(((0 0, 1 1, 1 0, 0 0)), CURVEPOLYGON(CURVESTRING(0 0, 1 1, 2 2, 0 1, 0 0))) +*/ +static void lwmsurface_to_wkt_sb(const LWMSURFACE *msurf, stringbuffer_t *sb, int precision, uint8_t variant) +{ + uint32_t i = 0; + + if ( ! (variant & WKT_NO_TYPE) ) + { + stringbuffer_append_len(sb, "MULTISURFACE", 12); /* "MULTISURFACE" */ + dimension_qualifiers_to_wkt_sb((LWGEOM*)msurf, sb, variant); + } + if ( msurf->ngeoms < 1 ) + { + empty_to_wkt_sb(sb); + return; + } + stringbuffer_append_len(sb, "(", 1); + variant = variant | WKT_IS_CHILD; /* Inform the sub-geometries they are childre */ + for ( i = 0; i < msurf->ngeoms; i++ ) + { + int type = msurf->geoms[i]->type; + if ( i > 0 ) + stringbuffer_append_len(sb, ",", 1); + switch (type) + { + case POLYGONTYPE: + /* Linestring subgeoms don't get type identifiers */ + lwpoly_to_wkt_sb((LWPOLY*)msurf->geoms[i], sb, precision, variant | WKT_NO_TYPE ); + break; + case CURVEPOLYTYPE: + /* But circstring subgeoms *do* get type identifiers */ + lwcurvepoly_to_wkt_sb((LWCURVEPOLY*)msurf->geoms[i], sb, precision, variant); + break; + default: + lwerror("lwmsurface_to_wkt_sb: Unknown type received %d - %s", type, lwtype_name(type)); + } + } + stringbuffer_append_len(sb, ")", 1); +} + +/* +* Geometry collections provide type information for all their curved sub-geometries +* but not their linear sub-geometries. +* GEOMETRYCOLLECTION(POLYGON((0 0, 1 1, 1 0, 0 0)), CURVEPOLYGON(CURVESTRING(0 0, 1 1, 2 2, 0 1, 0 0))) +*/ +static void lwcollection_to_wkt_sb(const LWCOLLECTION *collection, stringbuffer_t *sb, int precision, uint8_t variant) +{ + uint32_t i = 0; + + if ( ! (variant & WKT_NO_TYPE) ) + { + stringbuffer_append_len(sb, "GEOMETRYCOLLECTION", 18); /* "GEOMETRYCOLLECTION" */ + dimension_qualifiers_to_wkt_sb((LWGEOM*)collection, sb, variant); + } + if ( collection->ngeoms < 1 ) + { + empty_to_wkt_sb(sb); + return; + } + stringbuffer_append_len(sb, "(", 1); + variant = variant | WKT_IS_CHILD; /* Inform the sub-geometries they are children */ + for ( i = 0; i < collection->ngeoms; i++ ) + { + if ( i > 0 ) + stringbuffer_append_len(sb, ",", 1); + lwgeom_to_wkt_sb((LWGEOM*)collection->geoms[i], sb, precision, variant ); + } + stringbuffer_append_len(sb, ")", 1); +} + +/* +* TRIANGLE +*/ +static void lwtriangle_to_wkt_sb(const LWTRIANGLE *tri, stringbuffer_t *sb, int precision, uint8_t variant) +{ + if ( ! (variant & WKT_NO_TYPE) ) + { + stringbuffer_append_len(sb, "TRIANGLE", 8); /* "TRIANGLE" */ + dimension_qualifiers_to_wkt_sb((LWGEOM*)tri, sb, variant); + } + if ( lwtriangle_is_empty(tri) ) + { + empty_to_wkt_sb(sb); + return; + } + + stringbuffer_append_len(sb, "(", 1); /* Triangles have extraneous brackets */ + ptarray_to_wkt_sb(tri->points, sb, precision, variant); + stringbuffer_append_len(sb, ")", 1); +} + +/* +* TIN +*/ +static void lwtin_to_wkt_sb(const LWTIN *tin, stringbuffer_t *sb, int precision, uint8_t variant) +{ + uint32_t i = 0; + + if ( ! (variant & WKT_NO_TYPE) ) + { + stringbuffer_append_len(sb, "TIN", 3); /* "TIN" */ + dimension_qualifiers_to_wkt_sb((LWGEOM*)tin, sb, variant); + } + if ( tin->ngeoms < 1 ) + { + empty_to_wkt_sb(sb); + return; + } + + stringbuffer_append_len(sb, "(", 1); + for ( i = 0; i < tin->ngeoms; i++ ) + { + if ( i > 0 ) + stringbuffer_append_len(sb, ",", 1); + /* We don't want type strings on our subgeoms */ + lwtriangle_to_wkt_sb(tin->geoms[i], sb, precision, variant | WKT_NO_TYPE ); + } + stringbuffer_append_len(sb, ")", 1); +} + +/* +* POLYHEDRALSURFACE +*/ +static void lwpsurface_to_wkt_sb(const LWPSURFACE *psurf, stringbuffer_t *sb, int precision, uint8_t variant) +{ + uint32_t i = 0; + + if ( ! (variant & WKT_NO_TYPE) ) + { + stringbuffer_append_len(sb, "POLYHEDRALSURFACE", 17); /* "POLYHEDRALSURFACE" */ + dimension_qualifiers_to_wkt_sb((LWGEOM*)psurf, sb, variant); + } + if ( psurf->ngeoms < 1 ) + { + empty_to_wkt_sb(sb); + return; + } + + variant = variant | WKT_IS_CHILD; /* Inform the sub-geometries they are childre */ + + stringbuffer_append_len(sb, "(", 1); + for ( i = 0; i < psurf->ngeoms; i++ ) + { + if ( i > 0 ) + stringbuffer_append_len(sb, ",", 1); + /* We don't want type strings on our subgeoms */ + lwpoly_to_wkt_sb(psurf->geoms[i], sb, precision, variant | WKT_NO_TYPE ); + } + stringbuffer_append_len(sb, ")", 1); +} + + +/* +* Generic GEOMETRY +*/ +static void lwgeom_to_wkt_sb(const LWGEOM *geom, stringbuffer_t *sb, int precision, uint8_t variant) +{ + LWDEBUGF(4, "lwgeom_to_wkt_sb: type %s, hasz %d, hasm %d", + lwtype_name(geom->type), (geom->type), + FLAGS_GET_Z(geom->flags)?1:0, FLAGS_GET_M(geom->flags)?1:0); + + switch (geom->type) + { + case POINTTYPE: + lwpoint_to_wkt_sb((LWPOINT*)geom, sb, precision, variant); + break; + case LINETYPE: + lwline_to_wkt_sb((LWLINE*)geom, sb, precision, variant); + break; + case POLYGONTYPE: + lwpoly_to_wkt_sb((LWPOLY*)geom, sb, precision, variant); + break; + case MULTIPOINTTYPE: + lwmpoint_to_wkt_sb((LWMPOINT*)geom, sb, precision, variant); + break; + case MULTILINETYPE: + lwmline_to_wkt_sb((LWMLINE*)geom, sb, precision, variant); + break; + case MULTIPOLYGONTYPE: + lwmpoly_to_wkt_sb((LWMPOLY*)geom, sb, precision, variant); + break; + case COLLECTIONTYPE: + lwcollection_to_wkt_sb((LWCOLLECTION*)geom, sb, precision, variant); + break; + case CIRCSTRINGTYPE: + lwcircstring_to_wkt_sb((LWCIRCSTRING*)geom, sb, precision, variant); + break; + case COMPOUNDTYPE: + lwcompound_to_wkt_sb((LWCOMPOUND*)geom, sb, precision, variant); + break; + case CURVEPOLYTYPE: + lwcurvepoly_to_wkt_sb((LWCURVEPOLY*)geom, sb, precision, variant); + break; + case MULTICURVETYPE: + lwmcurve_to_wkt_sb((LWMCURVE*)geom, sb, precision, variant); + break; + case MULTISURFACETYPE: + lwmsurface_to_wkt_sb((LWMSURFACE*)geom, sb, precision, variant); + break; + case TRIANGLETYPE: + lwtriangle_to_wkt_sb((LWTRIANGLE*)geom, sb, precision, variant); + break; + case TINTYPE: + lwtin_to_wkt_sb((LWTIN*)geom, sb, precision, variant); + break; + case POLYHEDRALSURFACETYPE: + lwpsurface_to_wkt_sb((LWPSURFACE*)geom, sb, precision, variant); + break; + default: + lwerror("lwgeom_to_wkt_sb: Type %d - %s unsupported.", + geom->type, lwtype_name(geom->type)); + } +} + +static stringbuffer_t * +lwgeom_to_wkt_internal(const LWGEOM *geom, uint8_t variant, int precision) +{ + stringbuffer_t *sb; + if ( geom == NULL ) + return NULL; + sb = stringbuffer_create(); + /* Extended mode starts with an "SRID=" section for geoms that have one */ + if ( (variant & WKT_EXTENDED) && lwgeom_has_srid(geom) ) + { + stringbuffer_aprintf(sb, "SRID=%d;", geom->srid); + } + lwgeom_to_wkt_sb(geom, sb, precision, variant); + if ( stringbuffer_getstring(sb) == NULL ) + { + lwerror("Uh oh"); + return NULL; + } + return sb; +} + +/** + * WKT emitter function. Allocates a new *char and fills it with the WKT + * representation. If size_out is not NULL, it will be set to the size of the + * allocated *char. + * + * @param variant Bitmasked value, accepts one of WKT_ISO, WKT_SFSQL, + * WKT_EXTENDED. + * @param precision Maximal number of digits after comma in the output doubles. + * @param size_out If supplied, will return the size of the returned string, + * including the null terminator. + */ +char * +lwgeom_to_wkt(const LWGEOM *geom, uint8_t variant, int precision, size_t *size_out) +{ + stringbuffer_t *sb = lwgeom_to_wkt_internal(geom, variant, precision); + if (!sb) + return NULL; + char *str = stringbuffer_getstringcopy(sb); + if ( size_out ) + *size_out = stringbuffer_getlength(sb) + 1; + stringbuffer_destroy(sb); + return str; +} + +lwvarlena_t * +lwgeom_to_wkt_varlena(const LWGEOM *geom, uint8_t variant, int precision) +{ + stringbuffer_t *sb = lwgeom_to_wkt_internal(geom, variant, precision); + if (!sb) + return NULL; + lwvarlena_t *output = stringbuffer_getvarlenacopy(sb); + stringbuffer_destroy(sb); + return output; +} diff --git a/mgist-postgis/liblwgeom/lwout_x3d.c b/mgist-postgis/liblwgeom/lwout_x3d.c new file mode 100644 index 0000000..b1a8d7b --- /dev/null +++ b/mgist-postgis/liblwgeom/lwout_x3d.c @@ -0,0 +1,565 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2011-2017 Arrival 3D, Regina Obe + * + **********************************************************************/ + +/** +* @file X3D output routines. +* +**********************************************************************/ + +#include "lwout_x3d.h" + +/* + * VERSION X3D 3.0.2 http://www.web3d.org/specifications/x3d-3.0.dtd + */ +/* takes a GEOMETRY and returns a X3D representation */ +lwvarlena_t * +lwgeom_to_x3d3(const LWGEOM *geom, int precision, int opts, const char *defid) +{ + stringbuffer_t *sb; + int rv; + + /* Empty varlena for empties */ + if( lwgeom_is_empty(geom) ) + { + lwvarlena_t *v = lwalloc(LWVARHDRSZ); + LWSIZE_SET(v->size, LWVARHDRSZ); + return v; + } + + sb = stringbuffer_create(); + rv = lwgeom_to_x3d3_sb(geom, precision, opts, defid, sb); + + if ( rv == LW_FAILURE ) + { + stringbuffer_destroy(sb); + return NULL; + } + + lwvarlena_t *v = stringbuffer_getvarlenacopy(sb); + stringbuffer_destroy(sb); + + return v; +} +/* takes a GEOMETRY and appends to string buffer the x3d output */ +static int +lwgeom_to_x3d3_sb(const LWGEOM *geom, int precision, int opts, const char *defid, stringbuffer_t *sb) +{ + int type = geom->type; + + switch (type) + { + case POINTTYPE: + return asx3d3_point_sb((LWPOINT *)geom, precision, opts, defid, sb); + + case LINETYPE: + return asx3d3_line_sb((LWLINE *)geom, precision, opts, defid, sb); + + case POLYGONTYPE: + { + /** We might change this later, but putting a polygon in an indexed face set + * seems like the simplest way to go so treat just like a mulitpolygon + */ + LWCOLLECTION *tmp = (LWCOLLECTION*)lwgeom_as_multi(geom); + asx3d3_multi_sb(tmp, precision, opts, defid, sb); + lwcollection_free(tmp); + return LW_SUCCESS; + } + + case TRIANGLETYPE: + return asx3d3_triangle_sb((LWTRIANGLE *)geom, precision, opts, defid, sb); + + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + return asx3d3_multi_sb((LWCOLLECTION *)geom, precision, opts, defid, sb); + + case POLYHEDRALSURFACETYPE: + return asx3d3_psurface_sb((LWPSURFACE *)geom, precision, opts, defid, sb); + + case TINTYPE: + return asx3d3_tin_sb((LWTIN *)geom, precision, opts, defid, sb); + + case COLLECTIONTYPE: + return asx3d3_collection_sb((LWCOLLECTION *)geom, precision, opts, defid, sb); + + default: + lwerror("lwgeom_to_x3d3: '%s' geometry type not supported", lwtype_name(type)); + return LW_FAILURE; + } +} + +static int +asx3d3_point_sb(const LWPOINT *point, + int precision, + int opts, + __attribute__((__unused__)) const char *defid, + stringbuffer_t *sb) +{ + /** for point we just output the coordinates **/ + return ptarray_to_x3d3_sb(point->point, precision, opts, 0, sb); +} + +static int +asx3d3_line_coords_sb(const LWLINE *line, int precision, int opts, stringbuffer_t *sb) +{ + return ptarray_to_x3d3_sb(line->points, precision, opts, lwline_is_closed(line), sb); +} + +/* Calculate the coordIndex property of the IndexedLineSet for the multilinestring +and add to string buffer */ +static int +asx3d3_mline_coordindex_sb(const LWMLINE *mgeom, stringbuffer_t *sb) +{ + LWLINE *geom; + uint32_t i, j, k, si; + POINTARRAY *pa; + uint32_t np; + + j = 0; + for (i=0; i < mgeom->ngeoms; i++) + { + geom = (LWLINE *) mgeom->geoms[i]; + pa = geom->points; + np = pa->npoints; + si = j; /* start index of first point of linestring */ + for (k=0; k < np ; k++) + { + if (k) + { + stringbuffer_aprintf(sb, " "); + } + /** if the linestring is closed, we put the start point index + * for the last vertex to denote use first point + * and don't increment the index **/ + if (!lwline_is_closed(geom) || k < (np -1) ) + { + stringbuffer_aprintf(sb, "%u", j); + j += 1; + } + else + { + stringbuffer_aprintf(sb,"%u", si); + } + } + if (i < (mgeom->ngeoms - 1) ) + { + stringbuffer_aprintf(sb, " -1 "); /* separator for each linestring */ + } + } + return LW_SUCCESS; +} + +/* Calculate the coordIndex property of the IndexedLineSet for a multipolygon + This is not ideal -- would be really nice to just share this function with psurf, + but I'm not smart enough to do that yet*/ +static int +asx3d3_mpoly_coordindex_sb(const LWMPOLY *psur, stringbuffer_t *sb) +{ + LWPOLY *patch; + uint32_t i, j, k, l; + uint32_t np; + j = 0; + for (i=0; ingeoms; i++) + { + patch = (LWPOLY *) psur->geoms[i]; + for (l=0; l < patch->nrings; l++) + { + np = patch->rings[l]->npoints - 1; + for (k=0; k < np ; k++) + { + if (k) + { + stringbuffer_aprintf(sb, " "); + } + stringbuffer_aprintf(sb, "%d", (j + k)); + } + j += k; + if (l < (patch->nrings - 1) ) + { + /** @todo TODO: Decide the best way to render holes + * Evidently according to my X3D expert the X3D consortium doesn't really + * support holes and it's an issue of argument among many that feel it should. He thinks CAD x3d extensions to spec might. + * What he has done and others developing X3D exports to simulate a hole is to cut around it. + * So if you have a donut, you would cut it into half and have 2 solid polygons. Not really sure the best way to handle this. + * For now will leave it as polygons stacked on top of each other -- which is what we are doing here and perhaps an option + * to color differently. It's not ideal but the alternative sounds complicated. + **/ + stringbuffer_aprintf(sb, " -1 "); /* separator for each inner ring. Ideally we should probably triangulate and cut around as others do */ + } + } + if (i < (psur->ngeoms - 1) ) + { + stringbuffer_aprintf(sb, " -1 "); /* separator for each subgeom */ + } + } + return LW_SUCCESS; +} + +/** Return the linestring as an X3D LineSet */ +static int +asx3d3_line_sb(const LWLINE *line, + int precision, + int opts, + __attribute__((__unused__)) const char *defid, + stringbuffer_t *sb) +{ + + /* int dimension=2; */ + POINTARRAY *pa; + + + /* if (FLAGS_GET_Z(line->flags)) dimension = 3; */ + + pa = line->points; + stringbuffer_aprintf(sb, "", defid, pa->npoints); + + if ( X3D_USE_GEOCOORDS(opts) ) stringbuffer_aprintf(sb, "points, precision, opts, lwline_is_closed((LWLINE *) line), sb); + + + stringbuffer_aprintf(sb, "' />"); + + return stringbuffer_aprintf(sb, ""); +} + +/** Compute the X3D coordinates of the polygon and add to string buffer **/ +static int +asx3d3_poly_sb(const LWPOLY *poly, + int precision, + int opts, + __attribute__((__unused__)) int is_patch, + __attribute__((__unused__)) const char *defid, + stringbuffer_t *sb) +{ + uint32_t i; + for (i=0; inrings; i++) + { + if (i) stringbuffer_aprintf(sb, " "); /* inner ring points start */ + ptarray_to_x3d3_sb(poly->rings[i], precision, opts, 1, sb); + } + return LW_SUCCESS; +} + +static int +asx3d3_triangle_sb(const LWTRIANGLE *triangle, + int precision, + int opts, + __attribute__((__unused__)) const char *defid, + stringbuffer_t *sb) +{ + return ptarray_to_x3d3_sb(triangle->points, precision, opts, 1, sb); +} + + +/* + * Don't call this with single-geoms inspected! + */ +static int +asx3d3_multi_sb(const LWCOLLECTION *col, int precision, int opts, const char *defid, stringbuffer_t *sb) +{ + char *x3dtype; + uint32_t i; + int dimension=2; + + if (FLAGS_GET_Z(col->flags)) dimension = 3; + LWGEOM *subgeom; + x3dtype=""; + + + switch (col->type) + { + case MULTIPOINTTYPE: + x3dtype = "PointSet"; + if ( dimension == 2 ){ /** Use Polypoint2D instead **/ + x3dtype = "Polypoint2D"; + stringbuffer_aprintf(sb, "<%s %s point='", x3dtype, defid); + } + else { + stringbuffer_aprintf(sb, "<%s %s>", x3dtype, defid); + } + break; + case MULTILINETYPE: + x3dtype = "IndexedLineSet"; + stringbuffer_aprintf(sb, "<%s %s coordIndex='", x3dtype, defid); + asx3d3_mline_coordindex_sb((const LWMLINE *)col, sb); + stringbuffer_aprintf(sb, "'>"); + break; + case MULTIPOLYGONTYPE: + x3dtype = "IndexedFaceSet"; + stringbuffer_aprintf(sb, "<%s %s convex='false' coordIndex='", x3dtype, defid); + asx3d3_mpoly_coordindex_sb((const LWMPOLY *)col, sb); + stringbuffer_aprintf(sb, "'>"); + break; + default: + lwerror("asx3d3_multi_buf: '%s' geometry type not supported", lwtype_name(col->type)); + return 0; + } + if (dimension == 3){ + if ( X3D_USE_GEOCOORDS(opts) ) + stringbuffer_aprintf(sb, "ngeoms; i++) + { + subgeom = col->geoms[i]; + if (subgeom->type == POINTTYPE) + { + asx3d3_point_sb((LWPOINT *)subgeom, precision, opts, defid, sb); + stringbuffer_aprintf(sb, " "); + } + else if (subgeom->type == LINETYPE) + { + asx3d3_line_coords_sb((LWLINE*)subgeom, precision, opts, sb); + stringbuffer_aprintf(sb, " "); + } + else if (subgeom->type == POLYGONTYPE) + { + asx3d3_poly_sb((LWPOLY *)subgeom, precision, opts, 0, defid, sb); + stringbuffer_aprintf(sb, " "); + } + } + + /* Close outmost tag */ + if (dimension == 3){ + stringbuffer_aprintf(sb, "' />", x3dtype); + } + else { stringbuffer_aprintf(sb, "' />"); } + return LW_SUCCESS; + +} + +/* + * Don't call this with single-geoms inspected! + */ +static int +asx3d3_psurface_sb(const LWPSURFACE *psur, int precision, int opts, const char *defid, stringbuffer_t *sb) +{ + uint32_t i; + uint32_t j; + uint32_t k; + uint32_t np; + LWPOLY *patch; + + /* Open outmost tag */ + stringbuffer_aprintf(sb, ""); +} + +/* + * Computes X3D representation of TIN (as IndexedTriangleSet and adds to string buffer) + */ +static int +asx3d3_tin_sb(const LWTIN *tin, int precision, int opts, const char *defid, stringbuffer_t *sb) +{ + uint32_t i; + uint32_t k; + /* int dimension=2; */ + + stringbuffer_aprintf(sb,""); +} + +static int +asx3d3_collection_sb(const LWCOLLECTION *col, int precision, int opts, const char *defid, stringbuffer_t *sb) +{ + uint32_t i; + LWGEOM *subgeom; + + /* Open outmost tag */ + /** @TODO: if collection should be grouped, we'll wrap in a group tag. Still needs cleanup + * like the shapes should really be in a transform **/ +#ifdef PGIS_X3D_OUTERMOST_TAGS + stringbuffer_aprintf(sb, "<%sGroup>", defid); +#endif + + for (i=0; ingeoms; i++) + { + subgeom = col->geoms[i]; + stringbuffer_aprintf(sb, "", defid); + if ( subgeom->type == POINTTYPE ) + { + asx3d3_point_sb((LWPOINT *)subgeom, precision, opts, defid, sb); + } + else if ( subgeom->type == LINETYPE ) + { + asx3d3_line_sb((LWLINE *)subgeom, precision, opts, defid, sb); + } + else if ( subgeom->type == POLYGONTYPE ) + { + asx3d3_poly_sb((LWPOLY *)subgeom, precision, opts, 0, defid, sb); + } + else if ( subgeom->type == TINTYPE ) + { + asx3d3_tin_sb((LWTIN *)subgeom, precision, opts, defid, sb); + } + else if ( subgeom->type == POLYHEDRALSURFACETYPE ) + { + asx3d3_psurface_sb((LWPSURFACE *)subgeom, precision, opts, defid, sb); + } + else if ( lwgeom_is_collection(subgeom) ) + { + if ( subgeom->type == COLLECTIONTYPE ) + asx3d3_collection_sb((LWCOLLECTION *)subgeom, precision, opts, defid, sb); + else + asx3d3_multi_sb((LWCOLLECTION *)subgeom, precision, opts, defid, sb); + } + else + lwerror("asx3d3_collection_buf: unknown geometry type"); + + stringbuffer_aprintf(sb, ""); + } + + /* Close outmost tag */ +#ifdef PGIS_X3D_OUTERMOST_TAGS + stringbuffer_aprintf(sb, "", defid); +#endif + + return LW_SUCCESS; +} + +/** In X3D3, coordinates are separated by a space separator + */ +static int +ptarray_to_x3d3_sb(POINTARRAY *pa, int precision, int opts, int is_closed, stringbuffer_t *sb ) +{ + uint32_t i; + char x[OUT_DOUBLE_BUFFER_SIZE]; + char y[OUT_DOUBLE_BUFFER_SIZE]; + char z[OUT_DOUBLE_BUFFER_SIZE]; + + if ( ! FLAGS_GET_Z(pa->flags) ) + { + for (i=0; inpoints; i++) + { + /** Only output the point if it is not the last point of a closed object or it is a non-closed type **/ + if ( !is_closed || i < (pa->npoints - 1) ) + { + POINT2D pt; + getPoint2d_p(pa, i, &pt); + + lwprint_double(pt.x, precision, x); + lwprint_double(pt.y, precision, y); + + if ( i ) stringbuffer_append_len(sb," ",1); + + if ( ( opts & LW_X3D_FLIP_XY) ) + stringbuffer_aprintf(sb, "%s %s", y, x); + else + stringbuffer_aprintf(sb, "%s %s", x, y); + } + } + } + else + { + for (i=0; inpoints; i++) + { + /** Only output the point if it is not the last point of a closed object or it is a non-closed type **/ + if ( !is_closed || i < (pa->npoints - 1) ) + { + POINT4D pt; + getPoint4d_p(pa, i, &pt); + + lwprint_double(pt.x, precision, x); + lwprint_double(pt.y, precision, y); + lwprint_double(pt.z, precision, z); + + if ( i ) stringbuffer_append_len(sb," ",1); + + if ( ( opts & LW_X3D_FLIP_XY) ) + stringbuffer_aprintf(sb, "%s %s %s", y, x, z); + else + stringbuffer_aprintf(sb, "%s %s %s", x, y, z); + } + } + } + + return LW_SUCCESS; +} diff --git a/mgist-postgis/liblwgeom/lwout_x3d.h b/mgist-postgis/liblwgeom/lwout_x3d.h new file mode 100644 index 0000000..ca8c6db --- /dev/null +++ b/mgist-postgis/liblwgeom/lwout_x3d.h @@ -0,0 +1,48 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2011-2017 Arrival 3D, Regina Obe + * + **********************************************************************/ + +/** +* @file X3D output routines. +* +**********************************************************************/ +#include +#include "liblwgeom_internal.h" +#include "stringbuffer.h" + +/** defid is the id of the coordinate can be used to hold other elements DEF='abc' transform='' etc. **/ +static int lwgeom_to_x3d3_sb(const LWGEOM *geom, int precision, int opts, const char *defid, stringbuffer_t *sb); + +static int asx3d3_point_sb(const LWPOINT *point, int precision, int opts, const char *defid, stringbuffer_t *sb); +static int asx3d3_line_sb(const LWLINE *line, int precision, int opts, const char *defid, stringbuffer_t *sb); + +static int +asx3d3_triangle_sb(const LWTRIANGLE *triangle, int precision, int opts, const char *defid, stringbuffer_t *sb); + +static int asx3d3_multi_sb(const LWCOLLECTION *col, int precision, int opts, const char *defid, stringbuffer_t *sb); +static int asx3d3_psurface_sb(const LWPSURFACE *psur, int precision, int opts, const char *defid, stringbuffer_t *sb); +static int asx3d3_tin_sb(const LWTIN *tin, int precision, int opts, const char *defid, stringbuffer_t *sb); + +static int +asx3d3_collection_sb(const LWCOLLECTION *col, int precision, int opts, const char *defid, stringbuffer_t *sb); +static int ptarray_to_x3d3_sb(POINTARRAY *pa, int precision, int opts, int is_closed, stringbuffer_t *sb ); diff --git a/mgist-postgis/liblwgeom/lwpoint.c b/mgist-postgis/liblwgeom/lwpoint.c new file mode 100644 index 0000000..125214c --- /dev/null +++ b/mgist-postgis/liblwgeom/lwpoint.c @@ -0,0 +1,324 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2001-2006 Refractions Research Inc. + * + **********************************************************************/ + + +#include +#include +#include +// #include "../postgis_config.h" +/*#define POSTGIS_DEBUG_LEVEL 4*/ +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + + +/* + * Convenience functions to hide the POINTARRAY + * TODO: obsolete this + */ +int +lwpoint_getPoint2d_p(const LWPOINT *point, POINT2D *out) +{ + return lwpoint_is_empty(point) ? 0 : getPoint2d_p(point->point, 0, out); +} + +/* convenience functions to hide the POINTARRAY */ +int +lwpoint_getPoint3dz_p(const LWPOINT *point, POINT3DZ *out) +{ + return lwpoint_is_empty(point) ? 0 : getPoint3dz_p(point->point,0,out); +} +int +lwpoint_getPoint3dm_p(const LWPOINT *point, POINT3DM *out) +{ + return lwpoint_is_empty(point) ? 0 : getPoint3dm_p(point->point,0,out); +} +int +lwpoint_getPoint4d_p(const LWPOINT *point, POINT4D *out) +{ + return lwpoint_is_empty(point) ? 0 : getPoint4d_p(point->point,0,out); +} + +double +lwpoint_get_x(const LWPOINT *point) +{ + POINT4D pt; + if ( lwpoint_is_empty(point) ) + { + lwerror("lwpoint_get_x called with empty geometry"); + return 0; + } + getPoint4d_p(point->point, 0, &pt); + return pt.x; +} + +double +lwpoint_get_y(const LWPOINT *point) +{ + POINT4D pt; + if ( lwpoint_is_empty(point) ) + { + lwerror("lwpoint_get_y called with empty geometry"); + return 0; + } + getPoint4d_p(point->point, 0, &pt); + return pt.y; +} + +double +lwpoint_get_z(const LWPOINT *point) +{ + POINT4D pt; + if ( lwpoint_is_empty(point) ) + { + lwerror("lwpoint_get_z called with empty geometry"); + return 0; + } + if ( ! FLAGS_GET_Z(point->flags) ) + { + lwerror("lwpoint_get_z called without z dimension"); + return 0; + } + getPoint4d_p(point->point, 0, &pt); + return pt.z; +} + +double +lwpoint_get_m(const LWPOINT *point) +{ + POINT4D pt; + if ( lwpoint_is_empty(point) ) + { + lwerror("lwpoint_get_m called with empty geometry"); + return 0; + } + if ( ! FLAGS_GET_M(point->flags) ) + { + lwerror("lwpoint_get_m called without m dimension"); + return 0; + } + getPoint4d_p(point->point, 0, &pt); + return pt.m; +} + +/* + * Construct a new point. point will not be copied + * use SRID=SRID_UNKNOWN for unknown SRID (will have 8bit type's S = 0) + */ +LWPOINT * +lwpoint_construct(int32_t srid, GBOX *bbox, POINTARRAY *point) +{ + LWPOINT *result; + lwflags_t flags = 0; + + if (point == NULL) + return NULL; /* error */ + + result = lwalloc(sizeof(LWPOINT)); + result->type = POINTTYPE; + FLAGS_SET_Z(flags, FLAGS_GET_Z(point->flags)); + FLAGS_SET_M(flags, FLAGS_GET_M(point->flags)); + FLAGS_SET_BBOX(flags, bbox?1:0); + result->flags = flags; + result->srid = srid; + result->point = point; + result->bbox = bbox; + + return result; +} + +LWPOINT * +lwpoint_construct_empty(int32_t srid, char hasz, char hasm) +{ + LWPOINT *result = lwalloc(sizeof(LWPOINT)); + result->type = POINTTYPE; + result->flags = lwflags(hasz, hasm, 0); + result->srid = srid; + result->point = ptarray_construct(hasz, hasm, 0); + result->bbox = NULL; + return result; +} + +LWPOINT * +lwpoint_make2d(int32_t srid, double x, double y) +{ + POINT4D p = {x, y, 0.0, 0.0}; + POINTARRAY *pa = ptarray_construct_empty(0, 0, 1); + + ptarray_append_point(pa, &p, LW_TRUE); + return lwpoint_construct(srid, NULL, pa); +} + +LWPOINT * +lwpoint_make3dz(int32_t srid, double x, double y, double z) +{ + POINT4D p = {x, y, z, 0.0}; + POINTARRAY *pa = ptarray_construct_empty(1, 0, 1); + + ptarray_append_point(pa, &p, LW_TRUE); + + return lwpoint_construct(srid, NULL, pa); +} + +LWPOINT * +lwpoint_make3dm(int32_t srid, double x, double y, double m) +{ + POINT4D p = {x, y, 0.0, m}; + POINTARRAY *pa = ptarray_construct_empty(0, 1, 1); + + ptarray_append_point(pa, &p, LW_TRUE); + + return lwpoint_construct(srid, NULL, pa); +} + +LWPOINT * +lwpoint_make4d(int32_t srid, double x, double y, double z, double m) +{ + POINT4D p = {x, y, z, m}; + POINTARRAY *pa = ptarray_construct_empty(1, 1, 1); + + ptarray_append_point(pa, &p, LW_TRUE); + + return lwpoint_construct(srid, NULL, pa); +} + +LWPOINT * +lwpoint_make(int32_t srid, int hasz, int hasm, const POINT4D *p) +{ + POINTARRAY *pa = ptarray_construct_empty(hasz, hasm, 1); + ptarray_append_point(pa, p, LW_TRUE); + return lwpoint_construct(srid, NULL, pa); +} + +void lwpoint_free(LWPOINT *pt) +{ + if ( ! pt ) return; + + if ( pt->bbox ) + lwfree(pt->bbox); + if ( pt->point ) + ptarray_free(pt->point); + lwfree(pt); +} + +void printLWPOINT(LWPOINT *point) +{ + lwnotice("LWPOINT {"); + lwnotice(" ndims = %i", (int)FLAGS_NDIMS(point->flags)); + lwnotice(" BBOX = %i", FLAGS_GET_BBOX(point->flags) ? 1 : 0 ); + lwnotice(" SRID = %i", (int)point->srid); + printPA(point->point); + lwnotice("}"); +} + +/* @brief Clone LWPOINT object. Serialized point lists are not copied. + * + * @see ptarray_clone + */ +LWPOINT * +lwpoint_clone(const LWPOINT *g) +{ + LWPOINT *ret = lwalloc(sizeof(LWPOINT)); + + LWDEBUG(2, "lwpoint_clone called"); + + memcpy(ret, g, sizeof(LWPOINT)); + + ret->point = ptarray_clone(g->point); + + if ( g->bbox ) ret->bbox = gbox_copy(g->bbox); + return ret; +} + + + +void +lwpoint_release(LWPOINT *lwpoint) +{ + lwgeom_release(lwpoint_as_lwgeom(lwpoint)); +} + + +/* check coordinate equality */ +char +lwpoint_same(const LWPOINT *p1, const LWPOINT *p2) +{ + return ptarray_same(p1->point, p2->point); +} + +/* check 2d coordinate equality */ +char +lwpoint_same2d(const LWPOINT *p1, const LWPOINT *p2) +{ + return ptarray_same2d(p1->point, p2->point); +} + +LWPOINT * +lwpoint_project_lwpoint(const LWPOINT* lwpoint1, const LWPOINT* lwpoint2, double distance) +{ + POINT4D p1, p2, p3; + int srid = lwgeom_get_srid((const LWGEOM*)lwpoint1); + int hasz = lwgeom_has_z((const LWGEOM*)lwpoint1); + int hasm = lwgeom_has_m((const LWGEOM*)lwpoint1); + lwpoint_getPoint4d_p(lwpoint1, &p1); + lwpoint_getPoint4d_p(lwpoint2, &p2); + project_pt_pt(&p1, &p2, distance, &p3); + return lwpoint_make(srid, hasz, hasm, &p3); +} + +LWPOINT * +lwpoint_project(const LWPOINT* lwpoint1, double distance, double azimuth) +{ + POINT4D p1, p2; + int srid = lwgeom_get_srid((const LWGEOM*)lwpoint1); + int hasz = lwgeom_has_z((const LWGEOM*)lwpoint1); + int hasm = lwgeom_has_m((const LWGEOM*)lwpoint1); + lwpoint_getPoint4d_p(lwpoint1, &p1); + lwpoint_getPoint4d_p(lwpoint1, &p2); + project_pt((POINT2D*)&p1, distance, azimuth, (POINT2D*)&p2); + return lwpoint_make(srid, hasz, hasm, &p2); +} + + +LWPOINT* +lwpoint_force_dims(const LWPOINT *point, int hasz, int hasm, double zval, double mval) +{ + POINTARRAY *pdims = NULL; + LWPOINT *pointout; + + /* Return 2D empty */ + if( lwpoint_is_empty(point) ) + { + pointout = lwpoint_construct_empty(point->srid, hasz, hasm); + } + else + { + /* Always we duplicate the ptarray and return */ + pdims = ptarray_force_dims(point->point, hasz, hasm, zval, mval); + pointout = lwpoint_construct(point->srid, NULL, pdims); + } + pointout->type = point->type; + return pointout; +} + + diff --git a/mgist-postgis/liblwgeom/lwpoly.c b/mgist-postgis/liblwgeom/lwpoly.c new file mode 100644 index 0000000..716bf00 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwpoly.c @@ -0,0 +1,560 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2012 Sandro Santilli + * Copyright (C) 2001-2006 Refractions Research Inc. + * + **********************************************************************/ + + +/* basic LWPOLY manipulation */ + +#include +#include +#include +#include +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + + +#define CHECK_POLY_RINGS_ZM 1 + +/* construct a new LWPOLY. arrays (points/points per ring) will NOT be copied + * use SRID=SRID_UNKNOWN for unknown SRID (will have 8bit type's S = 0) + */ +LWPOLY * +lwpoly_construct(int32_t srid, GBOX *bbox, uint32_t nrings, POINTARRAY **points) +{ + LWPOLY *result; + int hasz, hasm; +#ifdef CHECK_POLY_RINGS_ZM + char zm; + uint32_t i; +#endif + + if ( nrings < 1 ) lwerror("lwpoly_construct: need at least 1 ring"); + + hasz = FLAGS_GET_Z(points[0]->flags); + hasm = FLAGS_GET_M(points[0]->flags); + +#ifdef CHECK_POLY_RINGS_ZM + zm = FLAGS_GET_ZM(points[0]->flags); + for (i=1; iflags) ) + lwerror("lwpoly_construct: mixed dimensioned rings"); + } +#endif + + result = (LWPOLY*) lwalloc(sizeof(LWPOLY)); + result->type = POLYGONTYPE; + result->flags = lwflags(hasz, hasm, 0); + FLAGS_SET_BBOX(result->flags, bbox?1:0); + result->srid = srid; + result->nrings = nrings; + result->maxrings = nrings; + result->rings = points; + result->bbox = bbox; + + return result; +} + +LWPOLY* +lwpoly_construct_rectangle(char hasz, char hasm, POINT4D *p1, POINT4D *p2, + POINT4D *p3, POINT4D *p4) +{ + POINTARRAY *pa = ptarray_construct_empty(hasz, hasm, 5); + LWPOLY *lwpoly = lwpoly_construct_empty(SRID_UNKNOWN, hasz, hasm); + + ptarray_append_point(pa, p1, LW_TRUE); + ptarray_append_point(pa, p2, LW_TRUE); + ptarray_append_point(pa, p3, LW_TRUE); + ptarray_append_point(pa, p4, LW_TRUE); + ptarray_append_point(pa, p1, LW_TRUE); + + lwpoly_add_ring(lwpoly, pa); + + return lwpoly; +} + +LWPOLY * +lwpoly_construct_envelope(int32_t srid, double x1, double y1, double x2, double y2) +{ + POINT4D p1, p2, p3, p4; + LWPOLY *poly; + + p1.x = x1; + p1.y = y1; + p2.x = x1; + p2.y = y2; + p3.x = x2; + p3.y = y2; + p4.x = x2; + p4.y = y1; + + poly = lwpoly_construct_rectangle(0, 0, &p1, &p2, &p3, &p4); + lwgeom_set_srid(lwpoly_as_lwgeom(poly), srid); + lwgeom_add_bbox(lwpoly_as_lwgeom(poly)); + + return poly; +} + +LWPOLY * +lwpoly_construct_circle(int32_t srid, double x, double y, double radius, uint32_t segments_per_quarter, char exterior) +{ + const uint32_t segments = 4*segments_per_quarter; + double theta; + LWPOLY *lwpoly; + POINTARRAY *pa; + POINT4D pt; + uint32_t i; + + if (segments_per_quarter == 0) + { + lwerror("Need at least one segment per quarter-circle."); + return NULL; + } + + if (radius < 0) + { + lwerror("Radius must be positive."); + return NULL; + } + + theta = 2*M_PI / segments; + + lwpoly = lwpoly_construct_empty(srid, LW_FALSE, LW_FALSE); + pa = ptarray_construct_empty(LW_FALSE, LW_FALSE, segments + 1); + + if (exterior) + radius *= sqrt(1 + pow(tan(theta/2), 2)); + + for (i = 0; i <= segments; i++) + { + pt.x = x + radius*sin(i * theta); + pt.y = y + radius*cos(i * theta); + ptarray_append_point(pa, &pt, LW_TRUE); + } + + lwpoly_add_ring(lwpoly, pa); + return lwpoly; +} + +LWPOLY * +lwpoly_construct_empty(int32_t srid, char hasz, char hasm) +{ + LWPOLY *result = lwalloc(sizeof(LWPOLY)); + result->type = POLYGONTYPE; + result->flags = lwflags(hasz,hasm,0); + result->srid = srid; + result->nrings = 0; + result->maxrings = 1; /* Allocate room for ring, just in case. */ + result->rings = lwalloc(result->maxrings * sizeof(POINTARRAY*)); + result->bbox = NULL; + return result; +} + +void +lwpoly_free(LWPOLY* poly) +{ + uint32_t t; + + if (!poly) return; + + if (poly->bbox) lwfree(poly->bbox); + + if ( poly->rings ) + { + for (t = 0; t < poly->nrings; t++) + if (poly->rings[t]) ptarray_free(poly->rings[t]); + lwfree(poly->rings); + } + + lwfree(poly); +} + +void printLWPOLY(LWPOLY *poly) +{ + uint32_t t; + lwnotice("LWPOLY {"); + lwnotice(" ndims = %i", (int)FLAGS_NDIMS(poly->flags)); + lwnotice(" SRID = %i", (int)poly->srid); + lwnotice(" nrings = %i", (int)poly->nrings); + for (t=0; tnrings; t++) + { + lwnotice(" RING # %i :",t); + printPA(poly->rings[t]); + } + lwnotice("}"); +} + +/* @brief Clone LWLINE object. Serialized point lists are not copied. + * + * @see ptarray_clone + */ +LWPOLY * +lwpoly_clone(const LWPOLY *g) +{ + uint32_t i; + LWPOLY *ret = lwalloc(sizeof(LWPOLY)); + memcpy(ret, g, sizeof(LWPOLY)); + ret->rings = lwalloc(sizeof(POINTARRAY *)*g->nrings); + for ( i = 0; i < g->nrings; i++ ) { + ret->rings[i] = ptarray_clone(g->rings[i]); + } + if ( g->bbox ) ret->bbox = gbox_copy(g->bbox); + return ret; +} + +/* Deep clone LWPOLY object. POINTARRAY are copied, as is ring array */ +LWPOLY * +lwpoly_clone_deep(const LWPOLY *g) +{ + uint32_t i; + LWPOLY *ret = lwalloc(sizeof(LWPOLY)); + memcpy(ret, g, sizeof(LWPOLY)); + if ( g->bbox ) ret->bbox = gbox_copy(g->bbox); + ret->rings = lwalloc(sizeof(POINTARRAY *)*g->nrings); + for ( i = 0; i < ret->nrings; i++ ) + { + ret->rings[i] = ptarray_clone_deep(g->rings[i]); + } + FLAGS_SET_READONLY(ret->flags,0); + return ret; +} + +/** +* Add a ring to a polygon. Point array will be referenced, not copied. +*/ +int +lwpoly_add_ring(LWPOLY *poly, POINTARRAY *pa) +{ + if( ! poly || ! pa ) + return LW_FAILURE; + + /* We have used up our storage, add some more. */ + if( poly->nrings >= poly->maxrings ) + { + int new_maxrings = 2 * (poly->nrings + 1); + poly->rings = lwrealloc(poly->rings, new_maxrings * sizeof(POINTARRAY*)); + poly->maxrings = new_maxrings; + } + + /* Add the new ring entry. */ + poly->rings[poly->nrings] = pa; + poly->nrings++; + + return LW_SUCCESS; +} + +void +lwpoly_force_clockwise(LWPOLY *poly) +{ + uint32_t i; + + /* No-op empties */ + if ( lwpoly_is_empty(poly) ) + return; + + /* External ring */ + if ( ptarray_isccw(poly->rings[0]) ) + ptarray_reverse_in_place(poly->rings[0]); + + /* Internal rings */ + for (i=1; inrings; i++) + if ( ! ptarray_isccw(poly->rings[i]) ) + ptarray_reverse_in_place(poly->rings[i]); + +} + +int +lwpoly_is_clockwise(LWPOLY *poly) +{ + uint32_t i; + + if ( lwpoly_is_empty(poly) ) + return LW_TRUE; + + if ( ptarray_isccw(poly->rings[0]) ) + return LW_FALSE; + + for ( i = 1; i < poly->nrings; i++) + if ( !ptarray_isccw(poly->rings[i]) ) + return LW_FALSE; + + return LW_TRUE; +} + +void +lwpoly_release(LWPOLY *lwpoly) +{ + lwgeom_release(lwpoly_as_lwgeom(lwpoly)); +} + +LWPOLY * +lwpoly_segmentize2d(const LWPOLY *poly, double dist) +{ + POINTARRAY **newrings; + uint32_t i; + + newrings = lwalloc(sizeof(POINTARRAY *)*poly->nrings); + for (i=0; inrings; i++) + { + newrings[i] = ptarray_segmentize2d(poly->rings[i], dist); + if ( ! newrings[i] ) + { + uint32_t j = 0; + for (j = 0; j < i; j++) + ptarray_free(newrings[j]); + lwfree(newrings); + return NULL; + } + } + return lwpoly_construct(poly->srid, NULL, + poly->nrings, newrings); +} + +/* + * check coordinate equality + * ring and coordinate order is considered + */ +char +lwpoly_same(const LWPOLY *p1, const LWPOLY *p2) +{ + uint32_t i; + + if ( p1->nrings != p2->nrings ) return 0; + for (i=0; inrings; i++) + { + if ( ! ptarray_same(p1->rings[i], p2->rings[i]) ) + return 0; + } + return 1; +} + +/* + * Construct a polygon from a LWLINE being + * the shell and an array of LWLINE (possibly NULL) being holes. + * Pointarrays from intput geoms are cloned. + * SRID must be the same for each input line. + * Input lines must have at least 4 points, and be closed. + */ +LWPOLY * +lwpoly_from_lwlines(const LWLINE *shell, + uint32_t nholes, const LWLINE **holes) +{ + uint32_t nrings; + POINTARRAY **rings = lwalloc((nholes+1)*sizeof(POINTARRAY *)); + int32_t srid = shell->srid; + LWPOLY *ret; + + if ( shell->points->npoints < 4 ) + lwerror("lwpoly_from_lwlines: shell must have at least 4 points"); + if ( ! ptarray_is_closed_2d(shell->points) ) + lwerror("lwpoly_from_lwlines: shell must be closed"); + rings[0] = ptarray_clone_deep(shell->points); + + for (nrings=1; nrings<=nholes; nrings++) + { + const LWLINE *hole = holes[nrings-1]; + + if ( hole->srid != srid ) + lwerror("lwpoly_from_lwlines: mixed SRIDs in input lines"); + + if ( hole->points->npoints < 4 ) + lwerror("lwpoly_from_lwlines: holes must have at least 4 points"); + if ( ! ptarray_is_closed_2d(hole->points) ) + lwerror("lwpoly_from_lwlines: holes must be closed"); + + rings[nrings] = ptarray_clone_deep(hole->points); + } + + ret = lwpoly_construct(srid, NULL, nrings, rings); + return ret; +} + +LWPOLY* +lwpoly_force_dims(const LWPOLY *poly, int hasz, int hasm, double zval, double mval) +{ + LWPOLY *polyout; + + /* Return 2D empty */ + if( lwpoly_is_empty(poly) ) + { + polyout = lwpoly_construct_empty(poly->srid, hasz, hasm); + } + else + { + POINTARRAY **rings = NULL; + uint32_t i; + rings = lwalloc(sizeof(POINTARRAY*) * poly->nrings); + for( i = 0; i < poly->nrings; i++ ) + { + rings[i] = ptarray_force_dims(poly->rings[i], hasz, hasm, zval, mval); + } + polyout = lwpoly_construct(poly->srid, NULL, poly->nrings, rings); + } + polyout->type = poly->type; + return polyout; +} + +uint32_t lwpoly_count_vertices(const LWPOLY *poly) +{ + uint32_t i = 0; + uint32_t v = 0; /* vertices */ + assert(poly); + for ( i = 0; i < poly->nrings; i ++ ) + { + v += poly->rings[i]->npoints; + } + return v; +} + +/** +* Find the area of the outer ring - sum (area of inner rings). +*/ +double +lwpoly_area(const LWPOLY *poly) +{ + double poly_area = 0.0; + uint32_t i; + + if ( ! poly ) + lwerror("lwpoly_area called with null polygon pointer!"); + + for ( i=0; i < poly->nrings; i++ ) + { + POINTARRAY *ring = poly->rings[i]; + double ringarea = 0.0; + + /* Empty or messed-up ring. */ + if ( ring->npoints < 3 ) + continue; + + ringarea = fabs(ptarray_signed_area(ring)); + if ( i == 0 ) /* Outer ring, positive area! */ + poly_area += ringarea; + else /* Inner ring, negative area! */ + poly_area -= ringarea; + } + + return poly_area; +} + + +/** + * Compute the sum of polygon rings length. + * Could use a more numerically stable calculator... + */ +double +lwpoly_perimeter(const LWPOLY *poly) +{ + double result=0.0; + uint32_t i; + + LWDEBUGF(2, "in lwgeom_polygon_perimeter (%d rings)", poly->nrings); + + for (i=0; inrings; i++) + result += ptarray_length(poly->rings[i]); + + return result; +} + +/** + * Compute the sum of polygon rings length (forcing 2d computation). + * Could use a more numerically stable calculator... + */ +double +lwpoly_perimeter_2d(const LWPOLY *poly) +{ + double result=0.0; + uint32_t i; + + LWDEBUGF(2, "in lwgeom_polygon_perimeter (%d rings)", poly->nrings); + + for (i=0; inrings; i++) + result += ptarray_length_2d(poly->rings[i]); + + return result; +} + +int +lwpoly_is_closed(const LWPOLY *poly) +{ + uint32_t i = 0; + + if ( poly->nrings == 0 ) + return LW_TRUE; + + for ( i = 0; i < poly->nrings; i++ ) + { + if (FLAGS_GET_Z(poly->flags)) + { + if ( ! ptarray_is_closed_3d(poly->rings[i]) ) + return LW_FALSE; + } + else + { + if ( ! ptarray_is_closed_2d(poly->rings[i]) ) + return LW_FALSE; + } + } + + return LW_TRUE; +} + +int +lwpoly_startpoint(const LWPOLY* poly, POINT4D* pt) +{ + if ( poly->nrings < 1 ) + return LW_FAILURE; + return ptarray_startpoint(poly->rings[0], pt); +} + +int +lwpoly_contains_point(const LWPOLY *poly, const POINT2D *pt) +{ + uint32_t i; + int t; + + if ( lwpoly_is_empty(poly) ) + return LW_OUTSIDE; + + t = ptarray_contains_point(poly->rings[0], pt); + + if (t == LW_INSIDE) + { + for (i = 1; i < poly->nrings; i++) + { + t = ptarray_contains_point(poly->rings[i], pt); + if (t == LW_INSIDE) + return LW_OUTSIDE; + if (t == LW_BOUNDARY) + { + return LW_BOUNDARY; + } + } + return LW_INSIDE; + } + else + return t; +} + + diff --git a/mgist-postgis/liblwgeom/lwprint.c b/mgist-postgis/liblwgeom/lwprint.c new file mode 100644 index 0000000..adfa338 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwprint.c @@ -0,0 +1,474 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2010-2015 Paul Ramsey + * Copyright (C) 2011 Sandro Santilli + * + **********************************************************************/ + +#include "liblwgeom_internal.h" + +#include +#include +#include +#include + +#include "ryu.h" + +/* Ensures the given lat and lon are in the "normal" range: + * -90 to +90 for lat, -180 to +180 for lon. */ +static void lwprint_normalize_latlon(double *lat, double *lon) +{ + /* First remove all the truly excessive trips around the world via up or down. */ + while (*lat > 270) + { + *lat -= 360; + } + while (*lat < -270) + { + *lat += 360; + } + + /* Now see if latitude is past the top or bottom of the world. + * Past 90 or -90 puts us on the other side of the earth, + * so wrap latitude and add 180 to longitude to reflect that. */ + if (*lat > 90) + { + *lat = 180 - *lat; + *lon += 180; + } + if (*lat < -90) + { + *lat = -180 - *lat; + *lon += 180; + } + /* Now make sure lon is in the normal range. Wrapping longitude + * has no effect on latitude. */ + while (*lon > 180) + { + *lon -= 360; + } + while (*lon < -180) + { + *lon += 360; + } +} + +/* Converts a single double to DMS given the specified DMS format string. + * Symbols are specified since N/S or E/W are the only differences when printing + * lat vs. lon. They are only used if the "C" (compass dir) token appears in the + * format string. + * NOTE: Format string and symbols are required to be in UTF-8. */ +static char * lwdouble_to_dms(double val, const char *pos_dir_symbol, const char *neg_dir_symbol, const char * format) +{ + /* 3 numbers, 1 sign or compass dir, and 5 possible strings (degree signs, spaces, misc text, etc) between or around them.*/ +# define NUM_PIECES 9 +# define WORK_SIZE 1024 + char pieces[NUM_PIECES][WORK_SIZE]; + int current_piece = 0; + int is_negative = 0; + + double degrees = 0.0; + double minutes = 0.0; + double seconds = 0.0; + + int compass_dir_piece = -1; + + int reading_deg = 0; + int deg_digits = 0; + int deg_has_decpoint = 0; + int deg_dec_digits = 0; + int deg_piece = -1; + + int reading_min = 0; + int min_digits = 0; + int min_has_decpoint = 0; + int min_dec_digits = 0; + int min_piece = -1; + + int reading_sec = 0; + int sec_digits = 0; + int sec_has_decpoint = 0; + int sec_dec_digits = 0; + int sec_piece = -1; + + int round_pow = 0; + + int format_length = ((NULL == format) ? 0 : strlen(format)); + + char * result; + + int index, following_byte_index; + int multibyte_char_width = 1; + + /* Initialize the working strs to blank. We may not populate all of them, and + * this allows us to concat them all at the end without worrying about how many + * we actually needed. */ + for (index = 0; index < NUM_PIECES; index++) + { + pieces[index][0] = '\0'; + } + + /* If no format is provided, use a default. */ + if (0 == format_length) + { + /* C2B0 is UTF-8 for the degree symbol. */ + format = "D\xC2\xB0""M'S.SSS\"C"; + format_length = strlen(format); + } + else if (format_length > WORK_SIZE) + { + /* Sanity check, we don't want to overwrite an entire piece of work and no one should need a 1K-sized + * format string anyway. */ + lwerror("Bad format, exceeds maximum length (%d).", WORK_SIZE); + } + + for (index = 0; index < format_length; index++) + { + char next_char = format[index]; + switch (next_char) + { + case 'D': + if (reading_deg) + { + /* If we're reading degrees, add another digit. */ + deg_has_decpoint ? deg_dec_digits++ : deg_digits++; + } + else + { + /* If we're not reading degrees, we are now. */ + current_piece++; + deg_piece = current_piece; + if (deg_digits > 0) + { + lwerror("Bad format, cannot include degrees (DD.DDD) more than once."); + } + reading_deg = 1; + reading_min = 0; + reading_sec = 0; + deg_digits++; + } + break; + case 'M': + if (reading_min) + { + /* If we're reading minutes, add another digit. */ + min_has_decpoint ? min_dec_digits++ : min_digits++; + } + else + { + /* If we're not reading minutes, we are now. */ + current_piece++; + min_piece = current_piece; + if (min_digits > 0) + { + lwerror("Bad format, cannot include minutes (MM.MMM) more than once."); + } + reading_deg = 0; + reading_min = 1; + reading_sec = 0; + min_digits++; + } + break; + case 'S': + if (reading_sec) + { + /* If we're reading seconds, add another digit. */ + sec_has_decpoint ? sec_dec_digits++ : sec_digits++; + } + else + { + /* If we're not reading seconds, we are now. */ + current_piece++; + sec_piece = current_piece; + if (sec_digits > 0) + { + lwerror("Bad format, cannot include seconds (SS.SSS) more than once."); + } + reading_deg = 0; + reading_min = 0; + reading_sec = 1; + sec_digits++; + } + break; + case 'C': + /* We're done reading anything else we might have been reading. */ + if (reading_deg || reading_min || reading_sec) + { + /* We were reading something, that means this is the next piece. */ + reading_deg = 0; + reading_min = 0; + reading_sec = 0; + } + current_piece++; + + if (compass_dir_piece >= 0) + { + lwerror("Bad format, cannot include compass dir (C) more than once."); + } + /* The compass dir is a piece all by itself. */ + compass_dir_piece = current_piece; + current_piece++; + break; + case '.': + /* If we're reading deg, min, or sec, we want a decimal point for it. */ + if (reading_deg) + { + deg_has_decpoint = 1; + } + else if (reading_min) + { + min_has_decpoint = 1; + } + else if (reading_sec) + { + sec_has_decpoint = 1; + } + else + { + /* Not reading anything, just pass through the '.' */ + strncat(pieces[current_piece], &next_char, 1); + } + break; + default: + /* Any other char is just passed through unchanged. But it does mean we are done reading D, M, or S.*/ + if (reading_deg || reading_min || reading_sec) + { + /* We were reading something, that means this is the next piece. */ + current_piece++; + reading_deg = 0; + reading_min = 0; + reading_sec = 0; + } + + /* Check if this is a multi-byte UTF-8 character. If so go ahead and read the rest of the bytes as well. */ + multibyte_char_width = 1; + if (next_char & 0x80) + { + if ((next_char & 0xF8) == 0xF0) + { + multibyte_char_width += 3; + } + else if ((next_char & 0xF0) == 0xE0) + { + multibyte_char_width += 2; + } + else if ((next_char & 0xE0) == 0xC0) + { + multibyte_char_width += 1; + } + else + { + lwerror("Bad format, invalid high-order byte found first, format string may not be UTF-8."); + } + } + if (multibyte_char_width > 1) + { + if (index + multibyte_char_width >= format_length) + { + lwerror("Bad format, UTF-8 character first byte found with insufficient following bytes, format string may not be UTF-8."); + } + for (following_byte_index = (index + 1); following_byte_index < (index + multibyte_char_width); following_byte_index++) + { + if ((format[following_byte_index] & 0xC0) != 0x80) + { + lwerror("Bad format, invalid byte found following leading byte of multibyte character, format string may not be UTF-8."); + } + } + } + /* Copy all the character's bytes into the current piece. */ + strncat(pieces[current_piece], &(format[index]), multibyte_char_width); + /* Now increment index past the rest of those bytes. */ + index += multibyte_char_width - 1; + break; + } + if (current_piece >= NUM_PIECES) + { + lwerror("Internal error, somehow needed more pieces than it should."); + } + } + if (deg_piece < 0) + { + lwerror("Bad format, degrees (DD.DDD) must be included."); + } + + /* Divvy the number up into D, DM, or DMS */ + if (val < 0) + { + val *= -1; + is_negative = 1; + } + degrees = val; + if (min_digits > 0) + { + /* Break degrees to integer and use fraction for minutes */ + minutes = modf(val, °rees) * 60; + } + if (sec_digits > 0) + { + if (0 == min_digits) + { + lwerror("Bad format, cannot include seconds (SS.SSS) without including minutes (MM.MMM)."); + } + seconds = modf(minutes, &minutes) * 60; + if (sec_piece >= 0) + { + /* See if the formatted seconds round up to 60. If so, increment minutes and reset seconds. */ + round_pow = pow(10, sec_dec_digits); + if (floorf(seconds * round_pow) / round_pow >= 60) + { + minutes += 1; + seconds = 0; + } + } + } + + /* Handle the compass direction. If not using compass dir, display degrees as a positive/negative number. */ + if (compass_dir_piece >= 0) + { + strcpy(pieces[compass_dir_piece], is_negative ? neg_dir_symbol : pos_dir_symbol); + } + else if (is_negative) + { + degrees *= -1; + } + + /* Format the degrees into their string piece. */ + if (deg_digits + deg_dec_digits + 2 > WORK_SIZE) + { + lwerror("Bad format, degrees (DD.DDD) number of digits was greater than our working limit."); + } + if(deg_piece >= 0) + { + snprintf(pieces[deg_piece], WORK_SIZE, "%*.*f", deg_digits, deg_dec_digits, degrees); + } + + if (min_piece >= 0) + { + /* Format the minutes into their string piece. */ + if (min_digits + min_dec_digits + 2 > WORK_SIZE) + { + lwerror("Bad format, minutes (MM.MMM) number of digits was greater than our working limit."); + } + snprintf(pieces[min_piece], WORK_SIZE, "%*.*f", min_digits, min_dec_digits, minutes); + } + if (sec_piece >= 0) + { + /* Format the seconds into their string piece. */ + if (sec_digits + sec_dec_digits + 2 > WORK_SIZE) + { + lwerror("Bad format, seconds (SS.SSS) number of digits was greater than our working limit."); + } + snprintf(pieces[sec_piece], WORK_SIZE, "%*.*f", sec_digits, sec_dec_digits, seconds); + } + + /* Allocate space for the result. Leave plenty of room for excess digits, negative sign, etc.*/ + result = (char*)lwalloc(format_length + WORK_SIZE); + memset(result, 0, format_length + WORK_SIZE); + + /* Append all the pieces together. There may be less than 9, but in that case the rest will be blank. */ + strcpy(result, pieces[0]); + for (index = 1; index < NUM_PIECES; index++) + { + strcat(result, pieces[index]); + } + + return result; +} + +/* Print two doubles (lat and lon) in DMS form using the specified format. + * First normalizes them so they will display as -90 to 90 and -180 to 180. + * Format string may be null or 0-length, in which case a default format will be used. + * NOTE: Format string is required to be in UTF-8. + * NOTE2: returned string is lwalloc'ed, caller is responsible to lwfree it up + */ +static char * lwdoubles_to_latlon(double lat, double lon, const char * format) +{ + char * lat_text; + char * lon_text; + char * result; + size_t sz; + + /* Normalize lat/lon to the normal (-90 to 90, -180 to 180) range. */ + lwprint_normalize_latlon(&lat, &lon); + /* This is somewhat inefficient as the format is parsed twice. */ + lat_text = lwdouble_to_dms(lat, "N", "S", format); + lon_text = lwdouble_to_dms(lon, "E", "W", format); + + /* lat + lon + a space between + the null terminator. */ + sz = strlen(lat_text) + strlen(lon_text) + 2; + result = (char*)lwalloc(sz); + snprintf(result, sz, "%s %s", lat_text, lon_text); + lwfree(lat_text); + lwfree(lon_text); + return result; +} + +/* Print the X (lon) and Y (lat) of the given point in DMS form using + * the specified format. + * First normalizes the values so they will display as -90 to 90 and -180 to 180. + * Format string may be null or 0-length, in which case a default format will be used. + * NOTE: Format string is required to be in UTF-8. + * NOTE2: returned string is lwalloc'ed, caller is responsible to lwfree it up + */ +char* lwpoint_to_latlon(const LWPOINT * pt, const char *format) +{ + const POINT2D *p; + if (NULL == pt) + { + lwerror("Cannot convert a null point into formatted text."); + } + if (lwgeom_is_empty((LWGEOM *)pt)) + { + lwerror("Cannot convert an empty point into formatted text."); + } + p = getPoint2d_cp(pt->point, 0); + return lwdoubles_to_latlon(p->y, p->x, format); +} + +/* + * Print an ordinate value using at most **maxdd** number of decimal digits + * The actual number of printed decimal digits may be less than the + * requested ones if out of significant digits. + * + * The function will write at most OUT_DOUBLE_BUFFER_SIZE bytes, including the + * terminating NULL. + * It returns the number of bytes written (exluding the final NULL) + * + */ +int +lwprint_double(double d, int maxdd, char *buf) +{ + int length; + double ad = fabs(d); + int precision = FP_MAX(0, maxdd); + + if (ad <= OUT_MIN_DOUBLE || ad >= OUT_MAX_DOUBLE) + { + length = d2sexp_buffered_n(d, precision, buf); + } + else + { + length = d2sfixed_buffered_n(d, precision, buf); + } + buf[length] = '\0'; + + return length; +} diff --git a/mgist-postgis/liblwgeom/lwpsurface.c b/mgist-postgis/liblwgeom/lwpsurface.c new file mode 100644 index 0000000..a4527c3 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwpsurface.c @@ -0,0 +1,204 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2001-2006 Refractions Research Inc. + * + **********************************************************************/ + + +#include +#include +#include +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + + +LWPSURFACE* lwpsurface_add_lwpoly(LWPSURFACE *mobj, const LWPOLY *obj) +{ + return (LWPSURFACE*)lwcollection_add_lwgeom((LWCOLLECTION*)mobj, (LWGEOM*)obj); +} + + +void lwpsurface_free(LWPSURFACE *psurf) +{ + uint32_t i; + if ( ! psurf ) return; + if ( psurf->bbox ) + lwfree(psurf->bbox); + + for ( i = 0; i < psurf->ngeoms; i++ ) + if ( psurf->geoms && psurf->geoms[i] ) + lwpoly_free(psurf->geoms[i]); + + if ( psurf->geoms ) + lwfree(psurf->geoms); + + lwfree(psurf); +} + + +void printLWPSURFACE(LWPSURFACE *psurf) +{ + uint32_t i, j; + LWPOLY *patch; + + if (psurf->type != POLYHEDRALSURFACETYPE) + lwerror("printLWPSURFACE called with something else than a POLYHEDRALSURFACE"); + + lwnotice("LWPSURFACE {"); + lwnotice(" ndims = %i", (int)FLAGS_NDIMS(psurf->flags)); + lwnotice(" SRID = %i", (int)psurf->srid); + lwnotice(" ngeoms = %i", (int)psurf->ngeoms); + + for (i=0; ingeoms; i++) + { + patch = (LWPOLY *) psurf->geoms[i]; + for (j=0; jnrings; j++) + { + lwnotice(" RING # %i :",j); + printPA(patch->rings[j]); + } + } + lwnotice("}"); +} + + + + +/* + * TODO rewrite all this stuff to be based on a truly topological model + */ + +struct struct_psurface_arcs +{ + double ax, ay, az; + double bx, by, bz; + uint32_t cnt, face; +}; +typedef struct struct_psurface_arcs *psurface_arcs; + +/* We supposed that the geometry is valid + we could have wrong result if not */ +int lwpsurface_is_closed(const LWPSURFACE *psurface) +{ + uint32_t i, j, k; + uint32_t narcs, carc; + int found; + psurface_arcs arcs; + POINT4D pa, pb; + LWPOLY *patch; + + /* If surface is not 3D, it's can't be closed */ + if (!FLAGS_GET_Z(psurface->flags)) return 0; + + /* If surface is less than 4 faces hard to be closed too */ + if (psurface->ngeoms < 4) return 0; + + /* Max theoretical arcs number if no one is shared ... */ + for (i=0, narcs=0 ; i < psurface->ngeoms ; i++) + { + patch = (LWPOLY *) psurface->geoms[i]; + narcs += patch->rings[0]->npoints - 1; + } + + arcs = lwalloc(sizeof(struct struct_psurface_arcs) * narcs); + for (i=0, carc=0; i < psurface->ngeoms ; i++) + { + + patch = (LWPOLY *) psurface->geoms[i]; + for (j=0; j < patch->rings[0]->npoints - 1; j++) + { + + getPoint4d_p(patch->rings[0], j, &pa); + getPoint4d_p(patch->rings[0], j+1, &pb); + + /* remove redundant points if any */ + if (pa.x == pb.x && pa.y == pb.y && pa.z == pb.z) continue; + + /* Make sure to order the 'lower' point first */ + if ( (pa.x > pb.x) || + (pa.x == pb.x && pa.y > pb.y) || + (pa.x == pb.x && pa.y == pb.y && pa.z > pb.z) ) + { + pa = pb; + getPoint4d_p(patch->rings[0], j, &pb); + } + + for (found=0, k=0; k < carc ; k++) + { + + if ( ( arcs[k].ax == pa.x && arcs[k].ay == pa.y && + arcs[k].az == pa.z && arcs[k].bx == pb.x && + arcs[k].by == pb.y && arcs[k].bz == pb.z && + arcs[k].face != i) ) + { + arcs[k].cnt++; + found = 1; + + /* Look like an invalid PolyhedralSurface + anyway not a closed one */ + if (arcs[k].cnt > 2) + { + lwfree(arcs); + return 0; + } + } + } + + if (!found) + { + arcs[carc].cnt=1; + arcs[carc].face=i; + arcs[carc].ax = pa.x; + arcs[carc].ay = pa.y; + arcs[carc].az = pa.z; + arcs[carc].bx = pb.x; + arcs[carc].by = pb.y; + arcs[carc].bz = pb.z; + carc++; + + /* Look like an invalid PolyhedralSurface + anyway not a closed one */ + if (carc > narcs) + { + lwfree(arcs); + return 0; + } + } + } + } + + /* A polyhedron is closed if each edge + is shared by exactly 2 faces */ + for (k=0; k < carc ; k++) + { + if (arcs[k].cnt != 2) + { + lwfree(arcs); + return 0; + } + } + lwfree(arcs); + + /* Invalid Polyhedral case */ + if (carc < psurface->ngeoms) return 0; + + return 1; +} diff --git a/mgist-postgis/liblwgeom/lwrandom.c b/mgist-postgis/liblwgeom/lwrandom.c new file mode 100644 index 0000000..8506f61 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwrandom.c @@ -0,0 +1,119 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2019 Mike Taves + * + **********************************************************************/ + +#include "lwrandom.h" + +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#define getpid _getpid +#else +#include +#endif + +static unsigned char _lwrandom_seed_set = 0; +static int32_t _lwrandom_seed[3] = {0x330e, 0xabcd, 0x1234}; + +/* + * Set seed for a random number generator. + * Repeatable numbers are generated with seed values >= 1. + * When seed is zero and has not previously been set, it is based on + * Unix time (seconds) and process ID. */ +void +lwrandom_set_seed(int32_t seed) +{ + if (seed == 0) + { + if (_lwrandom_seed_set == 0) + seed = (unsigned int)time(NULL) + (unsigned int)getpid() - 0xbadd; + else + return; + } + /* s1 value between 1 and 2147483562 */ + _lwrandom_seed[1] = (((int64_t)seed + 0xfeed) % 2147483562) + 1; + /* s2 value between 1 and 2147483398 */ + _lwrandom_seed[2] = ((((int64_t)seed + 0xdefeb) << 5) % 2147483398) + 1; + _lwrandom_seed_set = 1; +} + +/* for low-level external debugging */ +void +_lwrandom_set_seeds(int32_t s1, int32_t s2) +{ + /* _lwrandom_seed[0] not used */ + _lwrandom_seed[1] = s1; + _lwrandom_seed[2] = s2; + _lwrandom_seed_set = 1; +} +int32_t +_lwrandom_get_seed(size_t idx) +{ + return _lwrandom_seed[idx]; +} + +/* + * Generate a random floating-point value. + * Values are uniformly distributed between 0 and 1. + * + * Authors: + * Pierre L'Ecuyer (1988), see source code in Figure 3. + * C version by John Burkardt, modified by Mike Taves. + * + * Reference: + * Pierre L'Ecuyer, + * Efficient and Portable Combined Random Number Generators, + * Communications of the ACM, Volume 31, Number 6, June 1988, + * pages 742-751. doi:10.1145/62959.62969 + */ +double +lwrandom_uniform(void) +{ + double value; + int32_t k; + int32_t z; + int32_t *s1 = &_lwrandom_seed[1]; + int32_t *s2 = &_lwrandom_seed[2]; + + k = *s1 / 53668; + *s1 = 40014 * (*s1 - k * 53668) - k * 12211; + if (*s1 < 0) + *s1 += 2147483563; + + k = *s2 / 52774; + *s2 = 40692 * (*s2 - k * 52774) - k * 3791; + if (*s2 < 0) + *s2 += 2147483399; + + z = *s1 - *s2; + if (z < 1) + z += 2147483562; + + value = (double)(z) / 2147483563.0; + + return value; +} diff --git a/mgist-postgis/liblwgeom/lwrandom.h b/mgist-postgis/liblwgeom/lwrandom.h new file mode 100644 index 0000000..11a92bd --- /dev/null +++ b/mgist-postgis/liblwgeom/lwrandom.h @@ -0,0 +1,33 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2019 Mike Taves + * + **********************************************************************/ + +#include +#include + +void lwrandom_set_seed(int32_t seed); +double lwrandom_uniform(void); + +/* for low-level external debugging */ +void _lwrandom_set_seeds(int32_t s1, int32_t s2); +int32_t _lwrandom_get_seed(size_t idx); diff --git a/mgist-postgis/liblwgeom/lwspheroid.c b/mgist-postgis/liblwgeom/lwspheroid.c new file mode 100644 index 0000000..bf8c5cb --- /dev/null +++ b/mgist-postgis/liblwgeom/lwspheroid.c @@ -0,0 +1,705 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2009 Paul Ramsey + * Copyright (C) 2009 David Skea + * + **********************************************************************/ + + +#include "liblwgeom_internal.h" +#include "lwgeodetic.h" +#include "lwgeom_log.h" + +/* In proj4.9, GeographicLib is in special header */ +#ifdef PROJ_GEODESIC +#include +#endif + +/** +* Initialize spheroid object based on major and minor axis +*/ +void spheroid_init(SPHEROID *s, double a, double b) +{ + s->a = a; + s->b = b; + s->f = (a - b) / a; + s->e_sq = (a*a - b*b)/(a*a); + s->radius = (2.0 * a + b ) / 3.0; +} + +#ifndef PROJ_GEODESIC +static double spheroid_mu2(double alpha, const SPHEROID *s) +{ + double b2 = POW2(s->b); + return POW2(cos(alpha)) * (POW2(s->a) - b2) / b2; +} + +static double spheroid_big_a(double u2) +{ + return 1.0 + (u2 / 16384.0) * (4096.0 + u2 * (-768.0 + u2 * (320.0 - 175.0 * u2))); +} + +static double spheroid_big_b(double u2) +{ + return (u2 / 1024.0) * (256.0 + u2 * (-128.0 + u2 * (74.0 - 47.0 * u2))); +} +#endif /* ! PROJ_GEODESIC */ + + +#ifdef PROJ_GEODESIC + +/** +* Computes the shortest distance along the surface of the spheroid +* between two points, using the inverse geodesic problem from +* GeographicLib (Karney 2013). +* +* @param a - location of first point +* @param b - location of second point +* @param s - spheroid to calculate on +* @return spheroidal distance between a and b in spheroid units +*/ +double spheroid_distance(const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const SPHEROID *spheroid) +{ + struct geod_geodesic gd; + + /* Same point => zero distance */ + if ( geographic_point_equals(a, b) ) + return 0.0; + + geod_init(&gd, spheroid->a, spheroid->f); + double lat1 = a->lat * 180.0 / M_PI; + double lon1 = a->lon * 180.0 / M_PI; + double lat2 = b->lat * 180.0 / M_PI; + double lon2 = b->lon * 180.0 / M_PI; + double s12 = 0.0; /* return distance */ + geod_inverse(&gd, lat1, lon1, lat2, lon2, &s12, 0, 0); + return s12; +} + +/** +* Computes the forward azimuth of the geodesic joining two points on +* the spheroid, using the inverse geodesic problem (Karney 2013). +* +* @param r - location of first point +* @param s - location of second point +* @return azimuth of line joining r to s (but not reverse) +*/ +double spheroid_direction(const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const SPHEROID *spheroid) +{ + struct geod_geodesic gd; + geod_init(&gd, spheroid->a, spheroid->f); + double lat1 = a->lat * 180.0 / M_PI; + double lon1 = a->lon * 180.0 / M_PI; + double lat2 = b->lat * 180.0 / M_PI; + double lon2 = b->lon * 180.0 / M_PI; + double azi1; /* return azimuth */ + geod_inverse(&gd, lat1, lon1, lat2, lon2, 0, &azi1, 0); + return azi1 * M_PI / 180.0; +} + +/** +* Given a location, an azimuth and a distance, computes the location of +* the projected point. Using the direct geodesic problem from +* GeographicLib (Karney 2013). +* +* @param r - location of first point +* @param distance - distance in meters +* @param azimuth - azimuth in radians +* @return g - location of projected point +*/ +int spheroid_project(const GEOGRAPHIC_POINT *r, const SPHEROID *spheroid, double distance, double azimuth, GEOGRAPHIC_POINT *g) +{ + struct geod_geodesic gd; + geod_init(&gd, spheroid->a, spheroid->f); + double lat1 = r->lat * 180.0 / M_PI; + double lon1 = r->lon * 180.0 / M_PI; + double lat2, lon2; /* return projected position */ + geod_direct(&gd, lat1, lon1, azimuth * 180.0 / M_PI, distance, &lat2, &lon2, 0); + g->lat = lat2 * M_PI / 180.0; + g->lon = lon2 * M_PI / 180.0; + return LW_SUCCESS; +} + + +static double ptarray_area_spheroid(const POINTARRAY *pa, const SPHEROID *spheroid) +{ + /* Return zero on non-sensical inputs */ + if ( ! pa || pa->npoints < 4 ) + return 0.0; + + struct geod_geodesic gd; + geod_init(&gd, spheroid->a, spheroid->f); + struct geod_polygon poly; + geod_polygon_init(&poly, 0); + uint32_t i; + double area; /* returned polygon area */ + POINT2D p; /* long/lat units are degrees */ + + /* Pass points from point array; don't close the linearring */ + for ( i = 0; i < pa->npoints - 1; i++ ) + { + getPoint2d_p(pa, i, &p); + geod_polygon_addpoint(&gd, &poly, p.y, p.x); + LWDEBUGF(4, "geod_polygon_addpoint %d: %.12g %.12g", i, p.y, p.x); + } + i = geod_polygon_compute(&gd, &poly, 0, 1, &area, 0); + if ( i != pa->npoints - 1 ) + { + lwerror("ptarray_area_spheroid: different number of points %d vs %d", + i, pa->npoints - 1); + } + LWDEBUGF(4, "geod_polygon_compute area: %.12g", area); + return fabs(area); +} + +/* Above use Proj GeographicLib */ +#else /* ! PROJ_GEODESIC */ +/* Below use pre-version 2.2 geodesic functions */ + +/** +* Computes the shortest distance along the surface of the spheroid +* between two points. Based on Vincenty's formula for the geodetic +* inverse problem as described in "Geocentric Datum of Australia +* Technical Manual", Chapter 4. Tested against: +* http://mascot.gdbc.gov.bc.ca/mascot/util1a.html +* and +* http://www.ga.gov.au/nmd/geodesy/datums/vincenty_inverse.jsp +* +* @param a - location of first point. +* @param b - location of second point. +* @param s - spheroid to calculate on +* @return spheroidal distance between a and b in spheroid units. +*/ +double spheroid_distance(const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const SPHEROID *spheroid) +{ + double lambda = (b->lon - a->lon); + double f = spheroid->f; + double omf = 1 - spheroid->f; + double u1, u2; + double cos_u1, cos_u2; + double sin_u1, sin_u2; + double big_a, big_b, delta_sigma; + double alpha, sin_alpha, cos_alphasq, c; + double sigma, sin_sigma, cos_sigma, cos2_sigma_m, sqrsin_sigma, last_lambda, omega; + double cos_lambda, sin_lambda; + double distance; + int i = 0; + + /* Same point => zero distance */ + if ( geographic_point_equals(a, b) ) + { + return 0.0; + } + + u1 = atan(omf * tan(a->lat)); + cos_u1 = cos(u1); + sin_u1 = sin(u1); + u2 = atan(omf * tan(b->lat)); + cos_u2 = cos(u2); + sin_u2 = sin(u2); + + omega = lambda; + do + { + cos_lambda = cos(lambda); + sin_lambda = sin(lambda); + sqrsin_sigma = POW2(cos_u2 * sin_lambda) + + POW2((cos_u1 * sin_u2 - sin_u1 * cos_u2 * cos_lambda)); + sin_sigma = sqrt(sqrsin_sigma); + cos_sigma = sin_u1 * sin_u2 + cos_u1 * cos_u2 * cos_lambda; + sigma = atan2(sin_sigma, cos_sigma); + sin_alpha = cos_u1 * cos_u2 * sin_lambda / sin(sigma); + + /* Numerical stability issue, ensure asin is not NaN */ + if ( sin_alpha > 1.0 ) + alpha = M_PI_2; + else if ( sin_alpha < -1.0 ) + alpha = -1.0 * M_PI_2; + else + alpha = asin(sin_alpha); + + cos_alphasq = POW2(cos(alpha)); + cos2_sigma_m = cos(sigma) - (2.0 * sin_u1 * sin_u2 / cos_alphasq); + + /* Numerical stability issue, cos2 is in range */ + if ( cos2_sigma_m > 1.0 ) + cos2_sigma_m = 1.0; + if ( cos2_sigma_m < -1.0 ) + cos2_sigma_m = -1.0; + + c = (f / 16.0) * cos_alphasq * (4.0 + f * (4.0 - 3.0 * cos_alphasq)); + last_lambda = lambda; + lambda = omega + (1.0 - c) * f * sin(alpha) * (sigma + c * sin(sigma) * + (cos2_sigma_m + c * cos(sigma) * (-1.0 + 2.0 * POW2(cos2_sigma_m)))); + i++; + } + while ( (i < 999) && (lambda != 0.0) && (fabs((last_lambda - lambda)/lambda) > 1.0e-9) ); + + u2 = spheroid_mu2(alpha, spheroid); + big_a = spheroid_big_a(u2); + big_b = spheroid_big_b(u2); + delta_sigma = big_b * sin_sigma * (cos2_sigma_m + (big_b / 4.0) * (cos_sigma * (-1.0 + 2.0 * POW2(cos2_sigma_m)) - + (big_b / 6.0) * cos2_sigma_m * (-3.0 + 4.0 * sqrsin_sigma) * (-3.0 + 4.0 * POW2(cos2_sigma_m)))); + + distance = spheroid->b * big_a * (sigma - delta_sigma); + + /* Algorithm failure, distance == NaN, fallback to sphere */ + if ( distance != distance ) + { + lwerror("spheroid_distance returned NaN: (%.20g %.20g) (%.20g %.20g) a = %.20g b = %.20g",a->lat, a->lon, b->lat, b->lon, spheroid->a, spheroid->b); + return spheroid->radius * sphere_distance(a, b); + } + + return distance; +} + +/** +* Computes the direction of the geodesic joining two points on +* the spheroid. Based on Vincenty's formula for the geodetic +* inverse problem as described in "Geocentric Datum of Australia +* Technical Manual", Chapter 4. Tested against: +* http://mascot.gdbc.gov.bc.ca/mascot/util1a.html +* and +* http://www.ga.gov.au/nmd/geodesy/datums/vincenty_inverse.jsp +* +* @param r - location of first point +* @param s - location of second point +* @return azimuth of line joining r and s +*/ +double spheroid_direction(const GEOGRAPHIC_POINT *r, const GEOGRAPHIC_POINT *s, const SPHEROID *spheroid) +{ + int i = 0; + double lambda = s->lon - r->lon; + double omf = 1 - spheroid->f; + double u1 = atan(omf * tan(r->lat)); + double cos_u1 = cos(u1); + double sin_u1 = sin(u1); + double u2 = atan(omf * tan(s->lat)); + double cos_u2 = cos(u2); + double sin_u2 = sin(u2); + + double omega = lambda; + double alpha, sigma, sin_sigma, cos_sigma, cos2_sigma_m, sqr_sin_sigma, last_lambda; + double sin_alpha, cos_alphasq, C, alphaFD; + do + { + sqr_sin_sigma = POW2(cos_u2 * sin(lambda)) + + POW2((cos_u1 * sin_u2 - sin_u1 * cos_u2 * cos(lambda))); + sin_sigma = sqrt(sqr_sin_sigma); + cos_sigma = sin_u1 * sin_u2 + cos_u1 * cos_u2 * cos(lambda); + sigma = atan2(sin_sigma, cos_sigma); + sin_alpha = cos_u1 * cos_u2 * sin(lambda) / sin(sigma); + + /* Numerical stability issue, ensure asin is not NaN */ + if ( sin_alpha > 1.0 ) + alpha = M_PI_2; + else if ( sin_alpha < -1.0 ) + alpha = -1.0 * M_PI_2; + else + alpha = asin(sin_alpha); + + cos_alphasq = POW2(cos(alpha)); + cos2_sigma_m = cos(sigma) - (2.0 * sin_u1 * sin_u2 / cos_alphasq); + + /* Numerical stability issue, cos2 is in range */ + if ( cos2_sigma_m > 1.0 ) + cos2_sigma_m = 1.0; + if ( cos2_sigma_m < -1.0 ) + cos2_sigma_m = -1.0; + + C = (spheroid->f / 16.0) * cos_alphasq * (4.0 + spheroid->f * (4.0 - 3.0 * cos_alphasq)); + last_lambda = lambda; + lambda = omega + (1.0 - C) * spheroid->f * sin(alpha) * (sigma + C * sin(sigma) * + (cos2_sigma_m + C * cos(sigma) * (-1.0 + 2.0 * POW2(cos2_sigma_m)))); + i++; + } + while ( (i < 999) && (lambda != 0) && (fabs((last_lambda - lambda) / lambda) > 1.0e-9) ); + + alphaFD = atan2((cos_u2 * sin(lambda)), + (cos_u1 * sin_u2 - sin_u1 * cos_u2 * cos(lambda))); + if (alphaFD < 0.0) + { + alphaFD = alphaFD + 2.0 * M_PI; + } + if (alphaFD > 2.0 * M_PI) + { + alphaFD = alphaFD - 2.0 * M_PI; + } + return alphaFD; +} + + +/** +* Given a location, an azimuth and a distance, computes the +* location of the projected point. Based on Vincenty's formula +* for the geodetic direct problem as described in "Geocentric +* Datum of Australia Technical Manual", Chapter 4. Tested against: +* http://mascot.gdbc.gov.bc.ca/mascot/util1b.html +* and +* http://www.ga.gov.au/nmd/geodesy/datums/vincenty_direct.jsp +* +* @param r - location of first point. +* @param distance - distance in meters. +* @param azimuth - azimuth in radians. +* @return s - location of projected point. +*/ +int spheroid_project(const GEOGRAPHIC_POINT *r, const SPHEROID *spheroid, double distance, double azimuth, GEOGRAPHIC_POINT *g) +{ + double omf = 1 - spheroid->f; + double tan_u1 = omf * tan(r->lat); + double u1 = atan(tan_u1); + double sigma, last_sigma, delta_sigma, two_sigma_m; + double sigma1, sin_alpha, alpha, cos_alphasq; + double u2, A, B; + double lat2, lambda, lambda2, C, omega; + int i = 0; + + if (azimuth < 0.0) + { + azimuth = azimuth + M_PI * 2.0; + } + if (azimuth > (M_PI * 2.0)) + { + azimuth = azimuth - M_PI * 2.0; + } + + sigma1 = atan2(tan_u1, cos(azimuth)); + sin_alpha = cos(u1) * sin(azimuth); + alpha = asin(sin_alpha); + cos_alphasq = 1.0 - POW2(sin_alpha); + + u2 = spheroid_mu2(alpha, spheroid); + A = spheroid_big_a(u2); + B = spheroid_big_b(u2); + + sigma = (distance / (spheroid->b * A)); + do + { + two_sigma_m = 2.0 * sigma1 + sigma; + delta_sigma = B * sin(sigma) * (cos(two_sigma_m) + (B / 4.0) * (cos(sigma) * (-1.0 + 2.0 * POW2(cos(two_sigma_m)) - (B / 6.0) * cos(two_sigma_m) * (-3.0 + 4.0 * POW2(sin(sigma))) * (-3.0 + 4.0 * POW2(cos(two_sigma_m)))))); + last_sigma = sigma; + sigma = (distance / (spheroid->b * A)) + delta_sigma; + i++; + } + while (i < 999 && fabs((last_sigma - sigma) / sigma) > 1.0e-9); + + lat2 = atan2((sin(u1) * cos(sigma) + cos(u1) * sin(sigma) * + cos(azimuth)), (omf * sqrt(POW2(sin_alpha) + + POW2(sin(u1) * sin(sigma) - cos(u1) * cos(sigma) * + cos(azimuth))))); + lambda = atan2((sin(sigma) * sin(azimuth)), (cos(u1) * cos(sigma) - + sin(u1) * sin(sigma) * cos(azimuth))); + C = (spheroid->f / 16.0) * cos_alphasq * (4.0 + spheroid->f * (4.0 - 3.0 * cos_alphasq)); + omega = lambda - (1.0 - C) * spheroid->f * sin_alpha * (sigma + C * sin(sigma) * + (cos(two_sigma_m) + C * cos(sigma) * (-1.0 + 2.0 * POW2(cos(two_sigma_m))))); + lambda2 = r->lon + omega; + g->lat = lat2; + g->lon = lambda2; + return LW_SUCCESS; +} + + +static inline double spheroid_prime_vertical_radius_of_curvature(double latitude, const SPHEROID *spheroid) +{ + return spheroid->a / (sqrt(1.0 - spheroid->e_sq * POW2(sin(latitude)))); +} + +static inline double spheroid_parallel_arc_length(double latitude, double deltaLongitude, const SPHEROID *spheroid) +{ + return spheroid_prime_vertical_radius_of_curvature(latitude, spheroid) + * cos(latitude) + * deltaLongitude; +} + + +/** +* Computes the area on the spheroid of a box bounded by meridians and +* parallels. The box is defined by two points, the South West corner +* and the North East corner. Formula based on Bagratuni 1967. +* +* @param southWestCorner - lower left corner of bounding box. +* @param northEastCorner - upper right corner of bounding box. +* @return area in square meters. +*/ +static double spheroid_boundingbox_area(const GEOGRAPHIC_POINT *southWestCorner, const GEOGRAPHIC_POINT *northEastCorner, const SPHEROID *spheroid) +{ + double z0 = (northEastCorner->lon - southWestCorner->lon) * POW2(spheroid->b) / 2.0; + double e = sqrt(spheroid->e_sq); + double sinPhi1 = sin(southWestCorner->lat); + double sinPhi2 = sin(northEastCorner->lat); + double t1p1 = sinPhi1 / (1.0 - spheroid->e_sq * sinPhi1 * sinPhi1); + double t1p2 = sinPhi2 / (1.0 - spheroid->e_sq * sinPhi2 * sinPhi2); + double oneOver2e = 1.0 / (2.0 * e); + double t2p1 = oneOver2e * log((1.0 + e * sinPhi1) / (1.0 - e * sinPhi1)); + double t2p2 = oneOver2e * log((1.0 + e * sinPhi2) / (1.0 - e * sinPhi2)); + return z0 * (t1p2 + t2p2) - z0 * (t1p1 + t2p1); +} + +/** +* This function doesn't work for edges crossing the dateline or in the southern +* hemisphere. Points are pre-conditioned in ptarray_area_spheroid. +*/ +static double spheroid_striparea(const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, double latitude_min, const SPHEROID *spheroid) +{ + GEOGRAPHIC_POINT A, B, mL, nR; + double deltaLng, baseArea, topArea; + double bE, tE, ratio, sign; + + A = *a; + B = *b; + + mL.lat = latitude_min; + mL.lon = FP_MIN(A.lon, B.lon); + nR.lat = FP_MIN(A.lat, B.lat); + nR.lon = FP_MAX(A.lon, B.lon); + LWDEBUGF(4, "mL (%.12g %.12g)", mL.lat, mL.lon); + LWDEBUGF(4, "nR (%.12g %.12g)", nR.lat, nR.lon); + baseArea = spheroid_boundingbox_area(&mL, &nR, spheroid); + LWDEBUGF(4, "baseArea %.12g", baseArea); + + mL.lat = FP_MIN(A.lat, B.lat); + mL.lon = FP_MIN(A.lon, B.lon); + nR.lat = FP_MAX(A.lat, B.lat); + nR.lon = FP_MAX(A.lon, B.lon); + LWDEBUGF(4, "mL (%.12g %.12g)", mL.lat, mL.lon); + LWDEBUGF(4, "nR (%.12g %.12g)", nR.lat, nR.lon); + topArea = spheroid_boundingbox_area(&mL, &nR, spheroid); + LWDEBUGF(4, "topArea %.12g", topArea); + + deltaLng = B.lon - A.lon; + LWDEBUGF(4, "deltaLng %.12g", deltaLng); + bE = spheroid_parallel_arc_length(A.lat, deltaLng, spheroid); + tE = spheroid_parallel_arc_length(B.lat, deltaLng, spheroid); + LWDEBUGF(4, "bE %.12g", bE); + LWDEBUGF(4, "tE %.12g", tE); + + ratio = (bE + tE)/tE; + sign = SIGNUM(B.lon - A.lon); + return (baseArea + topArea / ratio) * sign; +} + +static double ptarray_area_spheroid(const POINTARRAY *pa, const SPHEROID *spheroid) +{ + GEOGRAPHIC_POINT a, b; + POINT2D p; + uint32_t i; + double area = 0.0; + GBOX gbox2d; + int in_south = LW_FALSE; + double delta_lon_tolerance; + double latitude_min; + + gbox2d.flags = lwflags(0, 0, 0); + + /* Return zero on non-sensical inputs */ + if ( ! pa || pa->npoints < 4 ) + return 0.0; + + /* Get the raw min/max values for the latitudes */ + ptarray_calculate_gbox_cartesian(pa, &gbox2d); + + if ( SIGNUM(gbox2d.ymin) != SIGNUM(gbox2d.ymax) ) + lwerror("ptarray_area_spheroid: cannot handle ptarray that crosses equator"); + + /* Geodetic bbox < 0.0 implies geometry is entirely in southern hemisphere */ + if ( gbox2d.ymax < 0.0 ) + in_south = LW_TRUE; + + LWDEBUGF(4, "gbox2d.ymax %.12g", gbox2d.ymax); + + /* Tolerance for strip area calculation */ + if ( in_south ) + { + delta_lon_tolerance = (90.0 / (fabs(gbox2d.ymin) / 8.0) - 2.0) / 10000.0; + latitude_min = deg2rad(fabs(gbox2d.ymax)); + } + else + { + delta_lon_tolerance = (90.0 / (fabs(gbox2d.ymax) / 8.0) - 2.0) / 10000.0; + latitude_min = deg2rad(gbox2d.ymin); + } + + /* Initialize first point */ + getPoint2d_p(pa, 0, &p); + geographic_point_init(p.x, p.y, &a); + + for ( i = 1; i < pa->npoints; i++ ) + { + GEOGRAPHIC_POINT a1, b1; + double strip_area = 0.0; + double delta_lon = 0.0; + LWDEBUGF(4, "edge #%d", i); + + getPoint2d_p(pa, i, &p); + geographic_point_init(p.x, p.y, &b); + + a1 = a; + b1 = b; + + /* Flip into north if in south */ + if ( in_south ) + { + a1.lat = -1.0 * a1.lat; + b1.lat = -1.0 * b1.lat; + } + + LWDEBUGF(4, "in_south %d", in_south); + + LWDEBUGF(4, "crosses_dateline(a, b) %d", crosses_dateline(&a, &b) ); + + if ( crosses_dateline(&a, &b) ) + { + double shift; + + if ( a1.lon > 0.0 ) + shift = (M_PI - a1.lon) + 0.088; /* About 5deg more */ + else + shift = (M_PI - b1.lon) + 0.088; /* About 5deg more */ + + LWDEBUGF(4, "shift: %.8g", shift); + LWDEBUGF(4, "before shift a1(%.8g %.8g) b1(%.8g %.8g)", a1.lat, a1.lon, b1.lat, b1.lon); + point_shift(&a1, shift); + point_shift(&b1, shift); + LWDEBUGF(4, "after shift a1(%.8g %.8g) b1(%.8g %.8g)", a1.lat, a1.lon, b1.lat, b1.lon); + + } + + + delta_lon = fabs(b1.lon - a1.lon); + + LWDEBUGF(4, "a1(%.18g %.18g) b1(%.18g %.18g)", a1.lat, a1.lon, b1.lat, b1.lon); + LWDEBUGF(4, "delta_lon %.18g", delta_lon); + LWDEBUGF(4, "delta_lon_tolerance %.18g", delta_lon_tolerance); + + if ( delta_lon > 0.0 ) + { + if ( delta_lon < delta_lon_tolerance ) + { + strip_area = spheroid_striparea(&a1, &b1, latitude_min, spheroid); + LWDEBUGF(4, "strip_area %.12g", strip_area); + area += strip_area; + } + else + { + GEOGRAPHIC_POINT p, q; + double step = floor(delta_lon / delta_lon_tolerance); + double distance = spheroid_distance(&a1, &b1, spheroid); + double pDistance = 0.0; + int j = 0; + LWDEBUGF(4, "step %.18g", step); + LWDEBUGF(4, "distance %.18g", distance); + step = distance / step; + LWDEBUGF(4, "step %.18g", step); + p = a1; + while (pDistance < (distance - step * 1.01)) + { + double azimuth = spheroid_direction(&p, &b1, spheroid); + j++; + LWDEBUGF(4, " iteration %d", j); + LWDEBUGF(4, " azimuth %.12g", azimuth); + pDistance = pDistance + step; + LWDEBUGF(4, " pDistance %.12g", pDistance); + spheroid_project(&p, spheroid, step, azimuth, &q); + strip_area = spheroid_striparea(&p, &q, latitude_min, spheroid); + LWDEBUGF(4, " strip_area %.12g", strip_area); + area += strip_area; + LWDEBUGF(4, " area %.12g", area); + p.lat = q.lat; + p.lon = q.lon; + } + strip_area = spheroid_striparea(&p, &b1, latitude_min, spheroid); + area += strip_area; + } + } + + /* B gets incremented in the next loop, so we save the value here */ + a = b; + } + return fabs(area); +} +#endif /* else ! PROJ_GEODESIC */ + +/** +* Calculate the area of an LWGEOM. Anything except POLYGON, MULTIPOLYGON +* and GEOMETRYCOLLECTION return zero immediately. Multi's recurse, polygons +* calculate external ring area and subtract internal ring area. A GBOX is +* required to check relationship to equator an outside point. +* WARNING: Does NOT WORK for polygons over equator or pole. +*/ +double lwgeom_area_spheroid(const LWGEOM *lwgeom, const SPHEROID *spheroid) +{ + int type; + + assert(lwgeom); + + /* No area in nothing */ + if ( lwgeom_is_empty(lwgeom) ) + return 0.0; + + /* Read the geometry type number */ + type = lwgeom->type; + + /* Anything but polygons and collections returns zero */ + if ( ! ( type == POLYGONTYPE || type == MULTIPOLYGONTYPE || type == COLLECTIONTYPE ) ) + return 0.0; + + /* Actually calculate area */ + if ( type == POLYGONTYPE ) + { + LWPOLY *poly = (LWPOLY*)lwgeom; + uint32_t i; + double area = 0.0; + + /* Just in case there's no rings */ + if ( poly->nrings < 1 ) + return 0.0; + + /* First, the area of the outer ring */ + area += ptarray_area_spheroid(poly->rings[0], spheroid); + + /* Subtract areas of inner rings */ + for ( i = 1; i < poly->nrings; i++ ) + { + area -= ptarray_area_spheroid(poly->rings[i], spheroid); + } + return area; + } + + /* Recurse into sub-geometries to get area */ + if ( type == MULTIPOLYGONTYPE || type == COLLECTIONTYPE ) + { + LWCOLLECTION *col = (LWCOLLECTION*)lwgeom; + uint32_t i; + double area = 0.0; + + for ( i = 0; i < col->ngeoms; i++ ) + { + area += lwgeom_area_spheroid(col->geoms[i], spheroid); + } + return area; + } + + /* Shouldn't get here. */ + return 0.0; +} + + + diff --git a/mgist-postgis/liblwgeom/lwstroke.c b/mgist-postgis/liblwgeom/lwstroke.c new file mode 100644 index 0000000..82c7a6a --- /dev/null +++ b/mgist-postgis/liblwgeom/lwstroke.c @@ -0,0 +1,1291 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2001-2006 Refractions Research Inc. + * Copyright (C) 2017 Sandro Santilli + * Copyright (C) 2018 Daniel Baston + * + **********************************************************************/ + + +#include +#include +#include +#include + +// #include "../postgis_config.h" + +/*#define POSTGIS_DEBUG_LEVEL 3*/ + +#include "lwgeom_log.h" + +#include "liblwgeom_internal.h" + +LWGEOM *pta_unstroke(const POINTARRAY *points, int32_t srid); +LWGEOM* lwline_unstroke(const LWLINE *line); +LWGEOM* lwpolygon_unstroke(const LWPOLY *poly); +LWGEOM* lwmline_unstroke(const LWMLINE *mline); +LWGEOM* lwmpolygon_unstroke(const LWMPOLY *mpoly); +LWGEOM* lwcollection_unstroke(const LWCOLLECTION *c); +LWGEOM* lwgeom_unstroke(const LWGEOM *geom); + + +/* + * Determines (recursively in the case of collections) whether the geometry + * contains at least on arc geometry or segment. + */ +int +lwgeom_has_arc(const LWGEOM *geom) +{ + LWCOLLECTION *col; + uint32_t i; + + LWDEBUG(2, "lwgeom_has_arc called."); + + switch (geom->type) + { + case POINTTYPE: + case LINETYPE: + case POLYGONTYPE: + case TRIANGLETYPE: + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + return LW_FALSE; + case CIRCSTRINGTYPE: + return LW_TRUE; + /* It's a collection that MAY contain an arc */ + default: + col = (LWCOLLECTION *)geom; + for (i=0; ingeoms; i++) + { + if (lwgeom_has_arc(col->geoms[i]) == LW_TRUE) + return LW_TRUE; + } + return LW_FALSE; + } +} + +int +lwgeom_type_arc(const LWGEOM *geom) +{ + switch (geom->type) + { + case COMPOUNDTYPE: + case CIRCSTRINGTYPE: + case CURVEPOLYTYPE: + case MULTISURFACETYPE: + case MULTICURVETYPE: + return LW_TRUE; + default: + return LW_FALSE; + } +} + +/******************************************************************************* + * Begin curve segmentize functions + ******************************************************************************/ + +static double interpolate_arc(double angle, double a1, double a2, double a3, double zm1, double zm2, double zm3) +{ + LWDEBUGF(4,"angle %.05g a1 %.05g a2 %.05g a3 %.05g zm1 %.05g zm2 %.05g zm3 %.05g",angle,a1,a2,a3,zm1,zm2,zm3); + /* Counter-clockwise sweep */ + if ( a1 < a2 ) + { + if ( angle <= a2 ) + return zm1 + (zm2-zm1) * (angle-a1) / (a2-a1); + else + return zm2 + (zm3-zm2) * (angle-a2) / (a3-a2); + } + /* Clockwise sweep */ + else + { + if ( angle >= a2 ) + return zm1 + (zm2-zm1) * (a1-angle) / (a1-a2); + else + return zm2 + (zm3-zm2) * (a2-angle) / (a2-a3); + } +} + +/* Compute the angle covered by a single segment such that + * a given number of segments per quadrant is achieved. */ +static double angle_increment_using_segments_per_quad(double tol) +{ + double increment; + int perQuad = rint(tol); + // error out if tol != perQuad ? (not-round) + if ( perQuad != tol ) + { + lwerror("lwarc_linearize: segments per quadrant must be an integer value, got %.15g", tol, perQuad); + return -1; + } + if ( perQuad < 1 ) + { + lwerror("lwarc_linearize: segments per quadrant must be at least 1, got %d", perQuad); + return -1; + } + increment = fabs(M_PI_2 / perQuad); + LWDEBUGF(2, "lwarc_linearize: perQuad:%d, increment:%g (%g degrees)", perQuad, increment, increment*180/M_PI); + + return increment; +} + +/* Compute the angle covered by a single quadrant such that + * the segment deviates from the arc by no more than a given + * amount. */ +static double angle_increment_using_max_deviation(double max_deviation, double radius) +{ + double increment, halfAngle, maxErr; + if ( max_deviation <= 0 ) + { + lwerror("lwarc_linearize: max deviation must be bigger than 0, got %.15g", max_deviation); + return -1; + } + + /* + * Ref: https://en.wikipedia.org/wiki/Sagitta_(geometry) + * + * An arc "sagitta" (distance between middle point of arc and + * middle point of corresponding chord) is defined as: + * + * sagitta = radius * ( 1 - cos( angle ) ); + * + * We want our sagitta to be at most "tolerance" long, + * and we want to find out angle, so we use the inverse + * formula: + * + * tol = radius * ( 1 - cos( angle ) ); + * 1 - cos( angle ) = tol/radius + * - cos( angle ) = tol/radius - 1 + * cos( angle ) = - tol/radius + 1 + * angle = acos( 1 - tol/radius ) + * + * Constraints: 1.0 - tol/radius must be between -1 and 1 + * which means tol must be between 0 and 2 times + * the radius, which makes sense as you cannot have a + * sagitta bigger than twice the radius! + * + */ + maxErr = max_deviation; + if ( maxErr > radius * 2 ) + { + maxErr = radius * 2; + LWDEBUGF(2, + "lwarc_linearize: tolerance %g is too big, " + "using arc-max 2 * radius == %g", + max_deviation, + maxErr); + } + do { + halfAngle = acos( 1.0 - maxErr / radius ); + /* TODO: avoid a loop here, going rather straight to + * a minimum angle value */ + if ( halfAngle != 0 ) break; + LWDEBUGF(2, "lwarc_linearize: tolerance %g is too small for this arc" + " to compute approximation angle, doubling it", maxErr); + maxErr *= 2; + } while(1); + increment = 2 * halfAngle; + LWDEBUGF(2, + "lwarc_linearize: maxDiff:%g, radius:%g, halfAngle:%g, increment:%g (%g degrees)", + max_deviation, + radius, + halfAngle, + increment, + increment * 180 / M_PI); + + return increment; +} + +/* Check that a given angle is positive and, if so, take + * it to be the angle covered by a single segment. */ +static double angle_increment_using_max_angle(double tol) +{ + if ( tol <= 0 ) + { + lwerror("lwarc_linearize: max angle must be bigger than 0, got %.15g", tol); + return -1; + } + + return tol; +} + + +/** + * Segmentize an arc + * + * Does not add the final vertex + * + * @param to POINTARRAY to append segmentized vertices to + * @param p1 first point defining the arc + * @param p2 second point defining the arc + * @param p3 third point defining the arc + * @param tol tolerance, semantic driven by tolerance_type + * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE + * @param flags LW_LINEARIZE_FLAGS + * + * @return number of points appended (0 if collinear), + * or -1 on error (lwerror would be called). + * + */ +static int +lwarc_linearize(POINTARRAY *to, + const POINT4D *p1, const POINT4D *p2, const POINT4D *p3, + double tol, LW_LINEARIZE_TOLERANCE_TYPE tolerance_type, + int flags) +{ + POINT2D center; + POINT2D *t1 = (POINT2D*)p1; + POINT2D *t2 = (POINT2D*)p2; + POINT2D *t3 = (POINT2D*)p3; + POINT4D pt; + int p2_side = 0; + int clockwise = LW_TRUE; + double radius; /* Arc radius */ + double increment; /* Angle per segment */ + double angle_shift = 0; + double a1, a2, a3; + POINTARRAY *pa; + int is_circle = LW_FALSE; + int points_added = 0; + int reverse = 0; + int segments = 0; + + LWDEBUG(2, "lwarc_linearize called."); + + LWDEBUGF(2, " curve is CIRCULARSTRING(%.15g %.15f, %.15f %.15f, %.15f %15f)", + t1->x, t1->y, t2->x, t2->y, t3->x, t3->y); + + p2_side = lw_segment_side(t1, t3, t2); + + LWDEBUGF(2, " p2 side is %d", p2_side); + + /* Force counterclockwise scan if SYMMETRIC operation is requested */ + if ( p2_side == -1 && flags & LW_LINEARIZE_FLAG_SYMMETRIC ) + { + /* swap p1-p3 */ + t1 = (POINT2D*)p3; + t3 = (POINT2D*)p1; + p1 = (POINT4D*)t1; + p3 = (POINT4D*)t3; + p2_side = 1; + reverse = 1; + } + + radius = lw_arc_center(t1, t2, t3, ¢er); + LWDEBUGF(2, " center is POINT(%.15g %.15g) - radius:%g", center.x, center.y, radius); + + /* Matched start/end points imply circle */ + if ( p1->x == p3->x && p1->y == p3->y ) + is_circle = LW_TRUE; + + /* Negative radius signals straight line, p1/p2/p3 are collinear */ + if ( (radius < 0.0 || p2_side == 0) && ! is_circle ) + return 0; + + /* The side of the p1/p3 line that p2 falls on dictates the sweep + direction from p1 to p3. */ + if ( p2_side == -1 ) + clockwise = LW_TRUE; + else + clockwise = LW_FALSE; + + /* Compute the increment (angle per segment) depending on + * our tolerance type. */ + switch(tolerance_type) + { + case LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD: + increment = angle_increment_using_segments_per_quad(tol); + break; + case LW_LINEARIZE_TOLERANCE_TYPE_MAX_DEVIATION: + increment = angle_increment_using_max_deviation(tol, radius); + break; + case LW_LINEARIZE_TOLERANCE_TYPE_MAX_ANGLE: + increment = angle_increment_using_max_angle(tol); + break; + default: + lwerror("lwarc_linearize: unsupported tolerance type %d", tolerance_type); + return -1; + } + + if (increment < 0) + { + /* Error occurred in increment calculation somewhere + * (lwerror already called) + */ + return -1; + } + + /* Angles of each point that defines the arc section */ + a1 = atan2(p1->y - center.y, p1->x - center.x); + a2 = atan2(p2->y - center.y, p2->x - center.x); + a3 = atan2(p3->y - center.y, p3->x - center.x); + + LWDEBUGF(2, "lwarc_linearize A1:%g (%g) A2:%g (%g) A3:%g (%g)", + a1, a1*180/M_PI, a2, a2*180/M_PI, a3, a3*180/M_PI); + + /* Calculate total arc angle, in radians */ + double total_angle = clockwise ? a1 - a3 : a3 - a1; + if ( total_angle <= 0 ) total_angle += M_PI * 2; + + /* At extreme tolerance values (very low or very high, depending on + * the semantic) we may cause our arc to collapse. In this case, + * we want shrink the increment enough so that we get two segments + * for a standard arc, or three segments for a complete circle. */ + int min_segs = is_circle ? 3 : 2; + segments = ceil(total_angle / increment); + if (segments < min_segs) + { + segments = min_segs; + increment = total_angle / min_segs; + } + + if ( flags & LW_LINEARIZE_FLAG_SYMMETRIC ) + { + LWDEBUGF(2, "lwarc_linearize SYMMETRIC requested - total angle %g deg", total_angle * 180 / M_PI); + + if ( flags & LW_LINEARIZE_FLAG_RETAIN_ANGLE ) + { + /* Number of complete steps */ + segments = trunc(total_angle / increment); + + /* Figure out the angle remainder, i.e. the amount of the angle + * that is left after we can take no more complete angle + * increments. */ + double angle_remainder = total_angle - (increment * segments); + + /* Shift the starting angle by half of the remainder. This + * will have the effect of evenly distributing the remainder + * among the first and last segments in the arc. */ + angle_shift = angle_remainder / 2.0; + + LWDEBUGF(2, + "lwarc_linearize RETAIN_ANGLE operation requested - " + "total angle %g, steps %d, increment %g, remainder %g", + total_angle * 180 / M_PI, + segments, + increment * 180 / M_PI, + angle_remainder * 180 / M_PI); + } + else + { + /* Number of segments in output */ + segments = ceil(total_angle / increment); + /* Tweak increment to be regular for all the arc */ + increment = total_angle / segments; + + LWDEBUGF(2, + "lwarc_linearize SYMMETRIC operation requested - " + "total angle %g degrees - LINESTRING(%g %g,%g %g,%g %g) - S:%d - I:%g", + total_angle * 180 / M_PI, + p1->x, + p1->y, + center.x, + center.y, + p3->x, + p3->y, + segments, + increment * 180 / M_PI); + } + } + + /* p2 on left side => clockwise sweep */ + if ( clockwise ) + { + LWDEBUG(2, " Clockwise sweep"); + increment *= -1; + angle_shift *= -1; + /* Adjust a3 down so we can decrement from a1 to a3 cleanly */ + if ( a3 > a1 ) + a3 -= 2.0 * M_PI; + if ( a2 > a1 ) + a2 -= 2.0 * M_PI; + } + /* p2 on right side => counter-clockwise sweep */ + else + { + LWDEBUG(2, " Counterclockwise sweep"); + /* Adjust a3 up so we can increment from a1 to a3 cleanly */ + if ( a3 < a1 ) + a3 += 2.0 * M_PI; + if ( a2 < a1 ) + a2 += 2.0 * M_PI; + } + + /* Override angles for circle case */ + if( is_circle ) + { + increment = fabs(increment); + segments = ceil(total_angle / increment); + if (segments < 3) + { + segments = 3; + increment = total_angle / 3; + } + a3 = a1 + 2.0 * M_PI; + a2 = a1 + M_PI; + clockwise = LW_FALSE; + angle_shift = 0.0; + } + + LWDEBUGF(2, "lwarc_linearize angle_shift:%g, increment:%g", + angle_shift * 180/M_PI, increment * 180/M_PI); + + if ( reverse ) + { + /* Append points in order to a temporary POINTARRAY and + * reverse them before writing to the output POINTARRAY. */ + const int capacity = 8; /* TODO: compute exactly ? */ + pa = ptarray_construct_empty(ptarray_has_z(to), ptarray_has_m(to), capacity); + } + else + { + /* Append points directly to the output POINTARRAY, + * starting with p1. */ + pa = to; + + ptarray_append_point(pa, p1, LW_FALSE); + ++points_added; + } + + /* Sweep from a1 to a3 */ + int seg_start = 1; /* First point is added manually */ + int seg_end = segments; + if (angle_shift != 0.0) + { + /* When we have extra angles we need to add the extra segments at the + * start and end that cover those parts of the arc */ + seg_start = 0; + seg_end = segments + 1; + } + LWDEBUGF(2, "a1:%g (%g deg), a3:%g (%g deg), inc:%g, shi:%g, cw:%d", + a1, a1 * 180 / M_PI, a3, a3 * 180 / M_PI, increment, angle_shift, clockwise); + for (int s = seg_start; s < seg_end; s++) + { + double angle = a1 + increment * s + angle_shift; + LWDEBUGF(2, " SA: %g ( %g deg )", angle, angle*180/M_PI); + pt.x = center.x + radius * cos(angle); + pt.y = center.y + radius * sin(angle); + pt.z = interpolate_arc(angle, a1, a2, a3, p1->z, p2->z, p3->z); + pt.m = interpolate_arc(angle, a1, a2, a3, p1->m, p2->m, p3->m); + ptarray_append_point(pa, &pt, LW_FALSE); + ++points_added; + } + + /* Ensure the final point is EXACTLY the same as the first for the circular case */ + if ( is_circle ) + { + ptarray_remove_point(pa, pa->npoints - 1); + ptarray_append_point(pa, p1, LW_FALSE); + } + + if ( reverse ) + { + int i; + ptarray_append_point(to, p3, LW_FALSE); + for ( i=pa->npoints; i>0; i-- ) { + getPoint4d_p(pa, i-1, &pt); + ptarray_append_point(to, &pt, LW_FALSE); + } + ptarray_free(pa); + } + + return points_added; +} + +/* + * @param icurve input curve + * @param tol tolerance, semantic driven by tolerance_type + * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE + * @param flags see flags in lwarc_linearize + * + * @return a newly allocated LWLINE + */ +static LWLINE * +lwcircstring_linearize(const LWCIRCSTRING *icurve, double tol, + LW_LINEARIZE_TOLERANCE_TYPE tolerance_type, + int flags) +{ + LWLINE *oline; + POINTARRAY *ptarray; + uint32_t i, j; + POINT4D p1, p2, p3, p4; + int ret; + + LWDEBUGF(2, "lwcircstring_linearize called., dim = %d", icurve->points->flags); + + ptarray = ptarray_construct_empty(FLAGS_GET_Z(icurve->points->flags), FLAGS_GET_M(icurve->points->flags), 64); + + for (i = 2; i < icurve->points->npoints; i+=2) + { + LWDEBUGF(3, "lwcircstring_linearize: arc ending at point %d", i); + + getPoint4d_p(icurve->points, i - 2, &p1); + getPoint4d_p(icurve->points, i - 1, &p2); + getPoint4d_p(icurve->points, i, &p3); + + ret = lwarc_linearize(ptarray, &p1, &p2, &p3, tol, tolerance_type, flags); + if ( ret > 0 ) + { + LWDEBUGF(3, "lwcircstring_linearize: generated %d points", ptarray->npoints); + } + else if ( ret == 0 ) + { + LWDEBUG(3, "lwcircstring_linearize: points are colinear, returning curve points as line"); + + for (j = i - 2 ; j < i ; j++) + { + getPoint4d_p(icurve->points, j, &p4); + ptarray_append_point(ptarray, &p4, LW_TRUE); + } + } + else + { + /* An error occurred, lwerror should have been called by now */ + ptarray_free(ptarray); + return NULL; + } + } + getPoint4d_p(icurve->points, icurve->points->npoints-1, &p1); + ptarray_append_point(ptarray, &p1, LW_FALSE); + + oline = lwline_construct(icurve->srid, NULL, ptarray); + return oline; +} + +/* + * @param icompound input compound curve + * @param tol tolerance, semantic driven by tolerance_type + * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE + * @param flags see flags in lwarc_linearize + * + * @return a newly allocated LWLINE + */ +static LWLINE * +lwcompound_linearize(const LWCOMPOUND *icompound, double tol, + LW_LINEARIZE_TOLERANCE_TYPE tolerance_type, + int flags) +{ + LWGEOM *geom; + POINTARRAY *ptarray = NULL; + LWLINE *tmp = NULL; + uint32_t i, j; + POINT4D p; + + LWDEBUG(2, "lwcompound_stroke called."); + + ptarray = ptarray_construct_empty(FLAGS_GET_Z(icompound->flags), FLAGS_GET_M(icompound->flags), 64); + + for (i = 0; i < icompound->ngeoms; i++) + { + geom = icompound->geoms[i]; + if (geom->type == CIRCSTRINGTYPE) + { + tmp = lwcircstring_linearize((LWCIRCSTRING *)geom, tol, tolerance_type, flags); + for (j = 0; j < tmp->points->npoints; j++) + { + getPoint4d_p(tmp->points, j, &p); + ptarray_append_point(ptarray, &p, LW_TRUE); + } + lwline_free(tmp); + } + else if (geom->type == LINETYPE) + { + tmp = (LWLINE *)geom; + for (j = 0; j < tmp->points->npoints; j++) + { + getPoint4d_p(tmp->points, j, &p); + ptarray_append_point(ptarray, &p, LW_TRUE); + } + } + else + { + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(geom->type)); + return NULL; + } + } + + ptarray_remove_repeated_points_in_place(ptarray, 0.0, 2); + return lwline_construct(icompound->srid, NULL, ptarray); +} + + +/* + * @param icompound input curve polygon + * @param tol tolerance, semantic driven by tolerance_type + * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE + * @param flags see flags in lwarc_linearize + * + * @return a newly allocated LWPOLY + */ +static LWPOLY * +lwcurvepoly_linearize(const LWCURVEPOLY *curvepoly, double tol, + LW_LINEARIZE_TOLERANCE_TYPE tolerance_type, + int flags) +{ + LWPOLY *ogeom; + LWGEOM *tmp; + LWLINE *line; + POINTARRAY **ptarray; + uint32_t i; + + LWDEBUG(2, "lwcurvepoly_linearize called."); + + ptarray = lwalloc(sizeof(POINTARRAY *)*curvepoly->nrings); + + for (i = 0; i < curvepoly->nrings; i++) + { + tmp = curvepoly->rings[i]; + if (tmp->type == CIRCSTRINGTYPE) + { + line = lwcircstring_linearize((LWCIRCSTRING *)tmp, tol, tolerance_type, flags); + ptarray[i] = ptarray_clone_deep(line->points); + lwline_free(line); + } + else if (tmp->type == LINETYPE) + { + line = (LWLINE *)tmp; + ptarray[i] = ptarray_clone_deep(line->points); + } + else if (tmp->type == COMPOUNDTYPE) + { + line = lwcompound_linearize((LWCOMPOUND *)tmp, tol, tolerance_type, flags); + ptarray[i] = ptarray_clone_deep(line->points); + lwline_free(line); + } + else + { + lwerror("Invalid ring type found in CurvePoly."); + return NULL; + } + } + + ogeom = lwpoly_construct(curvepoly->srid, NULL, curvepoly->nrings, ptarray); + return ogeom; +} + +/* Kept for backward compatibility - TODO: drop */ +LWPOLY * +lwcurvepoly_stroke(const LWCURVEPOLY *curvepoly, uint32_t perQuad) +{ + return lwcurvepoly_linearize(curvepoly, perQuad, LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD, 0); +} + + +/** + * @param mcurve input compound curve + * @param tol tolerance, semantic driven by tolerance_type + * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE + * @param flags see flags in lwarc_linearize + * + * @return a newly allocated LWMLINE + */ +static LWMLINE * +lwmcurve_linearize(const LWMCURVE *mcurve, double tol, + LW_LINEARIZE_TOLERANCE_TYPE type, + int flags) +{ + LWMLINE *ogeom; + LWGEOM **lines; + uint32_t i; + + LWDEBUGF(2, "lwmcurve_linearize called, geoms=%d, dim=%d.", mcurve->ngeoms, FLAGS_NDIMS(mcurve->flags)); + + lines = lwalloc(sizeof(LWGEOM *)*mcurve->ngeoms); + + for (i = 0; i < mcurve->ngeoms; i++) + { + const LWGEOM *tmp = mcurve->geoms[i]; + if (tmp->type == CIRCSTRINGTYPE) + { + lines[i] = (LWGEOM *)lwcircstring_linearize((LWCIRCSTRING *)tmp, tol, type, flags); + } + else if (tmp->type == LINETYPE) + { + lines[i] = (LWGEOM *)lwline_construct(mcurve->srid, NULL, ptarray_clone_deep(((LWLINE *)tmp)->points)); + } + else if (tmp->type == COMPOUNDTYPE) + { + lines[i] = (LWGEOM *)lwcompound_linearize((LWCOMPOUND *)tmp, tol, type, flags); + } + else + { + lwerror("Unsupported geometry found in MultiCurve."); + return NULL; + } + } + + ogeom = (LWMLINE *)lwcollection_construct(MULTILINETYPE, mcurve->srid, NULL, mcurve->ngeoms, lines); + return ogeom; +} + +/** + * @param msurface input multi surface + * @param tol tolerance, semantic driven by tolerance_type + * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE + * @param flags see flags in lwarc_linearize + * + * @return a newly allocated LWMPOLY + */ +static LWMPOLY * +lwmsurface_linearize(const LWMSURFACE *msurface, double tol, + LW_LINEARIZE_TOLERANCE_TYPE type, + int flags) +{ + LWMPOLY *ogeom; + LWGEOM *tmp; + LWPOLY *poly; + LWGEOM **polys; + POINTARRAY **ptarray; + uint32_t i, j; + + LWDEBUG(2, "lwmsurface_linearize called."); + + polys = lwalloc(sizeof(LWGEOM *)*msurface->ngeoms); + + for (i = 0; i < msurface->ngeoms; i++) + { + tmp = msurface->geoms[i]; + if (tmp->type == CURVEPOLYTYPE) + { + polys[i] = (LWGEOM *)lwcurvepoly_linearize((LWCURVEPOLY *)tmp, tol, type, flags); + } + else if (tmp->type == POLYGONTYPE) + { + poly = (LWPOLY *)tmp; + ptarray = lwalloc(sizeof(POINTARRAY *)*poly->nrings); + for (j = 0; j < poly->nrings; j++) + { + ptarray[j] = ptarray_clone_deep(poly->rings[j]); + } + polys[i] = (LWGEOM *)lwpoly_construct(msurface->srid, NULL, poly->nrings, ptarray); + } + } + ogeom = (LWMPOLY *)lwcollection_construct(MULTIPOLYGONTYPE, msurface->srid, NULL, msurface->ngeoms, polys); + return ogeom; +} + +/** + * @param collection input geometry collection + * @param tol tolerance, semantic driven by tolerance_type + * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE + * @param flags see flags in lwarc_linearize + * + * @return a newly allocated LWCOLLECTION + */ +static LWCOLLECTION * +lwcollection_linearize(const LWCOLLECTION *collection, double tol, + LW_LINEARIZE_TOLERANCE_TYPE type, + int flags) +{ + LWCOLLECTION *ocol; + LWGEOM *tmp; + LWGEOM **geoms; + uint32_t i; + + LWDEBUG(2, "lwcollection_linearize called."); + + geoms = lwalloc(sizeof(LWGEOM *)*collection->ngeoms); + + for (i=0; ingeoms; i++) + { + tmp = collection->geoms[i]; + switch (tmp->type) + { + case CIRCSTRINGTYPE: + geoms[i] = (LWGEOM *)lwcircstring_linearize((LWCIRCSTRING *)tmp, tol, type, flags); + break; + case COMPOUNDTYPE: + geoms[i] = (LWGEOM *)lwcompound_linearize((LWCOMPOUND *)tmp, tol, type, flags); + break; + case CURVEPOLYTYPE: + geoms[i] = (LWGEOM *)lwcurvepoly_linearize((LWCURVEPOLY *)tmp, tol, type, flags); + break; + case MULTICURVETYPE: + case MULTISURFACETYPE: + case COLLECTIONTYPE: + geoms[i] = (LWGEOM *)lwcollection_linearize((LWCOLLECTION *)tmp, tol, type, flags); + break; + default: + geoms[i] = lwgeom_clone_deep(tmp); + break; + } + } + ocol = lwcollection_construct(COLLECTIONTYPE, collection->srid, NULL, collection->ngeoms, geoms); + return ocol; +} + +LWGEOM * +lwcurve_linearize(const LWGEOM *geom, double tol, + LW_LINEARIZE_TOLERANCE_TYPE type, + int flags) +{ + LWGEOM * ogeom = NULL; + switch (geom->type) + { + case CIRCSTRINGTYPE: + ogeom = (LWGEOM *)lwcircstring_linearize((LWCIRCSTRING *)geom, tol, type, flags); + break; + case COMPOUNDTYPE: + ogeom = (LWGEOM *)lwcompound_linearize((LWCOMPOUND *)geom, tol, type, flags); + break; + case CURVEPOLYTYPE: + ogeom = (LWGEOM *)lwcurvepoly_linearize((LWCURVEPOLY *)geom, tol, type, flags); + break; + case MULTICURVETYPE: + ogeom = (LWGEOM *)lwmcurve_linearize((LWMCURVE *)geom, tol, type, flags); + break; + case MULTISURFACETYPE: + ogeom = (LWGEOM *)lwmsurface_linearize((LWMSURFACE *)geom, tol, type, flags); + break; + case COLLECTIONTYPE: + ogeom = (LWGEOM *)lwcollection_linearize((LWCOLLECTION *)geom, tol, type, flags); + break; + default: + ogeom = lwgeom_clone_deep(geom); + } + return ogeom; +} + +/* Kept for backward compatibility - TODO: drop */ +LWGEOM * +lwgeom_stroke(const LWGEOM *geom, uint32_t perQuad) +{ + return lwcurve_linearize(geom, perQuad, LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD, 0); +} + +/** + * Return ABC angle in radians + * TODO: move to lwalgorithm + */ +static double +lw_arc_angle(const POINT2D *a, const POINT2D *b, const POINT2D *c) +{ + POINT2D ab, cb; + + ab.x = b->x - a->x; + ab.y = b->y - a->y; + + cb.x = b->x - c->x; + cb.y = b->y - c->y; + + double dot = (ab.x * cb.x + ab.y * cb.y); /* dot product */ + double cross = (ab.x * cb.y - ab.y * cb.x); /* cross product */ + + double alpha = atan2(cross, dot); + + return alpha; +} + +/** +* Returns LW_TRUE if b is on the arc formed by a1/a2/a3, but not within +* that portion already described by a1/a2/a3 +*/ +static int pt_continues_arc(const POINT4D *a1, const POINT4D *a2, const POINT4D *a3, const POINT4D *b) +{ + POINT2D center; + POINT2D *t1 = (POINT2D*)a1; + POINT2D *t2 = (POINT2D*)a2; + POINT2D *t3 = (POINT2D*)a3; + POINT2D *tb = (POINT2D*)b; + double radius = lw_arc_center(t1, t2, t3, ¢er); + double b_distance, diff; + + /* Co-linear a1/a2/a3 */ + if ( radius < 0.0 ) + return LW_FALSE; + + b_distance = distance2d_pt_pt(tb, ¢er); + diff = fabs(radius - b_distance); + LWDEBUGF(4, "circle_radius=%g, b_distance=%g, diff=%g, percentage=%g", radius, b_distance, diff, diff/radius); + + /* Is the point b on the circle? */ + if ( diff < EPSILON_SQLMM ) + { + int a2_side = lw_segment_side(t1, t3, t2); + int b_side = lw_segment_side(t1, t3, tb); + double angle1 = lw_arc_angle(t1, t2, t3); + double angle2 = lw_arc_angle(t2, t3, tb); + + /* Is the angle similar to the previous one ? */ + diff = fabs(angle1 - angle2); + LWDEBUGF(4, " angle1: %g, angle2: %g, diff:%g", angle1, angle2, diff); + if ( diff > EPSILON_SQLMM ) + { + return LW_FALSE; + } + + /* Is the point b on the same side of a1/a3 as the mid-point a2 is? */ + /* If not, it's in the unbounded part of the circle, so it continues the arc, return true. */ + if ( b_side != a2_side ) + return LW_TRUE; + } + return LW_FALSE; +} + +static LWGEOM * +linestring_from_pa(const POINTARRAY *pa, int32_t srid, int start, int end) +{ + int i = 0, j = 0; + POINT4D p; + POINTARRAY *pao = ptarray_construct(ptarray_has_z(pa), ptarray_has_m(pa), end-start+2); + LWDEBUGF(4, "srid=%d, start=%d, end=%d", srid, start, end); + for( i = start; i < end + 2; i++ ) + { + getPoint4d_p(pa, i, &p); + ptarray_set_point4d(pao, j++, &p); + } + return lwline_as_lwgeom(lwline_construct(srid, NULL, pao)); +} + +static LWGEOM * +circstring_from_pa(const POINTARRAY *pa, int32_t srid, int start, int end) +{ + + POINT4D p0, p1, p2; + POINTARRAY *pao = ptarray_construct(ptarray_has_z(pa), ptarray_has_m(pa), 3); + LWDEBUGF(4, "srid=%d, start=%d, end=%d", srid, start, end); + getPoint4d_p(pa, start, &p0); + ptarray_set_point4d(pao, 0, &p0); + getPoint4d_p(pa, (start+end+1)/2, &p1); + ptarray_set_point4d(pao, 1, &p1); + getPoint4d_p(pa, end+1, &p2); + ptarray_set_point4d(pao, 2, &p2); + return lwcircstring_as_lwgeom(lwcircstring_construct(srid, NULL, pao)); +} + +static LWGEOM * +geom_from_pa(const POINTARRAY *pa, int32_t srid, int is_arc, int start, int end) +{ + LWDEBUGF(4, "srid=%d, is_arc=%d, start=%d, end=%d", srid, is_arc, start, end); + if ( is_arc ) + return circstring_from_pa(pa, srid, start, end); + else + return linestring_from_pa(pa, srid, start, end); +} + +LWGEOM * +pta_unstroke(const POINTARRAY *points, int32_t srid) +{ + int i = 0, j, k; + POINT4D a1, a2, a3, b; + POINT4D first, center; + char *edges_in_arcs; + int found_arc = LW_FALSE; + int current_arc = 1; + int num_edges; + int edge_type; /* non-zero if edge is part of an arc */ + int start, end; + LWCOLLECTION *outcol; + /* Minimum number of edges, per quadrant, required to define an arc */ + const unsigned int min_quad_edges = 2; + + /* Die on null input */ + if ( ! points ) + lwerror("pta_unstroke called with null pointarray"); + + /* Null on empty input? */ + if ( points->npoints == 0 ) + return NULL; + + /* We can't desegmentize anything shorter than four points */ + if ( points->npoints < 4 ) + { + /* Return a linestring here*/ + lwerror("pta_unstroke needs implementation for npoints < 4"); + } + + /* Allocate our result array of vertices that are part of arcs */ + num_edges = points->npoints - 1; + edges_in_arcs = lwalloc(num_edges + 1); + memset(edges_in_arcs, 0, num_edges + 1); + + /* We make a candidate arc of the first two edges, */ + /* And then see if the next edge follows it */ + while( i < num_edges-2 ) + { + unsigned int arc_edges; + double num_quadrants; + double angle; + + found_arc = LW_FALSE; + /* Make candidate arc */ + getPoint4d_p(points, i , &a1); + getPoint4d_p(points, i+1, &a2); + getPoint4d_p(points, i+2, &a3); + memcpy(&first, &a1, sizeof(POINT4D)); + + for( j = i+3; j < num_edges+1; j++ ) + { + LWDEBUGF(4, "i=%d, j=%d", i, j); + getPoint4d_p(points, j, &b); + /* Does this point fall on our candidate arc? */ + if ( pt_continues_arc(&a1, &a2, &a3, &b) ) + { + /* Yes. Mark this edge and the two preceding it as arc components */ + LWDEBUGF(4, "pt_continues_arc #%d", current_arc); + found_arc = LW_TRUE; + for ( k = j-1; k > j-4; k-- ) + edges_in_arcs[k] = current_arc; + } + else + { + /* No. So we're done with this candidate arc */ + LWDEBUG(4, "pt_continues_arc = false"); + current_arc++; + break; + } + + memcpy(&a1, &a2, sizeof(POINT4D)); + memcpy(&a2, &a3, sizeof(POINT4D)); + memcpy(&a3, &b, sizeof(POINT4D)); + } + /* Jump past all the edges that were added to the arc */ + if ( found_arc ) + { + /* Check if an arc was composed by enough edges to be + * really considered an arc + * See http://trac.osgeo.org/postgis/ticket/2420 + */ + arc_edges = j - 1 - i; + LWDEBUGF(4, "arc defined by %d edges found", arc_edges); + if ( first.x == b.x && first.y == b.y ) { + LWDEBUG(4, "arc is a circle"); + num_quadrants = 4; + } + else { + lw_arc_center((POINT2D*)&first, (POINT2D*)&b, (POINT2D*)&a1, (POINT2D*)¢er); + angle = lw_arc_angle((POINT2D*)&first, (POINT2D*)¢er, (POINT2D*)&b); + int p2_side = lw_segment_side((POINT2D*)&first, (POINT2D*)&a1, (POINT2D*)&b); + if ( p2_side >= 0 ) angle = -angle; + + if ( angle < 0 ) angle = 2 * M_PI + angle; + num_quadrants = ( 4 * angle ) / ( 2 * M_PI ); + LWDEBUGF(4, "arc angle (%g %g, %g %g, %g %g) is %g (side is %d), quadrants:%g", first.x, first.y, center.x, center.y, b.x, b.y, angle, p2_side, num_quadrants); + } + /* a1 is first point, b is last point */ + if ( arc_edges < min_quad_edges * num_quadrants ) { + LWDEBUGF(4, "Not enough edges for a %g quadrants arc, %g needed", num_quadrants, min_quad_edges * num_quadrants); + for ( k = j-1; k >= i; k-- ) + edges_in_arcs[k] = 0; + } + + i = j-1; + } + else + { + /* Mark this edge as a linear edge */ + edges_in_arcs[i] = 0; + i = i+1; + } + } + +#if POSTGIS_DEBUG_LEVEL > 3 + { + char *edgestr = lwalloc(num_edges+1); + for ( i = 0; i < num_edges; i++ ) + { + if ( edges_in_arcs[i] ) + edgestr[i] = 48 + edges_in_arcs[i]; + else + edgestr[i] = '.'; + } + edgestr[num_edges] = 0; + LWDEBUGF(3, "edge pattern %s", edgestr); + lwfree(edgestr); + } +#endif + + start = 0; + edge_type = edges_in_arcs[0]; + outcol = lwcollection_construct_empty(COMPOUNDTYPE, srid, ptarray_has_z(points), ptarray_has_m(points)); + for( i = 1; i < num_edges; i++ ) + { + if( edge_type != edges_in_arcs[i] ) + { + end = i - 1; + lwcollection_add_lwgeom(outcol, geom_from_pa(points, srid, edge_type, start, end)); + start = i; + edge_type = edges_in_arcs[i]; + } + } + lwfree(edges_in_arcs); /* not needed anymore */ + + /* Roll out last item */ + end = num_edges - 1; + lwcollection_add_lwgeom(outcol, geom_from_pa(points, srid, edge_type, start, end)); + + /* Strip down to singleton if only one entry */ + if ( outcol->ngeoms == 1 ) + { + LWGEOM *outgeom = outcol->geoms[0]; + outcol->ngeoms = 0; lwcollection_free(outcol); + return outgeom; + } + return lwcollection_as_lwgeom(outcol); +} + + +LWGEOM * +lwline_unstroke(const LWLINE *line) +{ + LWDEBUG(2, "lwline_unstroke called."); + + if ( line->points->npoints < 4 ) return lwline_as_lwgeom(lwline_clone_deep(line)); + else return pta_unstroke(line->points, line->srid); +} + +LWGEOM * +lwpolygon_unstroke(const LWPOLY *poly) +{ + LWGEOM **geoms; + uint32_t i, hascurve = 0; + + LWDEBUG(2, "lwpolygon_unstroke called."); + + geoms = lwalloc(sizeof(LWGEOM *)*poly->nrings); + for (i=0; inrings; i++) + { + geoms[i] = pta_unstroke(poly->rings[i], poly->srid); + if (geoms[i]->type == CIRCSTRINGTYPE || geoms[i]->type == COMPOUNDTYPE) + { + hascurve = 1; + } + } + if (hascurve == 0) + { + for (i=0; inrings; i++) + { + lwfree(geoms[i]); /* TODO: should this be lwgeom_free instead ? */ + } + return lwgeom_clone_deep((LWGEOM *)poly); + } + + return (LWGEOM *)lwcollection_construct(CURVEPOLYTYPE, poly->srid, NULL, poly->nrings, geoms); +} + +LWGEOM * +lwmline_unstroke(const LWMLINE *mline) +{ + LWGEOM **geoms; + uint32_t i, hascurve = 0; + + LWDEBUG(2, "lwmline_unstroke called."); + + geoms = lwalloc(sizeof(LWGEOM *)*mline->ngeoms); + for (i=0; ingeoms; i++) + { + geoms[i] = lwline_unstroke((LWLINE *)mline->geoms[i]); + if (geoms[i]->type == CIRCSTRINGTYPE || geoms[i]->type == COMPOUNDTYPE) + { + hascurve = 1; + } + } + if (hascurve == 0) + { + for (i=0; ingeoms; i++) + { + lwfree(geoms[i]); /* TODO: should this be lwgeom_free instead ? */ + } + return lwgeom_clone_deep((LWGEOM *)mline); + } + return (LWGEOM *)lwcollection_construct(MULTICURVETYPE, mline->srid, NULL, mline->ngeoms, geoms); +} + +LWGEOM * +lwmpolygon_unstroke(const LWMPOLY *mpoly) +{ + LWGEOM **geoms; + uint32_t i, hascurve = 0; + + LWDEBUG(2, "lwmpoly_unstroke called."); + + geoms = lwalloc(sizeof(LWGEOM *)*mpoly->ngeoms); + for (i=0; ingeoms; i++) + { + geoms[i] = lwpolygon_unstroke((LWPOLY *)mpoly->geoms[i]); + if (geoms[i]->type == CURVEPOLYTYPE) + { + hascurve = 1; + } + } + if (hascurve == 0) + { + for (i=0; ingeoms; i++) + { + lwfree(geoms[i]); /* TODO: should this be lwgeom_free instead ? */ + } + return lwgeom_clone_deep((LWGEOM *)mpoly); + } + return (LWGEOM *)lwcollection_construct(MULTISURFACETYPE, mpoly->srid, NULL, mpoly->ngeoms, geoms); +} + +LWGEOM * +lwcollection_unstroke(const LWCOLLECTION *c) +{ + LWCOLLECTION *ret = lwalloc(sizeof(LWCOLLECTION)); + memcpy(ret, c, sizeof(LWCOLLECTION)); + + if (c->ngeoms > 0) + { + uint32_t i; + ret->geoms = lwalloc(sizeof(LWGEOM *)*c->ngeoms); + for (i=0; i < c->ngeoms; i++) + { + ret->geoms[i] = lwgeom_unstroke(c->geoms[i]); + } + if (c->bbox) + { + ret->bbox = gbox_copy(c->bbox); + } + } + else + { + ret->bbox = NULL; + ret->geoms = NULL; + } + return (LWGEOM *)ret; +} + + +LWGEOM * +lwgeom_unstroke(const LWGEOM *geom) +{ + LWDEBUG(2, "lwgeom_unstroke called."); + + switch (geom->type) + { + case LINETYPE: + return lwline_unstroke((LWLINE *)geom); + case POLYGONTYPE: + return lwpolygon_unstroke((LWPOLY *)geom); + case MULTILINETYPE: + return lwmline_unstroke((LWMLINE *)geom); + case MULTIPOLYGONTYPE: + return lwmpolygon_unstroke((LWMPOLY *)geom); + case COLLECTIONTYPE: + return lwcollection_unstroke((LWCOLLECTION *)geom); + default: + return lwgeom_clone_deep(geom); + } +} + diff --git a/mgist-postgis/liblwgeom/lwtin.c b/mgist-postgis/liblwgeom/lwtin.c new file mode 100644 index 0000000..8bcc9b6 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwtin.c @@ -0,0 +1,188 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2001-2006 Refractions Research Inc. + * + **********************************************************************/ + + +#include +#include +#include + +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + + +LWTIN* lwtin_add_lwtriangle(LWTIN *mobj, const LWTRIANGLE *obj) +{ + return (LWTIN*)lwcollection_add_lwgeom((LWCOLLECTION*)mobj, (LWGEOM*)obj); +} + +void lwtin_free(LWTIN *tin) +{ + uint32_t i; + if ( ! tin ) return; + if ( tin->bbox ) + lwfree(tin->bbox); + + for ( i = 0; i < tin->ngeoms; i++ ) + if ( tin->geoms && tin->geoms[i] ) + lwtriangle_free(tin->geoms[i]); + + if ( tin->geoms ) + lwfree(tin->geoms); + + lwfree(tin); +} + + +void printLWTIN(LWTIN *tin) +{ + uint32_t i; + LWTRIANGLE *triangle; + + if (tin->type != TINTYPE) + lwerror("printLWTIN called with something else than a TIN"); + + lwnotice("LWTIN {"); + lwnotice(" ndims = %i", (int)FLAGS_NDIMS(tin->flags)); + lwnotice(" SRID = %i", (int)tin->srid); + lwnotice(" ngeoms = %i", (int)tin->ngeoms); + + for (i=0; ingeoms; i++) + { + triangle = (LWTRIANGLE *) tin->geoms[i]; + printPA(triangle->points); + } + lwnotice("}"); +} + + +/* + * TODO rewrite all this stuff to be based on a truly topological model + */ + +struct struct_tin_arcs +{ + double ax, ay, az; + double bx, by, bz; + uint32_t cnt, face; +}; +typedef struct struct_tin_arcs *tin_arcs; + +/* We supposed that the geometry is valid + we could have wrong result if not */ +int lwtin_is_closed(const LWTIN *tin) +{ + uint32_t i, j, k; + uint32_t narcs, carc; + int found; + tin_arcs arcs; + POINT4D pa, pb; + LWTRIANGLE *patch; + + /* If surface is not 3D, it's can't be closed */ + if (!FLAGS_GET_Z(tin->flags)) return 0; + + /* Max theoretical arcs number if no one is shared ... */ + narcs = 3 * tin->ngeoms; + + arcs = lwalloc(sizeof(struct struct_tin_arcs) * narcs); + for (i=0, carc=0; i < tin->ngeoms ; i++) + { + + patch = (LWTRIANGLE *) tin->geoms[i]; + for (j=0; j < 3 ; j++) + { + + getPoint4d_p(patch->points, j, &pa); + getPoint4d_p(patch->points, j+1, &pb); + + /* Make sure to order the 'lower' point first */ + if ( (pa.x > pb.x) || + (pa.x == pb.x && pa.y > pb.y) || + (pa.x == pb.x && pa.y == pb.y && pa.z > pb.z) ) + { + pa = pb; + getPoint4d_p(patch->points, j, &pb); + } + + for (found=0, k=0; k < carc ; k++) + { + + if ( ( arcs[k].ax == pa.x && arcs[k].ay == pa.y && + arcs[k].az == pa.z && arcs[k].bx == pb.x && + arcs[k].by == pb.y && arcs[k].bz == pb.z && + arcs[k].face != i) ) + { + arcs[k].cnt++; + found = 1; + + /* Look like an invalid TIN + anyway not a closed one */ + if (arcs[k].cnt > 2) + { + lwfree(arcs); + return 0; + } + } + } + + if (!found) + { + arcs[carc].cnt=1; + arcs[carc].face=i; + arcs[carc].ax = pa.x; + arcs[carc].ay = pa.y; + arcs[carc].az = pa.z; + arcs[carc].bx = pb.x; + arcs[carc].by = pb.y; + arcs[carc].bz = pb.z; + carc++; + + /* Look like an invalid TIN + anyway not a closed one */ + if (carc > narcs) + { + lwfree(arcs); + return 0; + } + } + } + } + + /* A TIN is closed if each edge + is shared by exactly 2 faces */ + for (k=0; k < carc ; k++) + { + if (arcs[k].cnt != 2) + { + lwfree(arcs); + return 0; + } + } + lwfree(arcs); + + /* Invalid TIN case */ + if (carc < tin->ngeoms) return 0; + + return 1; +} diff --git a/mgist-postgis/liblwgeom/lwtree.c b/mgist-postgis/liblwgeom/lwtree.c new file mode 100644 index 0000000..481b329 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwtree.c @@ -0,0 +1,1384 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2009-2012 Paul Ramsey + * + **********************************************************************/ + +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" +#include "lwtree.h" +#include "measures.h" + +static inline int +rect_node_is_leaf(const RECT_NODE *node) +{ + return node->type == RECT_NODE_LEAF_TYPE; +} + +/* +* Support qsort of nodes for collection/multi types so nodes +* are in "spatial adjacent" order prior to merging. +*/ +static int +rect_node_cmp(const void *pn1, const void *pn2) +{ + GBOX b1, b2; + RECT_NODE *n1 = *((RECT_NODE**)pn1); + RECT_NODE *n2 = *((RECT_NODE**)pn2); + uint64_t h1, h2; + b1.flags = 0; + b1.xmin = n1->xmin; + b1.xmax = n1->xmax; + b1.ymin = n1->ymin; + b1.ymax = n1->ymax; + + b2.flags = 0; + b2.xmin = n2->xmin; + b2.xmax = n2->xmax; + b2.ymin = n2->ymin; + b2.ymax = n2->ymax; + + h1 = gbox_get_sortable_hash(&b1, 0); + h2 = gbox_get_sortable_hash(&b2, 0); + return h1 < h2 ? -1 : (h1 > h2 ? 1 : 0); +} + +/** +* Recurse from top of node tree and free all children. +* does not free underlying point array. +*/ +void +rect_tree_free(RECT_NODE *node) +{ + int i; + if (!node) return; + if (!rect_node_is_leaf(node)) + { + for (i = 0; i < node->i.num_nodes; i++) + { + rect_tree_free(node->i.nodes[i]); + node->i.nodes[i] = NULL; + } + } + lwfree(node); +} + +static int +rect_leaf_node_intersects(RECT_NODE_LEAF *n1, RECT_NODE_LEAF *n2) +{ + const POINT2D *p1, *p2, *p3, *q1, *q2, *q3; + DISTPTS dl; + lw_dist2d_distpts_init(&dl, 1); + switch (n1->seg_type) + { + case RECT_NODE_SEG_POINT: + { + p1 = getPoint2d_cp(n1->pa, n1->seg_num); + + switch (n2->seg_type) + { + case RECT_NODE_SEG_POINT: + q1 = getPoint2d_cp(n2->pa, n2->seg_num); + lw_dist2d_pt_pt(q1, p1, &dl); + return dl.distance == 0.0; + + case RECT_NODE_SEG_LINEAR: + q1 = getPoint2d_cp(n2->pa, n2->seg_num); + q2 = getPoint2d_cp(n2->pa, n2->seg_num+1); + lw_dist2d_pt_seg(p1, q1, q2, &dl); + return dl.distance == 0.0; + + case RECT_NODE_SEG_CIRCULAR: + q1 = getPoint2d_cp(n2->pa, n2->seg_num*2); + q2 = getPoint2d_cp(n2->pa, n2->seg_num*2+1); + q3 = getPoint2d_cp(n2->pa, n2->seg_num*2+2); + lw_dist2d_pt_arc(p1, q1, q2, q3, &dl); + return dl.distance == 0.0; + + default: + lwerror("%s: unsupported segment type", __func__); + break; + } + + break; + } + + case RECT_NODE_SEG_LINEAR: + { + p1 = getPoint2d_cp(n1->pa, n1->seg_num); + p2 = getPoint2d_cp(n1->pa, n1->seg_num+1); + + switch (n2->seg_type) + { + case RECT_NODE_SEG_POINT: + q1 = getPoint2d_cp(n2->pa, n2->seg_num); + lw_dist2d_pt_seg(q1, p1, p2, &dl); + return dl.distance == 0.0; + + case RECT_NODE_SEG_LINEAR: + q1 = getPoint2d_cp(n2->pa, n2->seg_num); + q2 = getPoint2d_cp(n2->pa, n2->seg_num+1); + return lw_segment_intersects(p1, p2, q1, q2) > 0; + + case RECT_NODE_SEG_CIRCULAR: + q1 = getPoint2d_cp(n2->pa, n2->seg_num*2); + q2 = getPoint2d_cp(n2->pa, n2->seg_num*2+1); + q3 = getPoint2d_cp(n2->pa, n2->seg_num*2+2); + lw_dist2d_seg_arc(p1, p2, q1, q2, q3, &dl); + return dl.distance == 0.0; + + default: + lwerror("%s: unsupported segment type", __func__); + break; + } + + break; + } + case RECT_NODE_SEG_CIRCULAR: + { + p1 = getPoint2d_cp(n1->pa, n1->seg_num*2); + p2 = getPoint2d_cp(n1->pa, n1->seg_num*2+1); + p3 = getPoint2d_cp(n1->pa, n1->seg_num*2+2); + + switch (n2->seg_type) + { + case RECT_NODE_SEG_POINT: + q1 = getPoint2d_cp(n2->pa, n2->seg_num); + lw_dist2d_pt_arc(q1, p1, p2, p3, &dl); + return dl.distance == 0.0; + + case RECT_NODE_SEG_LINEAR: + q1 = getPoint2d_cp(n2->pa, n2->seg_num); + q2 = getPoint2d_cp(n2->pa, n2->seg_num+1); + lw_dist2d_seg_arc(q1, q2, p1, p2, p3, &dl); + return dl.distance == 0.0; + + case RECT_NODE_SEG_CIRCULAR: + q1 = getPoint2d_cp(n2->pa, n2->seg_num*2); + q2 = getPoint2d_cp(n2->pa, n2->seg_num*2+1); + q3 = getPoint2d_cp(n2->pa, n2->seg_num*2+2); + lw_dist2d_arc_arc(p1, p2, p3, q1, q2, q3, &dl); + return dl.distance == 0.0; + + default: + lwerror("%s: unsupported segment type", __func__); + break; + } + + break; + } + default: + return LW_FALSE; + } + return LW_FALSE; +} + + +/* +* Returns 1 if segment is to the right of point. +*/ +static inline int +rect_leaf_node_segment_side(RECT_NODE_LEAF *node, const POINT2D *q, int *on_boundary) +{ + const POINT2D *p1, *p2, *p3; + switch (node->seg_type) + { + case RECT_NODE_SEG_LINEAR: + { + int side; + p1 = getPoint2d_cp(node->pa, node->seg_num); + p2 = getPoint2d_cp(node->pa, node->seg_num+1); + + side = lw_segment_side(p1, p2, q); + + /* Always note case where we're on boundary */ + if (side == 0 && lw_pt_in_seg(q, p1, p2)) + { + *on_boundary = LW_TRUE; + return 0; + } + + /* Segment points up and point is on left */ + if (p1->y < p2->y && side == -1 && q->y != p2->y) + { + return 1; + } + + /* Segment points down and point is on right */ + if (p1->y > p2->y && side == 1 && q->y != p2->y) + { + return 1; + } + + /* Segment is horizontal, do we cross first point? */ + if (p1->y == p2->y && q->x < p1->x) + { + return 1; + } + + return 0; + } + case RECT_NODE_SEG_CIRCULAR: + { + int arc_side, seg_side; + + p1 = getPoint2d_cp(node->pa, node->seg_num*2); + p2 = getPoint2d_cp(node->pa, node->seg_num*2+1); + p3 = getPoint2d_cp(node->pa, node->seg_num*2+2); + + /* Always note case where we're on boundary */ + arc_side = lw_arc_side(p1, p2, p3, q); + if (arc_side == 0) + { + *on_boundary = LW_TRUE; + return 0; + } + + seg_side = lw_segment_side(p1, p3, q); + if (seg_side == arc_side) + { + /* Segment points up and point is on left */ + if (p1->y < p3->y && seg_side == -1 && q->y != p3->y) + { + return 1; + } + + /* Segment points down and point is on right */ + if (p1->y > p3->y && seg_side == 1 && q->y != p3->y) + { + return 1; + } + } + else + { + /* Segment points up and point is on left */ + if (p1->y < p3->y && seg_side == 1 && q->y != p3->y) + { + return 1; + } + + /* Segment points down and point is on right */ + if (p1->y > p3->y && seg_side == -1 && q->y != p3->y) + { + return 1; + } + + /* Segment is horizontal, do we cross first point? */ + if (p1->y == p3->y) + { + return 1; + } + } + + return 0; + + } + default: + { + lwerror("%s: unsupported seg_type - %d", __func__, node->seg_type); + return 0; + } + } + + return 0; +} + +/* +* Only pass the head of a ring. Either a LinearRing from a polygon +* or a CompoundCurve from a CurvePolygon. +* Takes a horizontal line through the ring, and adds up the +* crossing directions. An "inside" point will be on the same side +* ("both left" or "both right") of the edges the line crosses, +* so it will have a non-zero return sum. An "outside" point will +* be on both sides, and will have a zero return sum. +*/ +static int +rect_tree_ring_contains_point(RECT_NODE *node, const POINT2D *pt, int *on_boundary) +{ + /* Only test nodes that straddle our stabline vertically */ + /* and might be to the right horizontally */ + if (node->ymin <= pt->y && pt->y <= node->ymax && pt->x <= node->xmax) + { + if (rect_node_is_leaf(node)) + { + return rect_leaf_node_segment_side(&node->l, pt, on_boundary); + } + else + { + int i, r = 0; + for (i = 0; i < node->i.num_nodes; i++) + { + r += rect_tree_ring_contains_point(node->i.nodes[i], pt, on_boundary); + } + return r; + } + } + return 0; +} + +/* +* Only pass in the head of an "area" type. Polygon or CurvePolygon. +* Sums up containment of exterior (+1) and interior (-1) rings, so +* that zero is uncontained, +1 is contained and negative is an error +* (multiply contained by interior rings?) +*/ +static int +rect_tree_area_contains_point(RECT_NODE *node, const POINT2D *pt) +{ + /* Can't do anything with a leaf node, makes no sense */ + if (rect_node_is_leaf(node)) + return 0; + + /* Iterate into area until we find ring heads */ + if (node->i.ring_type == RECT_NODE_RING_NONE) + { + int i, sum = 0; + for (i = 0; i < node->i.num_nodes; i++) + sum += rect_tree_area_contains_point(node->i.nodes[i], pt); + return sum; + } + /* See if the ring encloses the point */ + else + { + int on_boundary = 0; + int edge_crossing_count = rect_tree_ring_contains_point(node, pt, &on_boundary); + /* Odd number of crossings => contained */ + int contained = (edge_crossing_count % 2 == 1); + /* External rings return positive containment, interior ones negative, */ + /* so that a point-in-hole case nets out to zero (contained by both */ + /* interior and exterior rings. */ + if (node->i.ring_type == RECT_NODE_RING_INTERIOR) + { + return on_boundary ? 0 : -1 * contained; + } + else + { + return contained || on_boundary; + } + + } +} + +/* +* Simple containment test for node/point inputs +*/ +static int +rect_node_bounds_point(RECT_NODE *node, const POINT2D *pt) +{ + if (pt->y < node->ymin || pt->y > node->ymax || + pt->x < node->xmin || pt->x > node->xmax) + return LW_FALSE; + else + return LW_TRUE; +} + +/* +* Pass in arbitrary tree, get back true if point is contained or on boundary, +* and false otherwise. +*/ +int +rect_tree_contains_point(RECT_NODE *node, const POINT2D *pt) +{ + int i, c; + + /* Object cannot contain point if bounds don't */ + if (!rect_node_bounds_point(node, pt)) + return 0; + + switch (node->geom_type) + { + case POLYGONTYPE: + case CURVEPOLYTYPE: + return rect_tree_area_contains_point(node, pt) > 0; + + case MULTIPOLYGONTYPE: + case MULTISURFACETYPE: + case COLLECTIONTYPE: + { + for (i = 0; i < node->i.num_nodes; i++) + { + c = rect_tree_contains_point(node->i.nodes[i], pt); + if (c) return LW_TRUE; + } + return LW_FALSE; + } + + default: + return LW_FALSE; + } +} + +/* +* For area types, doing intersects and distance, we will +* need to do a point-in-poly test first to find the full-contained +* case where an intersection exists without any edges actually +* intersecting. +*/ +static int +rect_tree_is_area(const RECT_NODE *node) +{ + switch (node->geom_type) + { + case POLYGONTYPE: + case CURVEPOLYTYPE: + case MULTISURFACETYPE: + return LW_TRUE; + + case COLLECTIONTYPE: + { + if (rect_node_is_leaf(node)) + return LW_FALSE; + else + { + int i; + for (i = 0; i < node->i.num_nodes; i++) + { + if (rect_tree_is_area(node->i.nodes[i])) + return LW_TRUE; + } + return LW_FALSE; + } + } + + default: + return LW_FALSE; + } +} + +static RECT_NODE_SEG_TYPE lwgeomTypeArc[] = +{ + RECT_NODE_SEG_UNKNOWN, // "Unknown" + RECT_NODE_SEG_POINT, // "Point" + RECT_NODE_SEG_LINEAR, // "LineString" + RECT_NODE_SEG_LINEAR, // "Polygon" + RECT_NODE_SEG_UNKNOWN, // "MultiPoint" + RECT_NODE_SEG_LINEAR, // "MultiLineString" + RECT_NODE_SEG_LINEAR, // "MultiPolygon" + RECT_NODE_SEG_UNKNOWN, // "GeometryCollection" + RECT_NODE_SEG_CIRCULAR, // "CircularString" + RECT_NODE_SEG_UNKNOWN, // "CompoundCurve" + RECT_NODE_SEG_UNKNOWN, // "CurvePolygon" + RECT_NODE_SEG_UNKNOWN, // "MultiCurve" + RECT_NODE_SEG_UNKNOWN, // "MultiSurface" + RECT_NODE_SEG_LINEAR, // "PolyhedralSurface" + RECT_NODE_SEG_LINEAR, // "Triangle" + RECT_NODE_SEG_LINEAR // "Tin" +}; + +/* +* Create a new leaf node. +*/ +static RECT_NODE * +rect_node_leaf_new(const POINTARRAY *pa, int seg_num, int geom_type) +{ + const POINT2D *p1, *p2, *p3; + RECT_NODE *node; + GBOX gbox; + RECT_NODE_SEG_TYPE seg_type = lwgeomTypeArc[geom_type]; + + switch (seg_type) + { + case RECT_NODE_SEG_POINT: + { + p1 = getPoint2d_cp(pa, seg_num); + gbox.xmin = gbox.xmax = p1->x; + gbox.ymin = gbox.ymax = p1->y; + break; + } + + case RECT_NODE_SEG_LINEAR: + { + p1 = getPoint2d_cp(pa, seg_num); + p2 = getPoint2d_cp(pa, seg_num+1); + /* Zero length edge, doesn't get a node */ + if ((p1->x == p2->x) && (p1->y == p2->y)) + return NULL; + gbox.xmin = FP_MIN(p1->x, p2->x); + gbox.xmax = FP_MAX(p1->x, p2->x); + gbox.ymin = FP_MIN(p1->y, p2->y); + gbox.ymax = FP_MAX(p1->y, p2->y); + break; + } + + case RECT_NODE_SEG_CIRCULAR: + { + p1 = getPoint2d_cp(pa, 2*seg_num); + p2 = getPoint2d_cp(pa, 2*seg_num+1); + p3 = getPoint2d_cp(pa, 2*seg_num+2); + /* Zero length edge, doesn't get a node */ + if ((p1->x == p2->x) && (p2->x == p3->x) && + (p1->y == p2->y) && (p2->y == p3->y)) + return NULL; + lw_arc_calculate_gbox_cartesian_2d(p1, p2, p3, &gbox); + break; + } + + default: + { + lwerror("%s: unsupported seg_type - %d", __func__, seg_type); + return NULL; + } + } + + node = lwalloc(sizeof(RECT_NODE)); + node->type = RECT_NODE_LEAF_TYPE; + node->geom_type = geom_type; + node->xmin = gbox.xmin; + node->xmax = gbox.xmax; + node->ymin = gbox.ymin; + node->ymax = gbox.ymax; + node->l.seg_num = seg_num; + node->l.seg_type = seg_type; + node->l.pa = pa; + return node; +} + + +static void +rect_node_internal_add_node(RECT_NODE *node, RECT_NODE *add) +{ + if (rect_node_is_leaf(node)) + lwerror("%s: call on leaf node", __func__); + node->xmin = FP_MIN(node->xmin, add->xmin); + node->xmax = FP_MAX(node->xmax, add->xmax); + node->ymin = FP_MIN(node->ymin, add->ymin); + node->ymax = FP_MAX(node->ymax, add->ymax); + node->i.nodes[node->i.num_nodes++] = add; + return; +} + + +static RECT_NODE * +rect_node_internal_new(const RECT_NODE *seed) +{ + RECT_NODE *node = lwalloc(sizeof(RECT_NODE)); + node->xmin = seed->xmin; + node->xmax = seed->xmax; + node->ymin = seed->ymin; + node->ymax = seed->ymax; + node->geom_type = seed->geom_type; + node->type = RECT_NODE_INTERNAL_TYPE; + node->i.num_nodes = 0; + node->i.ring_type = RECT_NODE_RING_NONE; + node->i.sorted = 0; + return node; +} + +/* +* We expect the incoming nodes to be in a spatially coherent +* order. For incoming nodes derived from point arrays, +* the very fact that they are +* a vertex list implies a reasonable ordering: points nearby in +* the list will be nearby in space. For incoming nodes from higher +* level structures (collections, etc) the caller should sort the +* nodes using a z-order first, so that this merge step results in a +* spatially coherent structure. +*/ +static RECT_NODE * +rect_nodes_merge(RECT_NODE ** nodes, uint32_t num_nodes) +{ + if (num_nodes < 1) + { + return NULL; + } + + while (num_nodes > 1) + { + uint32_t i, k = 0; + RECT_NODE *node = NULL; + for (i = 0; i < num_nodes; i++) + { + if (!node) + node = rect_node_internal_new(nodes[i]); + + rect_node_internal_add_node(node, nodes[i]); + + if (node->i.num_nodes == RECT_NODE_SIZE) + { + nodes[k++] = node; + node = NULL; + } + } + if (node) + nodes[k++] = node; + num_nodes = k; + } + + return nodes[0]; +} + +/* +* Build a tree of nodes from a point array, one node per edge. +*/ +RECT_NODE * +rect_tree_from_ptarray(const POINTARRAY *pa, int geom_type) +{ + int num_edges = 0, i = 0, j = 0; + RECT_NODE_SEG_TYPE seg_type = lwgeomTypeArc[geom_type]; + RECT_NODE **nodes = NULL; + RECT_NODE *tree = NULL; + + /* No-op on empty ring/line/pt */ + if ( pa->npoints < 1 ) + return NULL; + + /* For arcs, 3 points per edge, for lines, 2 per edge */ + switch(seg_type) + { + case RECT_NODE_SEG_POINT: + return rect_node_leaf_new(pa, 0, geom_type); + break; + case RECT_NODE_SEG_LINEAR: + num_edges = pa->npoints - 1; + break; + case RECT_NODE_SEG_CIRCULAR: + num_edges = (pa->npoints - 1)/2; + break; + default: + lwerror("%s: unsupported seg_type - %d", __func__, seg_type); + } + + /* First create a flat list of nodes, one per edge. */ + nodes = lwalloc(sizeof(RECT_NODE*) * num_edges); + for (i = 0; i < num_edges; i++) + { + RECT_NODE *node = rect_node_leaf_new(pa, i, geom_type); + if (node) /* Not zero length? */ + nodes[j++] = node; + } + + /* Merge the list into a tree */ + tree = rect_nodes_merge(nodes, j); + + /* Free the old list structure, leaving the tree in place */ + lwfree(nodes); + + /* Return top of tree */ + return tree; +} + +LWGEOM * +rect_tree_to_lwgeom(const RECT_NODE *node) +{ + LWGEOM *poly = (LWGEOM*)lwpoly_construct_envelope(0, node->xmin, node->ymin, node->xmax, node->ymax); + if (rect_node_is_leaf(node)) + { + return poly; + } + else + { + int i; + LWCOLLECTION *col = lwcollection_construct_empty(COLLECTIONTYPE, 0, 0, 0); + lwcollection_add_lwgeom(col, poly); + for (i = 0; i < node->i.num_nodes; i++) + { + lwcollection_add_lwgeom(col, rect_tree_to_lwgeom(node->i.nodes[i])); + } + return (LWGEOM*)col; + } +} + +char * +rect_tree_to_wkt(const RECT_NODE *node) +{ + LWGEOM *geom = rect_tree_to_lwgeom(node); + char *wkt = lwgeom_to_wkt(geom, WKT_ISO, 12, 0); + lwgeom_free(geom); + return wkt; +} + +void +rect_tree_printf(const RECT_NODE *node, int depth) +{ + printf("%*s----\n", depth, ""); + printf("%*stype: %d\n", depth, "", node->type); + printf("%*sgeom_type: %d\n", depth, "", node->geom_type); + printf("%*sbox: %g %g, %g %g\n", depth, "", node->xmin, node->ymin, node->xmax, node->ymax); + if (node->type == RECT_NODE_LEAF_TYPE) + { + printf("%*sseg_type: %d\n", depth, "", node->l.seg_type); + printf("%*sseg_num: %d\n", depth, "", node->l.seg_num); + } + else + { + int i; + for (i = 0; i < node->i.num_nodes; i++) + { + rect_tree_printf(node->i.nodes[i], depth+2); + } + } +} + +static RECT_NODE * +rect_tree_from_lwpoint(const LWGEOM *lwgeom) +{ + const LWPOINT *lwpt = (const LWPOINT*)lwgeom; + return rect_tree_from_ptarray(lwpt->point, lwgeom->type); +} + +static RECT_NODE * +rect_tree_from_lwline(const LWGEOM *lwgeom) +{ + const LWLINE *lwline = (const LWLINE*)lwgeom; + return rect_tree_from_ptarray(lwline->points, lwgeom->type); +} + +static RECT_NODE * +rect_tree_from_lwpoly(const LWGEOM *lwgeom) +{ + RECT_NODE **nodes; + RECT_NODE *tree; + uint32_t i, j = 0; + const LWPOLY *lwpoly = (const LWPOLY*)lwgeom; + + if (lwpoly->nrings < 1) + return NULL; + + nodes = lwalloc(sizeof(RECT_NODE*) * lwpoly->nrings); + for (i = 0; i < lwpoly->nrings; i++) + { + RECT_NODE *node = rect_tree_from_ptarray(lwpoly->rings[i], lwgeom->type); + if (node) + { + node->i.ring_type = i ? RECT_NODE_RING_INTERIOR : RECT_NODE_RING_EXTERIOR; + nodes[j++] = node; + } + } + tree = rect_nodes_merge(nodes, j); + tree->geom_type = lwgeom->type; + lwfree(nodes); + return tree; +} + +static RECT_NODE * +rect_tree_from_lwcurvepoly(const LWGEOM *lwgeom) +{ + RECT_NODE **nodes; + RECT_NODE *tree; + uint32_t i, j = 0; + const LWCURVEPOLY *lwcol = (const LWCURVEPOLY*)lwgeom; + + if (lwcol->nrings < 1) + return NULL; + + nodes = lwalloc(sizeof(RECT_NODE*) * lwcol->nrings); + for (i = 0; i < lwcol->nrings; i++) + { + RECT_NODE *node = rect_tree_from_lwgeom(lwcol->rings[i]); + if (node) + { + /* + * In the case of arc circle, it's possible for a ring to consist + * of a single closed edge. That will arrive as a leaf node. We + * need to wrap that node in an internal node with an appropriate + * ring type so all the other code can try and make sense of it. + */ + if (node->type == RECT_NODE_LEAF_TYPE) + { + RECT_NODE *internal = rect_node_internal_new(node); + rect_node_internal_add_node(internal, node); + node = internal; + } + /* Each subcomponent is a ring */ + node->i.ring_type = i ? RECT_NODE_RING_INTERIOR : RECT_NODE_RING_EXTERIOR; + nodes[j++] = node; + } + } + /* Put the top nodes in a z-order curve for a spatially coherent */ + /* tree after node merge */ + qsort(nodes, j, sizeof(RECT_NODE*), rect_node_cmp); + + tree = rect_nodes_merge(nodes, j); + + tree->geom_type = lwgeom->type; + lwfree(nodes); + return tree; + +} + +static RECT_NODE * +rect_tree_from_lwcollection(const LWGEOM *lwgeom) +{ + RECT_NODE **nodes; + RECT_NODE *tree; + uint32_t i, j = 0; + const LWCOLLECTION *lwcol = (const LWCOLLECTION*)lwgeom; + + if (lwcol->ngeoms < 1) + return NULL; + + /* Build one tree for each sub-geometry, then below */ + /* we merge the root notes of those trees to get a single */ + /* top node for the collection */ + nodes = lwalloc(sizeof(RECT_NODE*) * lwcol->ngeoms); + for (i = 0; i < lwcol->ngeoms; i++) + { + RECT_NODE *node = rect_tree_from_lwgeom(lwcol->geoms[i]); + if (node) + { + /* Curvepolygons are collections where the sub-geometries */ + /* are the rings, and will need to doint point-in-poly */ + /* tests in order to do intersects and distance calculations */ + /* correctly */ + if (lwgeom->type == CURVEPOLYTYPE) + node->i.ring_type = i ? RECT_NODE_RING_INTERIOR : RECT_NODE_RING_EXTERIOR; + nodes[j++] = node; + } + } + /* Sort the nodes using a z-order curve, so that merging the nodes */ + /* gives a spatially coherent tree (near things are in near nodes) */ + /* Note: CompoundCurve has edges already spatially organized, no */ + /* sorting needed */ + if (lwgeom->type != COMPOUNDTYPE) + qsort(nodes, j, sizeof(RECT_NODE*), rect_node_cmp); + + tree = rect_nodes_merge(nodes, j); + + tree->geom_type = lwgeom->type; + lwfree(nodes); + return tree; +} + +RECT_NODE * +rect_tree_from_lwgeom(const LWGEOM *lwgeom) +{ + switch(lwgeom->type) + { + case POINTTYPE: + return rect_tree_from_lwpoint(lwgeom); + case TRIANGLETYPE: + case CIRCSTRINGTYPE: + case LINETYPE: + return rect_tree_from_lwline(lwgeom); + case POLYGONTYPE: + return rect_tree_from_lwpoly(lwgeom); + case CURVEPOLYTYPE: + return rect_tree_from_lwcurvepoly(lwgeom); + case COMPOUNDTYPE: + case MULTICURVETYPE: + case MULTISURFACETYPE: + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case POLYHEDRALSURFACETYPE: + case TINTYPE: + case COLLECTIONTYPE: + return rect_tree_from_lwcollection(lwgeom); + default: + lwerror("%s: Unknown geometry type: %s", __func__, lwtype_name(lwgeom->type)); + return NULL; + } + return NULL; +} + +/* +* Get an actual coordinate point from a tree to use +* for point-in-polygon testing. +*/ +static const POINT2D * +rect_tree_get_point(const RECT_NODE *node) +{ + if (!node) return NULL; + if (rect_node_is_leaf(node)) + return getPoint2d_cp(node->l.pa, 0); + else + return rect_tree_get_point(node->i.nodes[0]); +} + +static inline int +rect_node_intersects(const RECT_NODE *n1, const RECT_NODE *n2) +{ + if (n1->xmin > n2->xmax || n2->xmin > n1->xmax || + n1->ymin > n2->ymax || n2->ymin > n1->ymax) + { + return 0; + } + else + { + return 1; + } +} + +#if POSTGIS_DEBUG_LEVEL >= 4 +static char * +rect_node_to_str(const RECT_NODE *n) +{ + char *buf = lwalloc(256); + snprintf(buf, 256, "(%.9g %.9g,%.9g %.9g)", + n->xmin, n->ymin, n->xmax, n->ymax); + return buf; +} +#endif + +/* +* Work down to leaf nodes, until we find a pair of leaf nodes +* that intersect. Prune branches that do not intersect. +*/ +static int +rect_tree_intersects_tree_recursive(RECT_NODE *n1, RECT_NODE *n2) +{ + int i, j; +#if POSTGIS_DEBUG_LEVEL >= 4 + char *n1_str = rect_node_to_str(n1); + char *n2_str = rect_node_to_str(n2); + LWDEBUGF(4,"n1 %s n2 %s", n1, n2); + lwfree(n1_str); + lwfree(n2_str); +#endif + /* There can only be an edge intersection if the rectangles overlap */ + if (rect_node_intersects(n1, n2)) + { + LWDEBUG(4," interaction found"); + /* We can only test for a true intersection if the nodes are both leaf nodes */ + if (rect_node_is_leaf(n1) && rect_node_is_leaf(n2)) + { + LWDEBUG(4," leaf node test"); + /* Check for true intersection */ + return rect_leaf_node_intersects(&n1->l, &n2->l); + } + else if (rect_node_is_leaf(n2) && !rect_node_is_leaf(n1)) + { + for (i = 0; i < n1->i.num_nodes; i++) + { + if (rect_tree_intersects_tree_recursive(n1->i.nodes[i], n2)) + return LW_TRUE; + } + } + else if (rect_node_is_leaf(n1) && !rect_node_is_leaf(n2)) + { + for (i = 0; i < n2->i.num_nodes; i++) + { + if (rect_tree_intersects_tree_recursive(n2->i.nodes[i], n1)) + return LW_TRUE; + } + } + else + { + for (j = 0; j < n1->i.num_nodes; j++) + { + for (i = 0; i < n2->i.num_nodes; i++) + { + if (rect_tree_intersects_tree_recursive(n2->i.nodes[i], n1->i.nodes[j])) + return LW_TRUE; + } + } + } + } + return LW_FALSE; +} + + +int +rect_tree_intersects_tree(RECT_NODE *n1, RECT_NODE *n2) +{ + /* + * It is possible for an area to intersect another object + * without any edges intersecting, if the object is fully contained. + * If that is so, then any point in the object will be contained, + * so we do a quick point-in-poly test first for those cases + */ + if (rect_tree_is_area(n1) && + rect_tree_contains_point(n1, rect_tree_get_point(n2))) + { + return LW_TRUE; + } + + if (rect_tree_is_area(n2) && + rect_tree_contains_point(n2, rect_tree_get_point(n1))) + { + return LW_TRUE; + } + + /* + * Not contained, so intersection can only happen if + * edges actually intersect. + */ + return rect_tree_intersects_tree_recursive(n1, n2); +} + +/* +* For slightly more speed when doing distance comparisons, +* where we only care if d1 < d2 and not what the actual value +* of d1 or d2 is, we use the squared distance and avoid the +* sqrt() +*/ +static inline double +distance_sq(double x1, double y1, double x2, double y2) +{ + double dx = x1-x2; + double dy = y1-y2; + return dx*dx + dy*dy; +} + +static inline double +distance(double x1, double y1, double x2, double y2) +{ + return sqrt(distance_sq(x1, y1, x2, y2)); +} + +/* +* The closest any two objects in two nodes can be is the smallest +* distance between the nodes themselves. +*/ +static inline double +rect_node_min_distance(const RECT_NODE *n1, const RECT_NODE *n2) +{ + int left = n1->xmin > n2->xmax; + int right = n1->xmax < n2->xmin; + int bottom = n1->ymin > n2->ymax; + int top = n1->ymax < n2->ymin; + + //lwnotice("rect_node_min_distance"); + + if (top && left) + return distance(n1->xmin, n1->ymax, n2->xmax, n2->ymin); + else if (top && right) + return distance(n1->xmax, n1->ymax, n2->xmin, n2->ymin); + else if (bottom && left) + return distance(n1->xmin, n1->ymin, n2->xmax, n2->ymax); + else if (bottom && right) + return distance(n1->xmax, n1->ymin, n2->xmin, n2->ymax); + else if (left) + return n1->xmin - n2->xmax; + else if (right) + return n2->xmin - n1->xmax; + else if (bottom) + return n1->ymin - n2->ymax; + else if (top) + return n2->ymin - n1->ymax; + else + return 0.0; + + return 0.0; +} + +/* +* The furthest apart the objects in two nodes can be is if they +* are at opposite corners of the bbox that contains both nodes +*/ +static inline double +rect_node_max_distance(const RECT_NODE *n1, const RECT_NODE *n2) +{ + double xmin = FP_MIN(n1->xmin, n2->xmin); + double ymin = FP_MIN(n1->ymin, n2->ymin); + double xmax = FP_MAX(n1->xmax, n2->xmax); + double ymax = FP_MAX(n1->ymax, n2->ymax); + double dx = xmax - xmin; + double dy = ymax - ymin; + //lwnotice("rect_node_max_distance"); + return sqrt(dx*dx + dy*dy); +} + +/* +* Leaf nodes represent individual edges from the original shape. +* As such, they can be either points (if original was a (multi)point) +* two-point straight edges (for linear types), or +* three-point circular arcs (for curvilinear types). +* The distance calculations for each possible combination of primitive +* edges is different, so there's a big case switch in here to match +* up the right combination of inputs to the right distance calculation. +*/ +static double +rect_leaf_node_distance(const RECT_NODE_LEAF *n1, const RECT_NODE_LEAF *n2, RECT_TREE_DISTANCE_STATE *state) +{ + const POINT2D *p1, *p2, *p3, *q1, *q2, *q3; + DISTPTS dl; + + //lwnotice("rect_leaf_node_distance, %d<->%d", n1->seg_num, n2->seg_num); + + lw_dist2d_distpts_init(&dl, DIST_MIN); + + switch (n1->seg_type) + { + case RECT_NODE_SEG_POINT: + { + p1 = getPoint2d_cp(n1->pa, n1->seg_num); + + switch (n2->seg_type) + { + case RECT_NODE_SEG_POINT: + q1 = getPoint2d_cp(n2->pa, n2->seg_num); + lw_dist2d_pt_pt(q1, p1, &dl); + break; + + case RECT_NODE_SEG_LINEAR: + q1 = getPoint2d_cp(n2->pa, n2->seg_num); + q2 = getPoint2d_cp(n2->pa, n2->seg_num+1); + lw_dist2d_pt_seg(p1, q1, q2, &dl); + break; + + case RECT_NODE_SEG_CIRCULAR: + q1 = getPoint2d_cp(n2->pa, n2->seg_num*2); + q2 = getPoint2d_cp(n2->pa, n2->seg_num*2+1); + q3 = getPoint2d_cp(n2->pa, n2->seg_num*2+2); + lw_dist2d_pt_arc(p1, q1, q2, q3, &dl); + break; + + default: + lwerror("%s: unsupported segment type", __func__); + } + break; + } + + case RECT_NODE_SEG_LINEAR: + { + p1 = getPoint2d_cp(n1->pa, n1->seg_num); + p2 = getPoint2d_cp(n1->pa, n1->seg_num+1); + + switch (n2->seg_type) + { + case RECT_NODE_SEG_POINT: + q1 = getPoint2d_cp(n2->pa, n2->seg_num); + lw_dist2d_pt_seg(q1, p1, p2, &dl); + break; + + case RECT_NODE_SEG_LINEAR: + q1 = getPoint2d_cp(n2->pa, n2->seg_num); + q2 = getPoint2d_cp(n2->pa, n2->seg_num+1); + lw_dist2d_seg_seg(q1, q2, p1, p2, &dl); + // lwnotice( + // "%d\tLINESTRING(%g %g,%g %g)\t%d\tLINESTRING(%g %g,%g %g)\t%g\t%g\t%g", + // n1->seg_num, + // p1->x, p1->y, p2->x, p2->y, + // n2->seg_num, + // q1->x, q1->y, q2->x, q2->y, + // dl.distance, state->min_dist, state->max_dist); + break; + + case RECT_NODE_SEG_CIRCULAR: + q1 = getPoint2d_cp(n2->pa, n2->seg_num*2); + q2 = getPoint2d_cp(n2->pa, n2->seg_num*2+1); + q3 = getPoint2d_cp(n2->pa, n2->seg_num*2+2); + lw_dist2d_seg_arc(p1, p2, q1, q2, q3, &dl); + break; + + default: + lwerror("%s: unsupported segment type", __func__); + } + break; + } + case RECT_NODE_SEG_CIRCULAR: + { + p1 = getPoint2d_cp(n1->pa, n1->seg_num*2); + p2 = getPoint2d_cp(n1->pa, n1->seg_num*2+1); + p3 = getPoint2d_cp(n1->pa, n1->seg_num*2+2); + + switch (n2->seg_type) + { + case RECT_NODE_SEG_POINT: + q1 = getPoint2d_cp(n2->pa, n2->seg_num); + lw_dist2d_pt_arc(q1, p1, p2, p3, &dl); + break; + + case RECT_NODE_SEG_LINEAR: + q1 = getPoint2d_cp(n2->pa, n2->seg_num); + q2 = getPoint2d_cp(n2->pa, n2->seg_num+1); + lw_dist2d_seg_arc(q1, q2, p1, p2, p3, &dl); + break; + + case RECT_NODE_SEG_CIRCULAR: + q1 = getPoint2d_cp(n2->pa, n2->seg_num*2); + q2 = getPoint2d_cp(n2->pa, n2->seg_num*2+1); + q3 = getPoint2d_cp(n2->pa, n2->seg_num*2+2); + lw_dist2d_arc_arc(p1, p2, p3, q1, q2, q3, &dl); + break; + + default: + lwerror("%s: unsupported segment type", __func__); + } + break; + } + default: + lwerror("%s: unsupported segment type", __func__); + } + + /* If this is a new global minima, save it */ + if (dl.distance < state->min_dist) + { + state->min_dist = dl.distance; + state->p1 = dl.p1; + state->p2 = dl.p2; + } + + return dl.distance; +} + + +static int +rect_tree_node_sort_cmp(const void *a, const void *b) +{ + RECT_NODE *n1 = *(RECT_NODE**)a; + RECT_NODE *n2 = *(RECT_NODE**)b; + if (n1->d < n2->d) return -1; + else if (n1->d > n2->d) return 1; + else return 0; +} + +/* Calculate the center of a node */ +static inline void +rect_node_to_p2d(const RECT_NODE *n, POINT2D *pt) +{ + pt->x = (n->xmin + n->xmax)/2; + pt->y = (n->ymin + n->ymax)/2; +} + +/* +* (If necessary), sort the children of each node in +* order of their distance from the enter of the other node. +*/ +static void +rect_tree_node_sort(RECT_NODE *n1, RECT_NODE *n2) +{ + int i; + RECT_NODE *n; + POINT2D c1, c2, c; + if (!rect_node_is_leaf(n1) && ! n1->i.sorted) + { + rect_node_to_p2d(n2, &c2); + /* Distance of each child from center of other node */ + for (i = 0; i < n1->i.num_nodes; i++) + { + n = n1->i.nodes[i]; + rect_node_to_p2d(n, &c); + n->d = distance2d_sqr_pt_pt(&c2, &c); + } + /* Sort the children by distance */ + n1->i.sorted = 1; + qsort(n1->i.nodes, + n1->i.num_nodes, + sizeof(RECT_NODE*), + rect_tree_node_sort_cmp); + } + if (!rect_node_is_leaf(n2) && ! n2->i.sorted) + { + rect_node_to_p2d(n1, &c1); + /* Distance of each child from center of other node */ + for (i = 0; i < n2->i.num_nodes; i++) + { + n = n2->i.nodes[i]; + rect_node_to_p2d(n, &c); + n->d = distance2d_sqr_pt_pt(&c1, &c); + } + /* Sort the children by distance */ + n2->i.sorted = 1; + qsort(n2->i.nodes, + n2->i.num_nodes, + sizeof(RECT_NODE*), + rect_tree_node_sort_cmp); + } +} + +static double +rect_tree_distance_tree_recursive(RECT_NODE *n1, RECT_NODE *n2, RECT_TREE_DISTANCE_STATE *state) +{ + double min, max; + + /* Short circuit if we've already hit the minimum */ + if (state->min_dist < state->threshold || state->min_dist == 0.0) + return state->min_dist; + + /* If your minimum is greater than anyone's maximum, you can't hold the winner */ + min = rect_node_min_distance(n1, n2); + if (min > state->max_dist) + { + //lwnotice("pruning pair %p, %p", n1, n2); + LWDEBUGF(4, "pruning pair %p, %p", n1, n2); + return FLT_MAX; + } + + /* If your maximum is a new low, we'll use that as our new global tolerance */ + max = rect_node_max_distance(n1, n2); + if (max < state->max_dist) + state->max_dist = max; + + /* Both leaf nodes, do a real distance calculation */ + if (rect_node_is_leaf(n1) && rect_node_is_leaf(n2)) + { + return rect_leaf_node_distance(&n1->l, &n2->l, state); + } + /* Recurse into nodes */ + else + { + int i, j; + double d_min = FLT_MAX; + rect_tree_node_sort(n1, n2); + if (rect_node_is_leaf(n1) && !rect_node_is_leaf(n2)) + { + for (i = 0; i < n2->i.num_nodes; i++) + { + min = rect_tree_distance_tree_recursive(n1, n2->i.nodes[i], state); + d_min = FP_MIN(d_min, min); + } + } + else if (rect_node_is_leaf(n2) && !rect_node_is_leaf(n1)) + { + for (i = 0; i < n1->i.num_nodes; i++) + { + min = rect_tree_distance_tree_recursive(n1->i.nodes[i], n2, state); + d_min = FP_MIN(d_min, min); + } + } + else + { + for (i = 0; i < n1->i.num_nodes; i++) + { + for (j = 0; j < n2->i.num_nodes; j++) + { + min = rect_tree_distance_tree_recursive(n1->i.nodes[i], n2->i.nodes[j], state); + d_min = FP_MIN(d_min, min); + } + } + } + return d_min; + } +} + +double rect_tree_distance_tree(RECT_NODE *n1, RECT_NODE *n2, double threshold) +{ + double distance; + RECT_TREE_DISTANCE_STATE state; + + /* + * It is possible for an area to intersect another object + * without any edges intersecting, if the object is fully contained. + * If that is so, then any point in the object will be contained, + * so we do a quick point-in-poly test first for those cases + */ + if (rect_tree_is_area(n1) && + rect_tree_contains_point(n1, rect_tree_get_point(n2))) + { + return 0.0; + } + + if (rect_tree_is_area(n2) && + rect_tree_contains_point(n2, rect_tree_get_point(n1))) + { + return 0.0; + } + + state.threshold = threshold; + state.min_dist = FLT_MAX; + state.max_dist = FLT_MAX; + distance = rect_tree_distance_tree_recursive(n1, n2, &state); + // *p1 = state.p1; + // *p2 = state.p2; + return distance; +} diff --git a/mgist-postgis/liblwgeom/lwtree.h b/mgist-postgis/liblwgeom/lwtree.h new file mode 100644 index 0000000..0aa4cf3 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwtree.h @@ -0,0 +1,114 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2009-2012 Paul Ramsey + * + **********************************************************************/ + +#define RECT_NODE_SIZE 8 + +typedef enum +{ + RECT_NODE_INTERNAL_TYPE, + RECT_NODE_LEAF_TYPE +} RECT_NODE_TYPE; + +typedef enum +{ + RECT_NODE_RING_NONE = 0, + RECT_NODE_RING_EXTERIOR, + RECT_NODE_RING_INTERIOR +} RECT_NODE_RING_TYPE; + +typedef enum +{ + RECT_NODE_SEG_UNKNOWN = 0, + RECT_NODE_SEG_POINT, + RECT_NODE_SEG_LINEAR, + RECT_NODE_SEG_CIRCULAR +} RECT_NODE_SEG_TYPE; + +typedef struct +{ + const POINTARRAY *pa; + RECT_NODE_SEG_TYPE seg_type; + int seg_num; +} RECT_NODE_LEAF; + +struct rect_node; + +typedef struct +{ + int num_nodes; + RECT_NODE_RING_TYPE ring_type; + struct rect_node *nodes[RECT_NODE_SIZE]; + int sorted; +} RECT_NODE_INTERNAL; + +typedef struct rect_node +{ + RECT_NODE_TYPE type; + unsigned char geom_type; + double xmin; + double xmax; + double ymin; + double ymax; + double d; + union { + RECT_NODE_INTERNAL i; + RECT_NODE_LEAF l; + }; +} RECT_NODE; + +typedef struct rect_tree_distance_state +{ + double threshold; + double min_dist; + double max_dist; + POINT2D p1; + POINT2D p2; +} RECT_TREE_DISTANCE_STATE; + +/** +* Create a tree index on top an LWGEOM. Do not free the LWGEOM +* until the tree is freed. +*/ +RECT_NODE * rect_tree_from_lwgeom(const LWGEOM *geom); + +/** +* Test if two RECT_NODE trees intersect one another. +*/ +int rect_tree_intersects_tree(RECT_NODE *tree1, RECT_NODE *tree2); + +/** +* Return the distance between two RECT_NODE trees. +*/ +double rect_tree_distance_tree(RECT_NODE *n1, RECT_NODE *n2, double threshold); + +/** +* Free the rect-tree memory +*/ +void rect_tree_free(RECT_NODE *node); + +int rect_tree_contains_point(RECT_NODE *tree, const POINT2D *pt); +RECT_NODE * rect_tree_from_ptarray(const POINTARRAY *pa, int geom_type); +LWGEOM * rect_tree_to_lwgeom(const RECT_NODE *tree); +char * rect_tree_to_wkt(const RECT_NODE *node); +void rect_tree_printf(const RECT_NODE *node, int depth); diff --git a/mgist-postgis/liblwgeom/lwtriangle.c b/mgist-postgis/liblwgeom/lwtriangle.c new file mode 100644 index 0000000..adfa9c0 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwtriangle.c @@ -0,0 +1,216 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2010 - Oslandia + * + **********************************************************************/ + + +/* basic LWTRIANGLE manipulation */ + +#include +#include +#include +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + + + +/* construct a new LWTRIANGLE. + * use SRID=SRID_UNKNOWN for unknown SRID (will have 8bit type's S = 0) + */ +LWTRIANGLE * +lwtriangle_construct(int32_t srid, GBOX *bbox, POINTARRAY *points) +{ + LWTRIANGLE *result; + + result = (LWTRIANGLE*) lwalloc(sizeof(LWTRIANGLE)); + result->type = TRIANGLETYPE; + + result->flags = points->flags; + FLAGS_SET_BBOX(result->flags, bbox?1:0); + + result->srid = srid; + result->points = points; + result->bbox = bbox; + + return result; +} + +LWTRIANGLE * +lwtriangle_construct_empty(int32_t srid, char hasz, char hasm) +{ + LWTRIANGLE *result = lwalloc(sizeof(LWTRIANGLE)); + result->type = TRIANGLETYPE; + result->flags = lwflags(hasz,hasm,0); + result->srid = srid; + result->points = ptarray_construct_empty(hasz, hasm, 1); + result->bbox = NULL; + return result; +} + +void lwtriangle_free(LWTRIANGLE *triangle) +{ + if ( ! triangle ) return; + + if (triangle->bbox) + lwfree(triangle->bbox); + + if (triangle->points) + ptarray_free(triangle->points); + + lwfree(triangle); +} + +void printLWTRIANGLE(LWTRIANGLE *triangle) +{ + if (triangle->type != TRIANGLETYPE) + lwerror("printLWTRIANGLE called with something else than a Triangle"); + + lwnotice("LWTRIANGLE {"); + lwnotice(" ndims = %i", (int)FLAGS_NDIMS(triangle->flags)); + lwnotice(" SRID = %i", (int)triangle->srid); + printPA(triangle->points); + lwnotice("}"); +} + +/* @brief Clone LWTRIANGLE object. Serialized point lists are not copied. + * + * @see ptarray_clone + */ +LWTRIANGLE * +lwtriangle_clone(const LWTRIANGLE *g) +{ + LWDEBUGF(2, "lwtriangle_clone called with %p", g); + return (LWTRIANGLE *)lwline_clone((const LWLINE *)g); +} + +void +lwtriangle_force_clockwise(LWTRIANGLE *triangle) +{ + if ( ptarray_isccw(triangle->points) ) + ptarray_reverse_in_place(triangle->points); +} + +int +lwtriangle_is_clockwise(LWTRIANGLE *triangle) +{ + return !ptarray_isccw(triangle->points); +} + +void +lwtriangle_release(LWTRIANGLE *lwtriangle) +{ + lwgeom_release(lwtriangle_as_lwgeom(lwtriangle)); +} + +/* check coordinate equality */ +char +lwtriangle_same(const LWTRIANGLE *t1, const LWTRIANGLE *t2) +{ + char r = ptarray_same(t1->points, t2->points); + LWDEBUGF(5, "returning %d", r); + return r; +} + +static char +lwtriangle_is_repeated_points(LWTRIANGLE *triangle) +{ + char ret; + POINTARRAY *pa; + + pa = ptarray_remove_repeated_points(triangle->points, 0.0); + ret = ptarray_same(pa, triangle->points); + ptarray_free(pa); + + return ret; +} + +/* + * Construct a triangle from a LWLINE being + * the shell + * Pointarray from input geom is cloned. + * Input line must have 4 points, and be closed. + */ +LWTRIANGLE * +lwtriangle_from_lwline(const LWLINE *shell) +{ + LWTRIANGLE *ret; + POINTARRAY *pa; + + if ( shell->points->npoints != 4 ) + lwerror("lwtriangle_from_lwline: shell must have exactly 4 points"); + + if ( (!FLAGS_GET_Z(shell->flags) && !ptarray_is_closed_2d(shell->points)) || + (FLAGS_GET_Z(shell->flags) && !ptarray_is_closed_3d(shell->points)) ) + lwerror("lwtriangle_from_lwline: shell must be closed"); + + pa = ptarray_clone_deep(shell->points); + ret = lwtriangle_construct(shell->srid, NULL, pa); + + if (lwtriangle_is_repeated_points(ret)) + lwerror("lwtriangle_from_lwline: some points are repeated in triangle"); + + return ret; +} + +/** + * Find the area of the outer ring + */ +double +lwtriangle_area(const LWTRIANGLE *triangle) +{ + double area=0.0; + uint32_t i; + POINT2D p1; + POINT2D p2; + + if (! triangle->points->npoints) return area; /* empty triangle */ + + for (i=0; i < triangle->points->npoints-1; i++) + { + getPoint2d_p(triangle->points, i, &p1); + getPoint2d_p(triangle->points, i+1, &p2); + area += ( p1.x * p2.y ) - ( p1.y * p2.x ); + } + + area /= 2.0; + + return fabs(area); +} + + +double +lwtriangle_perimeter(const LWTRIANGLE *triangle) +{ + if( triangle->points ) + return ptarray_length(triangle->points); + else + return 0.0; +} + +double +lwtriangle_perimeter_2d(const LWTRIANGLE *triangle) +{ + if( triangle->points ) + return ptarray_length_2d(triangle->points); + else + return 0.0; +} diff --git a/mgist-postgis/liblwgeom/lwunionfind.c b/mgist-postgis/liblwgeom/lwunionfind.c new file mode 100644 index 0000000..854a9e8 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwunionfind.c @@ -0,0 +1,213 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2015 Daniel Baston + * + **********************************************************************/ + + +#include "liblwgeom.h" +#include "lwunionfind.h" +#include +#include + +static int cmp_int(const void *a, const void *b); +static int cmp_int_ptr(const void *a, const void *b); + +UNIONFIND* +UF_create(uint32_t N) +{ + size_t i; + UNIONFIND* uf = lwalloc(sizeof(UNIONFIND)); + uf->N = N; + uf->num_clusters = N; + uf->clusters = lwalloc(N * sizeof(uint32_t)); + uf->cluster_sizes = lwalloc(N * sizeof(uint32_t)); + + for (i = 0; i < N; i++) + { + uf->clusters[i] = i; + uf->cluster_sizes[i] = 1; + } + + return uf; +} + +void +UF_destroy(UNIONFIND* uf) +{ + lwfree(uf->clusters); + lwfree(uf->cluster_sizes); + lwfree(uf); +} + +uint32_t +UF_find (UNIONFIND* uf, uint32_t i) +{ + uint32_t base = i; + while (uf->clusters[base] != base) { + base = uf->clusters[base]; + } + + while (i != base) { + uint32_t next = uf->clusters[i]; + uf->clusters[i] = base; + i = next; + } + + return i; +} + +uint32_t +UF_size (UNIONFIND* uf, uint32_t i) +{ + return uf->cluster_sizes[UF_find(uf, i)]; +} + +void +UF_union(UNIONFIND* uf, uint32_t i, uint32_t j) +{ + uint32_t a = UF_find(uf, i); + uint32_t b = UF_find(uf, j); + + if (a == b) + { + return; + } + + if (uf->cluster_sizes[a] < uf->cluster_sizes[b] || + (uf->cluster_sizes[a] == uf->cluster_sizes[b] && a > b)) + { + uf->clusters[a] = uf->clusters[b]; + uf->cluster_sizes[b] += uf->cluster_sizes[a]; + uf->cluster_sizes[a] = 0; + } + else + { + uf->clusters[b] = uf->clusters[a]; + uf->cluster_sizes[a] += uf->cluster_sizes[b]; + uf->cluster_sizes[b] = 0; + } + + uf->num_clusters--; +} + +uint32_t* +UF_ordered_by_cluster(UNIONFIND* uf) +{ + size_t i; + uint32_t** cluster_id_ptr_by_elem_id = lwalloc(uf->N * sizeof (uint32_t*)); + uint32_t* ordered_ids = lwalloc(uf->N * sizeof (uint32_t)); + + for (i = 0; i < uf->N; i++) + { + /* Make sure each value in uf->clusters is pointing to the + * root of the cluster. + * */ + UF_find(uf, i); + cluster_id_ptr_by_elem_id[i] = &(uf->clusters[i]); + } + + /* Sort the array of cluster id pointers, so that pointers to the + * same cluster id are grouped together. + * */ + qsort(cluster_id_ptr_by_elem_id, uf->N, sizeof (uint32_t*), &cmp_int_ptr); + + /* Recover the input element ids from the cluster id pointers, so + * we can return element ids grouped by cluster id. + * */ + for (i = 0; i < uf-> N; i++) + { + ordered_ids[i] = (cluster_id_ptr_by_elem_id[i] - uf->clusters); + } + + lwfree(cluster_id_ptr_by_elem_id); + return ordered_ids; +} + +uint32_t* +UF_get_collapsed_cluster_ids(UNIONFIND* uf, const char* is_in_cluster) +{ + uint32_t* ordered_components = UF_ordered_by_cluster(uf); + uint32_t* new_ids = lwalloc(uf->N * sizeof(uint32_t)); + uint32_t last_old_id, current_new_id, i; + char encountered_cluster = LW_FALSE; + + current_new_id = 0; last_old_id = 0; + for (i = 0; i < uf->N; i++) + { + uint32_t j = ordered_components[i]; + if (!is_in_cluster || is_in_cluster[j]) + { + uint32_t current_old_id = UF_find(uf, j); + if (!encountered_cluster) + { + encountered_cluster = LW_TRUE; + last_old_id = current_old_id; + } + + if (current_old_id != last_old_id) + current_new_id++; + + new_ids[j] = current_new_id; + last_old_id = current_old_id; + } + } + + lwfree(ordered_components); + + return new_ids; +} + +static int +cmp_int(const void *a, const void *b) +{ + if (*((uint32_t*) a) > *((uint32_t*) b)) + { + return 1; + } + else if (*((uint32_t*) a) < *((uint32_t*) b)) + { + return -1; + } + else + { + return 0; + } +} + +static int +cmp_int_ptr(const void *a, const void *b) +{ + int val_cmp = cmp_int(*((uint32_t**) a), *((uint32_t**) b)); + if (val_cmp != 0) + { + return val_cmp; + } + if (a > b) + { + return 1; + } + if (a < b) + { + return -1; + } + return 0; +} diff --git a/mgist-postgis/liblwgeom/lwunionfind.h b/mgist-postgis/liblwgeom/lwunionfind.h new file mode 100644 index 0000000..34b2efd --- /dev/null +++ b/mgist-postgis/liblwgeom/lwunionfind.h @@ -0,0 +1,64 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2015 Daniel Baston + * + **********************************************************************/ + + +#ifndef _LWUNIONFIND +#define _LWUNIONFIND 1 + +#include "liblwgeom.h" + +typedef struct +{ + uint32_t* clusters; + uint32_t* cluster_sizes; + uint32_t num_clusters; + uint32_t N; +} UNIONFIND; + +/* Allocate a UNIONFIND structure of capacity N */ +UNIONFIND* UF_create(uint32_t N); + +/* Release memory associated with UNIONFIND structure */ +void UF_destroy(UNIONFIND* uf); + +/* Identify the cluster id associated with specified component id */ +uint32_t UF_find(UNIONFIND* uf, uint32_t i); + +/* Get the size of the cluster associated with the specified component id */ +uint32_t UF_size(UNIONFIND* uf, uint32_t i); + +/* Merge the clusters that contain the two specified component ids */ +void UF_union(UNIONFIND* uf, uint32_t i, uint32_t j); + +/* Return an array of component ids, where components that are in the + * same cluster are contiguous in the array */ +uint32_t* UF_ordered_by_cluster(UNIONFIND* uf); + +/* Replace the cluster ids in a UNIONFIND with sequential ids starting at one. + * If is_in_cluster array is provided, it will be used to skip any indexes + * that are not in a cluster. + * */ +uint32_t* UF_get_collapsed_cluster_ids(UNIONFIND* uf, const char* is_in_cluster); + +#endif diff --git a/mgist-postgis/liblwgeom/lwutil.c b/mgist-postgis/liblwgeom/lwutil.c new file mode 100644 index 0000000..756f4c3 --- /dev/null +++ b/mgist-postgis/liblwgeom/lwutil.c @@ -0,0 +1,559 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2004-2015 Sandro Santilli + * Copyright (C) 2006 Mark Leslie + * Copyright (C) 2008-2009 Mark Cave-Ayland + * Copyright (C) 2009-2015 Paul Ramsey + * Copyright (C) 2010 Olivier Courtin + * + **********************************************************************/ + +#include +#include +#include +#include +#include /* for tolower */ + +/* Global variables */ +// #include "../postgis_config.h" +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + +/* Default allocators */ +static void * default_allocator(size_t size); +static void default_freeor(void *mem); +static void * default_reallocator(void *mem, size_t size); +lwallocator lwalloc_var = default_allocator; +lwreallocator lwrealloc_var = default_reallocator; +lwfreeor lwfree_var = default_freeor; + +/* Default reporters */ +static void default_noticereporter(const char *fmt, va_list ap); +static void default_errorreporter(const char *fmt, va_list ap); +lwreporter lwnotice_var = default_noticereporter; +lwreporter lwerror_var = default_errorreporter; + +/* Default logger */ +static void default_debuglogger(int level, const char *fmt, va_list ap); +lwdebuglogger lwdebug_var = default_debuglogger; + +#define LW_MSG_MAXLEN 256 + +static char *lwgeomTypeName[] = +{ + "Unknown", + "Point", + "LineString", + "Polygon", + "MultiPoint", + "MultiLineString", + "MultiPolygon", + "GeometryCollection", + "CircularString", + "CompoundCurve", + "CurvePolygon", + "MultiCurve", + "MultiSurface", + "PolyhedralSurface", + "Triangle", + "Tin" +}; + +/* + * Default allocators + * + * We include some default allocators that use malloc/free/realloc + * along with stdout/stderr since this is the most common use case + * + */ + +static void * +default_allocator(size_t size) +{ + void *mem = malloc(size); + return mem; +} + +static void +default_freeor(void *mem) +{ + free(mem); +} + +static void * +default_reallocator(void *mem, size_t size) +{ + void *ret = realloc(mem, size); + return ret; +} + +/* + * Default lwnotice/lwerror handlers + * + * Since variadic functions cannot pass their parameters directly, we need + * wrappers for these functions to convert the arguments into a va_list + * structure. + */ + +static void +default_noticereporter(const char *fmt, va_list ap) +{ + char msg[LW_MSG_MAXLEN+1]; + vsnprintf (msg, LW_MSG_MAXLEN, fmt, ap); + msg[LW_MSG_MAXLEN]='\0'; + fprintf(stderr, "%s\n", msg); +} + +static void +default_debuglogger(int level, const char *fmt, va_list ap) +{ + char msg[LW_MSG_MAXLEN+1]; + if ( POSTGIS_DEBUG_LEVEL >= level ) + { + /* Space pad the debug output */ + int i; + for ( i = 0; i < level; i++ ) + msg[i] = ' '; + vsnprintf(msg+i, LW_MSG_MAXLEN-i, fmt, ap); + msg[LW_MSG_MAXLEN]='\0'; + fprintf(stderr, "%s\n", msg); + } +} + +static void +default_errorreporter(const char *fmt, va_list ap) +{ + char msg[LW_MSG_MAXLEN+1]; + vsnprintf (msg, LW_MSG_MAXLEN, fmt, ap); + msg[LW_MSG_MAXLEN]='\0'; + fprintf(stderr, "%s\n", msg); + exit(1); +} + +/** + * This function is called by programs which want to set up custom handling + * for memory management and error reporting + * + * Only non-NULL values change their respective handler + */ +void +lwgeom_set_handlers(lwallocator allocator, lwreallocator reallocator, + lwfreeor freeor, lwreporter errorreporter, + lwreporter noticereporter) { + + if ( allocator ) lwalloc_var = allocator; + if ( reallocator ) lwrealloc_var = reallocator; + if ( freeor ) lwfree_var = freeor; + + if ( errorreporter ) lwerror_var = errorreporter; + if ( noticereporter ) lwnotice_var = noticereporter; +} + +void +lwgeom_set_debuglogger(lwdebuglogger debuglogger) { + + if ( debuglogger ) lwdebug_var = debuglogger; +} + +void +lwnotice(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + + /* Call the supplied function */ + (*lwnotice_var)(fmt, ap); + + va_end(ap); +} + +void +lwerror(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + + /* Call the supplied function */ + (*lwerror_var)(fmt, ap); + + va_end(ap); +} + +void +lwdebug(int level, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + + /* Call the supplied function */ + (*lwdebug_var)(level, fmt, ap); + + va_end(ap); +} + +const char* +lwtype_name(uint8_t type) +{ + if ( type > 15 ) + { + /* assert(0); */ + return "Invalid type"; + } + return lwgeomTypeName[(int ) type]; +} + +void * +lwalloc(size_t size) +{ + void *mem = lwalloc_var(size); + LWDEBUGF(5, "lwalloc: %d@%p", size, mem); + return mem; +} + +void * +lwrealloc(void *mem, size_t size) +{ + LWDEBUGF(5, "lwrealloc: %d@%p", size, mem); + return lwrealloc_var(mem, size); +} + +void +lwfree(void *mem) +{ + lwfree_var(mem); +} + +char * +lwstrdup(const char* a) +{ + size_t l = strlen(a)+1; + char *b = lwalloc(l); + strncpy(b, a, l); + return b; +} + +/* + * Returns a new string which contains a maximum of maxlength characters starting + * from startpos and finishing at endpos (0-based indexing). If the string is + * truncated then the first or last characters are replaced by "..." as + * appropriate. + * + * The caller should specify start or end truncation by setting the truncdirection + * parameter as follows: + * 0 - start truncation (i.e. characters are removed from the beginning) + * 1 - end truncation (i.e. characters are removed from the end) + */ + +char *lwmessage_truncate(char *str, int startpos, int endpos, int maxlength, int truncdirection) +{ + char *output; + char *outstart; + + /* Allocate space for new string */ + output = lwalloc(maxlength + 4); + output[0] = '\0'; + + /* Start truncation */ + if (truncdirection == 0) + { + /* Calculate the start position */ + if (endpos - startpos < maxlength) + { + outstart = str + startpos; + strncat(output, outstart, endpos - startpos + 1); + } + else + { + if (maxlength >= 3) + { + /* Add "..." prefix */ + outstart = str + endpos + 1 - maxlength + 3; + strncat(output, "...", 4); + strncat(output, outstart, maxlength - 3); + } + else + { + /* maxlength is too small; just output "..." */ + strncat(output, "...", 4); + } + } + } + + /* End truncation */ + if (truncdirection == 1) + { + /* Calculate the end position */ + if (endpos - startpos < maxlength) + { + outstart = str + startpos; + strncat(output, outstart, endpos - startpos + 1); + } + else + { + if (maxlength >= 3) + { + /* Add "..." suffix */ + outstart = str + startpos; + strncat(output, outstart, maxlength - 3); + strncat(output, "...", 4); + } + else + { + /* maxlength is too small; just output "..." */ + strncat(output, "...", 4); + } + } + } + + return output; +} + +int32_t +clamp_srid(int32_t srid) +{ + int newsrid = srid; + + if ( newsrid <= 0 ) { + if ( newsrid != SRID_UNKNOWN ) { + newsrid = SRID_UNKNOWN; + lwnotice("SRID value %d converted to the officially unknown SRID value %d", srid, newsrid); + } + } else if ( srid > SRID_MAXIMUM ) { + newsrid = SRID_USER_MAXIMUM + 1 + + /* -1 is to reduce likelyhood of clashes */ + /* NOTE: must match implementation in postgis_restore.pl */ + ( srid % ( SRID_MAXIMUM - SRID_USER_MAXIMUM - 1 ) ); + lwnotice("SRID value %d > SRID_MAXIMUM converted to %d", srid, newsrid); + } + + return newsrid; +} + + + + +/* Structure for the type array */ +struct geomtype_struct +{ + char *typename; + int type; + int z; + int m; +}; + +/* Type array. Note that the order of this array is important in + that any typename in the list must *NOT* occur within an entry + before it. Otherwise if we search for "POINT" at the top of the + list we would also match MULTIPOINT, for example. */ + +struct geomtype_struct geomtype_struct_array[] = +{ + { "GEOMETRYCOLLECTIONZM", COLLECTIONTYPE, 1, 1 }, + { "GEOMETRYCOLLECTIONZ", COLLECTIONTYPE, 1, 0 }, + { "GEOMETRYCOLLECTIONM", COLLECTIONTYPE, 0, 1 }, + { "GEOMETRYCOLLECTION", COLLECTIONTYPE, 0, 0 }, + + { "GEOMETRYZM", 0, 1, 1 }, + { "GEOMETRYZ", 0, 1, 0 }, + { "GEOMETRYM", 0, 0, 1 }, + { "GEOMETRY", 0, 0, 0 }, + + { "POLYHEDRALSURFACEZM", POLYHEDRALSURFACETYPE, 1, 1 }, + { "POLYHEDRALSURFACEZ", POLYHEDRALSURFACETYPE, 1, 0 }, + { "POLYHEDRALSURFACEM", POLYHEDRALSURFACETYPE, 0, 1 }, + { "POLYHEDRALSURFACE", POLYHEDRALSURFACETYPE, 0, 0 }, + + { "TINZM", TINTYPE, 1, 1 }, + { "TINZ", TINTYPE, 1, 0 }, + { "TINM", TINTYPE, 0, 1 }, + { "TIN", TINTYPE, 0, 0 }, + + { "CIRCULARSTRINGZM", CIRCSTRINGTYPE, 1, 1 }, + { "CIRCULARSTRINGZ", CIRCSTRINGTYPE, 1, 0 }, + { "CIRCULARSTRINGM", CIRCSTRINGTYPE, 0, 1 }, + { "CIRCULARSTRING", CIRCSTRINGTYPE, 0, 0 }, + + { "COMPOUNDCURVEZM", COMPOUNDTYPE, 1, 1 }, + { "COMPOUNDCURVEZ", COMPOUNDTYPE, 1, 0 }, + { "COMPOUNDCURVEM", COMPOUNDTYPE, 0, 1 }, + { "COMPOUNDCURVE", COMPOUNDTYPE, 0, 0 }, + + { "CURVEPOLYGONZM", CURVEPOLYTYPE, 1, 1 }, + { "CURVEPOLYGONZ", CURVEPOLYTYPE, 1, 0 }, + { "CURVEPOLYGONM", CURVEPOLYTYPE, 0, 1 }, + { "CURVEPOLYGON", CURVEPOLYTYPE, 0, 0 }, + + { "MULTICURVEZM", MULTICURVETYPE, 1, 1 }, + { "MULTICURVEZ", MULTICURVETYPE, 1, 0 }, + { "MULTICURVEM", MULTICURVETYPE, 0, 1 }, + { "MULTICURVE", MULTICURVETYPE, 0, 0 }, + + { "MULTISURFACEZM", MULTISURFACETYPE, 1, 1 }, + { "MULTISURFACEZ", MULTISURFACETYPE, 1, 0 }, + { "MULTISURFACEM", MULTISURFACETYPE, 0, 1 }, + { "MULTISURFACE", MULTISURFACETYPE, 0, 0 }, + + { "MULTILINESTRINGZM", MULTILINETYPE, 1, 1 }, + { "MULTILINESTRINGZ", MULTILINETYPE, 1, 0 }, + { "MULTILINESTRINGM", MULTILINETYPE, 0, 1 }, + { "MULTILINESTRING", MULTILINETYPE, 0, 0 }, + + { "MULTIPOLYGONZM", MULTIPOLYGONTYPE, 1, 1 }, + { "MULTIPOLYGONZ", MULTIPOLYGONTYPE, 1, 0 }, + { "MULTIPOLYGONM", MULTIPOLYGONTYPE, 0, 1 }, + { "MULTIPOLYGON", MULTIPOLYGONTYPE, 0, 0 }, + + { "MULTIPOINTZM", MULTIPOINTTYPE, 1, 1 }, + { "MULTIPOINTZ", MULTIPOINTTYPE, 1, 0 }, + { "MULTIPOINTM", MULTIPOINTTYPE, 0, 1 }, + { "MULTIPOINT", MULTIPOINTTYPE, 0, 0 }, + + { "LINESTRINGZM", LINETYPE, 1, 1 }, + { "LINESTRINGZ", LINETYPE, 1, 0 }, + { "LINESTRINGM", LINETYPE, 0, 1 }, + { "LINESTRING", LINETYPE, 0, 0 }, + + { "TRIANGLEZM", TRIANGLETYPE, 1, 1 }, + { "TRIANGLEZ", TRIANGLETYPE, 1, 0 }, + { "TRIANGLEM", TRIANGLETYPE, 0, 1 }, + { "TRIANGLE", TRIANGLETYPE, 0, 0 }, + + { "POLYGONZM", POLYGONTYPE, 1, 1 }, + { "POLYGONZ", POLYGONTYPE, 1, 0 }, + { "POLYGONM", POLYGONTYPE, 0, 1 }, + { "POLYGON", POLYGONTYPE, 0, 0 }, + + { "POINTZM", POINTTYPE, 1, 1 }, + { "POINTZ", POINTTYPE, 1, 0 }, + { "POINTM", POINTTYPE, 0, 1 }, + { "POINT", POINTTYPE, 0, 0 } + +}; +#define GEOMTYPE_STRUCT_ARRAY_LEN (sizeof geomtype_struct_array/sizeof(struct geomtype_struct)) + +/* +* We use a very simple upper case mapper here, because the system toupper() function +* is locale dependent and may have trouble mapping lower case strings to the upper +* case ones we expect (see, the "Turkisk I", http://www.i18nguy.com/unicode/turkish-i18n.html) +* We could also count on PgSQL sending us *lower* case inputs, as it seems to do that +* regardless of the case the user provides for the type arguments. +*/ +const char dumb_upper_map[128] = "................................................0123456789.......ABCDEFGHIJKLMNOPQRSTUVWXYZ......ABCDEFGHIJKLMNOPQRSTUVWXYZ....."; + +static char dumb_toupper(int in) +{ + if ( in < 0 || in > 127 ) + return '.'; + return dumb_upper_map[in]; +} + +lwflags_t lwflags(int hasz, int hasm, int geodetic) +{ + lwflags_t flags = 0; + if (hasz) + FLAGS_SET_Z(flags, 1); + if (hasm) + FLAGS_SET_M(flags, 1); + if (geodetic) + FLAGS_SET_GEODETIC(flags, 1); + return flags; +} + +/** +* Calculate type integer and dimensional flags from string input. +* Case insensitive, and insensitive to spaces at front and back. +* Type == 0 in the case of the string "GEOMETRY" or "GEOGRAPHY". +* Return LW_SUCCESS for success. +*/ +int geometry_type_from_string(const char *str, uint8_t *type, int *z, int *m) +{ + char *tmpstr; + size_t tmpstartpos, tmpendpos; + size_t i; + + assert(str); + assert(type); + assert(z); + assert(m); + + /* Initialize. */ + *type = 0; + *z = 0; + *m = 0; + + /* Locate any leading/trailing spaces */ + tmpstartpos = 0; + for (i = 0; i < strlen(str); i++) + { + if (str[i] != ' ') + { + tmpstartpos = i; + break; + } + } + + tmpendpos = strlen(str) - 1; + for (i = strlen(str) - 1; i != 0; i--) + { + if (str[i] != ' ') + { + tmpendpos = i; + break; + } + } + + /* Copy and convert to upper case for comparison */ + tmpstr = lwalloc(tmpendpos - tmpstartpos + 2); + for (i = tmpstartpos; i <= tmpendpos; i++) + tmpstr[i - tmpstartpos] = dumb_toupper(str[i]); + + /* Add NULL to terminate */ + tmpstr[i - tmpstartpos] = '\0'; + + /* Now check for the type */ + for (i = 0; i < GEOMTYPE_STRUCT_ARRAY_LEN; i++) + { + if (!strcmp(tmpstr, geomtype_struct_array[i].typename)) + { + *type = geomtype_struct_array[i].type; + *z = geomtype_struct_array[i].z; + *m = geomtype_struct_array[i].m; + + lwfree(tmpstr); + + return LW_SUCCESS; + } + + } + + lwfree(tmpstr); + + return LW_FAILURE; +} + + + + + + diff --git a/mgist-postgis/liblwgeom/measures.c b/mgist-postgis/liblwgeom/measures.c new file mode 100644 index 0000000..7331704 --- /dev/null +++ b/mgist-postgis/liblwgeom/measures.c @@ -0,0 +1,2513 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2001-2006 Refractions Research Inc. + * Copyright 2010 Nicklas Avén + * Copyright 2012 Paul Ramsey + * Copyright 2019 Darafei Praliaskouski + * + **********************************************************************/ + +#include +#include + +#include "measures.h" +#include "lwgeom_log.h" + +/*------------------------------------------------------------------------------------------------------------ +Initializing functions +The functions starting the distance-calculation processses +--------------------------------------------------------------------------------------------------------------*/ + +LWGEOM * +lwgeom_closest_line(const LWGEOM *lw1, const LWGEOM *lw2) +{ + return lw_dist2d_distanceline(lw1, lw2, lw1->srid, DIST_MIN); +} + +LWGEOM * +lwgeom_furthest_line(const LWGEOM *lw1, const LWGEOM *lw2) +{ + return lw_dist2d_distanceline(lw1, lw2, lw1->srid, DIST_MAX); +} + +LWGEOM * +lwgeom_closest_point(const LWGEOM *lw1, const LWGEOM *lw2) +{ + return lw_dist2d_distancepoint(lw1, lw2, lw1->srid, DIST_MIN); +} + +LWGEOM * +lwgeom_furthest_point(const LWGEOM *lw1, const LWGEOM *lw2) +{ + return lw_dist2d_distancepoint(lw1, lw2, lw1->srid, DIST_MAX); +} + +void +lw_dist2d_distpts_init(DISTPTS *dl, int mode) +{ + dl->twisted = -1; + dl->p1.x = dl->p1.y = 0.0; + dl->p2.x = dl->p2.y = 0.0; + dl->mode = mode; + dl->tolerance = 0.0; + if (mode == DIST_MIN) + dl->distance = FLT_MAX; + else + dl->distance = -1 * FLT_MAX; +} + +/** +Function initializing shortestline and longestline calculations. +*/ +LWGEOM * +lw_dist2d_distanceline(const LWGEOM *lw1, const LWGEOM *lw2, int32_t srid, int mode) +{ + double x1, x2, y1, y2; + + double initdistance = (mode == DIST_MIN ? FLT_MAX : -1.0); + DISTPTS thedl; + LWPOINT *lwpoints[2]; + LWGEOM *result; + + thedl.mode = mode; + thedl.distance = initdistance; + thedl.tolerance = 0.0; + + LWDEBUG(2, "lw_dist2d_distanceline is called"); + + if (!lw_dist2d_comp(lw1, lw2, &thedl)) + { + /*should never get here. all cases ought to be error handled earlier*/ + lwerror("Some unspecified error."); + result = (LWGEOM *)lwcollection_construct_empty(COLLECTIONTYPE, srid, 0, 0); + } + + /*if thedl.distance is unchanged there where only empty geometries input*/ + if (thedl.distance == initdistance) + { + LWDEBUG(3, "didn't find geometries to measure between, returning null"); + result = (LWGEOM *)lwcollection_construct_empty(COLLECTIONTYPE, srid, 0, 0); + } + else + { + x1 = thedl.p1.x; + y1 = thedl.p1.y; + x2 = thedl.p2.x; + y2 = thedl.p2.y; + + lwpoints[0] = lwpoint_make2d(srid, x1, y1); + lwpoints[1] = lwpoint_make2d(srid, x2, y2); + + result = (LWGEOM *)lwline_from_ptarray(srid, 2, lwpoints); + } + return result; +} + +/** +Function initializing closestpoint calculations. +*/ +LWGEOM * +lw_dist2d_distancepoint(const LWGEOM *lw1, const LWGEOM *lw2, int32_t srid, int mode) +{ + double x, y; + DISTPTS thedl; + double initdistance = FLT_MAX; + LWGEOM *result; + + thedl.mode = mode; + thedl.distance = initdistance; + thedl.tolerance = 0; + + LWDEBUG(2, "lw_dist2d_distancepoint is called"); + + if (!lw_dist2d_comp(lw1, lw2, &thedl)) + { + /*should never get here. all cases ought to be error handled earlier*/ + lwerror("Some unspecified error."); + result = (LWGEOM *)lwcollection_construct_empty(COLLECTIONTYPE, srid, 0, 0); + } + if (thedl.distance == initdistance) + { + LWDEBUG(3, "didn't find geometries to measure between, returning null"); + result = (LWGEOM *)lwcollection_construct_empty(COLLECTIONTYPE, srid, 0, 0); + } + else + { + x = thedl.p1.x; + y = thedl.p1.y; + result = (LWGEOM *)lwpoint_make2d(srid, x, y); + } + return result; +} + +/** +Function initializing max distance calculation +*/ +double +lwgeom_maxdistance2d(const LWGEOM *lw1, const LWGEOM *lw2) +{ + LWDEBUG(2, "lwgeom_maxdistance2d is called"); + + return lwgeom_maxdistance2d_tolerance(lw1, lw2, 0.0); +} + +/** +Function handling max distance calculations and dfullywithin calculations. +The difference is just the tolerance. +*/ +double +lwgeom_maxdistance2d_tolerance(const LWGEOM *lw1, const LWGEOM *lw2, double tolerance) +{ + /*double thedist;*/ + DISTPTS thedl; + LWDEBUG(2, "lwgeom_maxdistance2d_tolerance is called"); + thedl.mode = DIST_MAX; + thedl.distance = -1; + thedl.tolerance = tolerance; + if (lw_dist2d_comp(lw1, lw2, &thedl)) + return thedl.distance; + + /*should never get here. all cases ought to be error handled earlier*/ + lwerror("Some unspecified error."); + return -1; +} + +/** + Function initializing min distance calculation +*/ +double +lwgeom_mindistance2d(const LWGEOM *lw1, const LWGEOM *lw2) +{ + return lwgeom_mindistance2d_tolerance(lw1, lw2, 0.0); +} + +/** + Function handling min distance calculations and dwithin calculations. + The difference is just the tolerance. +*/ +double +lwgeom_mindistance2d_tolerance(const LWGEOM *lw1, const LWGEOM *lw2, double tolerance) +{ + DISTPTS thedl; + LWDEBUG(2, "lwgeom_mindistance2d_tolerance is called"); + thedl.mode = DIST_MIN; + thedl.distance = FLT_MAX; + thedl.tolerance = tolerance; + if (lw_dist2d_comp(lw1, lw2, &thedl)) + return thedl.distance; + /*should never get here. all cases ought to be error handled earlier*/ + lwerror("Some unspecified error."); + return FLT_MAX; +} + +/*------------------------------------------------------------------------------------------------------------ +End of Initializing functions +--------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------ +Preprocessing functions +Functions preparing geometries for distance-calculations +--------------------------------------------------------------------------------------------------------------*/ + +/** + This function just deserializes geometries + Bboxes is not checked here since it is the subgeometries + bboxes we will use anyway. +*/ +int +lw_dist2d_comp(const LWGEOM *lw1, const LWGEOM *lw2, DISTPTS *dl) +{ + return lw_dist2d_recursive(lw1, lw2, dl); +} + +static int +lw_dist2d_is_collection(const LWGEOM *g) +{ + /* Differs from lwgeom_is_collection by not treating CURVEPOLYGON as collection */ + switch (g->type) + { + case TINTYPE: + case MULTIPOINTTYPE: + case MULTILINETYPE: + case MULTIPOLYGONTYPE: + case COLLECTIONTYPE: + case MULTICURVETYPE: + case MULTISURFACETYPE: + case COMPOUNDTYPE: + case POLYHEDRALSURFACETYPE: + return LW_TRUE; + break; + + default: + return LW_FALSE; + } +} + +/** +This is a recursive function delivering every possible combination of subgeometries +*/ +int +lw_dist2d_recursive(const LWGEOM *lwg1, const LWGEOM *lwg2, DISTPTS *dl) +{ + int i, j; + int n1 = 1; + int n2 = 1; + LWGEOM *g1 = NULL; + LWGEOM *g2 = NULL; + LWCOLLECTION *c1 = NULL; + LWCOLLECTION *c2 = NULL; + + LWDEBUGF(2, "lw_dist2d_comp is called with type1=%d, type2=%d", lwg1->type, lwg2->type); + + if (lw_dist2d_is_collection(lwg1)) + { + LWDEBUG(3, "First geometry is collection"); + c1 = lwgeom_as_lwcollection(lwg1); + n1 = c1->ngeoms; + } + if (lw_dist2d_is_collection(lwg2)) + { + LWDEBUG(3, "Second geometry is collection"); + c2 = lwgeom_as_lwcollection(lwg2); + n2 = c2->ngeoms; + } + + for (i = 0; i < n1; i++) + { + + if (lw_dist2d_is_collection(lwg1)) + g1 = c1->geoms[i]; + else + g1 = (LWGEOM *)lwg1; + + if (lwgeom_is_empty(g1)) + continue; + + if (lw_dist2d_is_collection(g1)) + { + LWDEBUG(3, "Found collection inside first geometry collection, recursing"); + if (!lw_dist2d_recursive(g1, lwg2, dl)) + return LW_FALSE; + continue; + } + for (j = 0; j < n2; j++) + { + if (lw_dist2d_is_collection(lwg2)) + g2 = c2->geoms[j]; + else + g2 = (LWGEOM *)lwg2; + + if (lw_dist2d_is_collection(g2)) + { + LWDEBUG(3, "Found collection inside second geometry collection, recursing"); + if (!lw_dist2d_recursive(g1, g2, dl)) + return LW_FALSE; + continue; + } + + if (!g1->bbox) + lwgeom_add_bbox(g1); + + if (!g2->bbox) + lwgeom_add_bbox(g2); + + /* If one of geometries is empty, skip */ + if (lwgeom_is_empty(g1) || lwgeom_is_empty(g2)) + continue; + + if ((dl->mode != DIST_MAX) && (!lw_dist2d_check_overlap(g1, g2)) && + (g1->type == LINETYPE || g1->type == POLYGONTYPE || g1->type == TRIANGLETYPE) && + (g2->type == LINETYPE || g2->type == POLYGONTYPE || g2->type == TRIANGLETYPE)) + { + if (!lw_dist2d_distribute_fast(g1, g2, dl)) + return LW_FALSE; + } + else + { + if (!lw_dist2d_distribute_bruteforce(g1, g2, dl)) + return LW_FALSE; + if (dl->distance <= dl->tolerance && dl->mode == DIST_MIN) + return LW_TRUE; /*just a check if the answer is already given*/ + } + } + } + return LW_TRUE; +} + +int +lw_dist2d_distribute_bruteforce(const LWGEOM *lwg1, const LWGEOM *lwg2, DISTPTS *dl) +{ + + int t1 = lwg1->type; + int t2 = lwg2->type; + + switch (t1) + { + case POINTTYPE: + { + dl->twisted = 1; + switch (t2) + { + case POINTTYPE: + return lw_dist2d_point_point((LWPOINT *)lwg1, (LWPOINT *)lwg2, dl); + case LINETYPE: + return lw_dist2d_point_line((LWPOINT *)lwg1, (LWLINE *)lwg2, dl); + case TRIANGLETYPE: + return lw_dist2d_point_tri((LWPOINT *)lwg1, (LWTRIANGLE *)lwg2, dl); + case POLYGONTYPE: + return lw_dist2d_point_poly((LWPOINT *)lwg1, (LWPOLY *)lwg2, dl); + case CIRCSTRINGTYPE: + return lw_dist2d_point_circstring((LWPOINT *)lwg1, (LWCIRCSTRING *)lwg2, dl); + case CURVEPOLYTYPE: + return lw_dist2d_point_curvepoly((LWPOINT *)lwg1, (LWCURVEPOLY *)lwg2, dl); + default: + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(t2)); + return LW_FALSE; + } + } + case LINETYPE: + { + dl->twisted = 1; + switch (t2) + { + case POINTTYPE: + dl->twisted = -1; + return lw_dist2d_point_line((LWPOINT *)lwg2, (LWLINE *)lwg1, dl); + case LINETYPE: + return lw_dist2d_line_line((LWLINE *)lwg1, (LWLINE *)lwg2, dl); + case TRIANGLETYPE: + return lw_dist2d_line_tri((LWLINE *)lwg1, (LWTRIANGLE *)lwg2, dl); + case POLYGONTYPE: + return lw_dist2d_line_poly((LWLINE *)lwg1, (LWPOLY *)lwg2, dl); + case CIRCSTRINGTYPE: + return lw_dist2d_line_circstring((LWLINE *)lwg1, (LWCIRCSTRING *)lwg2, dl); + case CURVEPOLYTYPE: + return lw_dist2d_line_curvepoly((LWLINE *)lwg1, (LWCURVEPOLY *)lwg2, dl); + default: + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(t2)); + return LW_FALSE; + } + } + case TRIANGLETYPE: + { + dl->twisted = 1; + switch (t2) + { + case POINTTYPE: + dl->twisted = -1; + return lw_dist2d_point_tri((LWPOINT *)lwg2, (LWTRIANGLE *)lwg1, dl); + case LINETYPE: + dl->twisted = -1; + return lw_dist2d_line_tri((LWLINE *)lwg2, (LWTRIANGLE *)lwg1, dl); + case TRIANGLETYPE: + return lw_dist2d_tri_tri((LWTRIANGLE *)lwg1, (LWTRIANGLE *)lwg2, dl); + case POLYGONTYPE: + return lw_dist2d_tri_poly((LWTRIANGLE *)lwg1, (LWPOLY *)lwg2, dl); + case CIRCSTRINGTYPE: + return lw_dist2d_tri_circstring((LWTRIANGLE *)lwg1, (LWCIRCSTRING *)lwg2, dl); + case CURVEPOLYTYPE: + return lw_dist2d_tri_curvepoly((LWTRIANGLE *)lwg1, (LWCURVEPOLY *)lwg2, dl); + default: + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(t2)); + return LW_FALSE; + } + } + case CIRCSTRINGTYPE: + { + dl->twisted = 1; + switch (t2) + { + case POINTTYPE: + dl->twisted = -1; + return lw_dist2d_point_circstring((LWPOINT *)lwg2, (LWCIRCSTRING *)lwg1, dl); + case LINETYPE: + dl->twisted = -1; + return lw_dist2d_line_circstring((LWLINE *)lwg2, (LWCIRCSTRING *)lwg1, dl); + case TRIANGLETYPE: + dl->twisted = -1; + return lw_dist2d_tri_circstring((LWTRIANGLE *)lwg2, (LWCIRCSTRING *)lwg1, dl); + case POLYGONTYPE: + return lw_dist2d_circstring_poly((LWCIRCSTRING *)lwg1, (LWPOLY *)lwg2, dl); + case CIRCSTRINGTYPE: + return lw_dist2d_circstring_circstring((LWCIRCSTRING *)lwg1, (LWCIRCSTRING *)lwg2, dl); + case CURVEPOLYTYPE: + return lw_dist2d_circstring_curvepoly((LWCIRCSTRING *)lwg1, (LWCURVEPOLY *)lwg2, dl); + default: + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(t2)); + return LW_FALSE; + } + } + case POLYGONTYPE: + { + dl->twisted = -1; + switch (t2) + { + case POINTTYPE: + return lw_dist2d_point_poly((LWPOINT *)lwg2, (LWPOLY *)lwg1, dl); + case LINETYPE: + return lw_dist2d_line_poly((LWLINE *)lwg2, (LWPOLY *)lwg1, dl); + case TRIANGLETYPE: + return lw_dist2d_tri_poly((LWTRIANGLE *)lwg2, (LWPOLY *)lwg1, dl); + case CIRCSTRINGTYPE: + return lw_dist2d_circstring_poly((LWCIRCSTRING *)lwg2, (LWPOLY *)lwg1, dl); + case POLYGONTYPE: + dl->twisted = 1; + return lw_dist2d_poly_poly((LWPOLY *)lwg1, (LWPOLY *)lwg2, dl); + case CURVEPOLYTYPE: + dl->twisted = 1; + return lw_dist2d_poly_curvepoly((LWPOLY *)lwg1, (LWCURVEPOLY *)lwg2, dl); + default: + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(t2)); + return LW_FALSE; + } + } + case CURVEPOLYTYPE: + { + dl->twisted = -1; + switch (t2) + { + case POINTTYPE: + return lw_dist2d_point_curvepoly((LWPOINT *)lwg2, (LWCURVEPOLY *)lwg1, dl); + case LINETYPE: + return lw_dist2d_line_curvepoly((LWLINE *)lwg2, (LWCURVEPOLY *)lwg1, dl); + case TRIANGLETYPE: + return lw_dist2d_tri_curvepoly((LWTRIANGLE *)lwg2, (LWCURVEPOLY *)lwg1, dl); + case POLYGONTYPE: + return lw_dist2d_poly_curvepoly((LWPOLY *)lwg2, (LWCURVEPOLY *)lwg1, dl); + case CIRCSTRINGTYPE: + return lw_dist2d_circstring_curvepoly((LWCIRCSTRING *)lwg2, (LWCURVEPOLY *)lwg1, dl); + case CURVEPOLYTYPE: + dl->twisted = 1; + return lw_dist2d_curvepoly_curvepoly((LWCURVEPOLY *)lwg1, (LWCURVEPOLY *)lwg2, dl); + default: + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(t2)); + return LW_FALSE; + } + } + default: + { + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(t1)); + return LW_FALSE; + } + } + + return LW_FALSE; +} + +/* Check for overlapping bboxes */ +int +lw_dist2d_check_overlap(LWGEOM *lwg1, LWGEOM *lwg2) +{ + LWDEBUG(2, "lw_dist2d_check_overlap is called"); + if (!lwg1->bbox) + lwgeom_calculate_gbox(lwg1, lwg1->bbox); + if (!lwg2->bbox) + lwgeom_calculate_gbox(lwg2, lwg2->bbox); + + /* Check if the geometries intersect. */ + if ((lwg1->bbox->xmax < lwg2->bbox->xmin || lwg1->bbox->xmin > lwg2->bbox->xmax || + lwg1->bbox->ymax < lwg2->bbox->ymin || lwg1->bbox->ymin > lwg2->bbox->ymax)) + { + LWDEBUG(3, "geometries bboxes did not overlap"); + return LW_FALSE; + } + LWDEBUG(3, "geometries bboxes overlap"); + return LW_TRUE; +} + +/** Geometries are distributed for the new faster distance-calculations */ +int +lw_dist2d_distribute_fast(LWGEOM *lwg1, LWGEOM *lwg2, DISTPTS *dl) +{ + POINTARRAY *pa1, *pa2; + int type1 = lwg1->type; + int type2 = lwg2->type; + + LWDEBUGF(2, "lw_dist2d_distribute_fast is called with typ1=%d, type2=%d", lwg1->type, lwg2->type); + + switch (type1) + { + case LINETYPE: + pa1 = ((LWLINE *)lwg1)->points; + break; + case POLYGONTYPE: + pa1 = ((LWPOLY *)lwg1)->rings[0]; + break; + case TRIANGLETYPE: + pa1 = ((LWTRIANGLE *)lwg1)->points; + break; + default: + lwerror("Unsupported geometry1 type: %s", lwtype_name(type1)); + return LW_FALSE; + } + switch (type2) + { + case LINETYPE: + pa2 = ((LWLINE *)lwg2)->points; + break; + case POLYGONTYPE: + pa2 = ((LWPOLY *)lwg2)->rings[0]; + break; + case TRIANGLETYPE: + pa2 = ((LWTRIANGLE *)lwg2)->points; + break; + default: + lwerror("Unsupported geometry2 type: %s", lwtype_name(type1)); + return LW_FALSE; + } + dl->twisted = 1; + return lw_dist2d_fast_ptarray_ptarray(pa1, pa2, dl, lwg1->bbox, lwg2->bbox); +} + +/*------------------------------------------------------------------------------------------------------------ +End of Preprocessing functions +--------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------ +Brute force functions +The old way of calculating distances, now used for: +1) distances to points (because there shouldn't be anything to gain by the new way of doing it) +2) distances when subgeometries geometries bboxes overlaps +--------------------------------------------------------------------------------------------------------------*/ + +/** +point to point calculation +*/ +int +lw_dist2d_point_point(LWPOINT *point1, LWPOINT *point2, DISTPTS *dl) +{ + const POINT2D *p1 = getPoint2d_cp(point1->point, 0); + const POINT2D *p2 = getPoint2d_cp(point2->point, 0); + return lw_dist2d_pt_pt(p1, p2, dl); +} +/** +point to line calculation +*/ +int +lw_dist2d_point_line(LWPOINT *point, LWLINE *line, DISTPTS *dl) +{ + const POINT2D *p = getPoint2d_cp(point->point, 0); + return lw_dist2d_pt_ptarray(p, line->points, dl); +} + +int +lw_dist2d_point_tri(LWPOINT *point, LWTRIANGLE *tri, DISTPTS *dl) +{ + const POINT2D *pt = getPoint2d_cp(point->point, 0); + /* Is point inside triangle? */ + if (dl->mode == DIST_MIN && ptarray_contains_point(tri->points, pt) != LW_OUTSIDE) + { + dl->distance = 0.0; + dl->p1.x = dl->p2.x = pt->x; + dl->p1.y = dl->p2.y = pt->y; + return LW_TRUE; + } + + return lw_dist2d_pt_ptarray(pt, tri->points, dl); +} + +int +lw_dist2d_point_circstring(LWPOINT *point, LWCIRCSTRING *circ, DISTPTS *dl) +{ + const POINT2D *p; + p = getPoint2d_cp(point->point, 0); + return lw_dist2d_pt_ptarrayarc(p, circ->points, dl); +} + +/** + * 1. see if pt in outer boundary. if no, then treat the outer ring like a line + * 2. if in the boundary, test to see if its in a hole. + * if so, then return dist to hole, else return 0 (point in polygon) + */ +int +lw_dist2d_point_poly(LWPOINT *point, LWPOLY *poly, DISTPTS *dl) +{ + const POINT2D *p = getPoint2d_cp(point->point, 0); + + /* Max distance? Check only outer ring.*/ + if (dl->mode == DIST_MAX) + return lw_dist2d_pt_ptarray(p, poly->rings[0], dl); + + /* Return distance to outer ring if not inside it */ + if (ptarray_contains_point(poly->rings[0], p) == LW_OUTSIDE) + return lw_dist2d_pt_ptarray(p, poly->rings[0], dl); + + /* + * Inside the outer ring. + * Scan though each of the inner rings looking to + * see if its inside. If not, distance==0. + * Otherwise, distance = pt to ring distance + */ + for (uint32_t i = 1; i < poly->nrings; i++) + if (ptarray_contains_point(poly->rings[i], p) != LW_OUTSIDE) + return lw_dist2d_pt_ptarray(p, poly->rings[i], dl); + + /* Is inside the polygon */ + dl->distance = 0.0; + dl->p1.x = dl->p2.x = p->x; + dl->p1.y = dl->p2.y = p->y; + return LW_TRUE; +} + +int +lw_dist2d_point_curvepoly(LWPOINT *point, LWCURVEPOLY *poly, DISTPTS *dl) +{ + const POINT2D *p = getPoint2d_cp(point->point, 0); + + if (dl->mode == DIST_MAX) + lwerror("lw_dist2d_point_curvepoly cannot calculate max distance"); + + /* Return distance to outer ring if not inside it */ + if (lwgeom_contains_point(poly->rings[0], p) == LW_OUTSIDE) + return lw_dist2d_recursive((LWGEOM *)point, poly->rings[0], dl); + + /* Inside the outer ring. + * Scan though each of the inner rings looking to see if its inside. If not, distance==0. + * Otherwise, distance = pt to ring distance. + */ + for (uint32_t i = 1; i < poly->nrings; i++) + if (lwgeom_contains_point(poly->rings[i], p) == LW_INSIDE) + return lw_dist2d_recursive((LWGEOM *)point, poly->rings[i], dl); + + /* Is inside the polygon */ + dl->distance = 0.0; + dl->p1.x = dl->p2.x = p->x; + dl->p1.y = dl->p2.y = p->y; + return LW_TRUE; +} + +int +lw_dist2d_line_line(LWLINE *line1, LWLINE *line2, DISTPTS *dl) +{ + POINTARRAY *pa1 = line1->points; + POINTARRAY *pa2 = line2->points; + return lw_dist2d_ptarray_ptarray(pa1, pa2, dl); +} + +int +lw_dist2d_line_tri(LWLINE *line, LWTRIANGLE *tri, DISTPTS *dl) +{ + const POINT2D *pt = getPoint2d_cp(line->points, 0); + /* Is there a point inside triangle? */ + if (dl->mode == DIST_MIN && ptarray_contains_point(tri->points, pt) != LW_OUTSIDE) + { + dl->distance = 0.0; + dl->p1.x = dl->p2.x = pt->x; + dl->p1.y = dl->p2.y = pt->y; + return LW_TRUE; + } + + return lw_dist2d_ptarray_ptarray(line->points, tri->points, dl); +} + +int +lw_dist2d_line_circstring(LWLINE *line1, LWCIRCSTRING *line2, DISTPTS *dl) +{ + return lw_dist2d_ptarray_ptarrayarc(line1->points, line2->points, dl); +} + +/** + * line to polygon calculation + * Brute force. + * Test line-ring distance against each ring. + * If there's an intersection (distance==0) then return 0 (crosses boundary). + * Otherwise, test to see if any point is inside outer rings of polygon, + * but not in inner rings. + * If so, return 0 (line inside polygon), + * otherwise return min distance to a ring (could be outside + * polygon or inside a hole) + */ +int +lw_dist2d_line_poly(LWLINE *line, LWPOLY *poly, DISTPTS *dl) +{ + POINTARRAY *pa = line->points; + const POINT2D *pt = getPoint2d_cp(pa, 0); + + /* Line has a pount outside poly. Check distance to outer ring only. */ + if (ptarray_contains_point(poly->rings[0], pt) == LW_OUTSIDE || dl->mode == DIST_MAX) + return lw_dist2d_ptarray_ptarray(pa, poly->rings[0], dl); + + for (uint32_t i = 1; i < poly->nrings; i++) + { + if (!lw_dist2d_ptarray_ptarray(pa, poly->rings[i], dl)) + return LW_FALSE; + + /* just a check if the answer is already given */ + if (dl->distance <= dl->tolerance && dl->mode == DIST_MIN) + return LW_TRUE; + } + + /* It's inside a hole, then the actual distance is the min ring distance */ + for (uint32_t i = 1; i < poly->nrings; i++) + if (ptarray_contains_point(poly->rings[i], pt) != LW_OUTSIDE) + return LW_TRUE; + + /* Not in hole, so inside polygon */ + if (dl->mode == DIST_MIN) + { + dl->distance = 0.0; + dl->p1.x = dl->p2.x = pt->x; + dl->p1.y = dl->p2.y = pt->y; + } + return LW_TRUE; +} + +int +lw_dist2d_line_curvepoly(LWLINE *line, LWCURVEPOLY *poly, DISTPTS *dl) +{ + const POINT2D *pt = getPoint2d_cp(line->points, 0); + + /* Line has a pount outside curvepoly. Check distance to outer ring only. */ + if (lwgeom_contains_point(poly->rings[0], pt) == LW_OUTSIDE) + return lw_dist2d_recursive((LWGEOM *)line, poly->rings[0], dl); + + for (uint32_t i = 1; i < poly->nrings; i++) + { + if (!lw_dist2d_recursive((LWGEOM *)line, poly->rings[i], dl)) + return LW_FALSE; + + /* just a check if the answer is already given */ + if (dl->distance <= dl->tolerance && dl->mode == DIST_MIN) + return LW_TRUE; + } + + /* It's inside a hole, then distance is actual min distance */ + for (uint32_t i = 1; i < poly->nrings; i++) + if (lwgeom_contains_point(poly->rings[i], pt) != LW_OUTSIDE) + return LW_TRUE; + + /* Not in hole, so inside polygon */ + if (dl->mode == DIST_MIN) + { + dl->distance = 0.0; + dl->p1.x = dl->p2.x = pt->x; + dl->p1.y = dl->p2.y = pt->y; + } + return LW_TRUE; +} + +int +lw_dist2d_tri_tri(LWTRIANGLE *tri1, LWTRIANGLE *tri2, DISTPTS *dl) +{ + POINTARRAY *pa1 = tri1->points; + POINTARRAY *pa2 = tri2->points; + const POINT2D *pt = getPoint2d_cp(pa2, 0); + if (dl->mode == DIST_MIN && ptarray_contains_point(pa1, pt) != LW_OUTSIDE) + { + dl->distance = 0.0; + dl->p1.x = dl->p2.x = pt->x; + dl->p1.y = dl->p2.y = pt->y; + return LW_TRUE; + } + + pt = getPoint2d_cp(pa1, 0); + if (dl->mode == DIST_MIN && ptarray_contains_point(pa2, pt) != LW_OUTSIDE) + { + dl->distance = 0.0; + dl->p1.x = dl->p2.x = pt->x; + dl->p1.y = dl->p2.y = pt->y; + return LW_TRUE; + } + + return lw_dist2d_ptarray_ptarray(pa1, pa2, dl); +} + +int +lw_dist2d_tri_poly(LWTRIANGLE *tri, LWPOLY *poly, DISTPTS *dl) +{ + POINTARRAY *pa = tri->points; + const POINT2D *pt = getPoint2d_cp(pa, 0); + + /* If we are looking for maxdistance, just check the outer rings.*/ + if (dl->mode == DIST_MAX) + return lw_dist2d_ptarray_ptarray(pa, poly->rings[0], dl); + + /* Triangle has a point outside poly. Check distance to outer ring only. */ + if (ptarray_contains_point(poly->rings[0], pt) == LW_OUTSIDE) + { + if (!lw_dist2d_ptarray_ptarray(pa, poly->rings[0], dl)) + return LW_FALSE; + + /* just a check if the answer is already given */ + if (dl->distance <= dl->tolerance) + return LW_TRUE; + + /* Maybe poly is inside triangle? */ + const POINT2D *pt2 = getPoint2d_cp(poly->rings[0], 0); + if (ptarray_contains_point(pa, pt2) != LW_OUTSIDE) + { + dl->distance = 0.0; + dl->p1.x = dl->p2.x = pt2->x; + dl->p1.y = dl->p2.y = pt2->y; + return LW_TRUE; + } + } + + for (uint32_t i = 1; i < poly->nrings; i++) + { + if (!lw_dist2d_ptarray_ptarray(pa, poly->rings[i], dl)) + return LW_FALSE; + + /* just a check if the answer is already given */ + if (dl->distance <= dl->tolerance && dl->mode == DIST_MIN) + return LW_TRUE; + } + + /* It's inside a hole, then the actual distance is the min ring distance */ + for (uint32_t i = 1; i < poly->nrings; i++) + if (ptarray_contains_point(poly->rings[i], pt) != LW_OUTSIDE) + return LW_TRUE; + + /* Not in hole, so inside polygon */ + dl->distance = 0.0; + dl->p1.x = dl->p2.x = pt->x; + dl->p1.y = dl->p2.y = pt->y; + return LW_TRUE; +} +static const POINT2D * +lw_curvering_getfirstpoint2d_cp(LWGEOM *geom) +{ + switch (geom->type) + { + case LINETYPE: + return getPoint2d_cp(((LWLINE *)geom)->points, 0); + case CIRCSTRINGTYPE: + return getPoint2d_cp(((LWCIRCSTRING *)geom)->points, 0); + case COMPOUNDTYPE: + { + LWCOMPOUND *comp = (LWCOMPOUND *)geom; + LWLINE *line = (LWLINE *)(comp->geoms[0]); + return getPoint2d_cp(line->points, 0); + } + default: + lwerror("lw_curvering_getfirstpoint2d_cp: unknown type"); + } + return NULL; +} + +int +lw_dist2d_tri_curvepoly(LWTRIANGLE *tri, LWCURVEPOLY *poly, DISTPTS *dl) +{ + const POINT2D *pt = getPoint2d_cp(tri->points, 0); + + /* If we are looking for maxdistance, just check the outer rings.*/ + if (dl->mode == DIST_MAX) + return lw_dist2d_recursive((LWGEOM *)tri, poly->rings[0], dl); + + /* Line has a pount outside curvepoly. Check distance to outer ring only. */ + if (lwgeom_contains_point(poly->rings[0], pt) == LW_OUTSIDE) + { + if (lw_dist2d_recursive((LWGEOM *)tri, poly->rings[0], dl)) + return LW_TRUE; + /* Maybe poly is inside triangle? */ + if (lwgeom_contains_point((LWGEOM *)tri, lw_curvering_getfirstpoint2d_cp(poly->rings[0])) != LW_OUTSIDE) + { + dl->distance = 0.0; + dl->p1.x = dl->p2.x = pt->x; + dl->p1.y = dl->p2.y = pt->y; + return LW_TRUE; + } + } + + for (uint32_t i = 1; i < poly->nrings; i++) + { + if (!lw_dist2d_recursive((LWGEOM *)tri, poly->rings[i], dl)) + return LW_FALSE; + + /* just a check if the answer is already given */ + if (dl->distance <= dl->tolerance && dl->mode == DIST_MIN) + return LW_TRUE; + } + + /* It's inside a hole, then distance is actual min distance */ + for (uint32_t i = 1; i < poly->nrings; i++) + if (lwgeom_contains_point(poly->rings[i], pt) != LW_OUTSIDE) + return LW_TRUE; + + /* Not in hole, so inside polygon */ + dl->distance = 0.0; + dl->p1.x = dl->p2.x = pt->x; + dl->p1.y = dl->p2.y = pt->y; + return LW_TRUE; +} + +int +lw_dist2d_tri_circstring(LWTRIANGLE *tri, LWCIRCSTRING *line, DISTPTS *dl) +{ + const POINT2D *pt = lw_curvering_getfirstpoint2d_cp((LWGEOM *)line); + if (ptarray_contains_point(tri->points, pt) != LW_OUTSIDE && dl->mode == DIST_MIN) + { + dl->distance = 0.0; + dl->p1.x = dl->p2.x = pt->x; + dl->p1.y = dl->p2.y = pt->y; + return LW_TRUE; + } + + return lw_dist2d_ptarray_ptarrayarc(tri->points, line->points, dl); +} + +/** +Function handling polygon to polygon calculation +1 if we are looking for maxdistance, just check the outer rings. +2 check if poly1 has first point outside poly2 and vice versa, if so, just check outer rings +3 check if first point of poly2 is in a hole of poly1. If so check outer ring of poly2 against that hole of poly1 +4 check if first point of poly1 is in a hole of poly2. If so check outer ring of poly1 against that hole of poly2 +5 If we have come all the way here we know that the first point of one of them is inside the other ones outer ring +and not in holes so we check which one is inside. + */ +int +lw_dist2d_poly_poly(LWPOLY *poly1, LWPOLY *poly2, DISTPTS *dl) +{ + const POINT2D *pt; + + LWDEBUG(2, "lw_dist2d_poly_poly called"); + + /*1 if we are looking for maxdistance, just check the outer rings.*/ + if (dl->mode == DIST_MAX) + return lw_dist2d_ptarray_ptarray(poly1->rings[0], poly2->rings[0], dl); + + /* 2 check if poly1 has first point outside poly2 and vice versa, if so, just check outer rings + here it would be possible to handle the information about which one is inside which one and only search for the + smaller ones in the bigger ones holes.*/ + pt = getPoint2d_cp(poly1->rings[0], 0); + if (ptarray_contains_point(poly2->rings[0], pt) == LW_OUTSIDE) + { + pt = getPoint2d_cp(poly2->rings[0], 0); + if (ptarray_contains_point(poly1->rings[0], pt) == LW_OUTSIDE) + return lw_dist2d_ptarray_ptarray(poly1->rings[0], poly2->rings[0], dl); + } + + /*3 check if first point of poly2 is in a hole of poly1. If so check outer ring of poly2 against that hole + * of poly1*/ + pt = getPoint2d_cp(poly2->rings[0], 0); + for (uint32_t i = 1; i < poly1->nrings; i++) + if (ptarray_contains_point(poly1->rings[i], pt) != LW_OUTSIDE) + return lw_dist2d_ptarray_ptarray(poly1->rings[i], poly2->rings[0], dl); + + /*4 check if first point of poly1 is in a hole of poly2. If so check outer ring of poly1 against that hole + * of poly2*/ + pt = getPoint2d_cp(poly1->rings[0], 0); + for (uint32_t i = 1; i < poly2->nrings; i++) + if (ptarray_contains_point(poly2->rings[i], pt) != LW_OUTSIDE) + return lw_dist2d_ptarray_ptarray(poly1->rings[0], poly2->rings[i], dl); + + /*5 If we have come all the way here we know that the first point of one of them is inside the other ones + * outer ring and not in holes so we check wich one is inside.*/ + pt = getPoint2d_cp(poly1->rings[0], 0); + if (ptarray_contains_point(poly2->rings[0], pt) != LW_OUTSIDE) + { + dl->distance = 0.0; + dl->p1.x = dl->p2.x = pt->x; + dl->p1.y = dl->p2.y = pt->y; + return LW_TRUE; + } + + pt = getPoint2d_cp(poly2->rings[0], 0); + if (ptarray_contains_point(poly1->rings[0], pt) != LW_OUTSIDE) + { + dl->distance = 0.0; + dl->p1.x = dl->p2.x = pt->x; + dl->p1.y = dl->p2.y = pt->y; + return LW_TRUE; + } + + lwerror("Unspecified error in function lw_dist2d_poly_poly"); + return LW_FALSE; +} + +int +lw_dist2d_poly_curvepoly(LWPOLY *poly1, LWCURVEPOLY *curvepoly2, DISTPTS *dl) +{ + LWCURVEPOLY *curvepoly1 = lwcurvepoly_construct_from_lwpoly(poly1); + int rv = lw_dist2d_curvepoly_curvepoly(curvepoly1, curvepoly2, dl); + lwgeom_free((LWGEOM *)curvepoly1); + return rv; +} + +int +lw_dist2d_circstring_poly(LWCIRCSTRING *circ, LWPOLY *poly, DISTPTS *dl) +{ + LWCURVEPOLY *curvepoly = lwcurvepoly_construct_from_lwpoly(poly); + int rv = lw_dist2d_line_curvepoly((LWLINE *)circ, curvepoly, dl); + lwgeom_free((LWGEOM *)curvepoly); + return rv; +} + +int +lw_dist2d_circstring_curvepoly(LWCIRCSTRING *circ, LWCURVEPOLY *poly, DISTPTS *dl) +{ + return lw_dist2d_line_curvepoly((LWLINE *)circ, poly, dl); +} + +int +lw_dist2d_circstring_circstring(LWCIRCSTRING *line1, LWCIRCSTRING *line2, DISTPTS *dl) +{ + return lw_dist2d_ptarrayarc_ptarrayarc(line1->points, line2->points, dl); +} + +int +lw_dist2d_curvepoly_curvepoly(LWCURVEPOLY *poly1, LWCURVEPOLY *poly2, DISTPTS *dl) +{ + const POINT2D *pt; + + /*1 if we are looking for maxdistance, just check the outer rings.*/ + if (dl->mode == DIST_MAX) + return lw_dist2d_recursive(poly1->rings[0], poly2->rings[0], dl); + + /* 2 check if poly1 has first point outside poly2 and vice versa, if so, just check outer rings + here it would be possible to handle the information about which one is inside which one and only search for the + smaller ones in the bigger ones holes.*/ + pt = lw_curvering_getfirstpoint2d_cp(poly1->rings[0]); + if (lwgeom_contains_point(poly2->rings[0], pt) == LW_OUTSIDE) + { + pt = lw_curvering_getfirstpoint2d_cp(poly2->rings[0]); + if (lwgeom_contains_point(poly1->rings[0], pt) == LW_OUTSIDE) + return lw_dist2d_recursive(poly1->rings[0], poly2->rings[0], dl); + } + + /*3 check if first point of poly2 is in a hole of poly1. If so check outer ring of poly2 against that hole + * of poly1*/ + pt = lw_curvering_getfirstpoint2d_cp(poly2->rings[0]); + for (uint32_t i = 1; i < poly1->nrings; i++) + if (lwgeom_contains_point(poly1->rings[i], pt) != LW_OUTSIDE) + return lw_dist2d_recursive(poly1->rings[i], poly2->rings[0], dl); + + /*4 check if first point of poly1 is in a hole of poly2. If so check outer ring of poly1 against that hole + * of poly2*/ + pt = lw_curvering_getfirstpoint2d_cp(poly1->rings[0]); + for (uint32_t i = 1; i < poly2->nrings; i++) + if (lwgeom_contains_point(poly2->rings[i], pt) != LW_OUTSIDE) + return lw_dist2d_recursive(poly1->rings[0], poly2->rings[i], dl); + + /*5 If we have come all the way here we know that the first point of one of them is inside the other ones + * outer ring and not in holes so we check which one is inside.*/ + pt = lw_curvering_getfirstpoint2d_cp(poly1->rings[0]); + if (lwgeom_contains_point(poly2->rings[0], pt) != LW_OUTSIDE) + { + dl->distance = 0.0; + dl->p1.x = dl->p2.x = pt->x; + dl->p1.y = dl->p2.y = pt->y; + return LW_TRUE; + } + + pt = lw_curvering_getfirstpoint2d_cp(poly2->rings[0]); + if (lwgeom_contains_point(poly1->rings[0], pt) != LW_OUTSIDE) + { + dl->distance = 0.0; + dl->p1.x = dl->p2.x = pt->x; + dl->p1.y = dl->p2.y = pt->y; + return LW_TRUE; + } + + lwerror("Unspecified error in function lw_dist2d_curvepoly_curvepoly"); + return LW_FALSE; +} + +/** + * search all the segments of pointarray to see which one is closest to p1 + * Returns minimum distance between point and pointarray + */ +int +lw_dist2d_pt_ptarray(const POINT2D *p, POINTARRAY *pa, DISTPTS *dl) +{ + const POINT2D *start, *end; + int twist = dl->twisted; + + start = getPoint2d_cp(pa, 0); + + if (!lw_dist2d_pt_pt(p, start, dl)) + return LW_FALSE; + + for (uint32_t t = 1; t < pa->npoints; t++) + { + dl->twisted = twist; + end = getPoint2d_cp(pa, t); + if (!lw_dist2d_pt_seg(p, start, end, dl)) + return LW_FALSE; + + if (dl->distance <= dl->tolerance && dl->mode == DIST_MIN) + return LW_TRUE; /*just a check if the answer is already given*/ + start = end; + } + + return LW_TRUE; +} + +/** + * Search all the arcs of pointarray to see which one is closest to p1 + * Returns minimum distance between point and arc pointarray. + */ +int +lw_dist2d_pt_ptarrayarc(const POINT2D *p, const POINTARRAY *pa, DISTPTS *dl) +{ + uint32_t t; + const POINT2D *A1; + const POINT2D *A2; + const POINT2D *A3; + int twist = dl->twisted; + + LWDEBUG(2, "lw_dist2d_pt_ptarrayarc is called"); + + if (pa->npoints % 2 == 0 || pa->npoints < 3) + { + lwerror("lw_dist2d_pt_ptarrayarc called with non-arc input"); + return LW_FALSE; + } + + if (dl->mode == DIST_MAX) + { + lwerror("lw_dist2d_pt_ptarrayarc does not currently support DIST_MAX mode"); + return LW_FALSE; + } + + A1 = getPoint2d_cp(pa, 0); + + if (!lw_dist2d_pt_pt(p, A1, dl)) + return LW_FALSE; + + for (t = 1; t < pa->npoints; t += 2) + { + dl->twisted = twist; + A2 = getPoint2d_cp(pa, t); + A3 = getPoint2d_cp(pa, t + 1); + + if (lw_dist2d_pt_arc(p, A1, A2, A3, dl) == LW_FALSE) + return LW_FALSE; + + if (dl->distance <= dl->tolerance && dl->mode == DIST_MIN) + return LW_TRUE; /*just a check if the answer is already given*/ + + A1 = A3; + } + + return LW_TRUE; +} + +/** + * test each segment of l1 against each segment of l2. + */ +int +lw_dist2d_ptarray_ptarray(POINTARRAY *l1, POINTARRAY *l2, DISTPTS *dl) +{ + uint32_t t, u; + const POINT2D *start, *end; + const POINT2D *start2, *end2; + int twist = dl->twisted; + + LWDEBUGF(2, "lw_dist2d_ptarray_ptarray called (points: %d-%d)", l1->npoints, l2->npoints); + + /* If we are searching for maxdistance we go straight to point-point calculation since the maxdistance have + * to be between two vertexes*/ + if (dl->mode == DIST_MAX) + { + for (t = 0; t < l1->npoints; t++) /*for each segment in L1 */ + { + start = getPoint2d_cp(l1, t); + for (u = 0; u < l2->npoints; u++) /*for each segment in L2 */ + { + start2 = getPoint2d_cp(l2, u); + lw_dist2d_pt_pt(start, start2, dl); + } + } + } + else + { + start = getPoint2d_cp(l1, 0); + for (t = 1; t < l1->npoints; t++) /*for each segment in L1 */ + { + end = getPoint2d_cp(l1, t); + start2 = getPoint2d_cp(l2, 0); + for (u = 1; u < l2->npoints; u++) /*for each segment in L2 */ + { + end2 = getPoint2d_cp(l2, u); + dl->twisted = twist; + lw_dist2d_seg_seg(start, end, start2, end2, dl); + if (dl->distance <= dl->tolerance && dl->mode == DIST_MIN) + return LW_TRUE; /*just a check if the answer is already given*/ + start2 = end2; + } + start = end; + } + } + return LW_TRUE; +} + +/** + * Test each segment of pa against each arc of pb for distance. + */ +int +lw_dist2d_ptarray_ptarrayarc(const POINTARRAY *pa, const POINTARRAY *pb, DISTPTS *dl) +{ + uint32_t t, u; + const POINT2D *A1; + const POINT2D *A2; + const POINT2D *B1; + const POINT2D *B2; + const POINT2D *B3; + int twist = dl->twisted; + + LWDEBUGF(2, "lw_dist2d_ptarray_ptarrayarc called (points: %d-%d)", pa->npoints, pb->npoints); + + if (pb->npoints % 2 == 0 || pb->npoints < 3) + { + lwerror("lw_dist2d_ptarray_ptarrayarc called with non-arc input"); + return LW_FALSE; + } + + if (dl->mode == DIST_MAX) + { + lwerror("lw_dist2d_ptarray_ptarrayarc does not currently support DIST_MAX mode"); + return LW_FALSE; + } + else + { + A1 = getPoint2d_cp(pa, 0); + for (t = 1; t < pa->npoints; t++) /* For each segment in pa */ + { + A2 = getPoint2d_cp(pa, t); + B1 = getPoint2d_cp(pb, 0); + for (u = 1; u < pb->npoints; u += 2) /* For each arc in pb */ + { + B2 = getPoint2d_cp(pb, u); + B3 = getPoint2d_cp(pb, u + 1); + dl->twisted = twist; + + lw_dist2d_seg_arc(A1, A2, B1, B2, B3, dl); + + /* If we've found a distance within tolerance, we're done */ + if (dl->distance <= dl->tolerance && dl->mode == DIST_MIN) + return LW_TRUE; + + B1 = B3; + } + A1 = A2; + } + } + return LW_TRUE; +} + +/** + * Test each arc of pa against each arc of pb for distance. + */ +int +lw_dist2d_ptarrayarc_ptarrayarc(const POINTARRAY *pa, const POINTARRAY *pb, DISTPTS *dl) +{ + uint32_t t, u; + const POINT2D *A1; + const POINT2D *A2; + const POINT2D *A3; + const POINT2D *B1; + const POINT2D *B2; + const POINT2D *B3; + int twist = dl->twisted; + + LWDEBUGF(2, "lw_dist2d_ptarrayarc_ptarrayarc called (points: %d-%d)", pa->npoints, pb->npoints); + + if (dl->mode == DIST_MAX) + { + lwerror("lw_dist2d_ptarrayarc_ptarrayarc does not currently support DIST_MAX mode"); + return LW_FALSE; + } + else + { + A1 = getPoint2d_cp(pa, 0); + for (t = 1; t < pa->npoints; t += 2) /* For each segment in pa */ + { + A2 = getPoint2d_cp(pa, t); + A3 = getPoint2d_cp(pa, t + 1); + B1 = getPoint2d_cp(pb, 0); + for (u = 1; u < pb->npoints; u += 2) /* For each arc in pb */ + { + B2 = getPoint2d_cp(pb, u); + B3 = getPoint2d_cp(pb, u + 1); + dl->twisted = twist; + + lw_dist2d_arc_arc(A1, A2, A3, B1, B2, B3, dl); + + /* If we've found a distance within tolerance, we're done */ + if (dl->distance <= dl->tolerance && dl->mode == DIST_MIN) + return LW_TRUE; + + B1 = B3; + } + A1 = A3; + } + } + return LW_TRUE; +} + +/** + * Calculate the shortest distance between an arc and an edge. + * Line/circle approach from http://stackoverflow.com/questions/1073336/circle-line-collision-detection + */ +int +lw_dist2d_seg_arc(const POINT2D *A1, + const POINT2D *A2, + const POINT2D *B1, + const POINT2D *B2, + const POINT2D *B3, + DISTPTS *dl) +{ + POINT2D C; /* center of arc circle */ + double radius_C; /* radius of arc circle */ + POINT2D D; /* point on A closest to C */ + double dist_C_D; /* distance from C to D */ + int pt_in_arc, pt_in_seg; + DISTPTS dltmp; + + /* Bail out on crazy modes */ + if (dl->mode < 0) + lwerror("lw_dist2d_seg_arc does not support maxdistance mode"); + + /* What if the "arc" is a point? */ + if (lw_arc_is_pt(B1, B2, B3)) + return lw_dist2d_pt_seg(B1, A1, A2, dl); + + /* Calculate center and radius of the circle. */ + radius_C = lw_arc_center(B1, B2, B3, &C); + + /* This "arc" is actually a line (B2 is collinear with B1,B3) */ + if (radius_C < 0.0) + return lw_dist2d_seg_seg(A1, A2, B1, B3, dl); + + /* Calculate distance between the line and circle center */ + lw_dist2d_distpts_init(&dltmp, DIST_MIN); + if (lw_dist2d_pt_seg(&C, A1, A2, &dltmp) == LW_FALSE) + lwerror("lw_dist2d_pt_seg failed in lw_dist2d_seg_arc"); + + D = dltmp.p1; + dist_C_D = dltmp.distance; + + /* Line intersects circle, maybe arc intersects edge? */ + /* If so, that's the closest point. */ + /* If not, the closest point is one of the end points of A */ + if (dist_C_D < radius_C) + { + double length_A; /* length of the segment A */ + POINT2D E, F; /* points of intersection of edge A and circle(B) */ + double dist_D_EF; /* distance from D to E or F (same distance both ways) */ + + dist_D_EF = sqrt(radius_C * radius_C - dist_C_D * dist_C_D); + length_A = sqrt((A2->x - A1->x) * (A2->x - A1->x) + (A2->y - A1->y) * (A2->y - A1->y)); + + /* Point of intersection E */ + E.x = D.x - (A2->x - A1->x) * dist_D_EF / length_A; + E.y = D.y - (A2->y - A1->y) * dist_D_EF / length_A; + /* Point of intersection F */ + F.x = D.x + (A2->x - A1->x) * dist_D_EF / length_A; + F.y = D.y + (A2->y - A1->y) * dist_D_EF / length_A; + + /* If E is within A and within B then it's an intersection point */ + pt_in_arc = lw_pt_in_arc(&E, B1, B2, B3); + pt_in_seg = lw_pt_in_seg(&E, A1, A2); + + if (pt_in_arc && pt_in_seg) + { + dl->distance = 0.0; + dl->p1 = E; + dl->p2 = E; + return LW_TRUE; + } + + /* If F is within A and within B then it's an intersection point */ + pt_in_arc = lw_pt_in_arc(&F, B1, B2, B3); + pt_in_seg = lw_pt_in_seg(&F, A1, A2); + + if (pt_in_arc && pt_in_seg) + { + dl->distance = 0.0; + dl->p1 = F; + dl->p2 = F; + return LW_TRUE; + } + } + + /* Line grazes circle, maybe arc intersects edge? */ + /* If so, grazing point is the closest point. */ + /* If not, the closest point is one of the end points of A */ + else if (dist_C_D == radius_C) + { + /* Closest point D is also the point of grazing */ + pt_in_arc = lw_pt_in_arc(&D, B1, B2, B3); + pt_in_seg = lw_pt_in_seg(&D, A1, A2); + + /* Is D contained in both A and B? */ + if (pt_in_arc && pt_in_seg) + { + dl->distance = 0.0; + dl->p1 = D; + dl->p2 = D; + return LW_TRUE; + } + } + /* Line misses circle. */ + /* If closest point to A on circle is within B, then that's the closest */ + /* Otherwise, the closest point will be an end point of A */ + else + { + POINT2D G; /* Point on circle closest to A */ + G.x = C.x + (D.x - C.x) * radius_C / dist_C_D; + G.y = C.y + (D.y - C.y) * radius_C / dist_C_D; + + pt_in_arc = lw_pt_in_arc(&G, B1, B2, B3); + pt_in_seg = lw_pt_in_seg(&D, A1, A2); + + /* Closest point is on the interior of A and B */ + if (pt_in_arc && pt_in_seg) + return lw_dist2d_pt_pt(&D, &G, dl); + } + + /* Now we test the many combinations of end points with either */ + /* arcs or edges. Each previous check determined if the closest */ + /* potential point was within the arc/segment inscribed on the */ + /* line/circle holding the arc/segment. */ + + /* Closest point is in the arc, but not in the segment, so */ + /* one of the segment end points must be the closest. */ + if (pt_in_arc && !pt_in_seg) + { + lw_dist2d_pt_arc(A1, B1, B2, B3, dl); + lw_dist2d_pt_arc(A2, B1, B2, B3, dl); + return LW_TRUE; + } + /* or, one of the arc end points is the closest */ + else if (pt_in_seg && !pt_in_arc) + { + lw_dist2d_pt_seg(B1, A1, A2, dl); + lw_dist2d_pt_seg(B3, A1, A2, dl); + return LW_TRUE; + } + /* Finally, one of the end-point to end-point combos is the closest. */ + else + { + lw_dist2d_pt_pt(A1, B1, dl); + lw_dist2d_pt_pt(A1, B3, dl); + lw_dist2d_pt_pt(A2, B1, dl); + lw_dist2d_pt_pt(A2, B3, dl); + return LW_TRUE; + } + + return LW_FALSE; +} + +int +lw_dist2d_pt_arc(const POINT2D *P, const POINT2D *A1, const POINT2D *A2, const POINT2D *A3, DISTPTS *dl) +{ + double radius_A, d; + POINT2D C; /* center of circle defined by arc A */ + POINT2D X; /* point circle(A) where line from C to P crosses */ + + if (dl->mode < 0) + lwerror("lw_dist2d_pt_arc does not support maxdistance mode"); + + /* What if the arc is a point? */ + if (lw_arc_is_pt(A1, A2, A3)) + return lw_dist2d_pt_pt(P, A1, dl); + + /* Calculate centers and radii of circles. */ + radius_A = lw_arc_center(A1, A2, A3, &C); + + /* This "arc" is actually a line (A2 is colinear with A1,A3) */ + if (radius_A < 0.0) + return lw_dist2d_pt_seg(P, A1, A3, dl); + + /* Distance from point to center */ + d = distance2d_pt_pt(&C, P); + + /* P is the center of the circle */ + if (FP_EQUALS(d, 0.0)) + { + dl->distance = radius_A; + dl->p1 = *A1; + dl->p2 = *P; + return LW_TRUE; + } + + /* X is the point on the circle where the line from P to C crosses */ + X.x = C.x + (P->x - C.x) * radius_A / d; + X.y = C.y + (P->y - C.y) * radius_A / d; + + /* Is crossing point inside the arc? Or arc is actually circle? */ + if (p2d_same(A1, A3) || lw_pt_in_arc(&X, A1, A2, A3)) + { + lw_dist2d_pt_pt(P, &X, dl); + } + else + { + /* Distance is the minimum of the distances to the arc end points */ + lw_dist2d_pt_pt(A1, P, dl); + lw_dist2d_pt_pt(A3, P, dl); + } + return LW_TRUE; +} + +/* Auxiliary function to calculate the distance between 2 concentric arcs*/ +int lw_dist2d_arc_arc_concentric(const POINT2D *A1, + const POINT2D *A2, + const POINT2D *A3, + double radius_A, + const POINT2D *B1, + const POINT2D *B2, + const POINT2D *B3, + double radius_B, + const POINT2D *CENTER, + DISTPTS *dl); + +int +lw_dist2d_arc_arc(const POINT2D *A1, + const POINT2D *A2, + const POINT2D *A3, + const POINT2D *B1, + const POINT2D *B2, + const POINT2D *B3, + DISTPTS *dl) +{ + POINT2D CA, CB; /* Center points of arcs A and B */ + double radius_A, radius_B, d; /* Radii of arcs A and B */ + POINT2D D; /* Mid-point between the centers CA and CB */ + int pt_in_arc_A, pt_in_arc_B; /* Test whether potential intersection point is within the arc */ + + if (dl->mode != DIST_MIN) + lwerror("lw_dist2d_arc_arc only supports mindistance"); + + /* TODO: Handle case where arc is closed circle (A1 = A3) */ + + /* What if one or both of our "arcs" is actually a point? */ + if (lw_arc_is_pt(B1, B2, B3) && lw_arc_is_pt(A1, A2, A3)) + return lw_dist2d_pt_pt(B1, A1, dl); + else if (lw_arc_is_pt(B1, B2, B3)) + return lw_dist2d_pt_arc(B1, A1, A2, A3, dl); + else if (lw_arc_is_pt(A1, A2, A3)) + return lw_dist2d_pt_arc(A1, B1, B2, B3, dl); + + /* Calculate centers and radii of circles. */ + radius_A = lw_arc_center(A1, A2, A3, &CA); + radius_B = lw_arc_center(B1, B2, B3, &CB); + + /* Two co-linear arcs?!? That's two segments. */ + if (radius_A < 0 && radius_B < 0) + return lw_dist2d_seg_seg(A1, A3, B1, B3, dl); + + /* A is co-linear, delegate to lw_dist_seg_arc here. */ + if (radius_A < 0) + return lw_dist2d_seg_arc(A1, A3, B1, B2, B3, dl); + + /* B is co-linear, delegate to lw_dist_seg_arc here. */ + if (radius_B < 0) + return lw_dist2d_seg_arc(B1, B3, A1, A2, A3, dl); + + /* Center-center distance */ + d = distance2d_pt_pt(&CA, &CB); + + /* Concentric arcs */ + if (FP_EQUALS(d, 0.0)) + return lw_dist2d_arc_arc_concentric(A1, A2, A3, radius_A, B1, B2, B3, radius_B, &CA, dl); + + /* Make sure that arc "A" has the bigger radius */ + if (radius_B > radius_A) + { + const POINT2D *tmp; + POINT2D TP; /* Temporary point P */ + double td; + tmp = B1; + B1 = A1; + A1 = tmp; + tmp = B2; + B2 = A2; + A2 = tmp; + tmp = B3; + B3 = A3; + A3 = tmp; + TP = CB; + CB = CA; + CA = TP; + td = radius_B; + radius_B = radius_A; + radius_A = td; + } + + /* Circles touch at a point. Is that point within the arcs? */ + if (d == (radius_A + radius_B)) + { + D.x = CA.x + (CB.x - CA.x) * radius_A / d; + D.y = CA.y + (CB.y - CA.y) * radius_A / d; + + pt_in_arc_A = lw_pt_in_arc(&D, A1, A2, A3); + pt_in_arc_B = lw_pt_in_arc(&D, B1, B2, B3); + + /* Arcs do touch at D, return it */ + if (pt_in_arc_A && pt_in_arc_B) + { + dl->distance = 0.0; + dl->p1 = D; + dl->p2 = D; + return LW_TRUE; + } + } + /* Disjoint or contained circles don't intersect. Closest point may be on */ + /* the line joining CA to CB. */ + else if (d > (radius_A + radius_B) /* Disjoint */ || d < (radius_A - radius_B) /* Contained */) + { + POINT2D XA, XB; /* Points where the line from CA to CB cross their circle bounds */ + + /* Calculate hypothetical nearest points, the places on the */ + /* two circles where the center-center line crosses. If both */ + /* arcs contain their hypothetical points, that's the crossing distance */ + XA.x = CA.x + (CB.x - CA.x) * radius_A / d; + XA.y = CA.y + (CB.y - CA.y) * radius_A / d; + XB.x = CB.x + (CA.x - CB.x) * radius_B / d; + XB.y = CB.y + (CA.y - CB.y) * radius_B / d; + + pt_in_arc_A = lw_pt_in_arc(&XA, A1, A2, A3); + pt_in_arc_B = lw_pt_in_arc(&XB, B1, B2, B3); + + /* If the nearest points are both within the arcs, that's our answer */ + /* the shortest distance is at the nearest points */ + if (pt_in_arc_A && pt_in_arc_B) + { + return lw_dist2d_pt_pt(&XA, &XB, dl); + } + } + /* Circles cross at two points, are either of those points in both arcs? */ + /* http://paulbourke.net/geometry/2circle/ */ + else if (d < (radius_A + radius_B)) + { + POINT2D E, F; /* Points where circle(A) and circle(B) cross */ + /* Distance from CA to D */ + double a = (radius_A * radius_A - radius_B * radius_B + d * d) / (2 * d); + /* Distance from D to E or F */ + double h = sqrt(radius_A * radius_A - a * a); + + /* Location of D */ + D.x = CA.x + (CB.x - CA.x) * a / d; + D.y = CA.y + (CB.y - CA.y) * a / d; + + /* Start from D and project h units perpendicular to CA-D to get E */ + E.x = D.x + (D.y - CA.y) * h / a; + E.y = D.y + (D.x - CA.x) * h / a; + + /* Crossing point E contained in arcs? */ + pt_in_arc_A = lw_pt_in_arc(&E, A1, A2, A3); + pt_in_arc_B = lw_pt_in_arc(&E, B1, B2, B3); + + if (pt_in_arc_A && pt_in_arc_B) + { + dl->p1 = dl->p2 = E; + dl->distance = 0.0; + return LW_TRUE; + } + + /* Start from D and project h units perpendicular to CA-D to get F */ + F.x = D.x - (D.y - CA.y) * h / a; + F.y = D.y - (D.x - CA.x) * h / a; + + /* Crossing point F contained in arcs? */ + pt_in_arc_A = lw_pt_in_arc(&F, A1, A2, A3); + pt_in_arc_B = lw_pt_in_arc(&F, B1, B2, B3); + + if (pt_in_arc_A && pt_in_arc_B) + { + dl->p1 = dl->p2 = F; + dl->distance = 0.0; + return LW_TRUE; + } + } + else + { + lwerror("lw_dist2d_arc_arc: arcs neither touch, intersect nor are disjoint! INCONCEIVABLE!"); + return LW_FALSE; + } + + /* Closest point is in the arc A, but not in the arc B, so */ + /* one of the B end points must be the closest. */ + if (pt_in_arc_A && !pt_in_arc_B) + { + lw_dist2d_pt_arc(B1, A1, A2, A3, dl); + lw_dist2d_pt_arc(B3, A1, A2, A3, dl); + return LW_TRUE; + } + /* Closest point is in the arc B, but not in the arc A, so */ + /* one of the A end points must be the closest. */ + else if (pt_in_arc_B && !pt_in_arc_A) + { + lw_dist2d_pt_arc(A1, B1, B2, B3, dl); + lw_dist2d_pt_arc(A3, B1, B2, B3, dl); + return LW_TRUE; + } + /* Finally, one of the end-point to end-point combos is the closest. */ + else + { + lw_dist2d_pt_pt(A1, B1, dl); + lw_dist2d_pt_pt(A1, B3, dl); + lw_dist2d_pt_pt(A3, B1, dl); + lw_dist2d_pt_pt(A3, B3, dl); + return LW_TRUE; + } + + return LW_TRUE; +} + +int +lw_dist2d_arc_arc_concentric(const POINT2D *A1, + const POINT2D *A2, + const POINT2D *A3, + double radius_A, + const POINT2D *B1, + const POINT2D *B2, + const POINT2D *B3, + double radius_B, + const POINT2D *CENTER, + DISTPTS *dl) +{ + int seg_size; + double dist_sqr, shortest_sqr; + const POINT2D *P1; + const POINT2D *P2; + POINT2D proj; + + if (radius_A == radius_B) + { + /* Check if B1 or B3 are in the same side as A2 in the A1-A3 arc */ + seg_size = lw_segment_side(A1, A3, A2); + if (seg_size == lw_segment_side(A1, A3, B1)) + { + dl->p1 = *B1; + dl->p2 = *B1; + dl->distance = 0; + return LW_TRUE; + } + if (seg_size == lw_segment_side(A1, A3, B3)) + { + dl->p1 = *B3; + dl->p2 = *B3; + dl->distance = 0; + return LW_TRUE; + } + /* Check if A1 or A3 are in the same side as B2 in the B1-B3 arc */ + seg_size = lw_segment_side(B1, B3, B2); + if (seg_size == lw_segment_side(B1, B3, A1)) + { + dl->p1 = *A1; + dl->p2 = *A1; + dl->distance = 0; + return LW_TRUE; + } + if (seg_size == lw_segment_side(B1, B3, A3)) + { + dl->p1 = *A3; + dl->p2 = *A3; + dl->distance = 0; + return LW_TRUE; + } + } + else + { + /* Check if any projection of B ends are in A*/ + seg_size = lw_segment_side(A1, A3, A2); + + /* B1 */ + proj.x = CENTER->x + (B1->x - CENTER->x) * radius_A / radius_B; + proj.y = CENTER->y + (B1->y - CENTER->y) * radius_A / radius_B; + + if (seg_size == lw_segment_side(A1, A3, &proj)) + { + dl->p1 = proj; + dl->p2 = *B1; + dl->distance = fabs(radius_A - radius_B); + return LW_TRUE; + } + /* B3 */ + proj.x = CENTER->x + (B3->x - CENTER->x) * radius_A / radius_B; + proj.y = CENTER->y + (B3->y - CENTER->y) * radius_A / radius_B; + if (seg_size == lw_segment_side(A1, A3, &proj)) + { + dl->p1 = proj; + dl->p2 = *B3; + dl->distance = fabs(radius_A - radius_B); + return LW_TRUE; + } + + /* Now check projections of A in B */ + seg_size = lw_segment_side(B1, B3, B2); + + /* A1 */ + proj.x = CENTER->x + (A1->x - CENTER->x) * radius_B / radius_A; + proj.y = CENTER->y + (A1->y - CENTER->y) * radius_B / radius_A; + if (seg_size == lw_segment_side(B1, B3, &proj)) + { + dl->p1 = proj; + dl->p2 = *A1; + dl->distance = fabs(radius_A - radius_B); + return LW_TRUE; + } + + /* A3 */ + proj.x = CENTER->x + (A3->x - CENTER->x) * radius_B / radius_A; + proj.y = CENTER->y + (A3->y - CENTER->y) * radius_B / radius_A; + if (seg_size == lw_segment_side(B1, B3, &proj)) + { + dl->p1 = proj; + dl->p2 = *A3; + dl->distance = fabs(radius_A - radius_B); + return LW_TRUE; + } + } + + /* Check the shortest between the distances of the 4 ends */ + shortest_sqr = dist_sqr = distance2d_sqr_pt_pt(A1, B1); + P1 = A1; + P2 = B1; + + dist_sqr = distance2d_sqr_pt_pt(A1, B3); + if (dist_sqr < shortest_sqr) + { + shortest_sqr = dist_sqr; + P1 = A1; + P2 = B3; + } + + dist_sqr = distance2d_sqr_pt_pt(A3, B1); + if (dist_sqr < shortest_sqr) + { + shortest_sqr = dist_sqr; + P1 = A3; + P2 = B1; + } + + dist_sqr = distance2d_sqr_pt_pt(A3, B3); + if (dist_sqr < shortest_sqr) + { + shortest_sqr = dist_sqr; + P1 = A3; + P2 = B3; + } + + dl->p1 = *P1; + dl->p2 = *P2; + dl->distance = sqrt(shortest_sqr); + + return LW_TRUE; +} + +/** +Finds the shortest distance between two segments. +This function is changed so it is not doing any comparison of distance +but just sending every possible combination further to lw_dist2d_pt_seg +*/ +int +lw_dist2d_seg_seg(const POINT2D *A, const POINT2D *B, const POINT2D *C, const POINT2D *D, DISTPTS *dl) +{ + double s_top, s_bot, s; + double r_top, r_bot, r; + + /*A and B are the same point */ + if ((A->x == B->x) && (A->y == B->y)) + { + return lw_dist2d_pt_seg(A, C, D, dl); + } + + /*U and V are the same point */ + if ((C->x == D->x) && (C->y == D->y)) + { + dl->twisted = ((dl->twisted) * (-1)); + return lw_dist2d_pt_seg(D, A, B, dl); + } + + /* AB and CD are line segments */ + /* from comp.graphics.algo + + Solving the above for r and s yields + (Ay-Cy)(Dx-Cx)-(Ax-Cx)(Dy-Cy) + r = ----------------------------- (eqn 1) + (Bx-Ax)(Dy-Cy)-(By-Ay)(Dx-Cx) + + (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) + s = ----------------------------- (eqn 2) + (Bx-Ax)(Dy-Cy)-(By-Ay)(Dx-Cx) + Let P be the position vector of the intersection point, then + P=A+r(B-A) or + Px=Ax+r(Bx-Ax) + Py=Ay+r(By-Ay) + By examining the values of r & s, you can also determine some other limiting conditions: + If 0<=r<=1 & 0<=s<=1, intersection exists + r<0 or r>1 or s<0 or s>1 line segments do not intersect + If the denominator in eqn 1 is zero, AB & CD are parallel + If the numerator in eqn 1 is also zero, AB & CD are collinear. + + */ + r_top = (A->y - C->y) * (D->x - C->x) - (A->x - C->x) * (D->y - C->y); + r_bot = (B->x - A->x) * (D->y - C->y) - (B->y - A->y) * (D->x - C->x); + + s_top = (A->y - C->y) * (B->x - A->x) - (A->x - C->x) * (B->y - A->y); + s_bot = (B->x - A->x) * (D->y - C->y) - (B->y - A->y) * (D->x - C->x); + + if ((r_bot == 0) || (s_bot == 0)) + { + if ((lw_dist2d_pt_seg(A, C, D, dl)) && (lw_dist2d_pt_seg(B, C, D, dl))) + { + /* change the order of inputted geometries and that we notice by changing sign on dl->twisted*/ + dl->twisted *= -1; + return ((lw_dist2d_pt_seg(C, A, B, dl)) && (lw_dist2d_pt_seg(D, A, B, dl))); + } + else + return LW_FALSE; /* if any of the calls to lw_dist2d_pt_seg goes wrong we return false*/ + } + + s = s_top / s_bot; + r = r_top / r_bot; + + if (((r < 0) || (r > 1) || (s < 0) || (s > 1)) || (dl->mode == DIST_MAX)) + { + if ((lw_dist2d_pt_seg(A, C, D, dl)) && (lw_dist2d_pt_seg(B, C, D, dl))) + { + /* change the order of inputted geometries and that we notice by changing sign on dl->twisted*/ + dl->twisted *= -1; + return ((lw_dist2d_pt_seg(C, A, B, dl)) && (lw_dist2d_pt_seg(D, A, B, dl))); + } + else + return LW_FALSE; /* if any of the calls to lw_dist2d_pt_seg goes wrong we return false*/ + } + else + { + /* If there is intersection we identify the intersection point and return it but only if we are looking + * for mindistance */ + if (dl->mode == DIST_MIN) + { + POINT2D theP; + + if (((A->x == C->x) && (A->y == C->y)) || ((A->x == D->x) && (A->y == D->y))) + { + theP.x = A->x; + theP.y = A->y; + } + else if (((B->x == C->x) && (B->y == C->y)) || ((B->x == D->x) && (B->y == D->y))) + { + theP.x = B->x; + theP.y = B->y; + } + else + { + theP.x = A->x + r * (B->x - A->x); + theP.y = A->y + r * (B->y - A->y); + } + dl->distance = 0.0; + dl->p1 = theP; + dl->p2 = theP; + } + return LW_TRUE; + } +} + +/*------------------------------------------------------------------------------------------------------------ +End of Brute force functions +--------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------ +New faster distance calculations +--------------------------------------------------------------------------------------------------------------*/ + +/** + +The new faster calculation comparing pointarray to another pointarray +the arrays can come from both polygons and linestrings. +The naming is not good but comes from that it compares a +chosen selection of the points not all of them +*/ +int +lw_dist2d_fast_ptarray_ptarray(POINTARRAY *l1, POINTARRAY *l2, DISTPTS *dl, GBOX *box1, GBOX *box2) +{ + /*here we define two lists to hold our calculated "z"-values and the order number in the geometry*/ + + double k, thevalue; + float deltaX, deltaY, c1m, c2m; + POINT2D c1, c2; + const POINT2D *theP; + float min1X, max1X, max1Y, min1Y, min2X, max2X, max2Y, min2Y; + int t; + int n1 = l1->npoints; + int n2 = l2->npoints; + + LISTSTRUCT *list1, *list2; + list1 = (LISTSTRUCT *)lwalloc(sizeof(LISTSTRUCT) * n1); + list2 = (LISTSTRUCT *)lwalloc(sizeof(LISTSTRUCT) * n2); + + LWDEBUG(2, "lw_dist2d_fast_ptarray_ptarray is called"); + + max1X = box1->xmax; + min1X = box1->xmin; + max1Y = box1->ymax; + min1Y = box1->ymin; + max2X = box2->xmax; + min2X = box2->xmin; + max2Y = box2->ymax; + min2Y = box2->ymin; + /*we want the center of the bboxes, and calculate the slope between the centerpoints*/ + c1.x = min1X + (max1X - min1X) / 2; + c1.y = min1Y + (max1Y - min1Y) / 2; + c2.x = min2X + (max2X - min2X) / 2; + c2.y = min2Y + (max2Y - min2Y) / 2; + + deltaX = (c2.x - c1.x); + deltaY = (c2.y - c1.y); + + /*Here we calculate where the line perpendicular to the center-center line crosses the axes for each vertex + if the center-center line is vertical the perpendicular line will be horizontal and we find it's crossing the + Y-axes with z = y-kx */ + if ((deltaX * deltaX) < (deltaY * deltaY)) /*North or South*/ + { + k = -deltaX / deltaY; + for (t = 0; t < n1; t++) /*for each segment in L1 */ + { + theP = getPoint2d_cp(l1, t); + thevalue = theP->y - (k * theP->x); + list1[t].themeasure = thevalue; + list1[t].pnr = t; + } + for (t = 0; t < n2; t++) /*for each segment in L2*/ + { + theP = getPoint2d_cp(l2, t); + thevalue = theP->y - (k * theP->x); + list2[t].themeasure = thevalue; + list2[t].pnr = t; + } + c1m = c1.y - (k * c1.x); + c2m = c2.y - (k * c2.x); + } + + /*if the center-center line is horizontal the perpendicular line will be vertical. To eliminate problems with + dividing by zero we are here mirroring the coordinate-system and we find it's crossing the X-axes with z = + x-(1/k)y */ + else /*West or East*/ + { + k = -deltaY / deltaX; + for (t = 0; t < n1; t++) /*for each segment in L1 */ + { + theP = getPoint2d_cp(l1, t); + thevalue = theP->x - (k * theP->y); + list1[t].themeasure = thevalue; + list1[t].pnr = t; + /* lwnotice("l1 %d, measure=%f",t,thevalue ); */ + } + for (t = 0; t < n2; t++) /*for each segment in L2*/ + { + theP = getPoint2d_cp(l2, t); + thevalue = theP->x - (k * theP->y); + list2[t].themeasure = thevalue; + list2[t].pnr = t; + /* lwnotice("l2 %d, measure=%f",t,thevalue ); */ + } + c1m = c1.x - (k * c1.y); + c2m = c2.x - (k * c2.y); + } + + /*we sort our lists by the calculated values*/ + qsort(list1, n1, sizeof(LISTSTRUCT), struct_cmp_by_measure); + qsort(list2, n2, sizeof(LISTSTRUCT), struct_cmp_by_measure); + + if (c1m < c2m) + { + if (!lw_dist2d_pre_seg_seg(l1, l2, list1, list2, k, dl)) + { + lwfree(list1); + lwfree(list2); + return LW_FALSE; + } + } + else + { + dl->twisted = ((dl->twisted) * (-1)); + if (!lw_dist2d_pre_seg_seg(l2, l1, list2, list1, k, dl)) + { + lwfree(list1); + lwfree(list2); + return LW_FALSE; + } + } + lwfree(list1); + lwfree(list2); + return LW_TRUE; +} + +int +struct_cmp_by_measure(const void *a, const void *b) +{ + LISTSTRUCT *ia = (LISTSTRUCT *)a; + LISTSTRUCT *ib = (LISTSTRUCT *)b; + return (ia->themeasure > ib->themeasure) ? 1 : ((ia->themeasure < ib->themeasure) ? -1 : 0); +} + +/** preparation before lw_dist2d_seg_seg. */ +int +lw_dist2d_pre_seg_seg(POINTARRAY *l1, POINTARRAY *l2, LISTSTRUCT *list1, LISTSTRUCT *list2, double k, DISTPTS *dl) +{ + const POINT2D *p1, *p2, *p3, *p4, *p01, *p02; + int pnr1, pnr2, pnr3, pnr4, n1, n2, i, u, r, twist; + double maxmeasure; + n1 = l1->npoints; + n2 = l2->npoints; + + LWDEBUG(2, "lw_dist2d_pre_seg_seg is called"); + + p1 = getPoint2d_cp(l1, list1[0].pnr); + p3 = getPoint2d_cp(l2, list2[0].pnr); + lw_dist2d_pt_pt(p1, p3, dl); + maxmeasure = sqrt(dl->distance * dl->distance + (dl->distance * dl->distance * k * k)); + twist = dl->twisted; /*to keep the incoming order between iterations*/ + for (i = (n1 - 1); i >= 0; --i) + { + /*we break this iteration when we have checked every point closer to our perpendicular "checkline" than + * our shortest found distance*/ + if (((list2[0].themeasure - list1[i].themeasure)) > maxmeasure) + break; + /*because we are not iterating in the original point order we have to check the segment before and after + * every point*/ + for (r = -1; r <= 1; r += 2) + { + pnr1 = list1[i].pnr; + p1 = getPoint2d_cp(l1, pnr1); + if (pnr1 + r < 0) + { + p01 = getPoint2d_cp(l1, (n1 - 1)); + if ((p1->x == p01->x) && (p1->y == p01->y)) + pnr2 = (n1 - 1); + else + pnr2 = pnr1; /* if it is a line and the last and first point is not the same we + avoid the edge between start and end this way*/ + } + + else if (pnr1 + r > (n1 - 1)) + { + p01 = getPoint2d_cp(l1, 0); + if ((p1->x == p01->x) && (p1->y == p01->y)) + pnr2 = 0; + else + pnr2 = pnr1; /* if it is a line and the last and first point is not the same we + avoid the edge between start and end this way*/ + } + else + pnr2 = pnr1 + r; + + p2 = getPoint2d_cp(l1, pnr2); + for (u = 0; u < n2; ++u) + { + if (((list2[u].themeasure - list1[i].themeasure)) >= maxmeasure) + break; + pnr3 = list2[u].pnr; + p3 = getPoint2d_cp(l2, pnr3); + if (pnr3 == 0) + { + p02 = getPoint2d_cp(l2, (n2 - 1)); + if ((p3->x == p02->x) && (p3->y == p02->y)) + pnr4 = (n2 - 1); + else + pnr4 = pnr3; /* if it is a line and the last and first point is not the + same we avoid the edge between start and end this way*/ + } + else + pnr4 = pnr3 - 1; + + p4 = getPoint2d_cp(l2, pnr4); + dl->twisted = twist; + if (!lw_dist2d_selected_seg_seg(p1, p2, p3, p4, dl)) + return LW_FALSE; + + if (pnr3 >= (n2 - 1)) + { + p02 = getPoint2d_cp(l2, 0); + if ((p3->x == p02->x) && (p3->y == p02->y)) + pnr4 = 0; + else + pnr4 = pnr3; /* if it is a line and the last and first point is not the + same we avoid the edge between start and end this way*/ + } + + else + pnr4 = pnr3 + 1; + + p4 = getPoint2d_cp(l2, pnr4); + dl->twisted = twist; /*we reset the "twist" for each iteration*/ + if (!lw_dist2d_selected_seg_seg(p1, p2, p3, p4, dl)) + return LW_FALSE; + /*here we "translate" the found mindistance so it can be compared to our "z"-values*/ + maxmeasure = sqrt(dl->distance * dl->distance + (dl->distance * dl->distance * k * k)); + } + } + } + + return LW_TRUE; +} + +/** + This is the same function as lw_dist2d_seg_seg but + without any calculations to determine intersection since we + already know they do not intersect +*/ +int +lw_dist2d_selected_seg_seg(const POINT2D *A, const POINT2D *B, const POINT2D *C, const POINT2D *D, DISTPTS *dl) +{ + /*A and B are the same point */ + if ((A->x == B->x) && (A->y == B->y)) + { + return lw_dist2d_pt_seg(A, C, D, dl); + } + /*U and V are the same point */ + + if ((C->x == D->x) && (C->y == D->y)) + { + dl->twisted *= -1; + return lw_dist2d_pt_seg(D, A, B, dl); + } + + if ((lw_dist2d_pt_seg(A, C, D, dl)) && (lw_dist2d_pt_seg(B, C, D, dl))) + { + /* change the order of inputted geometries and that we notice by changing sign on dl->twisted */ + dl->twisted *= -1; + return ((lw_dist2d_pt_seg(C, A, B, dl)) && (lw_dist2d_pt_seg(D, A, B, dl))); + } + else + return LW_FALSE; /* if any of the calls to lw_dist2d_pt_seg goes wrong we return false*/ +} + +/*------------------------------------------------------------------------------------------------------------ +End of New faster distance calculations +--------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------ +Functions in common for Brute force and new calculation +--------------------------------------------------------------------------------------------------------------*/ + +/** +lw_dist2d_comp from p to line A->B +This one is now sending every occasion to lw_dist2d_pt_pt +Before it was handling occasions where r was between 0 and 1 internally +and just returning the distance without identifying the points. +To get this points it was necessary to change and it also showed to be about 10%faster. +*/ +int +lw_dist2d_pt_seg(const POINT2D *p, const POINT2D *A, const POINT2D *B, DISTPTS *dl) +{ + POINT2D c; + double r; + /*if start==end, then use pt distance */ + if ((A->x == B->x) && (A->y == B->y)) + return lw_dist2d_pt_pt(p, A, dl); + + /* + * otherwise, we use comp.graphics.algorithms + * Frequently Asked Questions method + * + * (1) AC dot AB + * r = --------- + * ||AB||^2 + * r has the following meaning: + * r=0 P = A + * r=1 P = B + * r<0 P is on the backward extension of AB + * r>1 P is on the forward extension of AB + * 0x - A->x) * (B->x - A->x) + (p->y - A->y) * (B->y - A->y)) / + ((B->x - A->x) * (B->x - A->x) + (B->y - A->y) * (B->y - A->y)); + + /*This is for finding the maxdistance. + the maxdistance have to be between two vertexes, compared to mindistance which can be between two vertexes.*/ + if (dl->mode == DIST_MAX) + { + if (r >= 0.5) + return lw_dist2d_pt_pt(p, A, dl); + else /* (r < 0.5) */ + return lw_dist2d_pt_pt(p, B, dl); + } + + if (r < 0) /*If p projected on the line is outside point A*/ + return lw_dist2d_pt_pt(p, A, dl); + if (r >= 1) /*If p projected on the line is outside point B or on point B*/ + return lw_dist2d_pt_pt(p, B, dl); + + /*If the point p is on the segment this is a more robust way to find out that*/ + if ((((A->y - p->y) * (B->x - A->x) == (A->x - p->x) * (B->y - A->y))) && (dl->mode == DIST_MIN)) + { + dl->distance = 0.0; + dl->p1 = *p; + dl->p2 = *p; + } + + /*If the projection of point p on the segment is between A and B + then we find that "point on segment" and send it to lw_dist2d_pt_pt*/ + c.x = A->x + r * (B->x - A->x); + c.y = A->y + r * (B->y - A->y); + + return lw_dist2d_pt_pt(p, &c, dl); +} + +/** Compares incoming points and stores the points closest to each other or most far away from each other depending on + * dl->mode (max or min) */ +int +lw_dist2d_pt_pt(const POINT2D *thep1, const POINT2D *thep2, DISTPTS *dl) +{ + double hside = thep2->x - thep1->x; + double vside = thep2->y - thep1->y; + double dist = sqrt(hside * hside + vside * vside); + + /*multiplication with mode to handle mindistance (mode=1) and maxdistance (mode = (-1)*/ + if (((dl->distance - dist) * (dl->mode)) > 0) + { + dl->distance = dist; + + /* To get the points in right order. twisted is updated between 1 and (-1) every time the order is + * changed earlier in the chain*/ + if (dl->twisted > 0) + { + dl->p1 = *thep1; + dl->p2 = *thep2; + } + else + { + dl->p1 = *thep2; + dl->p2 = *thep1; + } + } + return LW_TRUE; +} + +/*------------------------------------------------------------------------------------------------------------ +End of Functions in common for Brute force and new calculation +--------------------------------------------------------------------------------------------------------------*/ + +inline double +distance2d_pt_pt(const POINT2D *p1, const POINT2D *p2) +{ + double hside = p2->x - p1->x; + double vside = p2->y - p1->y; + + return hypot(hside, vside); +} + +/* return distance squared, useful to avoid sqrt calculations */ +double +distance2d_sqr_pt_seg(const POINT2D *C, const POINT2D *A, const POINT2D *B) +{ + /*if start==end, then use pt distance */ + if ((A->x == B->x) && (A->y == B->y)) + return distance2d_sqr_pt_pt(C, A); + + /* + * otherwise, we use comp.graphics.algorithms + * Frequently Asked Questions method + * + * (1) AC dot AB + * r = --------- + * ||AB||^2 + * r has the following meaning: + * r=0 P = A + * r=1 P = B + * r<0 P is on the backward extension of AB + * r>1 P is on the forward extension of AB + * 0x - A->x); + double ba_y = (B->y - A->y); + double ab_length_sqr = (ba_x * ba_x + ba_y * ba_y); + double ca_x = (C->x - A->x); + double ca_y = (C->y - A->y); + double dot_ac_ab = (ca_x * ba_x + ca_y * ba_y); + + if (dot_ac_ab <= 0) + return distance2d_sqr_pt_pt(C, A); + if (dot_ac_ab >= ab_length_sqr) + return distance2d_sqr_pt_pt(C, B); + + /* + * (2) + * (Ay-Cy)(Bx-Ax)-(Ax-Cx)(By-Ay) + * s = ----------------------------- + * L^2 + * + * Then the distance from C to P = |s|*L. + * + */ + + double s_numerator = ca_x * ba_y - ca_y * ba_x; + + /* Distance = (s_num / ab) * (s_num / ab) * ab == s_num * s_num / ab) */ + return s_numerator * s_numerator / ab_length_sqr; +} + +/** + * Compute the azimuth of segment AB in radians. + * Return 0 on exception (same point), 1 otherwise. + */ +int +azimuth_pt_pt(const POINT2D *A, const POINT2D *B, double *d) +{ + if (A->x == B->x && A->y == B->y) + return LW_FALSE; + *d = fmod(2 * M_PI + M_PI / 2 - atan2(B->y - A->y, B->x - A->x), 2 * M_PI); + return LW_TRUE; +} + +/** + * Azimuth is angle in radians from vertical axis + * + */ +int +project_pt(const POINT2D *P, double distance, double azimuth, POINT2D *R) +{ + const double TWOPI = 2.0 * M_PI; + double slope; + /* Deal with azimuth out of (-360,360) range */ + int orbits = floor(azimuth / TWOPI); + azimuth -= TWOPI * orbits; + /* Convert from azimuth to conventional slope */ + slope = TWOPI - azimuth + M_PI_2; + if (slope > 0 && slope > TWOPI) slope -= TWOPI; + if (slope < 0 && slope < -TWOPI) slope += TWOPI; + + double dx = cos(slope) * distance; + double dy = sin(slope) * distance; + R->x = P->x + dx; + R->y = P->y + dy; + return LW_TRUE; +} + +/** + * Azimuth is angle in radians from vertical axis + * + */ +int +project_pt_pt(const POINT4D *A, const POINT4D *B, double distance, POINT4D *R) +{ + /* Convert from azimuth to conventional slope */ + double len = distance2d_pt_pt((const POINT2D *)A, (const POINT2D *)B); + double prop = distance / len; + double dx = (B->x - A->x) * prop; + double dy = (B->y - A->y) * prop; + double dz = (B->z - A->z) * prop; + double dm = (B->m - A->m) * prop; + R->x = B->x + dx; + R->y = B->y + dy; + if (isfinite(dz)) R->z = B->z + dz; + if (isfinite(dm)) R->m = B->m + dm; + return LW_TRUE; +} + diff --git a/mgist-postgis/liblwgeom/measures.h b/mgist-postgis/liblwgeom/measures.h new file mode 100644 index 0000000..9cf0fb9 --- /dev/null +++ b/mgist-postgis/liblwgeom/measures.h @@ -0,0 +1,136 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2010 Nicklas Avén + * + **********************************************************************/ + + +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2010 Nicklas Avén + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#ifndef _MEASURES_H +#define _MEASURES_H 1 + +#include "liblwgeom_internal.h" + +/* for the measure functions*/ +#define DIST_MAX -1 +#define DIST_MIN 1 + +/** +* Structure used in distance-calculations +*/ +typedef struct +{ + double distance; /*the distance between p1 and p2*/ + POINT2D p1; + POINT2D p2; + int mode; /*the direction of looking, if thedir = -1 then we look for maxdistance and if it is 1 then we look for mindistance*/ + int twisted; /*To preserve the order of incoming points to match the first and second point in shortest and longest line*/ + double tolerance; /*the tolerance for dwithin and dfullywithin*/ +} DISTPTS; + +typedef struct +{ + double themeasure; /*a value calculated to compare distances*/ + int pnr; /*pointnumber. the ordernumber of the point*/ +} LISTSTRUCT; + + +/* +* Preprocessing functions +*/ +int lw_dist2d_comp(const LWGEOM *lw1, const LWGEOM *lw2, DISTPTS *dl); +int lw_dist2d_distribute_bruteforce(const LWGEOM *lwg1, const LWGEOM *lwg2, DISTPTS *dl); +int lw_dist2d_recursive(const LWGEOM *lwg1, const LWGEOM *lwg2, DISTPTS *dl); +int lw_dist2d_check_overlap(LWGEOM *lwg1, LWGEOM *lwg2); +int lw_dist2d_distribute_fast(LWGEOM *lwg1, LWGEOM *lwg2, DISTPTS *dl); + +/* +* Brute force functions +*/ +int lw_dist2d_pt_ptarray(const POINT2D *p, POINTARRAY *pa, DISTPTS *dl); +int lw_dist2d_pt_ptarrayarc(const POINT2D *p, const POINTARRAY *pa, DISTPTS *dl); +int lw_dist2d_ptarray_ptarray(POINTARRAY *l1, POINTARRAY *l2, DISTPTS *dl); +int lw_dist2d_ptarray_ptarrayarc(const POINTARRAY *pa, const POINTARRAY *pb, DISTPTS *dl); +int lw_dist2d_ptarrayarc_ptarrayarc(const POINTARRAY *pa, const POINTARRAY *pb, DISTPTS *dl); +int lw_dist2d_ptarray_poly(POINTARRAY *pa, LWPOLY *poly, DISTPTS *dl); +int lw_dist2d_point_point(LWPOINT *point1, LWPOINT *point2, DISTPTS *dl); +int lw_dist2d_point_line(LWPOINT *point, LWLINE *line, DISTPTS *dl); +int lw_dist2d_point_tri(LWPOINT *point, LWTRIANGLE *tri, DISTPTS *dl); +int lw_dist2d_point_circstring(LWPOINT *point, LWCIRCSTRING *circ, DISTPTS *dl); +int lw_dist2d_point_poly(LWPOINT *point, LWPOLY *poly, DISTPTS *dl); +int lw_dist2d_point_curvepoly(LWPOINT *point, LWCURVEPOLY *poly, DISTPTS *dl); +int lw_dist2d_line_line(LWLINE *line1, LWLINE *line2, DISTPTS *dl); +int lw_dist2d_line_tri(LWLINE *line, LWTRIANGLE *tri, DISTPTS *dl); +int lw_dist2d_line_circstring(LWLINE *line1, LWCIRCSTRING *line2, DISTPTS *dl); +int lw_dist2d_line_poly(LWLINE *line, LWPOLY *poly, DISTPTS *dl); +int lw_dist2d_line_curvepoly(LWLINE *line, LWCURVEPOLY *poly, DISTPTS *dl); +int lw_dist2d_tri_tri(LWTRIANGLE *tri1, LWTRIANGLE *tri2, DISTPTS *dl); +int lw_dist2d_tri_circstring(LWTRIANGLE *tri, LWCIRCSTRING *line, DISTPTS *dl); +int lw_dist2d_tri_poly(LWTRIANGLE *tri, LWPOLY *poly, DISTPTS *dl); +int lw_dist2d_tri_curvepoly(LWTRIANGLE *tri, LWCURVEPOLY *poly, DISTPTS *dl); +int lw_dist2d_circstring_circstring(LWCIRCSTRING *line1, LWCIRCSTRING *line2, DISTPTS *dl); +int lw_dist2d_circstring_poly(LWCIRCSTRING *circ, LWPOLY *poly, DISTPTS *dl); +int lw_dist2d_circstring_curvepoly(LWCIRCSTRING *circ, LWCURVEPOLY *poly, DISTPTS *dl); +int lw_dist2d_poly_poly(LWPOLY *poly1, LWPOLY *poly2, DISTPTS *dl); +int lw_dist2d_poly_curvepoly(LWPOLY *poly1, LWCURVEPOLY *curvepoly2, DISTPTS *dl); +int lw_dist2d_curvepoly_curvepoly(LWCURVEPOLY *poly1, LWCURVEPOLY *poly2, DISTPTS *dl); + +/* +* New faster distance calculations +*/ +int lw_dist2d_pre_seg_seg(POINTARRAY *l1, POINTARRAY *l2,LISTSTRUCT *list1, LISTSTRUCT *list2,double k, DISTPTS *dl); +int lw_dist2d_selected_seg_seg(const POINT2D *A, const POINT2D *B, const POINT2D *C, const POINT2D *D, DISTPTS *dl); +int struct_cmp_by_measure(const void *a, const void *b); +int lw_dist2d_fast_ptarray_ptarray(POINTARRAY *l1,POINTARRAY *l2, DISTPTS *dl, GBOX *box1, GBOX *box2); + +/* +* Distance calculation primitives. +*/ +int lw_dist2d_pt_pt (const POINT2D *P, const POINT2D *Q, DISTPTS *dl); +int lw_dist2d_pt_seg (const POINT2D *P, const POINT2D *A1, const POINT2D *A2, DISTPTS *dl); +int lw_dist2d_pt_arc (const POINT2D *P, const POINT2D *A1, const POINT2D *A2, const POINT2D *A3, DISTPTS *dl); +int lw_dist2d_seg_seg(const POINT2D *A1, const POINT2D *A2, const POINT2D *B1, const POINT2D *B2, DISTPTS *dl); +int lw_dist2d_seg_arc(const POINT2D *A1, const POINT2D *A2, const POINT2D *B1, const POINT2D *B2, const POINT2D *B3, DISTPTS *dl); +int lw_dist2d_arc_arc(const POINT2D *A1, const POINT2D *A2, const POINT2D *A3, const POINT2D *B1, const POINT2D *B2, const POINT2D* B3, DISTPTS *dl); +void lw_dist2d_distpts_init(DISTPTS *dl, int mode); + +/* +* Length primitives +*/ +double lw_arc_length(const POINT2D *A1, const POINT2D *A2, const POINT2D *A3); + +/* +* Geometry returning functions +*/ +LWGEOM *lw_dist2d_distancepoint(const LWGEOM *lw1, const LWGEOM *lw2, int32_t srid, int mode); +LWGEOM *lw_dist2d_distanceline(const LWGEOM *lw1, const LWGEOM *lw2, int32_t srid, int mode); + +#endif /* !defined _MEASURES_H */ diff --git a/mgist-postgis/liblwgeom/measures3d.c b/mgist-postgis/liblwgeom/measures3d.c new file mode 100644 index 0000000..6f2996b --- /dev/null +++ b/mgist-postgis/liblwgeom/measures3d.c @@ -0,0 +1,1688 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2011 Nicklas Avén + * Copyright 2019 Darafei Praliaskouski + * + **********************************************************************/ + +#include +#include + +#include "measures3d.h" +#include "lwrandom.h" +#include "lwgeom_log.h" + +static inline int +get_3dvector_from_points(POINT3DZ *p1, POINT3DZ *p2, VECTOR3D *v) +{ + v->x = p2->x - p1->x; + v->y = p2->y - p1->y; + v->z = p2->z - p1->z; + + return (!FP_IS_ZERO(v->x) || !FP_IS_ZERO(v->y) || !FP_IS_ZERO(v->z)); +} + +static inline int +get_3dcross_product(VECTOR3D *v1, VECTOR3D *v2, VECTOR3D *v) +{ + v->x = (v1->y * v2->z) - (v1->z * v2->y); + v->y = (v1->z * v2->x) - (v1->x * v2->z); + v->z = (v1->x * v2->y) - (v1->y * v2->x); + + return (!FP_IS_ZERO(v->x) || !FP_IS_ZERO(v->y) || !FP_IS_ZERO(v->z)); +} + +/** +This function is used to create a vertical line used for cases where one if the +geometries lacks z-values. The vertical line crosses the 2d point that is closest +and the z-range is from maxz to minz in the geometry that has z values. +*/ +static LWGEOM * +create_v_line(const LWGEOM *lwgeom, double x, double y, int32_t srid) +{ + + LWPOINT *lwpoints[2]; + GBOX gbox; + int rv = lwgeom_calculate_gbox(lwgeom, &gbox); + + if (rv == LW_FAILURE) + return NULL; + + lwpoints[0] = lwpoint_make3dz(srid, x, y, gbox.zmin); + lwpoints[1] = lwpoint_make3dz(srid, x, y, gbox.zmax); + + return (LWGEOM *)lwline_from_ptarray(srid, 2, lwpoints); +} + +LWGEOM * +lwgeom_closest_line_3d(const LWGEOM *lw1, const LWGEOM *lw2) +{ + return lw_dist3d_distanceline(lw1, lw2, lw1->srid, DIST_MIN); +} + +LWGEOM * +lwgeom_furthest_line_3d(LWGEOM *lw1, LWGEOM *lw2) +{ + return lw_dist3d_distanceline(lw1, lw2, lw1->srid, DIST_MAX); +} + +LWGEOM * +lwgeom_closest_point_3d(const LWGEOM *lw1, const LWGEOM *lw2) +{ + return lw_dist3d_distancepoint(lw1, lw2, lw1->srid, DIST_MIN); +} + +/** +Function initializing 3dshortestline and 3dlongestline calculations. +*/ +LWGEOM * +lw_dist3d_distanceline(const LWGEOM *lw1, const LWGEOM *lw2, int32_t srid, int mode) +{ + LWDEBUG(2, "lw_dist3d_distanceline is called"); + double x1, x2, y1, y2, z1, z2, x, y; + double initdistance = (mode == DIST_MIN ? DBL_MAX : -1.0); + DISTPTS3D thedl; + LWPOINT *lwpoints[2]; + LWGEOM *result; + + thedl.mode = mode; + thedl.distance = initdistance; + thedl.tolerance = 0.0; + + /* Check if we really have 3D geometries + * If not, send it to 2D-calculations which will give the same result + * as an infinite z-value at one or two of the geometries */ + if (!lwgeom_has_z(lw1) || !lwgeom_has_z(lw2)) + { + + lwnotice( + "One or both of the geometries is missing z-value. The unknown z-value will be regarded as \"any value\""); + + if (!lwgeom_has_z(lw1) && !lwgeom_has_z(lw2)) + return lw_dist2d_distanceline(lw1, lw2, srid, mode); + + DISTPTS thedl2d; + thedl2d.mode = mode; + thedl2d.distance = initdistance; + thedl2d.tolerance = 0.0; + if (!lw_dist2d_comp(lw1, lw2, &thedl2d)) + { + /* should never get here. all cases ought to be error handled earlier */ + lwerror("Some unspecified error."); + result = (LWGEOM *)lwcollection_construct_empty(COLLECTIONTYPE, srid, 0, 0); + } + LWGEOM *vertical_line; + if (!lwgeom_has_z(lw1)) + { + x = thedl2d.p1.x; + y = thedl2d.p1.y; + + vertical_line = create_v_line(lw2, x, y, srid); + if (!lw_dist3d_recursive(vertical_line, lw2, &thedl)) + { + /* should never get here. all cases ought to be error handled earlier */ + lwfree(vertical_line); + lwerror("Some unspecified error."); + result = (LWGEOM *)lwcollection_construct_empty(COLLECTIONTYPE, srid, 0, 0); + } + lwfree(vertical_line); + } + if (!lwgeom_has_z(lw2)) + { + x = thedl2d.p2.x; + y = thedl2d.p2.y; + + vertical_line = create_v_line(lw1, x, y, srid); + if (!lw_dist3d_recursive(lw1, vertical_line, &thedl)) + { + /* should never get here. all cases ought to be error handled earlier */ + lwfree(vertical_line); + lwerror("Some unspecified error."); + return (LWGEOM *)lwcollection_construct_empty(COLLECTIONTYPE, srid, 0, 0); + } + lwfree(vertical_line); + } + } + else + { + if (!lw_dist3d_recursive(lw1, lw2, &thedl)) + { + /* should never get here. all cases ought to be error handled earlier */ + lwerror("Some unspecified error."); + result = (LWGEOM *)lwcollection_construct_empty(COLLECTIONTYPE, srid, 0, 0); + } + } + /*if thedl.distance is unchanged there where only empty geometries input*/ + if (thedl.distance == initdistance) + { + LWDEBUG(3, "didn't find geometries to measure between, returning null"); + result = (LWGEOM *)lwcollection_construct_empty(COLLECTIONTYPE, srid, 0, 0); + } + else + { + x1 = thedl.p1.x; + y1 = thedl.p1.y; + z1 = thedl.p1.z; + x2 = thedl.p2.x; + y2 = thedl.p2.y; + z2 = thedl.p2.z; + + lwpoints[0] = lwpoint_make3dz(srid, x1, y1, z1); + lwpoints[1] = lwpoint_make3dz(srid, x2, y2, z2); + + result = (LWGEOM *)lwline_from_ptarray(srid, 2, lwpoints); + } + + return result; +} + +/** +Function initializing 3dclosestpoint calculations. +*/ +LWGEOM * +lw_dist3d_distancepoint(const LWGEOM *lw1, const LWGEOM *lw2, int32_t srid, int mode) +{ + + double x, y, z; + DISTPTS3D thedl; + double initdistance = DBL_MAX; + LWGEOM *result; + + thedl.mode = mode; + thedl.distance = initdistance; + thedl.tolerance = 0; + + LWDEBUG(2, "lw_dist3d_distancepoint is called"); + + /* Check if we really have 3D geometries + * If not, send it to 2D-calculations which will give the same result + * as an infinite z-value at one or two of the geometries + */ + if (!lwgeom_has_z(lw1) || !lwgeom_has_z(lw2)) + { + lwnotice( + "One or both of the geometries is missing z-value. The unknown z-value will be regarded as \"any value\""); + + if (!lwgeom_has_z(lw1) && !lwgeom_has_z(lw2)) + return lw_dist2d_distancepoint(lw1, lw2, srid, mode); + + DISTPTS thedl2d; + thedl2d.mode = mode; + thedl2d.distance = initdistance; + thedl2d.tolerance = 0.0; + if (!lw_dist2d_comp(lw1, lw2, &thedl2d)) + { + /* should never get here. all cases ought to be error handled earlier */ + lwerror("Some unspecified error."); + return (LWGEOM *)lwcollection_construct_empty(COLLECTIONTYPE, srid, 0, 0); + } + + LWGEOM *vertical_line; + if (!lwgeom_has_z(lw1)) + { + x = thedl2d.p1.x; + y = thedl2d.p1.y; + + vertical_line = create_v_line(lw2, x, y, srid); + if (!lw_dist3d_recursive(vertical_line, lw2, &thedl)) + { + /* should never get here. all cases ought to be error handled earlier */ + lwfree(vertical_line); + lwerror("Some unspecified error."); + return (LWGEOM *)lwcollection_construct_empty(COLLECTIONTYPE, srid, 0, 0); + } + lwfree(vertical_line); + } + + if (!lwgeom_has_z(lw2)) + { + x = thedl2d.p2.x; + y = thedl2d.p2.y; + + vertical_line = create_v_line(lw1, x, y, srid); + if (!lw_dist3d_recursive(lw1, vertical_line, &thedl)) + { + /* should never get here. all cases ought to be error handled earlier */ + lwfree(vertical_line); + lwerror("Some unspecified error."); + result = (LWGEOM *)lwcollection_construct_empty(COLLECTIONTYPE, srid, 0, 0); + } + lwfree(vertical_line); + } + } + else + { + if (!lw_dist3d_recursive(lw1, lw2, &thedl)) + { + /* should never get here. all cases ought to be error handled earlier */ + lwerror("Some unspecified error."); + result = (LWGEOM *)lwcollection_construct_empty(COLLECTIONTYPE, srid, 0, 0); + } + } + if (thedl.distance == initdistance) + { + LWDEBUG(3, "didn't find geometries to measure between, returning null"); + result = (LWGEOM *)lwcollection_construct_empty(COLLECTIONTYPE, srid, 0, 0); + } + else + { + x = thedl.p1.x; + y = thedl.p1.y; + z = thedl.p1.z; + result = (LWGEOM *)lwpoint_make3dz(srid, x, y, z); + } + + return result; +} + +/** +Function initializing 3d max distance calculation +*/ +double +lwgeom_maxdistance3d(const LWGEOM *lw1, const LWGEOM *lw2) +{ + return lwgeom_maxdistance3d_tolerance(lw1, lw2, 0.0); +} + +/** +Function handling 3d max distance calculations and dfullywithin calculations. +The difference is just the tolerance. +*/ +double +lwgeom_maxdistance3d_tolerance(const LWGEOM *lw1, const LWGEOM *lw2, double tolerance) +{ + if (!lwgeom_has_z(lw1) || !lwgeom_has_z(lw2)) + { + lwnotice( + "One or both of the geometries is missing z-value. The unknown z-value will be regarded as \"any value\""); + return lwgeom_maxdistance2d_tolerance(lw1, lw2, tolerance); + } + DISTPTS3D thedl; + LWDEBUG(2, "lwgeom_maxdistance3d_tolerance is called"); + thedl.mode = DIST_MAX; + thedl.distance = -1; + thedl.tolerance = tolerance; + if (lw_dist3d_recursive(lw1, lw2, &thedl)) + return thedl.distance; + + /* should never get here. all cases ought to be error handled earlier */ + lwerror("Some unspecified error."); + return -1; +} + +/** + Function initializing 3d min distance calculation +*/ +double +lwgeom_mindistance3d(const LWGEOM *lw1, const LWGEOM *lw2) +{ + return lwgeom_mindistance3d_tolerance(lw1, lw2, 0.0); +} + +static inline int +gbox_contains_3d(const GBOX *g1, const GBOX *g2) +{ + return (g2->xmin >= g1->xmin) && (g2->xmax <= g1->xmax) && (g2->ymin >= g1->ymin) && (g2->ymax <= g1->ymax) && + (g2->zmin >= g1->zmin) && (g2->zmax <= g1->zmax); +} + +static inline int +lwgeom_solid_contains_lwgeom(const LWGEOM *solid, const LWGEOM *g) +{ + const GBOX *b1; + const GBOX *b2; + + /* If solid isn't solid it can't contain anything */ + if (!FLAGS_GET_SOLID(solid->flags)) + return LW_FALSE; + + b1 = lwgeom_get_bbox(solid); + b2 = lwgeom_get_bbox(g); + + /* If box won't contain box, shape won't contain shape */ + if (!gbox_contains_3d(b1, b2)) + return LW_FALSE; + else /* Raycast upwards in Z */ + { + POINT4D pt; + /* We'll skew copies if we're not lucky */ + LWGEOM *solid_copy = lwgeom_clone_deep(solid); + LWGEOM *g_copy = lwgeom_clone_deep(g); + + while (LW_TRUE) + { + uint8_t is_boundary = LW_FALSE; + uint8_t is_inside = LW_FALSE; + + /* take first point */ + if (!lwgeom_startpoint(g_copy, &pt)) + return LW_FALSE; + + /* get part of solid that is upwards */ + LWCOLLECTION *c = lwgeom_clip_to_ordinate_range(solid_copy, 'Z', pt.z, DBL_MAX, 0); + + for (uint32_t i = 0; i < c->ngeoms; i++) + { + if (c->geoms[i]->type == POLYGONTYPE) + { + /* 3D raycast along Z is 2D point in polygon */ + int t = lwpoly_contains_point((LWPOLY *)c->geoms[i], (POINT2D *)&pt); + + if (t == LW_INSIDE) + is_inside = !is_inside; + else if (t == LW_BOUNDARY) + { + is_boundary = LW_TRUE; + break; + } + } + else if (c->geoms[i]->type == TRIANGLETYPE) + { + /* 3D raycast along Z is 2D point in polygon */ + LWTRIANGLE *tri = (LWTRIANGLE *)c->geoms[i]; + int t = ptarray_contains_point(tri->points, (POINT2D *)&pt); + + if (t == LW_INSIDE) + is_inside = !is_inside; + else if (t == LW_BOUNDARY) + { + is_boundary = LW_TRUE; + break; + } + } + } + + lwcollection_free(c); + lwgeom_free(solid_copy); + lwgeom_free(g_copy); + + if (is_boundary) + { + /* randomly skew a bit and restart*/ + double cx = lwrandom_uniform() - 0.5; + double cy = lwrandom_uniform() - 0.5; + AFFINE aff = {1, 0, cx, 0, 1, cy, 0, 0, 1, 0, 0, 0}; + + solid_copy = lwgeom_clone_deep(solid); + lwgeom_affine(solid_copy, &aff); + + g_copy = lwgeom_clone_deep(g); + lwgeom_affine(g_copy, &aff); + + continue; + } + return is_inside; + } + } +} + +/** + Function handling 3d min distance calculations and dwithin calculations. + The difference is just the tolerance. +*/ +double +lwgeom_mindistance3d_tolerance(const LWGEOM *lw1, const LWGEOM *lw2, double tolerance) +{ + assert(tolerance >= 0); + if (!lwgeom_has_z(lw1) || !lwgeom_has_z(lw2)) + { + lwnotice( + "One or both of the geometries is missing z-value. The unknown z-value will be regarded as \"any value\""); + + return lwgeom_mindistance2d_tolerance(lw1, lw2, tolerance); + } + DISTPTS3D thedl; + thedl.mode = DIST_MIN; + thedl.distance = DBL_MAX; + thedl.tolerance = tolerance; + + if (lw_dist3d_recursive(lw1, lw2, &thedl)) + { + if (thedl.distance <= tolerance) + return thedl.distance; + if (lwgeom_solid_contains_lwgeom(lw1, lw2) || lwgeom_solid_contains_lwgeom(lw2, lw1)) + return 0; + + return thedl.distance; + } + + /* should never get here. all cases ought to be error handled earlier */ + lwerror("Some unspecified error."); + return DBL_MAX; +} + +/*------------------------------------------------------------------------------------------------------------ +End of Initializing functions +--------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------ +Preprocessing functions +Functions preparing geometries for distance-calculations +--------------------------------------------------------------------------------------------------------------*/ + +/** +This is a recursive function delivering every possible combination of subgeometries +*/ +int +lw_dist3d_recursive(const LWGEOM *lwg1, const LWGEOM *lwg2, DISTPTS3D *dl) +{ + int i, j; + int n1 = 1; + int n2 = 1; + LWGEOM *g1 = NULL; + LWGEOM *g2 = NULL; + LWCOLLECTION *c1 = NULL; + LWCOLLECTION *c2 = NULL; + + LWDEBUGF(2, "lw_dist3d_recursive is called with type1=%d, type2=%d", lwg1->type, lwg2->type); + + if (lwgeom_is_collection(lwg1)) + { + LWDEBUG(3, "First geometry is collection"); + c1 = lwgeom_as_lwcollection(lwg1); + n1 = c1->ngeoms; + } + if (lwgeom_is_collection(lwg2)) + { + LWDEBUG(3, "Second geometry is collection"); + c2 = lwgeom_as_lwcollection(lwg2); + n2 = c2->ngeoms; + } + + for (i = 0; i < n1; i++) + { + if (lwgeom_is_collection(lwg1)) + g1 = c1->geoms[i]; + else + g1 = (LWGEOM *)lwg1; + + if (lwgeom_is_empty(g1)) + continue; + + if (lwgeom_is_collection(g1)) + { + LWDEBUG(3, "Found collection inside first geometry collection, recursing"); + if (!lw_dist3d_recursive(g1, lwg2, dl)) + return LW_FALSE; + continue; + } + for (j = 0; j < n2; j++) + { + if (lwgeom_is_collection(lwg2)) + g2 = c2->geoms[j]; + else + g2 = (LWGEOM *)lwg2; + + if (lwgeom_is_empty(g2)) + continue; + + if (lwgeom_is_collection(g2)) + { + LWDEBUG(3, "Found collection inside second geometry collection, recursing"); + if (!lw_dist3d_recursive(g1, g2, dl)) + return LW_FALSE; + continue; + } + + /*If one of geometries is empty, return. True here only means continue searching. False would + * have stopped the process*/ + if (lwgeom_is_empty(g1) || lwgeom_is_empty(g2)) + return LW_TRUE; + + if (!lw_dist3d_distribute_bruteforce(g1, g2, dl)) + return LW_FALSE; + if (dl->distance <= dl->tolerance && dl->mode == DIST_MIN) + return LW_TRUE; /*just a check if the answer is already given*/ + } + } + return LW_TRUE; +} + +/** + +This function distributes the brute-force for 3D so far the only type, tasks depending on type +*/ +int +lw_dist3d_distribute_bruteforce(const LWGEOM *lwg1, const LWGEOM *lwg2, DISTPTS3D *dl) +{ + int t1 = lwg1->type; + int t2 = lwg2->type; + + LWDEBUGF(2, "lw_dist3d_distribute_bruteforce is called with typ1=%d, type2=%d", lwg1->type, lwg2->type); + + if (t1 == POINTTYPE) + { + if (t2 == POINTTYPE) + { + dl->twisted = 1; + return lw_dist3d_point_point((LWPOINT *)lwg1, (LWPOINT *)lwg2, dl); + } + else if (t2 == LINETYPE) + { + dl->twisted = 1; + return lw_dist3d_point_line((LWPOINT *)lwg1, (LWLINE *)lwg2, dl); + } + else if (t2 == POLYGONTYPE) + { + dl->twisted = 1; + return lw_dist3d_point_poly((LWPOINT *)lwg1, (LWPOLY *)lwg2, dl); + } + else if (t2 == TRIANGLETYPE) + { + dl->twisted = 1; + return lw_dist3d_point_tri((LWPOINT *)lwg1, (LWTRIANGLE *)lwg2, dl); + } + else + { + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(t2)); + return LW_FALSE; + } + } + else if (t1 == LINETYPE) + { + if (t2 == POINTTYPE) + { + dl->twisted = -1; + return lw_dist3d_point_line((LWPOINT *)lwg2, (LWLINE *)lwg1, dl); + } + else if (t2 == LINETYPE) + { + dl->twisted = 1; + return lw_dist3d_line_line((LWLINE *)lwg1, (LWLINE *)lwg2, dl); + } + else if (t2 == POLYGONTYPE) + { + dl->twisted = 1; + return lw_dist3d_line_poly((LWLINE *)lwg1, (LWPOLY *)lwg2, dl); + } + else if (t2 == TRIANGLETYPE) + { + dl->twisted = 1; + return lw_dist3d_line_tri((LWLINE *)lwg1, (LWTRIANGLE *)lwg2, dl); + } + else + { + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(t2)); + return LW_FALSE; + } + } + else if (t1 == POLYGONTYPE) + { + if (t2 == POLYGONTYPE) + { + dl->twisted = 1; + return lw_dist3d_poly_poly((LWPOLY *)lwg1, (LWPOLY *)lwg2, dl); + } + else if (t2 == POINTTYPE) + { + dl->twisted = -1; + return lw_dist3d_point_poly((LWPOINT *)lwg2, (LWPOLY *)lwg1, dl); + } + else if (t2 == LINETYPE) + { + dl->twisted = -1; + return lw_dist3d_line_poly((LWLINE *)lwg2, (LWPOLY *)lwg1, dl); + } + else if (t2 == TRIANGLETYPE) + { + dl->twisted = 1; + return lw_dist3d_poly_tri((LWPOLY *)lwg1, (LWTRIANGLE *)lwg2, dl); + } + else + { + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(t2)); + return LW_FALSE; + } + } + else if (t1 == TRIANGLETYPE) + { + if (t2 == POLYGONTYPE) + { + dl->twisted = -1; + return lw_dist3d_poly_tri((LWPOLY *)lwg2, (LWTRIANGLE *)lwg1, dl); + } + else if (t2 == POINTTYPE) + { + dl->twisted = -1; + return lw_dist3d_point_tri((LWPOINT *)lwg2, (LWTRIANGLE *)lwg1, dl); + } + else if (t2 == LINETYPE) + { + dl->twisted = -1; + return lw_dist3d_line_tri((LWLINE *)lwg2, (LWTRIANGLE *)lwg1, dl); + } + else if (t2 == TRIANGLETYPE) + { + dl->twisted = 1; + return lw_dist3d_tri_tri((LWTRIANGLE *)lwg1, (LWTRIANGLE *)lwg2, dl); + } + else + { + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(t2)); + return LW_FALSE; + } + } + + else + { + lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(t1)); + return LW_FALSE; + } +} + +/*------------------------------------------------------------------------------------------------------------ +End of Preprocessing functions +--------------------------------------------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------------------------------------------ +Brute force functions +So far the only way to do 3D-calculations +--------------------------------------------------------------------------------------------------------------*/ + +/** + +point to point calculation +*/ +int +lw_dist3d_point_point(LWPOINT *point1, LWPOINT *point2, DISTPTS3D *dl) +{ + POINT3DZ p1; + POINT3DZ p2; + LWDEBUG(2, "lw_dist3d_point_point is called"); + + getPoint3dz_p(point1->point, 0, &p1); + getPoint3dz_p(point2->point, 0, &p2); + + return lw_dist3d_pt_pt(&p1, &p2, dl); +} +/** + +point to line calculation +*/ +int +lw_dist3d_point_line(LWPOINT *point, LWLINE *line, DISTPTS3D *dl) +{ + POINT3DZ p; + POINTARRAY *pa = line->points; + LWDEBUG(2, "lw_dist3d_point_line is called"); + + getPoint3dz_p(point->point, 0, &p); + return lw_dist3d_pt_ptarray(&p, pa, dl); +} + +/** + +Computes point to polygon distance +For mindistance that means: +1) find the plane of the polygon +2) projecting the point to the plane of the polygon +3) finding if that projected point is inside the polygon, if so the distance is measured to that projected point +4) if not in polygon above, check the distance against the boundary of the polygon +for max distance it is always point against boundary + +*/ + +int +lw_dist3d_point_poly(LWPOINT *point, LWPOLY *poly, DISTPTS3D *dl) +{ + POINT3DZ p, projp; /*projp is "point projected on plane"*/ + PLANE3D plane; + LWDEBUG(2, "lw_dist3d_point_poly is called"); + getPoint3dz_p(point->point, 0, &p); + + /* If we are looking for max distance, longestline or dfullywithin */ + if (dl->mode == DIST_MAX) + return lw_dist3d_pt_ptarray(&p, poly->rings[0], dl); + + /* Find the plane of the polygon, the "holes" have to be on the same plane. so we only care about the boudary */ + if (!define_plane(poly->rings[0], &plane)) + { + /* Polygon does not define a plane: Return distance point -> line */ + return lw_dist3d_pt_ptarray(&p, poly->rings[0], dl); + } + + /* Get our point projected on the plane of the polygon */ + project_point_on_plane(&p, &plane, &projp); + + return lw_dist3d_pt_poly(&p, poly, &plane, &projp, dl); +} + +/* point to triangle calculation */ +int +lw_dist3d_point_tri(LWPOINT *point, LWTRIANGLE *tri, DISTPTS3D *dl) +{ + POINT3DZ p, projp; /*projp is "point projected on plane"*/ + PLANE3D plane; + getPoint3dz_p(point->point, 0, &p); + + /* If we are looking for max distance, longestline or dfullywithin */ + if (dl->mode == DIST_MAX) + return lw_dist3d_pt_ptarray(&p, tri->points, dl); + + /* If triangle does not define a plane, treat it as a line */ + if (!define_plane(tri->points, &plane)) + return lw_dist3d_pt_ptarray(&p, tri->points, dl); + + /* Get our point projected on the plane of triangle */ + project_point_on_plane(&p, &plane, &projp); + + return lw_dist3d_pt_tri(&p, tri, &plane, &projp, dl); +} + +/** line to line calculation */ +int +lw_dist3d_line_line(LWLINE *line1, LWLINE *line2, DISTPTS3D *dl) +{ + POINTARRAY *pa1 = line1->points; + POINTARRAY *pa2 = line2->points; + LWDEBUG(2, "lw_dist3d_line_line is called"); + + return lw_dist3d_ptarray_ptarray(pa1, pa2, dl); +} + +/** line to polygon calculation */ +int +lw_dist3d_line_poly(LWLINE *line, LWPOLY *poly, DISTPTS3D *dl) +{ + PLANE3D plane; + LWDEBUG(2, "lw_dist3d_line_poly is called"); + + if (dl->mode == DIST_MAX) + return lw_dist3d_ptarray_ptarray(line->points, poly->rings[0], dl); + + /* if polygon does not define a plane: Return distance line to line */ + if (!define_plane(poly->rings[0], &plane)) + return lw_dist3d_ptarray_ptarray(line->points, poly->rings[0], dl); + + return lw_dist3d_ptarray_poly(line->points, poly, &plane, dl); +} + +/** line to triangle calculation */ +int +lw_dist3d_line_tri(LWLINE *line, LWTRIANGLE *tri, DISTPTS3D *dl) +{ + PLANE3D plane; + + if (dl->mode == DIST_MAX) + return lw_dist3d_ptarray_ptarray(line->points, tri->points, dl); + + /* if triangle does not define a plane: Return distance line to line */ + if (!define_plane(tri->points, &plane)) + return lw_dist3d_ptarray_ptarray(line->points, tri->points, dl); + + return lw_dist3d_ptarray_tri(line->points, tri, &plane, dl); +} + +/** polygon to polygon calculation */ +int +lw_dist3d_poly_poly(LWPOLY *poly1, LWPOLY *poly2, DISTPTS3D *dl) +{ + PLANE3D plane1, plane2; + int planedef1, planedef2; + LWDEBUG(2, "lw_dist3d_poly_poly is called"); + if (dl->mode == DIST_MAX) + return lw_dist3d_ptarray_ptarray(poly1->rings[0], poly2->rings[0], dl); + + planedef1 = define_plane(poly1->rings[0], &plane1); + planedef2 = define_plane(poly2->rings[0], &plane2); + + if (!planedef1 || !planedef2) + { + /* Neither polygon define a plane: Return distance line to line */ + if (!planedef1 && !planedef2) + return lw_dist3d_ptarray_ptarray(poly1->rings[0], poly2->rings[0], dl); + + /* Only poly2 defines a plane: Return distance from line (poly1) to poly2 */ + else if (!planedef1) + return lw_dist3d_ptarray_poly(poly1->rings[0], poly2, &plane2, dl); + + /* Only poly1 defines a plane: Return distance from line (poly2) to poly1 */ + else + return lw_dist3d_ptarray_poly(poly2->rings[0], poly1, &plane1, dl); + } + + /* What we do here is to compare the boundary of one polygon with the other polygon + and then take the second boundary comparing with the first polygon */ + dl->twisted = 1; + if (!lw_dist3d_ptarray_poly(poly1->rings[0], poly2, &plane2, dl)) + return LW_FALSE; + if (dl->distance < dl->tolerance) /* Just check if the answer already is given*/ + return LW_TRUE; + + dl->twisted = -1; /* because we switch the order of geometries we switch "twisted" to -1 which will give the + right order of points in shortest line. */ + return lw_dist3d_ptarray_poly(poly2->rings[0], poly1, &plane1, dl); +} + +/** polygon to triangle calculation */ +int +lw_dist3d_poly_tri(LWPOLY *poly, LWTRIANGLE *tri, DISTPTS3D *dl) +{ + PLANE3D plane1, plane2; + int planedef1, planedef2; + + if (dl->mode == DIST_MAX) + return lw_dist3d_ptarray_ptarray(poly->rings[0], tri->points, dl); + + planedef1 = define_plane(poly->rings[0], &plane1); + planedef2 = define_plane(tri->points, &plane2); + + if (!planedef1 || !planedef2) + { + /* Neither define a plane: Return distance line to line */ + if (!planedef1 && !planedef2) + return lw_dist3d_ptarray_ptarray(poly->rings[0], tri->points, dl); + + /* Only tri defines a plane: Return distance from line (poly) to tri */ + else if (!planedef1) + return lw_dist3d_ptarray_tri(poly->rings[0], tri, &plane2, dl); + + /* Only poly defines a plane: Return distance from line (tri) to poly */ + else + return lw_dist3d_ptarray_poly(tri->points, poly, &plane1, dl); + } + + /* What we do here is to compare the boundary of one polygon with the other polygon + and then take the second boundary comparing with the first polygon */ + dl->twisted = 1; + if (!lw_dist3d_ptarray_tri(poly->rings[0], tri, &plane2, dl)) + return LW_FALSE; + if (dl->distance < dl->tolerance) /* Just check if the answer already is given*/ + return LW_TRUE; + + dl->twisted = -1; /* because we switch the order of geometries we switch "twisted" to -1 which will give the + right order of points in shortest line. */ + return lw_dist3d_ptarray_poly(tri->points, poly, &plane1, dl); +} + +/** triangle to triangle calculation */ +int +lw_dist3d_tri_tri(LWTRIANGLE *tri1, LWTRIANGLE *tri2, DISTPTS3D *dl) +{ + PLANE3D plane1, plane2; + int planedef1, planedef2; + + if (dl->mode == DIST_MAX) + return lw_dist3d_ptarray_ptarray(tri1->points, tri2->points, dl); + + planedef1 = define_plane(tri1->points, &plane1); + planedef2 = define_plane(tri2->points, &plane2); + + if (!planedef1 || !planedef2) + { + /* Neither define a plane: Return distance line to line */ + if (!planedef1 && !planedef2) + return lw_dist3d_ptarray_ptarray(tri1->points, tri2->points, dl); + + /* Only tri defines a plane: Return distance from line (tri1) to tri2 */ + else if (!planedef1) + return lw_dist3d_ptarray_tri(tri1->points, tri2, &plane2, dl); + + /* Only poly defines a plane: Return distance from line (tri2) to tri1 */ + else + return lw_dist3d_ptarray_tri(tri2->points, tri1, &plane1, dl); + } + + /* What we do here is to compare the boundary of one polygon with the other polygon + and then take the second boundary comparing with the first polygon */ + dl->twisted = 1; + if (!lw_dist3d_ptarray_tri(tri1->points, tri2, &plane2, dl)) + return LW_FALSE; + if (dl->distance < dl->tolerance) /* Just check if the answer already is given*/ + return LW_TRUE; + + dl->twisted = -1; /* because we switch the order of geometries we switch "twisted" to -1 which will give the + right order of points in shortest line. */ + return lw_dist3d_ptarray_tri(tri2->points, tri1, &plane1, dl); +} + +/** + * search all the segments of pointarray to see which one is closest to p + * Returns distance between point and pointarray + */ +int +lw_dist3d_pt_ptarray(POINT3DZ *p, POINTARRAY *pa, DISTPTS3D *dl) +{ + uint32_t t; + POINT3DZ start, end; + int twist = dl->twisted; + if (!pa) + return LW_FALSE; + + getPoint3dz_p(pa, 0, &start); + + for (t = 1; t < pa->npoints; t++) + { + dl->twisted = twist; + getPoint3dz_p(pa, t, &end); + if (!lw_dist3d_pt_seg(p, &start, &end, dl)) + return LW_FALSE; + + if (dl->distance <= dl->tolerance && dl->mode == DIST_MIN) + return LW_TRUE; /*just a check if the answer is already given*/ + start = end; + } + + return LW_TRUE; +} + +/** +If searching for min distance, this one finds the closest point on segment A-B from p. +if searching for max distance it just sends p-A and p-B to pt-pt calculation +*/ +int +lw_dist3d_pt_seg(POINT3DZ *p, POINT3DZ *A, POINT3DZ *B, DISTPTS3D *dl) +{ + POINT3DZ c; + double r; + /*if start==end, then use pt distance */ + if ((A->x == B->x) && (A->y == B->y) && (A->z == B->z)) + { + return lw_dist3d_pt_pt(p, A, dl); + } + + r = ((p->x - A->x) * (B->x - A->x) + (p->y - A->y) * (B->y - A->y) + (p->z - A->z) * (B->z - A->z)) / + ((B->x - A->x) * (B->x - A->x) + (B->y - A->y) * (B->y - A->y) + (B->z - A->z) * (B->z - A->z)); + + /*This is for finding the 3Dmaxdistance. + the maxdistance have to be between two vertexes, + compared to mindistance which can be between + two vertexes vertex.*/ + if (dl->mode == DIST_MAX) + { + if (r >= 0.5) + return lw_dist3d_pt_pt(p, A, dl); + if (r < 0.5) + return lw_dist3d_pt_pt(p, B, dl); + } + + if (r <= 0) /*If the first vertex A is closest to the point p*/ + return lw_dist3d_pt_pt(p, A, dl); + if (r >= 1) /*If the second vertex B is closest to the point p*/ + return lw_dist3d_pt_pt(p, B, dl); + + /*else if the point p is closer to some point between a and b + then we find that point and send it to lw_dist3d_pt_pt*/ + c.x = A->x + r * (B->x - A->x); + c.y = A->y + r * (B->y - A->y); + c.z = A->z + r * (B->z - A->z); + + return lw_dist3d_pt_pt(p, &c, dl); +} + +double +distance3d_pt_pt(const POINT3D *p1, const POINT3D *p2) +{ + double dx = p2->x - p1->x; + double dy = p2->y - p1->y; + double dz = p2->z - p1->z; + return sqrt(dx * dx + dy * dy + dz * dz); +} + +/** + +Compares incoming points and +stores the points closest to each other +or most far away from each other +depending on dl->mode (max or min) +*/ +int +lw_dist3d_pt_pt(POINT3DZ *thep1, POINT3DZ *thep2, DISTPTS3D *dl) +{ + double dx = thep2->x - thep1->x; + double dy = thep2->y - thep1->y; + double dz = thep2->z - thep1->z; + double dist = sqrt(dx * dx + dy * dy + dz * dz); + LWDEBUGF(2, + "lw_dist3d_pt_pt called (with points: p1.x=%f, p1.y=%f,p1.z=%f,p2.x=%f, p2.y=%f,p2.z=%f)", + thep1->x, + thep1->y, + thep1->z, + thep2->x, + thep2->y, + thep2->z); + + if (((dl->distance - dist) * (dl->mode)) > + 0) /*multiplication with mode to handle mindistance (mode=1) and maxdistance (mode = (-1)*/ + { + dl->distance = dist; + + if (dl->twisted > 0) /*To get the points in right order. twisted is updated between 1 and (-1) every + time the order is changed earlier in the chain*/ + { + dl->p1 = *thep1; + dl->p2 = *thep2; + } + else + { + dl->p1 = *thep2; + dl->p2 = *thep1; + } + } + return LW_TRUE; +} + +/** + +Finds all combinations of segments between two pointarrays +*/ +int +lw_dist3d_ptarray_ptarray(POINTARRAY *l1, POINTARRAY *l2, DISTPTS3D *dl) +{ + uint32_t t, u; + POINT3DZ start, end; + POINT3DZ start2, end2; + int twist = dl->twisted; + LWDEBUGF(2, "lw_dist3d_ptarray_ptarray called (points: %d-%d)", l1->npoints, l2->npoints); + + if (dl->mode == DIST_MAX) /*If we are searching for maxdistance we go straight to point-point calculation since + the maxdistance have to be between two vertexes*/ + { + for (t = 0; t < l1->npoints; t++) /*for each segment in L1 */ + { + getPoint3dz_p(l1, t, &start); + for (u = 0; u < l2->npoints; u++) /*for each segment in L2 */ + { + getPoint3dz_p(l2, u, &start2); + lw_dist3d_pt_pt(&start, &start2, dl); + } + } + } + else + { + getPoint3dz_p(l1, 0, &start); + for (t = 1; t < l1->npoints; t++) /*for each segment in L1 */ + { + getPoint3dz_p(l1, t, &end); + getPoint3dz_p(l2, 0, &start2); + for (u = 1; u < l2->npoints; u++) /*for each segment in L2 */ + { + getPoint3dz_p(l2, u, &end2); + dl->twisted = twist; + lw_dist3d_seg_seg(&start, &end, &start2, &end2, dl); + LWDEBUGF( + 4, "mindist_ptarray_ptarray; seg %i * seg %i, dist = %g\n", t, u, dl->distance); + LWDEBUGF(3, " seg%d-seg%d dist: %f, mindist: %f", t, u, dl->distance, dl->tolerance); + if (dl->distance <= dl->tolerance && dl->mode == DIST_MIN) + return LW_TRUE; /*just a check if the answer is already given*/ + start2 = end2; + } + start = end; + } + } + return LW_TRUE; +} + +/** + +Finds the two closest points on two linesegments +*/ +int +lw_dist3d_seg_seg(POINT3DZ *s1p1, POINT3DZ *s1p2, POINT3DZ *s2p1, POINT3DZ *s2p2, DISTPTS3D *dl) +{ + VECTOR3D v1, v2, vl; + double s1k, s2k; /*two variables representing where on Line 1 (s1k) and where on Line 2 (s2k) a connecting line + between the two lines is perpendicular to both lines*/ + POINT3DZ p1, p2; + double a, b, c, d, e, D; + + /*s1p1 and s1p2 are the same point */ + if ((s1p1->x == s1p2->x) && (s1p1->y == s1p2->y) && (s1p1->z == s1p2->z)) + { + return lw_dist3d_pt_seg(s1p1, s2p1, s2p2, dl); + } + /*s2p1 and s2p2 are the same point */ + if ((s2p1->x == s2p2->x) && (s2p1->y == s2p2->y) && (s2p1->z == s2p2->z)) + { + dl->twisted = ((dl->twisted) * (-1)); + return lw_dist3d_pt_seg(s2p1, s1p1, s1p2, dl); + } + + /* + Here we use algorithm from softsurfer.com + that can be found here + http://softsurfer.com/Archive/algorithm_0106/algorithm_0106.htm + */ + + if (!get_3dvector_from_points(s1p1, s1p2, &v1)) + return LW_FALSE; + + if (!get_3dvector_from_points(s2p1, s2p2, &v2)) + return LW_FALSE; + + if (!get_3dvector_from_points(s2p1, s1p1, &vl)) + return LW_FALSE; + + a = DOT(v1, v1); + b = DOT(v1, v2); + c = DOT(v2, v2); + d = DOT(v1, vl); + e = DOT(v2, vl); + D = a * c - b * b; + + if (D < 0.000000001) + { /* the lines are almost parallel*/ + s1k = + 0.0; /*If the lines are parallel we try by using the startpoint of first segment. If that gives a + projected point on the second line outside segment 2 it wil be found that s2k is >1 or <0.*/ + if (b > c) /* use the largest denominator*/ + s2k = d / b; + else + s2k = e / c; + } + else + { + s1k = (b * e - c * d) / D; + s2k = (a * e - b * d) / D; + } + + /* Now we check if the projected closest point on the infinite lines is outside our segments. If so the + * combinations with start and end points will be tested*/ + + if (s1k <= 0.0 || s1k >= 1.0 || s2k <= 0.0 || s2k >= 1.0) + { + if (s1k <= 0.0) + { + if (!lw_dist3d_pt_seg(s1p1, s2p1, s2p2, dl)) + return LW_FALSE; + } + if (s1k >= 1.0) + { + if (!lw_dist3d_pt_seg(s1p2, s2p1, s2p2, dl)) + return LW_FALSE; + } + if (s2k <= 0.0) + { + dl->twisted = ((dl->twisted) * (-1)); + if (!lw_dist3d_pt_seg(s2p1, s1p1, s1p2, dl)) + return LW_FALSE; + } + if (s2k >= 1.0) + { + dl->twisted = ((dl->twisted) * (-1)); + if (!lw_dist3d_pt_seg(s2p2, s1p1, s1p2, dl)) + return LW_FALSE; + } + } + else + { /*Find the closest point on the edges of both segments*/ + p1.x = s1p1->x + s1k * (s1p2->x - s1p1->x); + p1.y = s1p1->y + s1k * (s1p2->y - s1p1->y); + p1.z = s1p1->z + s1k * (s1p2->z - s1p1->z); + + p2.x = s2p1->x + s2k * (s2p2->x - s2p1->x); + p2.y = s2p1->y + s2k * (s2p2->y - s2p1->y); + p2.z = s2p1->z + s2k * (s2p2->z - s2p1->z); + + if (!lw_dist3d_pt_pt(&p1, &p2, dl)) /* Send the closest points to point-point calculation*/ + { + return LW_FALSE; + } + } + return LW_TRUE; +} + +/** + +Checking if the point projected on the plane of the polygon actually is inside that polygon. +If so the mindistance is between that projected point and our original point. +If not we check from original point to the boundary. +If the projected point is inside a hole of the polygon we check the distance to the boundary of that hole. +*/ +int +lw_dist3d_pt_poly(POINT3DZ *p, LWPOLY *poly, PLANE3D *plane, POINT3DZ *projp, DISTPTS3D *dl) +{ + uint32_t i; + + if (pt_in_ring_3d(projp, poly->rings[0], plane)) + { + for (i = 1; i < poly->nrings; i++) + { + /* Inside a hole. Distance = pt -> ring */ + if (pt_in_ring_3d(projp, poly->rings[i], plane)) + return lw_dist3d_pt_ptarray(p, poly->rings[i], dl); + } + + /* if the projected point is inside the polygon the shortest distance is between that point and the + * input point */ + return lw_dist3d_pt_pt(p, projp, dl); + } + else + /* if the projected point is outside the polygon we search for the closest distance against the boundary + * instead */ + return lw_dist3d_pt_ptarray(p, poly->rings[0], dl); + + return LW_TRUE; +} + +int +lw_dist3d_pt_tri(POINT3DZ *p, LWTRIANGLE *tri, PLANE3D *plane, POINT3DZ *projp, DISTPTS3D *dl) +{ + if (pt_in_ring_3d(projp, tri->points, plane)) + /* if the projected point is inside the polygon the shortest distance is between that point and the + * input point */ + return lw_dist3d_pt_pt(p, projp, dl); + else + /* if the projected point is outside the polygon we search for the closest distance against the boundary + * instead */ + return lw_dist3d_pt_ptarray(p, tri->points, dl); + + return LW_TRUE; +} + +/** Computes pointarray to polygon distance */ +int +lw_dist3d_ptarray_poly(POINTARRAY *pa, LWPOLY *poly, PLANE3D *plane, DISTPTS3D *dl) +{ + uint32_t i, j, k; + double f, s1, s2; + VECTOR3D projp1_projp2; + POINT3DZ p1, p2, projp1, projp2, intersectionp; + + getPoint3dz_p(pa, 0, &p1); + + /* the sign of s1 tells us on which side of the plane the point is. */ + s1 = project_point_on_plane(&p1, plane, &projp1); + lw_dist3d_pt_poly(&p1, poly, plane, &projp1, dl); + if ((s1 == 0.0) && (dl->distance < dl->tolerance)) + return LW_TRUE; + + for (i = 1; i < pa->npoints; i++) + { + int intersects; + getPoint3dz_p(pa, i, &p2); + s2 = project_point_on_plane(&p2, plane, &projp2); + lw_dist3d_pt_poly(&p2, poly, plane, &projp2, dl); + if ((s2 == 0.0) && (dl->distance < dl->tolerance)) + return LW_TRUE; + + /* If s1 and s2 has different signs that means they are on different sides of the plane of the polygon. + * That means that the edge between the points crosses the plane and might intersect with the polygon */ + if ((s1 * s2) < 0) + { + /* The size of s1 and s2 is the distance from the point to the plane. */ + f = fabs(s1) / (fabs(s1) + fabs(s2)); + get_3dvector_from_points(&projp1, &projp2, &projp1_projp2); + + /* Get the point where the line segment crosses the plane */ + intersectionp.x = projp1.x + f * projp1_projp2.x; + intersectionp.y = projp1.y + f * projp1_projp2.y; + intersectionp.z = projp1.z + f * projp1_projp2.z; + + /* We set intersects to true until the opposite is proved */ + intersects = LW_TRUE; + + if (pt_in_ring_3d(&intersectionp, poly->rings[0], plane)) /*Inside outer ring*/ + { + for (k = 1; k < poly->nrings; k++) + { + /* Inside a hole, so no intersection with the polygon*/ + if (pt_in_ring_3d(&intersectionp, poly->rings[k], plane)) + { + intersects = LW_FALSE; + break; + } + } + if (intersects) + { + dl->distance = 0.0; + dl->p1.x = intersectionp.x; + dl->p1.y = intersectionp.y; + dl->p1.z = intersectionp.z; + + dl->p2.x = intersectionp.x; + dl->p2.y = intersectionp.y; + dl->p2.z = intersectionp.z; + return LW_TRUE; + } + } + } + + projp1 = projp2; + s1 = s2; + p1 = p2; + } + + /* check our pointarray against boundary and inner boundaries of the polygon */ + for (j = 0; j < poly->nrings; j++) + lw_dist3d_ptarray_ptarray(pa, poly->rings[j], dl); + + return LW_TRUE; +} + +/** Computes pointarray to triangle distance */ +int +lw_dist3d_ptarray_tri(POINTARRAY *pa, LWTRIANGLE *tri, PLANE3D *plane, DISTPTS3D *dl) +{ + uint32_t i; + double f, s1, s2; + VECTOR3D projp1_projp2; + POINT3DZ p1, p2, projp1, projp2, intersectionp; + + getPoint3dz_p(pa, 0, &p1); + + /* the sign of s1 tells us on which side of the plane the point is. */ + s1 = project_point_on_plane(&p1, plane, &projp1); + lw_dist3d_pt_tri(&p1, tri, plane, &projp1, dl); + if ((s1 == 0.0) && (dl->distance < dl->tolerance)) + return LW_TRUE; + + for (i = 1; i < pa->npoints; i++) + { + int intersects; + getPoint3dz_p(pa, i, &p2); + s2 = project_point_on_plane(&p2, plane, &projp2); + lw_dist3d_pt_tri(&p2, tri, plane, &projp2, dl); + if ((s2 == 0.0) && (dl->distance < dl->tolerance)) + return LW_TRUE; + + /* If s1 and s2 has different signs that means they are on different sides of the plane of the triangle. + * That means that the edge between the points crosses the plane and might intersect with the triangle + */ + if ((s1 * s2) < 0) + { + /* The size of s1 and s2 is the distance from the point to the plane. */ + f = fabs(s1) / (fabs(s1) + fabs(s2)); + get_3dvector_from_points(&projp1, &projp2, &projp1_projp2); + + /* Get the point where the line segment crosses the plane */ + intersectionp.x = projp1.x + f * projp1_projp2.x; + intersectionp.y = projp1.y + f * projp1_projp2.y; + intersectionp.z = projp1.z + f * projp1_projp2.z; + + /* We set intersects to true until the opposite is proved */ + intersects = LW_TRUE; + + if (pt_in_ring_3d(&intersectionp, tri->points, plane)) /*Inside outer ring*/ + { + if (intersects) + { + dl->distance = 0.0; + dl->p1.x = intersectionp.x; + dl->p1.y = intersectionp.y; + dl->p1.z = intersectionp.z; + + dl->p2.x = intersectionp.x; + dl->p2.y = intersectionp.y; + dl->p2.z = intersectionp.z; + return LW_TRUE; + } + } + } + + projp1 = projp2; + s1 = s2; + p1 = p2; + } + + /* check our pointarray against triangle contour */ + lw_dist3d_ptarray_ptarray(pa, tri->points, dl); + return LW_TRUE; +} + +/* Here we define the plane of a polygon (boundary pointarray of a polygon) + * the plane is stored as a point in plane (plane.pop) and a normal vector (plane.pv) + * + * We are calculating the average point and using it to break the polygon into + * POL_BREAKS (3) parts. Then we calculate the normal of those 3 vectors and + * use its average to define the normal of the plane.This is done to minimize + * the error introduced by FP precision + * We use [3] breaks to contemplate the special case of 3d triangles + */ +int +define_plane(POINTARRAY *pa, PLANE3D *pl) +{ + const uint32_t POL_BREAKS = 3; + + assert(pa); + assert(pa->npoints > 3); + if (!pa) + return LW_FALSE; + + uint32_t unique_points = pa->npoints - 1; + uint32_t i; + + if (pa->npoints < 3) + return LW_FALSE; + + /* Calculate the average point */ + pl->pop.x = 0.0; + pl->pop.y = 0.0; + pl->pop.z = 0.0; + for (i = 0; i < unique_points; i++) + { + POINT3DZ p; + getPoint3dz_p(pa, i, &p); + pl->pop.x += p.x; + pl->pop.y += p.y; + pl->pop.z += p.z; + } + + pl->pop.x /= unique_points; + pl->pop.y /= unique_points; + pl->pop.z /= unique_points; + + pl->pv.x = 0.0; + pl->pv.y = 0.0; + pl->pv.z = 0.0; + for (i = 0; i < POL_BREAKS; i++) + { + POINT3DZ point1, point2; + VECTOR3D v1, v2, vp; + uint32_t n1, n2; + n1 = i * unique_points / POL_BREAKS; + n2 = n1 + unique_points / POL_BREAKS; /* May overflow back to the first / last point */ + + if (n1 == n2) + continue; + + getPoint3dz_p(pa, n1, &point1); + if (!get_3dvector_from_points(&pl->pop, &point1, &v1)) + continue; + + getPoint3dz_p(pa, n2, &point2); + if (!get_3dvector_from_points(&pl->pop, &point2, &v2)) + continue; + + if (get_3dcross_product(&v1, &v2, &vp)) + { + /* If the cross product isn't zero these 3 points aren't collinear + * We divide by its lengthsq to normalize the additions */ + double vl = vp.x * vp.x + vp.y * vp.y + vp.z * vp.z; + pl->pv.x += vp.x / vl; + pl->pv.y += vp.y / vl; + pl->pv.z += vp.z / vl; + } + } + + return (!FP_IS_ZERO(pl->pv.x) || !FP_IS_ZERO(pl->pv.y) || !FP_IS_ZERO(pl->pv.z)); +} + +/** + +Finds a point on a plane from where the original point is perpendicular to the plane +*/ +double +project_point_on_plane(POINT3DZ *p, PLANE3D *pl, POINT3DZ *p0) +{ + /*In our plane definition we have a point on the plane and a normal vector (pl.pv), perpendicular to the plane + this vector will be parallel to the line between our inputted point above the plane and the point we are + searching for on the plane. So, we already have a direction from p to find p0, but we don't know the distance. + */ + + VECTOR3D v1; + double f; + + if (!get_3dvector_from_points(&(pl->pop), p, &v1)) + return 0.0; + + f = DOT(pl->pv, v1); + if (FP_IS_ZERO(f)) + { + /* Point is in the plane */ + *p0 = *p; + return 0; + } + + f = -f / DOT(pl->pv, pl->pv); + + p0->x = p->x + pl->pv.x * f; + p0->y = p->y + pl->pv.y * f; + p0->z = p->z + pl->pv.z * f; + + return f; +} + +/** + * pt_in_ring_3d(): crossing number test for a point in a polygon + * input: p = a point, + * pa = vertex points of a ring V[n+1] with V[n]=V[0] + * plane=the plane that the vertex points are lying on + * returns: 0 = outside, 1 = inside + * + * Our polygons have first and last point the same, + * + * The difference in 3D variant is that we exclude the dimension that faces the plane least. + * That is the dimension with the highest number in pv + */ +int +pt_in_ring_3d(const POINT3DZ *p, const POINTARRAY *ring, PLANE3D *plane) +{ + + uint32_t cn = 0; /* the crossing number counter */ + uint32_t i; + POINT3DZ v1, v2; + + POINT3DZ first, last; + + getPoint3dz_p(ring, 0, &first); + getPoint3dz_p(ring, ring->npoints - 1, &last); + if (memcmp(&first, &last, sizeof(POINT3DZ))) + { + lwerror("pt_in_ring_3d: V[n] != V[0] (%g %g %g!= %g %g %g)", + first.x, + first.y, + first.z, + last.x, + last.y, + last.z); + return LW_FALSE; + } + + LWDEBUGF(2, "pt_in_ring_3d called with point: %g %g %g", p->x, p->y, p->z); + /* printPA(ring); */ + + /* loop through all edges of the polygon */ + getPoint3dz_p(ring, 0, &v1); + + if (fabs(plane->pv.z) >= fabs(plane->pv.x) && + fabs(plane->pv.z) >= fabs(plane->pv.y)) /*If the z vector of the normal vector to the plane is larger than x + and y vector we project the ring to the xy-plane*/ + { + for (i = 0; i < ring->npoints - 1; i++) + { + double vt; + getPoint3dz_p(ring, i + 1, &v2); + + /* edge from vertex i to vertex i+1 */ + if ( + /* an upward crossing */ + ((v1.y <= p->y) && (v2.y > p->y)) + /* a downward crossing */ + || ((v1.y > p->y) && (v2.y <= p->y))) + { + + vt = (double)(p->y - v1.y) / (v2.y - v1.y); + + /* P.x x < v1.x + vt * (v2.x - v1.x)) + { + /* a valid crossing of y=p.y right of p.x */ + ++cn; + } + } + v1 = v2; + } + } + else if (fabs(plane->pv.y) >= fabs(plane->pv.x) && + fabs(plane->pv.y) >= fabs(plane->pv.z)) /*If the y vector of the normal vector to the plane is larger + than x and z vector we project the ring to the xz-plane*/ + { + for (i = 0; i < ring->npoints - 1; i++) + { + double vt; + getPoint3dz_p(ring, i + 1, &v2); + + /* edge from vertex i to vertex i+1 */ + if ( + /* an upward crossing */ + ((v1.z <= p->z) && (v2.z > p->z)) + /* a downward crossing */ + || ((v1.z > p->z) && (v2.z <= p->z))) + { + + vt = (double)(p->z - v1.z) / (v2.z - v1.z); + + /* P.x x < v1.x + vt * (v2.x - v1.x)) + { + /* a valid crossing of y=p.y right of p.x */ + ++cn; + } + } + v1 = v2; + } + } + else /*Hopefully we only have the cases where x part of the normal vector is largest left*/ + { + for (i = 0; i < ring->npoints - 1; i++) + { + double vt; + getPoint3dz_p(ring, i + 1, &v2); + + /* edge from vertex i to vertex i+1 */ + if ( + /* an upward crossing */ + ((v1.z <= p->z) && (v2.z > p->z)) + /* a downward crossing */ + || ((v1.z > p->z) && (v2.z <= p->z))) + { + + vt = (double)(p->z - v1.z) / (v2.z - v1.z); + + /* P.x y < v1.y + vt * (v2.y - v1.y)) + { + /* a valid crossing of y=p.y right of p.x */ + ++cn; + } + } + v1 = v2; + } + } + LWDEBUGF(3, "pt_in_ring_3d returning %d", cn & 1); + + return (cn & 1); /* 0 if even (out), and 1 if odd (in) */ +} + +/*------------------------------------------------------------------------------------------------------------ +End of Brute force functions +--------------------------------------------------------------------------------------------------------------*/ diff --git a/mgist-postgis/liblwgeom/measures3d.h b/mgist-postgis/liblwgeom/measures3d.h new file mode 100644 index 0000000..fd79084 --- /dev/null +++ b/mgist-postgis/liblwgeom/measures3d.h @@ -0,0 +1,111 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2011 Nicklas Avén + * Copyright 2019 Darafei Praliaskouski + * + **********************************************************************/ + +#ifndef _MEASURES3D_H +#define _MEASURES3D_H 1 +#include +#include "measures.h" + +#define DOT(u, v) ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z) +#define VECTORLENGTH(v) sqrt(((v).x * (v).x) + ((v).y * (v).y) + ((v).z * (v).z)) + +/** + +Structure used in distance-calculations +*/ +typedef struct +{ + double distance; /*the distance between p1 and p2*/ + POINT3DZ p1; + POINT3DZ p2; + int mode; /*the direction of looking, if thedir = -1 then we look for 3dmaxdistance and if it is 1 then we look + for 3dmindistance*/ + int twisted; /*To preserve the order of incoming points to match the first and second point in 3dshortest and + 3dlongest line*/ + double tolerance; /*the tolerance for 3ddwithin and 3ddfullywithin*/ +} DISTPTS3D; + +typedef struct +{ + double x, y, z; +} VECTOR3D; + +typedef struct +{ + POINT3DZ pop; /*Point On Plane*/ + VECTOR3D pv; /*Perpendicular normal vector*/ +} PLANE3D; + +/* +Geometry returning functions +*/ +LWGEOM *lw_dist3d_distancepoint(const LWGEOM *lw1, const LWGEOM *lw2, int32_t srid, int mode); +LWGEOM *lw_dist3d_distanceline(const LWGEOM *lw1, const LWGEOM *lw2, int32_t srid, int mode); + +/* +Preprocessing functions +*/ +int lw_dist3d_distribute_bruteforce(const LWGEOM *lwg1, const LWGEOM *lwg2, DISTPTS3D *dl); +int lw_dist3d_recursive(const LWGEOM *lwg1, const LWGEOM *lwg2, DISTPTS3D *dl); +int lw_dist3d_distribute_fast(const LWGEOM *lwg1, const LWGEOM *lwg2, DISTPTS3D *dl); + +/* +Brute force functions +*/ +int lw_dist3d_pt_ptarray(POINT3DZ *p, POINTARRAY *pa, DISTPTS3D *dl); +int lw_dist3d_point_point(LWPOINT *point1, LWPOINT *point2, DISTPTS3D *dl); +int lw_dist3d_point_line(LWPOINT *point, LWLINE *line, DISTPTS3D *dl); +int lw_dist3d_point_poly(LWPOINT *point, LWPOLY *poly, DISTPTS3D *dl); +int lw_dist3d_point_tri(LWPOINT *point, LWTRIANGLE *tri, DISTPTS3D *dl); + +int lw_dist3d_line_line(LWLINE *line1, LWLINE *line2, DISTPTS3D *dl); +int lw_dist3d_line_poly(LWLINE *line, LWPOLY *poly, DISTPTS3D *dl); +int lw_dist3d_line_tri(LWLINE *line, LWTRIANGLE *tri, DISTPTS3D *dl); + +int lw_dist3d_poly_poly(LWPOLY *poly1, LWPOLY *poly2, DISTPTS3D *dl); +int lw_dist3d_poly_tri(LWPOLY *poly, LWTRIANGLE *tri, DISTPTS3D *dl); + +int lw_dist3d_tri_tri(LWTRIANGLE *tri1, LWTRIANGLE *tri2, DISTPTS3D *dl); + +int lw_dist3d_pt_pt(POINT3DZ *p1, POINT3DZ *p2, DISTPTS3D *dl); +int lw_dist3d_pt_seg(POINT3DZ *p, POINT3DZ *A, POINT3DZ *B, DISTPTS3D *dl); +int lw_dist3d_pt_poly(POINT3DZ *p, LWPOLY *poly, PLANE3D *plane, POINT3DZ *projp, DISTPTS3D *dl); +int lw_dist3d_pt_tri(POINT3DZ *p, LWTRIANGLE *tri, PLANE3D *plane, POINT3DZ *projp, DISTPTS3D *dl); + +int lw_dist3d_seg_seg(POINT3DZ *A, POINT3DZ *B, POINT3DZ *C, POINT3DZ *D, DISTPTS3D *dl); + +int lw_dist3d_ptarray_ptarray(POINTARRAY *l1, POINTARRAY *l2, DISTPTS3D *dl); +int lw_dist3d_ptarray_poly(POINTARRAY *pa, LWPOLY *poly, PLANE3D *plane, DISTPTS3D *dl); +int lw_dist3d_ptarray_tri(POINTARRAY *pa, LWTRIANGLE *tri, PLANE3D *plane, DISTPTS3D *dl); + +double project_point_on_plane(POINT3DZ *p, PLANE3D *pl, POINT3DZ *p0); +int define_plane(POINTARRAY *pa, PLANE3D *pl); +int pt_in_ring_3d(const POINT3DZ *p, const POINTARRAY *ring, PLANE3D *plane); + +/* +Helper functions +*/ + +#endif /* !defined _MEASURES3D_H */ diff --git a/mgist-postgis/liblwgeom/optionlist.c b/mgist-postgis/liblwgeom/optionlist.c new file mode 100644 index 0000000..f41cbfd --- /dev/null +++ b/mgist-postgis/liblwgeom/optionlist.c @@ -0,0 +1,173 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2021 Paul Ramsey + * + **********************************************************************/ + +#include "liblwgeom_internal.h" +#include "optionlist.h" + +#include // tolower +#include // strtok + +static void +option_list_string_to_lower(char* key) +{ + if (!key) return; + while (*key) { + *key = tolower(*key); + key++; + } + return; +} + +// static void +// option_list_string_to_upper(char* key) +// { +// if (!key) return; +// while (*key) { +// *key = toupper(*key); +// key++; +// } +// return; +// } + +const char* +option_list_search(char** olist, const char* key) +{ + size_t i = 0; + if (!olist) return NULL; + if (!key) return NULL; + while (olist[i]) { + // Even entries are keys + if (!(i % 2)) { + // Does this key match ours? + if (strcmp(olist[i], key) == 0) { + return olist[i+1]; + } + } + i++; + } + return NULL; +} + +size_t +option_list_length(char** olist) +{ + size_t i = 0; + char **iter = olist; + if (!olist) return 0; + while(*iter) { + i++; + iter++; + } + return i; +} + +void +option_list_parse(char* input, char** olist) +{ + const char *toksep = " "; + const char kvsep = '='; + char *key, *val; + if (!input) return; + size_t i = 0, sz; + + /* strtok nulls out the space between each token */ + for (key = strtok(input, toksep); key; key = strtok(NULL, toksep)) { + if (i >= OPTION_LIST_SIZE) return; + olist[i] = key; + i += 2; + } + + sz = i; + /* keys are every second entry in the olist */ + for (i = 0; i < sz; i += 2) { + if (i >= OPTION_LIST_SIZE) return; + key = olist[i]; + /* find the key/value separator */ + val = strchr(key, kvsep); + if (!val) { + lwerror("Option string entry '%s' lacks separator '%c'", key, kvsep); + } + /* null out the separator */ + *val = '\0'; + /* point value entry to just after separator */ + olist[i+1] = ++val; + /* all keys forced to lower case */ + option_list_string_to_lower(key); + } +} + +void +option_list_gdal_parse(char* input, char** olist) +{ + const char *toksep = " "; + const char kvsep = '='; + const char q2 = '"'; + const char q1 = '\''; + const char notspace = 0x1F; + + char *key, *val; + int in_str = 0; + size_t i = 0, sz, input_sz; + char *ptr = input; + + if (!input) + lwerror("Option string is null"); + input_sz = strlen(input); + + /* Temporarily hide quoted spaces */ + while(*ptr) { + if (*ptr == q2 || *ptr == q1) + in_str = !in_str; + else if (in_str && *ptr == ' ') + *ptr = notspace; + + ptr++; + } + + /* Tokenize on spaces */ + for (key = strtok(input, toksep); key; key = strtok(NULL, toksep)) { + if (i >= OPTION_LIST_SIZE) return; + olist[i++] = key; + } + + /* Check that these are GDAL KEY=VALUE options */ + sz = i; + for (i = 0; i < sz; ++i) { + if (i >= OPTION_LIST_SIZE) return; + key = olist[i]; + /* find the key/value separator */ + val = strchr(key, kvsep); + if (!val) { + lwerror("Option string entry '%s' lacks separator '%c'", key, kvsep); + return; + } + } + + /* Unhide quoted space */ + for (i = 0; i <= input_sz; ++i) { + if (input[i] == notspace) + input[i] = ' '; + } +} + diff --git a/mgist-postgis/liblwgeom/optionlist.h b/mgist-postgis/liblwgeom/optionlist.h new file mode 100644 index 0000000..4508b67 --- /dev/null +++ b/mgist-postgis/liblwgeom/optionlist.h @@ -0,0 +1,73 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2021 Paul Ramsey + * + **********************************************************************/ + + +#ifndef _OPTIONLIST_H +#define _OPTIONLIST_H 1 + +#include "liblwgeom_internal.h" + +#define OPTION_LIST_SIZE 128 + +/** +* option_list is a null-terminated list of strings, where every odd +* string is a key and every even string is a value. To avoid lots of +* memory allocations, we use the input string as the backing store +* and fill in the olist with pointers to the start of the key and +* value strings, with keys in the even slots and values in the +* odd slots. The input needs to be writeable because we write a +* '\0' into the string at the break between token separators. +* +* // array of char* +* char *olist[OPTION_LIST_SIZE]; +* // zero out all the pointers so our list ends up null-terminated +* memset(olist, 0, sizeof(olist)); +* char input[OPTION_LIST_SIZE]; +* strcpy(input, "key1=value1 key2=value2"); // writeable +* option_list_parse(input, olist); +* char* key = "key2"; +* const char* value = option_list_search(olist, key); +* printf("value of '%s' is '%s'\n", key, value); +* +*/ +extern void option_list_parse(char* input, char **olist); + +/** +* Returns null if the key cannot be found. Only +* use fully lowercase keys, because we lowercase +* keys when we parse the olist +*/ +extern const char* option_list_search(char** olist, const char* key); + +/** +* Returns the total number of keys and values in the list +*/ +extern size_t option_list_length(char **olist); + + +extern void option_list_gdal_parse(char* input, char** olist); + + +#endif + diff --git a/mgist-postgis/liblwgeom/ptarray.c b/mgist-postgis/liblwgeom/ptarray.c new file mode 100644 index 0000000..670a89c --- /dev/null +++ b/mgist-postgis/liblwgeom/ptarray.c @@ -0,0 +1,2248 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2012-2021 Sandro Santilli + * Copyright (C) 2001-2006 Refractions Research Inc. + * + **********************************************************************/ + + +#include +#include +#include + +// #include "../postgis_config.h" +/*#define POSTGIS_DEBUG_LEVEL 4*/ +#include "liblwgeom_internal.h" +#include "lwgeom_log.h" + +int +ptarray_has_z(const POINTARRAY *pa) +{ + if ( ! pa ) return LW_FALSE; + return FLAGS_GET_Z(pa->flags); +} + +int +ptarray_has_m(const POINTARRAY *pa) +{ + if ( ! pa ) return LW_FALSE; + return FLAGS_GET_M(pa->flags); +} + +POINTARRAY* +ptarray_construct(char hasz, char hasm, uint32_t npoints) +{ + POINTARRAY *pa = ptarray_construct_empty(hasz, hasm, npoints); + pa->npoints = npoints; + return pa; +} + +POINTARRAY* +ptarray_construct_empty(char hasz, char hasm, uint32_t maxpoints) +{ + POINTARRAY *pa = lwalloc(sizeof(POINTARRAY)); + pa->serialized_pointlist = NULL; + + /* Set our dimensionality info on the bitmap */ + pa->flags = lwflags(hasz, hasm, 0); + + /* We will be allocating a bit of room */ + pa->npoints = 0; + pa->maxpoints = maxpoints; + + /* Allocate the coordinate array */ + if ( maxpoints > 0 ) + pa->serialized_pointlist = lwalloc(maxpoints * ptarray_point_size(pa)); + else + pa->serialized_pointlist = NULL; + + return pa; +} + +/* +* Add a point into a pointarray. Only adds as many dimensions as the +* pointarray supports. +*/ +int +ptarray_insert_point(POINTARRAY *pa, const POINT4D *p, uint32_t where) +{ + if (!pa || !p) + return LW_FAILURE; + size_t point_size = ptarray_point_size(pa); + LWDEBUGF(5,"pa = %p; p = %p; where = %d", pa, p, where); + LWDEBUGF(5,"pa->npoints = %d; pa->maxpoints = %d", pa->npoints, pa->maxpoints); + + if ( FLAGS_GET_READONLY(pa->flags) ) + { + lwerror("ptarray_insert_point: called on read-only point array"); + return LW_FAILURE; + } + + /* Error on invalid offset value */ + if ( where > pa->npoints ) + { + lwerror("ptarray_insert_point: offset out of range (%d)", where); + return LW_FAILURE; + } + + /* If we have no storage, let's allocate some */ + if( pa->maxpoints == 0 || ! pa->serialized_pointlist ) + { + pa->maxpoints = 32; + pa->npoints = 0; + pa->serialized_pointlist = lwalloc(ptarray_point_size(pa) * pa->maxpoints); + } + + /* Error out if we have a bad situation */ + if ( pa->npoints > pa->maxpoints ) + { + lwerror("npoints (%d) is greater than maxpoints (%d)", pa->npoints, pa->maxpoints); + return LW_FAILURE; + } + + /* Check if we have enough storage, add more if necessary */ + if( pa->npoints == pa->maxpoints ) + { + pa->maxpoints *= 2; + pa->serialized_pointlist = lwrealloc(pa->serialized_pointlist, ptarray_point_size(pa) * pa->maxpoints); + } + + /* Make space to insert the new point */ + if( where < pa->npoints ) + { + size_t copy_size = point_size * (pa->npoints - where); + memmove(getPoint_internal(pa, where+1), getPoint_internal(pa, where), copy_size); + LWDEBUGF(5,"copying %d bytes to start vertex %d from start vertex %d", copy_size, where+1, where); + } + + /* We have one more point */ + ++pa->npoints; + + /* Copy the new point into the gap */ + ptarray_set_point4d(pa, where, p); + LWDEBUGF(5,"copying new point to start vertex %d", point_size, where); + + return LW_SUCCESS; +} + +int +ptarray_append_point(POINTARRAY *pa, const POINT4D *pt, int repeated_points) +{ + /* Check for pathology */ + if( ! pa || ! pt ) + { + lwerror("ptarray_append_point: null input"); + return LW_FAILURE; + } + + /* Check for duplicate end point */ + if ( repeated_points == LW_FALSE && pa->npoints > 0 ) + { + POINT4D tmp; + getPoint4d_p(pa, pa->npoints-1, &tmp); + LWDEBUGF(4,"checking for duplicate end point (pt = POINT(%g %g) pa->npoints-q = POINT(%g %g))",pt->x,pt->y,tmp.x,tmp.y); + + /* Return LW_SUCCESS and do nothing else if previous point in list is equal to this one */ + if ( (pt->x == tmp.x) && (pt->y == tmp.y) && + (FLAGS_GET_Z(pa->flags) ? pt->z == tmp.z : 1) && + (FLAGS_GET_M(pa->flags) ? pt->m == tmp.m : 1) ) + { + return LW_SUCCESS; + } + } + + /* Append is just a special case of insert */ + return ptarray_insert_point(pa, pt, pa->npoints); +} + +int +ptarray_append_ptarray(POINTARRAY *pa1, POINTARRAY *pa2, double gap_tolerance) +{ + unsigned int poff = 0; + unsigned int npoints; + unsigned int ncap; + unsigned int ptsize; + + /* Check for pathology */ + if( ! pa1 || ! pa2 ) + { + lwerror("ptarray_append_ptarray: null input"); + return LW_FAILURE; + } + + npoints = pa2->npoints; + + if ( ! npoints ) return LW_SUCCESS; /* nothing more to do */ + + if( FLAGS_GET_READONLY(pa1->flags) ) + { + lwerror("ptarray_append_ptarray: target pointarray is read-only"); + return LW_FAILURE; + } + + if( FLAGS_GET_ZM(pa1->flags) != FLAGS_GET_ZM(pa2->flags) ) + { + lwerror("ptarray_append_ptarray: appending mixed dimensionality is not allowed"); + return LW_FAILURE; + } + + ptsize = ptarray_point_size(pa1); + + /* Check for duplicate end point */ + if ( pa1->npoints ) + { + POINT2D tmp1, tmp2; + getPoint2d_p(pa1, pa1->npoints-1, &tmp1); + getPoint2d_p(pa2, 0, &tmp2); + + /* If the end point and start point are the same, then don't copy start point */ + if (p2d_same(&tmp1, &tmp2)) { + poff = 1; + --npoints; + } + else if ( gap_tolerance == 0 || ( gap_tolerance > 0 && + distance2d_pt_pt(&tmp1, &tmp2) > gap_tolerance ) ) + { + lwerror("Second line start point too far from first line end point"); + return LW_FAILURE; + } + } + + /* Check if we need extra space */ + ncap = pa1->npoints + npoints; + if ( pa1->maxpoints < ncap ) + { + pa1->maxpoints = ncap > pa1->maxpoints*2 ? + ncap : pa1->maxpoints*2; + pa1->serialized_pointlist = lwrealloc(pa1->serialized_pointlist, ptsize * pa1->maxpoints); + } + + memcpy(getPoint_internal(pa1, pa1->npoints), + getPoint_internal(pa2, poff), ptsize * npoints); + + pa1->npoints = ncap; + + return LW_SUCCESS; +} + +/* +* Add a point into a pointarray. Only adds as many dimensions as the +* pointarray supports. +*/ +int +ptarray_remove_point(POINTARRAY *pa, uint32_t where) +{ + /* Check for pathology */ + if( ! pa ) + { + lwerror("ptarray_remove_point: null input"); + return LW_FAILURE; + } + + /* Error on invalid offset value */ + if ( where >= pa->npoints ) + { + lwerror("ptarray_remove_point: offset out of range (%d)", where); + return LW_FAILURE; + } + + /* If the point is any but the last, we need to copy the data back one point */ + if (where < pa->npoints - 1) + memmove(getPoint_internal(pa, where), + getPoint_internal(pa, where + 1), + ptarray_point_size(pa) * (pa->npoints - where - 1)); + + /* We have one less point */ + pa->npoints--; + + return LW_SUCCESS; +} + +/** +* Build a new #POINTARRAY, but on top of someone else's ordinate array. +* Flag as read-only, so that ptarray_free() does not free the serialized_ptlist +*/ +POINTARRAY* ptarray_construct_reference_data(char hasz, char hasm, uint32_t npoints, uint8_t *ptlist) +{ + POINTARRAY *pa = lwalloc(sizeof(POINTARRAY)); + LWDEBUGF(5, "hasz = %d, hasm = %d, npoints = %d, ptlist = %p", hasz, hasm, npoints, ptlist); + pa->flags = lwflags(hasz, hasm, 0); + FLAGS_SET_READONLY(pa->flags, 1); /* We don't own this memory, so we can't alter or free it. */ + pa->npoints = npoints; + pa->maxpoints = npoints; + pa->serialized_pointlist = ptlist; + return pa; +} + + +POINTARRAY* +ptarray_construct_copy_data(char hasz, char hasm, uint32_t npoints, const uint8_t *ptlist) +{ + POINTARRAY *pa = lwalloc(sizeof(POINTARRAY)); + + pa->flags = lwflags(hasz, hasm, 0); + pa->npoints = npoints; + pa->maxpoints = npoints; + + if ( npoints > 0 ) + { + pa->serialized_pointlist = lwalloc(ptarray_point_size(pa) * npoints); + memcpy(pa->serialized_pointlist, ptlist, ptarray_point_size(pa) * npoints); + } + else + { + pa->serialized_pointlist = NULL; + } + + return pa; +} + +void +ptarray_free(POINTARRAY *pa) +{ + if (pa) + { + if (pa->serialized_pointlist && (!FLAGS_GET_READONLY(pa->flags))) + lwfree(pa->serialized_pointlist); + lwfree(pa); + } +} + + +void +ptarray_reverse_in_place(POINTARRAY *pa) +{ + if (!pa->npoints) + return; + uint32_t i; + uint32_t last = pa->npoints - 1; + uint32_t mid = pa->npoints / 2; + + double *d = (double*)(pa->serialized_pointlist); + int j; + int ndims = FLAGS_NDIMS(pa->flags); + for (i = 0; i < mid; i++) + { + for (j = 0; j < ndims; j++) + { + double buf; + buf = d[i*ndims+j]; + d[i*ndims+j] = d[(last-i)*ndims+j]; + d[(last-i)*ndims+j] = buf; + } + } + return; +} + + +/** + * Reverse X and Y axis on a given POINTARRAY + */ +POINTARRAY* +ptarray_flip_coordinates(POINTARRAY *pa) +{ + uint32_t i; + double d; + POINT4D p; + + for (i=0 ; i < pa->npoints ; i++) + { + getPoint4d_p(pa, i, &p); + d = p.y; + p.y = p.x; + p.x = d; + ptarray_set_point4d(pa, i, &p); + } + + return pa; +} + +void +ptarray_swap_ordinates(POINTARRAY *pa, LWORD o1, LWORD o2) +{ + uint32_t i; + double d, *dp1, *dp2; + POINT4D p; + + dp1 = ((double*)&p)+(unsigned)o1; + dp2 = ((double*)&p)+(unsigned)o2; + for (i=0 ; i < pa->npoints ; i++) + { + getPoint4d_p(pa, i, &p); + d = *dp2; + *dp2 = *dp1; + *dp1 = d; + ptarray_set_point4d(pa, i, &p); + } +} + +/** + * @brief Returns a modified #POINTARRAY so that no segment is + * longer than the given distance (computed using 2d). + * + * Every input point is kept. + * Z and M values for added points (if needed) are set proportionally. + */ +POINTARRAY * +ptarray_segmentize2d(const POINTARRAY *ipa, double dist) +{ + double segdist; + POINT4D p1, p2; + POINT4D pbuf; + POINTARRAY *opa; + uint32_t i, j, nseg; + int hasz = FLAGS_GET_Z(ipa->flags); + int hasm = FLAGS_GET_M(ipa->flags); + + pbuf.x = pbuf.y = pbuf.z = pbuf.m = 0; + + /* Initial storage */ + opa = ptarray_construct_empty(hasz, hasm, ipa->npoints); + + /* Add first point */ + getPoint4d_p(ipa, 0, &p1); + ptarray_append_point(opa, &p1, LW_FALSE); + + /* Loop on all other input points */ + for (i = 1; i < ipa->npoints; i++) + { + /* + * We use these pointers to avoid + * "strict-aliasing rules break" warning raised + * by gcc (3.3 and up). + * + * It looks that casting a variable address (also + * referred to as "type-punned pointer") + * breaks those "strict" rules. + */ + POINT4D *p1ptr=&p1, *p2ptr=&p2; + double segments; + + getPoint4d_p(ipa, i, &p2); + + segdist = distance2d_pt_pt((POINT2D *)p1ptr, (POINT2D *)p2ptr); + /* Split input segment into shorter even chunks */ + segments = ceil(segdist / dist); + + /* Uses INT32_MAX instead of UINT32_MAX to be safe that it fits */ + if (segments >= INT32_MAX) + { + lwnotice("%s:%d - %s: Too many segments required (%e)", + __FILE__, __LINE__,__func__, segments); + ptarray_free(opa); + return NULL; + } + nseg = segments; + + for (j = 1; j < nseg; j++) + { + pbuf.x = p1.x + (p2.x - p1.x) * j / nseg; + pbuf.y = p1.y + (p2.y - p1.y) * j / nseg; + if (hasz) + pbuf.z = p1.z + (p2.z - p1.z) * j / nseg; + if (hasm) + pbuf.m = p1.m + (p2.m - p1.m) * j / nseg; + ptarray_append_point(opa, &pbuf, LW_FALSE); + LW_ON_INTERRUPT(ptarray_free(opa); return NULL); + } + + ptarray_append_point(opa, &p2, (ipa->npoints == 2) ? LW_TRUE : LW_FALSE); + p1 = p2; + LW_ON_INTERRUPT(ptarray_free(opa); return NULL); + } + + return opa; +} + +char +ptarray_same(const POINTARRAY *pa1, const POINTARRAY *pa2) +{ + uint32_t i; + size_t ptsize; + + if ( FLAGS_GET_ZM(pa1->flags) != FLAGS_GET_ZM(pa2->flags) ) return LW_FALSE; + LWDEBUG(5,"dimensions are the same"); + + if ( pa1->npoints != pa2->npoints ) return LW_FALSE; + LWDEBUG(5,"npoints are the same"); + + ptsize = ptarray_point_size(pa1); + LWDEBUGF(5, "ptsize = %d", ptsize); + + for (i=0; inpoints; i++) + { + if ( memcmp(getPoint_internal(pa1, i), getPoint_internal(pa2, i), ptsize) ) + return LW_FALSE; + LWDEBUGF(5,"point #%d is the same",i); + } + + return LW_TRUE; +} + +char +ptarray_same2d(const POINTARRAY *pa1, const POINTARRAY *pa2) +{ + uint32_t i; + + if ( FLAGS_GET_ZM(pa1->flags) != FLAGS_GET_ZM(pa2->flags) ) return LW_FALSE; + LWDEBUG(5,"dimensions are the same"); + + if ( pa1->npoints != pa2->npoints ) return LW_FALSE; + LWDEBUG(5,"npoints are the same"); + + for (i=0; inpoints; i++) + { + if ( memcmp(getPoint_internal(pa1, i), getPoint_internal(pa2, i), sizeof(POINT2D)) ) + return LW_FALSE; + LWDEBUGF(5,"point #%d is the same",i); + } + + return LW_TRUE; +} + +POINTARRAY * +ptarray_addPoint(const POINTARRAY *pa, uint8_t *p, size_t pdims, uint32_t where) +{ + POINTARRAY *ret; + POINT4D pbuf; + size_t ptsize = ptarray_point_size(pa); + + LWDEBUGF(3, "pa %x p %x size %d where %d", + pa, p, pdims, where); + + if ( pdims < 2 || pdims > 4 ) + { + lwerror("ptarray_addPoint: point dimension out of range (%d)", + pdims); + return NULL; + } + + if ( where > pa->npoints ) + { + lwerror("ptarray_addPoint: offset out of range (%d)", + where); + return NULL; + } + + LWDEBUG(3, "called with a %dD point"); + + pbuf.x = pbuf.y = pbuf.z = pbuf.m = 0.0; + memcpy((uint8_t *)&pbuf, p, pdims*sizeof(double)); + + LWDEBUG(3, "initialized point buffer"); + + ret = ptarray_construct(FLAGS_GET_Z(pa->flags), + FLAGS_GET_M(pa->flags), pa->npoints+1); + + + if ( where ) + { + memcpy(getPoint_internal(ret, 0), getPoint_internal(pa, 0), ptsize*where); + } + + memcpy(getPoint_internal(ret, where), (uint8_t *)&pbuf, ptsize); + + if ( where+1 != ret->npoints ) + { + memcpy(getPoint_internal(ret, where+1), + getPoint_internal(pa, where), + ptsize*(pa->npoints-where)); + } + + return ret; +} + +POINTARRAY * +ptarray_removePoint(POINTARRAY *pa, uint32_t which) +{ + POINTARRAY *ret; + size_t ptsize = ptarray_point_size(pa); + + LWDEBUGF(3, "pa %x which %d", pa, which); + +#if PARANOIA_LEVEL > 0 + if ( which > pa->npoints-1 ) + { + lwerror("%s [%d] offset (%d) out of range (%d..%d)", __FILE__, __LINE__, + which, 0, pa->npoints-1); + return NULL; + } + + if ( pa->npoints < 3 ) + { + lwerror("%s [%d] can't remove a point from a 2-vertex POINTARRAY", __FILE__, __LINE__); + return NULL; + } +#endif + + ret = ptarray_construct(FLAGS_GET_Z(pa->flags), + FLAGS_GET_M(pa->flags), pa->npoints-1); + + /* copy initial part */ + if ( which ) + { + memcpy(getPoint_internal(ret, 0), getPoint_internal(pa, 0), ptsize*which); + } + + /* copy final part */ + if ( which < pa->npoints-1 ) + { + memcpy(getPoint_internal(ret, which), getPoint_internal(pa, which+1), + ptsize*(pa->npoints-which-1)); + } + + return ret; +} + +POINTARRAY * +ptarray_merge(POINTARRAY *pa1, POINTARRAY *pa2) +{ + POINTARRAY *pa; + size_t ptsize = ptarray_point_size(pa1); + + if (FLAGS_GET_ZM(pa1->flags) != FLAGS_GET_ZM(pa2->flags)) + lwerror("ptarray_cat: Mixed dimension"); + + pa = ptarray_construct( FLAGS_GET_Z(pa1->flags), + FLAGS_GET_M(pa1->flags), + pa1->npoints + pa2->npoints); + + memcpy( getPoint_internal(pa, 0), + getPoint_internal(pa1, 0), + ptsize*(pa1->npoints)); + + memcpy( getPoint_internal(pa, pa1->npoints), + getPoint_internal(pa2, 0), + ptsize*(pa2->npoints)); + + ptarray_free(pa1); + ptarray_free(pa2); + + return pa; +} + + +/** + * @brief Deep clone a pointarray (also clones serialized pointlist) + */ +POINTARRAY * +ptarray_clone_deep(const POINTARRAY *in) +{ + POINTARRAY *out = lwalloc(sizeof(POINTARRAY)); + + LWDEBUG(3, "ptarray_clone_deep called."); + + out->flags = in->flags; + out->npoints = in->npoints; + out->maxpoints = in->npoints; + + FLAGS_SET_READONLY(out->flags, 0); + + if (!in->npoints) + { + // Avoid calling lwalloc of 0 bytes + out->serialized_pointlist = NULL; + } + else + { + size_t size = in->npoints * ptarray_point_size(in); + out->serialized_pointlist = lwalloc(size); + memcpy(out->serialized_pointlist, in->serialized_pointlist, size); + } + + return out; +} + +/** + * @brief Clone a POINTARRAY object. Serialized pointlist is not copied. + */ +POINTARRAY * +ptarray_clone(const POINTARRAY *in) +{ + POINTARRAY *out = lwalloc(sizeof(POINTARRAY)); + + LWDEBUG(3, "ptarray_clone called."); + + out->flags = in->flags; + out->npoints = in->npoints; + out->maxpoints = in->maxpoints; + + FLAGS_SET_READONLY(out->flags, 1); + + out->serialized_pointlist = in->serialized_pointlist; + + return out; +} + +/** +* Check for ring closure using whatever dimensionality is declared on the +* pointarray. +*/ +int +ptarray_is_closed(const POINTARRAY *in) +{ + if (!in) + { + lwerror("ptarray_is_closed: called with null point array"); + return 0; + } + if (in->npoints <= 1 ) return in->npoints; /* single-point are closed, empty not closed */ + + return 0 == memcmp(getPoint_internal(in, 0), getPoint_internal(in, in->npoints-1), ptarray_point_size(in)); +} + + +int +ptarray_is_closed_2d(const POINTARRAY *in) +{ + if (!in) + { + lwerror("ptarray_is_closed_2d: called with null point array"); + return 0; + } + if (in->npoints <= 1 ) return in->npoints; /* single-point are closed, empty not closed */ + + return 0 == memcmp(getPoint_internal(in, 0), getPoint_internal(in, in->npoints-1), sizeof(POINT2D) ); +} + +int +ptarray_is_closed_3d(const POINTARRAY *in) +{ + if (!in) + { + lwerror("ptarray_is_closed_3d: called with null point array"); + return 0; + } + if (in->npoints <= 1 ) return in->npoints; /* single-point are closed, empty not closed */ + + return 0 == memcmp(getPoint_internal(in, 0), getPoint_internal(in, in->npoints-1), sizeof(POINT3D) ); +} + +int +ptarray_is_closed_z(const POINTARRAY *in) +{ + if ( FLAGS_GET_Z(in->flags) ) + return ptarray_is_closed_3d(in); + else + return ptarray_is_closed_2d(in); +} + +/** +* Return 1 if the point is inside the POINTARRAY, -1 if it is outside, +* and 0 if it is on the boundary. +*/ +int +ptarray_contains_point(const POINTARRAY *pa, const POINT2D *pt) +{ + return ptarray_contains_point_partial(pa, pt, LW_TRUE, NULL); +} + +int +ptarray_contains_point_partial(const POINTARRAY *pa, const POINT2D *pt, int check_closed, int *winding_number) +{ + int wn = 0; + uint32_t i; + double side; + const POINT2D *seg1; + const POINT2D *seg2; + double ymin, ymax; + + seg1 = getPoint2d_cp(pa, 0); + seg2 = getPoint2d_cp(pa, pa->npoints-1); + if ( check_closed && ! p2d_same(seg1, seg2) ) + lwerror("ptarray_contains_point called on unclosed ring"); + + for ( i=1; i < pa->npoints; i++ ) + { + seg2 = getPoint2d_cp(pa, i); + + /* Zero length segments are ignored. */ + if ( seg1->x == seg2->x && seg1->y == seg2->y ) + { + seg1 = seg2; + continue; + } + + ymin = FP_MIN(seg1->y, seg2->y); + ymax = FP_MAX(seg1->y, seg2->y); + + /* Only test segments in our vertical range */ + if ( pt->y > ymax || pt->y < ymin ) + { + seg1 = seg2; + continue; + } + + side = lw_segment_side(seg1, seg2, pt); + + /* + * A point on the boundary of a ring is not contained. + * WAS: if (fabs(side) < 1e-12), see #852 + */ + if ( (side == 0) && lw_pt_in_seg(pt, seg1, seg2) ) + { + return LW_BOUNDARY; + } + + /* + * If the point is to the left of the line, and it's rising, + * then the line is to the right of the point and + * circling counter-clockwise, so increment. + */ + if ( (side < 0) && (seg1->y <= pt->y) && (pt->y < seg2->y) ) + { + wn++; + } + + /* + * If the point is to the right of the line, and it's falling, + * then the line is to the right of the point and circling + * clockwise, so decrement. + */ + else if ( (side > 0) && (seg2->y <= pt->y) && (pt->y < seg1->y) ) + { + wn--; + } + + seg1 = seg2; + } + + /* Sent out the winding number for calls that are building on this as a primitive */ + if ( winding_number ) + *winding_number = wn; + + /* Outside */ + if (wn == 0) + { + return LW_OUTSIDE; + } + + /* Inside */ + return LW_INSIDE; +} + +/** +* For POINTARRAYs representing CIRCULARSTRINGS. That is, linked triples +* with each triple being control points of a circular arc. Such +* POINTARRAYs have an odd number of vertices. +* +* Return 1 if the point is inside the POINTARRAY, -1 if it is outside, +* and 0 if it is on the boundary. +*/ + +int +ptarrayarc_contains_point(const POINTARRAY *pa, const POINT2D *pt) +{ + return ptarrayarc_contains_point_partial(pa, pt, LW_TRUE /* Check closed*/, NULL); +} + +int +ptarrayarc_contains_point_partial(const POINTARRAY *pa, const POINT2D *pt, int check_closed, int *winding_number) +{ + int wn = 0; + uint32_t i; + int side; + const POINT2D *seg1; + const POINT2D *seg2; + const POINT2D *seg3; + GBOX gbox; + + /* Check for not an arc ring (always have odd # of points) */ + if ( (pa->npoints % 2) == 0 ) + { + lwerror("ptarrayarc_contains_point called with even number of points"); + return LW_OUTSIDE; + } + + /* Check for not an arc ring (always have >= 3 points) */ + if ( pa->npoints < 3 ) + { + lwerror("ptarrayarc_contains_point called too-short pointarray"); + return LW_OUTSIDE; + } + + /* Check for unclosed case */ + seg1 = getPoint2d_cp(pa, 0); + seg3 = getPoint2d_cp(pa, pa->npoints-1); + if ( check_closed && ! p2d_same(seg1, seg3) ) + { + lwerror("ptarrayarc_contains_point called on unclosed ring"); + return LW_OUTSIDE; + } + /* OK, it's closed. Is it just one circle? */ + else if ( p2d_same(seg1, seg3) && pa->npoints == 3 ) + { + double radius, d; + POINT2D c; + seg2 = getPoint2d_cp(pa, 1); + + /* Wait, it's just a point, so it can't contain anything */ + if ( lw_arc_is_pt(seg1, seg2, seg3) ) + return LW_OUTSIDE; + + /* See if the point is within the circle radius */ + radius = lw_arc_center(seg1, seg2, seg3, &c); + d = distance2d_pt_pt(pt, &c); + if ( FP_EQUALS(d, radius) ) + return LW_BOUNDARY; /* Boundary of circle */ + else if ( d < radius ) + return LW_INSIDE; /* Inside circle */ + else + return LW_OUTSIDE; /* Outside circle */ + } + else if ( p2d_same(seg1, pt) || p2d_same(seg3, pt) ) + { + return LW_BOUNDARY; /* Boundary case */ + } + + /* Start on the ring */ + seg1 = getPoint2d_cp(pa, 0); + for ( i=1; i < pa->npoints; i += 2 ) + { + seg2 = getPoint2d_cp(pa, i); + seg3 = getPoint2d_cp(pa, i+1); + + /* Catch an easy boundary case */ + if( p2d_same(seg3, pt) ) + return LW_BOUNDARY; + + /* Skip arcs that have no size */ + if ( lw_arc_is_pt(seg1, seg2, seg3) ) + { + seg1 = seg3; + continue; + } + + /* Only test segments in our vertical range */ + lw_arc_calculate_gbox_cartesian_2d(seg1, seg2, seg3, &gbox); + if ( pt->y > gbox.ymax || pt->y < gbox.ymin ) + { + seg1 = seg3; + continue; + } + + /* Outside of horizontal range, and not between end points we also skip */ + if ( (pt->x > gbox.xmax || pt->x < gbox.xmin) && + (pt->y > FP_MAX(seg1->y, seg3->y) || pt->y < FP_MIN(seg1->y, seg3->y)) ) + { + seg1 = seg3; + continue; + } + + side = lw_arc_side(seg1, seg2, seg3, pt); + + /* On the boundary */ + if ( (side == 0) && lw_pt_in_arc(pt, seg1, seg2, seg3) ) + { + return LW_BOUNDARY; + } + + /* Going "up"! Point to left of arc. */ + if ( side < 0 && (seg1->y <= pt->y) && (pt->y < seg3->y) ) + { + wn++; + } + + /* Going "down"! */ + if ( side > 0 && (seg3->y <= pt->y) && (pt->y < seg1->y) ) + { + wn--; + } + + /* Inside the arc! */ + if ( pt->x <= gbox.xmax && pt->x >= gbox.xmin ) + { + POINT2D C; + double radius = lw_arc_center(seg1, seg2, seg3, &C); + double d = distance2d_pt_pt(pt, &C); + + /* On the boundary! */ + if ( d == radius ) + return LW_BOUNDARY; + + /* Within the arc! */ + if ( d < radius ) + { + /* Left side, increment winding number */ + if ( side < 0 ) + wn++; + /* Right side, decrement winding number */ + if ( side > 0 ) + wn--; + } + } + + seg1 = seg3; + } + + /* Sent out the winding number for calls that are building on this as a primitive */ + if ( winding_number ) + *winding_number = wn; + + /* Outside */ + if (wn == 0) + { + return LW_OUTSIDE; + } + + /* Inside */ + return LW_INSIDE; +} + +/** +* Returns the area in cartesian units. Area is negative if ring is oriented CCW, +* positive if it is oriented CW and zero if the ring is degenerate or flat. +* http://en.wikipedia.org/wiki/Shoelace_formula +*/ +double +ptarray_signed_area(const POINTARRAY *pa) +{ + const POINT2D *P1; + const POINT2D *P2; + const POINT2D *P3; + double sum = 0.0; + double x0, x, y1, y2; + uint32_t i; + + if (! pa || pa->npoints < 3 ) + return 0.0; + + P1 = getPoint2d_cp(pa, 0); + P2 = getPoint2d_cp(pa, 1); + x0 = P1->x; + for ( i = 2; i < pa->npoints; i++ ) + { + P3 = getPoint2d_cp(pa, i); + x = P2->x - x0; + y1 = P3->y; + y2 = P1->y; + sum += x * (y2-y1); + + /* Move forwards! */ + P1 = P2; + P2 = P3; + } + return sum / 2.0; +} + +int +ptarray_isccw(const POINTARRAY *pa) +{ + double area = 0; + area = ptarray_signed_area(pa); + if ( area > 0 ) return LW_FALSE; + else return LW_TRUE; +} + +POINTARRAY* +ptarray_force_dims(const POINTARRAY *pa, int hasz, int hasm, double zval, double mval) +{ + /* TODO handle zero-length point arrays */ + uint32_t i; + int in_hasz = FLAGS_GET_Z(pa->flags); + int in_hasm = FLAGS_GET_M(pa->flags); + POINT4D pt; + POINTARRAY *pa_out = ptarray_construct_empty(hasz, hasm, pa->npoints); + + for( i = 0; i < pa->npoints; i++ ) + { + getPoint4d_p(pa, i, &pt); + if( hasz && ! in_hasz ) + pt.z = zval; + if( hasm && ! in_hasm ) + pt.m = mval; + ptarray_append_point(pa_out, &pt, LW_TRUE); + } + + return pa_out; +} + +POINTARRAY * +ptarray_substring(POINTARRAY *ipa, double from, double to, double tolerance) +{ + POINTARRAY *dpa; + POINT4D pt; + POINT4D p1, p2; + POINT4D *p1ptr=&p1; /* don't break strict-aliasing rule */ + POINT4D *p2ptr=&p2; + int nsegs, i; + double length, slength, tlength; + int state = 0; /* 0=before, 1=inside */ + + /* + * Create a dynamic pointarray with an initial capacity + * equal to full copy of input points + */ + dpa = ptarray_construct_empty(FLAGS_GET_Z(ipa->flags), FLAGS_GET_M(ipa->flags), ipa->npoints); + + /* Compute total line length */ + length = ptarray_length_2d(ipa); + + + LWDEBUGF(3, "Total length: %g", length); + + + /* Get 'from' and 'to' lengths */ + from = length*from; + to = length*to; + + + LWDEBUGF(3, "From/To: %g/%g", from, to); + + + tlength = 0; + getPoint4d_p(ipa, 0, &p1); + nsegs = ipa->npoints - 1; + for ( i = 0; i < nsegs; i++ ) + { + double dseg; + + getPoint4d_p(ipa, i+1, &p2); + + + LWDEBUGF(3 ,"Segment %d: (%g,%g,%g,%g)-(%g,%g,%g,%g)", + i, p1.x, p1.y, p1.z, p1.m, p2.x, p2.y, p2.z, p2.m); + + + /* Find the length of this segment */ + slength = distance2d_pt_pt((POINT2D *)p1ptr, (POINT2D *)p2ptr); + + /* + * We are before requested start. + */ + if ( state == 0 ) /* before */ + { + + LWDEBUG(3, " Before start"); + + if ( fabs ( from - ( tlength + slength ) ) <= tolerance ) + { + + LWDEBUG(3, " Second point is our start"); + + /* + * Second point is our start + */ + ptarray_append_point(dpa, &p2, LW_FALSE); + state=1; /* we're inside now */ + goto END; + } + + else if ( fabs(from - tlength) <= tolerance ) + { + + LWDEBUG(3, " First point is our start"); + + /* + * First point is our start + */ + ptarray_append_point(dpa, &p1, LW_FALSE); + + /* + * We're inside now, but will check + * 'to' point as well + */ + state=1; + } + + /* + * Didn't reach the 'from' point, + * nothing to do + */ + else if ( from > tlength + slength ) goto END; + + else /* tlength < from < tlength+slength */ + { + + LWDEBUG(3, " Seg contains first point"); + + /* + * Our start is between first and + * second point + */ + dseg = (from - tlength) / slength; + + interpolate_point4d(&p1, &p2, &pt, dseg); + + ptarray_append_point(dpa, &pt, LW_FALSE); + + /* + * We're inside now, but will check + * 'to' point as well + */ + state=1; + } + } + + if ( state == 1 ) /* inside */ + { + + LWDEBUG(3, " Inside"); + + /* + * 'to' point is our second point. + */ + if ( fabs(to - ( tlength + slength ) ) <= tolerance ) + { + + LWDEBUG(3, " Second point is our end"); + + ptarray_append_point(dpa, &p2, LW_FALSE); + break; /* substring complete */ + } + + /* + * 'to' point is our first point. + * (should only happen if 'to' is 0) + */ + else if ( fabs(to - tlength) <= tolerance ) + { + + LWDEBUG(3, " First point is our end"); + + ptarray_append_point(dpa, &p1, LW_FALSE); + + break; /* substring complete */ + } + + /* + * Didn't reach the 'end' point, + * just copy second point + */ + else if ( to > tlength + slength ) + { + ptarray_append_point(dpa, &p2, LW_FALSE); + goto END; + } + + /* + * 'to' point falls on this segment + * Interpolate and break. + */ + else if ( to < tlength + slength ) + { + + LWDEBUG(3, " Seg contains our end"); + + dseg = (to - tlength) / slength; + interpolate_point4d(&p1, &p2, &pt, dseg); + + ptarray_append_point(dpa, &pt, LW_FALSE); + + break; + } + + else + { + LWDEBUG(3, "Unhandled case"); + } + } + + +END: + + tlength += slength; + memcpy(&p1, &p2, sizeof(POINT4D)); + } + + LWDEBUGF(3, "Out of loop, ptarray has %d points", dpa->npoints); + + return dpa; +} + +/* + * Write into the *ret argument coordinates of the closes point on + * the given segment to the reference input point. + */ +void +closest_point_on_segment(const POINT4D *p, const POINT4D *A, const POINT4D *B, POINT4D *ret) +{ + double r; + + if ( FP_EQUALS(A->x, B->x) && FP_EQUALS(A->y, B->y) ) + { + *ret = *A; + return; + } + + /* + * We use comp.graphics.algorithms Frequently Asked Questions method + * + * (1) AC dot AB + * r = ---------- + * ||AB||^2 + * r has the following meaning: + * r=0 P = A + * r=1 P = B + * r<0 P is on the backward extension of AB + * r>1 P is on the forward extension of AB + * 0x-A->x) * (B->x-A->x) + (p->y-A->y) * (B->y-A->y) )/( (B->x-A->x)*(B->x-A->x) +(B->y-A->y)*(B->y-A->y) ); + + if (r<=0) + { + *ret = *A; + return; + } + if (r>=1) + { + *ret = *B; + return; + } + + ret->x = A->x + ( (B->x - A->x) * r ); + ret->y = A->y + ( (B->y - A->y) * r ); + ret->z = A->z + ( (B->z - A->z) * r ); + ret->m = A->m + ( (B->m - A->m) * r ); +} + +int +ptarray_closest_segment_2d(const POINTARRAY *pa, const POINT2D *qp, double *dist) +{ + const POINT2D *start = getPoint2d_cp(pa, 0), *end = NULL; + uint32_t t, seg=0; + double mindist=DBL_MAX; + + /* Loop through pointarray looking for nearest segment */ + for (t=1; tnpoints; t++) + { + double dist_sqr; + end = getPoint2d_cp(pa, t); + dist_sqr = distance2d_sqr_pt_seg(qp, start, end); + + if (dist_sqr < mindist) + { + mindist = dist_sqr; + seg=t-1; + if ( mindist == 0 ) + { + LWDEBUG(3, "Breaking on mindist=0"); + break; + } + } + + start = end; + } + + if ( dist ) *dist = sqrt(mindist); + return seg; +} + + +int +ptarray_closest_vertex_2d(const POINTARRAY *pa, const POINT2D *qp, double *dist) +{ + uint32_t t, pn=0; + const POINT2D *p; + double mindist = DBL_MAX; + + /* Loop through pointarray looking for nearest segment */ + for (t=0; tnpoints; t++) + { + double dist_sqr; + p = getPoint2d_cp(pa, t); + dist_sqr = distance2d_sqr_pt_pt(p, qp); + + if (dist_sqr < mindist) + { + mindist = dist_sqr; + pn = t; + if ( mindist == 0 ) + { + LWDEBUG(3, "Breaking on mindist=0"); + break; + } + } + } + if ( dist ) *dist = sqrt(mindist); + return pn; +} + +/* + * Given a point, returns the location of closest point on pointarray + * and, optionally, it's actual distance from the point array. + */ +double +ptarray_locate_point(const POINTARRAY *pa, const POINT4D *p4d, double *mindistout, POINT4D *proj4d) +{ + double mindist=DBL_MAX; + double tlen, plen; + uint32_t t, seg=0; + POINT4D start4d, end4d, projtmp; + POINT2D proj, p; + const POINT2D *start = NULL, *end = NULL; + + /* Initialize our 2D copy of the input parameter */ + p.x = p4d->x; + p.y = p4d->y; + + if ( ! proj4d ) proj4d = &projtmp; + + /* Check for special cases (length 0 and 1) */ + if ( pa->npoints <= 1 ) + { + if ( pa->npoints == 1 ) + { + getPoint4d_p(pa, 0, proj4d); + if ( mindistout ) + *mindistout = distance2d_pt_pt(&p, getPoint2d_cp(pa, 0)); + } + return 0.0; + } + + start = getPoint2d_cp(pa, 0); + /* Loop through pointarray looking for nearest segment */ + for (t=1; tnpoints; t++) + { + double dist_sqr; + end = getPoint2d_cp(pa, t); + dist_sqr = distance2d_sqr_pt_seg(&p, start, end); + + if (dist_sqr < mindist) + { + mindist = dist_sqr; + seg=t-1; + if ( mindist == 0 ) + { + LWDEBUG(3, "Breaking on mindist=0"); + break; + } + } + + start = end; + } + mindist = sqrt(mindist); + + if ( mindistout ) *mindistout = mindist; + + LWDEBUGF(3, "Closest segment: %d", seg); + LWDEBUGF(3, "mindist: %g", mindist); + + /* + * We need to project the + * point on the closest segment. + */ + getPoint4d_p(pa, seg, &start4d); + getPoint4d_p(pa, seg+1, &end4d); + closest_point_on_segment(p4d, &start4d, &end4d, proj4d); + + /* Copy 4D values into 2D holder */ + proj.x = proj4d->x; + proj.y = proj4d->y; + + LWDEBUGF(3, "Closest segment:%d, npoints:%d", seg, pa->npoints); + + /* For robustness, force 1 when closest point == endpoint */ + if ( (seg >= (pa->npoints-2)) && p2d_same(&proj, end) ) + { + return 1.0; + } + + LWDEBUGF(3, "Closest point on segment: %g,%g", proj.x, proj.y); + + tlen = ptarray_length_2d(pa); + + LWDEBUGF(3, "tlen %g", tlen); + + /* Location of any point on a zero-length line is 0 */ + /* See http://trac.osgeo.org/postgis/ticket/1772#comment:2 */ + if ( tlen == 0 ) return 0; + + plen=0; + start = getPoint2d_cp(pa, 0); + for (t=0; t 180 becomes X - 360 + */ +void +ptarray_longitude_shift(POINTARRAY *pa) +{ + uint32_t i; + double x; + + for (i=0; inpoints; i++) + { + memcpy(&x, getPoint_internal(pa, i), sizeof(double)); + if ( x < 0 ) x+= 360; + else if ( x > 180 ) x -= 360; + memcpy(getPoint_internal(pa, i), &x, sizeof(double)); + } +} + + +/* + * Returns a POINTARRAY with consecutive equal points + * removed. Equality test on all dimensions of input. + * + * Always returns a newly allocated object. + */ +static POINTARRAY * +ptarray_remove_repeated_points_minpoints(const POINTARRAY *in, double tolerance, int minpoints) +{ + POINTARRAY *out = ptarray_clone_deep(in); + ptarray_remove_repeated_points_in_place(out, tolerance, minpoints); + return out; +} + +POINTARRAY * +ptarray_remove_repeated_points(const POINTARRAY *in, double tolerance) +{ + return ptarray_remove_repeated_points_minpoints(in, tolerance, 2); +} + + +void +ptarray_remove_repeated_points_in_place(POINTARRAY *pa, double tolerance, uint32_t min_points) +{ + uint32_t i; + double tolsq = tolerance * tolerance; + const POINT2D *last = NULL; + const POINT2D *pt; + uint32_t n_points = pa->npoints; + uint32_t n_points_out = 1; + size_t pt_size = ptarray_point_size(pa); + + double dsq = FLT_MAX; + + /* No-op on short inputs */ + if ( n_points <= min_points ) return; + + last = getPoint2d_cp(pa, 0); + void *p_to = ((char *)last) + pt_size; + for (i = 1; i < n_points; i++) + { + int last_point = (i == n_points - 1); + + /* Look straight into the abyss */ + pt = getPoint2d_cp(pa, i); + + /* Don't drop points if we are running short of points */ + if (n_points + n_points_out > min_points + i) + { + if (tolerance > 0.0) + { + /* Only drop points that are within our tolerance */ + dsq = distance2d_sqr_pt_pt(last, pt); + /* Allow any point but the last one to be dropped */ + if (!last_point && dsq <= tolsq) + { + continue; + } + } + else + { + /* At tolerance zero, only skip exact dupes */ + if (memcmp((char*)pt, (char*)last, pt_size) == 0) + continue; + } + + /* Got to last point, and it's not very different from */ + /* the point that preceded it. We want to keep the last */ + /* point, not the second-to-last one, so we pull our write */ + /* index back one value */ + if (last_point && n_points_out > 1 && tolerance > 0.0 && dsq <= tolsq) + { + n_points_out--; + p_to = (char*)p_to - pt_size; + } + } + + /* Compact all remaining values to front of array */ + memcpy(p_to, pt, pt_size); + n_points_out++; + p_to = (char*)p_to + pt_size; + last = pt; + } + /* Adjust array length */ + pa->npoints = n_points_out; + return; +} + +/* Out of the points in pa [itfist .. itlast], finds the one that's farthest away from + * the segment determined by pts[itfist] and pts[itlast]. + * Returns itfirst if no point was found futher away than max_distance_sqr + */ +static uint32_t +ptarray_dp_findsplit_in_place(const POINTARRAY *pts, uint32_t it_first, uint32_t it_last, double max_distance_sqr) +{ + uint32_t split = it_first; + if ((it_first - it_last) < 2) + return it_first; + + const POINT2D *A = getPoint2d_cp(pts, it_first); + const POINT2D *B = getPoint2d_cp(pts, it_last); + + if (distance2d_sqr_pt_pt(A, B) < DBL_EPSILON) + { + /* If p1 == p2, we can just calculate the distance from each point to A */ + for (uint32_t itk = it_first + 1; itk < it_last; itk++) + { + const POINT2D *pk = getPoint2d_cp(pts, itk); + double distance_sqr = distance2d_sqr_pt_pt(pk, A); + if (distance_sqr > max_distance_sqr) + { + split = itk; + max_distance_sqr = distance_sqr; + } + } + return split; + } + + /* This is based on distance2d_sqr_pt_seg, but heavily inlined here to avoid recalculations */ + double ba_x = (B->x - A->x); + double ba_y = (B->y - A->y); + double ab_length_sqr = (ba_x * ba_x + ba_y * ba_y); + /* To avoid the division by ab_length_sqr in the 3rd path, we normalize here + * and multiply in the first two paths [(dot_ac_ab < 0) and (> ab_length_sqr)] */ + max_distance_sqr *= ab_length_sqr; + for (uint32_t itk = it_first + 1; itk < it_last; itk++) + { + const POINT2D *C = getPoint2d_cp(pts, itk); + double distance_sqr; + double ca_x = (C->x - A->x); + double ca_y = (C->y - A->y); + double dot_ac_ab = (ca_x * ba_x + ca_y * ba_y); + + if (dot_ac_ab <= 0.0) + { + distance_sqr = distance2d_sqr_pt_pt(C, A) * ab_length_sqr; + } + else if (dot_ac_ab >= ab_length_sqr) + { + distance_sqr = distance2d_sqr_pt_pt(C, B) * ab_length_sqr; + } + else + { + double s_numerator = ca_x * ba_y - ca_y * ba_x; + distance_sqr = s_numerator * s_numerator; /* Missing division by ab_length_sqr on purpose */ + } + + if (distance_sqr > max_distance_sqr) + { + split = itk; + max_distance_sqr = distance_sqr; + } + } + return split; +} + +/* O(N) simplification for tolearnce = 0 */ +static void +ptarray_simplify_in_place_tolerance0(POINTARRAY *pa) +{ + uint32_t kept_it = 0; + uint32_t last_it = pa->npoints - 1; + const POINT2D *kept_pt = getPoint2d_cp(pa, 0); + const size_t pt_size = ptarray_point_size(pa); + + for (uint32_t i = 1; i < last_it; i++) + { + const POINT2D *curr_pt = getPoint2d_cp(pa, i); + const POINT2D *next_pt = getPoint2d_cp(pa, i + 1); + + double ba_x = next_pt->x - kept_pt->x; + double ba_y = next_pt->y - kept_pt->y; + double ab_length_sqr = ba_x * ba_x + ba_y * ba_y; + + double ca_x = curr_pt->x - kept_pt->x; + double ca_y = curr_pt->y - kept_pt->y; + double dot_ac_ab = ca_x * ba_x + ca_y * ba_y; + double s_numerator = ca_x * ba_y - ca_y * ba_x; + + if (dot_ac_ab < 0.0 || dot_ac_ab > ab_length_sqr || s_numerator != 0) + { + kept_it++; + kept_pt = curr_pt; + if (kept_it != i) + memcpy(pa->serialized_pointlist + pt_size * kept_it, + pa->serialized_pointlist + pt_size * i, + pt_size); + } + } + + /* Append last point */ + kept_it++; + if (kept_it != last_it) + memcpy(pa->serialized_pointlist + pt_size * kept_it, + pa->serialized_pointlist + pt_size * last_it, + pt_size); + pa->npoints = kept_it + 1; +} + +void +ptarray_simplify_in_place(POINTARRAY *pa, double tolerance, uint32_t minpts) +{ + /* Do not try to simplify really short things */ + if (pa->npoints < 3 || pa->npoints <= minpts) + return; + + if (tolerance == 0 && minpts <= 2) + { + ptarray_simplify_in_place_tolerance0(pa); + return; + } + + /* We use this array to keep track of the points we are keeping, so + * we store just TRUE / FALSE in their position */ + uint8_t *kept_points = lwalloc(sizeof(uint8_t) * pa->npoints); + memset(kept_points, LW_FALSE, sizeof(uint8_t) * pa->npoints); + kept_points[0] = LW_TRUE; + kept_points[pa->npoints - 1] = LW_TRUE; + uint32_t keptn = 2; + + /* We use this array as a stack to store the iterators that we are going to need + * in the following steps. + * This is ~10% faster than iterating over @kept_points looking for them + */ + uint32_t *iterator_stack = lwalloc(sizeof(uint32_t) * pa->npoints); + iterator_stack[0] = 0; + uint32_t iterator_stack_size = 1; + + uint32_t it_first = 0; + uint32_t it_last = pa->npoints - 1; + + const double tolerance_sqr = tolerance * tolerance; + /* For the first @minpts points we ignore the tolerance */ + double it_tol = keptn >= minpts ? tolerance_sqr : -1.0; + + while (iterator_stack_size) + { + uint32_t split = ptarray_dp_findsplit_in_place(pa, it_first, it_last, it_tol); + if (split == it_first) + { + it_first = it_last; + it_last = iterator_stack[--iterator_stack_size]; + } + else + { + kept_points[split] = LW_TRUE; + keptn++; + + iterator_stack[iterator_stack_size++] = it_last; + it_last = split; + it_tol = keptn >= minpts ? tolerance_sqr : -1.0; + } + } + + const size_t pt_size = ptarray_point_size(pa); + /* The first point is already in place, so we don't need to copy it */ + size_t kept_it = 1; + if (keptn == 2) + { + /* If there are 2 points remaining, it has to be first and last as + * we added those at the start */ + memcpy(pa->serialized_pointlist + pt_size * kept_it, + pa->serialized_pointlist + pt_size * (pa->npoints - 1), + pt_size); + } + else if (pa->npoints != keptn) /* We don't need to move any points if we are keeping them all */ + { + for (uint32_t i = 1; i < pa->npoints; i++) + { + if (kept_points[i]) + { + memcpy(pa->serialized_pointlist + pt_size * kept_it, + pa->serialized_pointlist + pt_size * i, + pt_size); + kept_it++; + } + } + } + pa->npoints = keptn; + + lwfree(kept_points); + lwfree(iterator_stack); +} + +/************************************************************************/ + +/** +* Find the 2d length of the given #POINTARRAY, using circular +* arc interpolation between each coordinate triple. +* Length(A1, A2, A3, A4, A5) = Length(A1, A2, A3)+Length(A3, A4, A5) +*/ +double +ptarray_arc_length_2d(const POINTARRAY *pts) +{ + double dist = 0.0; + uint32_t i; + const POINT2D *a1; + const POINT2D *a2; + const POINT2D *a3; + + if ( pts->npoints % 2 != 1 ) + lwerror("arc point array with even number of points"); + + a1 = getPoint2d_cp(pts, 0); + + for ( i=2; i < pts->npoints; i += 2 ) + { + a2 = getPoint2d_cp(pts, i-1); + a3 = getPoint2d_cp(pts, i); + dist += lw_arc_length(a1, a2, a3); + a1 = a3; + } + return dist; +} + +/** +* Find the 2d length of the given #POINTARRAY (even if it's 3d) +*/ +double +ptarray_length_2d(const POINTARRAY *pts) +{ + double dist = 0.0; + uint32_t i; + const POINT2D *frm; + const POINT2D *to; + + if ( pts->npoints < 2 ) return 0.0; + + frm = getPoint2d_cp(pts, 0); + + for ( i=1; i < pts->npoints; i++ ) + { + to = getPoint2d_cp(pts, i); + + dist += sqrt( ((frm->x - to->x)*(frm->x - to->x)) + + ((frm->y - to->y)*(frm->y - to->y)) ); + + frm = to; + } + return dist; +} + +/** +* Find the 3d/2d length of the given #POINTARRAY +* (depending on its dimensionality) +*/ +double +ptarray_length(const POINTARRAY *pts) +{ + double dist = 0.0; + uint32_t i; + POINT3DZ frm; + POINT3DZ to; + + if ( pts->npoints < 2 ) return 0.0; + + /* compute 2d length if 3d is not available */ + if ( ! FLAGS_GET_Z(pts->flags) ) return ptarray_length_2d(pts); + + getPoint3dz_p(pts, 0, &frm); + for ( i=1; i < pts->npoints; i++ ) + { + getPoint3dz_p(pts, i, &to); + dist += sqrt( ((frm.x - to.x)*(frm.x - to.x)) + + ((frm.y - to.y)*(frm.y - to.y)) + + ((frm.z - to.z)*(frm.z - to.z)) ); + frm = to; + } + return dist; +} + + + +/** + * Affine transform a pointarray. + */ +void +ptarray_affine(POINTARRAY *pa, const AFFINE *a) +{ + if (FLAGS_GET_Z(pa->flags)) + { + for (uint32_t i = 0; i < pa->npoints; i++) + { + POINT4D *p4d = (POINT4D *)(getPoint_internal(pa, i)); + double x = p4d->x; + double y = p4d->y; + double z = p4d->z; + p4d->x = a->afac * x + a->bfac * y + a->cfac * z + a->xoff; + p4d->y = a->dfac * x + a->efac * y + a->ffac * z + a->yoff; + p4d->z = a->gfac * x + a->hfac * y + a->ifac * z + a->zoff; + } + } + else + { + for (uint32_t i = 0; i < pa->npoints; i++) + { + POINT2D *pt = (POINT2D *)(getPoint_internal(pa, i)); + double x = pt->x; + double y = pt->y; + pt->x = a->afac * x + a->bfac * y + a->xoff; + pt->y = a->dfac * x + a->efac * y + a->yoff; + } + } +} + +/** +* WARNING, make sure you send in only 16-member double arrays +* or obviously things will go pear-shaped fast. +*/ +#if 0 +static int gluInvertMatrix(const double *m, double *invOut) +{ + double inv[16], det; + int i; + + inv[0] = m[5] * m[10] * m[15] - + m[5] * m[11] * m[14] - + m[9] * m[6] * m[15] + + m[9] * m[7] * m[14] + + m[13] * m[6] * m[11] - + m[13] * m[7] * m[10]; + + inv[4] = -m[4] * m[10] * m[15] + + m[4] * m[11] * m[14] + + m[8] * m[6] * m[15] - + m[8] * m[7] * m[14] - + m[12] * m[6] * m[11] + + m[12] * m[7] * m[10]; + + inv[8] = m[4] * m[9] * m[15] - + m[4] * m[11] * m[13] - + m[8] * m[5] * m[15] + + m[8] * m[7] * m[13] + + m[12] * m[5] * m[11] - + m[12] * m[7] * m[9]; + + inv[12] = -m[4] * m[9] * m[14] + + m[4] * m[10] * m[13] + + m[8] * m[5] * m[14] - + m[8] * m[6] * m[13] - + m[12] * m[5] * m[10] + + m[12] * m[6] * m[9]; + + inv[1] = -m[1] * m[10] * m[15] + + m[1] * m[11] * m[14] + + m[9] * m[2] * m[15] - + m[9] * m[3] * m[14] - + m[13] * m[2] * m[11] + + m[13] * m[3] * m[10]; + + inv[5] = m[0] * m[10] * m[15] - + m[0] * m[11] * m[14] - + m[8] * m[2] * m[15] + + m[8] * m[3] * m[14] + + m[12] * m[2] * m[11] - + m[12] * m[3] * m[10]; + + inv[9] = -m[0] * m[9] * m[15] + + m[0] * m[11] * m[13] + + m[8] * m[1] * m[15] - + m[8] * m[3] * m[13] - + m[12] * m[1] * m[11] + + m[12] * m[3] * m[9]; + + inv[13] = m[0] * m[9] * m[14] - + m[0] * m[10] * m[13] - + m[8] * m[1] * m[14] + + m[8] * m[2] * m[13] + + m[12] * m[1] * m[10] - + m[12] * m[2] * m[9]; + + inv[2] = m[1] * m[6] * m[15] - + m[1] * m[7] * m[14] - + m[5] * m[2] * m[15] + + m[5] * m[3] * m[14] + + m[13] * m[2] * m[7] - + m[13] * m[3] * m[6]; + + inv[6] = -m[0] * m[6] * m[15] + + m[0] * m[7] * m[14] + + m[4] * m[2] * m[15] - + m[4] * m[3] * m[14] - + m[12] * m[2] * m[7] + + m[12] * m[3] * m[6]; + + inv[10] = m[0] * m[5] * m[15] - + m[0] * m[7] * m[13] - + m[4] * m[1] * m[15] + + m[4] * m[3] * m[13] + + m[12] * m[1] * m[7] - + m[12] * m[3] * m[5]; + + inv[14] = -m[0] * m[5] * m[14] + + m[0] * m[6] * m[13] + + m[4] * m[1] * m[14] - + m[4] * m[2] * m[13] - + m[12] * m[1] * m[6] + + m[12] * m[2] * m[5]; + + inv[3] = -m[1] * m[6] * m[11] + + m[1] * m[7] * m[10] + + m[5] * m[2] * m[11] - + m[5] * m[3] * m[10] - + m[9] * m[2] * m[7] + + m[9] * m[3] * m[6]; + + inv[7] = m[0] * m[6] * m[11] - + m[0] * m[7] * m[10] - + m[4] * m[2] * m[11] + + m[4] * m[3] * m[10] + + m[8] * m[2] * m[7] - + m[8] * m[3] * m[6]; + + inv[11] = -m[0] * m[5] * m[11] + + m[0] * m[7] * m[9] + + m[4] * m[1] * m[11] - + m[4] * m[3] * m[9] - + m[8] * m[1] * m[7] + + m[8] * m[3] * m[5]; + + inv[15] = m[0] * m[5] * m[10] - + m[0] * m[6] * m[9] - + m[4] * m[1] * m[10] + + m[4] * m[2] * m[9] + + m[8] * m[1] * m[6] - + m[8] * m[2] * m[5]; + + det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12]; + + if (det == 0) + return LW_FALSE; + + det = 1.0 / det; + + for (i = 0; i < 16; i++) + invOut[i] = inv[i] * det; + + return LW_TRUE; +} +#endif + +/** + * Scale a pointarray. + */ +void +ptarray_scale(POINTARRAY *pa, const POINT4D *fact) +{ + uint32_t i; + POINT4D p4d; + LWDEBUG(3, "ptarray_scale start"); + for (i=0; inpoints; i++) + { + getPoint4d_p(pa, i, &p4d); + p4d.x *= fact->x; + p4d.y *= fact->y; + p4d.z *= fact->z; + p4d.m *= fact->m; + ptarray_set_point4d(pa, i, &p4d); + } + LWDEBUG(3, "ptarray_scale end"); +} + +int +ptarray_startpoint(const POINTARRAY *pa, POINT4D *pt) +{ + return getPoint4d_p(pa, 0, pt); +} + + +/* + * Stick an array of points to the given gridspec. + * Return "gridded" points in *outpts and their number in *outptsn. + * + * Two consecutive points falling on the same grid cell are collapsed + * into one single point. + * + */ +void +ptarray_grid_in_place(POINTARRAY *pa, const gridspec *grid) +{ + uint32_t j = 0; + POINT4D *p, *p_out = NULL; + double x, y, z = 0, m = 0; + uint32_t ndims = FLAGS_NDIMS(pa->flags); + uint32_t has_z = FLAGS_GET_Z(pa->flags); + uint32_t has_m = FLAGS_GET_M(pa->flags); + + for (uint32_t i = 0; i < pa->npoints; i++) + { + /* Look straight into the abyss */ + p = (POINT4D *)(getPoint_internal(pa, i)); + x = p->x; + y = p->y; + if (ndims > 2) + z = p->z; + if (ndims > 3) + m = p->m; + + if (grid->xsize > 0) + x = rint((x - grid->ipx) / grid->xsize) * grid->xsize + grid->ipx; + + if (grid->ysize > 0) + y = rint((y - grid->ipy) / grid->ysize) * grid->ysize + grid->ipy; + + /* Read and round this point */ + /* Z is always in third position */ + if (has_z && grid->zsize > 0) + z = rint((z - grid->ipz) / grid->zsize) * grid->zsize + grid->ipz; + + /* M might be in 3rd or 4th position */ + if (has_m && grid->msize > 0) + { + /* In POINT ZM, M is in 4th position, in POINT M, M is in 3rd position which is Z in POINT4D */ + if (has_z) + m = rint((m - grid->ipm) / grid->msize) * grid->msize + grid->ipm; + else + z = rint((z - grid->ipm) / grid->msize) * grid->msize + grid->ipm; + } + + /* Skip duplicates */ + if (p_out && + p_out->x == x && + p_out->y == y && + (ndims > 2 ? p_out->z == z : 1) && + (ndims > 3 ? p_out->m == m : 1)) + { + continue; + } + + /* Write rounded values into the next available point */ + p_out = (POINT4D *)(getPoint_internal(pa, j++)); + p_out->x = x; + p_out->y = y; + if (ndims > 2) + p_out->z = z; + if (ndims > 3) + p_out->m = m; + } + + /* Update output ptarray length */ + pa->npoints = j; + return; +} + +int +ptarray_npoints_in_rect(const POINTARRAY *pa, const GBOX *gbox) +{ + const POINT2D *pt; + int n = 0; + uint32_t i; + for ( i = 0; i < pa->npoints; i++ ) + { + pt = getPoint2d_cp(pa, i); + if ( gbox_contains_point2d(gbox, pt) ) + n++; + } + return n; +} + + +/* + * Reorder the vertices of a closed pointarray so that the + * given point is the first/last one. + * + * Error out if pointarray is not closed or it does not + * contain the given point. + */ +int +ptarray_scroll_in_place(POINTARRAY *pa, const POINT4D *pt) +{ + POINTARRAY *tmp; + int found; + uint32_t it; + int ptsize; + + if ( ! ptarray_is_closed_2d(pa) ) + { + lwerror("ptarray_scroll_in_place: input POINTARRAY is not closed"); + return LW_FAILURE; + } + + ptsize = ptarray_point_size(pa); + + /* Find the point in the array */ + found = 0; + for ( it = 0; it < pa->npoints; ++it ) + { + if ( ! memcmp(getPoint_internal(pa, it), pt, ptsize) ) + { + found = 1; + break; + } + } + + if ( ! found ) + { + lwerror("ptarray_scroll_in_place: input POINTARRAY does not contain the given point"); + return LW_FAILURE; + } + + if ( 0 == it ) + { + /* Point is already the start/end point, just clone the input */ + return LW_SUCCESS; + } + + /* TODO: reduce allocations */ + tmp = ptarray_construct(FLAGS_GET_Z(pa->flags), FLAGS_GET_M(pa->flags), pa->npoints); + + bzero(getPoint_internal(tmp, 0), ptsize * pa->npoints); + /* Copy the block from found point to last point into the output array */ + memcpy( + getPoint_internal(tmp, 0), + getPoint_internal(pa, it), + ptsize * ( pa->npoints - it ) + ); + + /* Copy the block from second point to the found point into the last portion of the + * return */ + memcpy( + getPoint_internal(tmp, pa->npoints - it), + getPoint_internal(pa, 1), + ptsize * ( it ) + ); + + /* Copy the resulting pointarray back to source one */ + memcpy( + getPoint_internal(pa, 0), + getPoint_internal(tmp, 0), + ptsize * ( pa->npoints ) + ); + + ptarray_free(tmp); + + return LW_SUCCESS; +} diff --git a/mgist-postgis/liblwgeom/stringbuffer.c b/mgist-postgis/liblwgeom/stringbuffer.c new file mode 100644 index 0000000..0c71019 --- /dev/null +++ b/mgist-postgis/liblwgeom/stringbuffer.c @@ -0,0 +1,354 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2002 Thamer Alharbash + * Copyright 2009 Paul Ramsey + * + **********************************************************************/ + +#include "liblwgeom_internal.h" +#include "stringbuffer.h" + +/** +* Allocate a new stringbuffer_t. Use stringbuffer_destroy to free. +*/ +stringbuffer_t* +stringbuffer_create(void) +{ + return stringbuffer_create_with_size(STRINGBUFFER_STARTSIZE); +} + +static void +stringbuffer_init_with_size(stringbuffer_t *s, size_t size) +{ + s->str_start = lwalloc(size); + s->str_end = s->str_start; + s->capacity = size; + memset(s->str_start, 0, size); +} + +void +stringbuffer_release(stringbuffer_t *s) +{ + if ( s->str_start ) lwfree(s->str_start); +} + +void +stringbuffer_init(stringbuffer_t *s) +{ + stringbuffer_init_with_size(s, STRINGBUFFER_STARTSIZE); +} + +void +stringbuffer_init_varlena(stringbuffer_t *s) +{ + stringbuffer_init_with_size(s, STRINGBUFFER_STARTSIZE + LWVARHDRSZ); + /* Zero out LWVARHDRSZ bytes at the front of the buffer */ + stringbuffer_append_len(s, "\0\0\0\0\0\0\0\0", LWVARHDRSZ); +} + + +/** +* Allocate a new stringbuffer_t. Use stringbuffer_destroy to free. +*/ +stringbuffer_t* +stringbuffer_create_with_size(size_t size) +{ + stringbuffer_t *s; + + s = lwalloc(sizeof(stringbuffer_t)); + stringbuffer_init_with_size(s, size); + return s; +} + +/** +* Free the stringbuffer_t and all memory managed within it. +*/ +void +stringbuffer_destroy(stringbuffer_t *s) +{ + stringbuffer_release(s); + if ( s ) lwfree(s); +} + +/** +* Reset the stringbuffer_t. Useful for starting a fresh string +* without the expense of freeing and re-allocating a new +* stringbuffer_t. +*/ +void +stringbuffer_clear(stringbuffer_t *s) +{ + s->str_start[0] = '\0'; + s->str_end = s->str_start; +} + +/** +* Return the last character in the buffer. +*/ +char +stringbuffer_lastchar(stringbuffer_t *s) +{ + if( s->str_end == s->str_start ) + return 0; + + return *(s->str_end - 1); +} + + +/** +* Returns a reference to the internal string being managed by +* the stringbuffer. The current string will be null-terminated +* within the internal string. +*/ +const char* +stringbuffer_getstring(stringbuffer_t *s) +{ + return s->str_start; +} + +/** +* Returns a newly allocated string large enough to contain the +* current state of the string. Caller is responsible for +* freeing the return value. +*/ +char* +stringbuffer_getstringcopy(stringbuffer_t *s) +{ + size_t size = (s->str_end - s->str_start) + 1; + char *str = lwalloc(size); + memcpy(str, s->str_start, size); + str[size - 1] = '\0'; + return str; +} + +lwvarlena_t * +stringbuffer_getvarlena(stringbuffer_t *s) +{ + lwvarlena_t *output = (lwvarlena_t *)(s->str_start); + LWSIZE_SET(output->size, (s->str_end - s->str_start)); + return output; +} + +lwvarlena_t * +stringbuffer_getvarlenacopy(stringbuffer_t *s) +{ + size_t size = (s->str_end - s->str_start); + lwvarlena_t *output = (lwvarlena_t *)lwalloc(size + LWVARHDRSZ); + LWSIZE_SET(output->size, size + LWVARHDRSZ); + + memcpy(output->data, s->str_start, size); + return output; +} + +/** +* Returns the length of the current string, not including the +* null terminator (same behavior as strlen()). +*/ +int +stringbuffer_getlength(stringbuffer_t *s) +{ + return (s->str_end - s->str_start); +} + +/** +* Clear the stringbuffer_t and re-start it with the specified string. +*/ +void +stringbuffer_set(stringbuffer_t *s, const char *str) +{ + stringbuffer_clear(s); + stringbuffer_append(s, str); +} + +/** +* Copy the contents of src into dst. +*/ +void +stringbuffer_copy(stringbuffer_t *dst, stringbuffer_t *src) +{ + stringbuffer_set(dst, stringbuffer_getstring(src)); +} + +/** +* Appends a formatted string to the current string buffer, +* using the format and argument list provided. Returns -1 on error, +* check errno for reasons, documented in the printf man page. +*/ +static int +stringbuffer_avprintf(stringbuffer_t *s, const char *fmt, va_list ap) +{ + int maxlen = (s->capacity - (s->str_end - s->str_start)); + int len = 0; /* Length of the output */ + va_list ap2; + + /* Make a copy of the variadic arguments, in case we need to print twice */ + /* Print to our buffer */ + va_copy(ap2, ap); + len = vsnprintf(s->str_end, maxlen, fmt, ap2); + va_end(ap2); + + /* Propogate errors up */ + if ( len < 0 ) + #if defined(__MINGW64_VERSION_MAJOR) + va_copy(ap2, ap); + len = _vscprintf(fmt, ap2);/**Assume windows flaky vsnprintf that returns -1 if initial buffer to small and add more space **/ + va_end(ap2); + #else + return len; + #endif + + /* We didn't have enough space! */ + /* Either Unix vsnprint returned write length larger than our buffer */ + /* or Windows vsnprintf returned an error code. */ + if ( len >= maxlen ) + { + stringbuffer_makeroom(s, len + 1); + maxlen = (s->capacity - (s->str_end - s->str_start)); + + /* Try to print a second time */ + len = vsnprintf(s->str_end, maxlen, fmt, ap); + + /* Printing error? Error! */ + if ( len < 0 ) return len; + /* Too long still? Error! */ + if ( len >= maxlen ) return -1; + } + + /* Move end pointer forward and return. */ + s->str_end += len; + return len; +} + +/** +* Appends a formatted string to the current string buffer, +* using the format and argument list provided. +* Returns -1 on error, check errno for reasons, +* as documented in the printf man page. +*/ +int +stringbuffer_aprintf(stringbuffer_t *s, const char *fmt, ...) +{ + int r; + va_list ap; + va_start(ap, fmt); + r = stringbuffer_avprintf(s, fmt, ap); + va_end(ap); + return r; +} + +/** +* Trims whitespace off the end of the stringbuffer. Returns +* the number of characters trimmed. +*/ +int +stringbuffer_trim_trailing_white(stringbuffer_t *s) +{ + char *ptr = s->str_end; + int dist = 0; + + /* Roll backwards until we hit a non-space. */ + while( ptr > s->str_start ) + { + ptr--; + if( (*ptr == ' ') || (*ptr == '\t') ) + { + continue; + } + else + { + ptr++; + dist = s->str_end - ptr; + *ptr = '\0'; + s->str_end = ptr; + return dist; + } + } + return dist; +} + +/** +* Trims zeroes off the end of the last number in the stringbuffer. +* The number has to be the very last thing in the buffer. Only the +* last number will be trimmed. Returns the number of characters +* trimmed. +* +* eg: 1.22000 -> 1.22 +* 1.0 -> 1 +* 0.0 -> 0 +*/ +int +stringbuffer_trim_trailing_zeroes(stringbuffer_t *s) +{ + char *ptr = s->str_end; + char *decimal_ptr = NULL; + int dist; + + if ( s->str_end - s->str_start < 2) + return 0; + + /* Roll backwards to find the decimal for this number */ + while( ptr > s->str_start ) + { + ptr--; + if ( *ptr == '.' ) + { + decimal_ptr = ptr; + break; + } + if ( (*ptr >= '0') && (*ptr <= '9' ) ) + continue; + else + break; + } + + /* No decimal? Nothing to trim! */ + if ( ! decimal_ptr ) + return 0; + + ptr = s->str_end; + + /* Roll backwards again, with the decimal as stop point, trimming contiguous zeroes */ + while( ptr >= decimal_ptr ) + { + ptr--; + if ( *ptr == '0' ) + continue; + else + break; + } + + /* Huh, we get anywhere. Must not have trimmed anything. */ + if ( ptr == s->str_end ) + return 0; + + /* If we stopped at the decimal, we want to null that out. + It we stopped on a numeral, we want to preserve that, so push the + pointer forward one space. */ + if ( *ptr != '.' ) + ptr++; + + /* Add null terminator re-set the end of the stringbuffer. */ + *ptr = '\0'; + dist = s->str_end - ptr; + s->str_end = ptr; + return dist; +} + diff --git a/mgist-postgis/liblwgeom/stringbuffer.h b/mgist-postgis/liblwgeom/stringbuffer.h new file mode 100644 index 0000000..aa95ef4 --- /dev/null +++ b/mgist-postgis/liblwgeom/stringbuffer.h @@ -0,0 +1,127 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2002 Thamer Alharbash + * Copyright 2009 Paul Ramsey + * + **********************************************************************/ + + +#ifndef _STRINGBUFFER_H +#define _STRINGBUFFER_H 1 + +#include "liblwgeom_internal.h" + +#include +#include +#include +#include + +#define STRINGBUFFER_STARTSIZE 128 + +typedef struct +{ + size_t capacity; + char *str_end; + char *str_start; +} +stringbuffer_t; + +extern stringbuffer_t *stringbuffer_create_with_size(size_t size); +extern stringbuffer_t *stringbuffer_create(void); +extern void stringbuffer_init(stringbuffer_t *s); +extern void stringbuffer_init_varlena(stringbuffer_t *s); +extern void stringbuffer_release(stringbuffer_t *s); +extern void stringbuffer_destroy(stringbuffer_t *sb); +extern void stringbuffer_clear(stringbuffer_t *sb); +void stringbuffer_set(stringbuffer_t *sb, const char *s); +void stringbuffer_copy(stringbuffer_t *sb, stringbuffer_t *src); +extern int stringbuffer_aprintf(stringbuffer_t *sb, const char *fmt, ...); +extern const char *stringbuffer_getstring(stringbuffer_t *sb); +extern char *stringbuffer_getstringcopy(stringbuffer_t *sb); +extern lwvarlena_t *stringbuffer_getvarlenacopy(stringbuffer_t *s); +extern lwvarlena_t * stringbuffer_getvarlena(stringbuffer_t *s); +extern int stringbuffer_getlength(stringbuffer_t *sb); +extern char stringbuffer_lastchar(stringbuffer_t *s); +extern int stringbuffer_trim_trailing_white(stringbuffer_t *s); +extern int stringbuffer_trim_trailing_zeroes(stringbuffer_t *s); + +/** + * If necessary, expand the stringbuffer_t internal buffer to accommodate the + * specified additional size. + */ +static inline void +stringbuffer_makeroom(stringbuffer_t *s, size_t size_to_add) +{ + size_t current_size = (s->str_end - s->str_start); + size_t capacity = s->capacity; + size_t required_size = current_size + size_to_add; + + while (capacity < required_size) + capacity *= 2; + + if (capacity > s->capacity) + { + s->str_start = lwrealloc(s->str_start, capacity); + s->capacity = capacity; + s->str_end = s->str_start + current_size; + } +} + + +/** + * Append the specified string to the stringbuffer_t using known length + */ +inline static void +stringbuffer_append_len(stringbuffer_t *s, const char *a, size_t alen) +{ + int alen0 = alen + 1; /* Length including null terminator */ + stringbuffer_makeroom(s, alen0); + memcpy(s->str_end, a, alen0); + s->str_end += alen; +} + +/** + * Append the specified string to the stringbuffer_t. + */ +inline static void +stringbuffer_append(stringbuffer_t *s, const char *a) +{ + int alen = strlen(a); /* Length of string to append */ + stringbuffer_append_len(s, a, alen); +} + +inline static void +stringbuffer_append_double(stringbuffer_t *s, double d, int precision) +{ + stringbuffer_makeroom(s, OUT_MAX_BYTES_DOUBLE); + s->str_end += lwprint_double(d, precision, s->str_end); +} + +inline static void +stringbuffer_append_char(stringbuffer_t *s, char c) +{ + stringbuffer_makeroom(s, 1); + *(s->str_end) = c; + s->str_end++; +} + + +#endif /* _STRINGBUFFER_H */ diff --git a/mgist-postgis/liblwgeom/stringlist.c b/mgist-postgis/liblwgeom/stringlist.c new file mode 100644 index 0000000..37e7692 --- /dev/null +++ b/mgist-postgis/liblwgeom/stringlist.c @@ -0,0 +1,148 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2021 Paul Ramsey + * + **********************************************************************/ + +#include "liblwgeom_internal.h" +#include "stringlist.h" + +static size_t +stringlist_capacity_in_bytes(size_t capacity) +{ + return capacity * sizeof(char*); +} + +static void +stringlist_init_with_size(stringlist_t *s, size_t size) +{ + s->capacity = size; + s->length = 0; + s->data = lwalloc(stringlist_capacity_in_bytes(s->capacity)); + memset(s->data, 0, stringlist_capacity_in_bytes(s->capacity)); +} + +void +stringlist_init(stringlist_t *s) +{ + stringlist_init_with_size(s, STRINGLIST_STARTSIZE); +} + +void +stringlist_release(stringlist_t *s) +{ + size_t i; + if (!s || !s->data) return; + for (i = 0; i < s->length; i++) + if (s->data[i]) lwfree(s->data[i]); + lwfree(s->data); + memset(s, 0, sizeof(stringlist_t)); +} + + +stringlist_t * +stringlist_create_with_size(size_t size) +{ + stringlist_t *s = lwalloc(sizeof(stringlist_t)); + memset(s, 0, sizeof(stringlist_t)); + stringlist_init(s); + return s; +} + +stringlist_t * +stringlist_create(void) +{ + return stringlist_create_with_size(STRINGLIST_STARTSIZE); +} + +void +stringlist_destroy(stringlist_t *s) +{ + stringlist_release(s); + lwfree(s); +} + +static int +stringlist_cmp(const void *a, const void *b) +{ + const char **ia = (const char **)a; + const char **ib = (const char **)b; + return strcmp(*ia, *ib); +} + +static void +stringlist_add_string_internal(stringlist_t *s, const char* string, int dosort) +{ + if (!string) return; + if (s->capacity == 0) + { + stringlist_init(s); + } + if (s->length == s->capacity) + { + s->capacity *= 2; + s->data = lwrealloc(s->data, stringlist_capacity_in_bytes(s->capacity)); + }; + s->data[s->length++] = lwstrdup(string); + if (dosort) + stringlist_sort(s); + return; +} + +void +stringlist_add_string(stringlist_t *s, const char* string) +{ + stringlist_add_string_internal(s, string, 1); +} + +void +stringlist_add_string_nosort(stringlist_t *s, const char* string) +{ + stringlist_add_string_internal(s, string, 0); +} + +void +stringlist_sort(stringlist_t *s) +{ + qsort(s->data, s->length, sizeof(char*), stringlist_cmp); +} + +const char * +stringlist_find(stringlist_t *s, const char *key) +{ + char ** rslt = bsearch(&key, s->data, s->length, sizeof(char*), stringlist_cmp); + if (! rslt) return NULL; + return *rslt; +} + +size_t +stringlist_length(stringlist_t *s) +{ + return s->length; +} + +const char * +stringlist_get(stringlist_t *s, size_t i) +{ + if (i < s->length) + return s->data[i]; + return NULL; +} diff --git a/mgist-postgis/liblwgeom/stringlist.h b/mgist-postgis/liblwgeom/stringlist.h new file mode 100644 index 0000000..1649475 --- /dev/null +++ b/mgist-postgis/liblwgeom/stringlist.h @@ -0,0 +1,64 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2021 Paul Ramsey + * + **********************************************************************/ + + +#ifndef _STRINGLIST_H +#define _STRINGLIST_H 1 + +#include "liblwgeom_internal.h" + + +#include +#include +#include +#include + +#define STRINGLIST_STARTSIZE 8 + +typedef struct +{ + size_t capacity; + size_t length; + char **data; +} +stringlist_t; + + +extern stringlist_t * stringlist_create_with_size(size_t size); +extern stringlist_t * stringlist_create(void); +extern void stringlist_destroy(stringlist_t *s); + +extern void stringlist_init(stringlist_t *s); +extern void stringlist_release(stringlist_t *s); + +extern void stringlist_add_string(stringlist_t *s, const char* string); +extern void stringlist_add_string_nosort(stringlist_t *s, const char* string); +extern void stringlist_sort(stringlist_t *s); +extern const char * stringlist_find(stringlist_t *s, const char *key); +extern size_t stringlist_length(stringlist_t *s); +extern const char * stringlist_get(stringlist_t *s, size_t i); + + +#endif + diff --git a/mgist-postgis/liblwgeom/varint.c b/mgist-postgis/liblwgeom/varint.c new file mode 100644 index 0000000..fca0053 --- /dev/null +++ b/mgist-postgis/liblwgeom/varint.c @@ -0,0 +1,211 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2014 Sandro Santilli + * Copyright (C) 2013 Nicklas Avén + * + **********************************************************************/ + + +#include "varint.h" +#include "lwgeom_log.h" +#include "liblwgeom.h" + +/* -------------------------------------------------------------------------------- */ + +static size_t +_varint_u64_encode_buf(uint64_t val, uint8_t *buf) +{ + uint8_t grp; + uint64_t q = val; + uint8_t *ptr = buf; + while (1) + { + /* We put the 7 least significant bits in grp */ + grp = 0x7f & q; + /* We rightshift our input value 7 bits */ + /* which means that the 7 next least significant bits */ + /* becomes the 7 least significant */ + q = q >> 7; + /* Check if, after our rightshifting, we still have */ + /* anything to read in our input value. */ + if ( q > 0 ) + { + /* In the next line quite a lot is happening. */ + /* Since there is more to read in our input value */ + /* we signal that by setting the most significant bit */ + /* in our byte to 1. */ + /* Then we put that byte in our buffer and move the pointer */ + /* forward one step */ + *ptr = 0x80 | grp; + ptr++; + } + else + { + /* The same as above, but since there is nothing more */ + /* to read in our input value we leave the most significant bit unset */ + *ptr = grp; + ptr++; + return ptr - buf; + } + } + /* This cannot happen */ + lwerror("%s: Got out of infinite loop. Consciousness achieved.", __func__); + return (size_t)0; +} + + +size_t +varint_u64_encode_buf(uint64_t val, uint8_t *buf) +{ + return _varint_u64_encode_buf(val, buf); +} + + +size_t +varint_u32_encode_buf(uint32_t val, uint8_t *buf) +{ + return _varint_u64_encode_buf((uint64_t)val, buf); +} + +size_t +varint_s64_encode_buf(int64_t val, uint8_t *buf) +{ + return _varint_u64_encode_buf(zigzag64(val), buf); +} + +size_t +varint_s32_encode_buf(int32_t val, uint8_t *buf) +{ + return _varint_u64_encode_buf((uint64_t)zigzag32(val), buf); +} + +/* Read from signed 64bit varint */ +int64_t +varint_s64_decode(const uint8_t *the_start, const uint8_t *the_end, size_t *size) +{ + return unzigzag64(varint_u64_decode(the_start, the_end, size)); +} + +/* Read from unsigned 64bit varint */ +uint64_t +varint_u64_decode(const uint8_t *the_start, const uint8_t *the_end, size_t *size) +{ + uint64_t nVal = 0; + int nShift = 0; + uint8_t nByte; + const uint8_t *ptr = the_start; + + /* Check so we don't read beyond the twkb */ + while( ptr < the_end ) + { + nByte = *ptr; + /* Hibit is set, so this isn't the last byte */ + if (nByte & 0x80) + { + /* We get here when there is more to read in the input varInt */ + /* Here we take the least significant 7 bits of the read */ + /* byte and put it in the most significant place in the result variable. */ + nVal |= ((uint64_t)(nByte & 0x7f)) << nShift; + /* move the "cursor" of the input buffer step (8 bits) */ + ptr++; + /* move the cursor in the resulting variable (7 bits) */ + nShift += 7; + } + else + { + /* move the "cursor" one step */ + ptr++; + /* Move the last read byte to the most significant */ + /* place in the result and return the whole result */ + *size = ptr - the_start; + return nVal | ((uint64_t)nByte << nShift); + } + } + lwerror("%s: varint extends past end of buffer", __func__); + *size = 0; + return 0; +} + +size_t +varint_size(const uint8_t *the_start, const uint8_t *the_end) +{ + const uint8_t *ptr = the_start; + + /* Check so we don't read beyond the twkb */ + while( ptr < the_end ) + { + /* Hibit is set, this isn't the last byte */ + if (*ptr & 0x80) + { + ptr++; + } + else + { + ptr++; + return ptr - the_start; + } + } + return 0; +} + +uint64_t zigzag64(int64_t val) +{ + return val >= 0 ? + ((uint64_t)val) << 1 : + ((((uint64_t)(-1 - val)) << 1) | 0x01); +} + +uint32_t zigzag32(int32_t val) +{ + return val >= 0 ? + ((uint32_t)val) << 1 : + ((((uint32_t)(-1 - val)) << 1) | 0x01); +} + +uint8_t zigzag8(int8_t val) +{ + return val >= 0 ? + ((uint8_t)val) << 1 : + ((((uint8_t)(-1 - val)) << 1) | 0x01); +} + +int64_t unzigzag64(uint64_t val) +{ + return !(val & 0x01) ? + ((int64_t)(val >> 1)) : + (-1 * (int64_t)((val+1) >> 1)); +} + +int32_t unzigzag32(uint32_t val) +{ + return !(val & 0x01) ? + ((int32_t)(val >> 1)) : + (-1 * (int32_t)((val+1) >> 1)); +} + +int8_t unzigzag8(uint8_t val) +{ + return !(val & 0x01) ? + ((int8_t)(val >> 1)) : + (-1 * (int8_t)((val+1) >> 1)); +} + + diff --git a/mgist-postgis/liblwgeom/varint.h b/mgist-postgis/liblwgeom/varint.h new file mode 100644 index 0000000..62eb992 --- /dev/null +++ b/mgist-postgis/liblwgeom/varint.h @@ -0,0 +1,55 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright (C) 2014 Sandro Santilli + * Copyright (C) 2013 Nicklas Avén + * + **********************************************************************/ + + +#ifndef _LIBLWGEOM_VARINT_H +#define _LIBLWGEOM_VARINT_H 1 + +#include +#include + + +/* NEW SIGNATURES */ + +size_t varint_u32_encode_buf(uint32_t val, uint8_t *buf); +size_t varint_s32_encode_buf(int32_t val, uint8_t *buf); +size_t varint_u64_encode_buf(uint64_t val, uint8_t *buf); +size_t varint_s64_encode_buf(int64_t val, uint8_t *buf); +int64_t varint_s64_decode(const uint8_t *the_start, const uint8_t *the_end, size_t *size); +uint64_t varint_u64_decode(const uint8_t *the_start, const uint8_t *the_end, size_t *size); + +size_t varint_size(const uint8_t *the_start, const uint8_t *the_end); + +/* Support from -INT{8,32,64}_MAX to INT{8,32,64}_MAX), + * e.g INT8_MIN is not supported in zigzag8 */ +uint64_t zigzag64(int64_t val); +uint32_t zigzag32(int32_t val); +uint8_t zigzag8(int8_t val); +int64_t unzigzag64(uint64_t val); +int32_t unzigzag32(uint32_t val); +int8_t unzigzag8(uint8_t val); + +#endif /* !defined _LIBLWGEOM_VARINT_H */ + diff --git a/mgist-postgis/libpgcommon/Makefile.in b/mgist-postgis/libpgcommon/Makefile.in new file mode 100644 index 0000000..b99b02c --- /dev/null +++ b/mgist-postgis/libpgcommon/Makefile.in @@ -0,0 +1,72 @@ +# ********************************************************************** +# * +# * PostGIS - Spatial Types for PostgreSQL +# * http://postgis.net +# * Copyright 2008 Mark Cave-Ayland +# * +# * This is free software; you can redistribute and/or modify it under +# * the terms of the GNU General Public Licence. See the COPYING file. +# * +# ********************************************************************** + +srcdir = @srcdir@ +top_builddir = @top_builddir@ + +CC=@CC@ +CFLAGS= -I$(srcdir)/../liblwgeom -I$(top_builddir)/liblwgeom @CPPFLAGS@ @CFLAGS@ @PGSQL_BE_CPPFLAGS@ @PROJ_CPPFLAGS@ @PICFLAGS@ @GETTEXT_CFLAGS@ +LDFLAGS=@GETTEXT_LDFLAGS@ @LIBINTL@ + +YACC=@YACC@ +LEX=@LEX@ + +VPATH = $(srcdir) + +# Standalone COMMON objects +SA_OBJS = \ + gserialized_gist.o \ + lwgeom_transform.o \ + lwgeom_cache.o \ + lwgeom_pg.o \ + shared_gserialized.o + + +SA_HEADERS = \ + lwgeom_pg.h \ + lwgeom_transform.h \ + lwgeom_cache.h \ + gserialized_gist.h \ + pgsql_compat.h + +all: libpgcommon.a + +# nothing to install or uninstall +install uninstall: + +libpgcommon.a: $(SA_OBJS) $(SA_HEADERS) + @AR@ @AR_FLAGS@ libpgcommon.a $(SA_OBJS) + +maintainer-clean: clean + +clean: + $(MAKE) -C cunit clean + rm -f $(SA_OBJS) + rm -f $(NM_OBJS) + rm -f libpgcommon.a + +distclean: clean + $(MAKE) -C cunit distclean + rm -f Makefile + +check: check-unit + +check-regress: + +check-unit: libpgcommon.a + $(MAKE) -C cunit check + +# Command to build each of the .o files +$(SA_OBJS): %.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +$(SA_OBJS): ../postgis_config.h + diff --git a/mgist-postgis/libpgcommon/common.h b/mgist-postgis/libpgcommon/common.h new file mode 100644 index 0000000..a84b70c --- /dev/null +++ b/mgist-postgis/libpgcommon/common.h @@ -0,0 +1,19 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2011 OSGeo + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#ifndef PG_LIBCOMMON_H +#define PG_LIBCOMMON_H + +#include "pgsql_compat.h" +#include "lwgeom_pg.h" +#include "lwgeom_transform.h" + +#endif /* PG_LIBCOMMON_H */ diff --git a/mgist-postgis/libpgcommon/cunit/Makefile.in b/mgist-postgis/libpgcommon/cunit/Makefile.in new file mode 100644 index 0000000..b4a660b --- /dev/null +++ b/mgist-postgis/libpgcommon/cunit/Makefile.in @@ -0,0 +1,15 @@ +# ********************************************************************** +# * +# * PostGIS - Spatial Types for PostgreSQL +# * http://postgis.net +# * Copyright 2008 Mark Cave-Ayland +# * +# * This is free software; you can redistribute and/or modify it under +# * the terms of the GNU General Public Licence. See the COPYING file. +# * +# ********************************************************************** + +all check clean : + +distclean: clean + rm -f Makefile diff --git a/mgist-postgis/libpgcommon/gserialized_gist.c b/mgist-postgis/libpgcommon/gserialized_gist.c new file mode 100644 index 0000000..093ad50 --- /dev/null +++ b/mgist-postgis/libpgcommon/gserialized_gist.c @@ -0,0 +1,215 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * Copyright 2009 Paul Ramsey + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + + +#include "postgres.h" +#include "access/gist.h" /* For GiST */ +#include "access/itup.h" +#include "access/skey.h" + +#include "../postgis_config.h" + +/*#define POSTGIS_DEBUG_LEVEL 4*/ + +#include "liblwgeom.h" /* For standard geometry types. */ +#include "liblwgeom_internal.h" +#include "lwgeom_pg.h" /* For debugging macros. */ +#include "gserialized_gist.h" + +#define FLAGS_NDIMS_GIDX(f) ( FLAGS_GET_GEODETIC(f) ? 3 : \ + FLAGS_GET_M(f) ? 4 : \ + FLAGS_GET_Z(f) ? 3 : 2 ) + + +/* Generate human readable form for GIDX. */ +char* gidx_to_string(GIDX *a) +{ + static const int precision = 12; + char tmp[8 + 8 * (OUT_MAX_BYTES_DOUBLE + 1)] = {'G', 'I', 'D', 'X', '(', 0}; + int len = 5; + int ndims; + + if (a == NULL) + return pstrdup(""); + + ndims = GIDX_NDIMS(a); + + for (int i = 0; i < ndims; i++) + { + tmp[len++] = ' '; + len += lwprint_double(GIDX_GET_MIN(a, i), precision, &tmp[len]); + } + tmp[len++] = ','; + + for (int i = 0; i < ndims; i++) + { + tmp[len++] = ' '; + len += lwprint_double(GIDX_GET_MAX(a, i), precision, &tmp[len]); + } + + tmp[len++] = ')'; + + return pstrdup(tmp); +} + +/* Allocates a new GIDX on the heap of the requested dimensionality */ +GIDX* gidx_new(int ndims) +{ + size_t size = GIDX_SIZE(ndims); + GIDX *g = (GIDX*)palloc(size); + Assert( (ndims <= GIDX_MAX_DIM) && (size <= GIDX_MAX_SIZE) ); + POSTGIS_DEBUGF(5,"created new gidx of %d dimensions, size %d", ndims, (int)size); + SET_VARSIZE(g, size); + return g; +} + + +/* Convert a double-based GBOX into a float-based GIDX, + ensuring the float box is larger than the double box */ +static int gidx_from_gbox_p(GBOX box, GIDX *a) +{ + int ndims; + + ndims = FLAGS_NDIMS_GIDX(box.flags); + SET_VARSIZE(a, VARHDRSZ + ndims * 2 * sizeof(float)); + + GIDX_SET_MIN(a,0,next_float_down(box.xmin)); + GIDX_SET_MAX(a,0,next_float_up(box.xmax)); + GIDX_SET_MIN(a,1,next_float_down(box.ymin)); + GIDX_SET_MAX(a,1,next_float_up(box.ymax)); + + /* Geodetic indexes are always 3d, geocentric x/y/z */ + if ( FLAGS_GET_GEODETIC(box.flags) ) + { + GIDX_SET_MIN(a,2,next_float_down(box.zmin)); + GIDX_SET_MAX(a,2,next_float_up(box.zmax)); + } + else + { + /* Cartesian with Z implies Z is third dimension */ + if ( FLAGS_GET_Z(box.flags) ) + { + GIDX_SET_MIN(a,2,next_float_down(box.zmin)); + GIDX_SET_MAX(a,2,next_float_up(box.zmax)); + } + /* M is always fourth dimension, we pad if needed */ + if ( FLAGS_GET_M(box.flags) ) + { + if ( ! FLAGS_GET_Z(box.flags) ) + { + GIDX_SET_MIN(a,2,-1*FLT_MAX); + GIDX_SET_MAX(a,2,FLT_MAX); + } + GIDX_SET_MIN(a,3,next_float_down(box.mmin)); + GIDX_SET_MAX(a,3,next_float_up(box.mmax)); + } + } + + POSTGIS_DEBUGF(5, "converted %s to %s", gbox_to_string(&box), gidx_to_string(a)); + + return LW_SUCCESS; +} + +/* Convert a gidx to a gbox */ +void gbox_from_gidx(GIDX *a, GBOX *gbox, int flags) +{ + gbox->xmin = (double)GIDX_GET_MIN(a,0); + gbox->xmax = (double)GIDX_GET_MAX(a,0); + + gbox->ymin = (double)GIDX_GET_MIN(a,1); + gbox->ymax = (double)GIDX_GET_MAX(a,1); + + if ( FLAGS_GET_Z(flags) ) { + gbox->zmin = (double)GIDX_GET_MIN(a,2); + gbox->zmax = (double)GIDX_GET_MAX(a,2); + } + + if ( FLAGS_GET_M(flags) ) { + gbox->mmin = (double)GIDX_GET_MIN(a,3); + gbox->mmax = (double)GIDX_GET_MAX(a,3); + } +} + + +/** +* Peak into a #GSERIALIZED datum to find the bounding box. If the +* box is there, copy it out and return it. If not, calculate the box from the +* full object and return the box based on that. If no box is available, +* return #LW_FAILURE, otherwise #LW_SUCCESS. +*/ +int +gserialized_datum_get_gidx_p(Datum gsdatum, GIDX *gidx) +{ + GSERIALIZED *gpart = NULL; + int need_detoast = PG_GSERIALIZED_DATUM_NEEDS_DETOAST((struct varlena *)gsdatum); + if (need_detoast) + { + gpart = (GSERIALIZED *)PG_DETOAST_DATUM_SLICE(gsdatum, 0, gserialized_max_header_size()); + } + else + { + gpart = (GSERIALIZED *)gsdatum; + } + + /* Do we even have a serialized bounding box? */ + if (gserialized_has_bbox(gpart)) + { + /* Yes! Copy it out into the GIDX! */ + lwflags_t lwflags = gserialized_get_lwflags(gpart); + size_t size = gbox_serialized_size(lwflags); + size_t ndims, dim; + const float *f = gserialized_get_float_box_p(gpart, &ndims); + if (!f) return LW_FAILURE; + for (dim = 0; dim < ndims; dim++) + { + GIDX_SET_MIN(gidx, dim, f[2*dim]); + GIDX_SET_MAX(gidx, dim, f[2*dim+1]); + } + /* if M is present but Z is not, pad Z and shift M */ + if (gserialized_has_m(gpart) && ! gserialized_has_z(gpart)) + { + size += 2 * sizeof(float); + GIDX_SET_MIN(gidx,3,GIDX_GET_MIN(gidx,2)); + GIDX_SET_MAX(gidx,3,GIDX_GET_MAX(gidx,2)); + GIDX_SET_MIN(gidx,2,-1*FLT_MAX); + GIDX_SET_MAX(gidx,2,FLT_MAX); + } + SET_VARSIZE(gidx, VARHDRSZ + size); + } + else + { + /* No, we need to calculate it from the full object. */ + LWGEOM *lwgeom; + GBOX gbox; + if (need_detoast && LWSIZE_GET(gpart->size) >= gserialized_max_header_size()) + { + /* If we haven't, read the whole gserialized object */ + POSTGIS_FREE_IF_COPY_P(gpart, gsdatum); + gpart = (GSERIALIZED *)PG_DETOAST_DATUM(gsdatum); + } + + lwgeom = lwgeom_from_gserialized(gpart); + if (lwgeom_calculate_gbox(lwgeom, &gbox) == LW_FAILURE) + { + POSTGIS_DEBUG(4, "could not calculate bbox, returning failure"); + lwgeom_free(lwgeom); + POSTGIS_FREE_IF_COPY_P(gpart, gsdatum); + return LW_FAILURE; + } + lwgeom_free(lwgeom); + gidx_from_gbox_p(gbox, gidx); + } + POSTGIS_FREE_IF_COPY_P(gpart, gsdatum); + POSTGIS_DEBUGF(4, "got gidx %s", gidx_to_string(gidx)); + return LW_SUCCESS; +} + + + diff --git a/mgist-postgis/libpgcommon/gserialized_gist.h b/mgist-postgis/libpgcommon/gserialized_gist.h new file mode 100644 index 0000000..8edade5 --- /dev/null +++ b/mgist-postgis/libpgcommon/gserialized_gist.h @@ -0,0 +1,122 @@ +#include +/********************************************************************** +** GIDX structure. +** +** This is an n-dimensional (practically, the +** implementation is 2-4 dimensions) box used for index keys. The +** coordinates are anonymous, so we can have any number of dimensions. +** The sizeof a GIDX is 1 + 2 * ndims * sizeof(float). +** The order of values in a GIDX is +** xmin,xmax,ymin,ymax,zmin,zmax... +*/ +typedef struct +{ + int32 varsize; + float c[1]; +} GIDX; + +/* +** For some GiST support functions, it is more efficient to allocate +** memory for our GIDX off the stack and cast that memory into a GIDX. +** But, GIDX is variable length, what to do? We'll bake in the assumption +** that no GIDX is more than 4-dimensional for now, and ensure that much +** space is available. +** 4 bytes varsize + 4 dimensions * 2 ordinates * 4 bytes float size = 36 bytes +*/ +#define GIDX_MAX_SIZE 36 +#define GIDX_MAX_DIM 4 + +/********************************************************************** +** BOX2DF structure. +** +** This is a 2-dimensional key for simple cartesian indexes, +** with backwards compatible behavior to previous indexes in +** PostGIS +*/ + +typedef struct +{ + float xmin, xmax, ymin, ymax; +} BOX2DF; + +/********************************************************************************* +** GIDX support functions. +** +** We put the GIDX support here rather than libgeom because it is a specialized +** type only used for indexing purposes. It also makes use of some PgSQL +** infrastructure like the VARSIZE() macros. +*/ + +/* allocate a new gidx object on the heap */ +GIDX *gidx_new(int ndims); + +/* Note empty GIDX */ +bool gidx_is_unknown(const GIDX *a); + +/* Generate human readable form for GIDX. */ +char *gidx_to_string(GIDX *a); + +/* Returns number of dimensions for this GIDX */ +#define GIDX_NDIMS(gidx) ((VARSIZE((gidx)) - VARHDRSZ) / (2 * sizeof(float))) +/* Minimum accessor. */ +#define GIDX_GET_MIN(gidx, dimension) *( (gidx)->c + 2 * (dimension) ) +/* Maximum accessor. */ +#define GIDX_GET_MAX(gidx, dimension) *( (gidx)->c + 2 * (dimension) + 1 ) +/* Minimum setter. */ +#define GIDX_SET_MIN(gidx, dimension, value) ((gidx)->c[2 * (dimension)] = (value)) +/* Maximum setter. */ +#define GIDX_SET_MAX(gidx, dimension, value) ((gidx)->c[2 * (dimension) + 1] = (value)) +/* Returns the size required to store a GIDX of requested dimension */ +#define GIDX_SIZE(dimensions) (sizeof(int32) + 2 * (dimensions) * sizeof(float)) + +/* Allocate a copy of the box */ +BOX2DF *box2df_copy(BOX2DF *b); + +/* Grow the first argument to contain the second */ +void box2df_merge(BOX2DF *b_union, BOX2DF *b_new); + +/* Allocate a copy of the box */ +GIDX *gidx_copy(GIDX *b); + +/* Grow the first argument to contain the second */ +void gidx_merge(GIDX **b_union, GIDX *b_new); + +/* Note empty BOX2DF */ +bool box2df_is_empty(const BOX2DF *a); + +/* Fill in a gbox from a GIDX */ +void gbox_from_gidx(GIDX *a, GBOX *gbox, int flags); + +/* Fill in a gbox from a BOX2DF */ +int box2df_to_gbox_p(BOX2DF *a, GBOX *box); + +/********************************************************************************* +** GSERIALIZED support functions. +** +** Fast functions for pulling boxes out of serializations. +*/ + +/* Pull out the #GIDX bounding box and flags with a absolute minimum system overhead */ +int gserialized_datum_get_gidx_p(Datum gserialized_datum, GIDX *gidx); +int gserialized_datum_get_box2df_p(Datum gsdatum, BOX2DF *box2df); + +bool box2df_contains(const BOX2DF *a, const BOX2DF *b); +void box2df_set_empty(BOX2DF *a); +void box2df_set_finite(BOX2DF *a); +void box2df_validate(BOX2DF *b); +bool box2df_overlaps(const BOX2DF *a, const BOX2DF *b); +bool box2df_overleft(const BOX2DF *a, const BOX2DF *b); +bool box2df_equals(const BOX2DF *a, const BOX2DF *b); +bool box2df_left(const BOX2DF *a, const BOX2DF *b); +bool box2df_right(const BOX2DF *a, const BOX2DF *b); +bool box2df_overright(const BOX2DF *a, const BOX2DF *b); +bool box2df_overbelow(const BOX2DF *a, const BOX2DF *b); +bool box2df_below(const BOX2DF *a, const BOX2DF *b); +bool box2df_above(const BOX2DF *a, const BOX2DF *b); +bool box2df_overabove(const BOX2DF *a, const BOX2DF *b); + +void gidx_validate(GIDX *b); +void gidx_set_unknown(GIDX *a); +bool gidx_overlaps(GIDX *a, GIDX *b); +bool gidx_equals(GIDX *a, GIDX *b); +bool gidx_contains(GIDX *a, GIDX *b); diff --git a/mgist-postgis/libpgcommon/lwgeom_cache.c b/mgist-postgis/libpgcommon/lwgeom_cache.c new file mode 100644 index 0000000..7e01389 --- /dev/null +++ b/mgist-postgis/libpgcommon/lwgeom_cache.c @@ -0,0 +1,470 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright (C) 2012 Sandro Santilli + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include "postgres.h" + +#include "catalog/pg_type.h" /* for CSTRINGOID */ +#include "executor/spi.h" +#include "fmgr.h" +#include "utils/memutils.h" + +#include "../postgis_config.h" + +/* Include for VARATT_EXTERNAL_GET_POINTER */ +#if POSTGIS_PGSQL_VERSION < 130 +#include "access/tuptoaster.h" +#else +#include "access/detoast.h" +#endif + +#include "lwgeom_cache.h" + + +/* +* Generic statement caching infrastructure. We cache +* the following kinds of objects: +* +* geometries-with-trees +* PreparedGeometry, RTree, CIRC_TREE, RECT_TREE +* +* Each GenericCache* has a type, and after that +* some data. Similar to generic LWGEOM*. Test that +* the type number is what you expect before casting +* and de-referencing struct members. +*/ +typedef struct { + int type; + char data[1]; +} GenericCache; + +/* +* Although there are only two basic kinds of +* cache entries, the actual trees stored in the +* geometries-with-trees pattern are quite diverse, +* and they might be used in combination, so we have +* one slot for each tree type. +*/ +typedef struct { + GenericCache* entry[NUM_CACHE_ENTRIES]; +} GenericCacheCollection; + +MemoryContext +PostgisCacheContext(FunctionCallInfo fcinfo) +{ + if (!fcinfo->flinfo) + elog(ERROR, "%s: Could not find upper context", __func__); + return fcinfo->flinfo->fn_mcxt; +} + +/** +* Get the generic collection off the statement, allocate a +* new one if we don't have one already. +*/ +static GenericCacheCollection * +GetGenericCacheCollection(FunctionCallInfo fcinfo) +{ + GenericCacheCollection *internal_cache; + if (!fcinfo->flinfo) + elog(ERROR, "%s: Could not find upper context", __func__); + + internal_cache = fcinfo->flinfo->fn_extra; + + if (!internal_cache) + { + internal_cache = MemoryContextAllocZero(PostgisCacheContext(fcinfo), sizeof(GenericCacheCollection)); + fcinfo->flinfo->fn_extra = internal_cache; + } + return internal_cache; +} + + +/** +* Get an appropriate (based on the entry type number) +* GeomCache entry from the generic cache if one exists. +* Returns a cache pointer if there is a cache hit and we have an +* index built and ready to use. Returns NULL otherwise. +*/ +GeomCache * +GetGeomCache(FunctionCallInfo fcinfo, + const GeomCacheMethods *cache_methods, + SHARED_GSERIALIZED *g1, + SHARED_GSERIALIZED *g2) +{ + GeomCache* cache; + int cache_hit = 0; + MemoryContext old_context; + const GSERIALIZED *geom; + GenericCacheCollection* generic_cache = GetGenericCacheCollection(fcinfo); + uint32_t entry_number = cache_methods->entry_number; + + Assert(entry_number < NUM_CACHE_ENTRIES); + + cache = (GeomCache*)(generic_cache->entry[entry_number]); + if ( ! cache ) + { + old_context = MemoryContextSwitchTo(PostgisCacheContext(fcinfo)); + /* Allocate in the upper context */ + cache = cache_methods->GeomCacheAllocator(); + MemoryContextSwitchTo(old_context); + /* Store the pointer in GenericCache */ + cache->type = entry_number; + generic_cache->entry[entry_number] = (GenericCache*)cache; + } + + /* Cache hit on the first argument */ + if (g1 && cache->geom1 && cache->argnum != 2 && shared_gserialized_equal(g1, cache->geom1)) + { + cache_hit = 1; + geom = shared_gserialized_get(cache->geom1); + } + /* Cache hit on second argument */ + else if (g2 && cache->geom2 && cache->argnum != 1 && shared_gserialized_equal(g2, cache->geom2)) + { + cache_hit = 2; + geom = shared_gserialized_get(cache->geom2); + } + /* No cache hit. If we have a tree, free it. */ + else + { + cache_hit = 0; + if ( cache->argnum ) + { + cache_methods->GeomIndexFreer(cache); + cache->argnum = 0; + } + } + + /* Cache hit, but no tree built yet, build it! */ + if ( cache_hit && ! cache->argnum ) + { + int rv; + LWGEOM *lwgeom; + + /* Save the tree and supporting geometry in the cache */ + /* memory context */ + old_context = MemoryContextSwitchTo(PostgisCacheContext(fcinfo)); + lwgeom = lwgeom_from_gserialized(geom); + cache->argnum = 0; + + /* Can't build a tree on a NULL or empty */ + if ((!lwgeom) || lwgeom_is_empty(lwgeom)) + { + MemoryContextSwitchTo(old_context); + return NULL; + } + rv = cache_methods->GeomIndexBuilder(lwgeom, cache); + MemoryContextSwitchTo(old_context); + + /* Something went awry in the tree build phase */ + if ( ! rv ) + return NULL; + + /* Only set an argnum if everything completely successfully */ + cache->argnum = cache_hit; + } + + /* We have a hit and a calculated tree, we're done */ + if ( cache_hit && cache->argnum ) + return cache; + + /* Argument one didn't match, so copy the new value in. */ + if ( g1 && cache_hit != 1 ) + { + if (cache->geom1) + shared_gserialized_unref(fcinfo, cache->geom1); + cache->geom1 = shared_gserialized_ref(fcinfo, g1); + } + + /* Argument two didn't match, so copy the new value in. */ + if ( g2 && cache_hit != 2 ) + { + if (cache->geom2) + shared_gserialized_unref(fcinfo, cache->geom2); + cache->geom2 = shared_gserialized_ref(fcinfo, g2); + } + + return NULL; +} + +/******************************************************************************/ + +inline static ToastCache* +ToastCacheGet(FunctionCallInfo fcinfo) +{ + const uint32_t entry_number = TOAST_CACHE_ENTRY; + GenericCacheCollection* generic_cache = GetGenericCacheCollection(fcinfo); + ToastCache* cache = (ToastCache*)(generic_cache->entry[entry_number]); + if (!cache) + { + cache = MemoryContextAllocZero(PostgisCacheContext(fcinfo), sizeof(ToastCache)); + cache->type = entry_number; + generic_cache->entry[entry_number] = (GenericCache*)cache; + } + return cache; +} + +SHARED_GSERIALIZED * +ToastCacheGetGeometry(FunctionCallInfo fcinfo, uint32_t argnum) +{ + Assert(argnum < ToastCacheSize); + ToastCache* cache = ToastCacheGet(fcinfo); + ToastCacheArgument* arg = &(cache->arg[argnum]); + + Datum datum = PG_GETARG_DATUM(argnum); + struct varlena *attr = (struct varlena *) DatumGetPointer(datum); + + /* + * In general, you have to detoast the whole object to + * determine if it's different from the cached object, but + * for toasted objects, the va_valueid and va_toastrelid are + * a unique key. Only objects toasted to disk have this + * property, but fortunately those are also the objects + * that are costliest to de-TOAST, which is why this + * cache is a performance win. + * https://www.postgresql.org/message-id/8196.1585870220@sss.pgh.pa.us + */ + if (!VARATT_IS_EXTERNAL_ONDISK(attr)) + return shared_gserialized_new_nocache(datum); + + /* Retrieve the unique keys for this object */ + struct varatt_external ve; + VARATT_EXTERNAL_GET_POINTER(ve, attr); + Oid valueid = ve.va_valueid; + Oid toastrelid = ve.va_toastrelid; + + /* We've seen this object before? */ + if (arg->valueid == valueid && arg->toastrelid == toastrelid) + { + return arg->geom; + } + /* New object, clear our old copies and see if it */ + /* shows up a second time before taking a copy */ + else + { + if (arg->geom) + shared_gserialized_unref(fcinfo, arg->geom); + arg->valueid = valueid; + arg->toastrelid = toastrelid; + arg->geom = shared_gserialized_new_cached(fcinfo, datum); + return arg->geom; + } +} + +/* + * Retrieve an SRS from a given SRID + * Require valid spatial_ref_sys table entry + * + * Could return SRS as short one (i.e EPSG:4326) + * or as long one: (i.e urn:ogc:def:crs:EPSG::4326) + */ +static char * +getSRSbySRID(FunctionCallInfo fcinfo, int32_t srid, bool short_crs) +{ + static const uint16_t max_query_size = 512; + char query[512]; + char *srs, *srscopy; + int size, err; + postgis_initialize_cache(); + + if (SPI_OK_CONNECT != SPI_connect()) + { + elog(NOTICE, "%s: could not connect to SPI manager", __func__); + SPI_finish(); + return NULL; + } + + if (short_crs) + snprintf(query, + max_query_size, + "SELECT auth_name||':'||auth_srid \ + FROM %s WHERE srid='%d'", + postgis_spatial_ref_sys(), + srid); + else + snprintf(query, + max_query_size, + "SELECT 'urn:ogc:def:crs:'||auth_name||'::'||auth_srid \ + FROM %s WHERE srid='%d'", + postgis_spatial_ref_sys(), + srid); + + err = SPI_execute(query, true, 1); + if (err < 0) + { + elog(NOTICE, "%s: error executing query %d", __func__, err); + SPI_finish(); + return NULL; + } + + /* no entry in spatial_ref_sys */ + if (SPI_processed <= 0) + { + SPI_finish(); + return NULL; + } + + /* get result */ + srs = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1); + + /* NULL result */ + if (!srs) + { + SPI_finish(); + return NULL; + } + + size = strlen(srs) + 1; + + srscopy = MemoryContextAllocZero(PostgisCacheContext(fcinfo), size); + memcpy(srscopy, srs, size); + + /* disconnect from SPI */ + SPI_finish(); + + return srscopy; +} + +static inline SRSDescCache * +SRSDescCacheGet(FunctionCallInfo fcinfo) +{ + const uint32_t entry_number = SRSDESC_CACHE_ENTRY; + GenericCacheCollection *generic_cache = GetGenericCacheCollection(fcinfo); + SRSDescCache *cache = (SRSDescCache *)(generic_cache->entry[entry_number]); + if (!cache) + { + cache = MemoryContextAllocZero(PostgisCacheContext(fcinfo), sizeof(SRSDescCache)); + cache->type = entry_number; + generic_cache->entry[entry_number] = (GenericCache *)cache; + } + return cache; +} + +const char * +GetSRSCacheBySRID(FunctionCallInfo fcinfo, int32_t srid, bool short_crs) +{ + SRSDescCache *cache = SRSDescCacheGet(fcinfo); + SRSDescCacheArgument *arg = &(cache->arg[0]); + + if (arg->srid != srid || arg->short_mode != short_crs || !arg->srs) + { + arg->srid = srid; + arg->short_mode = short_crs; + if (arg->srs) + pfree(arg->srs); + arg->srs = getSRSbySRID(fcinfo, srid, short_crs); + } + return arg->srs; +} + +/* + * Retrieve an SRID from a given SRS + * Require valid spatial_ref_sys table entry + * + */ +static int32_t +getSRIDbySRS(FunctionCallInfo fcinfo, const char *srs) +{ + static const int16_t max_query_size = 512; + char query[512]; + Oid argtypes[] = {CSTRINGOID}; + Datum values[] = {CStringGetDatum(srs)}; + int32_t srid, err; + + postgis_initialize_cache(); + snprintf(query, + max_query_size, + "SELECT srid " + "FROM %s, " + "regexp_matches($1::text, E'([a-z]+):([0-9]+)', 'gi') AS re " + "WHERE re[1] ILIKE auth_name AND int4(re[2]) = auth_srid", + postgis_spatial_ref_sys()); + + if (!srs) + return 0; + + if (SPI_OK_CONNECT != SPI_connect()) + { + elog(NOTICE, "getSRIDbySRS: could not connect to SPI manager"); + return 0; + } + + err = SPI_execute_with_args(query, 1, argtypes, values, NULL, true, 1); + if (err < 0) + { + elog(NOTICE, "getSRIDbySRS: error executing query %d", err); + SPI_finish(); + return 0; + } + + /* no entry in spatial_ref_sys */ + if (SPI_processed <= 0) + { + snprintf(query, + max_query_size, + "SELECT srid " + "FROM %s, " + "regexp_matches($1::text, E'urn:ogc:def:crs:([a-z]+):.*:([0-9]+)', 'gi') AS re " + "WHERE re[1] ILIKE auth_name AND int4(re[2]) = auth_srid", + postgis_spatial_ref_sys()); + + err = SPI_execute_with_args(query, 1, argtypes, values, NULL, true, 1); + if (err < 0) + { + elog(NOTICE, "getSRIDbySRS: error executing query %d", err); + SPI_finish(); + return 0; + } + + if (SPI_processed <= 0) + { + SPI_finish(); + return 0; + } + } + + srid = atoi(SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1)); + SPI_finish(); + + return srid; +} + +static inline SRIDCache * +SRIDCacheGet(FunctionCallInfo fcinfo) +{ + const uint32_t entry_number = SRID_CACHE_ENTRY; + GenericCacheCollection *generic_cache = GetGenericCacheCollection(fcinfo); + SRIDCache *cache = (SRIDCache *)(generic_cache->entry[entry_number]); + if (!cache) + { + cache = MemoryContextAllocZero(PostgisCacheContext(fcinfo), sizeof(SRIDCache)); + cache->type = entry_number; + generic_cache->entry[entry_number] = (GenericCache *)cache; + } + return cache; +} + +int32_t +GetSRIDCacheBySRS(FunctionCallInfo fcinfo, const char *srs) +{ + SRIDCache *cache = SRIDCacheGet(fcinfo); + SRIDCacheArgument *arg = &(cache->arg[0]); + + if (!arg->srid || strcmp(srs, arg->srs) != 0) + { + size_t size = strlen(srs) + 1; + arg->srid = getSRIDbySRS(fcinfo, srs); + arg->srs = MemoryContextAlloc(PostgisCacheContext(fcinfo), size); + memcpy(arg->srs, srs, size); + } + + return arg->srid; +} diff --git a/mgist-postgis/libpgcommon/lwgeom_cache.h b/mgist-postgis/libpgcommon/lwgeom_cache.h new file mode 100644 index 0000000..bbf3e7e --- /dev/null +++ b/mgist-postgis/libpgcommon/lwgeom_cache.h @@ -0,0 +1,138 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright (C) 2012 Sandro Santilli + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#ifndef LWGEOM_CACHE_H_ +#define LWGEOM_CACHE_H_ 1 + +#include "postgres.h" +#include "fmgr.h" + +#include "liblwgeom.h" +#include "lwgeodetic_tree.h" +#include "lwgeom_pg.h" + + +#define TOAST_CACHE_ENTRY 0 +#define PREP_CACHE_ENTRY 1 +#define RTREE_CACHE_ENTRY 2 +#define CIRC_CACHE_ENTRY 3 +#define RECT_CACHE_ENTRY 4 +#define SRSDESC_CACHE_ENTRY 5 +#define SRID_CACHE_ENTRY 6 + +#define NUM_CACHE_ENTRIES 7 + +/* Returns the MemoryContext used to store the caches */ +MemoryContext PostgisCacheContext(FunctionCallInfo fcinfo); + +typedef struct { + GSERIALIZED *geom; + uint32_t count; +} SHARED_GSERIALIZED; + +SHARED_GSERIALIZED *shared_gserialized_new_nocache(Datum d); +SHARED_GSERIALIZED *shared_gserialized_new_cached(FunctionCallInfo fcinfo, Datum d); +SHARED_GSERIALIZED *shared_gserialized_ref(FunctionCallInfo fcinfo, SHARED_GSERIALIZED *ref); +void shared_gserialized_unref(FunctionCallInfo fcinfo, SHARED_GSERIALIZED *ref); +bool shared_gserialized_equal(SHARED_GSERIALIZED *r1, SHARED_GSERIALIZED *r2); +const GSERIALIZED *shared_gserialized_get(SHARED_GSERIALIZED *s); + +/* +* A generic GeomCache just needs space for the cache type, +* the cache keys (GSERIALIZED geometries), the key sizes, +* and the argument number the cached index/tree is going +* to refer to. +*/ +typedef struct { + uint32_t type; + uint32 argnum; + SHARED_GSERIALIZED *geom1; + SHARED_GSERIALIZED *geom2; +} GeomCache; + +/* +* Other specific geometry cache types are the +* RTreeGeomCache - lwgeom_rtree.h +* PrepGeomCache - lwgeom_geos_prepared.h +*/ + +/** +* Generic signature for functions to manage a geometry +* cache structure. +*/ +typedef struct +{ + uint32_t entry_number; /* What kind of structure is this? */ + int (*GeomIndexBuilder)(const LWGEOM* lwgeom, GeomCache* cache); /* Build an index/tree and add it to your cache */ + int (*GeomIndexFreer)(GeomCache* cache); /* Free the index/tree in your cache */ + GeomCache* (*GeomCacheAllocator)(void); /* Allocate the kind of cache object you use (GeomCache+some extra space) */ +} GeomCacheMethods; + +/* +* Cache retrieval functions +*/ +GeomCache *GetGeomCache(FunctionCallInfo fcinfo, + const GeomCacheMethods *cache_methods, + SHARED_GSERIALIZED *g1, + SHARED_GSERIALIZED *g2); + +/******************************************************************************/ + +#define ToastCacheSize 2 + +typedef struct +{ + Oid valueid; + Oid toastrelid; + SHARED_GSERIALIZED *geom; +} ToastCacheArgument; + +typedef struct +{ + int type; + ToastCacheArgument arg[ToastCacheSize]; +} ToastCache; + +SHARED_GSERIALIZED *ToastCacheGetGeometry(FunctionCallInfo fcinfo, uint32_t argnum); + +/******************************************************************************/ + +#define SRSDescCacheSize 1 +typedef struct { + int32_t srid; + bool short_mode; + char *srs; +} SRSDescCacheArgument; + +typedef struct { + int type; + SRSDescCacheArgument arg[SRSDescCacheSize]; +} SRSDescCache; + +const char *GetSRSCacheBySRID(FunctionCallInfo fcinfo, int32_t srid, bool short_crs); + +/******************************************************************************/ + +#define SRIDCacheSize 1 +typedef struct { + char *srs; + int32_t srid; +} SRIDCacheArgument; + +typedef struct { + int type; + SRIDCacheArgument arg[SRIDCacheSize]; +} SRIDCache; + +int32_t GetSRIDCacheBySRS(FunctionCallInfo fcinfo, const char *srs); + +#endif /* LWGEOM_CACHE_H_ */ diff --git a/mgist-postgis/libpgcommon/lwgeom_pg.c b/mgist-postgis/libpgcommon/lwgeom_pg.c new file mode 100644 index 0000000..4a5a99d --- /dev/null +++ b/mgist-postgis/libpgcommon/lwgeom_pg.c @@ -0,0 +1,555 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * + * http://postgis.net + * + * Copyright (C) 2011 Sandro Santilli + * Copyright (C) 2009-2011 Paul Ramsey + * Copyright (C) 2008 Mark Cave-Ayland + * Copyright (C) 2004-2007 Refractions Research Inc. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../postgis_config.h" + +#include + +#include "liblwgeom.h" +#include "lwgeom_pg.h" + +#include +#include +#include + +#define PGC_ERRMSG_MAXLEN 2048 //256 + +/****************************************************************************************/ +/* Global to hold all the run-time constants */ + +postgisConstants *POSTGIS_CONSTANTS = NULL; + +/* Utility call to lookup type oid given name and nspoid */ +static Oid TypenameNspGetTypid(const char *typname, Oid nsp_oid) +{ + return GetSysCacheOid2(TYPENAMENSP, + Anum_pg_type_oid, + PointerGetDatum(typname), + ObjectIdGetDatum(nsp_oid)); +} + +/* + * get_extension_schema - given an extension OID, fetch its extnamespace + * + * Returns InvalidOid if no such extension. + */ +static Oid +postgis_get_extension_schema(Oid ext_oid) +{ + Oid result; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + Relation rel = table_open(ExtensionRelationId, AccessShareLock); + ScanKeyInit(&entry[0], + Anum_pg_extension_oid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(ext_oid)); + + scandesc = systable_beginscan(rel, ExtensionOidIndexId, true, + NULL, 1, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + result = ((Form_pg_extension) GETSTRUCT(tuple))->extnamespace; + else + result = InvalidOid; + + systable_endscan(scandesc); + + table_close(rel, AccessShareLock); + + return result; +} + +static Oid +postgis_get_full_version_schema() +{ + const char* proname = "postgis_full_version"; + + #if POSTGIS_PGSQL_VERSION < 160 + List* names = stringToQualifiedNameList(proname); + #else + List* names = stringToQualifiedNameList(proname, NULL); + #endif + + #if POSTGIS_PGSQL_VERSION < 140 + FuncCandidateList clist = FuncnameGetCandidates(names, -1, NIL, false, false, false); + #else + FuncCandidateList clist = FuncnameGetCandidates(names, -1, NIL, false, false, false, false); + #endif + if (!clist) + return InvalidOid; + + return get_func_namespace(clist->oid); +} + + +/* Cache type lookups in per-session location */ +static postgisConstants * +getPostgisConstants() +{ + Oid nsp_oid = InvalidOid; + Oid ext_oid = get_extension_oid("postgis", true); + if (ext_oid != InvalidOid) + { + nsp_oid = postgis_get_extension_schema(ext_oid); + } + else + { + nsp_oid = postgis_get_full_version_schema(); + } + + /* early exit if we cannot lookup nsp_name, cf #4067 */ + if (nsp_oid == InvalidOid) + elog(ERROR, "Unable to determine 'postgis' install schema"); + + /* Put constants cache in a child of the CacheContext */ + MemoryContext context = AllocSetContextCreate( + CacheMemoryContext, + "PostGIS Constants Context", + ALLOCSET_SMALL_SIZES); + + /* Allocate in the CacheContext so we don't lose this at the end of the statement */ + postgisConstants* constants = MemoryContextAlloc(context, sizeof(postgisConstants)); + + /* Calculate fully qualified name of 'spatial_ref_sys' */ + char *nsp_name = get_namespace_name(nsp_oid); + constants->install_nsp_oid = nsp_oid; + constants->install_nsp = MemoryContextStrdup(CacheMemoryContext, nsp_name); + + char *spatial_ref_sys_fullpath = quote_qualified_identifier(nsp_name, "spatial_ref_sys"); + constants->spatial_ref_sys = MemoryContextStrdup(CacheMemoryContext, spatial_ref_sys_fullpath); + elog(DEBUG4, "%s: Spatial ref sys qualified as %s", __func__, spatial_ref_sys_fullpath); + pfree(nsp_name); + pfree(spatial_ref_sys_fullpath); + + /* Lookup all the type names in the context of the install schema */ + constants->geometry_oid = TypenameNspGetTypid("geometry", nsp_oid); + constants->geography_oid = TypenameNspGetTypid("geography", nsp_oid); + constants->box2df_oid = TypenameNspGetTypid("box2df", nsp_oid); + constants->box3d_oid = TypenameNspGetTypid("box3d", nsp_oid); + constants->gidx_oid = TypenameNspGetTypid("gidx", nsp_oid); + constants->raster_oid = TypenameNspGetTypid("raster", nsp_oid); + + /* Done */ + return constants; +} + +Oid +postgis_oid(postgisType typ) +{ + /* Use a schema qualified, cached lookup if we can */ + postgisConstants *cnsts = POSTGIS_CONSTANTS; + if (cnsts) + { + switch (typ) + { + case GEOMETRYOID: + return cnsts->geometry_oid; + case GEOGRAPHYOID: + return cnsts->geography_oid; + case BOX3DOID: + return cnsts->box3d_oid; + case BOX2DFOID: + return cnsts->box2df_oid; + case GIDXOID: + return cnsts->gidx_oid; + case RASTEROID: + return cnsts->raster_oid; + case POSTGISNSPOID: + return cnsts->install_nsp_oid; + default: + return InvalidOid; + } + } + /* Fall back to a bare lookup and hope the type in is */ + /* the search_path */ + else + { + switch (typ) + { + case GEOMETRYOID: + return TypenameGetTypid("geometry"); + case GEOGRAPHYOID: + return TypenameGetTypid("geography"); + case BOX3DOID: + return TypenameGetTypid("box3d"); + case BOX2DFOID: + return TypenameGetTypid("box2df"); + case GIDXOID: + return TypenameGetTypid("gidx"); + case RASTEROID: + return TypenameGetTypid("raster"); + default: + return InvalidOid; + } + } +} + +void +postgis_initialize_cache() +{ + /* Cache the info if we don't already have it */ + if (!POSTGIS_CONSTANTS) + POSTGIS_CONSTANTS = getPostgisConstants(); +} + +const char * +postgis_spatial_ref_sys() +{ + if (!POSTGIS_CONSTANTS) + return NULL; + return POSTGIS_CONSTANTS->spatial_ref_sys; +} + +/****************************************************************************************/ + + +/* + * Error message parsing functions + * + * Produces nicely formatted messages for parser/unparser errors with optional HINT + */ + +void +pg_parser_errhint(LWGEOM_PARSER_RESULT *lwg_parser_result) +{ + char *hintbuffer; + + /* Only display the parser position if the location is > 0; this provides a nicer output when the first token + within the input stream cannot be matched */ + if (lwg_parser_result->errlocation > 0) + { + /* Return a copy of the input string start truncated + * at the error location */ + hintbuffer = lwmessage_truncate( + (char *)lwg_parser_result->wkinput, 0, + lwg_parser_result->errlocation - 1, 40, 0); + + ereport(ERROR, + (errmsg("%s", lwg_parser_result->message), + errhint("\"%s\" <-- parse error at position %d within geometry", hintbuffer, lwg_parser_result->errlocation)) + ); + } + else + { + ereport(ERROR, + (errmsg("%s", lwg_parser_result->message), + errhint("You must specify a valid OGC WKT geometry type such as POINT, LINESTRING or POLYGON")) + ); + } +} + +void +pg_unparser_errhint(LWGEOM_UNPARSER_RESULT *lwg_unparser_result) +{ + /* For the unparser simply output the error message without any associated HINT */ + elog(ERROR, "%s", lwg_unparser_result->message); +} + +static void * +pg_alloc(size_t size) +{ + void * result; + + CHECK_FOR_INTERRUPTS(); /* give interrupter a chance */ + + POSTGIS_DEBUGF(5, " pg_alloc(%d) called", (int)size); + + result = palloc(size); + + POSTGIS_DEBUGF(5, " pg_alloc(%d) returning %p", (int)size, result); + + return result; +} + +static void * +pg_realloc(void *mem, size_t size) +{ + void * result; + + CHECK_FOR_INTERRUPTS(); /* give interrupter a chance */ + + POSTGIS_DEBUGF(5, " pg_realloc(%p, %d) called", mem, (int)size); + + result = repalloc(mem, size); + + POSTGIS_DEBUGF(5, " pg_realloc(%p, %d) returning %p", mem, (int)size, result); + + return result; +} + +static void +pg_free(void *ptr) +{ + pfree(ptr); +} + +static void +pg_error(const char *fmt, va_list ap) +{ + char errmsg[PGC_ERRMSG_MAXLEN+1]; + + vsnprintf (errmsg, PGC_ERRMSG_MAXLEN, fmt, ap); + + errmsg[PGC_ERRMSG_MAXLEN]='\0'; + ereport(ERROR, (errmsg_internal("%s", errmsg))); +} + +static void +pg_warning(const char *fmt, va_list ap) +{ + char errmsg[PGC_ERRMSG_MAXLEN+1]; + + vsnprintf (errmsg, PGC_ERRMSG_MAXLEN, fmt, ap); + + errmsg[PGC_ERRMSG_MAXLEN]='\0'; + ereport(WARNING, (errmsg_internal("%s", errmsg))); +} + +static void +pg_notice(const char *fmt, va_list ap) +{ + char errmsg[PGC_ERRMSG_MAXLEN+1]; + + vsnprintf (errmsg, PGC_ERRMSG_MAXLEN, fmt, ap); + + errmsg[PGC_ERRMSG_MAXLEN]='\0'; + ereport(NOTICE, (errmsg_internal("%s", errmsg))); +} + +static void +pg_debug(int level, const char *fmt, va_list ap) +{ + char errmsg[PGC_ERRMSG_MAXLEN+1]; + vsnprintf (errmsg, PGC_ERRMSG_MAXLEN, fmt, ap); + errmsg[PGC_ERRMSG_MAXLEN]='\0'; + int pglevel[6] = {NOTICE, DEBUG1, DEBUG2, DEBUG3, DEBUG4, DEBUG5}; + + if ( level >= 0 && level <= 5 ) + ereport(pglevel[level], (errmsg_internal("%s", errmsg))); + else + ereport(DEBUG5, (errmsg_internal("%s", errmsg))); +} + +void +pg_install_lwgeom_handlers(void) +{ + /* install PostgreSQL handlers */ + lwgeom_set_handlers(pg_alloc, pg_realloc, pg_free, pg_error, pg_notice); + /* + If you want to try with malloc: + lwgeom_set_handlers(NULL, NULL, NULL, pg_error, pg_notice); + */ + lwgeom_set_debuglogger(pg_debug); +} + +/** +* Utility method to call the serialization and then set the +* PgSQL varsize header appropriately with the serialized size. +*/ +GSERIALIZED* geography_serialize(LWGEOM *lwgeom) +{ + size_t ret_size; + GSERIALIZED *g; + /** force to geodetic in case it's not **/ + lwgeom_set_geodetic(lwgeom, true); + + g = gserialized_from_lwgeom(lwgeom, &ret_size); + SET_VARSIZE(g, ret_size); + return g; +} + + +/** +* Utility method to call the serialization and then set the +* PgSQL varsize header appropriately with the serialized size. +*/ +GSERIALIZED* geometry_serialize(LWGEOM *lwgeom) +{ + size_t ret_size; + GSERIALIZED *g; + + g = gserialized_from_lwgeom(lwgeom, &ret_size); + SET_VARSIZE(g, ret_size); + return g; +} + +void +lwpgnotice(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + + pg_notice(fmt, ap); + + va_end(ap); +} + +void +lwpgwarning(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + + pg_warning(fmt, ap); + + va_end(ap); +} + +void +lwpgerror(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + + pg_error(fmt, ap); + + va_end(ap); +} + + +/* + * the bare comparison function for GUC names + */ +int +postgis_guc_name_compare(const char *namea, const char *nameb) +{ + /* + * The temptation to use strcasecmp() here must be resisted, because the + * array ordering has to remain stable across setlocale() calls. So, build + * our own with a simple ASCII-only downcasing. + */ + while (*namea && *nameb) + { + char cha = *namea++; + char chb = *nameb++; + + if (cha >= 'A' && cha <= 'Z') + cha += 'a' - 'A'; + if (chb >= 'A' && chb <= 'Z') + chb += 'a' - 'A'; + if (cha != chb) + return cha - chb; + } + if (*namea) + return 1; /* a is longer */ + if (*nameb) + return -1; /* b is longer */ + return 0; +} + +/* + * comparator for qsorting and bsearching guc_variables array + */ +int +postgis_guc_var_compare(const void *a, const void *b) +{ + const struct config_generic *confa = *(struct config_generic * const *) a; + const struct config_generic *confb = *(struct config_generic * const *) b; + + return postgis_guc_name_compare(confa->name, confb->name); +} + +/* +* This is copied from the top half of the find_option function +* in postgresql's guc.c. We search the guc_variables for our option. +* Then we make sure it's not a placeholder. Only then are we sure +* we have a potential conflict, of the sort experienced during an +* extension upgrade. +*/ +int +postgis_guc_find_option(const char *name) +{ + const char **key = &name; + struct config_generic **res; + + /* + * By equating const char ** with struct config_generic *, we are assuming + * the name field is first in config_generic. + */ +#if POSTGIS_PGSQL_VERSION >= 160 + res = (struct config_generic **) find_option((void *) &key, false, true, ERROR); +#else + res = (struct config_generic **) bsearch((void *) &key, + (void *) get_guc_variables(), + GetNumConfigOptions(), + sizeof(struct config_generic *), + postgis_guc_var_compare); +#endif + + /* Found nothing? Good */ + if ( ! res ) return 0; + + /* Hm, you found something, but maybe it's just a placeholder? */ + /* We'll consider a placehold a "not found" */ + if ( (*res)->flags & GUC_CUSTOM_PLACEHOLDER ) + return 0; + + return 1; +} + + +/* PgSQL 12+ still lacks 3-argument version of these functions */ +Datum +CallerFInfoFunctionCall3(PGFunction func, FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2, Datum arg3) +{ + LOCAL_FCINFO(fcinfo, 3); + Datum result; + + InitFunctionCallInfoData(*fcinfo, flinfo, 3, collation, NULL, NULL); + + fcinfo->args[0].value = arg1; + fcinfo->args[0].isnull = false; + fcinfo->args[1].value = arg2; + fcinfo->args[1].isnull = false; + fcinfo->args[2].value = arg3; + fcinfo->args[2].isnull = false; + + result = (*func) (fcinfo); + + /* Check for null result, since caller is clearly not expecting one */ + if (fcinfo->isnull) + elog(ERROR, "function %p returned NULL", (void *) func); + + return result; +} diff --git a/mgist-postgis/libpgcommon/lwgeom_pg.h b/mgist-postgis/libpgcommon/lwgeom_pg.h new file mode 100644 index 0000000..b6f6973 --- /dev/null +++ b/mgist-postgis/libpgcommon/lwgeom_pg.h @@ -0,0 +1,263 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * + * Copyright (C) 2011 Sandro Santilli + * Copyright (C) 2009-2011 Paul Ramsey + * Copyright (C) 2008 Mark Cave-Ayland + * Copyright (C) 2004-2007 Refractions Research Inc. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#ifndef _LWGEOM_PG_H +#define _LWGEOM_PG_H 1 + +#include "postgres.h" +#include "fmgr.h" +#include "catalog/namespace.h" /* For TypenameGetTypid */ +#include "catalog/pg_type_d.h" /* For TypenameGetTypid */ +#include "utils/geo_decls.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/syscache.h" + +#include "liblwgeom.h" +#include "pgsql_compat.h" + +#if POSTGIS_PGSQL_VERSION > 150 +#include "varatt.h" +#endif + +/****************************************************************************************/ + +typedef enum +{ + GEOMETRYOID = 1, + GEOGRAPHYOID, + BOX3DOID, + BOX2DFOID, + GIDXOID, + RASTEROID, + POSTGISNSPOID +} postgisType; + +typedef struct +{ + Oid geometry_oid; + Oid geography_oid; + Oid box2df_oid; + Oid box3d_oid; + Oid gidx_oid; + Oid raster_oid; + Oid install_nsp_oid; + char *install_nsp; + char *spatial_ref_sys; +} postgisConstants; + +/* Global to hold all the run-time constants */ +extern postgisConstants *POSTGIS_CONSTANTS; + +/* Infer the install location of postgis, and thus the namespace to use + * when looking up the type name, and cache oids */ +void postgis_initialize_cache(); + +/* Useful if postgis_initialize_cache() has been called before. + * Otherwise it tries to do a bare lookup */ +Oid postgis_oid(postgisType typ); + +/* Returns the fully qualified, and properly quoted, identifier of spatial_ref_sys + * Note that it's length can be up to strlen(schema) + "." + strlen("spatial_ref_sys") + NULL, i.e: 80 bytes + * Only useful if postgis_initialize_cache has been called before. Otherwise returns "spatial_ref_sys" + */ +const char *postgis_spatial_ref_sys(); + +/****************************************************************************************/ + +/* Globals to hold GEOMETRYOID, GEOGRAPHYOID */ +// extern Oid GEOMETRYOID; +// extern Oid GEOGRAPHYOID; +// Oid postgis_geometry_oid(void); +// Oid postgis_geography_oid(void); + +/****************************************************************************************/ + +/* Install PosgreSQL handlers for liblwgeom use */ +void pg_install_lwgeom_handlers(void); + +/* Argument handling macros */ +#define PG_GETARG_GSERIALIZED_P(varno) ((GSERIALIZED *)PG_DETOAST_DATUM(PG_GETARG_DATUM(varno))) +#define PG_GETARG_GSERIALIZED_P_COPY(varno) ((GSERIALIZED *)PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(varno))) +#define PG_GSERIALIZED_DATUM_NEEDS_DETOAST(datum) \ + (VARATT_IS_EXTENDED((datum)) || VARATT_IS_EXTERNAL((datum)) || VARATT_IS_COMPRESSED((datum))) +#define PG_GETARG_GSERIALIZED_HEADER(varno) \ + PG_GSERIALIZED_DATUM_NEEDS_DETOAST(PG_GETARG_DATUM(varno)) \ + ? ((GSERIALIZED *)PG_DETOAST_DATUM_SLICE(PG_GETARG_DATUM(varno), 0, gserialized_max_header_size())) \ + : ((GSERIALIZED *)(PG_GETARG_DATUM(varno))) + +/* Debugging macros */ +#if POSTGIS_DEBUG_LEVEL > 0 + +/* Display a simple message at NOTICE level */ +/* from PgSQL utils/elog.h, LOG is 15, and DEBUG5 is 10 and everything else is in between */ +#define POSTGIS_DEBUG(level, msg) \ + do { \ + if (POSTGIS_DEBUG_LEVEL >= level) \ + ereport((level < 1 || level > 5) ? DEBUG5 : (LOG - level), (errmsg_internal("[%s:%s:%d] " msg, __FILE__, __func__, __LINE__))); \ + } while (0); + +/* Display a formatted message at NOTICE level (like printf, with variadic arguments) */ +#define POSTGIS_DEBUGF(level, msg, ...) \ + do { \ + if (POSTGIS_DEBUG_LEVEL >= level) \ + ereport((level < 1 || level > 5) ? DEBUG5 : (LOG - level), (errmsg_internal("[%s:%s:%d] " msg, __FILE__, __func__, __LINE__, __VA_ARGS__))); \ + } while (0); + +#else /* POSTGIS_DEBUG_LEVEL */ + +/* Empty prototype that can be optimised away by the compiler for non-debug builds */ +#define POSTGIS_DEBUG(level, msg) \ + ((void) 0) + +/* Empty prototype that can be optimised away by the compiler for non-debug builds */ +#define POSTGIS_DEBUGF(level, msg, ...) \ + ((void) 0) + +#endif /* POSTGIS_DEBUG_LEVEL */ + +/* +* GUC name search functions stolen from PostgreSQL to +* support searching for already-defined GUC variables +*/ +int postgis_guc_name_compare(const char *namea, const char *nameb); +int postgis_guc_var_compare(const void *a, const void *b); +int postgis_guc_find_option(const char *name); + +/* + * Standard macro for reporting parser errors to PostgreSQL + */ + +extern void pg_parser_errhint(LWGEOM_PARSER_RESULT *lwg_parser_result); +extern void pg_unparser_errhint(LWGEOM_UNPARSER_RESULT *lwg_unparser_result); + +#define PG_PARSER_ERROR(lwg_parser_result) \ + do { \ + pg_parser_errhint(&lwg_parser_result); \ + } while(0); + +/* + * Standard macro for reporting unparser errors to PostgreSQL + */ +#define PG_UNPARSER_ERROR(lwg_unparser_result) \ + do { \ + pg_unparser_errhint(&lwg_unparser_result); \ + } while(0); + +/* TODO: only cancel the interrupt if inside an outer call ? */ +#define LWGEOM_INIT() { \ + lwgeom_cancel_interrupt(); \ +} + +/** +* Utility method to call the serialization and then set the +* PgSQL varsize header appropriately with the serialized size. +*/ +GSERIALIZED *geometry_serialize(LWGEOM *lwgeom); + +/** +* Utility method to call the serialization and then set the +* PgSQL varsize header appropriately with the serialized size. +*/ +GSERIALIZED* geography_serialize(LWGEOM *lwgeom); + +/** + * Compare SRIDs of two GSERIALIZEDs and print informative error message if they differ. + */ +void gserialized_error_if_srid_mismatch(const GSERIALIZED *g1, const GSERIALIZED *g2, const char *funcname); + +/** + * Compare SRIDs of GSERIALIZEDs to reference and print informative error message if they differ. + */ +void gserialized_error_if_srid_mismatch_reference(const GSERIALIZED *g1, const int32_t srid, const char *funcname); + +/** +* Pull out a gbox bounding box as fast as possible. +* Tries to read cached box from front of serialized vardata. +* If no cached box, calculates box from scratch. +* Fails on empty. +*/ +int gserialized_datum_get_gbox_p(Datum gsdatum, GBOX *gbox); +int gserialized_datum_get_internals_p(Datum gsdatum, GBOX *gbox, lwflags_t *flags, uint8_t *type, int32_t *srid); + +/* PG-exposed */ +Datum BOX2D_same(PG_FUNCTION_ARGS); +Datum BOX2D_overlap(PG_FUNCTION_ARGS); +Datum BOX2D_overleft(PG_FUNCTION_ARGS); +Datum BOX2D_left(PG_FUNCTION_ARGS); +Datum BOX2D_right(PG_FUNCTION_ARGS); +Datum BOX2D_overright(PG_FUNCTION_ARGS); +Datum BOX2D_overbelow(PG_FUNCTION_ARGS); +Datum BOX2D_below(PG_FUNCTION_ARGS); +Datum BOX2D_above(PG_FUNCTION_ARGS); +Datum BOX2D_overabove(PG_FUNCTION_ARGS); +Datum BOX2D_contained(PG_FUNCTION_ARGS); +Datum BOX2D_contain(PG_FUNCTION_ARGS); +Datum BOX2D_intersects(PG_FUNCTION_ARGS); +Datum BOX2D_union(PG_FUNCTION_ARGS); + +Datum LWGEOM_same(PG_FUNCTION_ARGS); + +/** needed for sp-gist support PostgreSQL 11+ **/ +Datum BOX3D_construct(PG_FUNCTION_ARGS); + +Datum LWGEOM_to_BOX2DF(PG_FUNCTION_ARGS); +Datum LWGEOM_to_BOX3D(PG_FUNCTION_ARGS); +Datum BOX3D_contains(PG_FUNCTION_ARGS); +Datum BOX3D_contained(PG_FUNCTION_ARGS); +Datum BOX3D_overlaps(PG_FUNCTION_ARGS); +Datum BOX3D_same(PG_FUNCTION_ARGS); +Datum BOX3D_left(PG_FUNCTION_ARGS); +Datum BOX3D_overleft(PG_FUNCTION_ARGS); +Datum BOX3D_right(PG_FUNCTION_ARGS); +Datum BOX3D_overright(PG_FUNCTION_ARGS); +Datum BOX3D_below(PG_FUNCTION_ARGS); +Datum BOX3D_overbelow(PG_FUNCTION_ARGS); +Datum BOX3D_above(PG_FUNCTION_ARGS); +Datum BOX3D_overabove(PG_FUNCTION_ARGS); +Datum BOX3D_front(PG_FUNCTION_ARGS); +Datum BOX3D_overfront(PG_FUNCTION_ARGS); +Datum BOX3D_back(PG_FUNCTION_ARGS); +Datum BOX3D_overback(PG_FUNCTION_ARGS); +Datum BOX3D_distance(PG_FUNCTION_ARGS); + +#define DatumGetBox3DP(X) ((BOX3D *)DatumGetPointer(X)) +#define Box3DPGetDatum(X) PointerGetDatum(X) +#define PG_GETARG_BOX3D_P(n) DatumGetBox3DP(PG_GETARG_DATUM(n)) +#define PG_RETURN_BOX3D_P(x) return Box3DPGetDatum(x) + +Datum LWGEOM_force_2d(PG_FUNCTION_ARGS); +Datum LWGEOM_force_3dm(PG_FUNCTION_ARGS); +Datum LWGEOM_force_3dz(PG_FUNCTION_ARGS); +Datum LWGEOM_force_4d(PG_FUNCTION_ARGS); +Datum LWGEOM_force_collection(PG_FUNCTION_ARGS); +Datum LWGEOM_force_multi(PG_FUNCTION_ARGS); +Datum LWGEOM_force_curve(PG_FUNCTION_ARGS); + +Datum LWGEOMFromEWKB(PG_FUNCTION_ARGS); +Datum LWGEOMFromTWKB(PG_FUNCTION_ARGS); + +Datum LWGEOM_getBBOX(PG_FUNCTION_ARGS); +Datum LWGEOM_addBBOX(PG_FUNCTION_ARGS); +Datum LWGEOM_dropBBOX(PG_FUNCTION_ARGS); + +void lwpgerror(const char *fmt, ...); +void lwpgnotice(const char *fmt, ...); +void lwpgwarning(const char *fmt, ...); + +Datum CallerFInfoFunctionCall3(PGFunction func, FmgrInfo *flinfo, Oid collation, Datum arg1, Datum arg2, Datum arg3); + + + +#endif /* !defined _LWGEOM_PG_H */ diff --git a/mgist-postgis/libpgcommon/lwgeom_transform.c b/mgist-postgis/libpgcommon/lwgeom_transform.c new file mode 100644 index 0000000..95394bd --- /dev/null +++ b/mgist-postgis/libpgcommon/lwgeom_transform.c @@ -0,0 +1,566 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright (C) 2001-2003 Refractions Research Inc. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + + +/* PostgreSQL headers */ +#include "postgres.h" +#include "fmgr.h" +#include "miscadmin.h" +#include "utils/memutils.h" +#include "executor/spi.h" +#include "access/hash.h" +#include "utils/hsearch.h" + +/* PostGIS headers */ +#include "../postgis_config.h" +#include "liblwgeom.h" +#include "lwgeom_pg.h" +#include "lwgeom_cache.h" +#include "lwgeom_transform.h" + +/* C headers */ +#include +#include +#include + +#define maxprojlen 512 +#define spibufferlen 512 + + +/* + * PROJ 4 backend hash table initial hash size + * (since 16 is the default portal hash table size, and we would + * typically have 2 entries per portal + * then we shall use a default size of 256) + */ +#define PROJ_BACKEND_HASH_SIZE 256 + +/* Global to hold the Proj object cache */ +PROJSRSCache *PROJ_CACHE = NULL; + + +/** + * Utility structure to get many potential string representations + * from spatial_ref_sys query. + */ +typedef struct { + char* authtext; /* auth_name:auth_srid */ + char* srtext; + char* proj4text; +} PjStrs; + +/* Internal Cache API */ +static LWPROJ * +AddToPROJSRSCache(PROJSRSCache *PROJCache, int32_t srid_from, int32_t srid_to); +static void DeleteFromPROJSRSCache(PROJSRSCache *PROJCache, uint32_t position); + +static void +PROJSRSDestroyPJ(void *projection) +{ + LWPROJ *pj = (LWPROJ *)projection; + + if (pj->pj) + { + proj_destroy(pj->pj); + pj->pj = NULL; + } +} + +/** +* Destroy all the malloc'ed proj objects stored +* in the PROJSRSCache, in case the containing +* context is removed (shouldn't actually happen) +*/ +static void +PROJSRSDestroyPortalCache(void *portalCache) +{ + PROJSRSCache* cache = (PROJSRSCache*)portalCache; + for (uint32_t i = 0; i < cache->PROJSRSCacheCount; i++) + { + if (cache->PROJSRSCache[i].projection) + PROJSRSDestroyPJ(cache->PROJSRSCache[i].projection); + } +} + +/** +* Get the Proj cache entry from the global variable if one exists. +* If it doesn't exist, make a new blank one and return it. +*/ +PROJSRSCache * +GetPROJSRSCache() +{ + PROJSRSCache* cache = PROJ_CACHE; + if (!cache) + { + /* Put proj cache in a child of the CacheContext */ + MemoryContext context = AllocSetContextCreate( + CacheMemoryContext, + "Proj Context", + ALLOCSET_SMALL_SIZES); + + /* Allocate in the upper context */ + cache = MemoryContextAllocZero(context, sizeof(PROJSRSCache)); + + if (!cache) + elog(ERROR, "Unable to allocate space for PROJSRSCache in context %p", context); + + cache->PROJSRSCacheCount = 0; + cache->PROJSRSCacheContext = context; + + /* Use this to clean up PROJSRSCache in event of MemoryContext reset */ + MemoryContextCallback* callback = MemoryContextAlloc(context, sizeof(MemoryContextCallback)); + callback->func = PROJSRSDestroyPortalCache; + callback->arg = (void *)cache; + MemoryContextRegisterResetCallback(context, callback); + + PROJ_CACHE = cache; + } + return cache; +} + + +/***************************************************************************** + * Per-cache management functions + */ + +static LWPROJ * +GetProjectionFromPROJCache(PROJSRSCache *cache, int32_t srid_from, int32_t srid_to) +{ + uint32_t i; + for (i = 0; i < cache->PROJSRSCacheCount; i++) + { + if (cache->PROJSRSCache[i].srid_from == srid_from && + cache->PROJSRSCache[i].srid_to == srid_to) + { + cache->PROJSRSCache[i].hits++; + return cache->PROJSRSCache[i].projection; + } + } + + return NULL; +} + +static char* +SPI_pstrdup(const char* str) +{ + char* ostr = NULL; + if (str) + { + ostr = SPI_palloc(strlen(str)+1); + strcpy(ostr, str); + } + return ostr; +} + +static PjStrs +GetProjStringsSPI(int32_t srid) +{ + int spi_result; + char proj_spi_buffer[spibufferlen]; + PjStrs strs; + memset(&strs, 0, sizeof(strs)); + + /* Connect */ + spi_result = SPI_connect(); + if (spi_result != SPI_OK_CONNECT) + { + elog(ERROR, "Could not connect to database using SPI"); + } + + static char *proj_str_tmpl = + "SELECT proj4text, auth_name, auth_srid, srtext " + "FROM %s " + "WHERE srid = %d " + "LIMIT 1"; + snprintf(proj_spi_buffer, spibufferlen, proj_str_tmpl, postgis_spatial_ref_sys(), srid); + + /* Execute the query, noting the readonly status of this SQL */ + spi_result = SPI_execute(proj_spi_buffer, true, 1); + + /* Read back the PROJ text */ + if (spi_result == SPI_OK_SELECT && SPI_processed > 0) + { + /* Select the first (and only tuple) */ + TupleDesc tupdesc = SPI_tuptable->tupdesc; + SPITupleTable *tuptable = SPI_tuptable; + HeapTuple tuple = tuptable->vals[0]; + /* Always return the proj4text */ + char* proj4text = SPI_getvalue(tuple, tupdesc, 1); + if (proj4text && strlen(proj4text)) + strs.proj4text = SPI_pstrdup(proj4text); + + /* For Proj >= 6 prefer "AUTHNAME:AUTHSRID" to proj strings */ + /* as proj_create_crs_to_crs() will give us more consistent */ + /* results with authority numbers than with proj strings */ + char* authname = SPI_getvalue(tuple, tupdesc, 2); + char* authsrid = SPI_getvalue(tuple, tupdesc, 3); + if (authname && authsrid && + strlen(authname) && strlen(authsrid)) + { + char tmp[maxprojlen]; + snprintf(tmp, maxprojlen, "%s:%s", authname, authsrid); + strs.authtext = SPI_pstrdup(tmp); + } + + /* Proj6+ can parse srtext, so return that too */ + char* srtext = SPI_getvalue(tuple, tupdesc, 4); + if (srtext && strlen(srtext)) + strs.srtext = SPI_pstrdup(srtext); + } + else + { + elog(ERROR, "Cannot find SRID (%d) in spatial_ref_sys", srid); + } + + spi_result = SPI_finish(); + if (spi_result != SPI_OK_FINISH) + { + elog(ERROR, "Could not disconnect from database using SPI"); + } + + return strs; +} + + +/** + * Given an SRID, return the corresponding proj strings + * (auth_name:auth_srid/srtext/proj4text) + * + * If the integer is one of the "well known" projections we support + * (WGS84 UTM N/S, Polar Stereographic N/S - see SRID_* macros), + * return the proj4text for those. + */ +static PjStrs +GetProjStrings(int32_t srid) +{ + PjStrs strs; + memset(&strs, 0, sizeof(strs)); + + /* SRIDs in SPATIAL_REF_SYS */ + if ( srid < SRID_RESERVE_OFFSET ) + { + return GetProjStringsSPI(srid); + } + /* Automagic SRIDs */ + else + { + strs.proj4text = palloc(maxprojlen); + int id = srid; + /* UTM North */ + if ( id >= SRID_NORTH_UTM_START && id <= SRID_NORTH_UTM_END ) + { + snprintf(strs.proj4text, maxprojlen, "+proj=utm +zone=%d +ellps=WGS84 +datum=WGS84 +units=m +no_defs", id - SRID_NORTH_UTM_START + 1); + } + /* UTM South */ + else if ( id >= SRID_SOUTH_UTM_START && id <= SRID_SOUTH_UTM_END ) + { + snprintf(strs.proj4text, maxprojlen, "+proj=utm +zone=%d +south +ellps=WGS84 +datum=WGS84 +units=m +no_defs", id - SRID_SOUTH_UTM_START + 1); + } + /* Lambert zones (about 30x30, larger in higher latitudes) */ + /* There are three latitude zones, divided at -90,-60,-30,0,30,60,90. */ + /* In yzones 2,3 (equator) zones, the longitudinal zones are divided every 30 degrees (12 of them) */ + /* In yzones 1,4 (temperate) zones, the longitudinal zones are every 45 degrees (8 of them) */ + /* In yzones 0,5 (polar) zones, the longitudinal zones are ever 90 degrees (4 of them) */ + else if ( id >= SRID_LAEA_START && id <= SRID_LAEA_END ) + { + int zone = id - SRID_LAEA_START; + int xzone = zone % 20; + int yzone = zone / 20; + double lat_0 = 30.0 * (yzone - 3) + 15.0; + double lon_0 = 0.0; + + /* The number of xzones is variable depending on yzone */ + if ( yzone == 2 || yzone == 3 ) + lon_0 = 30.0 * (xzone - 6) + 15.0; + else if ( yzone == 1 || yzone == 4 ) + lon_0 = 45.0 * (xzone - 4) + 22.5; + else if ( yzone == 0 || yzone == 5 ) + lon_0 = 90.0 * (xzone - 2) + 45.0; + else + lwerror("Unknown yzone encountered!"); + + snprintf(strs.proj4text, maxprojlen, "+proj=laea +ellps=WGS84 +datum=WGS84 +lat_0=%g +lon_0=%g +units=m +no_defs", lat_0, lon_0); + } + /* Lambert Azimuthal Equal Area South Pole */ + else if ( id == SRID_SOUTH_LAMBERT ) + { + strncpy(strs.proj4text, "+proj=laea +lat_0=-90 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs", maxprojlen ); + } + /* Polar Sterographic South */ + else if ( id == SRID_SOUTH_STEREO ) + { + strncpy(strs.proj4text, "+proj=stere +lat_0=-90 +lat_ts=-71 +lon_0=0 +k=1 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs", maxprojlen); + } + /* Lambert Azimuthal Equal Area North Pole */ + else if ( id == SRID_NORTH_LAMBERT ) + { + strncpy(strs.proj4text, "+proj=laea +lat_0=90 +lon_0=-40 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs", maxprojlen ); + } + /* Polar Stereographic North */ + else if ( id == SRID_NORTH_STEREO ) + { + strncpy(strs.proj4text, "+proj=stere +lat_0=90 +lat_ts=71 +lon_0=0 +k=1 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs", maxprojlen ); + } + /* World Mercator */ + else if ( id == SRID_WORLD_MERCATOR ) + { + strncpy(strs.proj4text, "+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs", maxprojlen ); + } + else + { + elog(ERROR, "Invalid reserved SRID (%d)", srid); + return strs; + } + + POSTGIS_DEBUGF(3, "returning on SRID=%d: %s", srid, strs.proj4text); + return strs; + } +} + +static int +pjstrs_has_entry(const PjStrs *strs) +{ + if ((strs->proj4text && strlen(strs->proj4text)) || + (strs->authtext && strlen(strs->authtext)) || + (strs->srtext && strlen(strs->srtext))) + return 1; + else + return 0; +} + +static void +pjstrs_pfree(PjStrs *strs) +{ + if (strs->proj4text) + pfree(strs->proj4text); + if (strs->authtext) + pfree(strs->authtext); + if (strs->srtext) + pfree(strs->srtext); +} + +static char* +pgstrs_get_entry(const PjStrs *strs, int n) +{ + switch (n) + { + case 0: + return strs->authtext; + case 1: + return strs->srtext; + case 2: + return strs->proj4text; + default: + return NULL; + } +} + +/** + * Add an entry to the local PROJ SRS cache. If we need to wrap around then + * we must make sure the entry we choose to delete does not contain other_srid + * which is the definition for the other half of the transformation. + */ +static LWPROJ * +AddToPROJSRSCache(PROJSRSCache *PROJCache, int32_t srid_from, int32_t srid_to) +{ + MemoryContext oldContext; + + PjStrs from_strs, to_strs; + char *pj_from_str, *pj_to_str; + + /* + ** Turn the SRID number into a proj4 string, by reading from spatial_ref_sys + ** or instantiating a magical value from a negative srid. + */ + from_strs = GetProjStrings(srid_from); + if (!pjstrs_has_entry(&from_strs)) + elog(ERROR, "got NULL for SRID (%d)", srid_from); + to_strs = GetProjStrings(srid_to); + if (!pjstrs_has_entry(&to_strs)) + elog(ERROR, "got NULL for SRID (%d)", srid_to); + + oldContext = MemoryContextSwitchTo(PROJCache->PROJSRSCacheContext); + + + LWPROJ *projection = NULL; + /* Try combinations of AUTH_NAME:AUTH_SRID/SRTEXT/PROJ4TEXT until we find */ + /* one that gives us a usable transform. Note that we prefer */ + /* AUTH_NAME:AUTH_SRID over SRTEXT and SRTEXT over PROJ4TEXT */ + /* (3 entries * 3 entries = 9 combos) */ + uint32_t i; + for (i = 0; i < 9; i++) + { + pj_from_str = pgstrs_get_entry(&from_strs, i / 3); + pj_to_str = pgstrs_get_entry(&to_strs, i % 3); + if (!(pj_from_str && pj_to_str)) + continue; + + projection = lwproj_from_str(pj_from_str, pj_to_str); + if (projection) + break; + } + if (!projection) + { + elog(ERROR, "could not form projection (LWPROJ) from 'srid=%d' to 'srid=%d'", srid_from, srid_to); + return NULL; + } + + /* If the cache is already full then find the least used element and delete it */ + uint32_t cache_position = PROJCache->PROJSRSCacheCount; + uint32_t hits = 1; + if (cache_position == PROJ_CACHE_ITEMS) + { + cache_position = 0; + hits = PROJCache->PROJSRSCache[0].hits; + for (uint32_t i = 1; i < PROJ_CACHE_ITEMS; i++) + { + if (PROJCache->PROJSRSCache[i].hits < hits) + { + cache_position = i; + hits = PROJCache->PROJSRSCache[i].hits; + } + } + DeleteFromPROJSRSCache(PROJCache, cache_position); + /* To avoid the element we are introduced now being evicted next (as + * it would have 1 hit, being most likely the lower one) we reuse the + * hits from the evicted position and add some extra buffer + */ + hits += 5; + } + else + { + PROJCache->PROJSRSCacheCount++; + } + + POSTGIS_DEBUGF(3, + "adding transform %d => %d aka \"%s\" => \"%s\" to query cache at index %d", + srid_from, + srid_to, + pj_from_str, + pj_to_str, + cache_position); + + /* Free the projection strings */ + pjstrs_pfree(&from_strs); + pjstrs_pfree(&to_strs); + + /* Store everything in new cache entry */ + PROJCache->PROJSRSCache[cache_position].srid_from = srid_from; + PROJCache->PROJSRSCache[cache_position].srid_to = srid_to; + PROJCache->PROJSRSCache[cache_position].projection = projection; + PROJCache->PROJSRSCache[cache_position].hits = hits; + + MemoryContextSwitchTo(oldContext); + return projection; +} + +static void +DeleteFromPROJSRSCache(PROJSRSCache *PROJCache, uint32_t position) +{ + POSTGIS_DEBUGF(3, + "removing query cache entry with SRIDs %d => %d at index %u", + PROJCache->PROJSRSCache[position].srid_from, + PROJCache->PROJSRSCache[position].srid_to, + position); + + /* Call PROJSRSDestroyPJ to free the PROJ objects memory now instead of + * waiting for the parent memory context to exit */ + PROJSRSDestroyPJ(PROJCache->PROJSRSCache[position].projection); + + PROJCache->PROJSRSCache[position].projection = NULL; + PROJCache->PROJSRSCache[position].srid_from = SRID_UNKNOWN; + PROJCache->PROJSRSCache[position].srid_to = SRID_UNKNOWN; +} + + +int +lwproj_lookup(int32_t srid_from, int32_t srid_to, LWPROJ **pj) +{ + /* get or initialize the cache for this round */ + PROJSRSCache* proj_cache = GetPROJSRSCache(); + if (!proj_cache) + return LW_FAILURE; + + postgis_initialize_cache(); + /* Add the output srid to the cache if it's not already there */ + *pj = GetProjectionFromPROJCache(proj_cache, srid_from, srid_to); + if (*pj == NULL) + { + *pj = AddToPROJSRSCache(proj_cache, srid_from, srid_to); + } + + return pj != NULL; +} + +int +lwproj_is_latlong(const LWPROJ *pj) +{ + return pj->source_is_latlong; +} + +static int +srid_is_latlong(int32_t srid) +{ + LWPROJ *pj; + if ( lwproj_lookup(srid, srid, &pj) == LW_FAILURE) + return LW_FALSE; + return lwproj_is_latlong(pj); +} + +void +srid_check_latlong(int32_t srid) +{ + if (srid == SRID_DEFAULT || srid == SRID_UNKNOWN) + return; + + if (srid_is_latlong(srid)) + return; + + ereport(ERROR, ( + errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Only lon/lat coordinate systems are supported in geography."))); +} + +srs_precision +srid_axis_precision(int32_t srid, int precision) +{ + srs_precision sp; + sp.precision_xy = precision; + sp.precision_z = precision; + sp.precision_m = precision; + + if ( srid == SRID_UNKNOWN ) + return sp; + + if ( srid_is_latlong(srid) ) + { + sp.precision_xy += 5; + return sp; + } + + return sp; +} + +int +spheroid_init_from_srid(int32_t srid, SPHEROID *s) +{ + LWPROJ *pj; + + if ( lwproj_lookup(srid, srid, &pj) == LW_FAILURE) + return LW_FAILURE; + + if (!pj->source_is_latlong) + return LW_FAILURE; + spheroid_init(s, pj->source_semi_major_metre, pj->source_semi_minor_metre); + + return LW_SUCCESS; +} diff --git a/mgist-postgis/libpgcommon/lwgeom_transform.h b/mgist-postgis/libpgcommon/lwgeom_transform.h new file mode 100644 index 0000000..51e71de --- /dev/null +++ b/mgist-postgis/libpgcommon/lwgeom_transform.h @@ -0,0 +1,116 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * Copyright 2001-2003 Refractions Research Inc. + * + * This is free software; you can redistribute and/or modify it under + * the terms of the GNU General Public Licence. See the COPYING file. + * + **********************************************************************/ + +#include "postgres.h" +#include "lwgeom_log.h" +#include "liblwgeom.h" +#include "lwgeom_pg.h" + + +/* +* Proj4 caching has it's own mechanism, and is +* stored globally as the cost of proj_create_crs_to_crs() +* is so high (20-40ms) that the lifetime of fcinfo->flinfo->fn_extra +* is too short to assist some work loads. +*/ + +/* An entry in the PROJ SRS cache */ +typedef struct struct_PROJSRSCacheItem +{ + int32_t srid_from; + int32_t srid_to; + uint64_t hits; + LWPROJ *projection; +} +PROJSRSCacheItem; + +/* PROJ 4 lookup transaction cache methods */ +#define PROJ_CACHE_ITEMS 128 + +/* +* The proj4 cache holds a fixed number of reprojection +* entries. In normal usage we don't expect it to have +* many entries, so we always linearly scan the list. +*/ +typedef struct struct_PROJSRSCache +{ + PROJSRSCacheItem PROJSRSCache[PROJ_CACHE_ITEMS]; + uint32_t PROJSRSCacheCount; + MemoryContext PROJSRSCacheContext; +} +PROJSRSCache; + + +typedef struct srs_precision +{ + int precision_xy; + int precision_z; + int precision_m; +} srs_precision; + +#if POSTGIS_PROJ_VERSION < 61 +/* Needs to call postgis_initialize_cache first */ +char *GetProj4String(int32_t srid); +#endif + + +/* Prototypes */ +PROJSRSCache* GetPROJSRSCache(); +int lwproj_lookup(int32_t srid_from, int32_t srid_to, LWPROJ **pj); +int lwproj_is_latlong(const LWPROJ *pj); +int spheroid_init_from_srid(int32_t srid, SPHEROID *s); +void srid_check_latlong(int32_t srid); +srs_precision srid_axis_precision(int32_t srid, int precision); + +/** + * Builtin SRID values + * @{ + */ + +/** Start of the reserved offset */ +#define SRID_RESERVE_OFFSET 999000 + +/** World Mercator, equivalent to EPSG:3395 */ +#define SRID_WORLD_MERCATOR 999000 + +/** Start of UTM North zone, equivalent to EPSG:32601 */ +#define SRID_NORTH_UTM_START 999001 + +/** End of UTM North zone, equivalent to EPSG:32660 */ +#define SRID_NORTH_UTM_END 999060 + +/** Lambert Azimuthal Equal Area (LAEA) North Pole, equivalent to EPSG:3574 */ +#define SRID_NORTH_LAMBERT 999061 + +/** PolarSteregraphic North, equivalent to EPSG:3995 */ +#define SRID_NORTH_STEREO 999062 + +/** Start of UTM South zone, equivalent to EPSG:32701 */ +#define SRID_SOUTH_UTM_START 999101 + +/** Start of UTM South zone, equivalent to EPSG:32760 */ +#define SRID_SOUTH_UTM_END 999160 + +/** Lambert Azimuthal Equal Area (LAEA) South Pole, equivalent to EPSG:3409 */ +#define SRID_SOUTH_LAMBERT 999161 + +/** PolarSteregraphic South, equivalent to EPSG:3031 */ +#define SRID_SOUTH_STEREO 999162 + +/** LAEA zones start (6 latitude bands x up to 20 longitude bands) */ +#define SRID_LAEA_START 999163 + +/** LAEA zones end (6 latitude bands x up to 20 longitude bands) */ +#define SRID_LAEA_END 999283 + + +/** @} */ + diff --git a/mgist-postgis/libpgcommon/pgsql_compat.h b/mgist-postgis/libpgcommon/pgsql_compat.h new file mode 100644 index 0000000..0760e3c --- /dev/null +++ b/mgist-postgis/libpgcommon/pgsql_compat.h @@ -0,0 +1,11 @@ +#ifndef _PGSQL_COMPAT_H +#define _PGSQL_COMPAT_H 1 + +#include "access/tupdesc.h" + +/* TupleDescAttr was backported into 9.6.5 but we support any 9.6.X */ +#ifndef TupleDescAttr +#define TupleDescAttr(tupdesc, i) ((tupdesc)->attrs[(i)]) +#endif + +#endif /* _PGSQL_COMPAT_H */ diff --git a/mgist-postgis/libpgcommon/shared_gserialized.c b/mgist-postgis/libpgcommon/shared_gserialized.c new file mode 100644 index 0000000..8513c5a --- /dev/null +++ b/mgist-postgis/libpgcommon/shared_gserialized.c @@ -0,0 +1,108 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * PostGIS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * PostGIS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PostGIS. If not, see . + * + ********************************************************************** + * + * Copyright 2020 Raúl Marín + * + **********************************************************************/ + +#include "lwgeom_cache.h" + +SHARED_GSERIALIZED * +shared_gserialized_new_nocache(Datum d) +{ + SHARED_GSERIALIZED *s = palloc(sizeof(SHARED_GSERIALIZED)); + s->count = 0; + s->geom = (GSERIALIZED *)PG_DETOAST_DATUM(d); + return s; +} + +SHARED_GSERIALIZED * +shared_gserialized_new_cached(FunctionCallInfo fcinfo, Datum d) +{ + SHARED_GSERIALIZED *s = MemoryContextAlloc(PostgisCacheContext(fcinfo), sizeof(SHARED_GSERIALIZED)); + MemoryContext old_context = MemoryContextSwitchTo(PostgisCacheContext(fcinfo)); + s->geom = (GSERIALIZED *)PG_DETOAST_DATUM_COPY(d); + MemoryContextSwitchTo(old_context); + s->count = 1; + return s; +} + +SHARED_GSERIALIZED * +shared_gserialized_ref(FunctionCallInfo fcinfo, SHARED_GSERIALIZED *ref) +{ +#if POSTGIS_PGSQL_VERSION >= 160 +/** MemoryContextContains was removed in PG16 **/ + if ( PostgisCacheContext(fcinfo) == GetMemoryChunkContext(ref) ) +#else + if (MemoryContextContains(PostgisCacheContext(fcinfo), ref)) +#endif + { + ref->count++; + return ref; + } + else + { + SHARED_GSERIALIZED *sg = MemoryContextAlloc(PostgisCacheContext(fcinfo), sizeof(SHARED_GSERIALIZED)); + sg->count = 1; + sg->geom = MemoryContextAlloc(PostgisCacheContext(fcinfo), VARSIZE(ref->geom)); + memcpy(sg->geom, ref->geom, VARSIZE(ref->geom)); + return sg; + } +} + +void +shared_gserialized_unref(FunctionCallInfo fcinfo, SHARED_GSERIALIZED *ref) +{ +#if POSTGIS_PGSQL_VERSION >= 160 +/** MemoryContextContains was removed in PG16 **/ + if ( PostgisCacheContext(fcinfo) == GetMemoryChunkContext(ref) ) +#else + if (MemoryContextContains(PostgisCacheContext(fcinfo), ref)) +#endif + { + ref->count--; + if (!ref->count) + { + pfree(ref->geom); + pfree(ref); + } + } + else + { + pfree(ref->geom); + pfree(ref); + } +} + +bool +shared_gserialized_equal(SHARED_GSERIALIZED *r1, SHARED_GSERIALIZED *r2) +{ + if (r1->geom == r2->geom) + return true; + if (VARSIZE(r1->geom) != VARSIZE(r2->geom)) + return false; + return memcmp(r1->geom, r2->geom, VARSIZE(r1->geom)) == 0; +} + +const GSERIALIZED * +shared_gserialized_get(SHARED_GSERIALIZED *s) +{ + return s->geom; +} diff --git a/mgist-postgis/libpgcommon/sql/AddToSearchPath.sql.inc b/mgist-postgis/libpgcommon/sql/AddToSearchPath.sql.inc new file mode 100644 index 0000000..a5e0227 --- /dev/null +++ b/mgist-postgis/libpgcommon/sql/AddToSearchPath.sql.inc @@ -0,0 +1,69 @@ +/* + + Common body for a function to add a schema to the search path + Use as follows: + + CREATE OR REPLACE FUNCTION someName(someARg text) + #include "libpgcommon/sql/AddToSearchPath.sql.inc" +*/ +RETURNS text +AS +$BODY$ +DECLARE + var_result text; + var_cur_search_path text; + a_schema_name text := $1; +BEGIN + WITH settings AS ( + SELECT unnest(setconfig) config + FROM pg_db_role_setting + WHERE setdatabase = ( + SELECT oid + FROM pg_database + WHERE datname = current_database() + ) and setrole = 0 + ) + SELECT regexp_replace(config, '^search_path=', '') + FROM settings WHERE config like 'search_path=%' + INTO var_cur_search_path; + + RAISE NOTICE 'cur_search_path from pg_db_role_setting is %', var_cur_search_path; + + -- only run this test if person creating the extension is a super user + IF var_cur_search_path IS NULL AND (SELECT rolsuper FROM pg_roles where rolname = CURRENT_USER) THEN + SELECT setting + INTO var_cur_search_path + FROM pg_file_settings + WHERE name = 'search_path' AND applied; + + RAISE NOTICE 'cur_search_path from pg_file_settings is %', var_cur_search_path; + END IF; + + IF var_cur_search_path IS NULL THEN + SELECT boot_val + INTO var_cur_search_path + FROM pg_settings + WHERE name = 'search_path'; + + RAISE NOTICE 'cur_search_path from pg_settings is %', var_cur_search_path; + END IF; + + IF var_cur_search_path LIKE '%' || quote_ident(a_schema_name) || '%' THEN + var_result := a_schema_name || ' already in database search_path'; + ELSE + var_cur_search_path := var_cur_search_path || ', ' + || quote_ident(a_schema_name); + EXECUTE 'ALTER DATABASE ' || quote_ident(current_database()) + || ' SET search_path = ' || var_cur_search_path; + var_result := a_schema_name || ' has been added to end of database search_path '; + END IF; + + EXECUTE 'SET search_path = ' || var_cur_search_path; + + RETURN var_result; +END +$BODY$ +SET search_path = pg_catalog -- make safe +LANGUAGE 'plpgsql' VOLATILE STRICT +; + diff --git a/mgist-postgis/mgist_postgis--1.0.sql b/mgist-postgis/mgist_postgis--1.0.sql new file mode 100755 index 0000000..7ddbb32 --- /dev/null +++ b/mgist-postgis/mgist_postgis--1.0.sql @@ -0,0 +1,31 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION mgist_postgis" to load this file. \quit + +/****************************************************************************** + * Multi Entry R-Tree for geometry types using ME-GiST + ******************************************************************************/ + +CREATE FUNCTION mgist_geometry_extract(internal, internal, internal) + RETURNS internal + AS 'MODULE_PATHNAME', 'mgist_geometry_extract' + LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; + +CREATE OPERATOR CLASS mgist_geometry_ops_2d + DEFAULT FOR TYPE geometry USING mgist AS + STORAGE box2df, + -- overlaps + OPERATOR 3 &&, + -- nearest approach distance + OPERATOR 13 <-> FOR ORDER BY pg_catalog.float_ops, + -- functions + FUNCTION 8 geometry_gist_distance_2d (internal, geometry, integer), + FUNCTION 1 geometry_gist_consistent_2d (internal, geometry, integer), + FUNCTION 2 geometry_gist_union_2d (bytea, internal), + FUNCTION 3 geometry_gist_compress_2d (internal), + FUNCTION 4 geometry_gist_decompress_2d (internal), + FUNCTION 5 geometry_gist_penalty_2d (internal, internal, internal), + FUNCTION 6 geometry_gist_picksplit_2d (internal, internal), + FUNCTION 7 geometry_gist_same_2d (geom1 geometry, geom2 geometry, internal), + FUNCTION 12 mgist_geometry_extract(internal, internal, internal); + +/******************************************************************************/ \ No newline at end of file diff --git a/mgist-postgis/mgist_postgis.c b/mgist-postgis/mgist_postgis.c new file mode 100755 index 0000000..d3a72d4 --- /dev/null +++ b/mgist-postgis/mgist_postgis.c @@ -0,0 +1,66 @@ +/* + * mgist_postgis.C + * + * Multi Entry R-Tree for geometry types using Multi-Entry GiST + * + * Author: Maxime Schoemans + */ + +#include +#include + +#include "postgres.h" +#include "fmgr.h" +#include "access/gist.h" +#include "access/reloptions.h" +#include "utils/array.h" +#include "utils/date.h" +#include "utils/float.h" +#include "utils/timestamp.h" + +#include "liblwgeom.h" + +PG_MODULE_MAGIC; + +#define PG_GETARG_GSERIALIZED_P(varno) ((GSERIALIZED *)PG_DETOAST_DATUM(PG_GETARG_DATUM(varno))) + +/***************************************************************************** + * ME-GiST extract methods + *****************************************************************************/ + +PG_FUNCTION_INFO_V1(mgist_geometry_extract); +Datum +mgist_geometry_extract(PG_FUNCTION_ARGS) +{ + GSERIALIZED *gs = PG_GETARG_GSERIALIZED_P(0); + int32 *nkeys = (int32 *) PG_GETARG_POINTER(1); + // bool **nullFlags = (bool **) PG_GETARG_POINTER(2); + + uint32_t gstype = gserialized_get_type(gs); + if (! lwtype_is_collection(gstype)) + { + *nkeys = 1; + PG_RETURN_POINTER(gs); + } + + LWGEOM *lwgeom = lwgeom_from_gserialized(gs); + LWCOLLECTION *lwcoll = lwcollection_extract((LWCOLLECTION *) lwgeom, 0); + + *nkeys = lwcoll->ngeoms; + Datum *keys = palloc(sizeof(Datum) * lwcoll->ngeoms); + for (int i = 0; i < lwcoll->ngeoms; ++i) + { + size_t size; + GSERIALIZED *g = gserialized_from_lwgeom(lwcoll->geoms[i], &size); + SET_VARSIZE(g, size); + keys[i] = PointerGetDatum(g); + } + + lwgeom_free(lwgeom); + lwcollection_free(lwcoll); + + PG_FREE_IF_COPY(gs, 0); + PG_RETURN_POINTER(keys); +} + +/*****************************************************************************/ \ No newline at end of file diff --git a/mgist-postgis/mgist_postgis.control b/mgist-postgis/mgist_postgis.control new file mode 100755 index 0000000..1f66f9c --- /dev/null +++ b/mgist-postgis/mgist_postgis.control @@ -0,0 +1,5 @@ +# mgist_postgis extension +comment = 'mgist_postgis - Multi Entry R-Tree for geometry types using Multi-Entry GiST' +default_version = '1.0' +module_pathname = '$libdir/libmgist_postgis' +requires = 'postgis, mgist' diff --git a/mgist-postgis/ryu/.clang-format b/mgist-postgis/ryu/.clang-format new file mode 100644 index 0000000..e384528 --- /dev/null +++ b/mgist-postgis/ryu/.clang-format @@ -0,0 +1 @@ +DisableFormat: true diff --git a/mgist-postgis/ryu/CMakeLists.txt b/mgist-postgis/ryu/CMakeLists.txt new file mode 100644 index 0000000..77dd884 --- /dev/null +++ b/mgist-postgis/ryu/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library(ryu OBJECT + d2s.c + ) + +set_property(TARGET ryu PROPERTY C_VISIBILITY_PRESET hidden) +set_property(TARGET ryu PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/mgist-postgis/ryu/LICENSE b/mgist-postgis/ryu/LICENSE new file mode 100644 index 0000000..d64a467 --- /dev/null +++ b/mgist-postgis/ryu/LICENSE @@ -0,0 +1 @@ +All the C code is copyrighted by Ulf Adams and contributors, and may be used freely in accordance with the Apache 2.0 license. Alternatively, it may be used freely in accordance with the Boost 1.0 license. \ No newline at end of file diff --git a/mgist-postgis/ryu/LICENSE-Apache2 b/mgist-postgis/ryu/LICENSE-Apache2 new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/mgist-postgis/ryu/LICENSE-Apache2 @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/mgist-postgis/ryu/LICENSE-Boost b/mgist-postgis/ryu/LICENSE-Boost new file mode 100644 index 0000000..36b7cd9 --- /dev/null +++ b/mgist-postgis/ryu/LICENSE-Boost @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/mgist-postgis/ryu/Makefile.in b/mgist-postgis/ryu/Makefile.in new file mode 100644 index 0000000..6bbd547 --- /dev/null +++ b/mgist-postgis/ryu/Makefile.in @@ -0,0 +1,63 @@ +#/********************************************************************** +# * +# * PostGIS - Spatial Types for PostgreSQL +# * http://postgis.net +# * +# * PostGIS is free software: you can redistribute it and/or modify +# * it under the terms of the GNU General Public License as published by +# * the Free Software Foundation, either version 2 of the License, or +# * (at your option) any later version. +# * +# * PostGIS is distributed in the hope that it will be useful, +# * but WITHOUT ANY WARRANTY; without even the implied warranty of +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# * GNU General Public License for more details. +# * +# * You should have received a copy of the GNU General Public License +# * along with PostGIS. If not, see . +# * +# ********************************************************************** +# * +# * Copyright 2019 Raúl Marín +# * Copyright 2022 Sandro Santilli +# * +# **********************************************************************/ + +top_builddir = @top_builddir@ +srcdir = @srcdir@ + +CC=@CC@ +CFLAGS = -I$(srcdir)/.. @CPPFLAGS@ @CFLAGS@ @PICFLAGS@ -DRYU_NO_TRAILING_ZEROS +SHELL = @SHELL@ +INSTALL = @INSTALL@ +LIBTOOL = @LIBTOOL@ + +VPATH = $(srcdir) + +RYU_OBJS = d2s.o +LT_RYU_OBJS = $(RYU_OBJS:.o=.lo) + +all: @RYU_LIB@ + +@RYU_LIB@: $(LT_RYU_OBJS) + $(LIBTOOL) --tag=CC --mode=link $(CC) $(LT_RYU_OBJS) -static -o $@ + + +$(LT_RYU_OBJS): %.lo: %.c + $(LIBTOOL) --mode=compile $(CC) $(CFLAGS) -c -o $@ $< + +clean: + rm -f @RYU_LIB@ $(RYU_OBJS) $(LT_RYU_OBJS) + rm -rf .libs + +distclean: clean + rm -f Makefile + + +install: + +uninstall: + +check: + +.PHONY: clean distclean install uninstall check diff --git a/mgist-postgis/ryu/README.md b/mgist-postgis/ryu/README.md new file mode 100644 index 0000000..25a0bb9 --- /dev/null +++ b/mgist-postgis/ryu/README.md @@ -0,0 +1,43 @@ +# Ryū + +Ryu generates the shortest decimal representation of a floating point number that maintains round-trip safety. That is, a correct parser can recover the exact original number. For example, consider the binary 64-bit floating point number 00111110100110011001100110011010. The stored value is exactly 0.300000011920928955078125. However, this floating point number is also the closest number to the decimal number 0.3, so that is what Ryu outputs. + +This problem of generating the shortest possible representation was originally posed by White and Steele [1], for which they described an algorithm called "Dragon". It was subsequently improved upon with algorithms that also had dragon-themed names. I followed in the same vein using the japanese word for dragon, Ryu. In general, all these algorithms should produce identical output given identical input, and this is checked when running the benchmark program. + +## Ryū Printf + +Since Ryu generates the shortest decimal representation, it is not immediately suitable for use in languages that have printf-like facilities. In most implementations, printf provides three floating-point specific formatters, %f, %e, and %g: + +The %f format prints the full decimal part of the given floating point number, and then appends as many digits of the fractional part as specified using the precision parameter. + +The %e format prints the decimal number in scientific notation with as many digits after the initial digit as specified using the precision parameter. + +The %g format prints either %f or %e format, whichever is shorter. + +Ryu Printf implements %f and %e formatting in a way that should be drop-in compatible with most implementations of printf, although it currently does not implement any formatting flags other than precision. + +## Implementation + +The C implementation of Ryu comes from the ryu/ directory in the git repostitory (https://github.com/ulfjack/ryu). + +We've only copied the necessary files, that is `d2s.c` and its headers. It is integrated in the project by generating a static library, libryu.la. + + +### Main considerations about the library + +The library has one single function to connect to liblwgeom, lwprint_double, which matches the behaviour as closely as possible to the previous output, based on printf. We only use 2 functions from ryu: + + - d2sfixed_buffered_n: Prints a double with decimal notation. + - d2sexp_buffered_n: Prints a double with exponential notation. + +Both functions have been heavily modified to support a new precision parameter that limits the amount of digits that are included after the floating point. This precision parameter affects both the scientific and fixed notation and respects proper rounding (round to nearest, ties to even). + +The output has also been changed to match the old behaviour: +- Uses "e+1" instead of E1. +- Uses "e-1" instead of E-1. +- Never outputs negative 0. It always returns "0". + +### Dependency changelog + + - 2019-01-10 - [Ryu] Library extraction from https://github.com/ulfjack/ryu/tree/master/ryu. Added changes to remove trailing zeros from the output and minor changes to please ubsan. + - 2020-07-20 - [Ryu] Switch from d2fixed/d2exp to d2sfixed/d2sexp, that is, using the shortest notation diff --git a/mgist-postgis/ryu/common.h b/mgist-postgis/ryu/common.h new file mode 100644 index 0000000..e89ff98 --- /dev/null +++ b/mgist-postgis/ryu/common.h @@ -0,0 +1,106 @@ +// Copyright 2018 Ulf Adams +// +// The contents of this file may be used under the terms of the Apache License, +// Version 2.0. +// +// (See accompanying file LICENSE-Apache or copy at +// http://www.apache.org/licenses/LICENSE-2.0) +// +// Alternatively, the contents of this file may be used under the terms of +// the Boost Software License, Version 1.0. +// (See accompanying file LICENSE-Boost or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Unless required by applicable law or agreed to in writing, this software +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. +#ifndef RYU_COMMON_H +#define RYU_COMMON_H + +#include +#include +#include + +#if defined(_M_IX86) || defined(_M_ARM) +#define RYU_32_BIT_PLATFORM +#endif + +// Returns the number of decimal digits in v, which must not contain more than 9 digits. +static inline uint32_t decimalLength9(const uint32_t v) { + // Function precondition: v is not a 10-digit number. + // (f2s: 9 digits are sufficient for round-tripping.) + // (d2fixed: We print 9-digit blocks.) + assert(v >= 0); + assert(v < 1000000000); + if (v >= 100000000) { return 9; } + if (v >= 10000000) { return 8; } + if (v >= 1000000) { return 7; } + if (v >= 100000) { return 6; } + if (v >= 10000) { return 5; } + if (v >= 1000) { return 4; } + if (v >= 100) { return 3; } + if (v >= 10) { return 2; } + return 1; +} + +// Returns e == 0 ? 1 : ceil(log_2(5^e)); requires 0 <= e <= 3528. +static inline int32_t pow5bits(const int32_t e) { + // This approximation works up to the point that the multiplication overflows at e = 3529. + // If the multiplication were done in 64 bits, it would fail at 5^4004 which is just greater + // than 2^9297. + assert(e >= 0); + assert(e <= 3528); + return (int32_t) (((((uint32_t) e) * 1217359) >> 19) + 1); +} + +// Returns e == 0 ? 1 : ceil(log_2(5^e)); requires 0 <= e <= 3528. +static inline int32_t ceil_log2pow5(const int32_t e) { + return pow5bits(e); +} + +// Returns floor(log_10(2^e)); requires 0 <= e <= 1650. +static inline uint32_t log10Pow2(const int32_t e) { + // The first value this approximation fails for is 2^1651 which is just greater than 10^297. + assert(e >= 0); + assert(e <= 1650); + return (((uint32_t) e) * 78913) >> 18; +} + +// Returns floor(log_10(5^e)); requires 0 <= e <= 2620. +static inline uint32_t log10Pow5(const int32_t e) { + // The first value this approximation fails for is 5^2621 which is just greater than 10^1832. + assert(e >= 0); + assert(e <= 2620); + return (((uint32_t) e) * 732923) >> 20; +} + +static inline int copy_special_str(char * const result, const bool sign, const bool exponent, const bool mantissa) { + if (mantissa) { + memcpy(result, "NaN", 3); + return 3; + } + if (exponent) { + /* PostGIS: Do not print signed zero */ + if (sign) { + result[0] = '-'; + } + memcpy(result + sign, "Infinity", 8); + return sign + 8; + } + memcpy(result, "0", 1); + return 1; +} + +static inline uint32_t float_to_bits(const float f) { + uint32_t bits = 0; + memcpy(&bits, &f, sizeof(float)); + return bits; +} + +static inline uint64_t double_to_bits(const double d) { + uint64_t bits = 0; + memcpy(&bits, &d, sizeof(double)); + return bits; +} + +#endif // RYU_COMMON_H diff --git a/mgist-postgis/ryu/d2fixed_full_table.h b/mgist-postgis/ryu/d2fixed_full_table.h new file mode 100644 index 0000000..7085765 --- /dev/null +++ b/mgist-postgis/ryu/d2fixed_full_table.h @@ -0,0 +1,4420 @@ +// Copyright 2018 Ulf Adams +// +// The contents of this file may be used under the terms of the Apache License, +// Version 2.0. +// +// (See accompanying file LICENSE-Apache or copy at +// http://www.apache.org/licenses/LICENSE-2.0) +// +// Alternatively, the contents of this file may be used under the terms of +// the Boost Software License, Version 1.0. +// (See accompanying file LICENSE-Boost or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Unless required by applicable law or agreed to in writing, this software +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. +#ifndef RYU_D2FIXED_FULL_TABLE_H +#define RYU_D2FIXED_FULL_TABLE_H + +#include + +#define TABLE_SIZE 64 + +static const uint16_t POW10_OFFSET[TABLE_SIZE] = { + 0, 2, 5, 8, 12, 16, 21, 26, 32, 39, + 46, 54, 62, 71, 80, 90, 100, 111, 122, 134, + 146, 159, 173, 187, 202, 217, 233, 249, 266, 283, + 301, 319, 338, 357, 377, 397, 418, 440, 462, 485, + 508, 532, 556, 581, 606, 632, 658, 685, 712, 740, + 769, 798, 828, 858, 889, 920, 952, 984, 1017, 1050, + 1084, 1118, 1153, 1188 +}; + +static const uint64_t POW10_SPLIT[1224][3] = { + { 1u, 72057594037927936u, 0u }, + { 699646928636035157u, 72057594u, 0u }, + { 1u, 0u, 256u }, + { 11902091922964236229u, 4722366482869u, 0u }, + { 6760415703743915872u, 4722u, 0u }, + { 1u, 0u, 16777216u }, + { 13369850649504950658u, 309485009821345068u, 0u }, + { 15151142278969419334u, 309485009u, 0u }, + { 1u, 0u, 75511627776u }, + { 4635408826454083567u, 9437866644873197963u, 1099u }, + { 12367138975830625353u, 20282409603651u, 0u }, + { 7555853734021184432u, 20282u, 0u }, + { 1u, 0u, 250037927936u }, + { 5171444645924616995u, 699646928636035156u, 72057594u }, + { 16672297533003297786u, 1329227995784915872u, 0u }, + { 14479142226848862515u, 1329227995u, 0u }, + { 1u, 0u, 181645213696u }, + { 12214193123817091081u, 11902091922964236228u, 114366482869u }, + { 16592893013671929435u, 6760415703743915871u, 4722u }, + { 4549827147718617003u, 87112285931760u, 0u }, + { 5274510003815168971u, 87112u, 0u }, + { 1u, 0u, 44724781056u }, + { 9794971998307800535u, 13369850649504950657u, 209821345068u }, + { 14720142899209240169u, 15151142278969419333u, 309485009u }, + { 4300745446091561535u, 5708990770823839524u, 0u }, + { 15197156861335443364u, 5708990770u, 0u }, + { 1u, 0u, 139251286016u }, + { 13484604155038683037u, 4635408826454083566u, 67670423947u }, + { 8356963862052375699u, 12367138975830625352u, 58409603651u }, + { 5850852848337610021u, 7555853734021184431u, 20282u }, + { 2712780827214982050u, 374144419156711u, 0u }, + { 7732076577307618052u, 374144u, 0u }, + { 1u, 0u, 84280344576u }, + { 17296309485351745867u, 5171444645924616994u, 160903807060u }, + { 16598859101615853088u, 16672297533003297785u, 219784915872u }, + { 7469952526870444257u, 14479142226848862514u, 1329227995u }, + { 13531654022114669524u, 6073184580144670117u, 1u }, + { 15757609704383306943u, 24519928653u, 0u }, + { 9590990814237149590u, 24u, 0u }, + { 1u, 0u, 196662132736u }, + { 15408590707489433890u, 12214193123817091080u, 95899502532u }, + { 18332056844289122710u, 16592893013671929434u, 240246646623u }, + { 11114572877353986193u, 4549827147718617002u, 72285931760u }, + { 1703393793997526525u, 5274510003815168970u, 87112u }, + { 5082852056285196265u, 1606938044258990u, 0u }, + { 816434266573722365u, 1606938u, 0u }, + { 1u, 0u, 129530986496u }, + { 5736523019264798742u, 9794971998307800534u, 69797980545u }, + { 10129314776268243339u, 14720142899209240168u, 36233143877u }, + { 16511595775483995364u, 4300745446091561534u, 50823839524u }, + { 12367293405401453325u, 15197156861335443363u, 5708990770u }, + { 16934621733248854291u, 13078571300009428617u, 5u }, + { 10278280417769171336u, 105312291668u, 0u }, + { 5760764486226151240u, 105u, 0u }, + { 1u, 0u, 238731001856u }, + { 4128368337188369761u, 13484604155038683036u, 72453031918u }, + { 10240941003671005056u, 8356963862052375698u, 175317175368u }, + { 17933378316822368251u, 5850852848337610020u, 231147060143u }, + { 8346249813075698616u, 2712780827214982049u, 128419156711u }, + { 15906203609160902695u, 7732076577307618051u, 374144u }, + { 14525607416135386328u, 6901746346790563u, 0u }, + { 6397156777364256320u, 6901746u, 0u }, + { 1u, 0u, 34937634816u }, + { 16798760952716600048u, 17296309485351745866u, 249899825954u }, + { 2419982808370854967u, 16598859101615853087u, 50404946937u }, + { 2922947087773078956u, 7469952526870444256u, 165733552434u }, + { 15419220167069510190u, 13531654022114669523u, 77854221733u }, + { 3452124642157173416u, 15757609704383306942u, 24519928653u }, + { 5979700067267186899u, 9590990814237149589u, 24u }, + { 4913998146922579597u, 452312848583u, 0u }, + { 5771037749337678924u, 452u, 0u }, + { 1u, 0u, 8835301376u }, + { 3464734175350698519u, 15408590707489433889u, 90993782792u }, + { 9334527711335850125u, 18332056844289122709u, 170602522202u }, + { 7269882896518450106u, 11114572877353986192u, 202092341162u }, + { 1372511258182263196u, 1703393793997526524u, 174275541962u }, + { 7571228438575951046u, 5082852056285196264u, 26044258990u }, + { 2992506536646070406u, 816434266573722364u, 1606938u }, + { 524517896824344606u, 29642774844752946u, 0u }, + { 15582941400898702773u, 29642774u, 0u }, + { 1u, 0u, 214310977536u }, + { 3846112492507251066u, 5736523019264798741u, 104549111254u }, + { 16681117750123089487u, 10129314776268243338u, 62895095400u }, + { 14986314536556547267u, 16511595775483995363u, 163670432318u }, + { 2573712825027107389u, 12367293405401453324u, 137918027683u }, + { 7504855874008324928u, 16934621733248854290u, 84557186697u }, + { 9572138030626879787u, 10278280417769171335u, 105312291668u }, + { 8520676959353394843u, 5760764486226151239u, 105u }, + { 13448984662897903496u, 1942668892225u, 0u }, + { 12338883700918130648u, 1942u, 0u }, + { 1u, 0u, 156223799296u }, + { 2517285787892561600u, 4128368337188369760u, 146555162524u }, + { 4338831817635138103u, 10240941003671005055u, 36972170386u }, + { 1561495325934523196u, 17933378316822368250u, 161452451108u }, + { 12262635050079398786u, 8346249813075698615u, 3862277025u }, + { 11144065765517284188u, 15906203609160902694u, 163787434755u }, + { 1212260522471875711u, 14525607416135386327u, 242346790563u }, + { 9695352922247418869u, 6397156777364256319u, 6901746u }, + { 7227025834627242948u, 127314748520905380u, 0u }, + { 9609008238705447829u, 127314748u, 0u }, + { 1u, 0u, 74910662656u }, + { 3609144142396852269u, 16798760952716600047u, 31131187530u }, + { 11568848377382068865u, 2419982808370854966u, 224158453279u }, + { 10068303578029323957u, 2922947087773078955u, 211835877600u }, + { 11645070846862630231u, 15419220167069510189u, 190187140051u }, + { 12449386705878485055u, 3452124642157173415u, 149324160190u }, + { 15025619323517318418u, 5979700067267186898u, 199266388373u }, + { 14996237555047131272u, 4913998146922579596u, 196312848583u }, + { 10211005638256058413u, 5771037749337678923u, 452u }, + { 1014743503555840530u, 8343699359066u, 0u }, + { 12900897707145290678u, 8343u, 0u }, + { 1u, 0u, 33187823616u }, + { 4718003016239473662u, 3464734175350698518u, 149506025761u }, + { 14865830648693666725u, 9334527711335850124u, 144394101141u }, + { 14754517212823091778u, 7269882896518450105u, 252074403984u }, + { 11113946551474911901u, 1372511258182263195u, 232410437116u }, + { 1963520352638130630u, 7571228438575951045u, 252162224104u }, + { 13342587341404964200u, 2992506536646070405u, 50028434172u }, + { 6240392545013573291u, 524517896824344605u, 22844752946u }, + { 14377490861349714758u, 15582941400898702772u, 29642774u }, + { 1717863312631397839u, 546812681195752981u, 0u }, + { 3611005143890591770u, 546812681u, 0u }, + { 1u, 0u, 21208498176u }, + { 13168252824351245504u, 3846112492507251065u, 138904285205u }, + { 735883891883379688u, 16681117750123089486u, 227812409738u }, + { 10609203866866106404u, 14986314536556547266u, 12139521251u }, + { 12358191111890306470u, 2573712825027107388u, 18406839052u }, + { 15229916368406413528u, 7504855874008324927u, 135518906642u }, + { 7241424335568075942u, 9572138030626879786u, 71461906823u }, + { 6049715868779871913u, 8520676959353394842u, 65729070919u }, + { 2000548404719336762u, 13448984662897903495u, 150668892225u }, + { 1410974761895205301u, 12338883700918130647u, 1942u }, + { 16000132467694084868u, 35835915874844u, 0u }, + { 16894908866816792556u, 35835u, 0u }, + { 1u, 0u, 96136462336u }, + { 589096329272056762u, 2517285787892561599u, 127235208544u }, + { 7097729792403256904u, 4338831817635138102u, 250084648831u }, + { 8553736750439287020u, 1561495325934523195u, 183664758778u }, + { 2114152625261065696u, 12262635050079398785u, 38604121015u }, + { 9817523680007641224u, 11144065765517284187u, 215065716774u }, + { 13047215537500048015u, 1212260522471875710u, 63525586135u }, + { 16755544192002345880u, 9695352922247418868u, 164391777855u }, + { 6930119832670648356u, 7227025834627242947u, 60520905380u }, + { 14560698131901886167u, 9609008238705447828u, 127314748u }, + { 16408020927503338035u, 2348542582773833227u, 0u }, + { 14274703510609809116u, 2348542582u, 0u }, + { 1u, 0u, 239195652096u }, + { 16428432973129962470u, 3609144142396852268u, 54627148527u }, + { 3721112279790863774u, 11568848377382068864u, 171545803830u }, + { 18032764903259620753u, 10068303578029323956u, 45631280555u }, + { 18058455550468776079u, 11645070846862630230u, 167674882605u }, + { 15692090139033993190u, 12449386705878485054u, 210814540455u }, + { 389416944300619393u, 15025619323517318417u, 140812947666u }, + { 12009691357260487293u, 14996237555047131271u, 75553539724u }, + { 13494259174449809900u, 10211005638256058412u, 90055009355u }, + { 18288583400616279877u, 1014743503555840529u, 151699359066u }, + { 7216107869057472u, 12900897707145290677u, 8343u }, + { 17237061291959073878u, 153914086704665u, 0u }, + { 1599418782488783273u, 153914u, 0u }, + { 1u, 0u, 22255763456u }, + { 9565464987240335777u, 4718003016239473661u, 140805878294u }, + { 857713933775880687u, 14865830648693666724u, 185799843980u }, + { 4621617820081363356u, 14754517212823091777u, 155602488249u }, + { 9630162611715632528u, 11113946551474911900u, 197106442651u }, + { 9283986497984645815u, 1963520352638130629u, 133723303109u }, + { 8981807745082630996u, 13342587341404964199u, 29338292357u }, + { 18350140531565934622u, 6240392545013573290u, 180779405341u }, + { 4411619033127524143u, 14377490861349714757u, 21093125556u }, + { 1852297584111266889u, 1717863312631397838u, 9195752981u }, + { 11746243463811666096u, 3611005143890591769u, 546812681u }, + { 6335244004343789147u, 10086913586276986678u, 0u }, + { 5109502367228239844u, 10086913586u, 0u }, + { 1603272682579847821u, 10u, 0u }, + { 1u, 0u, 121713852416u }, + { 6609546910952910052u, 13168252824351245503u, 78039892345u }, + { 3911171343112928288u, 735883891883379687u, 194575126094u }, + { 5254510615100863555u, 10609203866866106403u, 60669938882u }, + { 3881927570803887650u, 12358191111890306469u, 63825615420u }, + { 6379348759607163190u, 15229916368406413527u, 42392558399u }, + { 14595733737222406466u, 7241424335568075941u, 154327955754u }, + { 14670223432002373542u, 6049715868779871912u, 135108449946u }, + { 4045087795619708513u, 2000548404719336761u, 215076489095u }, + { 12598467307137142718u, 1410974761895205300u, 28867368919u }, + { 734704388050777108u, 16000132467694084867u, 251915874844u }, + { 5682201693687285822u, 16894908866816792555u, 35835u }, + { 11048712694145438788u, 661055968790248u, 0u }, + { 17871025777010319485u, 661055u, 0u }, + { 1u, 0u, 191031934976u }, + { 15268761435931663695u, 589096329272056761u, 54384768703u }, + { 5016238054648555438u, 7097729792403256903u, 59463698998u }, + { 14236047313993899750u, 8553736750439287019u, 129114608443u }, + { 6957759675154690848u, 2114152625261065695u, 91532209025u }, + { 18439367135478514473u, 9817523680007641223u, 126707290971u }, + { 8539004472540641041u, 13047215537500048014u, 244908319870u }, + { 1908462039431738399u, 16755544192002345879u, 195375682548u }, + { 714690453250792146u, 6930119832670648355u, 148789337027u }, + { 13782189447673929633u, 14560698131901886166u, 11889480596u }, + { 3584742913798803164u, 16408020927503338034u, 118773833227u }, + { 4347581515245125291u, 14274703510609809115u, 2348542582u }, + { 16836742268156371392u, 6429475823218628948u, 2u }, + { 11764082328865615308u, 43322963970u, 0u }, + { 5957633711383291746u, 43u, 0u }, + { 1u, 0u, 44890587136u }, + { 9917186842884466953u, 16428432973129962469u, 128201721900u }, + { 4751011869809829335u, 3721112279790863773u, 180977558144u }, + { 11068497969931435029u, 18032764903259620752u, 86978950836u }, + { 17118056985122509954u, 18058455550468776078u, 62850669910u }, + { 14607066080907684459u, 15692090139033993189u, 17021110334u }, + { 11768892370493391107u, 389416944300619392u, 135651046673u }, + { 4043396447647747170u, 12009691357260487292u, 44731525255u }, + { 1670341095362518057u, 13494259174449809899u, 17991426092u }, + { 3190817644167043165u, 18288583400616279876u, 181000391185u }, + { 10425820027224322486u, 7216107869057471u, 25934422965u }, + { 13139964660506311565u, 17237061291959073877u, 58086704665u }, + { 2297772885416059937u, 1599418782488783272u, 153914u }, + { 7677687919964523763u, 2839213766779714u, 0u }, + { 14144589152747892828u, 2839213u, 0u }, + { 1u, 0u, 253518544896u }, + { 17069730341503660290u, 9565464987240335776u, 164046496765u }, + { 18167423787163077107u, 857713933775880686u, 65250538404u }, + { 3765746945827805904u, 4621617820081363355u, 156522052161u }, + { 10241734342430761691u, 9630162611715632527u, 197503285916u }, + { 13345717282537140784u, 9283986497984645814u, 103486904773u }, + { 9313926784816939953u, 8981807745082630995u, 170994763111u }, + { 550974205049535019u, 18350140531565934621u, 69239154346u }, + { 4494692285504086222u, 4411619033127524142u, 206100413253u }, + { 1134308559863725587u, 1852297584111266888u, 25636765134u }, + { 17587558045116130233u, 11746243463811666095u, 54343434265u }, + { 9817142032346161594u, 6335244004343789146u, 50276986678u }, + { 6071944935834172568u, 5109502367228239843u, 10086913586u }, + { 11564168293299416955u, 1603272682579847820u, 10u }, + { 12458266507226064437u, 186070713419u, 0u }, + { 1304432355328256915u, 186u, 0u }, + { 1u, 0u, 191358304256u }, + { 15946798815542087355u, 6609546910952910051u, 231212025023u }, + { 12082566083831286138u, 3911171343112928287u, 35284847591u }, + { 11449623684706196411u, 5254510615100863554u, 165210439715u }, + { 17518743620362604446u, 3881927570803887649u, 215345825189u }, + { 9451061563087633805u, 6379348759607163189u, 165791236311u }, + { 13191114787623314926u, 14595733737222406465u, 168795274405u }, + { 8367349876734474799u, 14670223432002373541u, 57219284648u }, + { 6544253801674393507u, 4045087795619708512u, 180682964281u }, + { 16113906253336597498u, 12598467307137142717u, 3039828404u }, + { 10294087136797312392u, 734704388050777107u, 235308032771u }, + { 9127173070014462803u, 5682201693687285821u, 232598951915u }, + { 16266900839595484952u, 11048712694145438787u, 63968790248u }, + { 3299745387370952632u, 17871025777010319484u, 661055u }, + { 12061115182604399189u, 12194330274671844u, 0u }, + { 5066801222582989646u, 12194330u, 0u }, + { 1u, 0u, 185827721216u }, + { 7568423425299591513u, 15268761435931663694u, 71271930809u }, + { 16561505984665207377u, 5016238054648555437u, 235771737671u }, + { 4329114621856906245u, 14236047313993899749u, 223377180907u }, + { 1477500474861899139u, 6957759675154690847u, 135999600095u }, + { 16891579639263969684u, 18439367135478514472u, 142462900359u }, + { 4684451357140027420u, 8539004472540641040u, 151103457934u }, + { 14727186580409080709u, 1908462039431738398u, 35038743447u }, + { 15864176859687308834u, 714690453250792145u, 214747133987u }, + { 1755486942842684438u, 13782189447673929632u, 50194329302u }, + { 17417077516652710041u, 3584742913798803163u, 219235682866u }, + { 4290982361913532783u, 4347581515245125290u, 84912721627u }, + { 11826659981004351409u, 16836742268156371391u, 2637732180u }, + { 932930645678090820u, 11764082328865615307u, 43322963970u }, + { 12707792781328052617u, 5957633711383291745u, 43u }, + { 16491596426880311906u, 799167628880u, 0u }, + { 3092207065214166010u, 799u, 0u }, + { 1u, 0u, 229537611776u }, + { 8142946531605512550u, 9917186842884466952u, 157257552869u }, + { 5328402096432654515u, 4751011869809829334u, 144600024477u }, + { 1932004361303814512u, 11068497969931435028u, 142927971728u }, + { 2511477647985517771u, 17118056985122509953u, 229791850638u }, + { 17451375493324716694u, 14607066080907684458u, 128637992933u }, + { 9489266854478998489u, 11768892370493391106u, 124219192960u }, + { 8803053132063235169u, 4043396447647747169u, 235090549372u }, + { 16198682197142616773u, 1670341095362518056u, 68172974571u }, + { 13696242485403414202u, 3190817644167043164u, 191565184836u }, + { 16409082426079859931u, 10425820027224322485u, 85712318911u }, + { 11653410736879597610u, 13139964660506311564u, 168124562517u }, + { 13589514120653213261u, 2297772885416059936u, 66416208296u }, + { 8032934885905905774u, 7677687919964523762u, 173766779714u }, + { 2753021350129449273u, 14144589152747892827u, 2839213u }, + { 16974897459201404133u, 52374249726338269u, 0u }, + { 13398576176159101589u, 52374249u, 0u }, + { 1u, 0u, 160925351936u }, + { 10284586955251725351u, 17069730341503660289u, 238984858016u }, + { 5294476488634150891u, 18167423787163077106u, 155204141550u }, + { 15833244538135063323u, 3765746945827805903u, 143555205531u }, + { 10348512742273116664u, 10241734342430761690u, 182723472783u }, + { 13658504610142595663u, 13345717282537140783u, 83504908982u }, + { 11956362239240850266u, 9313926784816939952u, 29029868371u }, + { 13415901703662731781u, 550974205049535018u, 46243657757u }, + { 5161774027546852762u, 4494692285504086221u, 72061490990u }, + { 15274384838790587711u, 1134308559863725586u, 175953423432u }, + { 14233354597679374929u, 17587558045116130232u, 90532188335u }, + { 4274656492162486921u, 9817142032346161593u, 227329160794u }, + { 12040276505541795046u, 6071944935834172567u, 140626894819u }, + { 13238307206256765457u, 11564168293299416954u, 75675363980u }, + { 12850161204172713271u, 12458266507226064436u, 186070713419u }, + { 17531777095001445154u, 1304432355328256914u, 186u }, + { 5623628114515245990u, 3432398830065u, 0u }, + { 7357116143579573377u, 3432u, 0u }, + { 1u, 0u, 227864477696u }, + { 3555734177475596582u, 15946798815542087354u, 31654997219u }, + { 14001876724756424382u, 12082566083831286137u, 66620685343u }, + { 18159905057231476140u, 11449623684706196410u, 33949692994u }, + { 5585207679308509467u, 17518743620362604445u, 53512343073u }, + { 13948697622866724672u, 9451061563087633804u, 65715091765u }, + { 9807691927739036432u, 13191114787623314925u, 165453594945u }, + { 15818010096140820918u, 8367349876734474798u, 96354764709u }, + { 5629845624785010943u, 6544253801674393506u, 189873536608u }, + { 9517635131137734707u, 16113906253336597497u, 19558043581u }, + { 619338244618780585u, 10294087136797312391u, 61494785043u }, + { 11632367007491958899u, 9127173070014462802u, 67881830461u }, + { 12083314261009739916u, 16266900839595484951u, 124178879555u }, + { 16880538609458881650u, 3299745387370952631u, 228653834364u }, + { 17404223674486504228u, 12061115182604399188u, 26274671844u }, + { 7089067015287185433u, 5066801222582989645u, 12194330u }, + { 2592264228029443648u, 224945689727159819u, 0u }, + { 13413731084370224440u, 224945689u, 0u }, + { 1u, 0u, 78410285056u }, + { 9323915941641553425u, 7568423425299591512u, 173897801038u }, + { 12155831029092699564u, 16561505984665207376u, 229234681773u }, + { 17397171276588232676u, 4329114621856906244u, 31080095461u }, + { 11874560617553253769u, 1477500474861899138u, 40915694367u }, + { 13444839516837727954u, 16891579639263969683u, 16253944616u }, + { 16994416043584590671u, 4684451357140027419u, 30798362384u }, + { 15879694502877015730u, 14727186580409080708u, 209859998750u }, + { 4234647645735263359u, 15864176859687308833u, 160095165137u }, + { 7978589901512919496u, 1755486942842684437u, 219944181664u }, + { 6114237175390859894u, 17417077516652710040u, 170232614619u }, + { 8658612872088282708u, 4290982361913532782u, 191641124522u }, + { 10253813330683324853u, 11826659981004351408u, 203050574271u }, + { 13289465061747830991u, 932930645678090819u, 97688890827u }, + { 4123165538545565412u, 12707792781328052616u, 80894011233u }, + { 7846417485927038481u, 16491596426880311905u, 31167628880u }, + { 10562273346358018864u, 3092207065214166009u, 799u }, + { 2691512658346619120u, 14742040721959u, 0u }, + { 751187558544605998u, 14742u, 0u }, + { 1u, 0u, 8441430016u }, + { 3757709791947931308u, 8142946531605512549u, 214288853256u }, + { 3452755398462519465u, 5328402096432654514u, 20104734166u }, + { 3105818720159874523u, 1932004361303814511u, 129136147476u }, + { 16859138458894499364u, 2511477647985517770u, 106946040961u }, + { 12271894740606233755u, 17451375493324716693u, 2514414186u }, + { 5429638071845793701u, 9489266854478998488u, 97477214466u }, + { 145278150038876889u, 8803053132063235168u, 40878132321u }, + { 9050266019724932450u, 16198682197142616772u, 92742474792u }, + { 11907016253451490866u, 13696242485403414201u, 181889538140u }, + { 2472757296513770735u, 16409082426079859930u, 140631732661u }, + { 10558733798178239360u, 11653410736879597609u, 32736689036u }, + { 15917322570831255850u, 13589514120653213260u, 242435466272u }, + { 12254334656791355238u, 8032934885905905773u, 91149241586u }, + { 7869542424662730262u, 2753021350129449272u, 221920211035u }, + { 1378558986933000253u, 16974897459201404132u, 233726338269u }, + { 13521405041909411105u, 13398576176159101588u, 52374249u }, + { 3206744593298092012u, 966134380754314586u, 0u }, + { 13914648122214918505u, 966134380u, 0u }, + { 1u, 0u, 1557528576u }, + { 1235541077112082496u, 10284586955251725350u, 242287014145u }, + { 12014985518315533846u, 5294476488634150890u, 207858321906u }, + { 1561535086344155741u, 15833244538135063322u, 218560993999u }, + { 12761747276316224577u, 10348512742273116663u, 47740429018u }, + { 9745594781103966137u, 13658504610142595662u, 176648155695u }, + { 17514238702394846785u, 11956362239240850265u, 42727277488u }, + { 2428898913707151713u, 13415901703662731780u, 205279820330u }, + { 71666709959904945u, 5161774027546852761u, 18828026061u }, + { 4049380135452919193u, 15274384838790587710u, 184771591698u }, + { 18422240861777453733u, 14233354597679374928u, 185231729592u }, + { 2914504416394425696u, 4274656492162486920u, 151652704697u }, + { 12721377795748989418u, 12040276505541795045u, 122717650071u }, + { 2626074459217717422u, 13238307206256765456u, 52696608634u }, + { 4261529925046307655u, 12850161204172713270u, 146950399540u }, + { 11536038685430305586u, 17531777095001445153u, 241304857490u }, + { 12555757789435162768u, 5623628114515245989u, 104398830065u }, + { 11905178684546080059u, 7357116143579573376u, 3432u }, + { 14032797718924543051u, 63316582777114u, 0u }, + { 10750340288005853484u, 63316u, 0u }, + { 1u, 0u, 186192756736u }, + { 9660290106216358253u, 3555734177475596581u, 121759043258u }, + { 14820142034615351103u, 14001876724756424381u, 186984450425u }, + { 12674041783707777619u, 18159905057231476139u, 157302774714u }, + { 15386686816442679994u, 5585207679308509466u, 140756160413u }, + { 5679510383719146248u, 13948697622866724671u, 237531676044u }, + { 1391101719248678506u, 9807691927739036431u, 46857496045u }, + { 3364596672173710517u, 15818010096140820917u, 162305194542u }, + { 11276509210104319732u, 5629845624785010942u, 249515952034u }, + { 5316312656902630164u, 9517635131137734706u, 135033574393u }, + { 17470981304473644647u, 619338244618780584u, 82630591879u }, + { 7373293636384920591u, 11632367007491958898u, 23655037778u }, + { 7616810902585191937u, 12083314261009739915u, 183915095831u }, + { 12740295655921903924u, 16880538609458881649u, 84943484855u }, + { 18366635945916526940u, 17404223674486504227u, 77384299092u }, + { 4472171448243407067u, 7089067015287185432u, 11140526925u }, + { 229592460858185629u, 2592264228029443647u, 25727159819u }, + { 12749672866417114996u, 13413731084370224439u, 224945689u }, + { 9452256722867098693u, 4149515568880992958u, 0u }, + { 16251451636418604634u, 4149515568u, 0u }, + { 1u, 0u, 88505450496u }, + { 4515791283442995454u, 9323915941641553424u, 80658968920u }, + { 13306155670047701346u, 12155831029092699563u, 4943102544u }, + { 4456930152933417601u, 17397171276588232675u, 130643721220u }, + { 9089157128546489637u, 11874560617553253768u, 147728846210u }, + { 12437332180345515840u, 13444839516837727953u, 27921269139u }, + { 3433060408790452524u, 16994416043584590670u, 132860839963u }, + { 8275594526021936172u, 15879694502877015729u, 33229560708u }, + { 3846512444641107689u, 4234647645735263358u, 21432520225u }, + { 6210962618469046250u, 7978589901512919495u, 152331453461u }, + { 7272858906616296575u, 6114237175390859893u, 110469384344u }, + { 3710743300451225347u, 8658612872088282707u, 176555860334u }, + { 6424677242672030600u, 10253813330683324852u, 67720423344u }, + { 11485842256170301862u, 13289465061747830990u, 136223517251u }, + { 7355797963557024308u, 4123165538545565411u, 97425355144u }, + { 6358188982569427273u, 7846417485927038480u, 249572581985u }, + { 12475094728768767402u, 10562273346358018863u, 39145907193u }, + { 17288154837907896183u, 2691512658346619119u, 150040721959u }, + { 2983850577727105262u, 751187558544605997u, 14742u }, + { 13918604635001185935u, 271942652322184u, 0u }, + { 12033220395769876327u, 271942u, 0u }, + { 1u, 0u, 101203705856u }, + { 5782377197813462997u, 3757709791947931307u, 178187174245u }, + { 17732139848231399226u, 3452755398462519464u, 111168366770u }, + { 3628839527415562921u, 3105818720159874522u, 202913935727u }, + { 3188692267613601004u, 16859138458894499363u, 149665260746u }, + { 5168130193478377352u, 12271894740606233754u, 216294341269u }, + { 12556227529405091290u, 5429638071845793700u, 96007875544u }, + { 15087090312791441192u, 145278150038876888u, 196490615904u }, + { 10281804758610642494u, 9050266019724932449u, 185645480644u }, + { 14238177586158586580u, 11907016253451490865u, 218134048441u }, + { 7107927498217678128u, 2472757296513770734u, 41572390106u }, + { 3845814658485364450u, 10558733798178239359u, 76862879785u }, + { 714293333681725946u, 15917322570831255849u, 109664308812u }, + { 16766172658649116982u, 12254334656791355237u, 56426608749u }, + { 812461421432632215u, 7869542424662730261u, 228074731832u }, + { 15218024718633799196u, 1378558986933000252u, 148732996836u }, + { 8110797782612805146u, 13521405041909411104u, 90173837972u }, + { 15941193964933529227u, 3206744593298092011u, 108754314586u }, + { 14144280602323277933u, 13914648122214918504u, 966134380u }, + { 15072402647813125245u, 17822033662586700072u, 0u }, + { 10822706091283369889u, 17822033662u, 0u }, + { 15163844593710966731u, 17u, 0u }, + { 1u, 0u, 38066978816u }, + { 2408529687792073670u, 1235541077112082495u, 234651333670u }, + { 3980682212356510808u, 12014985518315533845u, 26084650986u }, + { 4202670442792148519u, 1561535086344155740u, 247691815706u }, + { 9419583343154651922u, 12761747276316224576u, 78528309751u }, + { 16359166491570434575u, 9745594781103966136u, 89949448782u }, + { 12567727056384237385u, 17514238702394846784u, 4131670873u }, + { 2068388267923286639u, 2428898913707151712u, 153003885060u }, + { 5689135844565021196u, 71666709959904944u, 62219517337u }, + { 3104061965171139313u, 4049380135452919192u, 80998671678u }, + { 7955173880156328016u, 18422240861777453732u, 136157995600u }, + { 1445179403240833754u, 2914504416394425695u, 229689627272u }, + { 12538201164459126715u, 12721377795748989417u, 16142359781u }, + { 7580606719088482667u, 2626074459217717421u, 54231018000u }, + { 8168318283218819755u, 4261529925046307654u, 33625369910u }, + { 5249615277755961676u, 11536038685430305585u, 165680648993u }, + { 6312997372068219831u, 12555757789435162767u, 128645381029u }, + { 9183815417025176703u, 11905178684546080058u, 26760719488u }, + { 10683849953373876937u, 14032797718924543050u, 84582777114u }, + { 17175012155615667568u, 10750340288005853483u, 63316u }, + { 18003508288378896912u, 1167984798111281u, 0u }, + { 14722554560950996951u, 1167984u, 0u }, + { 1u, 0u, 37523685376u }, + { 15059324482416394930u, 9660290106216358252u, 189803401509u }, + { 4134778595813308312u, 14820142034615351102u, 171687061181u }, + { 16321118342639660948u, 12674041783707777618u, 26834113963u }, + { 1523550293123468805u, 15386686816442679993u, 63307886874u }, + { 8016371634569878509u, 5679510383719146247u, 15075411775u }, + { 9884220139611134110u, 1391101719248678505u, 181182395151u }, + { 7218073002727840414u, 3364596672173710516u, 254611300789u }, + { 16062235669481359233u, 11276509210104319731u, 50288197886u }, + { 15558048660560338002u, 5316312656902630163u, 168947103794u }, + { 8394398745765058609u, 17470981304473644646u, 114399707048u }, + { 5693296366442904274u, 7373293636384920590u, 139412908146u }, + { 11783494675061161358u, 7616810902585191936u, 113690652811u }, + { 13377293110865447894u, 12740295655921903923u, 35995657329u }, + { 12840734051093062130u, 18366635945916526939u, 24242436899u }, + { 7009868331566697505u, 4472171448243407066u, 63012446232u }, + { 5019690705031194477u, 229592460858185628u, 55691161151u }, + { 8608277240439804984u, 12749672866417114995u, 190512407863u }, + { 12172482590657749222u, 9452256722867098692u, 48880992958u }, + { 16613484892678771990u, 16251451636418604633u, 4149515568u }, + { 5721488662757049244u, 2758075434182769113u, 4u }, + { 386931106438877039u, 76545051729u, 0u }, + { 10054429752182825659u, 76u, 0u }, + { 1u, 0u, 16244801536u }, + { 8634592106137071313u, 4515791283442995453u, 171721328144u }, + { 12626356501369830731u, 13306155670047701345u, 227241610667u }, + { 4803333258178976933u, 4456930152933417600u, 136492724195u }, + { 13613083223558209297u, 9089157128546489636u, 209674229128u }, + { 16106967997237446989u, 12437332180345515839u, 78186106577u }, + { 14832921244380020170u, 3433060408790452523u, 177448620878u }, + { 13774024637717231397u, 8275594526021936171u, 126208519857u }, + { 9673012968505228885u, 3846512444641107688u, 199336696958u }, + { 5391832334264815667u, 6210962618469046249u, 117394262471u }, + { 16514436292632703088u, 7272858906616296574u, 83201159797u }, + { 12025036352783454153u, 3710743300451225346u, 180348282451u }, + { 7059867105311401050u, 6424677242672030599u, 206622648756u }, + { 12769210631552594670u, 11485842256170301861u, 227398758606u }, + { 8328873878884556145u, 7355797963557024307u, 16344678115u }, + { 1016565892414238685u, 6358188982569427272u, 47676276240u }, + { 9662978461927250281u, 12475094728768767401u, 239937192751u }, + { 13729967277551868112u, 17288154837907896182u, 45161754863u }, + { 6371593776693359475u, 2983850577727105261u, 136754529069u }, + { 17617208110845643245u, 13918604635001185934u, 70652322184u }, + { 14960960225633086797u, 12033220395769876326u, 271942u }, + { 12090634301321662558u, 5016456510113118u, 0u }, + { 9409926148478635503u, 5016456u, 0u }, + { 1u, 0u, 171313463296u }, + { 4307062684900157136u, 5782377197813462996u, 168961261227u }, + { 15300759383869911853u, 17732139848231399225u, 218196719784u }, + { 16007534237643445447u, 3628839527415562920u, 35172859354u }, + { 7138502295759677634u, 3188692267613601003u, 154280164899u }, + { 8218537071653683708u, 5168130193478377351u, 164680674458u }, + { 2254219416760329296u, 12556227529405091289u, 216817872804u }, + { 3057410459568460683u, 15087090312791441191u, 97557377752u }, + { 8217810929938874370u, 10281804758610642493u, 49771853153u }, + { 11741126472498340929u, 14238177586158586579u, 238385321521u }, + { 1175325363726654805u, 7107927498217678127u, 127208482030u }, + { 9428843070696730900u, 3845814658485364449u, 41038721919u }, + { 12662500978715131896u, 714293333681725945u, 101908896041u }, + { 6443045597035184564u, 16766172658649116981u, 21044043621u }, + { 1921385512639171183u, 812461421432632214u, 60824970773u }, + { 10469475094355551399u, 15218024718633799195u, 32439687228u }, + { 14679174489076953574u, 8110797782612805145u, 235864173856u }, + { 11853074234719825644u, 15941193964933529226u, 104766762987u }, + { 8270896886596139124u, 14144280602323277932u, 40817076584u }, + { 16532667046659118126u, 15072402647813125244u, 254586700072u }, + { 148341279888833483u, 10822706091283369888u, 17822033662u }, + { 10364629296397276041u, 15163844593710966730u, 17u }, + { 14265682585545771671u, 328758493846u, 0u }, + { 13991741872911347878u, 328u, 0u }, + { 1u, 0u, 63130566656u }, + { 14029045786848724433u, 2408529687792073669u, 21215793215u }, + { 4005878521026842341u, 3980682212356510807u, 92227827221u }, + { 3428326338640386488u, 4202670442792148518u, 64510636636u }, + { 1010001558294829380u, 9419583343154651921u, 184886832192u }, + { 2012063724327403418u, 16359166491570434574u, 64681297848u }, + { 10997154538851372612u, 12567727056384237384u, 96112127552u }, + { 1917749645489607898u, 2068388267923286638u, 176308408672u }, + { 9763872523711218805u, 5689135844565021195u, 152168271536u }, + { 15875699078454059311u, 3104061965171139312u, 164431250840u }, + { 10966529452671276106u, 7955173880156328015u, 95078343332u }, + { 18073244132105736913u, 1445179403240833753u, 233679697247u }, + { 4435241176994913173u, 12538201164459126714u, 173410945513u }, + { 5464400086219074323u, 7580606719088482666u, 70442805421u }, + { 2445909179323258812u, 8168318283218819754u, 49284582214u }, + { 873962058644121211u, 5249615277755961675u, 143342228273u }, + { 16675872194112650857u, 6312997372068219830u, 58497855631u }, + { 10680102689274800355u, 9183815417025176702u, 74579172666u }, + { 2370498083108897524u, 10683849953373876936u, 43931059274u }, + { 15354400521451334666u, 17175012155615667567u, 49975972139u }, + { 259991949657381021u, 18003508288378896911u, 112798111281u }, + { 10335286558772966917u, 14722554560950996950u, 1167984u }, + { 16337526653906757263u, 21545516652742137u, 0u }, + { 12040967163702784894u, 21545516u, 0u }, + { 1u, 0u, 108816367616u }, + { 3373309160242342187u, 15059324482416394929u, 62224146796u }, + { 13639841054510584221u, 4134778595813308311u, 82884769598u }, + { 15898855427739708031u, 16321118342639660947u, 185082591826u }, + { 4544387940067005419u, 1523550293123468804u, 7434568377u }, + { 5281598644835398575u, 8016371634569878508u, 105535824647u }, + { 13675642405083408835u, 9884220139611134109u, 180391292521u }, + { 3973392623768015721u, 7218073002727840413u, 243870735540u }, + { 4491285101509114191u, 16062235669481359232u, 19843403507u }, + { 15002304272810270500u, 15558048660560338001u, 102455061267u }, + { 17325098540619893468u, 8394398745765058608u, 14308634214u }, + { 1137212864974584822u, 5693296366442904273u, 638784526u }, + { 2619406097224859078u, 11783494675061161357u, 51725184512u }, + { 8281347529729293732u, 13377293110865447893u, 91696097587u }, + { 11344719666795450104u, 12840734051093062129u, 218380005723u }, + { 17283870506679425783u, 7009868331566697504u, 156272117978u }, + { 11054210518010603775u, 5019690705031194476u, 115466655644u }, + { 6399455551799092885u, 8608277240439804983u, 68659871603u }, + { 12930529916573967170u, 12172482590657749221u, 89900618820u }, + { 14550097052337552404u, 16613484892678771989u, 217310162521u }, + { 12487632712206414748u, 5721488662757049243u, 81020975577u }, + { 5791017277843595715u, 386931106438877038u, 76545051729u }, + { 10227264183449036113u, 10054429752182825658u, 76u }, + { 2006055278511721441u, 1412006979354u, 0u }, + { 128746359043876333u, 1412u, 0u }, + { 1u, 0u, 253468082176u }, + { 7408146306870995754u, 8634592106137071312u, 97684476157u }, + { 8299024588195267962u, 12626356501369830730u, 128260389217u }, + { 1497052939192040881u, 4803333258178976932u, 36737966720u }, + { 16771714264265803747u, 13613083223558209296u, 63873160484u }, + { 142988846654429432u, 16106967997237446988u, 43804094271u }, + { 11839838367716104145u, 14832921244380020169u, 43746691371u }, + { 6019646776647679765u, 13774024637717231396u, 232524375083u }, + { 4611972391702034948u, 9673012968505228884u, 233292291816u }, + { 16447182322205429545u, 5391832334264815666u, 126895249385u }, + { 2113477168726764245u, 16514436292632703087u, 2651878526u }, + { 3536261187802311516u, 12025036352783454152u, 135382716162u }, + { 18444381860986709854u, 7059867105311401049u, 165692220295u }, + { 4734315730275909838u, 12769210631552594669u, 51451509157u }, + { 9974936316849658174u, 8328873878884556144u, 72055108147u }, + { 11864423681540657642u, 1016565892414238684u, 169523831112u }, + { 8207245621417902667u, 9662978461927250280u, 118744303017u }, + { 7992526918695295028u, 13729967277551868111u, 237345404790u }, + { 8679354522130259987u, 6371593776693359474u, 142955030765u }, + { 6065763799692166461u, 17617208110845643244u, 102811035278u }, + { 18143341109049024976u, 14960960225633086796u, 94655434598u }, + { 15242492331283350570u, 12090634301321662557u, 136510113118u }, + { 9986352353182266963u, 9409926148478635502u, 5016456u }, + { 17340463289911536077u, 92537289398950870u, 0u }, + { 7359344614214233035u, 92537289u, 0u }, + { 1u, 0u, 212233486336u }, + { 419091135888749535u, 4307062684900157135u, 57829455828u }, + { 1073142712661309790u, 15300759383869911852u, 168867770169u }, + { 11076438902195672286u, 16007534237643445446u, 235386978984u }, + { 1820390940081322073u, 7138502295759677633u, 135445527787u }, + { 18417808973944523597u, 8218537071653683707u, 217122201479u }, + { 10251294197731582957u, 2254219416760329295u, 39165742553u }, + { 1502394029870156428u, 3057410459568460682u, 61445488423u }, + { 321014853559106075u, 8217810929938874369u, 211636487741u }, + { 2390953058510591778u, 11741126472498340928u, 47063714515u }, + { 10685224265907994087u, 1175325363726654804u, 225511138607u }, + { 5967405799190505023u, 9428843070696730899u, 249686435553u }, + { 11210723659228214761u, 12662500978715131895u, 53349278201u }, + { 12327123641078462773u, 6443045597035184563u, 150104158517u }, + { 1709976940107894237u, 1921385512639171182u, 27567551382u }, + { 16607686590938553511u, 10469475094355551398u, 25795759643u }, + { 18332088094272679457u, 14679174489076953573u, 138642556441u }, + { 2946170632136780882u, 11853074234719825643u, 108448366218u }, + { 4824449494694383419u, 8270896886596139123u, 124896237676u }, + { 17008332258693407134u, 16532667046659118125u, 160008041596u }, + { 1773419466622750661u, 148341279888833482u, 202561867680u }, + { 3892343466023784379u, 10364629296397276040u, 150773344202u }, + { 12001571085575422796u, 14265682585545771670u, 72758493846u }, + { 12933506765500977582u, 13991741872911347877u, 328u }, + { 11884830007749143734u, 6064523798049u, 0u }, + { 9662368568096205337u, 6064u, 0u }, + { 1u, 0u, 197760516096u }, + { 16801499925276664442u, 14029045786848724432u, 87217159109u }, + { 10492407990787637084u, 4005878521026842340u, 38185849943u }, + { 7673849751013230269u, 3428326338640386487u, 17054752294u }, + { 6046724489853072367u, 1010001558294829379u, 14109074193u }, + { 3723941391207507903u, 2012063724327403417u, 72596156942u }, + { 16844122108860347659u, 10997154538851372611u, 110103961416u }, + { 10622020182694668027u, 1917749645489607897u, 11529300590u }, + { 8741198820686854862u, 9763872523711218804u, 240860623371u }, + { 6855480461211306807u, 15875699078454059310u, 79594496752u }, + { 10005708458011566304u, 10966529452671276105u, 217979752527u }, + { 8932093106442919061u, 18073244132105736912u, 186240434905u }, + { 9062763476260756743u, 4435241176994913172u, 106296225722u }, + { 13664977682032775521u, 5464400086219074322u, 170132593002u }, + { 1078499125430623453u, 2445909179323258811u, 75047377578u }, + { 6554586738078431161u, 873962058644121210u, 182904000843u }, + { 12177313698643242883u, 16675872194112650856u, 126578969526u }, + { 16615072271904633953u, 10680102689274800354u, 200128504958u }, + { 16375404983106569285u, 2370498083108897523u, 111832363720u }, + { 13552251831473522729u, 15354400521451334665u, 15014094191u }, + { 8330500218412111874u, 259991949657381020u, 214560277007u }, + { 7044338079053294004u, 10335286558772966916u, 249885659094u }, + { 2688849443046530184u, 16337526653906757262u, 44652742137u }, + { 855940991879596845u, 12040967163702784893u, 21545516u }, + { 7344363609485825662u, 397444631628981487u, 0u }, + { 11602660525134634992u, 397444631u, 0u }, + { 1u, 0u, 177182867456u }, + { 16945343208344873835u, 3373309160242342186u, 151739417265u }, + { 9617992661337889145u, 13639841054510584220u, 147861878679u }, + { 18280344933262742088u, 15898855427739708030u, 4246351763u }, + { 5179975582362777795u, 4544387940067005418u, 236286316036u }, + { 1798918997870037130u, 5281598644835398574u, 157741358060u }, + { 6327667344756325883u, 13675642405083408834u, 157215398045u }, + { 18380327574124007701u, 3973392623768015720u, 128243473053u }, + { 18015447557304295289u, 4491285101509114190u, 81813276544u }, + { 10315590748073249878u, 15002304272810270499u, 48939195473u }, + { 7697916092577993382u, 17325098540619893467u, 209061648432u }, + { 3124132817942110723u, 1137212864974584821u, 141141998289u }, + { 7448238998520507049u, 2619406097224859077u, 213448932749u }, + { 13892823322374205297u, 8281347529729293731u, 241614998485u }, + { 11042137840046332564u, 11344719666795450103u, 32936960497u }, + { 10513966307445593804u, 17283870506679425782u, 108599249952u }, + { 9388437460943526958u, 11054210518010603774u, 55346915180u }, + { 10967228614677896228u, 6399455551799092884u, 229700965431u }, + { 2310996671540235542u, 12930529916573967169u, 21788762341u }, + { 4989110555003898587u, 14550097052337552403u, 155676955925u }, + { 16271452421983657679u, 12487632712206414747u, 110313931675u }, + { 9523160181437090473u, 5791017277843595714u, 186554421102u }, + { 13137707423765072250u, 10227264183449036112u, 26108748474u }, + { 16846859744221860705u, 2006055278511721440u, 132006979354u }, + { 7767140033449795569u, 128746359043876332u, 1412u }, + { 17169456915721160017u, 26046931378436u, 0u }, + { 17180899661833327819u, 26046u, 0u }, + { 1u, 0u, 208401596416u }, + { 17572520700934791416u, 7408146306870995753u, 74449891024u }, + { 17968798858233825417u, 8299024588195267961u, 164081155402u }, + { 15338423313945305609u, 1497052939192040880u, 16909196452u }, + { 17895321323836726301u, 16771714264265803746u, 76007751440u }, + { 814069333008965773u, 142988846654429431u, 201641838924u }, + { 7200328959852723947u, 11839838367716104144u, 36326325705u }, + { 759884557248133773u, 6019646776647679764u, 84250015524u }, + { 13410165861863974851u, 4611972391702034947u, 50891603540u }, + { 6278452420856351570u, 16447182322205429544u, 111114571826u }, + { 9072115382556676442u, 2113477168726764244u, 200191701103u }, + { 2755882551854926563u, 3536261187802311515u, 89999871944u }, + { 8496072611504649269u, 18444381860986709853u, 237256647769u }, + { 4122009033579215815u, 4734315730275909837u, 112540742381u }, + { 10222217724450527221u, 9974936316849658173u, 220643171696u }, + { 2064539481554006325u, 11864423681540657641u, 104444915676u }, + { 7935605886598063693u, 8207245621417902666u, 207433275752u }, + { 7805147585347548429u, 7992526918695295027u, 114470508751u }, + { 5709020905457661273u, 8679354522130259986u, 236328825714u }, + { 16257370307404906674u, 6065763799692166460u, 76983552492u }, + { 14971258192939373646u, 18143341109049024975u, 93826297164u }, + { 1133404845901376390u, 15242492331283350569u, 238541361245u }, + { 9460827548162822047u, 9986352353182266962u, 214940028398u }, + { 1273897659779791346u, 17340463289911536076u, 201398950870u }, + { 7833262224435092783u, 7359344614214233034u, 92537289u }, + { 3033420566713364587u, 1707011694817242694u, 0u }, + { 15075466825360349103u, 1707011694u, 0u }, + { 1u, 0u, 207022718976u }, + { 2484134775182816690u, 419091135888749534u, 44058175183u }, + { 18400539815335991277u, 1073142712661309789u, 198600454956u }, + { 485494064952118286u, 11076438902195672285u, 193098683590u }, + { 17577048805241314891u, 1820390940081322072u, 251998431425u }, + { 2863946907557583807u, 18417808973944523596u, 79555723771u }, + { 13045307417786230800u, 10251294197731582956u, 138081444943u }, + { 12032088871615097766u, 1502394029870156427u, 1017402250u }, + { 8848763446997690580u, 321014853559106074u, 64129613825u }, + { 10031289150307672684u, 2390953058510591777u, 84579247168u }, + { 11592215575498656563u, 10685224265907994086u, 19323493716u }, + { 15894436747956898388u, 5967405799190505022u, 247607734547u }, + { 2091546719588500923u, 11210723659228214760u, 179668254711u }, + { 5863809244813756109u, 12327123641078462772u, 110092698035u }, + { 11303008753675411245u, 1709976940107894236u, 166900304494u }, + { 13238426537506910532u, 16607686590938553510u, 229993784486u }, + { 17258458071023005565u, 18332088094272679456u, 235159712229u }, + { 8385733444777075179u, 2946170632136780881u, 115261533931u }, + { 9530757096163247300u, 4824449494694383418u, 45922023539u }, + { 14423000845391072217u, 17008332258693407133u, 202096137261u }, + { 10953140011159884311u, 1773419466622750660u, 136211004362u }, + { 12228340237948264127u, 3892343466023784378u, 150650606472u }, + { 11279134946966259189u, 12001571085575422795u, 165701126806u }, + { 14640097792684582651u, 12933506765500977581u, 33644277925u }, + { 6232313315128656728u, 11884830007749143733u, 176523798049u }, + { 16136121832933322088u, 9662368568096205336u, 6064u }, + { 15074767079673358271u, 111870718431542u, 0u }, + { 13252722804829281908u, 111870u, 0u }, + { 1u, 0u, 208910811136u }, + { 7740175894281560509u, 16801499925276664441u, 228568794576u }, + { 15670495392425593226u, 10492407990787637083u, 183416000228u }, + { 15152257626756992778u, 7673849751013230268u, 67327793591u }, + { 4090073428152440422u, 6046724489853072366u, 153201875267u }, + { 14450327772834205584u, 3723941391207507902u, 67913121689u }, + { 4466091895542494216u, 16844122108860347658u, 217575820867u }, + { 10454115378553795377u, 10622020182694668026u, 116473861337u }, + { 2267817233475657788u, 8741198820686854861u, 46371636340u }, + { 5500455702636497521u, 6855480461211306806u, 73542410542u }, + { 15178768299492252549u, 10005708458011566303u, 208484209737u }, + { 7062359872332045590u, 8932093106442919060u, 148491293392u }, + { 12297347290027942576u, 9062763476260756742u, 18740779924u }, + { 8030124596941085588u, 13664977682032775520u, 187058465554u }, + { 6526656990996654843u, 1078499125430623452u, 122355324859u }, + { 6254287345256979850u, 6554586738078431160u, 104660133498u }, + { 6642007136244870032u, 12177313698643242882u, 226900704872u }, + { 2027592955437164718u, 16615072271904633952u, 243887712482u }, + { 942718349157325567u, 16375404983106569284u, 9734669043u }, + { 14617066671884002278u, 13552251831473522728u, 156451597321u }, + { 6831631114396133348u, 8330500218412111873u, 4381874332u }, + { 14603040013386939258u, 7044338079053294003u, 142145762820u }, + { 9906106765319401103u, 2688849443046530183u, 125046400654u }, + { 1396179595609933063u, 855940991879596844u, 239398138749u }, + { 11524884268464976417u, 7344363609485825661u, 23628981487u }, + { 382929570730827274u, 11602660525134634991u, 397444631u }, + { 6109721884461301381u, 7331559403129590068u, 0u }, + { 2390514825000339691u, 7331559403u, 0u }, + { 6116191454763441755u, 7u, 0u }, + { 1u, 0u, 42918608896u }, + { 11598868771099176310u, 16945343208344873834u, 156521392426u }, + { 14449966445520085105u, 9617992661337889144u, 126990979484u }, + { 11675595287405614726u, 18280344933262742087u, 234280807038u }, + { 15860796398550489897u, 5179975582362777794u, 174097519594u }, + { 16180408435245829662u, 1798918997870037129u, 194343023534u }, + { 13756992797154950706u, 6327667344756325882u, 104996399554u }, + { 8830551328786758466u, 18380327574124007700u, 78976619368u }, + { 16699955256560951264u, 18015447557304295288u, 35559209294u }, + { 10038983627153402074u, 10315590748073249877u, 219417304867u }, + { 15085100736692127346u, 7697916092577993381u, 245169359579u }, + { 10007783780289711125u, 3124132817942110722u, 197403769845u }, + { 17596907048353602192u, 7448238998520507048u, 163753131461u }, + { 13530650344896573509u, 13892823322374205296u, 247598595491u }, + { 6337724853398437005u, 11042137840046332563u, 246569963255u }, + { 12768885008904063297u, 10513966307445593803u, 254508948214u }, + { 2759773619512884114u, 9388437460943526957u, 148594534654u }, + { 8434364600126655292u, 10967228614677896227u, 65125279380u }, + { 3843827521199949338u, 2310996671540235541u, 19270460225u }, + { 4661660852957808994u, 4989110555003898586u, 155882077203u }, + { 15298044134177324417u, 16271452421983657678u, 194516251547u }, + { 7747773274913338217u, 9523160181437090472u, 80712196546u }, + { 10348785912020632966u, 13137707423765072249u, 224913270096u }, + { 4175372293197190170u, 16846859744221860704u, 236421057504u }, + { 11326064156813083145u, 7767140033449795568u, 4930758124u }, + { 8100407170505981763u, 17169456915721160016u, 190931378436u }, + { 1706556116319916846u, 17180899661833327818u, 26046u }, + { 15028897280749641942u, 480481077043500u, 0u }, + { 1421201742071739121u, 480481u, 0u }, + { 1u, 0u, 41952608256u }, + { 8480737406125178272u, 17572520700934791415u, 121974090537u }, + { 10947205650755620361u, 17968798858233825416u, 176831497593u }, + { 868577942165647781u, 15338423313945305608u, 226970107312u }, + { 16017710019091388479u, 17895321323836726300u, 247044130786u }, + { 6610879150827623375u, 814069333008965772u, 208390330615u }, + { 12110095866223762092u, 7200328959852723946u, 20041193424u }, + { 7756802952949470775u, 759884557248133772u, 3726966548u }, + { 2941800790804618759u, 13410165861863974850u, 40340355587u }, + { 11703600274199927522u, 6278452420856351569u, 212491800360u }, + { 806737539257940346u, 9072115382556676441u, 91149396692u }, + { 14579028397110132023u, 2755882551854926562u, 93460573019u }, + { 14247808875344366934u, 8496072611504649268u, 205223454557u }, + { 9713379923695279513u, 4122009033579215814u, 61554147533u }, + { 2246428675703313877u, 10222217724450527220u, 233111918909u }, + { 3549783776592680620u, 2064539481554006324u, 74430190057u }, + { 12645029747929213033u, 7935605886598063692u, 51423117898u }, + { 16279009267476580506u, 7805147585347548428u, 18309486643u }, + { 343358782242907186u, 5709020905457661272u, 60881313810u }, + { 10077054739085890321u, 16257370307404906673u, 207811593532u }, + { 10526715404712173586u, 14971258192939373645u, 41061441999u }, + { 11438715865125144243u, 1133404845901376389u, 82512872489u }, + { 5040916178827294801u, 9460827548162822046u, 204069058130u }, + { 16643761637275849508u, 1273897659779791345u, 202424641996u }, + { 4852542977279030386u, 7833262224435092782u, 70164442058u }, + { 7883373066544387129u, 3033420566713364586u, 110817242694u }, + { 16699064314768500978u, 15075466825360349102u, 1707011694u }, + { 6805863634444817214u, 13042063791413317777u, 1u }, + { 2266540253968903500u, 31488807865u, 0u }, + { 9016913589137908810u, 31u, 0u }, + { 1u, 0u, 222134665216u }, + { 11654451024602552034u, 2484134775182816689u, 93997495262u }, + { 5299013208454526793u, 18400539815335991276u, 221026318685u }, + { 14918550373926182540u, 485494064952118285u, 88952853725u }, + { 6225552657491071054u, 17577048805241314890u, 76155254872u }, + { 10344713496596235785u, 2863946907557583806u, 236707187532u }, + { 12972405634433280209u, 13045307417786230799u, 139652260844u }, + { 12911885282402784945u, 12032088871615097765u, 26479692427u }, + { 6934311832970995868u, 8848763446997690579u, 33543797274u }, + { 9975729197003430461u, 10031289150307672683u, 230628415265u }, + { 1982857556803548935u, 11592215575498656562u, 62861639142u }, + { 2095735223386298223u, 15894436747956898387u, 232113382974u }, + { 7110931538347639365u, 2091546719588500922u, 52317877736u }, + { 15822183724630969535u, 5863809244813756108u, 220612737332u }, + { 16931982690156327501u, 11303008753675411244u, 166717656540u }, + { 6740069226761666110u, 13238426537506910531u, 32935582886u }, + { 3138792961008474902u, 17258458071023005564u, 81454591520u }, + { 12154594426971851390u, 8385733444777075178u, 58516663377u }, + { 15780127219221910902u, 9530757096163247299u, 157781872442u }, + { 16421541930960194381u, 14423000845391072216u, 196593770909u }, + { 7485894627196740576u, 10953140011159884310u, 186662899652u }, + { 8897269432694476707u, 12228340237948264126u, 75611443130u }, + { 17189823634941678805u, 11279134946966259188u, 173793641291u }, + { 9585582064286255216u, 14640097792684582650u, 181337854381u }, + { 12835472279575022097u, 6232313315128656727u, 24874740917u }, + { 6776016669542754608u, 16136121832933322087u, 54817204760u }, + { 18340015775620871027u, 15074767079673358270u, 254718431542u }, + { 5254188752292365830u, 13252722804829281907u, 111870u }, + { 6798802596750151183u, 2063650512248692u, 0u }, + { 9449320530215272000u, 2063650u, 0u }, + { 1u, 0u, 121419595776u }, + { 17110720482574968811u, 7740175894281560508u, 91849499257u }, + { 16172441693558688213u, 15670495392425593225u, 188821405531u }, + { 6234654946353717320u, 15152257626756992777u, 238221723324u }, + { 11180283100679445438u, 4090073428152440421u, 190783353838u }, + { 14852260031176961272u, 14450327772834205583u, 10242107326u }, + { 4481533167346438750u, 4466091895542494215u, 250566718730u }, + { 4269718344362365664u, 10454115378553795376u, 205122938618u }, + { 11520029752381101466u, 2267817233475657787u, 54298180301u }, + { 16778682550309368417u, 5500455702636497520u, 223822842678u }, + { 9687587467301363608u, 15178768299492252548u, 148382851295u }, + { 10093971076828497318u, 7062359872332045589u, 6666640532u }, + { 1913763026490934696u, 12297347290027942575u, 96435313926u }, + { 12701450127613557000u, 8030124596941085587u, 220353810784u }, + { 8974572160711134644u, 6526656990996654842u, 184339045596u }, + { 9890000077336694124u, 6254287345256979849u, 130360063928u }, + { 4292326716201059148u, 6642007136244870031u, 96109916034u }, + { 14644519175104337420u, 2027592955437164717u, 68051104864u }, + { 5051178622270136798u, 942718349157325566u, 40792392772u }, + { 675983118348065839u, 14617066671884002277u, 1370343464u }, + { 4431647660065117244u, 6831631114396133347u, 179791632385u }, + { 8316115180008411962u, 14603040013386939257u, 135537011123u }, + { 9621158095544965602u, 9906106765319401102u, 44075687047u }, + { 15283478958951102072u, 1396179595609933062u, 125624765228u }, + { 13981553073094447813u, 11524884268464976416u, 239020758653u }, + { 4558368743929911607u, 382929570730827273u, 52331208687u }, + { 15217004469858477791u, 6109721884461301380u, 235129590068u }, + { 11589190369996515737u, 2390514825000339690u, 7331559403u }, + { 3670624237398152929u, 6116191454763441754u, 7u }, + { 13471713758418039777u, 135243399970u, 0u }, + { 4489936967610296411u, 135u, 0u }, + { 1u, 0u, 106628775936u }, + { 9052049303222747950u, 11598868771099176309u, 120783334250u }, + { 1011330006193020538u, 14449966445520085104u, 71632935288u }, + { 17412075644359478612u, 11675595287405614725u, 194859815495u }, + { 6358678384745980468u, 15860796398550489896u, 137877141698u }, + { 15262353928842850919u, 16180408435245829661u, 250745768073u }, + { 11145257686438581736u, 13756992797154950705u, 20478705146u }, + { 1600562031807691890u, 8830551328786758465u, 120905306388u }, + { 6775147337046626724u, 16699955256560951263u, 85544214392u }, + { 15772127322106297822u, 10038983627153402073u, 165817764949u }, + { 4141472200527441474u, 15085100736692127345u, 2542523045u }, + { 18246007807879281267u, 10007783780289711124u, 168953930242u }, + { 960746958654787123u, 17596907048353602191u, 112733498024u }, + { 11355981212264408477u, 13530650344896573508u, 147343568752u }, + { 1573078209576251481u, 6337724853398437004u, 203692202643u }, + { 6245294478780491367u, 12768885008904063296u, 45149607627u }, + { 7523292955659721510u, 2759773619512884113u, 35457227821u }, + { 14454736751015226505u, 8434364600126655291u, 21208374307u }, + { 7219786377781411316u, 3843827521199949337u, 218252709141u }, + { 10597123082209392431u, 4661660852957808993u, 206829308634u }, + { 6922353544343010714u, 15298044134177324416u, 168420007630u }, + { 14317523356293377430u, 7747773274913338216u, 121561008808u }, + { 4057766168681892717u, 10348785912020632965u, 96226347385u }, + { 15214083611901244045u, 4175372293197190169u, 240613987168u }, + { 8390569016883950721u, 11326064156813083144u, 80439123952u }, + { 10680472538208175055u, 8100407170505981762u, 202092512592u }, + { 12173567833130544927u, 1706556116319916845u, 44814718154u }, + { 1386341248286610026u, 15028897280749641941u, 225077043500u }, + { 12487300952797237352u, 1421201742071739120u, 480481u }, + { 2614759871804869720u, 8863311460481781u, 0u }, + { 8494389567327729477u, 8863311u, 0u }, + { 1u, 0u, 247459741696u }, + { 6260469580539185878u, 8480737406125178271u, 136593449207u }, + { 17818573101084525841u, 10947205650755620360u, 8047085704u }, + { 2201029069927307150u, 868577942165647780u, 28868321800u }, + { 10397997613804897039u, 16017710019091388478u, 140358376476u }, + { 14269915965770103741u, 6610879150827623374u, 234656489612u }, + { 16776139909196366727u, 12110095866223762091u, 140420497130u }, + { 6246513436385199720u, 7756802952949470774u, 194159475340u }, + { 2926026498821554288u, 2941800790804618758u, 81634453442u }, + { 15725499391028340982u, 11703600274199927521u, 89043733329u }, + { 8576577277771450827u, 806737539257940345u, 226790330713u }, + { 15523351176022259335u, 14579028397110132022u, 52772375266u }, + { 4775158829429176134u, 14247808875344366933u, 198526563380u }, + { 10141817222123532462u, 9713379923695279512u, 244121779142u }, + { 12847658900242624586u, 2246428675703313876u, 52192434164u }, + { 13708197964460514655u, 3549783776592680619u, 76685488436u }, + { 1951540006613246932u, 12645029747929213032u, 12882486860u }, + { 9979297327280092199u, 16279009267476580505u, 88018613516u }, + { 15381307706282553684u, 343358782242907185u, 177546278232u }, + { 10037428657543061177u, 10077054739085890320u, 77570654385u }, + { 2584877324547208668u, 10526715404712173585u, 133620094029u }, + { 1126624732730703576u, 11438715865125144242u, 158273268613u }, + { 1501064139624981020u, 5040916178827294800u, 241902260126u }, + { 5219661484955306109u, 16643761637275849507u, 46263056881u }, + { 5336997298570282212u, 4852542977279030385u, 106427358510u }, + { 12191131175733833362u, 7883373066544387128u, 174905258090u }, + { 3707068178994436536u, 16699064314768500977u, 145368946606u }, + { 5045484691732942022u, 6805863634444817213u, 185122869393u }, + { 14847900542908711232u, 2266540253968903499u, 31488807865u }, + { 9097257915916965135u, 9016913589137908809u, 31u }, + { 2472027983230314217u, 580865979874u, 0u }, + { 15974509111133272205u, 580u, 0u }, + { 1u, 0u, 177631789056u }, + { 12099486841948187399u, 11654451024602552033u, 236287260081u }, + { 5319910566029976328u, 5299013208454526792u, 13808736236u }, + { 11549214421017285864u, 14918550373926182539u, 74337487885u }, + { 1998791413186046700u, 6225552657491071053u, 190560788042u }, + { 17075171930090011210u, 10344713496596235784u, 15703235518u }, + { 15158296003813501474u, 12972405634433280208u, 165699954703u }, + { 1360083178079384115u, 12911885282402784944u, 211375909797u }, + { 6167980558592741158u, 6934311832970995867u, 107540785363u }, + { 3630180428124865653u, 9975729197003430460u, 50107490923u }, + { 2276550099763657677u, 1982857556803548934u, 83113610034u }, + { 407006713016100655u, 2095735223386298222u, 186385484371u }, + { 14242579061653496002u, 7110931538347639364u, 204857722298u }, + { 17944493332678643704u, 15822183724630969534u, 44917884620u }, + { 987185901870869452u, 16931982690156327500u, 67365379884u }, + { 5578665155415167745u, 6740069226761666109u, 124170154307u }, + { 4849210377429577536u, 3138792961008474901u, 234658901884u }, + { 10811995403388891862u, 12154594426971851389u, 195855442410u }, + { 7051931074990177294u, 15780127219221910901u, 216890213571u }, + { 2030832259446664275u, 16421541930960194380u, 22405811160u }, + { 6069512651054767896u, 7485894627196740575u, 190482321942u }, + { 10608701253763958799u, 8897269432694476706u, 244931862206u }, + { 15700053443426906717u, 17189823634941678804u, 250519635444u }, + { 17759719234725541222u, 9585582064286255215u, 87695812346u }, + { 15187321568916405210u, 12835472279575022096u, 103367328599u }, + { 11040156458113129594u, 6776016669542754607u, 190994214247u }, + { 2800727824598008497u, 18340015775620871026u, 115284830142u }, + { 2997236166375604479u, 5254188752292365829u, 116368563827u }, + { 6260091886451512841u, 6798802596750151182u, 34512248692u }, + { 17573059315228347474u, 9449320530215271999u, 2063650u }, + { 7519453664590169251u, 38067632857031246u, 0u }, + { 15809436065653866529u, 38067632u, 0u }, + { 1u, 0u, 188927574016u }, + { 228921437623588922u, 17110720482574968810u, 137876709820u }, + { 2195862230003073884u, 16172441693558688212u, 9337981321u }, + { 960207412233973688u, 6234654946353717319u, 101606084361u }, + { 2464387149230492479u, 11180283100679445437u, 143805142629u }, + { 3631866936444955213u, 14852260031176961271u, 7242944399u }, + { 1578304441149380227u, 4481533167346438749u, 48231461895u }, + { 18190538519673445181u, 4269718344362365663u, 59624502064u }, + { 1271000736479934749u, 11520029752381101465u, 112909574203u }, + { 18292963032817745634u, 16778682550309368416u, 132525165168u }, + { 17168014021925537455u, 9687587467301363607u, 21547195268u }, + { 18046757712870378949u, 10093971076828497317u, 175103745301u }, + { 14857998893911743220u, 1913763026490934695u, 147688546991u }, + { 11933607369968684575u, 12701450127613556999u, 250486512531u }, + { 3483798509902859162u, 8974572160711134643u, 137536137978u }, + { 7378828438829845831u, 9890000077336694123u, 143232687497u }, + { 15791137430347699565u, 4292326716201059147u, 173793880975u }, + { 17044141236829932641u, 14644519175104337419u, 254273824941u }, + { 9075651910862456484u, 5051178622270136797u, 229036645118u }, + { 17811207355884564095u, 675983118348065838u, 227240240101u }, + { 4438638126207305937u, 4431647660065117243u, 121450817507u }, + { 12507972635512950185u, 8316115180008411961u, 142521564025u }, + { 14658269128098109408u, 9621158095544965601u, 6828519054u }, + { 3642436268910286111u, 15283478958951102071u, 32757941510u }, + { 3783099432964819561u, 13981553073094447812u, 9247109664u }, + { 9497579866027539638u, 4558368743929911606u, 132824915465u }, + { 3395179445046271361u, 15217004469858477790u, 234628251268u }, + { 5938502732309497276u, 11589190369996515736u, 90198984938u }, + { 5793671185917606255u, 3670624237398152928u, 34730303066u }, + { 889272970253526588u, 13471713758418039776u, 135243399970u }, + { 8594177504370135501u, 4489936967610296410u, 135u }, + { 7374354721120724712u, 2494800386918u, 0u }, + { 14764532643665507567u, 2494u, 0u }, + { 1u, 0u, 117490712576u }, + { 5392404173658087695u, 9052049303222747949u, 112054824309u }, + { 4976586473237854316u, 1011330006193020537u, 133943910512u }, + { 6308932742419013569u, 17412075644359478611u, 40344704645u }, + { 4831846642430703059u, 6358678384745980467u, 29827373864u }, + { 18139507855949846901u, 15262353928842850918u, 49604185629u }, + { 4865833876326628410u, 11145257686438581735u, 65086766641u }, + { 14296661839130179261u, 1600562031807691889u, 223367281473u }, + { 9254773150378118248u, 6775147337046626723u, 217855008735u }, + { 12174712433727875143u, 15772127322106297821u, 113224509657u }, + { 705653145340915199u, 4141472200527441473u, 20989118065u }, + { 17763928858962481812u, 18246007807879281266u, 143052082196u }, + { 3982836567612046296u, 960746958654787122u, 68615608975u }, + { 12730849277561967739u, 11355981212264408476u, 140085276740u }, + { 17314488764367235908u, 1573078209576251480u, 64338558092u }, + { 15951418930590301119u, 6245294478780491366u, 145407838528u }, + { 7193356087283467261u, 7523292955659721509u, 59783592849u }, + { 17592945625696089446u, 14454736751015226504u, 25391385403u }, + { 3554461664875361428u, 7219786377781411315u, 97574471193u }, + { 2213779057785318208u, 10597123082209392430u, 128375261537u }, + { 3880940796082421148u, 6922353544343010713u, 104776154496u }, + { 4528237545358141043u, 14317523356293377429u, 133219971944u }, + { 11681196539088147363u, 4057766168681892716u, 25824757125u }, + { 9835005502912643017u, 15214083611901244044u, 8454853657u }, + { 4964088126040986696u, 8390569016883950720u, 66578989576u }, + { 3355564873147047622u, 10680472538208175054u, 45659930434u }, + { 1853093467828272927u, 12173567833130544926u, 213075153709u }, + { 14755341584803008677u, 1386341248286610025u, 240676937941u }, + { 4701571132542556621u, 12487300952797237351u, 245141746416u }, + { 6128849686644853851u, 2614759871804869719u, 79460481781u }, + { 12026867901170202094u, 8494389567327729476u, 8863311u }, + { 17909760324981426303u, 163499238157084246u, 0u }, + { 2897692901883393664u, 163499238u, 0u }, + { 1u, 0u, 159339380736u }, + { 12323704802554838154u, 6260469580539185877u, 8965946783u }, + { 7135886931147821732u, 17818573101084525840u, 164119318024u }, + { 15341283120292884947u, 2201029069927307149u, 62563676580u }, + { 3092789040392634166u, 10397997613804897038u, 206773573694u }, + { 8811761390822097865u, 14269915965770103740u, 171909436366u }, + { 16870860798610218169u, 16776139909196366726u, 54338624171u }, + { 17452041453591904833u, 6246513436385199719u, 6158620214u }, + { 10314783684009874908u, 2926026498821554287u, 225852481030u }, + { 4932636630789274903u, 15725499391028340981u, 121464937185u }, + { 18143884346082124480u, 8576577277771450826u, 54841522553u }, + { 2823209155405527322u, 15523351176022259334u, 85258861878u }, + { 16195396106620226251u, 4775158829429176133u, 152549789013u }, + { 1150544491807648944u, 10141817222123532461u, 212696472984u }, + { 7767455475523884824u, 12847658900242624585u, 171743122900u }, + { 15204378045683991808u, 13708197964460514654u, 104105793195u }, + { 17239732561718805622u, 1951540006613246931u, 153540978792u }, + { 12886430624522800062u, 9979297327280092198u, 49833822361u }, + { 18162250541178258136u, 15381307706282553683u, 16544130097u }, + { 17028935366700158084u, 10037428657543061176u, 17140126480u }, + { 16075467823964198637u, 2584877324547208667u, 178061074449u }, + { 9803858825574498304u, 1126624732730703575u, 80081372850u }, + { 17464070808143041817u, 1501064139624981019u, 35282958416u }, + { 17682703471239266776u, 5219661484955306108u, 113289319203u }, + { 18147688354161351336u, 5336997298570282211u, 56660882545u }, + { 6663423873348080051u, 12191131175733833361u, 241200960568u }, + { 9417270363716235133u, 3707068178994436535u, 61273516273u }, + { 9295013721571344179u, 5045484691732942021u, 75804906301u }, + { 6199479138350037783u, 14847900542908711231u, 73493163339u }, + { 887603005365085688u, 9097257915916965134u, 226134008905u }, + { 333989628642975696u, 2472027983230314216u, 68865979874u }, + { 4620735991403939439u, 15974509111133272204u, 580u }, + { 12418523063962801201u, 10715086071862u, 0u }, + { 1587745622680169419u, 10715u, 0u }, + { 1u, 0u, 225655914496u }, + { 10968905082284365638u, 12099486841948187398u, 72288392929u }, + { 14076907092801977812u, 5319910566029976327u, 139626084168u }, + { 3438322122816124202u, 11549214421017285863u, 77108354699u }, + { 14645413324829073676u, 1998791413186046699u, 8925646925u }, + { 12271281439492289999u, 17075171930090011209u, 208821732872u }, + { 6233751789862708246u, 15158296003813501473u, 176073730256u }, + { 1962644459455827991u, 1360083178079384114u, 155334366896u }, + { 8726934184642952500u, 6167980558592741157u, 60196792475u }, + { 4531087719737475147u, 3630180428124865652u, 6123412028u }, + { 481513520412720775u, 2276550099763657676u, 110022063878u }, + { 992149349835802669u, 407006713016100654u, 68772091758u }, + { 11165474436676191361u, 14242579061653496001u, 190972772932u }, + { 10240785855143707184u, 17944493332678643703u, 76053515454u }, + { 10059329918238932466u, 987185901870869451u, 61302420044u }, + { 14791716450947031886u, 5578665155415167744u, 21262876221u }, + { 15378882314737417403u, 4849210377429577535u, 125586119445u }, + { 14726970229242271128u, 10811995403388891861u, 117382285949u }, + { 5090110549507128156u, 7051931074990177293u, 76110091637u }, + { 17185220781106503841u, 2030832259446664274u, 223329028940u }, + { 9858517691519529306u, 6069512651054767895u, 162575098847u }, + { 5595905546638020703u, 10608701253763958798u, 212851101602u }, + { 15555173226968030256u, 15700053443426906716u, 111962756308u }, + { 10745236628845355771u, 17759719234725541221u, 16823306351u }, + { 9973314042399760760u, 15187321568916405209u, 47598488080u }, + { 4374506813558796576u, 11040156458113129593u, 114151827759u }, + { 15960826480426749933u, 2800727824598008496u, 5162480498u }, + { 9636454862798615738u, 2997236166375604478u, 14339360261u }, + { 17973331528911319269u, 6260091886451512840u, 63952637454u }, + { 7366495200039369602u, 17573059315228347473u, 78407630399u }, + { 10505831326526933399u, 7519453664590169250u, 176857031246u }, + { 2803218632575724145u, 15809436065653866528u, 38067632u }, + { 8425731874431741636u, 702223880805592151u, 0u }, + { 14860552245711912111u, 702223880u, 0u }, + { 1u, 0u, 234012409856u }, + { 6993664200669526994u, 228921437623588921u, 212119037930u }, + { 4065363582031999356u, 2195862230003073883u, 71052052948u }, + { 6899780515342669867u, 960207412233973687u, 189133594695u }, + { 17713500890201844939u, 2464387149230492478u, 247196883901u }, + { 6445781125105107086u, 3631866936444955212u, 93085560055u }, + { 13563044070717478571u, 1578304441149380226u, 223986111069u }, + { 13167612994149348885u, 18190538519673445180u, 153068901087u }, + { 5505463469596727288u, 1271000736479934748u, 96991663513u }, + { 12125446212518819372u, 18292963032817745633u, 151930679904u }, + { 12537707724735421794u, 17168014021925537454u, 165978316695u }, + { 15173675086703777069u, 18046757712870378948u, 167805453733u }, + { 13535510174093048476u, 14857998893911743219u, 7646922151u }, + { 10698912997087096629u, 11933607369968684574u, 179188857095u }, + { 16952559548431933861u, 3483798509902859161u, 107400007091u }, + { 13528255827744249993u, 7378828438829845830u, 75856039275u }, + { 14122167436324771955u, 15791137430347699564u, 11923964747u }, + { 13071007137740038297u, 17044141236829932640u, 221491992075u }, + { 13011887609328904025u, 9075651910862456483u, 46965547485u }, + { 3116434332871336590u, 17811207355884564094u, 59240619054u }, + { 9050993820536772770u, 4438638126207305936u, 57678058555u }, + { 11993719123438634238u, 12507972635512950184u, 225794626361u }, + { 1414857165879849301u, 14658269128098109407u, 119197456865u }, + { 13819438220812375094u, 3642436268910286110u, 196205082231u }, + { 6073063033888264440u, 3783099432964819560u, 54514864836u }, + { 6828883869150720294u, 9497579866027539637u, 222184053046u }, + { 4548265621068768345u, 3395179445046271360u, 152321926878u }, + { 10422524923581371874u, 5938502732309497275u, 224314075544u }, + { 1858996082510682634u, 5793671185917606254u, 224048207584u }, + { 890276727450878316u, 889272970253526587u, 90465891296u }, + { 3886008133802710905u, 8594177504370135500u, 102399764570u }, + { 612074409233016757u, 7374354721120724711u, 190800386918u }, + { 3927020336901729264u, 14764532643665507566u, 2494u }, + { 5298603480094474942u, 46020944252475u, 0u }, + { 17418383752590430025u, 46020u, 0u }, + { 1u, 0u, 45292322816u }, + { 8973799690601597929u, 5392404173658087694u, 121269781293u }, + { 1343055462055792431u, 4976586473237854315u, 83342007929u }, + { 17425118728683169659u, 6308932742419013568u, 51261934931u }, + { 18389781726026675967u, 4831846642430703058u, 102983344691u }, + { 272526939565961561u, 18139507855949846900u, 231263777382u }, + { 11293026845930963228u, 4865833876326628409u, 113775023591u }, + { 13997416438903902597u, 14296661839130179260u, 163501702257u }, + { 6186605805999441184u, 9254773150378118247u, 221659992483u }, + { 4401776373281836138u, 12174712433727875142u, 65038253533u }, + { 16338917089754547008u, 705653145340915198u, 114962984513u }, + { 13337700757935003056u, 17763928858962481811u, 50215910002u }, + { 14612496890816348693u, 3982836567612046295u, 156690140722u }, + { 3219935399907691719u, 12730849277561967738u, 88938620316u }, + { 10887238730052330387u, 17314488764367235907u, 102864728152u }, + { 360256418697768294u, 15951418930590301118u, 37389952614u }, + { 321440824631118565u, 7193356087283467260u, 136953715493u }, + { 10069228080701402580u, 17592945625696089445u, 243192687752u }, + { 9428069607611622975u, 3554461664875361427u, 46120009203u }, + { 14736799017468812344u, 2213779057785318207u, 153210386222u }, + { 10875332567307979280u, 3880940796082421147u, 149245476249u }, + { 4611492910339012807u, 4528237545358141042u, 108633238933u }, + { 10743508637597314786u, 11681196539088147362u, 140533156716u }, + { 9356196315668016028u, 9835005502912643016u, 128269103756u }, + { 15755598617722189347u, 4964088126040986695u, 206181905536u }, + { 1275276394173375542u, 3355564873147047621u, 30100456398u }, + { 12644999363867216251u, 1853093467828272926u, 105799888670u }, + { 4553830511509832021u, 14755341584803008676u, 103254872681u }, + { 8869400642218174412u, 4701571132542556620u, 87332245607u }, + { 16570849151159054040u, 6128849686644853850u, 68651977815u }, + { 16127119334101797673u, 12026867901170202093u, 86970890052u }, + { 9686867250420930550u, 17909760324981426302u, 230157084246u }, + { 10678226869774428035u, 2897692901883393663u, 163499238u }, + { 7767227962910162068u, 3016028602530220424u, 0u }, + { 9780840471948993674u, 3016028602u, 0u }, + { 1u, 0u, 213668069376u }, + { 6288709332106746357u, 12323704802554838153u, 16386837205u }, + { 9066785620141948673u, 7135886931147821731u, 141831652624u }, + { 8442375916704414909u, 15341283120292884946u, 14167660429u }, + { 11604629218100425803u, 3092789040392634165u, 188477686542u }, + { 3877248044010875762u, 8811761390822097864u, 134914571196u }, + { 16435137704395217283u, 16870860798610218168u, 103946077062u }, + { 14994442577577813271u, 17452041453591904832u, 111559165543u }, + { 4410105917142436089u, 10314783684009874907u, 245267398767u }, + { 4632574728444936970u, 4932636630789274902u, 202983581941u }, + { 9117147535650050359u, 18143884346082124479u, 134153046474u }, + { 588939301256904809u, 2823209155405527321u, 69877954182u }, + { 324393982565305683u, 16195396106620226250u, 173062371141u }, + { 9380909186923521175u, 1150544491807648943u, 73421074605u }, + { 4463385697777230217u, 7767455475523884823u, 94824230985u }, + { 16378985502426333808u, 15204378045683991807u, 211934567774u }, + { 18210894922387834354u, 17239732561718805621u, 38698574803u }, + { 1555748035329493205u, 12886430624522800061u, 83984577574u }, + { 4277055533891898507u, 18162250541178258135u, 184923140435u }, + { 11574429772510874408u, 17028935366700158083u, 219871452856u }, + { 17391099253493808815u, 16075467823964198636u, 215531468251u }, + { 5791212393959129882u, 9803858825574498303u, 27946729175u }, + { 11254268231455680880u, 17464070808143041816u, 124958581275u }, + { 16355477587312235322u, 17682703471239266775u, 227983788156u }, + { 2411485149249320633u, 18147688354161351335u, 145361224931u }, + { 12763114642070638360u, 6663423873348080050u, 183510511249u }, + { 1147543073987366419u, 9417270363716235132u, 197503883703u }, + { 8410777835225272692u, 9295013721571344178u, 63336074437u }, + { 8134725822306818018u, 6199479138350037782u, 14048117055u }, + { 8899607004752328377u, 887603005365085687u, 232018105614u }, + { 690976506652396830u, 333989628642975695u, 140250490600u }, + { 12281570945595192074u, 4620735991403939438u, 54673209484u }, + { 12592957291365552899u, 12418523063962801200u, 219086071862u }, + { 13595807339013970272u, 1587745622680169418u, 10715u }, + { 9698096389749839992u, 197658450495420u, 0u }, + { 8310173728816391804u, 197658u, 0u }, +}; + +#define TABLE_SIZE_2 69 +#define ADDITIONAL_BITS_2 120 + +static const uint16_t POW10_OFFSET_2[TABLE_SIZE_2] = { + 0, 2, 6, 12, 20, 29, 40, 52, 66, 80, + 95, 112, 130, 150, 170, 192, 215, 240, 265, 292, + 320, 350, 381, 413, 446, 480, 516, 552, 590, 629, + 670, 712, 755, 799, 845, 892, 940, 989, 1040, 1092, + 1145, 1199, 1254, 1311, 1369, 1428, 1488, 1550, 1613, 1678, + 1743, 1810, 1878, 1947, 2017, 2088, 2161, 2235, 2311, 2387, + 2465, 2544, 2625, 2706, 2789, 2873, 2959, 3046, 3133 +}; + +static const uint8_t MIN_BLOCK_2[TABLE_SIZE_2] = { + 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, + 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, + 8, 9, 9, 10, 11, 11, 12, 12, 13, 13, + 14, 14, 15, 15, 16, 16, 17, 17, 18, 19, + 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, + 24, 25, 26, 26, 27, 27, 28, 28, 29, 29, + 30, 30, 31, 31, 32, 32, 33, 34, 0 +}; + +static const uint64_t POW10_SPLIT_2[3133][3] = { + { 0u, 0u, 3906250u }, + { 0u, 0u, 202000000000u }, + { 0u, 11153727427136454656u, 59u }, + { 0u, 7205759403792793600u, 59604644775u }, + { 0u, 0u, 167390625000u }, + { 0u, 0u, 232000000000u }, + { 0u, 16777216000000000u, 0u }, + { 0u, 12945425605062557696u, 909494u }, + { 0u, 4388757836872548352u, 182701772928u }, + { 0u, 1152921504606846976u, 128237915039u }, + { 0u, 0u, 159062500000u }, + { 0u, 0u, 160000000000u }, + { 0u, 256000000000u, 0u }, + { 0u, 16192327041775828992u, 13u }, + { 0u, 15024075324038053888u, 13877787807u }, + { 0u, 5449091666327633920u, 159814456755u }, + { 0u, 2494994193563254784u, 179295395851u }, + { 0u, 4611686018427387904u, 11135253906u }, + { 0u, 0u, 146250000000u }, + { 0u, 0u, 128000000000u }, + { 0u, 3906250u, 0u }, + { 0u, 3906250000000000u, 0u }, + { 0u, 4368439412768899072u, 211758u }, + { 0u, 1563676642168012800u, 46236813575u }, + { 0u, 11532349341402398720u, 7084767080u }, + { 0u, 9048364970084925440u, 104625169910u }, + { 0u, 16609275425742389248u, 246490512847u }, + { 0u, 0u, 207900390625u }, + { 0u, 0u, 225000000000u }, + { 11153727427136454656u, 59u, 0u }, + { 7205759403792793600u, 59604644775u, 0u }, + { 0u, 4264412554261970152u, 3u }, + { 0u, 14485570586272534528u, 3231174267u }, + { 0u, 17827675094632103936u, 123785264354u }, + { 0u, 7347197909193981952u, 226966440203u }, + { 0u, 13677404030777688064u, 11398292396u }, + { 0u, 3810326759732150272u, 172741453558u }, + { 0u, 9943947977234055168u, 246206558227u }, + { 0u, 0u, 19539062500u }, + { 0u, 0u, 228000000000u }, + { 12945425605062557696u, 909494u, 0u }, + { 4388757836872548352u, 909494701772928u, 0u }, + { 1152921504606846976u, 14878706826214591391u, 49303u }, + { 0u, 4387341015746028192u, 151806576313u }, + { 0u, 651726680428265472u, 185237838233u }, + { 0u, 2570638187944738816u, 153035330174u }, + { 0u, 7419175577111756800u, 126139354575u }, + { 0u, 17299322326264840192u, 207402194313u }, + { 0u, 7990511638862102528u, 137937798142u }, + { 0u, 16717361816799281152u, 254433166503u }, + { 0u, 0u, 167906250000u }, + { 0u, 0u, 16000000000u }, + { 16192327041775828992u, 13u, 0u }, + { 15024075324038053888u, 13877787807u, 0u }, + { 5449091666327633920u, 13877787807814456755u, 0u }, + { 2494994193563254784u, 9707857417284919307u, 752316384u }, + { 4611686018427387904u, 1844515466944871826u, 224526264005u }, + { 0u, 15167599819856275072u, 197099991383u }, + { 0u, 14830185305589481472u, 87822237233u }, + { 0u, 6163721531743535104u, 49803945956u }, + { 0u, 14122847407012052992u, 228334136013u }, + { 0u, 335491783960035328u, 205765601092u }, + { 0u, 941252322120433664u, 68018187046u }, + { 0u, 11529215046068469760u, 38051025390u }, + { 0u, 0u, 238625000000u }, + { 0u, 0u, 64000000000u }, + { 4368439412768899072u, 211758u, 0u }, + { 1563676642168012800u, 211758236813575u, 0u }, + { 11532349341402398720u, 8061591463141767016u, 11479u }, + { 9048364970084925440u, 16628725344207857142u, 215437019748u }, + { 16609275425742389248u, 3555541870038531535u, 100901445007u }, + { 0u, 18316647450161853665u, 143192746310u }, + { 0u, 16709574568378075648u, 70992947447u }, + { 0u, 7696022835795591168u, 247905827852u }, + { 0u, 16664449640376041472u, 12417202233u }, + { 0u, 3109186955116544000u, 57903381625u }, + { 0u, 10515518101817131008u, 121168549362u }, + { 0u, 9961962375743537152u, 242570047378u }, + { 0u, 9223372036854775808u, 146540039062u }, + { 0u, 0u, 150500000000u }, + { 14485570586272534528u, 3231174267u, 0u }, + { 17827675094632103936u, 3231174267785264354u, 0u }, + { 7347197909193981952u, 748977172262750475u, 175162308u }, + { 13677404030777688064u, 15965033457315095468u, 196040602133u }, + { 3810326759732150272u, 16809402149066729206u, 21865466197u }, + { 9943947977234055168u, 7563769067065700371u, 85911239516u }, + { 0u, 13550322810840051428u, 92410032742u }, + { 0u, 8663209637545764864u, 102734564471u }, + { 0u, 8969247575312957440u, 119469633535u }, + { 0u, 6193172891660451840u, 255486223885u }, + { 0u, 3427954273864908800u, 13335732575u }, + { 0u, 10058367555266936832u, 95185829773u }, + { 0u, 13907115649320091648u, 141545265197u }, + { 0u, 0u, 45753906250u }, + { 0u, 0u, 74000000000u }, + { 14878706826214591391u, 49303u, 0u }, + { 4387341015746028192u, 49303806576313u, 0u }, + { 651726680428265472u, 14106411361315920281u, 2672u }, + { 2570638187944738816u, 3609034283485221502u, 112764710092u }, + { 7419175577111756800u, 9896072247338192335u, 204195646140u }, + { 17299322326264840192u, 8889095178479228297u, 188536467151u }, + { 7990511638862102528u, 3631796911038383102u, 207481878815u }, + { 16717361816799281152u, 898318840772166823u, 31196880105u }, + { 0u, 17293677953982795024u, 233048697961u }, + { 0u, 7353628266884669440u, 105937492160u }, + { 0u, 2404693032470315008u, 192398640987u }, + { 0u, 9191155893041889280u, 91130358670u }, + { 0u, 6353946855033798656u, 142498253559u }, + { 0u, 3767824038248841216u, 247344448149u }, + { 0u, 7205759403792793600u, 149204254150u }, + { 0u, 0u, 198390625000u }, + { 0u, 0u, 232000000000u }, + { 9707857417284919307u, 752316384u, 0u }, + { 1844515466944871826u, 752316384526264005u, 0u }, + { 15167599819856275072u, 17063068157692817751u, 40783152u }, + { 14830185305589481472u, 5385330256507239985u, 48924990778u }, + { 6163721531743535104u, 3373050282752075748u, 58291939338u }, + { 14122847407012052992u, 4116064001262906061u, 10182853422u }, + { 335491783960035328u, 11306582046748043076u, 46223132276u }, + { 941252322120433664u, 17035410946089626406u, 116612931040u }, + { 11529215046068469760u, 15618595715183448558u, 224923491477u }, + { 0u, 5141740092277295680u, 149846685770u }, + { 0u, 16973644291514990592u, 74278734288u }, + { 0u, 14625255268443750400u, 208920143100u }, + { 0u, 14021170507320131584u, 252792836676u }, + { 0u, 4451355232865091584u, 68760089176u }, + { 0u, 12891553933348044800u, 88241308450u }, + { 0u, 1152921504606846976u, 34698852539u }, + { 0u, 0u, 187062500000u }, + { 0u, 0u, 160000000000u }, + { 8061591463141767016u, 11479u, 0u }, + { 16628725344207857142u, 11479437019748u, 0u }, + { 3555541870038531535u, 5562205901560339855u, 622u }, + { 18316647450161853665u, 2106077949367544134u, 110301527786u }, + { 16709574568378075648u, 7496855998374373623u, 234114170714u }, + { 7696022835795591168u, 229183437194837004u, 90406405378u }, + { 16664449640376041472u, 465169186276472889u, 2012424059u }, + { 3109186955116544000u, 2152980561625316473u, 123025216872u }, + { 10515518101817131008u, 2059790725449340402u, 104116713310u }, + { 9961962375743537152u, 17891190926410198930u, 94111661478u }, + { 9223372036854775808u, 9930696175609809814u, 166969883403u }, + { 0u, 7276914261609005312u, 11538344118u }, + { 0u, 10539762974036983808u, 182394482312u }, + { 0u, 12851089458992250880u, 136571361695u }, + { 0u, 9449311677678878720u, 159696658955u }, + { 0u, 8699564697382289408u, 11512248212u }, + { 0u, 4224376450473525248u, 148471604347u }, + { 0u, 4611686018427387904u, 123229003906u }, + { 0u, 0u, 130250000000u }, + { 0u, 0u, 128000000000u }, + { 748977172262750475u, 175162308u, 0u }, + { 15965033457315095468u, 175162308040602133u, 0u }, + { 16809402149066729206u, 13756840147955779925u, 9495567u }, + { 7563769067065700371u, 13788447602092505948u, 15745759798u }, + { 13550322810840051428u, 4972540435632173670u, 54747473242u }, + { 8663209637545764864u, 2844874687533091959u, 90269561957u }, + { 8969247575312957440u, 15377573779532804095u, 101154220965u }, + { 6193172891660451840u, 17824715805091194381u, 165833619944u }, + { 3427954273864908800u, 18277569135638159711u, 232966279779u }, + { 10058367555266936832u, 4254645803379752845u, 99990829008u }, + { 13907115649320091648u, 2933643244178200621u, 208230644811u }, + { 0u, 17188148801879487562u, 75159033118u }, + { 0u, 11069762501163246592u, 30931771413u }, + { 0u, 11676570643941818368u, 21600093027u }, + { 0u, 17840016768744030208u, 99632988162u }, + { 0u, 16463817321652158464u, 2967109246u }, + { 0u, 6954191143357644800u, 126892505325u }, + { 0u, 5080060379673919488u, 237376987457u }, + { 0u, 0u, 65275390625u }, + { 0u, 0u, 161000000000u }, + { 14106411361315920281u, 2672u, 0u }, + { 3609034283485221502u, 2672764710092u, 0u }, + { 9896072247338192335u, 16433563478020213436u, 144u }, + { 8889095178479228297u, 4194750497955655375u, 144890865261u }, + { 3631796911038383102u, 2691539602252904735u, 109227397880u }, + { 898318840772166823u, 3775467271962795241u, 248145908654u }, + { 17293677953982795024u, 16980212613224918121u, 174204668490u }, + { 7353628266884669440u, 4172857038337333440u, 74920499170u }, + { 2404693032470315008u, 5936867627376461659u, 226226211033u }, + { 9191155893041889280u, 17856837443266866062u, 217321838238u }, + { 6353946855033798656u, 8956297047799810807u, 158968021097u }, + { 3767824038248841216u, 15356974049716912789u, 105485521835u }, + { 7205759403792793600u, 6923608913322982854u, 171832503231u }, + { 0u, 4855902993563955944u, 191375329591u }, + { 0u, 13835893222288330752u, 55263239028u }, + { 0u, 9114973913760137216u, 116750045274u }, + { 0u, 17937099003422310400u, 90494123725u }, + { 0u, 7007960010734960640u, 205972372085u }, + { 0u, 7683422439270776832u, 117379902273u }, + { 0u, 720575940379279360u, 65416519165u }, + { 0u, 0u, 253039062500u }, + { 0u, 0u, 228000000000u }, + { 17063068157692817751u, 40783152u, 0u }, + { 5385330256507239985u, 40783152924990778u, 0u }, + { 3373050282752075748u, 2768933352715741194u, 2210859u }, + { 4116064001262906061u, 15201941611824153390u, 43150104177u }, + { 11306582046748043076u, 1418128541727000180u, 113824098906u }, + { 17035410946089626406u, 5353350204565757408u, 90076876902u }, + { 15618595715183448558u, 1721001680354286741u, 102290205696u }, + { 5141740092277295680u, 637631411660453962u, 93295688u }, + { 16973644291514990592u, 1630012588870568400u, 72034566068u }, + { 14625255268443750400u, 9253063571656828156u, 180088363159u }, + { 14021170507320131584u, 6029146854993203780u, 151501609581u }, + { 4451355232865091584u, 16987401965352759896u, 109326840705u }, + { 12891553933348044800u, 14499131620542087970u, 129920888905u }, + { 1152921504606846976u, 1978417255298660539u, 73785999500u }, + { 0u, 5790079354402454176u, 140107250214u }, + { 0u, 13748918935842078720u, 38313880830u }, + { 0u, 18047438014740692992u, 254745330388u }, + { 0u, 3116889656839372800u, 212978353575u }, + { 0u, 15995952446606147584u, 167168966926u }, + { 0u, 12530140063251562496u, 14867142319u }, + { 0u, 16717361816799281152u, 175679260253u }, + { 0u, 0u, 93906250000u }, + { 0u, 0u, 16000000000u }, + { 5562205901560339855u, 622u, 0u }, + { 2106077949367544134u, 622301527786u, 0u }, + { 7496855998374373623u, 13558973353698967386u, 33u }, + { 229183437194837004u, 6228991722850501890u, 33735033418u }, + { 465169186276472889u, 16886831391703377787u, 74337674317u }, + { 2152980561625316473u, 1181713637872883048u, 77915436964u }, + { 2059790725449340402u, 12393932434925221726u, 164064060824u }, + { 17891190926410198930u, 10684799845419711910u, 152671876423u }, + { 9930696175609809814u, 4590318792215640843u, 71579224160u }, + { 7276914261609005312u, 6383712187366189238u, 96248841680u }, + { 10539762974036983808u, 1904270214927675016u, 208346061731u }, + { 12851089458992250880u, 3711506775113308575u, 163103230695u }, + { 9449311677678878720u, 8091219444738793995u, 231201201185u }, + { 8699564697382289408u, 39436684991068052u, 33438625885u }, + { 4224376450473525248u, 18025182908196512891u, 93002137866u }, + { 4611686018427387904u, 7853924592034603138u, 10977147123u }, + { 0u, 4815749283615688320u, 243425762105u }, + { 0u, 14242399906544287744u, 57261062291u }, + { 0u, 76242322576113664u, 147772082046u }, + { 0u, 10858088421377703936u, 126004133104u }, + { 0u, 14293835879041466368u, 240588618152u }, + { 0u, 12182236992037191680u, 168774870395u }, + { 0u, 11529215046068469760u, 123660400390u }, + { 0u, 0u, 6625000000u }, + { 0u, 0u, 64000000000u }, + { 13756840147955779925u, 9495567u, 0u }, + { 13788447602092505948u, 9495567745759798u, 0u }, + { 4972540435632173670u, 14000097438505379162u, 514755u }, + { 2844874687533091959u, 16451062686452429925u, 195758946802u }, + { 15377573779532804095u, 4009347599785716645u, 242891813895u }, + { 17824715805091194381u, 16544162347546196456u, 7217347168u }, + { 18277569135638159711u, 17674258299745817187u, 96896860837u }, + { 4254645803379752845u, 5215238411201214416u, 165958123462u }, + { 2933643244178200621u, 14253990228345322571u, 198282718640u }, + { 17188148801879487562u, 11214836553940194590u, 176772710358u }, + { 11069762501163246592u, 14620711348380590101u, 214607957507u }, + { 11676570643941818368u, 6638710787931587427u, 3792590350u }, + { 17840016768744030208u, 17320000343692853250u, 14359885232u }, + { 16463817321652158464u, 75147386268843646u, 176938919100u }, + { 6954191143357644800u, 17938801582125480173u, 188004073747u }, + { 5080060379673919488u, 6573358613626446145u, 19972464382u }, + { 0u, 8688505427903736481u, 254356342484u }, + { 0u, 539870168696556032u, 212471004823u }, + { 0u, 9002861336394465280u, 151029266420u }, + { 0u, 17989846818158018560u, 244488046090u }, + { 0u, 2700938287723315200u, 10975231550u }, + { 0u, 17800090499088908288u, 62146418157u }, + { 0u, 8809040871136690176u, 237964944839u }, + { 0u, 9223372036854775808u, 199477539062u }, + { 0u, 0u, 246500000000u }, + { 16433563478020213436u, 144u, 0u }, + { 4194750497955655375u, 144890865261u, 0u }, + { 2691539602252904735u, 15763656745260536568u, 7u }, + { 3775467271962795241u, 8787336846248645550u, 7854549544u }, + { 16980212613224918121u, 17584084447880694346u, 40476362484u }, + { 4172857038337333440u, 18041672551129683938u, 244953235127u }, + { 5936867627376461659u, 14025886302294509785u, 183978041028u }, + { 17856837443266866062u, 18430498103283160734u, 196760344819u }, + { 8956297047799810807u, 3292348826238025833u, 243999119304u }, + { 15356974049716912789u, 9211721212658275243u, 200178478587u }, + { 6923608913322982854u, 10233245872666307519u, 251499368407u }, + { 4855902993563955944u, 6200995035623311671u, 215554745370u }, + { 13835893222288330752u, 8480542380570450804u, 26336156614u }, + { 9114973913760137216u, 11870363864499900506u, 198459731123u }, + { 17937099003422310400u, 9301051379839581901u, 179643493714u }, + { 7007960010734960640u, 11456694803569638005u, 82504211005u }, + { 7683422439270776832u, 14327208890643983169u, 61621068669u }, + { 720575940379279360u, 4510081789599866365u, 125776679550u }, + { 0u, 13255356976020303332u, 126244492023u }, + { 0u, 9658806854127314944u, 247718574341u }, + { 0u, 13708435528809971712u, 5523604968u }, + { 0u, 1580190652103131136u, 232743135779u }, + { 0u, 16557336970347413504u, 35085662306u }, + { 0u, 12751520132434493440u, 98897575035u }, + { 0u, 9295429630892703744u, 123691261291u }, + { 0u, 0u, 107503906250u }, + { 0u, 0u, 202000000000u }, + { 2768933352715741194u, 2210859u, 0u }, + { 15201941611824153390u, 2210859150104177u, 0u }, + { 1418128541727000180u, 16872870088062921306u, 119850u }, + { 5353350204565757408u, 5112979788807802982u, 42914680120u }, + { 1721001680354286741u, 13742728082020150272u, 56277175189u }, + { 637631411660453962u, 2217110934613627976u, 149744994782u }, + { 1630012588870568400u, 11021433940188610484u, 222120189824u }, + { 9253063571656828156u, 1713669895470733463u, 128597473131u }, + { 6029146854993203780u, 3313382510572018285u, 107092898231u }, + { 16987401965352759896u, 14976595232784069505u, 183179618825u }, + { 14499131620542087970u, 7213172372862496841u, 9811882854u }, + { 1978417255298660539u, 15836474542502248588u, 102391026857u }, + { 5790079354402454176u, 3221099285878340134u, 169858497005u }, + { 13748918935842078720u, 3265814602578095358u, 237174616142u }, + { 18047438014740692992u, 6502528252282225364u, 78177040164u }, + { 3116889656839372800u, 16392476834556790183u, 36352502762u }, + { 15995952446606147584u, 15167629413417091342u, 234888637949u }, + { 12530140063251562496u, 1366763272626280111u, 253822238838u }, + { 16717361816799281152u, 8720523635169216093u, 118074092385u }, + { 0u, 9649171375767398672u, 97472740533u }, + { 0u, 7647980704001073152u, 181523082628u }, + { 0u, 13286434495608651776u, 132414597864u }, + { 0u, 4358271637167013888u, 232720259057u }, + { 0u, 15954987941890097152u, 241236262378u }, + { 0u, 7911135695429697536u, 234864921629u }, + { 0u, 7205759403792793600u, 29428863525u }, + { 0u, 0u, 37390625000u }, + { 0u, 0u, 232000000000u }, + { 13558973353698967386u, 33u, 0u }, + { 6228991722850501890u, 33735033418u, 0u }, + { 16886831391703377787u, 15288289344628122701u, 1u }, + { 1181713637872883048u, 952589339068938148u, 1828779826u }, + { 12393932434925221726u, 10058155040190817688u, 50051639971u }, + { 10684799845419711910u, 5322725640026584391u, 163545253677u }, + { 4590318792215640843u, 2269982385930389600u, 45288545535u }, + { 6383712187366189238u, 13216683679976310224u, 255123055991u }, + { 1904270214927675016u, 17417440642083494819u, 119716477857u }, + { 3711506775113308575u, 3029180749090900711u, 161944201349u }, + { 8091219444738793995u, 8315443826261908513u, 133164212217u }, + { 39436684991068052u, 1488962797247197277u, 249450781113u }, + { 18025182908196512891u, 18009099634999034122u, 185080716834u }, + { 7853924592034603138u, 8092455412807497971u, 34976275247u }, + { 4815749283615688320u, 17808458047236758329u, 47438692886u }, + { 14242399906544287744u, 3164591817527425171u, 22965398445u }, + { 76242322576113664u, 3314036340472350590u, 173171552866u }, + { 10858088421377703936u, 33234902404332784u, 98179654270u }, + { 14293835879041466368u, 12349284717857274280u, 126001801667u }, + { 12182236992037191680u, 18209607903013119355u, 195669456065u }, + { 11529215046068469760u, 7891549145984268038u, 193987144822u }, + { 0u, 7703609897518594624u, 118427801736u }, + { 0u, 6336912652634587136u, 136417613529u }, + { 0u, 4461621834659397632u, 217343524723u }, + { 0u, 5484660635557953536u, 115241865004u }, + { 0u, 15142619273265938432u, 44297324048u }, + { 0u, 12170977992968765440u, 16820883035u }, + { 0u, 1152921504606846976u, 91659790039u }, + { 0u, 0u, 215062500000u }, + { 0u, 0u, 160000000000u }, + { 14000097438505379162u, 514755u, 0u }, + { 16451062686452429925u, 514755758946802u, 0u }, + { 4009347599785716645u, 17812314011563521031u, 27904u }, + { 16544162347546196456u, 7684138864490314336u, 965607477u }, + { 17674258299745817187u, 9740522787420029605u, 53416558002u }, + { 5215238411201214416u, 6701109407732989894u, 178528034798u }, + { 14253990228345322571u, 16534886227502443952u, 238363267868u }, + { 11214836553940194590u, 8908667306968317910u, 28896357978u }, + { 14620711348380590101u, 7531472173477105155u, 90482939822u }, + { 6638710787931587427u, 11527371604834801166u, 174408281924u }, + { 17320000343692853250u, 15688593496691078576u, 68624900066u }, + { 75147386268843646u, 11394944804253312188u, 226850480357u }, + { 17938801582125480173u, 11182279880854372627u, 229617721195u }, + { 6573358613626446145u, 150579373068361470u, 107606192607u }, + { 8688505427903736481u, 3147220002440857300u, 223008162924u }, + { 539870168696556032u, 3630514817795505815u, 108170611138u }, + { 9002861336394465280u, 11708796588334233588u, 194196810602u }, + { 17989846818158018560u, 16844495466426369546u, 106634735134u }, + { 2700938287723315200u, 17636655472325475902u, 30913141928u }, + { 17800090499088908288u, 17038926655686645229u, 168956085008u }, + { 8809040871136690176u, 15602838456783529415u, 16923682064u }, + { 9223372036854775808u, 10869815869248876790u, 16845831567u }, + { 0u, 18407124180939800832u, 143589253898u }, + { 0u, 5705018517251293184u, 10997852201u }, + { 0u, 9660452258743058432u, 41309269673u }, + { 0u, 5646292272224927744u, 169523694166u }, + { 0u, 7410409304047484928u, 86306086117u }, + { 0u, 5953758707383795712u, 229401719093u }, + { 0u, 4611686018427387904u, 53322753906u }, + { 0u, 0u, 114250000000u }, + { 0u, 0u, 128000000000u }, + { 15763656745260536568u, 7u, 0u }, + { 8787336846248645550u, 7854549544u, 0u }, + { 17584084447880694346u, 7854549544476362484u, 0u }, + { 18041672551129683938u, 15035424419724983u, 425795984u }, + { 14025886302294509785u, 18280822466032836292u, 144000815071u }, + { 18430498103283160734u, 11524250747302615283u, 223991005371u }, + { 3292348826238025833u, 15212285943691810760u, 187624730884u }, + { 9211721212658275243u, 7951804027551297019u, 4824659673u }, + { 10233245872666307519u, 1706416229965221847u, 217431068160u }, + { 6200995035623311671u, 3406023111930700826u, 92505009u }, + { 8480542380570450804u, 16132696204133391302u, 177184640882u }, + { 11870363864499900506u, 11593846688794356915u, 114874555213u }, + { 9301051379839581901u, 6875759884161133906u, 77628503688u }, + { 11456694803569638005u, 3593593325323835965u, 136372735690u }, + { 14327208890643983169u, 9542049733257388925u, 202194809084u }, + { 4510081789599866365u, 9926551925937787518u, 252517275552u }, + { 13255356976020303332u, 3128491553219547895u, 160538119458u }, + { 9658806854127314944u, 17158408656931354885u, 34169595866u }, + { 13708435528809971712u, 2065169543154992616u, 218930159197u }, + { 1580190652103131136u, 4832622393556232739u, 93111953065u }, + { 16557336970347413504u, 16505930714733656162u, 169261976984u }, + { 12751520132434493440u, 18270988073492888699u, 152894788296u }, + { 9295429630892703744u, 2525111411519708523u, 200990472248u }, + { 0u, 16728989342518570442u, 56136886563u }, + { 0u, 7974052022039438336u, 35906880329u }, + { 0u, 5356554962386550784u, 73432274226u }, + { 0u, 6693869495028547584u, 50290379426u }, + { 0u, 8157517147199766528u, 162362875392u }, + { 0u, 12065776720423157760u, 442219890u }, + { 0u, 11997589407315001344u, 114654087066u }, + { 0u, 0u, 154650390625u }, + { 0u, 0u, 97000000000u }, + { 16872870088062921306u, 119850u, 0u }, + { 5112979788807802982u, 119850914680120u, 0u }, + { 13742728082020150272u, 2418433229320326037u, 6497u }, + { 2217110934613627976u, 1143911773589293534u, 97131103528u }, + { 11021433940188610484u, 9276183703610924928u, 40062011581u }, + { 1713669895470733463u, 3532180128827684715u, 189502862926u }, + { 3313382510572018285u, 8563997501322031543u, 78191479868u }, + { 14976595232784069505u, 14843890409658460681u, 60464255234u }, + { 7213172372862496841u, 9489417861634552678u, 2804688911u }, + { 15836474542502248588u, 1113198223322322089u, 15514422373u }, + { 3221099285878340134u, 11190777557146597869u, 101060346596u }, + { 3265814602578095358u, 17764553645932638286u, 228606653266u }, + { 6502528252282225364u, 14900777150991234852u, 82963018382u }, + { 16392476834556790183u, 17364899863357893610u, 142807772747u }, + { 15167629413417091342u, 15537570181590167037u, 75941353107u }, + { 1366763272626280111u, 5558052627121307766u, 147842293367u }, + { 8720523635169216093u, 12095241565795232609u, 119301302636u }, + { 9649171375767398672u, 2187936505958366389u, 108655684359u }, + { 7647980704001073152u, 12009203621325860228u, 7118608275u }, + { 13286434495608651776u, 14814842834750302952u, 147651020232u }, + { 4358271637167013888u, 5965296499605198833u, 200803114239u }, + { 15954987941890097152u, 4051026394962148842u, 255323379371u }, + { 7911135695429697536u, 16799526299141688349u, 171219606580u }, + { 7205759403792793600u, 9460214166646215205u, 52910704145u }, + { 0u, 10750736995029068008u, 17512839237u }, + { 0u, 5377963045376430080u, 69582798620u }, + { 0u, 15996910350253424640u, 28291539960u }, + { 0u, 13651157529655246848u, 248867194247u }, + { 0u, 9771305410219737088u, 135740030732u }, + { 0u, 12709439623416250368u, 12529703527u }, + { 0u, 9943947977234055168u, 103688980102u }, + { 0u, 0u, 134539062500u }, + { 0u, 0u, 228000000000u }, + { 952589339068938148u, 1828779826u, 0u }, + { 10058155040190817688u, 1828779826051639971u, 0u }, + { 5322725640026584391u, 371564423966525229u, 99138353u }, + { 2269982385930389600u, 14464859121514339583u, 49020142547u }, + { 13216683679976310224u, 3913119023023056247u, 211784141584u }, + { 17417440642083494819u, 5493396321716566945u, 16212130607u }, + { 3029180749090900711u, 5837454566818211973u, 47297797611u }, + { 8315443826261908513u, 2886670683193253881u, 235316449046u }, + { 1488962797247197277u, 5504823105587173817u, 22156486731u }, + { 18009099634999034122u, 9431834277334851106u, 75298417058u }, + { 8092455412807497971u, 12921661346456247087u, 162511300760u }, + { 17808458047236758329u, 3643076516404724246u, 152700484665u }, + { 3164591817527425171u, 12559396953196866477u, 57197491573u }, + { 3314036340472350590u, 1626880974916825698u, 117680846273u }, + { 33234902404332784u, 6806994170946429566u, 193088193394u }, + { 12349284717857274280u, 7596631230206896579u, 114369007893u }, + { 18209607903013119355u, 3100480253729502401u, 21411814204u }, + { 7891549145984268038u, 6310570748781063286u, 60168077371u }, + { 7703609897518594624u, 14251867077375744136u, 59342096725u }, + { 6336912652634587136u, 6701165793751570137u, 85772595262u }, + { 4461621834659397632u, 10856833140463959923u, 62363270925u }, + { 5484660635557953536u, 15867563727561248556u, 13588550103u }, + { 15142619273265938432u, 5048961008671491600u, 215860182353u }, + { 12170977992968765440u, 13278183119599849051u, 81273704724u }, + { 1152921504606846976u, 4547591784941053655u, 20719811749u }, + { 0u, 11815437715887182496u, 165246525444u }, + { 0u, 398495392178782208u, 4640516162u }, + { 0u, 9154841240825495552u, 66021602478u }, + { 0u, 1902683298245640192u, 174496284938u }, + { 0u, 5081900962138816512u, 10103144668u }, + { 0u, 3234710432358858752u, 220275490403u }, + { 0u, 16717361816799281152u, 99175354003u }, + { 0u, 0u, 147906250000u }, + { 0u, 0u, 16000000000u }, + { 17812314011563521031u, 27904u, 0u }, + { 7684138864490314336u, 27904965607477u, 0u }, + { 9740522787420029605u, 13488568028574514610u, 1512u }, + { 6701109407732989894u, 275784718433886190u, 232731216738u }, + { 16534886227502443952u, 10020568880357102364u, 98014950319u }, + { 8908667306968317910u, 8876397213146246746u, 175543216127u }, + { 7531472173477105155u, 2155905919114811310u, 255481190457u }, + { 11527371604834801166u, 1087100407155601220u, 57116871894u }, + { 15688593496691078576u, 2903498381705011170u, 214058931831u }, + { 11394944804253312188u, 12223476257006657765u, 119157398962u }, + { 11182279880854372627u, 12148657163736735595u, 178662635975u }, + { 150579373068361470u, 8951241323311673823u, 199658580024u }, + { 3147220002440857300u, 8463862715901576300u, 56485247764u }, + { 3630514817795505815u, 3873401978748963266u, 20458826917u }, + { 11708796588334233588u, 248364795947002730u, 165209977542u }, + { 16844495466426369546u, 10454378025404001822u, 198013463882u }, + { 17636655472325475902u, 6574176865628265640u, 74566732968u }, + { 17038926655686645229u, 16703315293848336u, 168356386842u }, + { 15602838456783529415u, 9896033222450013456u, 26000905488u }, + { 10869815869248876790u, 17311376269334085007u, 16536465035u }, + { 18407124180939800832u, 18378511316495639306u, 139938451587u }, + { 5705018517251293184u, 15120796393727584297u, 131996301094u }, + { 9660452258743058432u, 18253447805740347049u, 38819700014u }, + { 5646292272224927744u, 5842497225601731158u, 46989521388u }, + { 7410409304047484928u, 4369968404176723173u, 236316722409u }, + { 5953758707383795712u, 16142207253674488117u, 233236896461u }, + { 4611686018427387904u, 12124259227391928178u, 205875070808u }, + { 0u, 13019483264566077056u, 88657257409u }, + { 0u, 74901376448135168u, 193705787602u }, + { 0u, 13897060093813325824u, 210004060411u }, + { 0u, 4495486210810052608u, 251753361137u }, + { 0u, 14885496280087265280u, 241243700795u }, + { 0u, 4976477588244398080u, 59806944370u }, + { 0u, 11529215046068469760u, 114269775390u }, + { 0u, 0u, 30625000000u }, + { 0u, 0u, 64000000000u }, + { 15035424419724983u, 425795984u, 0u }, + { 18280822466032836292u, 425795984000815071u, 0u }, + { 11524250747302615283u, 10043594327130472635u, 23082446u }, + { 15212285943691810760u, 8336034337032909060u, 206544464339u }, + { 7951804027551297019u, 16717215784895280857u, 211451897326u }, + { 1706416229965221847u, 10968831263951212032u, 238906242083u }, + { 3406023111930700826u, 5536629379734406065u, 35594621534u }, + { 16132696204133391302u, 1618806894932332402u, 94300141280u }, + { 11593846688794356915u, 11363331325254998861u, 224087755697u }, + { 6875759884161133906u, 8775167772751754888u, 177616007425u }, + { 3593593325323835965u, 2898202945316114122u, 1475702798u }, + { 9542049733257388925u, 8868842714495185148u, 14157111896u }, + { 9926551925937787518u, 17052094667531999136u, 88480780926u }, + { 3128491553219547895u, 3658615537031138594u, 126924395904u }, + { 17158408656931354885u, 12486952437987190746u, 128198333945u }, + { 2065169543154992616u, 912079238520577629u, 249676919048u }, + { 4832622393556232739u, 10960072898031888041u, 8049443914u }, + { 16505930714733656162u, 6129550094334741912u, 74594146742u }, + { 18270988073492888699u, 7965724516573729480u, 182332283576u }, + { 2525111411519708523u, 5801761178810791992u, 184431822791u }, + { 16728989342518570442u, 13197466483098446115u, 199314514103u }, + { 7974052022039438336u, 11326268638393107273u, 183715436091u }, + { 5356554962386550784u, 3597339351794947378u, 59613998253u }, + { 6693869495028547584u, 353880726151383714u, 173195012157u }, + { 8157517147199766528u, 11154818162602073600u, 61019183912u }, + { 12065776720423157760u, 5141043976157511026u, 40604703904u }, + { 11997589407315001344u, 7188225141808859034u, 160278696552u }, + { 0u, 13894168943295705185u, 104389674465u }, + { 0u, 12176538069834828288u, 225753204407u }, + { 0u, 7994239409235165184u, 183660091451u }, + { 0u, 13707777025480065024u, 59433368586u }, + { 0u, 10120227247676719104u, 10743100081u }, + { 0u, 7358494763030413312u, 177548618618u }, + { 0u, 7656119366529843200u, 122398904800u }, + { 0u, 9223372036854775808u, 224415039062u }, + { 0u, 0u, 86500000000u }, + { 2418433229320326037u, 6497u, 0u }, + { 1143911773589293534u, 6497131103528u, 0u }, + { 9276183703610924928u, 3877189582299842749u, 352u }, + { 3532180128827684715u, 7625565791857948238u, 96210182868u }, + { 8563997501322031543u, 16568435163612007484u, 212413382749u }, + { 14843890409658460681u, 17592071940521808130u, 93898176669u }, + { 9489417861634552678u, 15158637878035490831u, 157953668130u }, + { 1113198223322322089u, 17789243229146401893u, 34821751405u }, + { 11190777557146597869u, 14677686051252896484u, 109964356807u }, + { 17764553645932638286u, 3531237481269211986u, 199795678955u }, + { 14900777150991234852u, 8074435404989280910u, 235191428767u }, + { 17364899863357893610u, 7086549341467684427u, 159437716020u }, + { 15537570181590167037u, 10556134770918626963u, 52384162609u }, + { 5558052627121307766u, 10772666134712966775u, 49572249212u }, + { 12095241565795232609u, 6195173298198112620u, 124583987401u }, + { 2187936505958366389u, 8144773843324250887u, 201335841017u }, + { 12009203621325860228u, 14144284817150924691u, 249441529074u }, + { 14814842834750302952u, 6464447844648863176u, 242766763216u }, + { 5965296499605198833u, 15760468443293179135u, 208350438419u }, + { 4051026394962148842u, 5172191224908322475u, 19854376706u }, + { 16799526299141688349u, 2357554307308969012u, 2280385048u }, + { 9460214166646215205u, 1602046917604361745u, 24127803275u }, + { 10750736995029068008u, 7830970218109515845u, 139086847137u }, + { 5377963045376430080u, 2899479134887821084u, 161424517746u }, + { 15996910350253424640u, 15792042302392017912u, 114157181078u }, + { 13651157529655246848u, 11286099112296056199u, 150856088328u }, + { 9771305410219737088u, 15161477829153947404u, 8611820658u }, + { 12709439623416250368u, 423831848142641767u, 114821905360u }, + { 9943947977234055168u, 9707413321046312582u, 208022975970u }, + { 0u, 10969483299803835620u, 226526239930u }, + { 0u, 4326479556120930304u, 186594656881u }, + { 0u, 12876227232041795584u, 113234538926u }, + { 0u, 16967986827791171584u, 174698021676u }, + { 0u, 1288146316538413056u, 44919836409u }, + { 0u, 13715290452691779584u, 249069830551u }, + { 0u, 4683743612465315840u, 151743507385u }, + { 0u, 0u, 185253906250u }, + { 0u, 0u, 74000000000u }, + { 371564423966525229u, 99138353u, 0u }, + { 14464859121514339583u, 99138353020142547u, 0u }, + { 3913119023023056247u, 16344805304534272784u, 5374300u }, + { 5493396321716566945u, 26429987091348271u, 92886053671u }, + { 5837454566818211973u, 8691371289609838059u, 39001432772u }, + { 2886670683193253881u, 12980168378493046550u, 196471160181u }, + { 5504823105587173817u, 14010125458129496139u, 117703656337u }, + { 9431834277334851106u, 17061829677031795106u, 145759490422u }, + { 12921661346456247087u, 2227928323072698520u, 118924923640u }, + { 3643076516404724246u, 7394752319272287289u, 248120776236u }, + { 12559396953196866477u, 8805771303577744757u, 44400870326u }, + { 1626880974916825698u, 16371027194302248385u, 182477361818u }, + { 6806994170946429566u, 9114324123731231602u, 154887475162u }, + { 7596631230206896579u, 14468189808746991893u, 218494088500u }, + { 3100480253729502401u, 2376054557800684348u, 52784322141u }, + { 6310570748781063286u, 12462238943546048571u, 93128806175u }, + { 14251867077375744136u, 15334855370842605909u, 31675579326u }, + { 6701165793751570137u, 7211347914013798462u, 190831304175u }, + { 10856833140463959923u, 13763642332572548877u, 239390927953u }, + { 15867563727561248556u, 16868268377740071383u, 81746128545u }, + { 5048961008671491600u, 1120013377627684177u, 161914430661u }, + { 13278183119599849051u, 15898107650717274388u, 197060716046u }, + { 4547591784941053655u, 12281923376333274277u, 14861838142u }, + { 11815437715887182496u, 6383530489286615044u, 62665804400u }, + { 398495392178782208u, 4253822060257126466u, 112346051881u }, + { 9154841240825495552u, 17614372438391501998u, 41230600155u }, + { 1902683298245640192u, 4309951310554333450u, 219954877043u }, + { 5081900962138816512u, 13106185988973773020u, 115233642928u }, + { 3234710432358858752u, 2070134359761960547u, 176710487766u }, + { 16717361816799281152u, 9399359914137865875u, 214112222208u }, + { 0u, 17415053284723541264u, 509540321u }, + { 0u, 4840502610448261120u, 225944071930u }, + { 0u, 5690599259712258048u, 250262404172u }, + { 0u, 114769594245185536u, 76308488004u }, + { 0u, 3150620882578178048u, 68006221672u }, + { 0u, 5136918324969472000u, 104170795500u }, + { 0u, 7205759403792793600u, 236278472900u }, + { 0u, 0u, 196390625000u }, + { 0u, 0u, 232000000000u }, + { 13488568028574514610u, 1512u, 0u }, + { 275784718433886190u, 1512731216738u, 0u }, + { 10020568880357102364u, 98202693831717807u, 82u }, + { 8876397213146246746u, 12909287260170414079u, 82005323578u }, + { 2155905919114811310u, 11728631949380786233u, 58699813864u }, + { 1087100407155601220u, 18263701925522197718u, 232635810411u }, + { 2903498381705011170u, 4868886449713321591u, 107990077265u }, + { 12223476257006657765u, 5870139507184082354u, 81263942863u }, + { 12148657163736735595u, 5978562500822661575u, 207318220900u }, + { 8951241323311673823u, 10821136839630268472u, 100324098522u }, + { 8463862715901576300u, 9490907630136752916u, 218586615003u }, + { 3873401978748963266u, 10564005678001613989u, 219514503133u }, + { 248364795947002730u, 5754050547468481222u, 221572675895u }, + { 10454378025404001822u, 3833909949855542602u, 55311927705u }, + { 6574176865628265640u, 15446538552665967784u, 153207836674u }, + { 16703315293848336u, 14924837848804399130u, 2837358532u }, + { 9896033222450013456u, 18140170340418344208u, 196809077080u }, + { 17311376269334085007u, 11380424819825208971u, 88983380604u }, + { 18378511316495639306u, 12416915664152252547u, 124616934065u }, + { 15120796393727584297u, 17195282241626289958u, 177673122346u }, + { 18253447805740347049u, 2649541045825281326u, 42932158118u }, + { 5842497225601731158u, 16577429864268509676u, 166143631907u }, + { 4369968404176723173u, 12051257060168107241u, 35898664273u }, + { 16142207253674488117u, 5363884561143470797u, 81653299954u }, + { 12124259227391928178u, 13054029903083620184u, 242290776764u }, + { 13019483264566077056u, 566314952158634945u, 188707660379u }, + { 74901376448135168u, 1329472079642345682u, 91030699995u }, + { 13897060093813325824u, 15686237486658857211u, 219072070825u }, + { 4495486210810052608u, 1069073549290598129u, 169850352638u }, + { 14885496280087265280u, 4323599065125928507u, 254057954593u }, + { 4976477588244398080u, 17861823329752681586u, 33234382774u }, + { 11529215046068469760u, 17220149985412802078u, 182968291382u }, + { 0u, 4344934572159429184u, 54933506201u }, + { 0u, 2252927464837120000u, 153235539375u }, + { 0u, 10910018171964489728u, 175122131442u }, + { 0u, 3597328585515335680u, 242591433270u }, + { 0u, 6972808074239148032u, 54195011573u }, + { 0u, 2227030015734710272u, 245377996683u }, + { 0u, 1152921504606846976u, 139120727539u }, + { 0u, 0u, 243062500000u }, + { 0u, 0u, 160000000000u }, + { 10043594327130472635u, 23082446u, 0u }, + { 8336034337032909060u, 23082446544464339u, 0u }, + { 16717215784895280857u, 17238287503805244910u, 1251301u }, + { 10968831263951212032u, 1434575446038410275u, 229934489438u }, + { 5536629379734406065u, 14009569747841241694u, 94077768490u }, + { 1618806894932332402u, 14938795732275951328u, 42759460297u }, + { 11363331325254998861u, 6687653542888983473u, 201809833739u }, + { 8775167772751754888u, 28238723295162625u, 11362538425u }, + { 2898202945316114122u, 4745270274832691214u, 185001530824u }, + { 8868842714495185148u, 926478968112308824u, 200257241617u }, + { 17052094667531999136u, 9213681606604198526u, 17050224525u }, + { 3658615537031138594u, 13346223820579313024u, 141499474680u }, + { 12486952437987190746u, 691642518601291257u, 248723500243u }, + { 912079238520577629u, 1153720150033789192u, 211037494016u }, + { 10960072898031888041u, 12089015034721780810u, 62543294u }, + { 6129550094334741912u, 3555868702841788854u, 190655346818u }, + { 7965724516573729480u, 11708406782758214328u, 130192764028u }, + { 5801761178810791992u, 9417497762905343943u, 124634714003u }, + { 13197466483098446115u, 12838336066957615287u, 147510523576u }, + { 11326268638393107273u, 13737708142128207419u, 184695967592u }, + { 3597339351794947378u, 11683434809834695853u, 104744722650u }, + { 353880726151383714u, 2689114340106315837u, 218633360270u }, + { 11154818162602073600u, 8859225263374261032u, 142145777180u }, + { 5141043976157511026u, 15761671984578600096u, 28480259563u }, + { 7188225141808859034u, 7087267079878005352u, 235854441950u }, + { 13894168943295705185u, 4601291730423121377u, 222384201518u }, + { 12176538069834828288u, 9559411037059581623u, 46249436524u }, + { 7994239409235165184u, 12969820289641388091u, 108518216710u }, + { 13707777025480065024u, 13628239920285957130u, 6703095366u }, + { 10120227247676719104u, 8049893933765800625u, 70738788366u }, + { 7358494763030413312u, 10391755948840250234u, 14436385624u }, + { 7656119366529843200u, 14454650777462444512u, 88563338218u }, + { 9223372036854775808u, 14244638523341127254u, 234783588188u }, + { 0u, 12246016810439753984u, 92772203401u }, + { 0u, 9382741764551081984u, 137663857901u }, + { 0u, 4608696190291148800u, 237508639450u }, + { 0u, 1696483666416369664u, 218249837921u }, + { 0u, 15416683541605384192u, 97091966563u }, + { 0u, 7683140964294066176u, 99835740089u }, + { 0u, 4611686018427387904u, 185416503906u }, + { 0u, 0u, 98250000000u }, + { 0u, 0u, 128000000000u }, + { 3877189582299842749u, 352u, 0u }, + { 7625565791857948238u, 352210182868u, 0u }, + { 16568435163612007484u, 1722045467931902045u, 19u }, + { 17592071940521808130u, 16095324008152856733u, 19093352271u }, + { 15158637878035490831u, 15216188060094280738u, 79872529262u }, + { 17789243229146401893u, 10793385929903030893u, 110824871207u }, + { 14677686051252896484u, 12613277226875940039u, 39585110623u }, + { 3531237481269211986u, 10644539625155600107u, 95683767128u }, + { 8074435404989280910u, 6181262895644173983u, 88577041649u }, + { 7086549341467684427u, 148914399627082292u, 241335086933u }, + { 10556134770918626963u, 14379289774887985969u, 85008072665u }, + { 10772666134712966775u, 11743339675582627452u, 217779502860u }, + { 6195173298198112620u, 7841621929809463497u, 12636607719u }, + { 8144773843324250887u, 11168944680251236601u, 231425095176u }, + { 14144284817150924691u, 6178560202529287410u, 8605469704u }, + { 6464447844648863176u, 13295243308201596112u, 8334940419u }, + { 15760468443293179135u, 17040673746172470291u, 3720736583u }, + { 5172191224908322475u, 14957442487039409922u, 71923776774u }, + { 2357554307308969012u, 17778155426506992152u, 6810844581u }, + { 1602046917604361745u, 14945404984219733899u, 165963755736u }, + { 7830970218109515845u, 11590754866058681505u, 216810192027u }, + { 2899479134887821084u, 6020790784469412466u, 155628336080u }, + { 15792042302392017912u, 7934351824569522326u, 208326387722u }, + { 11286099112296056199u, 5038361112172116744u, 10430122074u }, + { 15161477829153947404u, 3305187319649924210u, 90273130103u }, + { 423831848142641767u, 11470175511099161552u, 119179174563u }, + { 9707413321046312582u, 7308362160352048610u, 163621799460u }, + { 10969483299803835620u, 10666410671225576634u, 36396187106u }, + { 4326479556120930304u, 2181639019945820785u, 226578227281u }, + { 12876227232041795584u, 4615749499734847918u, 81118266888u }, + { 16967986827791171584u, 14076159200958497580u, 8250220281u }, + { 1288146316538413056u, 5470405257862074105u, 249763070119u }, + { 13715290452691779584u, 4565741478181339543u, 167296551263u }, + { 4683743612465315840u, 8901832997861862329u, 95247509341u }, + { 0u, 14190141170191714122u, 93482569333u }, + { 0u, 4240772322245764096u, 117769249094u }, + { 0u, 4422842195340951552u, 70229892728u }, + { 0u, 15448426386733137920u, 120239762755u }, + { 0u, 9203504548935630848u, 67837460872u }, + { 0u, 5936377627571912704u, 136498922981u }, + { 0u, 468374361246531584u, 229321811676u }, + { 0u, 0u, 220025390625u }, + { 0u, 0u, 33000000000u }, + { 16344805304534272784u, 5374300u, 0u }, + { 26429987091348271u, 5374300886053671u, 0u }, + { 8691371289609838059u, 8020875056524075716u, 291341u }, + { 12980168378493046550u, 1400288714762747253u, 13434812508u }, + { 14010125458129496139u, 6136037711314764689u, 92075909803u }, + { 17061829677031795106u, 15735488086392394102u, 171332635270u }, + { 2227928323072698520u, 7735094782793634552u, 134853022518u }, + { 7394752319272287289u, 7273689191766726188u, 54419320328u }, + { 8805771303577744757u, 3410634565056431030u, 8394307481u }, + { 16371027194302248385u, 4600927904885215898u, 153184890870u }, + { 9114324123731231602u, 9154871331680374746u, 246249416801u }, + { 14468189808746991893u, 6117978272461042996u, 97496286569u }, + { 2376054557800684348u, 13116904339287496285u, 105331656266u }, + { 12462238943546048571u, 867037205615660831u, 74711068809u }, + { 15334855370842605909u, 1802487145191504830u, 137047002181u }, + { 7211347914013798462u, 17242009718457409007u, 69097713023u }, + { 13763642332572548877u, 13620802355488468049u, 127934691219u }, + { 16868268377740071383u, 4442227880594435745u, 147738385175u }, + { 1120013377627684177u, 17354849212854314181u, 23240813655u }, + { 15898107650717274388u, 18202319179831567886u, 87940808260u }, + { 12281923376333274277u, 17568634016348874558u, 68986749699u }, + { 6383530489286615044u, 7496925598312450672u, 3952397558u }, + { 4253822060257126466u, 601870379496813865u, 246406409151u }, + { 17614372438391501998u, 11995106565680728027u, 191032627458u }, + { 4309951310554333450u, 16331071694764184179u, 2650256029u }, + { 13106185988973773020u, 9665962217000524208u, 157885309170u }, + { 2070134359761960547u, 13682661374415474390u, 242523992861u }, + { 9399359914137865875u, 6940361789924260864u, 29741738559u }, + { 17415053284723541264u, 9658039831644010465u, 63376237766u }, + { 4840502610448261120u, 6843715893910236922u, 198523563388u }, + { 5690599259712258048u, 47089792870595660u, 124370998582u }, + { 114769594245185536u, 14510386192097156932u, 54002552742u }, + { 3150620882578178048u, 12059931208360040296u, 166786609611u }, + { 5136918324969472000u, 14877013468459184620u, 203653770180u }, + { 7205759403792793600u, 2397668560671695044u, 196806484516u }, + { 0u, 2195572305559232232u, 36129977873u }, + { 0u, 3261686279425953792u, 17119022213u }, + { 0u, 9333850662059900928u, 133176816367u }, + { 0u, 5036522340217782272u, 239505989058u }, + { 0u, 2800120215143186432u, 194273030423u }, + { 0u, 441634238459019264u, 23151794821u }, + { 0u, 720575940379279360u, 133023941040u }, + { 0u, 0u, 176039062500u }, + { 0u, 0u, 228000000000u }, + { 98202693831717807u, 82u, 0u }, + { 12909287260170414079u, 82005323578u, 0u }, + { 11728631949380786233u, 8218347283861607400u, 4u }, + { 18263701925522197718u, 17896200385973633643u, 4445517498u }, + { 4868886449713321591u, 16333242102094352209u, 186970154966u }, + { 5870139507184082354u, 9981905728606788815u, 214885426828u }, + { 5978562500822661575u, 15219470018924839012u, 140541120193u }, + { 10821136839630268472u, 17152070168529617370u, 193825049122u }, + { 9490907630136752916u, 17841343440958328027u, 34929815586u }, + { 10564005678001613989u, 17291078023923990493u, 34967181165u }, + { 5754050547468481222u, 16744804581790759223u, 109937351217u }, + { 3833909949855542602u, 5001622214111594905u, 49907737675u }, + { 15446538552665967784u, 9676746897435398146u, 75271138483u }, + { 14924837848804399130u, 8109025833995118532u, 179524577500u }, + { 18140170340418344208u, 5495826424046694744u, 220439591171u }, + { 11380424819825208971u, 7890288164365705852u, 3297929347u }, + { 12416915664152252547u, 8616438349039895217u, 131427733378u }, + { 17195282241626289958u, 15787154801788760618u, 130467098058u }, + { 2649541045825281326u, 12418659311480782502u, 202855823376u }, + { 16577429864268509676u, 4486988874116669987u, 16673216870u }, + { 12051257060168107241u, 4828971301551875409u, 102243240154u }, + { 5363884561143470797u, 14769106422014442226u, 218261779058u }, + { 13054029903083620184u, 7763933466423188156u, 114800634863u }, + { 566314952158634945u, 10449097116253839963u, 239420883676u }, + { 1329472079642345682u, 12870692502472900571u, 220566446689u }, + { 15686237486658857211u, 11597479481311003817u, 97697721638u }, + { 1069073549290598129u, 8294994869530047486u, 38628700622u }, + { 4323599065125928507u, 16879315829924478241u, 206449672572u }, + { 17861823329752681586u, 11873324837601439670u, 124915029544u }, + { 17220149985412802078u, 3277599055636107318u, 40643654229u }, + { 4344934572159429184u, 15363467897354242201u, 85177679000u }, + { 2252927464837120000u, 10351182204479784367u, 152832855263u }, + { 10910018171964489728u, 12811517584931924466u, 223561138711u }, + { 3597328585515335680u, 16988930699558748726u, 23694513759u }, + { 6972808074239148032u, 11683499918824718325u, 95920971778u }, + { 2227030015734710272u, 13119300691281647499u, 2633363799u }, + { 1152921504606846976u, 10125549106595354099u, 87711198715u }, + { 0u, 17505352699870800544u, 251548907116u }, + { 0u, 6756039242241163264u, 108948967071u }, + { 0u, 3537338758766526464u, 159366245621u }, + { 0u, 6522626374119718912u, 245191759518u }, + { 0u, 4733294203482669056u, 158353592284u }, + { 0u, 16997710893603094528u, 220256592392u }, + { 0u, 16717361816799281152u, 8921447753u }, + { 0u, 0u, 73906250000u }, + { 0u, 0u, 16000000000u }, + { 17238287503805244910u, 1251301u, 0u }, + { 1434575446038410275u, 1251301934489438u, 0u }, + { 14009569747841241694u, 3943737498063000362u, 67833u }, + { 14938795732275951328u, 2870731037991212489u, 249213790438u }, + { 6687653542888983473u, 7389433400402095883u, 230155622641u }, + { 28238723295162625u, 5675049236146197433u, 241400581987u }, + { 4745270274832691214u, 9953779846262904264u, 99307645035u }, + { 926478968112308824u, 12691978937179636241u, 107539595486u }, + { 9213681606604198526u, 15523327331528198029u, 222688033556u }, + { 13346223820579313024u, 15722603279568118520u, 20841521260u }, + { 691642518601291257u, 11838632364171816147u, 108852324031u }, + { 1153720150033789192u, 7832751832367143680u, 191641773546u }, + { 12089015034721780810u, 12167724027162940862u, 234424614327u }, + { 3555868702841788854u, 4108211144748152962u, 183659613641u }, + { 11708406782758214328u, 7530983398136343676u, 201222706572u }, + { 9417497762905343943u, 1117587133956542355u, 140408255428u }, + { 12838336066957615287u, 17134748625149490872u, 196060584519u }, + { 13737708142128207419u, 4039918359454207848u, 71928876584u }, + { 11683434809834695853u, 1830218764589441242u, 40219004413u }, + { 2689114340106315837u, 637895981480825742u, 253099216358u }, + { 8859225263374261032u, 8246879226348334620u, 230034580410u }, + { 15761671984578600096u, 12389239568142583275u, 186447064218u }, + { 7087267079878005352u, 14041257178803154398u, 154671622022u }, + { 4601291730423121377u, 16312515716494630702u, 134761178076u }, + { 9559411037059581623u, 17088522799596987756u, 220884303248u }, + { 12969820289641388091u, 3588932524637852678u, 144926370677u }, + { 13628239920285957130u, 107218049069817414u, 117194556422u }, + { 8049893933765800625u, 1596707240462008334u, 6005812302u }, + { 10391755948840250234u, 17461913142391587672u, 78086557672u }, + { 14454650777462444512u, 4366474266651610090u, 232946612208u }, + { 14244638523341127254u, 5539304013194805084u, 240236707044u }, + { 12246016810439753984u, 4762470619211987849u, 228300286272u }, + { 9382741764551081984u, 10835638458986644717u, 64258174049u }, + { 4608696190291148800u, 16141642290510052058u, 97587401137u }, + { 1696483666416369664u, 17390568670756355425u, 177875040181u }, + { 15416683541605384192u, 12536768491333867107u, 181942744616u }, + { 7683140964294066176u, 13145148522871947193u, 40679619581u }, + { 4611686018427387904u, 5665349945233068642u, 253712599929u }, + { 0u, 17074607537751066240u, 121307119235u }, + { 0u, 6241525660962062336u, 131925616329u }, + { 0u, 1142860629783085056u, 201338353784u }, + { 0u, 16287527416870469632u, 120061954598u }, + { 0u, 9028002014738513920u, 38882948630u }, + { 0u, 16217462258161156096u, 22489408969u }, + { 0u, 11529215046068469760u, 201879150390u }, + { 0u, 0u, 54625000000u }, + { 0u, 0u, 64000000000u }, + { 1722045467931902045u, 19u, 0u }, + { 16095324008152856733u, 19093352271u, 0u }, + { 15216188060094280738u, 646608198162977646u, 1u }, + { 10793385929903030893u, 12170458846894708007u, 1035052700u }, + { 12613277226875940039u, 1797330480103086687u, 156659761896u }, + { 10644539625155600107u, 10332188564497263448u, 232097433480u }, + { 6181262895644173983u, 7524259485079594225u, 136560109064u }, + { 148914399627082292u, 62681109059153749u, 8407890924u }, + { 14379289774887985969u, 13480636451804037081u, 236003397949u }, + { 11743339675582627452u, 6948168233012789004u, 61730786766u }, + { 7841621929809463497u, 12015502974041806055u, 206376660954u }, + { 11168944680251236601u, 7343801660689004040u, 218651361721u }, + { 6178560202529287410u, 13670580858640731144u, 185398108285u }, + { 13295243308201596112u, 5605073897566574851u, 125741083673u }, + { 17040673746172470291u, 15387788940505247559u, 25303851664u }, + { 14957442487039409922u, 17565181499678113030u, 144834173709u }, + { 17778155426506992152u, 1893743623847493029u, 13952210397u }, + { 14945404984219733899u, 10243498996716269784u, 221102660047u }, + { 11590754866058681505u, 5619675836950314139u, 207555301193u }, + { 6020790784469412466u, 10224869737511515088u, 73304643237u }, + { 7934351824569522326u, 2574495974386198538u, 165554291299u }, + { 5038361112172116744u, 7825756347302873178u, 99139563706u }, + { 3305187319649924210u, 12071550103794656887u, 186424235101u }, + { 11470175511099161552u, 7195875213867606691u, 93654400042u }, + { 7308362160352048610u, 18271364438406891044u, 42390089176u }, + { 10666410671225576634u, 16966521933952564706u, 216990492650u }, + { 2181639019945820785u, 289920862029570129u, 234919756997u }, + { 4615749499734847918u, 7804199568098625032u, 197015716641u }, + { 14076159200958497580u, 5758118571242446585u, 33423066506u }, + { 5470405257862074105u, 4030788293606375591u, 138312148233u }, + { 4565741478181339543u, 4387716460037196127u, 9218509471u }, + { 8901832997861862329u, 16807506478881285981u, 159237858585u }, + { 14190141170191714122u, 17033060604413529717u, 25911136751u }, + { 4240772322245764096u, 10498418508292170054u, 239923364065u }, + { 4422842195340951552u, 13237752038744465016u, 225569120407u }, + { 15448426386733137920u, 17737618428304633155u, 151717619975u }, + { 9203504548935630848u, 13546183833248825736u, 7961558221u }, + { 5936377627571912704u, 826778452978976229u, 205734340097u }, + { 468374361246531584u, 13728076626990147292u, 1044819749u }, + { 0u, 2794860281883592225u, 37744200525u }, + { 0u, 8680705720425908736u, 77151509679u }, + { 0u, 731520517439488000u, 175470582000u }, + { 0u, 13120812320768917504u, 240039655806u }, + { 0u, 2722954908557901824u, 126711280661u }, + { 0u, 6860847004205973504u, 21147611681u }, + { 0u, 6503197861922996224u, 33371927261u }, + { 0u, 9223372036854775808u, 221352539062u }, + { 0u, 0u, 182500000000u }, + { 8020875056524075716u, 291341u, 0u }, + { 1400288714762747253u, 291341434812508u, 0u }, + { 6136037711314764689u, 12005656413127238315u, 15793u }, + { 15735488086392394102u, 4821130826186787462u, 177650827938u }, + { 7735094782793634552u, 14377899467066168118u, 162261354025u }, + { 7273689191766726188u, 16575613239625444872u, 41779427491u }, + { 3410634565056431030u, 4317827099179284377u, 163898565794u }, + { 4600927904885215898u, 1242354770412171254u, 162234069876u }, + { 9154871331680374746u, 994838588328896609u, 116067348187u }, + { 6117978272461042996u, 17283309862013060457u, 219053930307u }, + { 13116904339287496285u, 124242522249856586u, 67936930105u }, + { 867037205615660831u, 11564608014666985609u, 57006735200u }, + { 1802487145191504830u, 12401028575581654085u, 96626918656u }, + { 17242009718457409007u, 2490725392961465727u, 672261106u }, + { 13620802355488468049u, 1949482237120640915u, 242135022494u }, + { 4442227880594435745u, 15410502396166200087u, 158105681643u }, + { 17354849212854314181u, 15694919529799920727u, 235835405008u }, + { 18202319179831567886u, 10324869370171768388u, 208850823292u }, + { 17568634016348874558u, 1631866459122189059u, 124559712290u }, + { 7496925598312450672u, 172020494461226230u, 34088463658u }, + { 601870379496813865u, 12734610307908856767u, 42009325249u }, + { 11995106565680728027u, 1467513250829340930u, 193690344608u }, + { 16331071694764184179u, 13558759428494307997u, 160079554052u }, + { 9665962217000524208u, 7915355143999496434u, 4735021821u }, + { 13682661374415474390u, 2876370200608797469u, 253429092262u }, + { 6940361789924260864u, 343685370404989503u, 166155928341u }, + { 9658039831644010465u, 4837266557407634630u, 21018631221u }, + { 6843715893910236922u, 9622591415747161468u, 53262228745u }, + { 47089792870595660u, 16503783814424220982u, 9521641725u }, + { 14510386192097156932u, 5377083431343591334u, 253894671913u }, + { 12059931208360040296u, 16508482371299291595u, 41291492276u }, + { 14877013468459184620u, 10515883558812249028u, 180894926622u }, + { 2397668560671695044u, 63492062913405476u, 30570067190u }, + { 2195572305559232232u, 11571919759617799697u, 246003441911u }, + { 3261686279425953792u, 2956602334970088581u, 247627315027u }, + { 9333850662059900928u, 13604736747717849839u, 83160277733u }, + { 5036522340217782272u, 16573540719338151362u, 229737514256u }, + { 2800120215143186432u, 12620703004601168151u, 16898453442u }, + { 441634238459019264u, 14649407809089591941u, 194684169680u }, + { 720575940379279360u, 11290375247898624432u, 208794145988u }, + { 0u, 11020319450292874212u, 196612052468u }, + { 0u, 8754634933362354176u, 244597412714u }, + { 0u, 12976319450332528640u, 106474589710u }, + { 0u, 17447331119627239424u, 14703447686u }, + { 0u, 3665184902673858560u, 134945821715u }, + { 0u, 12949678516038795264u, 19198690071u }, + { 0u, 72057594037927936u, 23702003479u }, + { 0u, 0u, 23003906250u }, + { 0u, 0u, 202000000000u }, + { 8218347283861607400u, 4u, 0u }, + { 17896200385973633643u, 4445517498u, 0u }, + { 16333242102094352209u, 4445517498970154966u, 0u }, + { 9981905728606788815u, 9413159735776077452u, 240991986u }, + { 15219470018924839012u, 14279163482889998017u, 242510288411u }, + { 17152070168529617370u, 8693044629541194274u, 27774075003u }, + { 17841343440958328027u, 11863110253260222498u, 123471250893u }, + { 17291078023923990493u, 8319293368489531245u, 205643100495u }, + { 16744804581790759223u, 3376307525676489265u, 79450989797u }, + { 5001622214111594905u, 13205662254759912523u, 229183029997u }, + { 9676746897435398146u, 5276250334231686323u, 237715880385u }, + { 8109025833995118532u, 13790198520922745052u, 193286026103u }, + { 5495826424046694744u, 14195535250150996227u, 119747568159u }, + { 7890288164365705852u, 16425228796427004035u, 31769541507u }, + { 8616438349039895217u, 4295900841296269186u, 131890413437u }, + { 15787154801788760618u, 4533952595483946442u, 125232881251u }, + { 12418659311480782502u, 12885038019373447184u, 99245786062u }, + { 4486988874116669987u, 12140736240487831910u, 206698499310u }, + { 4828971301551875409u, 6927124077155322074u, 238658150630u }, + { 14769106422014442226u, 12477788342407819890u, 230375520148u }, + { 7763933466423188156u, 7980854329409711087u, 148676422261u }, + { 10449097116253839963u, 2062671021810827996u, 117432642980u }, + { 12870692502472900571u, 2739521363598172769u, 164111817620u }, + { 11597479481311003817u, 12897585686593465638u, 148148509750u }, + { 8294994869530047486u, 1127632646629044686u, 54699179521u }, + { 16879315829924478241u, 4833775019274666364u, 1061129088u }, + { 11873324837601439670u, 15867662672939849256u, 128262039468u }, + { 3277599055636107318u, 2092350330982953557u, 172860187717u }, + { 15363467897354242201u, 13330062299842493592u, 69113426538u }, + { 10351182204479784367u, 4479193352178519263u, 106722624125u }, + { 12811517584931924466u, 3149393938889064983u, 125242817558u }, + { 16988930699558748726u, 9736379904070620767u, 22170728987u }, + { 11683499918824718325u, 3816238703055069186u, 27527810212u }, + { 13119300691281647499u, 11598915938798661975u, 164206878714u }, + { 10125549106595354099u, 17821633264606555643u, 250628778492u }, + { 17505352699870800544u, 2514623558764574316u, 252966112675u }, + { 6756039242241163264u, 4976730480406253215u, 163136318016u }, + { 3537338758766526464u, 17276563697191611637u, 64269789099u }, + { 6522626374119718912u, 12524734095940998814u, 171936564394u }, + { 4733294203482669056u, 15331551308930355164u, 170678967195u }, + { 16997710893603094528u, 15417115581125943816u, 155831125061u }, + { 16717361816799281152u, 6010750237807115593u, 69835763510u }, + { 0u, 5624630987553628432u, 54325843423u }, + { 0u, 14881848243837640704u, 223304911856u }, + { 0u, 15281613886881529856u, 240806746609u }, + { 0u, 14057902358273196032u, 241828417948u }, + { 0u, 16075318494433902592u, 156762080413u }, + { 0u, 13891916000577716224u, 157871444761u }, + { 0u, 7205759403792793600u, 25753082275u }, + { 0u, 0u, 163390625000u }, + { 0u, 0u, 232000000000u }, + { 3943737498063000362u, 67833u, 0u }, + { 2870731037991212489u, 67833213790438u, 0u }, + { 7389433400402095883u, 4535831408134330609u, 3677u }, + { 5675049236146197433u, 6204770794376564579u, 93245887913u }, + { 9953779846262904264u, 13869812122751887467u, 169336361298u }, + { 12691978937179636241u, 14253229412394467550u, 82751884021u }, + { 15523327331528198029u, 12776557610216045332u, 245772669114u }, + { 15722603279568118520u, 16493640728678654060u, 186692618575u }, + { 11838632364171816147u, 9434398296825833151u, 79894122055u }, + { 7832751832367143680u, 8773374058285327850u, 71511439756u }, + { 12167724027162940862u, 12932015276748029367u, 140475605560u }, + { 4108211144748152962u, 16293958583527755209u, 56701045952u }, + { 7530983398136343676u, 13511893936143127948u, 192883297264u }, + { 1117587133956542355u, 18409936402005226436u, 240732481237u }, + { 17134748625149490872u, 2189663026458466887u, 213998004652u }, + { 4039918359454207848u, 9497725274248154664u, 172118701870u }, + { 1830218764589441242u, 14766925481127792125u, 46514872718u }, + { 637895981480825742u, 6982373971809635814u, 142800516634u }, + { 8246879226348334620u, 8616702383006884794u, 26378515251u }, + { 12389239568142583275u, 3059473300040871066u, 51467112372u }, + { 14041257178803154398u, 17123843157031495558u, 180165854379u }, + { 16312515716494630702u, 11210627174210626524u, 171928285397u }, + { 17088522799596987756u, 15868067138625928592u, 213607729316u }, + { 3588932524637852678u, 4467869511636937589u, 164860209643u }, + { 107218049069817414u, 10052108125844341766u, 235242203691u }, + { 1596707240462008334u, 7470588003218451534u, 43544925873u }, + { 17461913142391587672u, 2613527085490786280u, 177404981387u }, + { 4366474266651610090u, 3632919450036549616u, 139141679587u }, + { 5539304013194805084u, 179367907231218916u, 227196940958u }, + { 4762470619211987849u, 13553068184555874624u, 158009723553u }, + { 10835638458986644717u, 8798774862365584481u, 161734713298u }, + { 16141642290510052058u, 910911255817064881u, 210476982541u }, + { 17390568670756355425u, 2304331144765093813u, 13049380598u }, + { 12536768491333867107u, 12248937023083640360u, 246124918041u }, + { 13145148522871947193u, 10206039550662130685u, 25664016206u }, + { 5665349945233068642u, 12267881323837852537u, 78553270512u }, + { 17074607537751066240u, 2858642007937891971u, 240665043179u }, + { 6241525660962062336u, 14171330289750320841u, 235154967293u }, + { 1142860629783085056u, 6601103619749017720u, 253768229354u }, + { 16287527416870469632u, 4919573414486739494u, 234357846544u }, + { 9028002014738513920u, 3401998285294974486u, 16266690609u }, + { 16217462258161156096u, 10799436256515532233u, 49184422696u }, + { 11529215046068469760u, 10083786644665753398u, 40585438612u }, + { 0u, 6481194517685688896u, 148546643169u }, + { 0u, 15104161756860547072u, 225351346258u }, + { 0u, 9556039274244079616u, 82818798249u }, + { 0u, 1376343134954323968u, 169518033927u }, + { 0u, 15682488278596976640u, 7074611710u }, + { 0u, 1506454075355430912u, 254850149393u }, + { 0u, 1152921504606846976u, 17081665039u }, + { 0u, 0u, 15062500000u }, + { 0u, 0u, 160000000000u }, + { 12170458846894708007u, 1035052700u, 0u }, + { 1797330480103086687u, 1035052700659761896u, 0u }, + { 10332188564497263448u, 6172559441576707976u, 56110319u }, + { 7524259485079594225u, 15083329738554729992u, 239334615117u }, + { 62681109059153749u, 10013126833549229036u, 77817668943u }, + { 13480636451804037081u, 5817156823499936061u, 79542812693u }, + { 6948168233012789004u, 5282692560913632718u, 21315348703u }, + { 12015502974041806055u, 10252307034225766362u, 223286375337u }, + { 7343801660689004040u, 17981881283247669689u, 169555778677u }, + { 13670580858640731144u, 11689290159733383293u, 117974799737u }, + { 5605073897566574851u, 5530668968487988249u, 121633677689u }, + { 15387788940505247559u, 10083765740821947024u, 121299818165u }, + { 17565181499678113030u, 2798423656816843533u, 181546642036u }, + { 1893743623847493029u, 7614494481582904797u, 116151702850u }, + { 10243498996716269784u, 17811318500083423695u, 66412782572u }, + { 5619675836950314139u, 11641467412200329033u, 236965553510u }, + { 10224869737511515088u, 17733593025296340645u, 102631085212u }, + { 2574495974386198538u, 3689424000190644835u, 156961340004u }, + { 7825756347302873178u, 14966634145516728506u, 100200004075u }, + { 12071550103794656887u, 14171681941562070109u, 235811342862u }, + { 7195875213867606691u, 8130575762882608170u, 14768248417u }, + { 18271364438406891044u, 5234550794400656856u, 97440759395u }, + { 16966521933952564706u, 3020576149360486378u, 99283765567u }, + { 289920862029570129u, 3038675756589057221u, 63163745761u }, + { 7804199568098625032u, 15470260187120878369u, 225164726942u }, + { 5758118571242446585u, 3497929414841828746u, 158838644485u }, + { 4030788293606375591u, 9935840636861015305u, 5189623133u }, + { 4387716460037196127u, 3647355485153741471u, 93538623000u }, + { 16807506478881285981u, 766100215038272793u, 24197723537u }, + { 17033060604413529717u, 16128087474216800751u, 145041530375u }, + { 10498418508292170054u, 16216631732633731297u, 7874305373u }, + { 13237752038744465016u, 13760220872779997335u, 93879105367u }, + { 17737618428304633155u, 3826276262374222087u, 87745943068u }, + { 13546183833248825736u, 14938032745839181005u, 28207422851u }, + { 826778452978976229u, 14479259995009508865u, 131809792377u }, + { 13728076626990147292u, 2372033248156102437u, 121784922257u }, + { 2794860281883592225u, 792005346826701645u, 145128588180u }, + { 8680705720425908736u, 16278924527931792559u, 148042934695u }, + { 731520517439488000u, 17442516423538940144u, 167882482266u }, + { 13120812320768917504u, 13844184233048446u, 90945560710u }, + { 2722954908557901824u, 13486193870480782357u, 134000750494u }, + { 6860847004205973504u, 11931315179184648737u, 158731088034u }, + { 6503197861922996224u, 16492562205587485405u, 162646797891u }, + { 9223372036854775808u, 12128987217680380854u, 67894063588u }, + { 0u, 10568123814189138176u, 228657513714u }, + { 0u, 17007583519117541376u, 242572899139u }, + { 0u, 143791533903052800u, 67921982950u }, + { 0u, 12398714235792654336u, 230007794954u }, + { 0u, 9659957317919047680u, 10672135645u }, + { 0u, 9412523221204336640u, 221523667335u }, + { 0u, 4611686018427387904u, 135510253906u }, + { 0u, 0u, 82250000000u }, + { 0u, 0u, 128000000000u }, + { 12005656413127238315u, 15793u, 0u }, + { 4821130826186787462u, 15793650827938u, 0u }, + { 14377899467066168118u, 3237900842885170729u, 856u }, + { 16575613239625444872u, 7515893506498066595u, 88175526956u }, + { 4317827099179284377u, 7300206309181072546u, 44407437403u }, + { 1242354770412171254u, 5999737279837044u, 91395744977u }, + { 994838588328896609u, 7556839307242450651u, 209000325246u }, + { 17283309862013060457u, 12946035041643640643u, 126409657079u }, + { 124242522249856586u, 15885877642352740665u, 247701805965u }, + { 11564608014666985609u, 10770818348246089568u, 141861175152u }, + { 12401028575581654085u, 11635415503599551744u, 112583887232u }, + { 2490725392961465727u, 6248053924100826098u, 128630757138u }, + { 1949482237120640915u, 16894170802729859998u, 18338707681u }, + { 15410502396166200087u, 6143589029651889899u, 225915834834u }, + { 15694919529799920727u, 11812087701837886160u, 210333044628u }, + { 10324869370171768388u, 7306705080150829180u, 148640334557u }, + { 1631866459122189059u, 1485332570280714274u, 221396097276u }, + { 172020494461226230u, 18042602303295630634u, 252080520039u }, + { 12734610307908856767u, 13397029889257074369u, 103978091430u }, + { 1467513250829340930u, 9948104869613411488u, 166726254445u }, + { 13558759428494307997u, 10836066241170646532u, 109539287845u }, + { 7915355143999496434u, 18330574781234459389u, 37587424327u }, + { 2876370200608797469u, 666297360208433062u, 71993702450u }, + { 343685370404989503u, 5035352224889324309u, 50036120052u }, + { 4837266557407634630u, 1341745796439923765u, 244272966991u }, + { 9622591415747161468u, 6846932182653803785u, 79072736185u }, + { 16503783814424220982u, 6727685027257825533u, 185371172937u }, + { 5377083431343591334u, 2168538874806877737u, 73364708536u }, + { 16508482371299291595u, 17694936100676971444u, 184117556727u }, + { 10515883558812249028u, 2163944241059563294u, 247959244408u }, + { 63492062913405476u, 6727780864524301558u, 120117307652u }, + { 11571919759617799697u, 8599551977795002615u, 4364713731u }, + { 2956602334970088581u, 15428264807806859091u, 3466182646u }, + { 13604736747717849839u, 2126771385339683557u, 246836367911u }, + { 16573540719338151362u, 15094316562082972944u, 39115292507u }, + { 12620703004601168151u, 8111300598225956802u, 91818264540u }, + { 14649407809089591941u, 9481215200564260304u, 220439714486u }, + { 11290375247898624432u, 16836674128623424708u, 182513977705u }, + { 11020319450292874212u, 7087243115299722740u, 105912717933u }, + { 8754634933362354176u, 2343560867338408810u, 109384200219u }, + { 12976319450332528640u, 3431385749090422286u, 27127044689u }, + { 17447331119627239424u, 3504545517469224582u, 81186015794u }, + { 3665184902673858560u, 3333759805712094227u, 50189981793u }, + { 12949678516038795264u, 3595183476205994775u, 97180723481u }, + { 72057594037927936u, 14191566632569921303u, 25194895286u }, + { 0u, 12917427671358095562u, 182769326368u }, + { 0u, 3883793922738316288u, 32700255157u }, + { 0u, 7857281689266421760u, 181210540890u }, + { 0u, 15987081651486195712u, 90425944093u }, + { 0u, 16827562156399525888u, 29866661432u }, + { 0u, 7012737938513461248u, 56912223972u }, + { 0u, 7385903388887613440u, 228380161285u }, + { 0u, 0u, 5400390625u }, + { 0u, 0u, 225000000000u }, + { 9413159735776077452u, 240991986u, 0u }, + { 14279163482889998017u, 240991986510288411u, 0u }, + { 8693044629541194274u, 14135788013842776187u, 13064201u }, + { 11863110253260222498u, 13284322918167594445u, 9766302603u }, + { 8319293368489531245u, 7264587765474046287u, 139720144588u }, + { 3376307525676489265u, 16176482219778368741u, 204393814091u }, + { 13205662254759912523u, 5401983818872095469u, 75876928858u }, + { 5276250334231686323u, 11208857446851049921u, 90292842129u }, + { 13790198520922745052u, 13794690008281035639u, 145607633379u }, + { 14195535250150996227u, 14519782740993303071u, 227747811643u }, + { 16425228796427004035u, 10885858587044789123u, 59787118999u }, + { 4295900841296269186u, 8710500938899914621u, 151590123576u }, + { 4533952595483946442u, 1284182587483102819u, 56472197202u }, + { 12885038019373447184u, 10346074482131502030u, 82069615677u }, + { 12140736240487831910u, 9429804686255246574u, 61560861821u }, + { 6927124077155322074u, 6412022633845121254u, 125511190736u }, + { 12477788342407819890u, 8892351297529018260u, 208347596443u }, + { 7980854329409711087u, 14098160105983060597u, 155482055329u }, + { 2062671021810827996u, 13793833029739474340u, 161764262790u }, + { 2739521363598172769u, 16367653765996977044u, 134747765186u }, + { 12897585686593465638u, 10684788343333772342u, 194887292288u }, + { 1127632646629044686u, 13272681218705145345u, 128579223536u }, + { 4833775019274666364u, 11093568615497829248u, 240719513490u }, + { 15867662672939849256u, 12488220765137758124u, 146601383559u }, + { 2092350330982953557u, 3727114642519696453u, 135676987804u }, + { 13330062299842493592u, 11549865375695057514u, 156202047289u }, + { 4479193352178519263u, 11292809154908783229u, 57626119456u }, + { 3149393938889064983u, 17723904861837310998u, 32612184410u }, + { 9736379904070620767u, 14877674388187150875u, 90960814807u }, + { 3816238703055069186u, 12178961950105734308u, 215806520344u }, + { 11598915938798661975u, 4540604068069253114u, 24660222850u }, + { 17821633264606555643u, 13832478722153359868u, 130246146639u }, + { 2514623558764574316u, 1308046668730371491u, 79749860174u }, + { 4976730480406253215u, 18400531023544756800u, 78070909351u }, + { 17276563697191611637u, 9789823458621466539u, 167997494785u }, + { 12524734095940998814u, 1924870562610267306u, 1530707393u }, + { 15331551308930355164u, 5290016144582400923u, 193104347442u }, + { 15417115581125943816u, 15162883663174059077u, 50286772349u }, + { 6010750237807115593u, 8078086116520046390u, 125821981570u }, + { 5624630987553628432u, 15731407332173190623u, 130437913925u }, + { 14881848243837640704u, 5346389182763011056u, 69852801300u }, + { 15281613886881529856u, 6368422217216252401u, 20289828338u }, + { 14057902358273196032u, 2961453088119116188u, 242345232860u }, + { 16075318494433902592u, 10932141691610170525u, 220160540693u }, + { 13891916000577716224u, 11034016191361782553u, 21592632588u }, + { 7205759403792793600u, 5455325785621453219u, 12598155216u }, + { 0u, 7735615202566149352u, 208295733803u }, + { 0u, 7502396497775759360u, 43419348540u }, + { 0u, 1601286435751591936u, 60406705729u }, + { 0u, 11449383158571597824u, 65086805911u }, + { 0u, 13043944595690356736u, 151620672304u }, + { 0u, 7773494431818186752u, 48707113653u }, + { 0u, 9943947977234055168u, 181421401977u }, + { 0u, 0u, 121539062500u }, + { 0u, 0u, 228000000000u }, + { 4535831408134330609u, 3677u, 0u }, + { 6204770794376564579u, 3677245887913u, 0u }, + { 13869812122751887467u, 6343817245135589714u, 199u }, + { 14253229412394467550u, 17549323075660516085u, 199343899021u }, + { 12776557610216045332u, 3948641822109421754u, 141951350710u }, + { 16493640728678654060u, 1750739713693534543u, 182214056302u }, + { 9434398296825833151u, 962163898128633415u, 110094907790u }, + { 8773374058285327850u, 7967320249386531212u, 142052159009u }, + { 12932015276748029367u, 3018466665533383224u, 33431909296u }, + { 16293958583527755209u, 15076865731854945472u, 176163631405u }, + { 13511893936143127948u, 691187172844604400u, 45817318529u }, + { 18409936402005226436u, 13274492813370992341u, 129037469331u }, + { 2189663026458466887u, 6364168818499152300u, 147719611697u }, + { 9497725274248154664u, 17599380787401914158u, 49345002282u }, + { 14766925481127792125u, 3782323149461692814u, 42954064344u }, + { 6982373971809635814u, 14470163442442237466u, 216205040148u }, + { 8616702383006884794u, 476109872130437939u, 20784429132u }, + { 3059473300040871066u, 16330548844673355700u, 76025809967u }, + { 17123843157031495558u, 14089158961463739563u, 47885280826u }, + { 11210627174210626524u, 13385510793074798805u, 58763774837u }, + { 15868067138625928592u, 1549401308746959012u, 117725629994u }, + { 4467869511636937589u, 4607384943843027435u, 42083993213u }, + { 10052108125844341766u, 5157353797716093483u, 125249766838u }, + { 7470588003218451534u, 10846828782671550129u, 182279580709u }, + { 2613527085490786280u, 9915857350819131531u, 37588007766u }, + { 3632919450036549616u, 1673544973504317923u, 86537539704u }, + { 179367907231218916u, 14780986291622785694u, 120090723054u }, + { 13553068184555874624u, 8168111319515466401u, 238801278872u }, + { 8798774862365584481u, 16345760387859734482u, 152442794201u }, + { 910911255817064881u, 3177475373321281805u, 217886105446u }, + { 2304331144765093813u, 2558676822419554038u, 102172251285u }, + { 12248937023083640360u, 8813474062662382873u, 149138706148u }, + { 10206039550662130685u, 5426294560236228430u, 228477779386u }, + { 12267881323837852537u, 9919177474128333040u, 186294160017u }, + { 2858642007937891971u, 6197383943089627371u, 145537719688u }, + { 14171330289750320841u, 13673239314867423997u, 136335960856u }, + { 6601103619749017720u, 9309584098968723946u, 24741227788u }, + { 4919573414486739494u, 4647101757759615504u, 12504673565u }, + { 3401998285294974486u, 1405809295505096753u, 29251919891u }, + { 10799436256515532233u, 11332704079573859112u, 19076209074u }, + { 10083786644665753398u, 2960072434514044308u, 178614347119u }, + { 6481194517685688896u, 3887266602785432801u, 111160465848u }, + { 15104161756860547072u, 14545546084687849554u, 184210729144u }, + { 9556039274244079616u, 4617763804182385321u, 184788515633u }, + { 1376343134954323968u, 7857823815580249095u, 49250329477u }, + { 15682488278596976640u, 10939326736548364798u, 133425973482u }, + { 1506454075355430912u, 12262012446566951953u, 234593022090u }, + { 1152921504606846976u, 12555024338687723023u, 138664725026u }, + { 0u, 3332969632922829472u, 34680609233u }, + { 0u, 15535060143360327680u, 209180680645u }, + { 0u, 15794322927987458048u, 197842157297u }, + { 0u, 10571474314433921024u, 241856211961u }, + { 0u, 16679514427547975680u, 249573080770u }, + { 0u, 16925653299565166592u, 194904198288u }, + { 0u, 16717361816799281152u, 144917541503u }, + { 0u, 0u, 127906250000u }, + { 0u, 0u, 16000000000u }, + { 6172559441576707976u, 56110319u, 0u }, + { 15083329738554729992u, 56110319334615117u, 0u }, + { 10013126833549229036u, 9335385384027907407u, 3041746u }, + { 5817156823499936061u, 13237828406194798613u, 210506072255u }, + { 5282692560913632718u, 15667486867836528863u, 191717624115u }, + { 10252307034225766362u, 17982325043592934313u, 51849336164u }, + { 17981881283247669689u, 17159117626917379189u, 100974823793u }, + { 11689290159733383293u, 8336208968408929657u, 113930197630u }, + { 5530668968487988249u, 12767090573379150201u, 126451906793u }, + { 10083765740821947024u, 14736070002412246709u, 233692105366u }, + { 2798423656816843533u, 9697296975344560756u, 150798843955u }, + { 7614494481582904797u, 7291706381199103298u, 51525691522u }, + { 17811318500083423695u, 18098546597780825068u, 130395284194u }, + { 11641467412200329033u, 132913902678533478u, 226981124177u }, + { 17733593025296340645u, 1879347741692007580u, 81007205277u }, + { 3689424000190644835u, 4056624629214083684u, 157101879645u }, + { 14966634145516728506u, 14713227692042795499u, 93219910061u }, + { 14171681941562070109u, 7366415124022528526u, 173797605671u }, + { 8130575762882608170u, 825770353378039393u, 39399334164u }, + { 5234550794400656856u, 10244023944395357795u, 20044765100u }, + { 3020576149360486378u, 14302658294713551167u, 172555329650u }, + { 3038675756589057221u, 14246653166206862817u, 114775348659u }, + { 15470260187120878369u, 12404486258134291102u, 179772312615u }, + { 3497929414841828746u, 8887442218637942533u, 39672448547u }, + { 9935840636861015305u, 1186724038081863005u, 35481789208u }, + { 3647355485153741471u, 211331772484951576u, 24064332439u }, + { 766100215038272793u, 6311919513247413649u, 151011456318u }, + { 16128087474216800751u, 8131780018703965703u, 62342169842u }, + { 16216631732633731297u, 2262544347226725725u, 242440824678u }, + { 13760220872779997335u, 15318188749880522583u, 102122652774u }, + { 3826276262374222087u, 1073117094162650652u, 102830400676u }, + { 14938032745839181005u, 4447950380665871747u, 164058173794u }, + { 14479259995009508865u, 5373227185066463609u, 98241123873u }, + { 2372033248156102437u, 6739731406934274193u, 33291283229u }, + { 792005346826701645u, 12328812617001239444u, 29365361571u }, + { 16278924527931792559u, 3246111484407310759u, 163668346271u }, + { 17442516423538940144u, 3250825415176839770u, 159175972056u }, + { 13844184233048446u, 16146270540000862342u, 216176227598u }, + { 13486193870480782357u, 15686773375425916830u, 14875291079u }, + { 11931315179184648737u, 11920791905793880226u, 199850381688u }, + { 16492562205587485405u, 1853290561644080707u, 120646227424u }, + { 12128987217680380854u, 12157689141506159076u, 224100467082u }, + { 10568123814189138176u, 18100318838862562546u, 138659069648u }, + { 17007583519117541376u, 7171257882533475139u, 208981220250u }, + { 143791533903052800u, 14477550873015039462u, 154388754668u }, + { 12398714235792654336u, 8109481182495403274u, 236784829605u }, + { 9659957317919047680u, 14565395719337663965u, 165439615855u }, + { 9412523221204336640u, 1860318978161305991u, 111789591684u }, + { 4611686018427387904u, 16268646275151585618u, 132100848093u }, + { 0u, 13759019338835519104u, 221881925081u }, + { 0u, 17003783176010661888u, 217745877932u }, + { 0u, 18357489540307877888u, 172921776932u }, + { 0u, 905481790074912768u, 36995161502u }, + { 0u, 3638882110636294144u, 158049086266u }, + { 0u, 9011702854368362496u, 58197264194u }, + { 0u, 11529215046068469760u, 66488525390u }, + { 0u, 0u, 78625000000u }, + { 0u, 0u, 64000000000u }, + { 3237900842885170729u, 856u, 0u }, + { 7515893506498066595u, 856175526956u, 0u }, + { 7300206309181072546u, 7625299565768063067u, 46u }, + { 5999737279837044u, 13889021769065194705u, 46413368317u }, + { 7556839307242450651u, 14498170692313014398u, 253752925378u }, + { 12946035041643640643u, 1541631360972245751u, 194785947408u }, + { 15885877642352740665u, 9903958882920799117u, 16083572003u }, + { 10770818348246089568u, 15744148547788062576u, 35536894686u }, + { 11635415503599551744u, 17936061801321712000u, 222853492002u }, + { 6248053924100826098u, 9986394078324430610u, 34972315858u }, + { 16894170802729859998u, 13849561248103430369u, 210541363507u }, + { 6143589029651889899u, 12142378807953854930u, 51750786219u }, + { 11812087701837886160u, 2513847703931031444u, 171658239674u }, + { 7306705080150829180u, 1752183758129038045u, 186136275957u }, + { 1485332570280714274u, 15824833342220556540u, 245094986071u }, + { 18042602303295630634u, 8168747198299470695u, 87857865934u }, + { 13397029889257074369u, 17414799840149357478u, 206442828672u }, + { 9948104869613411488u, 83147520704167789u, 128944058191u }, + { 10836066241170646532u, 2383542703041471269u, 79004507436u }, + { 18330574781234459389u, 15540952725549257799u, 44129212108u }, + { 666297360208433062u, 6949835416232048690u, 204842476735u }, + { 5035352224889324309u, 15398868937585367540u, 191376751332u }, + { 1341745796439923765u, 14710915985268256079u, 228834774357u }, + { 6846932182653803785u, 9665704836873335737u, 85797480353u }, + { 6727685027257825533u, 2528789298740305993u, 161523978909u }, + { 2168538874806877737u, 10562914675687726264u, 157137085942u }, + { 17694936100676971444u, 17671658300096837111u, 246572616751u }, + { 2163944241059563294u, 356471401631698552u, 47957982516u }, + { 6727780864524301558u, 7450677157218003204u, 52019324353u }, + { 8599551977795002615u, 317174560787152643u, 193403902018u }, + { 15428264807806859091u, 7251937674440720374u, 66017194067u }, + { 2126771385339683557u, 1252631516699038247u, 83393128329u }, + { 15094316562082972944u, 10818009768860843867u, 137067905290u }, + { 8111300598225956802u, 12330114194950162396u, 10586445484u }, + { 9481215200564260304u, 15826681638261168822u, 172668416829u }, + { 16836674128623424708u, 14240150078499211625u, 61857966130u }, + { 7087243115299722740u, 10725372116242125421u, 50771960082u }, + { 2343560867338408810u, 8434925524647833627u, 18581423587u }, + { 3431385749090422286u, 17133902668520348241u, 227457258228u }, + { 3504545517469224582u, 15093996047981365810u, 244928830724u }, + { 3333759805712094227u, 6187974166976813153u, 4818247165u }, + { 3595183476205994775u, 13946144707720259865u, 253335450751u }, + { 14191566632569921303u, 9138079832881862582u, 127756022019u }, + { 12917427671358095562u, 6600697628576225568u, 3495376300u }, + { 3883793922738316288u, 8137099536646556597u, 172357824535u }, + { 7857281689266421760u, 14169855543453903706u, 23441113049u }, + { 15987081651486195712u, 3706403268650100765u, 217768149408u }, + { 16827562156399525888u, 14736932266877982264u, 160200924523u }, + { 7012737938513461248u, 18004795125138956004u, 107798890698u }, + { 7385903388887613440u, 9068489270661002501u, 202976041899u }, + { 0u, 7758835715193269217u, 171491603788u }, + { 0u, 16943947811135261184u, 76420607326u }, + { 0u, 6745843108403216384u, 94918533251u }, + { 0u, 12338229654069444608u, 131365692887u }, + { 0u, 14358176069683511296u, 215668856769u }, + { 0u, 7083775185760813056u, 193778358284u }, + { 0u, 5350276357316149248u, 12384012222u }, + { 0u, 9223372036854775808u, 190290039062u }, + { 0u, 0u, 22500000000u }, + { 14135788013842776187u, 13064201u, 0u }, + { 13284322918167594445u, 13064201766302603u, 0u }, + { 7264587765474046287u, 14699116688460625612u, 708211u }, + { 16176482219778368741u, 6684126021499623499u, 115796840712u }, + { 5401983818872095469u, 12614606079692508506u, 8362347197u }, + { 11208857446851049921u, 15358270276683001489u, 189683839165u }, + { 13794690008281035639u, 18077126190953408995u, 189832573499u }, + { 14519782740993303071u, 7864121581925945659u, 59979962974u }, + { 10885858587044789123u, 3518026639210514839u, 94426314885u }, + { 8710500938899914621u, 4698310163811252280u, 133190712606u }, + { 1284182587483102819u, 6101155398200416338u, 30254695904u }, + { 10346074482131502030u, 16049178580360033341u, 224330744296u }, + { 9429804686255246574u, 3167464649127375997u, 232870027714u }, + { 6412022633845121254u, 12778923935480989904u, 194171708602u }, + { 8892351297529018260u, 11875553912612980379u, 186692746854u }, + { 14098160105983060597u, 10628760849351697057u, 102643775067u }, + { 13793833029739474340u, 3408944711673234310u, 91576186280u }, + { 16367653765996977044u, 2102091496050506178u, 168184799263u }, + { 10684788343333772342u, 6254611118630245760u, 31113954608u }, + { 13272681218705145345u, 2647941151989776368u, 48339063148u }, + { 11093568615497829248u, 8855437735410157458u, 108143545177u }, + { 12488220765137758124u, 10184270603132180103u, 89480054241u }, + { 3727114642519696453u, 12079083162535627164u, 225552090415u }, + { 11549865375695057514u, 5952952868716156729u, 47654808410u }, + { 11292809154908783229u, 11958907037815852320u, 90322710221u }, + { 17723904861837310998u, 10101562137321697626u, 205648293649u }, + { 14877674388187150875u, 13633527411279258327u, 17547606780u }, + { 12178961950105734308u, 16555627393501768728u, 252739075001u }, + { 4540604068069253114u, 6359650463500280706u, 185897482359u }, + { 13832478722153359868u, 8093923611102181967u, 119344757342u }, + { 1308046668730371491u, 2848827352928635726u, 94438772478u }, + { 18400531023544756800u, 4686723431961561511u, 254154435240u }, + { 9789823458621466539u, 6245554925867652609u, 168254067786u }, + { 1924870562610267306u, 17527406820792516033u, 74338572210u }, + { 5290016144582400923u, 12119966834653692210u, 178950162627u }, + { 15162883663174059077u, 11606502845877928061u, 195657024718u }, + { 8078086116520046390u, 424311496652297090u, 206629189780u }, + { 15731407332173190623u, 5977664048034127173u, 148023001972u }, + { 5346389182763011056u, 6702712461535947028u, 116324049817u }, + { 6368422217216252401u, 11384349854055020018u, 153363354770u }, + { 2961453088119116188u, 3782955013294836188u, 146617146842u }, + { 10932141691610170525u, 3531805968821207061u, 218205074402u }, + { 11034016191361782553u, 3867566898657193228u, 226191459585u }, + { 5455325785621453219u, 12688734637425072080u, 1209661221u }, + { 7735615202566149352u, 18435982764454619691u, 37687857682u }, + { 7502396497775759360u, 4728836163964677692u, 18999416628u }, + { 1601286435751591936u, 2120012917348838977u, 52256350722u }, + { 11449383158571597824u, 9856965465824679831u, 2114926130u }, + { 13043944595690356736u, 11217197671061248816u, 50534347168u }, + { 7773494431818186752u, 3840562972677739189u, 160608085504u }, + { 9943947977234055168u, 17104366978925258617u, 208197335u }, + { 0u, 16177877219841993444u, 215927229591u }, + { 0u, 7338522384267208704u, 151877004481u }, + { 0u, 10935240458612244480u, 193397822095u }, + { 0u, 1732868046462124032u, 143592800573u }, + { 0u, 557965042578882560u, 61093938965u }, + { 0u, 10454684322475540480u, 21030247345u }, + { 0u, 13907115649320091648u, 177566749572u }, + { 0u, 0u, 132753906250u }, + { 0u, 0u, 74000000000u }, + { 6343817245135589714u, 199u, 0u }, + { 17549323075660516085u, 199343899021u, 0u }, + { 3948641822109421754u, 14876458284855834550u, 10u }, + { 1750739713693534543u, 10450704926982265198u, 10806454419u }, + { 962163898128633415u, 5385653213018257806u, 147566533849u }, + { 7967320249386531212u, 12735569669880147489u, 217291956845u }, + { 3018466665533383224u, 3619762560577729456u, 109690396615u }, + { 15076865731854945472u, 11123448126624084269u, 199196227721u }, + { 691187172844604400u, 4072715118852885633u, 137603003331u }, + { 13274492813370992341u, 18239087231420827283u, 195220782328u }, + { 6364168818499152300u, 423431461216085297u, 248988742900u }, + { 17599380787401914158u, 9360976716520160042u, 244022954265u }, + { 3782323149461692814u, 11655927117263208920u, 25507459564u }, + { 14470163442442237466u, 2646622721938364948u, 236631869075u }, + { 476109872130437939u, 4496462484548171852u, 147143473705u }, + { 16330548844673355700u, 13140258519803350063u, 41243753719u }, + { 14089158961463739563u, 13089764333320627770u, 247712334841u }, + { 13385510793074798805u, 6926286827289840501u, 249709597546u }, + { 1549401308746959012u, 4985580225290866218u, 106375474761u }, + { 4607384943843027435u, 10478790837359789693u, 73270268845u }, + { 5157353797716093483u, 10041191967455692214u, 173568056389u }, + { 10846828782671550129u, 5035461258013813797u, 69544334107u }, + { 9915857350819131531u, 14208759661559249750u, 27272972901u }, + { 1673544973504317923u, 12347272163241758840u, 101770258404u }, + { 14780986291622785694u, 3372534174410277614u, 228669346965u }, + { 8168111319515466401u, 17226704187274712984u, 149182825443u }, + { 16345760387859734482u, 4250480179449852121u, 227933861505u }, + { 3177475373321281805u, 4303723537755414374u, 129230418992u }, + { 2558676822419554038u, 8680503847344854165u, 48233305320u }, + { 8813474062662382873u, 8817608623911079652u, 232470571056u }, + { 5426294560236228430u, 5692030448698539450u, 48478003521u }, + { 9919177474128333040u, 16908836314686769809u, 65308565588u }, + { 6197383943089627371u, 6073762347067727240u, 84916629853u }, + { 13673239314867423997u, 10931066692585106200u, 93329259316u }, + { 9309584098968723946u, 14466591364061539596u, 52592574312u }, + { 4647101757759615504u, 4958077340960173341u, 104784235489u }, + { 1405809295505096753u, 4076890037156765715u, 225268777911u }, + { 11332704079573859112u, 14083973146609179058u, 183221008651u }, + { 2960072434514044308u, 2565183738039805295u, 11763493714u }, + { 3887266602785432801u, 1482420938751351224u, 82139058889u }, + { 14545546084687849554u, 2151089495335413944u, 201080362200u }, + { 4617763804182385321u, 3738604531753220913u, 216116610795u }, + { 7857823815580249095u, 14195686514836005765u, 235202670157u }, + { 10939326736548364798u, 17808833916231796970u, 77769549707u }, + { 12262012446566951953u, 1302384553035657354u, 139965418821u }, + { 12555024338687723023u, 1672033517974833698u, 69070602408u }, + { 3332969632922829472u, 11673925532927662545u, 168090641118u }, + { 15535060143360327680u, 3905334232240480709u, 222632844771u }, + { 15794322927987458048u, 17411087320267472625u, 227211708592u }, + { 10571474314433921024u, 16573305231063706617u, 176943856934u }, + { 16679514427547975680u, 15481103236037148354u, 38898440676u }, + { 16925653299565166592u, 907440704754420880u, 228839232288u }, + { 16717361816799281152u, 3224970785139077759u, 32049192459u }, + { 0u, 10560826509734608144u, 11174826016u }, + { 0u, 4700940027512659968u, 32572503552u }, + { 0u, 9733694683502084096u, 254838469u }, + { 0u, 1995535635724632064u, 197527664646u }, + { 0u, 10629833226245373952u, 6108178203u }, + { 0u, 15729384648544878592u, 27576244413u }, + { 0u, 7205759403792793600u, 189852691650u }, + { 0u, 0u, 194390625000u }, + { 0u, 0u, 232000000000u }, + { 9335385384027907407u, 3041746u, 0u }, + { 13237828406194798613u, 3041746506072255u, 0u }, + { 15667486867836528863u, 7535526066623007027u, 164893u }, + { 17982325043592934313u, 11302146918409311588u, 29408501686u }, + { 17159117626917379189u, 2480833299122194801u, 182612690612u }, + { 8336208968408929657u, 11513226205589330558u, 180134486242u }, + { 12767090573379150201u, 4073957068281936105u, 226624133243u }, + { 14736070002412246709u, 3729887061093812886u, 123220849655u }, + { 9697296975344560756u, 13616911779739451443u, 247202197582u }, + { 7291706381199103298u, 13039053282195777666u, 78738174266u }, + { 18098546597780825068u, 14490756113210417890u, 58706848494u }, + { 132913902678533478u, 17432486112977557585u, 238785545462u }, + { 1879347741692007580u, 14308820825344039837u, 246945016965u }, + { 4056624629214083684u, 4190949538817536349u, 133775682731u }, + { 14713227692042795499u, 13616552502810964397u, 171227191829u }, + { 7366415124022528526u, 4898145803694965031u, 21738154790u }, + { 825770353378039393u, 1399036321001644308u, 38265529016u }, + { 10244023944395357795u, 17170331128243738540u, 184075841910u }, + { 14302658294713551167u, 10641321388205367410u, 118930805515u }, + { 14246653166206862817u, 6648873641312572851u, 11576867188u }, + { 12404486258134291102u, 5988456964560374823u, 116360436162u }, + { 8887442218637942533u, 9972593758348346915u, 194324634902u }, + { 1186724038081863005u, 16709668921872818968u, 22540615390u }, + { 211331772484951576u, 6094829131503407767u, 222905832967u }, + { 6311919513247413649u, 4892016478899926334u, 7330401349u }, + { 8131780018703965703u, 13150857244079031538u, 69265196744u }, + { 2262544347226725725u, 12983943395318785894u, 200712909399u }, + { 15318188749880522583u, 15341644584614757478u, 87703860981u }, + { 1073117094162650652u, 7507635124856644772u, 245831672219u }, + { 4447950380665871747u, 11619655367084544354u, 155406989715u }, + { 5373227185066463609u, 11553116952478783009u, 147629902779u }, + { 6739731406934274193u, 17392150014233193245u, 187626295724u }, + { 12328812617001239444u, 8877887560294980515u, 172942830341u }, + { 3246111484407310759u, 18404180619915609503u, 5481271248u }, + { 3250825415176839770u, 10079413095288181976u, 208997692630u }, + { 16146270540000862342u, 14102802966539105550u, 214546406078u }, + { 15686773375425916830u, 13333966026135891399u, 190764514480u }, + { 11920791905793880226u, 12344968670173516152u, 176722835746u }, + { 1853290561644080707u, 10577007819804726752u, 34669222092u }, + { 12157689141506159076u, 15337041354031088010u, 204573380742u }, + { 18100318838862562546u, 14333607285614673616u, 134831422677u }, + { 7171257882533475139u, 17171597563219696538u, 213777026407u }, + { 14477550873015039462u, 2849642930482147564u, 103930874169u }, + { 8109481182495403274u, 14791248423979435173u, 57154479452u }, + { 14565395719337663965u, 13882371364576310127u, 92801835183u }, + { 1860318978161305991u, 11735995808941329540u, 175752564859u }, + { 16268646275151585618u, 11376996674339273181u, 123636209607u }, + { 13759019338835519104u, 9849638057168043481u, 199616748225u }, + { 17003783176010661888u, 18241520229279361964u, 193533949948u }, + { 18357489540307877888u, 1865852368526961444u, 252988874793u }, + { 905481790074912768u, 10601487369276448158u, 41101148059u }, + { 3638882110636294144u, 15999931310312762170u, 155574707781u }, + { 9011702854368362496u, 5773775867713013570u, 69867358014u }, + { 11529215046068469760u, 17726239863982547534u, 62312997016u }, + { 0u, 9711316695888316992u, 152960941388u }, + { 0u, 17872002620723724288u, 76526451532u }, + { 0u, 7429694208660733952u, 76968843203u }, + { 0u, 1782821038871019520u, 195402764530u }, + { 0u, 3225250234313474048u, 242096646922u }, + { 0u, 10009250171830927360u, 10174841165u }, + { 0u, 1152921504606846976u, 77542602539u }, + { 0u, 0u, 43062500000u }, + { 0u, 0u, 160000000000u }, + { 7625299565768063067u, 46u, 0u }, + { 13889021769065194705u, 46413368317u, 0u }, + { 14498170692313014398u, 9519880170333822146u, 2u }, + { 1541631360972245751u, 2285186318012886800u, 2516073738u }, + { 9903958882920799117u, 9706420951402272035u, 10123880198u }, + { 15744148547788062576u, 2369632031840402142u, 6526186134u }, + { 17936061801321712000u, 15599123897979399458u, 150128458009u }, + { 9986394078324430610u, 17579576584023912658u, 25845630200u }, + { 13849561248103430369u, 3480927339588501811u, 248952990756u }, + { 12142378807953854930u, 3547346616671294635u, 36188701449u }, + { 2513847703931031444u, 7705317123868384954u, 9192302045u }, + { 1752183758129038045u, 4969425237478353909u, 221417706078u }, + { 15824833342220556540u, 17043246700132217175u, 94269393081u }, + { 8168747198299470695u, 17053788362783499470u, 185923916254u }, + { 17414799840149357478u, 11102988228454224768u, 222924487719u }, + { 83147520704167789u, 16944305387801685839u, 39601894197u }, + { 2383542703041471269u, 11725142977459199276u, 53918552635u }, + { 15540952725549257799u, 8175984171998533324u, 59635621274u }, + { 6949835416232048690u, 1372352885142856895u, 154443220990u }, + { 15398868937585367540u, 17975093466502888164u, 254074395398u }, + { 14710915985268256079u, 6467823391459085653u, 6974431769u }, + { 9665704836873335737u, 11319386883146885025u, 25350621408u }, + { 2528789298740305993u, 9141999262922068637u, 224613625192u }, + { 10562914675687726264u, 1587330393383478774u, 104495588773u }, + { 17671658300096837111u, 884187548095712303u, 165086049353u }, + { 356471401631698552u, 488841225726377268u, 73047931903u }, + { 7450677157218003204u, 17462624199405856193u, 255026500135u }, + { 317174560787152643u, 13183677579115583554u, 39946650754u }, + { 7251937674440720374u, 11645015818917277779u, 130714688593u }, + { 1252631516699038247u, 8760523002035971977u, 81631277572u }, + { 10818009768860843867u, 10068817678491468042u, 4474908903u }, + { 12330114194950162396u, 1273658177787418284u, 231545831700u }, + { 15826681638261168822u, 3100019384328057661u, 20069045148u }, + { 14240150078499211625u, 10363063568089458738u, 156168052387u }, + { 10725372116242125421u, 13030756371481403666u, 163561782801u }, + { 8434925524647833627u, 6538878900684195299u, 17706398718u }, + { 17133902668520348241u, 8984884716779098868u, 254354473335u }, + { 15093996047981365810u, 8728727397070363908u, 119487071576u }, + { 6187974166976813153u, 6398650562917867005u, 88473185260u }, + { 13946144707720259865u, 1190873176164938879u, 236346871542u }, + { 9138079832881862582u, 4383628525805121795u, 246064557364u }, + { 6600697628576225568u, 10189374699734119852u, 52237636978u }, + { 8137099536646556597u, 5276291920541626391u, 114552367109u }, + { 14169855543453903706u, 2692252373800386521u, 5286028358u }, + { 3706403268650100765u, 11578684995169173920u, 70145947293u }, + { 14736932266877982264u, 5799408022254132587u, 157627681771u }, + { 18004795125138956004u, 15548569837712345290u, 235314386538u }, + { 9068489270661002501u, 15763030464322902955u, 106842889659u }, + { 7758835715193269217u, 13257749746581255500u, 187854515593u }, + { 16943947811135261184u, 16152470009188707678u, 137718704053u }, + { 6745843108403216384u, 13806790848493904003u, 181875627153u }, + { 12338229654069444608u, 11981226523265951191u, 145748467631u }, + { 14358176069683511296u, 5133628726077003713u, 175649503591u }, + { 7083775185760813056u, 16183955741910833164u, 103278294570u }, + { 5350276357316149248u, 13640425554331371454u, 42877333998u }, + { 9223372036854775808u, 18108120906868035862u, 238739448950u }, + { 0u, 6324011669895037184u, 118981643201u }, + { 0u, 10444437689515769856u, 193342825359u }, + { 0u, 12324712543665782784u, 143566194101u }, + { 0u, 13928941951563857920u, 181668124005u }, + { 0u, 3975288688270639104u, 101755089456u }, + { 0u, 11141905478114607104u, 48215500831u }, + { 0u, 4611686018427387904u, 31604003906u }, + { 0u, 0u, 66250000000u }, + { 0u, 0u, 128000000000u }, + { 14699116688460625612u, 708211u, 0u }, + { 6684126021499623499u, 708211796840712u, 0u }, + { 12614606079692508506u, 4398362855256705725u, 38392u }, + { 15358270276683001489u, 2812083125569302717u, 248238435728u }, + { 18077126190953408995u, 12868509142973100603u, 144152443331u }, + { 7864121581925945659u, 8726243776748165726u, 195697603278u }, + { 3518026639210514839u, 358304413426858117u, 206473050623u }, + { 4698310163811252280u, 3180720351566429470u, 255019423721u }, + { 6101155398200416338u, 14053818240400098784u, 233172427195u }, + { 16049178580360033341u, 7340140541492429288u, 187761859013u }, + { 3167464649127375997u, 1323571167904965058u, 197397909816u }, + { 12778923935480989904u, 14463851737583396026u, 56071750936u }, + { 11875553912612980379u, 15122784818916048486u, 24784086973u }, + { 10628760849351697057u, 13557974621377508955u, 189819807807u }, + { 3408944711673234310u, 17525172074563876264u, 63734979276u }, + { 2102091496050506178u, 15148880683074215967u, 204950041481u }, + { 6254611118630245760u, 6744828147558597936u, 137821222467u }, + { 2647941151989776368u, 9799290779647971692u, 67365637866u }, + { 8855437735410157458u, 11170890203898678105u, 234531220617u }, + { 10184270603132180103u, 7068779781287527905u, 137605575171u }, + { 12079083162535627164u, 14474741922505540911u, 3383199319u }, + { 5952952868716156729u, 17107062680405191514u, 87784677331u }, + { 11958907037815852320u, 2712598571300237005u, 211927375726u }, + { 10101562137321697626u, 3767556054903418641u, 110147050263u }, + { 13633527411279258327u, 18158239681706277628u, 23204239622u }, + { 16555627393501768728u, 10531652712128330681u, 6984360145u }, + { 6359650463500280706u, 9548395326934120567u, 209570922037u }, + { 8093923611102181967u, 15875647850297719390u, 53517619547u }, + { 2848827352928635726u, 8215825295203192574u, 91860620594u }, + { 4686723431961561511u, 12747310908260543144u, 50445380781u }, + { 6245554925867652609u, 77706528053613642u, 173691033109u }, + { 17527406820792516033u, 6024737704056756146u, 21004212479u }, + { 12119966834653692210u, 6819452388570089667u, 255326601685u }, + { 11606502845877928061u, 13695926775373186254u, 213369683254u }, + { 424311496652297090u, 3746531715392682132u, 54742457678u }, + { 5977664048034127173u, 4717376233154528116u, 78203099891u }, + { 6702712461535947028u, 385190957950313369u, 243255729478u }, + { 11384349854055020018u, 12388374310648616082u, 70020881243u }, + { 3782955013294836188u, 1078067332084407770u, 91671575117u }, + { 3531805968821207061u, 3257295319358714850u, 77058442147u }, + { 3867566898657193228u, 1545453099660723457u, 163176578333u }, + { 12688734637425072080u, 7495477664653506341u, 29083779180u }, + { 18435982764454619691u, 7225503732673614354u, 108406330658u }, + { 4728836163964677692u, 3935478326103643956u, 34391695342u }, + { 2120012917348838977u, 10082240682742686210u, 238213342707u }, + { 9856965465824679831u, 10838712705567897138u, 243546559362u }, + { 11217197671061248816u, 2142546572501643680u, 130587567793u }, + { 3840562972677739189u, 7893042119150331392u, 177116147682u }, + { 17104366978925258617u, 12084811642251302615u, 226427882670u }, + { 16177877219841993444u, 15317234482572954775u, 174655118951u }, + { 7338522384267208704u, 2283226355108359361u, 103830348945u }, + { 10935240458612244480u, 13359725152575722127u, 145123773948u }, + { 1732868046462124032u, 13126551011491594557u, 252724232151u }, + { 557965042578882560u, 3598021288691861269u, 215711591756u }, + { 10454684322475540480u, 16462621795896662961u, 76195049124u }, + { 13907115649320091648u, 14682112756964627332u, 164892440515u }, + { 0u, 7174112100896070218u, 195795918927u }, + { 0u, 5023109019590616064u, 79388909396u }, + { 0u, 10765223023086141440u, 84272303285u }, + { 0u, 8228137177297453056u, 181583583909u }, + { 0u, 2891199497780592640u, 165446048210u }, + { 0u, 15294857653247803392u, 210156732238u }, + { 0u, 14303432416528695296u, 78829135894u }, + { 0u, 0u, 22775390625u }, + { 0u, 0u, 161000000000u }, + { 14876458284855834550u, 10u, 0u }, + { 10450704926982265198u, 10806454419u, 0u }, + { 5385653213018257806u, 10806454419566533849u, 0u }, + { 12735569669880147489u, 17118225092618494573u, 585819067u }, + { 3619762560577729456u, 13385738875341807559u, 187927980841u }, + { 11123448126624084269u, 8272682717439277193u, 41725642358u }, + { 4072715118852885633u, 13402436483369350083u, 118448463028u }, + { 18239087231420827283u, 10946328903241612536u, 180726547537u }, + { 423431461216085297u, 16265808923426731252u, 81593401678u }, + { 9360976716520160042u, 11080374459871185177u, 78881771268u }, + { 11655927117263208920u, 1240761893433831916u, 4600668303u }, + { 2646622721938364948u, 367264070493390483u, 143067261837u }, + { 4496462484548171852u, 2863675693461092905u, 141019909425u }, + { 13140258519803350063u, 7511929581752138999u, 49155240170u }, + { 13089764333320627770u, 11154557789993845753u, 234407222518u }, + { 6926286827289840501u, 8325416539745948522u, 246604689789u }, + { 4985580225290866218u, 17745129874679852617u, 125451321734u }, + { 10478790837359789693u, 1074820986392253357u, 134961965418u }, + { 10041191967455692214u, 7820952682162838597u, 106058266162u }, + { 5035461258013813797u, 8215518006273528603u, 50423974694u }, + { 14208759661559249750u, 9680426791089900133u, 38445364123u }, + { 12347272163241758840u, 16128495723604797412u, 155524776987u }, + { 3372534174410277614u, 2264789053583348885u, 27874327505u }, + { 17226704187274712984u, 11175458488686298083u, 209122774460u }, + { 4250480179449852121u, 11026777810412287617u, 188605822818u }, + { 4303723537755414374u, 16199890034895598640u, 98597762822u }, + { 8680503847344854165u, 9094320719494763752u, 6878197798u }, + { 8817608623911079652u, 1250835564687222832u, 38493004114u }, + { 5692030448698539450u, 15362466642459337025u, 82067807931u }, + { 16908836314686769809u, 7831109835595423828u, 187832800985u }, + { 6073762347067727240u, 15426237284335022429u, 217424525314u }, + { 10931066692585106200u, 15636308361455434548u, 2836257998u }, + { 14466591364061539596u, 13967173875944980328u, 206847645974u }, + { 4958077340960173341u, 18245979923595824097u, 22757162012u }, + { 4076890037156765715u, 11335054479675278263u, 28989116553u }, + { 14083973146609179058u, 11165339882630461707u, 137614474534u }, + { 2565183738039805295u, 15944437408299395922u, 38605274287u }, + { 1482420938751351224u, 15806416348777321161u, 175864349683u }, + { 2151089495335413944u, 4201030477408556248u, 243856867547u }, + { 3738604531753220913u, 9485474942554588907u, 219227738318u }, + { 14195686514836005765u, 18238757647663230541u, 206514208626u }, + { 17808833916231796970u, 4642199687824746379u, 114988725033u }, + { 1302384553035657354u, 6134575894869364037u, 41251654149u }, + { 1672033517974833698u, 11524208547121316008u, 5332556025u }, + { 11673925532927662545u, 2734683241527878366u, 249624728597u }, + { 3905334232240480709u, 10629223456178675171u, 21148247475u }, + { 17411087320267472625u, 2788042336985254064u, 179576211358u }, + { 16573305231063706617u, 17285498758066142502u, 158151140077u }, + { 15481103236037148354u, 5525538192421886436u, 237937048765u }, + { 907440704754420880u, 11414325503043801888u, 189299540025u }, + { 3224970785139077759u, 7246608114685173259u, 57618771825u }, + { 10560826509734608144u, 1007884269852184608u, 113392839413u }, + { 4700940027512659968u, 13823717876510029312u, 245054637515u }, + { 9733694683502084096u, 12487410768239429317u, 203749385247u }, + { 1995535635724632064u, 3361062421598631942u, 31676943894u }, + { 10629833226245373952u, 17853337379088328475u, 22182203558u }, + { 15729384648544878592u, 11551561037491869885u, 166967831358u }, + { 7205759403792793600u, 11480877996635204802u, 62626211378u }, + { 0u, 5527488381934471912u, 50622379643u }, + { 0u, 11143438404407726080u, 123299645745u }, + { 0u, 6472279730688098304u, 49604087006u }, + { 0u, 4561816853579563008u, 222350862987u }, + { 0u, 2888714464062865408u, 139247296587u }, + { 0u, 16258276129784201216u, 75156597524u }, + { 0u, 720575940379279360u, 20881362915u }, + { 0u, 0u, 227039062500u }, + { 0u, 0u, 228000000000u }, + { 7535526066623007027u, 164893u, 0u }, + { 11302146918409311588u, 164893408501686u, 0u }, + { 2480833299122194801u, 16409970870640346804u, 8938u }, + { 11513226205589330558u, 7721907286269370594u, 234889586303u }, + { 4073957068281936105u, 14300743897882155131u, 127418605432u }, + { 3729887061093812886u, 2068482633821123575u, 120775244880u }, + { 13616911779739451443u, 4922882895416406094u, 80112132668u }, + { 13039053282195777666u, 9317632875623428410u, 60266870016u }, + { 14490756113210417890u, 5693844901999766254u, 505109890u }, + { 17432486112977557585u, 11569484900262102262u, 130308663950u }, + { 14308820825344039837u, 3138170119352085637u, 142627183033u }, + { 4190949538817536349u, 950584692575235243u, 185170120543u }, + { 13616552502810964397u, 8136430299747162645u, 95051531299u }, + { 4898145803694965031u, 6698711700804594470u, 35441076770u }, + { 1399036321001644308u, 17401191571004302008u, 34363137888u }, + { 17170331128243738540u, 4721732028538188150u, 96943320485u }, + { 10641321388205367410u, 2984214103553086219u, 165255965606u }, + { 6648873641312572851u, 13128675202005662068u, 166161774570u }, + { 5988456964560374823u, 14638512997670672834u, 234711706908u }, + { 9972593758348346915u, 12942085665769692438u, 28793555379u }, + { 16709668921872818968u, 14131134357119205086u, 179701591869u }, + { 6094829131503407767u, 8921946894736102919u, 61766050328u }, + { 4892016478899926334u, 5601522560505809989u, 24483659710u }, + { 13150857244079031538u, 8602606493507716808u, 190303659146u }, + { 12983943395318785894u, 8576789731078566487u, 138466348232u }, + { 15341644584614757478u, 17881118138842658549u, 200464948702u }, + { 7507635124856644772u, 11624372674432704923u, 222969337356u }, + { 11619655367084544354u, 6826284072848095635u, 12630158505u }, + { 11553116952478783009u, 1646466632033733563u, 169370053601u }, + { 17392150014233193245u, 17871081657060299180u, 225089255134u }, + { 8877887560294980515u, 15910893124677544709u, 222968793277u }, + { 18404180619915609503u, 11031217459450580944u, 189862531244u }, + { 10079413095288181976u, 13554987390037243094u, 172598003496u }, + { 14102802966539105550u, 15026714590903687870u, 40734817338u }, + { 13333966026135891399u, 4406379654994689200u, 58814599830u }, + { 12344968670173516152u, 13596329092861950242u, 150238870319u }, + { 10577007819804726752u, 284812388227373260u, 47737058477u }, + { 15337041354031088010u, 9285079159392309382u, 173015439710u }, + { 14333607285614673616u, 15046108141952711893u, 94503345149u }, + { 17171597563219696538u, 13795366909944958311u, 253815651156u }, + { 2849642930482147564u, 12909920641180059961u, 84747848338u }, + { 14791248423979435173u, 5333762939889788252u, 146699848200u }, + { 13882371364576310127u, 6411331390005944495u, 8289143868u }, + { 11735995808941329540u, 1447104583224217723u, 60347558971u }, + { 11376996674339273181u, 11940049226167932871u, 59078447696u }, + { 9849638057168043481u, 9772290783590472385u, 80647271365u }, + { 18241520229279361964u, 16351989577831528444u, 197529756944u }, + { 1865852368526961444u, 4376738725895725097u, 16886443131u }, + { 10601487369276448158u, 13851276297739812763u, 123237263481u }, + { 15999931310312762170u, 12641996203470333509u, 121750879192u }, + { 5773775867713013570u, 7707081716407945022u, 216685323987u }, + { 17726239863982547534u, 417638323657040024u, 211417801737u }, + { 9711316695888316992u, 16438047707692449100u, 9022640218u }, + { 17872002620723724288u, 14850108107043306316u, 90891108351u }, + { 7429694208660733952u, 10423290807904720835u, 255805025973u }, + { 1782821038871019520u, 16951162310302339314u, 181565047726u }, + { 3225250234313474048u, 2752437506572397322u, 174918924350u }, + { 10009250171830927360u, 3925815842962784589u, 62149209936u }, + { 1152921504606846976u, 5274166674003605291u, 80212818903u }, + { 0u, 5538963350863452832u, 215285913148u }, + { 0u, 16900671634439028736u, 60300267804u }, + { 0u, 2326997710751662080u, 28916187245u }, + { 0u, 12327726161625874432u, 109126146798u }, + { 0u, 5756455743825903616u, 238668287374u }, + { 0u, 3018537650245074944u, 142312058091u }, + { 0u, 16717361816799281152u, 235163635253u }, + { 0u, 0u, 53906250000u }, + { 0u, 0u, 16000000000u }, + { 2285186318012886800u, 2516073738u, 0u }, + { 9706420951402272035u, 2516073738123880198u, 0u }, + { 2369632031840402142u, 11997425759292732054u, 136396630u }, + { 15599123897979399458u, 11491152661270395161u, 86650381753u }, + { 17579576584023912658u, 18181063258234881272u, 185622936633u }, + { 3480927339588501811u, 2466921813123869732u, 57985597414u }, + { 3547346616671294635u, 8430880678232179465u, 230133732099u }, + { 7705317123868384954u, 6738034873677997533u, 3457038957u }, + { 4969425237478353909u, 7678250951042929246u, 109365269602u }, + { 17043246700132217175u, 1853560606315563193u, 98416238818u }, + { 17053788362783499470u, 14942676593409905118u, 226100481721u }, + { 11102988228454224768u, 4909892170837638183u, 185810044121u }, + { 16944305387801685839u, 16871149368312132405u, 217266165787u }, + { 11725142977459199276u, 16096130589333770811u, 27914586839u }, + { 8175984171998533324u, 12512479187631824282u, 215872572987u }, + { 1372352885142856895u, 16980304980540557310u, 59678302855u }, + { 17975093466502888164u, 8640919162749295366u, 135920504177u }, + { 6467823391459085653u, 7862382415464063513u, 113468425166u }, + { 11319386883146885025u, 14534157903009925344u, 206426220604u }, + { 9141999262922068637u, 12627464554215107944u, 60787898278u }, + { 1587330393383478774u, 2456849734836299173u, 166684536225u }, + { 884187548095712303u, 18428252197697827913u, 161133186090u }, + { 488841225726377268u, 7244734215936736255u, 42998997553u }, + { 17462624199405856193u, 14756175050504770087u, 49392737828u }, + { 13183677579115583554u, 6764116534566945922u, 36799933852u }, + { 11645015818917277779u, 1588822142405565521u, 156366683492u }, + { 8760523002035971977u, 17053265624843842052u, 100086130220u }, + { 10068817678491468042u, 16996891591759999207u, 44924459381u }, + { 1273658177787418284u, 8565556232370585876u, 117921403339u }, + { 3100019384328057661u, 14464960359145886620u, 203464339733u }, + { 10363063568089458738u, 5813189542048784035u, 21784147072u }, + { 13030756371481403666u, 9739241026882027025u, 128315133636u }, + { 6538878900684195299u, 18175068535675302910u, 196527965313u }, + { 8984884716779098868u, 10562697212061761911u, 129985272439u }, + { 8728727397070363908u, 4264834835660801368u, 119572604963u }, + { 6398650562917867005u, 13019066443690126316u, 35231197159u }, + { 1190873176164938879u, 1828040177823321846u, 231705765006u }, + { 4383628525805121795u, 11240369830376975668u, 142099098256u }, + { 10189374699734119852u, 8886938465302549874u, 144609341669u }, + { 5276291920541626391u, 9985240313589688325u, 229481761899u }, + { 2692252373800386521u, 722909126956573766u, 107541300962u }, + { 11578684995169173920u, 5493363474638452381u, 226039188982u }, + { 5799408022254132587u, 12410535279213120491u, 246297795830u }, + { 15548569837712345290u, 10543108918366869098u, 246672776465u }, + { 15763030464322902955u, 12953909016524823995u, 17571543079u }, + { 13257749746581255500u, 16505942145872588169u, 39702232814u }, + { 16152470009188707678u, 12428594380392015797u, 238894788916u }, + { 13806790848493904003u, 7528259605829768337u, 52673755451u }, + { 11981226523265951191u, 18147447600042811311u, 59408107770u }, + { 5133628726077003713u, 12021069431116183911u, 250983775105u }, + { 16183955741910833164u, 11819985069665662506u, 129651663479u }, + { 13640425554331371454u, 10401877114068152814u, 119640762674u }, + { 18108120906868035862u, 4611631138117837942u, 50563886888u }, + { 6324011669895037184u, 17200813398607252417u, 40249997024u }, + { 10444437689515769856u, 14100466137553658767u, 224932457962u }, + { 12324712543665782784u, 17887776768825509301u, 234764387800u }, + { 13928941951563857920u, 12632656857970087269u, 216969698321u }, + { 3975288688270639104u, 8923681664054686256u, 17684817700u }, + { 11141905478114607104u, 6213926103737837599u, 36483753752u }, + { 4611686018427387904u, 1233118281776157762u, 24336857609u }, + { 0u, 30716279628678784u, 9066847476u }, + { 0u, 15775734650898546688u, 244001665132u }, + { 0u, 976806005729918976u, 108855204289u }, + { 0u, 12460098853279891456u, 193052952759u }, + { 0u, 5635665595421687808u, 183675463312u }, + { 0u, 1805943450575568896u, 144305510044u }, + { 0u, 11529215046068469760u, 156097900390u }, + { 0u, 0u, 102625000000u }, + { 0u, 0u, 64000000000u }, + { 4398362855256705725u, 38392u, 0u }, + { 2812083125569302717u, 38392238435728u, 0u }, + { 12868509142973100603u, 4564018338575530435u, 2081u }, + { 8726243776748165726u, 16553437246451512014u, 33247415929u }, + { 358304413426858117u, 4339777136957372927u, 121897363631u }, + { 3180720351566429470u, 18439463366554654697u, 175235259789u }, + { 14053818240400098784u, 1370067356680643003u, 141999605312u }, + { 7340140541492429288u, 4210124040914115013u, 64074271500u }, + { 1323571167904965058u, 10692225626142609720u, 12228231281u }, + { 14463851737583396026u, 11592856673895384344u, 113579626712u }, + { 15122784818916048486u, 10284479231227406269u, 216628450019u }, + { 13557974621377508955u, 4961071383534266431u, 227557522736u }, + { 17525172074563876264u, 10960611551445686988u, 48268940218u }, + { 15148880683074215967u, 14616396723115619209u, 186594175942u }, + { 6744828147558597936u, 1025604265437492803u, 198792356454u }, + { 9799290779647971692u, 11711588454892179178u, 102055598118u }, + { 11170890203898678105u, 5580373263251565705u, 38634886482u }, + { 7068779781287527905u, 14109334653033148931u, 82302512640u }, + { 14474741922505540911u, 2899414033769399895u, 764868564u }, + { 17107062680405191514u, 13233457234892808147u, 212157177549u }, + { 2712598571300237005u, 3287946691509034862u, 205717387154u }, + { 3767556054903418641u, 5488480288717445911u, 146178239947u }, + { 18158239681706277628u, 11687233053874362630u, 203297531112u }, + { 10531652712128330681u, 6783772100089274577u, 232633566173u }, + { 9548395326934120567u, 7898291058728402485u, 221367749022u }, + { 15875647850297719390u, 4423684977486598491u, 158428167216u }, + { 8215825295203192574u, 2750833684599526706u, 48239808443u }, + { 12747310908260543144u, 15669689830489025709u, 187149122992u }, + { 77706528053613642u, 15117307274214954517u, 176849455587u }, + { 6024737704056756146u, 8148639818575698175u, 227819510869u }, + { 6819452388570089667u, 13006484426078994901u, 85441738649u }, + { 13695926775373186254u, 10287496057845513526u, 153705082933u }, + { 3746531715392682132u, 14159876032966532430u, 53557686278u }, + { 4717376233154528116u, 15742212196465548019u, 6767608417u }, + { 385190957950313369u, 2892220461917134150u, 97853387033u }, + { 12388374310648616082u, 7487151560715393883u, 25156787585u }, + { 1078067332084407770u, 7245756744165177933u, 129405879299u }, + { 3257295319358714850u, 3067122860671533987u, 3392793260u }, + { 1545453099660723457u, 8135043905834122525u, 172166269063u }, + { 7495477664653506341u, 14730019368921022572u, 135441001613u }, + { 7225503732673614354u, 495969939682055458u, 141798515950u }, + { 3935478326103643956u, 5617761407265775598u, 238026886584u }, + { 10082240682742686210u, 2087044847072781811u, 184304539456u }, + { 10838712705567897138u, 15929674232061203330u, 64113138927u }, + { 2142546572501643680u, 8658086469608285873u, 239863549370u }, + { 7893042119150331392u, 18369871790780313570u, 186469355807u }, + { 12084811642251302615u, 3545648451947416750u, 31995832745u }, + { 15317234482572954775u, 13347376792767929959u, 169192209987u }, + { 2283226355108359361u, 14482164459838203025u, 67723562745u }, + { 13359725152575722127u, 8899577765623565820u, 249785079708u }, + { 13126551011491594557u, 7095320096604405719u, 156482447077u }, + { 3598021288691861269u, 2968593824439315788u, 229384638073u }, + { 16462621795896662961u, 12621408323612585636u, 121160927793u }, + { 14682112756964627332u, 3954422936414648259u, 49684207916u }, + { 7174112100896070218u, 17143730087577690191u, 44214369696u }, + { 5023109019590616064u, 5033045529399041876u, 160929363470u }, + { 10765223023086141440u, 15857648521994521781u, 14272841944u }, + { 8228137177297453056u, 16655573486499109541u, 216859644848u }, + { 2891199497780592640u, 16652154439190075858u, 176902900447u }, + { 15294857653247803392u, 18016950600164130638u, 223902715100u }, + { 14303432416528695296u, 2086292996072613910u, 220976700849u }, + { 0u, 17324462585194799521u, 177113098169u }, + { 0u, 11079151463184927232u, 185939160998u }, + { 0u, 5239846817488961536u, 166600602004u }, + { 0u, 2778806963520143360u, 148284052665u }, + { 0u, 6240890740138835968u, 185150639427u }, + { 0u, 17250651344549707776u, 67338319364u }, + { 0u, 4197354852709302272u, 4935159683u }, + { 0u, 9223372036854775808u, 131227539062u }, + { 0u, 0u, 118500000000u }, + { 17118225092618494573u, 585819067u, 0u }, + { 13385738875341807559u, 585819067927980841u, 0u }, + { 8272682717439277193u, 5654803392547571318u, 31757315u }, + { 13402436483369350083u, 2931628102185393332u, 3306547506u }, + { 10946328903241612536u, 15964697617980212305u, 50158923877u }, + { 16265808923426731252u, 450380868305846606u, 101865447992u }, + { 11080374459871185177u, 14631133530814566148u, 56024415195u }, + { 1240761893433831916u, 31969822783742095u, 219793155338u }, + { 367264070493390483u, 10437269029385743245u, 10001733087u }, + { 2863675693461092905u, 15196146496377392433u, 223565805487u }, + { 7511929581752138999u, 4409099735137480938u, 175823784752u }, + { 11154557789993845753u, 10644987914903248118u, 48239017775u }, + { 8325416539745948522u, 3154431617534062973u, 47577065951u }, + { 17745129874679852617u, 11702056331247960454u, 223171002080u }, + { 1074820986392253357u, 15575315065965259114u, 224634369744u }, + { 7820952682162838597u, 10759747609480050226u, 208844339521u }, + { 8215518006273528603u, 12538236653960743718u, 65583287086u }, + { 9680426791089900133u, 17857942663978005403u, 46679699170u }, + { 16128495723604797412u, 11443004154750813211u, 226968081011u }, + { 2264789053583348885u, 4004313188770806737u, 115620326498u }, + { 11175458488686298083u, 17134872954824183228u, 98217074252u }, + { 11026777810412287617u, 2659553912986171234u, 76928883324u }, + { 16199890034895598640u, 9501854300969137926u, 124144174706u }, + { 9094320719494763752u, 14528169966301018150u, 114515096553u }, + { 1250835564687222832u, 18172091996515901778u, 233787573671u }, + { 15362466642459337025u, 1133541705604751035u, 167985111081u }, + { 7831109835595423828u, 18280349987988641497u, 41061449418u }, + { 15426237284335022429u, 9936015874712336386u, 202990979758u }, + { 15636308361455434548u, 15876720399740689614u, 174538632499u }, + { 13967173875944980328u, 8618117825152456982u, 51860678737u }, + { 18245979923595824097u, 8085525680745921564u, 81467189103u }, + { 11335054479675278263u, 8072355444669730953u, 111438317225u }, + { 11165339882630461707u, 9395030504766848294u, 169437603265u }, + { 15944437408299395922u, 3537903114058185903u, 193509305624u }, + { 15806416348777321161u, 2126094743961928691u, 24191790112u }, + { 4201030477408556248u, 289185362555601115u, 32115255827u }, + { 9485474942554588907u, 16909937501450129614u, 19015676769u }, + { 18238757647663230541u, 14449642060360499058u, 97916689548u }, + { 4642199687824746379u, 12433818908498244393u, 140783316665u }, + { 6134575894869364037u, 11884444034578008581u, 185674038673u }, + { 11524208547121316008u, 988625838444140793u, 145644257002u }, + { 2734683241527878366u, 1675370907158909973u, 234053593514u }, + { 10629223456178675171u, 15920186275316934067u, 170090822038u }, + { 2788042336985254064u, 5600921198503757726u, 150863035027u }, + { 17285498758066142502u, 10457357161776341741u, 147303626546u }, + { 5525538192421886436u, 12225356765775740093u, 50566894467u }, + { 11414325503043801888u, 4486633318598164537u, 131662737918u }, + { 7246608114685173259u, 10302486602879381361u, 254243220879u }, + { 1007884269852184608u, 15536428611301239541u, 143558498917u }, + { 13823717876510029312u, 12026126645955462603u, 101842231482u }, + { 12487410768239429317u, 14877968141142123551u, 186651937631u }, + { 3361062421598631942u, 734560801645383190u, 95806536269u }, + { 17853337379088328475u, 15648943144911081638u, 77039820620u }, + { 11551561037491869885u, 13664182862003235646u, 76848330907u }, + { 11480877996635204802u, 3895127525902132786u, 155740736837u }, + { 5527488381934471912u, 5249187334214137467u, 69211155286u }, + { 11143438404407726080u, 10642260063359027505u, 86284559015u }, + { 6472279730688098304u, 783598951897779422u, 167576918074u }, + { 4561816853579563008u, 5538576558607624843u, 58042478984u }, + { 2888714464062865408u, 15974581187564609611u, 136300246836u }, + { 16258276129784201216u, 7474269406918257428u, 52865983781u }, + { 720575940379279360u, 8045286838779138019u, 37405180956u }, + { 0u, 8184246376556341732u, 28436135873u }, + { 0u, 1493267152679331840u, 193443668885u }, + { 0u, 10179074811222818816u, 149080950174u }, + { 0u, 3892499202005008384u, 158551808751u }, + { 0u, 10341173215925108736u, 239211012804u }, + { 0u, 6230307872002015232u, 196560596123u }, + { 0u, 9295429630892703744u, 155337745666u }, + { 0u, 0u, 2503906250u }, + { 0u, 0u, 202000000000u }, + { 16409970870640346804u, 8938u, 0u }, + { 7721907286269370594u, 8938889586303u, 0u }, + { 14300743897882155131u, 10665454627995623288u, 484u }, + { 2068482633821123575u, 16803537892767562832u, 228578175453u }, + { 4922882895416406094u, 8099123106849104444u, 221910921614u }, + { 9317632875623428410u, 7077413686679401728u, 142439054343u }, + { 5693844901999766254u, 13536636358372449666u, 7383667364u }, + { 11569484900262102262u, 7280632235418610318u, 164733822527u }, + { 3138170119352085637u, 6187823673116858809u, 63394683864u }, + { 950584692575235243u, 8624343686231740255u, 216335442593u }, + { 8136430299747162645u, 806211610822132771u, 161467526608u }, + { 6698711700804594470u, 18388078233202190882u, 208043704818u }, + { 17401191571004302008u, 7628864426595573600u, 242996819718u }, + { 4721732028538188150u, 4530799784343874981u, 6413561569u }, + { 2984214103553086219u, 8561580552078486438u, 225245615148u }, + { 13128675202005662068u, 13349114951221999594u, 44464124211u }, + { 14638512997670672834u, 10029144738508991772u, 51723656971u }, + { 12942085665769692438u, 12601907197916268979u, 11543681025u }, + { 14131134357119205086u, 1329580921391066941u, 1683150758u }, + { 8921946894736102919u, 3198179786356761112u, 166072076726u }, + { 5601522560505809989u, 11406753413634654142u, 182173373673u }, + { 8602606493507716808u, 11131812960525182090u, 233618361341u }, + { 8576789731078566487u, 14299636753645227208u, 253603456789u }, + { 17881118138842658549u, 12964114684643663326u, 21775184861u }, + { 11624372674432704923u, 5019257593846306316u, 221702786065u }, + { 6826284072848095635u, 6929086798159998121u, 17272094499u }, + { 1646466632033733563u, 18359765766933703649u, 35375626547u }, + { 17871081657060299180u, 9993076234752063198u, 51995284896u }, + { 15910893124677544709u, 3257189215046584509u, 160541725748u }, + { 11031217459450580944u, 2905234736672690348u, 52176572581u }, + { 13554987390037243094u, 12064985302079670056u, 165157493090u }, + { 15026714590903687870u, 14315096064942799930u, 98654044163u }, + { 4406379654994689200u, 11943971043551974038u, 3776022912u }, + { 13596329092861950242u, 12472773152119929647u, 128647483967u }, + { 284812388227373260u, 7791259796982183085u, 63676150387u }, + { 9285079159392309382u, 16866829442051086686u, 115422365039u }, + { 15046108141952711893u, 3702498393844653053u, 111914352656u }, + { 13795366909944958311u, 2057239613841701716u, 16200712840u }, + { 12909920641180059961u, 17201969976738286226u, 136111523182u }, + { 5333762939889788252u, 18271566505443461640u, 110932520660u }, + { 6411331390005944495u, 18368509115417119804u, 212990503604u }, + { 1447104583224217723u, 7613923684154518587u, 180995758874u }, + { 11940049226167932871u, 17984805084714865232u, 26412751629u }, + { 9772290783590472385u, 4220802739051410373u, 13974958237u }, + { 16351989577831528444u, 17812459042810815760u, 157228810174u }, + { 4376738725895725097u, 10629526089664605307u, 190965615339u }, + { 13851276297739812763u, 17437443267816548473u, 235576227763u }, + { 12641996203470333509u, 12506371893701049304u, 179945285693u }, + { 7707081716407945022u, 15737221540003030739u, 61677971778u }, + { 417638323657040024u, 2358380859011605513u, 66853116489u }, + { 16438047707692449100u, 10042972713837039706u, 73127848082u }, + { 14850108107043306316u, 13424397272769642495u, 146544430641u }, + { 10423290807904720835u, 6867102315755663029u, 49727738034u }, + { 16951162310302339314u, 8690748404825506734u, 178372266362u }, + { 2752437506572397322u, 956229930815387710u, 122471126415u }, + { 3925815842962784589u, 7734449506297687888u, 143051837328u }, + { 5274166674003605291u, 16332184961683848151u, 144419285347u }, + { 5538963350863452832u, 15580777817612768828u, 99885369520u }, + { 16900671634439028736u, 17404245271944696092u, 176844635657u }, + { 2326997710751662080u, 13201420160494469229u, 9943486026u }, + { 12327726161625874432u, 16511717657124068078u, 74715650420u }, + { 5756455743825903616u, 14131292492116594062u, 116895102007u }, + { 3018537650245074944u, 18429136031865875691u, 55766058900u }, + { 16717361816799281152u, 2563978348305862197u, 148999045466u }, + { 0u, 14239974392147482896u, 90138993544u }, + { 0u, 11164201396098998272u, 136771950558u }, + { 0u, 7116971104932986880u, 222605212570u }, + { 0u, 12437629862867369984u, 154385811776u }, + { 0u, 16501893821638901760u, 64674245265u }, + { 0u, 10649324268870959104u, 145894569456u }, + { 0u, 7205759403792793600u, 240577301025u }, + { 0u, 0u, 33390625000u }, + { 0u, 0u, 232000000000u }, + { 11997425759292732054u, 136396630u, 0u }, + { 11491152661270395161u, 136396630650381753u, 0u }, + { 18181063258234881272u, 3016823727048309817u, 7394076u }, + { 2466921813123869732u, 17405973192644624358u, 28163542341u }, + { 8430880678232179465u, 8937219978302591747u, 69943579697u }, + { 6738034873677997533u, 15178463196824222317u, 49484487665u }, + { 7678250951042929246u, 11979404627460330594u, 241822826138u }, + { 1853560606315563193u, 2006448052689740002u, 154649404826u }, + { 14942676593409905118u, 16330465320863239865u, 154108769766u }, + { 4909892170837638183u, 17136208883957646553u, 230885276298u }, + { 16871149368312132405u, 140455118208931867u, 138928955745u }, + { 16096130589333770811u, 3964972929179372247u, 97007614087u }, + { 12512479187631824282u, 3378050330022776379u, 135214941613u }, + { 16980304980540557310u, 6065353437512901255u, 173183124475u }, + { 8640919162749295366u, 12768753059854699889u, 251328803468u }, + { 7862382415464063513u, 6848720690951013326u, 140692195490u }, + { 14534157903009925344u, 10953228058585475132u, 162371269892u }, + { 12627464554215107944u, 15539127852083296166u, 4593775682u }, + { 2456849734836299173u, 14534853647735598497u, 66842377808u }, + { 18428252197697827913u, 1506909603576368170u, 80787935995u }, + { 7244734215936736255u, 5475702579938239025u, 251081689733u }, + { 14756175050504770087u, 12039747373985783332u, 133296838431u }, + { 6764116534566945922u, 17572399137760898460u, 31652676012u }, + { 1588822142405565521u, 869552790852091236u, 172952601666u }, + { 17053265624843842052u, 4549585778048181804u, 66047138551u }, + { 16996891591759999207u, 4121918231767210357u, 247246633539u }, + { 8565556232370585876u, 1558397953312543179u, 67223449635u }, + { 14464960359145886620u, 6067524298738069781u, 35084480922u }, + { 5813189542048784035u, 5811095224555517056u, 154328921151u }, + { 9739241026882027025u, 6440894514158997188u, 63315020103u }, + { 18175068535675302910u, 4612748874388784257u, 71349161591u }, + { 10562697212061761911u, 9908101430749813367u, 119250057617u }, + { 4264834835660801368u, 15150017990912190499u, 145537119254u }, + { 13019066443690126316u, 17470426264690059239u, 22821284120u }, + { 1828040177823321846u, 9615161096851907726u, 24947073705u }, + { 11240369830376975668u, 9227932132124142224u, 169521238927u }, + { 8886938465302549874u, 4794113194321211621u, 143500247203u }, + { 9985240313589688325u, 391512698859146347u, 163259889397u }, + { 722909126956573766u, 17209658878068655842u, 245021223945u }, + { 5493363474638452381u, 3077364726606876150u, 9932937477u }, + { 12410535279213120491u, 1952989567673965814u, 5166824276u }, + { 10543108918366869098u, 11172860676923186449u, 84105871776u }, + { 12953909016524823995u, 17338078544784947239u, 160605681990u }, + { 16505942145872588169u, 4593380466519703278u, 70939899121u }, + { 12428594380392015797u, 786884753602720052u, 241249007654u }, + { 7528259605829768337u, 17848875822468020539u, 38042657107u }, + { 18147447600042811311u, 2899664567187130618u, 83967589497u }, + { 12021069431116183911u, 2973178834961857409u, 121157191131u }, + { 11819985069665662506u, 11117453141176836727u, 219161176347u }, + { 10401877114068152814u, 7535238370146462002u, 27602678342u }, + { 4611631138117837942u, 10246175467290865448u, 70408486090u }, + { 17200813398607252417u, 1203128834127050464u, 202555446285u }, + { 14100466137553658767u, 14518048959078919658u, 13065221744u }, + { 17887776768825509301u, 1553474987376920024u, 112787025011u }, + { 12632656857970087269u, 14956572380830948369u, 115084214047u }, + { 8923681664054686256u, 7594162606042048292u, 31810797413u }, + { 6213926103737837599u, 14461296147288811288u, 101411680379u }, + { 1233118281776157762u, 18305427728131488265u, 123783948434u }, + { 30716279628678784u, 10253208939347909876u, 146992339225u }, + { 15775734650898546688u, 6446028915490812012u, 25555827570u }, + { 976806005729918976u, 12986063676957432257u, 114349439927u }, + { 12460098853279891456u, 9769714697972762807u, 183703975922u }, + { 5635665595421687808u, 97429465146664592u, 242529617295u }, + { 1805943450575568896u, 16395571728207795868u, 143005281661u }, + { 11529215046068469760u, 6331668478323650406u, 125888805724u }, + { 0u, 18129911846294207040u, 92343240435u }, + { 0u, 9890094564876124160u, 243982824490u }, + { 0u, 12290856656987750400u, 42536143100u }, + { 0u, 8498454992640802816u, 252666288674u }, + { 0u, 5341660584200896512u, 34460702168u }, + { 0u, 9288674231451648000u, 216289572000u }, + { 0u, 1152921504606846976u, 160503540039u }, + { 0u, 0u, 71062500000u }, + { 0u, 0u, 160000000000u }, + { 4564018338575530435u, 2081u, 0u }, + { 16553437246451512014u, 2081247415929u, 0u }, + { 4339777136957372927u, 15212079674427582639u, 112u }, + { 18439463366554654697u, 10179808126814248333u, 112824648491u }, + { 1370067356680643003u, 6066766544199222848u, 43551848504u }, + { 4210124040914115013u, 6625308131806923532u, 56328880073u }, + { 10692225626142609720u, 9122786786400665713u, 201359158673u }, + { 11592856673895384344u, 11932880778639151320u, 145494547262u }, + { 10284479231227406269u, 3884040911779255011u, 62646882763u }, + { 4961071383534266431u, 13441817515637357872u, 203210554279u }, + { 10960611551445686988u, 11628577856022352826u, 167728682387u }, + { 14616396723115619209u, 13296656925520243654u, 147630386468u }, + { 1025604265437492803u, 5020720704545399398u, 36720813216u }, + { 11711588454892179178u, 14121973606499014694u, 160272173814u }, + { 5580373263251565705u, 3642481034345420114u, 246765553723u }, + { 14109334653033148931u, 9845536238569696768u, 59197459292u }, + { 2899414033769399895u, 17655403572195686356u, 92533727588u }, + { 13233457234892808147u, 8377495365136654029u, 100957101345u }, + { 3287946691509034862u, 13713682649609025426u, 33454144933u }, + { 5488480288717445911u, 1367709905452854731u, 165743420226u }, + { 11687233053874362630u, 9981467701727208680u, 66074143702u }, + { 6783772100089274577u, 6277920117543306205u, 214541096448u }, + { 7898291058728402485u, 9344111460418701726u, 340326731u }, + { 4423684977486598491u, 4918507011364617264u, 75506545297u }, + { 2750833684599526706u, 6554777203830755259u, 145266632799u }, + { 15669689830489025709u, 4198262173120265648u, 95355335184u }, + { 15117307274214954517u, 8080325935698446819u, 16227588248u }, + { 8148639818575698175u, 12797633874200091733u, 152438035346u }, + { 13006484426078994901u, 8376502502208665497u, 146693761122u }, + { 10287496057845513526u, 9891973386793349173u, 98454091110u }, + { 14159876032966532430u, 14877430279003795462u, 102536244951u }, + { 15742212196465548019u, 8759933935842067041u, 215806507111u }, + { 2892220461917134150u, 3753418510388703513u, 103474876970u }, + { 7487151560715393883u, 2961383332545305985u, 42203473225u }, + { 7245756744165177933u, 2497674184068629507u, 73160536912u }, + { 3067122860671533987u, 15244544070742305452u, 80135399188u }, + { 8135043905834122525u, 45953573565810823u, 20826408390u }, + { 14730019368921022572u, 3960077421351906445u, 198002491148u }, + { 495969939682055458u, 3173330011013883118u, 12214676227u }, + { 5617761407265775598u, 11026266219545759160u, 3172026564u }, + { 2087044847072781811u, 8886757764964685632u, 196597735089u }, + { 15929674232061203330u, 13952322129918090479u, 177481752103u }, + { 8658086469608285873u, 4127250666614902202u, 39756356898u }, + { 18369871790780313570u, 17649958504065306911u, 34223738706u }, + { 3545648451947416750u, 13269305359002216873u, 82956806167u }, + { 13347376792767929959u, 16236593433831947843u, 23719330484u }, + { 14482164459838203025u, 13580930396682424057u, 180880187493u }, + { 8899577765623565820u, 421976357197961116u, 101736223712u }, + { 7095320096604405719u, 2962130818798626533u, 224022875384u }, + { 2968593824439315788u, 8234383947306356345u, 248160577433u }, + { 12621408323612585636u, 4380469931801381425u, 153446386848u }, + { 3954422936414648259u, 15279887469027055916u, 160237465750u }, + { 17143730087577690191u, 8534542821713755552u, 150828324359u }, + { 5033045529399041876u, 7814613482565088782u, 7462658493u }, + { 15857648521994521781u, 13771954404705323224u, 189423631045u }, + { 16655573486499109541u, 4568173274762548144u, 197746579144u }, + { 16652154439190075858u, 8105292616250821343u, 200247641169u }, + { 18016950600164130638u, 2923678426777275612u, 81439388793u }, + { 2086292996072613910u, 1808633176918384049u, 121158492925u }, + { 17324462585194799521u, 18118642609460438969u, 253098046200u }, + { 11079151463184927232u, 18138164175864360870u, 248982213583u }, + { 5239846817488961536u, 4031433690465792404u, 207983271850u }, + { 2778806963520143360u, 5012226396942308537u, 170218544458u }, + { 6240890740138835968u, 7889712298793536835u, 74271713337u }, + { 17250651344549707776u, 13500762396543628804u, 57427702160u }, + { 4197354852709302272u, 501020624068841347u, 144731877796u }, + { 9223372036854775808u, 8370653768288261750u, 164027160382u }, + { 0u, 647579990023635200u, 62453774050u }, + { 0u, 11106569307181154304u, 226035105381u }, + { 0u, 10797461613892861952u, 101602088328u }, + { 0u, 17627230675448889344u, 136585331566u }, + { 0u, 12197735707942322176u, 110955574089u }, + { 0u, 12871287735024877568u, 73661240577u }, + { 0u, 4611686018427387904u, 1697753906u }, + { 0u, 0u, 50250000000u }, + { 0u, 0u, 128000000000u }, + { 5654803392547571318u, 31757315u, 0u }, + { 2931628102185393332u, 31757315306547506u, 0u }, + { 15964697617980212305u, 9451803574512021605u, 1721567u }, + { 450380868305846606u, 8662766454758138424u, 223512383298u }, + { 14631133530814566148u, 9207992007314947035u, 66469609510u }, + { 31969822783742095u, 17118602861291201802u, 38499166246u }, + { 10437269029385743245u, 11186560605745599967u, 38928001320u }, + { 15196146496377392433u, 10505549821532796847u, 40606424665u }, + { 4409099735137480938u, 18133667530488679216u, 89569506996u }, + { 10644987914903248118u, 10778135771244330799u, 180983028086u }, + { 3154431617534062973u, 17087985777033767391u, 118584283910u }, + { 11702056331247960454u, 2639185991757283040u, 6926341565u }, + { 15575315065965259114u, 5401720287293896400u, 189143070559u }, + { 10759747609480050226u, 9816495392633895233u, 95292827843u }, + { 12538236653960743718u, 10042051500090034990u, 195532153281u }, + { 17857942663978005403u, 11629689537856384738u, 193544380702u }, + { 11443004154750813211u, 2099086731766010483u, 30630446733u }, + { 4004313188770806737u, 13665537898516458594u, 141113791719u }, + { 17134872954824183228u, 16375672064669490764u, 231740810293u }, + { 2659553912986171234u, 7770550512184564348u, 53887726961u }, + { 9501854300969137926u, 6197048880720627314u, 113421242387u }, + { 14528169966301018150u, 17963594118523106281u, 19335942692u }, + { 18172091996515901778u, 8255454642407818663u, 36973808388u }, + { 1133541705604751035u, 16744201957549498409u, 4447529092u }, + { 18280349987988641497u, 17442505417202859722u, 132907705006u }, + { 9936015874712336386u, 6383975767786687150u, 174945560113u }, + { 15876720399740689614u, 15245442964998335795u, 49346076019u }, + { 8618117825152456982u, 2910016124519524433u, 115826457119u }, + { 8085525680745921564u, 3847913871169988463u, 31157752290u }, + { 8072355444669730953u, 17210451512590059177u, 226208595828u }, + { 9395030504766848294u, 17899408909991454145u, 116932980445u }, + { 3537903114058185903u, 5920601932753251608u, 221970328901u }, + { 2126094743961928691u, 16521781895108979744u, 69320956473u }, + { 289185362555601115u, 3697493405554698771u, 57895647591u }, + { 16909937501450129614u, 2816108280295732065u, 103200441519u }, + { 14449642060360499058u, 14251078772056398988u, 175152661535u }, + { 12433818908498244393u, 4543066550096031417u, 31772552528u }, + { 11884444034578008581u, 3099369389734296977u, 80246280131u }, + { 988625838444140793u, 5243484113636490986u, 195168017151u }, + { 1675370907158909973u, 6823370511605197226u, 255284249843u }, + { 15920186275316934067u, 11396290277624641942u, 243369895656u }, + { 5600921198503757726u, 15934361408437566099u, 232617794133u }, + { 10457357161776341741u, 14939272230935131954u, 85863803462u }, + { 12225356765775740093u, 7500666177940329347u, 70809859570u }, + { 4486633318598164537u, 4806714453065462270u, 242406611928u }, + { 10302486602879381361u, 11557851247268441487u, 216260572512u }, + { 15536428611301239541u, 10655523157206817381u, 96626552371u }, + { 12026126645955462603u, 14769600176490881210u, 51577637067u }, + { 14877968141142123551u, 16688495540925795167u, 203800661629u }, + { 734560801645383190u, 909793965395524173u, 125904685156u }, + { 15648943144911081638u, 12724590949761703756u, 100049320029u }, + { 13664182862003235646u, 10810739657314826395u, 93689801457u }, + { 3895127525902132786u, 2431218615388671301u, 241586051371u }, + { 5249187334214137467u, 4235001167959059286u, 43131796625u }, + { 10642260063359027505u, 6253317787396334247u, 145229579873u }, + { 783598951897779422u, 9534525563070371898u, 97338993036u }, + { 5538576558607624843u, 8392783992374030728u, 140516867666u }, + { 15974581187564609611u, 16356257019231647540u, 82454973731u }, + { 7474269406918257428u, 12896334001521091877u, 35886674469u }, + { 8045286838779138019u, 1427636373320877084u, 37699111667u }, + { 8184246376556341732u, 16116755731295043521u, 243077392322u }, + { 1493267152679331840u, 15945633911163986837u, 194873691078u }, + { 10179074811222818816u, 7510154241072743838u, 198864414546u }, + { 3892499202005008384u, 3571560509790395119u, 82407126277u }, + { 10341173215925108736u, 3576991649007035076u, 5193614683u }, + { 6230307872002015232u, 15509961892750732443u, 91193909105u }, + { 9295429630892703744u, 17789791359353349378u, 113840796718u }, + { 0u, 18331227331079738314u, 46964386521u }, + { 0u, 15386712883100476416u, 217993737824u }, + { 0u, 14082462055028752384u, 96834115376u }, + { 0u, 12919043128765186048u, 48763411797u }, + { 0u, 6125373368465096704u, 85700342731u }, + { 0u, 12335992698065387520u, 203332057155u }, + { 0u, 2774217370460225536u, 67668735504u }, + { 0u, 0u, 16150390625u }, + { 0u, 0u, 97000000000u }, + { 10665454627995623288u, 484u, 0u }, + { 16803537892767562832u, 484578175453u, 0u }, + { 8099123106849104444u, 4962829537462579598u, 26u }, + { 7077413686679401728u, 5711259460785241095u, 26269035528u }, + { 13536636358372449666u, 13845894607204897444u, 8309607995u }, + { 7280632235418610318u, 12116633056637003327u, 59750587450u }, + { 6187823673116858809u, 2965791047992089560u, 58656843994u }, + { 8624343686231740255u, 16021997451315962529u, 218160775854u }, + { 806211610822132771u, 3942052271663803856u, 174868554222u }, + { 18388078233202190882u, 15669876414782439922u, 238213699081u }, + { 7628864426595573600u, 10594415915406145286u, 9849465702u }, + { 4530799784343874981u, 10789820553031921377u, 102574324437u }, + { 8561580552078486438u, 3989990218583987244u, 213584917344u }, + { 13349114951221999594u, 2937341169808224563u, 96216297803u }, + { 10029144738508991772u, 16267436558584536843u, 75159233583u }, + { 12601907197916268979u, 16221580362814625793u, 47881859502u }, + { 1329580921391066941u, 9695437602320209830u, 174879373633u }, + { 3198179786356761112u, 10729753156793715126u, 65525590725u }, + { 11406753413634654142u, 2609241432056861929u, 197581661084u }, + { 11131812960525182090u, 8462663743997037565u, 156141447261u }, + { 14299636753645227208u, 14993422143908194069u, 93458761920u }, + { 12964114684643663326u, 1307443894537745373u, 192812795043u }, + { 5019257593846306316u, 10017257439419829265u, 163070876675u }, + { 6929086798159998121u, 16754772009970777891u, 3543036613u }, + { 18359765766933703649u, 11722573031602862387u, 197908278010u }, + { 9993076234752063198u, 7363764277467092384u, 250635481957u }, + { 3257189215046584509u, 6733958494847390772u, 101399190461u }, + { 2905234736672690348u, 8799796600227451045u, 189365048621u }, + { 12064985302079670056u, 10512023194742249826u, 45477037929u }, + { 14315096064942799930u, 4572542132337197059u, 105569857919u }, + { 11943971043551974038u, 12600500455757416832u, 127247878005u }, + { 12472773152119929647u, 7873789864743195199u, 117683074498u }, + { 7791259796982183085u, 15724851676325671539u, 194426839003u }, + { 16866829442051086686u, 8748017220462413167u, 219852445917u }, + { 3702498393844653053u, 14172589522760466448u, 221474230963u }, + { 2057239613841701716u, 9520545591489413768u, 179768297617u }, + { 17201969976738286226u, 12488551088392570222u, 145516109810u }, + { 18271566505443461640u, 1135798823651241684u, 242677005711u }, + { 18368509115417119804u, 11168725610120161972u, 143061571777u }, + { 7613923684154518587u, 9580104948718508826u, 193605457828u }, + { 17984805084714865232u, 16638722716909738765u, 164519338529u }, + { 4220802739051410373u, 15732724012348272797u, 33901986965u }, + { 17812459042810815760u, 12269722190021214142u, 149852872677u }, + { 10629526089664605307u, 13110655916311972587u, 229665142972u }, + { 17437443267816548473u, 6618112997062866867u, 188710730081u }, + { 12506371893701049304u, 8457936459015989309u, 97358768624u }, + { 15737221540003030739u, 3329167139937134914u, 240458505654u }, + { 2358380859011605513u, 5245511557216705097u, 182180474512u }, + { 10042972713837039706u, 5655931353280440466u, 144284359751u }, + { 13424397272769642495u, 604622132328697393u, 71306608653u }, + { 6867102315755663029u, 8673282619234652338u, 13032776631u }, + { 8690748404825506734u, 16929477433058445690u, 183470179592u }, + { 956229930815387710u, 11036952409253549455u, 8917748810u }, + { 7734449506297687888u, 18199392190170386320u, 74598314388u }, + { 16332184961683848151u, 9683116091880335715u, 148986591027u }, + { 15580777817612768828u, 2993913337608915120u, 51524922775u }, + { 17404245271944696092u, 4490779842162392585u, 151162300367u }, + { 13201420160494469229u, 946849923353644618u, 207243445663u }, + { 16511717657124068078u, 3613491058474899828u, 159051328837u }, + { 14131292492116594062u, 14624054199004410935u, 69195887742u }, + { 18429136031865875691u, 12088470271991908244u, 126792771566u }, + { 2563978348305862197u, 10071980927725011290u, 238655317286u }, + { 14239974392147482896u, 2833441711428854664u, 38546003180u }, + { 11164201396098998272u, 17655572411864340446u, 236153601182u }, + { 7116971104932986880u, 4997642792058747802u, 158957110498u }, + { 12437629862867369984u, 11489200787635734848u, 226270922758u }, + { 16501893821638901760u, 12983586226429536913u, 6622830822u }, + { 10649324268870959104u, 12311150768725063152u, 230703841619u }, + { 7205759403792793600u, 8530052476845967905u, 83667388820u }, + { 0u, 6282736361499820264u, 148462415071u }, + { 0u, 11337164765929082880u, 223340587820u }, + { 0u, 8343856200414134272u, 44614588933u }, + { 0u, 17889330377156198400u, 5452321350u }, + { 0u, 17730714064155312128u, 70969782542u }, + { 0u, 7449235258647511040u, 14961183935u }, + { 0u, 9943947977234055168u, 191403823852u }, + { 0u, 0u, 236539062500u }, + { 0u, 0u, 228000000000u }, + { 3016823727048309817u, 7394076u, 0u }, + { 17405973192644624358u, 7394076163542341u, 0u }, + { 8937219978302591747u, 12396245121240683569u, 400833u }, + { 15178463196824222317u, 10248996648596888561u, 193672001794u }, + { 11979404627460330594u, 11257495103713935002u, 2555599221u }, + { 2006448052689740002u, 7555396579247433114u, 117610270032u }, + { 16330465320863239865u, 4805022328730367462u, 80409578869u }, + { 17136208883957646553u, 7056637817080232586u, 117260480782u }, + { 140455118208931867u, 10811411483818434913u, 14382541102u }, + { 3964972929179372247u, 16962406704495245447u, 46586087790u }, + { 3378050330022776379u, 18074517319117194669u, 110919533909u }, + { 6065353437512901255u, 3702019776117654523u, 85979821547u }, + { 12768753059854699889u, 3551977551381082764u, 235200686894u }, + { 6848720690951013326u, 16442608985936005282u, 46192553088u }, + { 10953228058585475132u, 3580046275479139588u, 128891355619u }, + { 15539127852083296166u, 8737412692712715330u, 227194074697u }, + { 14534853647735598497u, 3082033243045084752u, 73473656091u }, + { 1506909603576368170u, 16401023756841128699u, 27167077356u }, + { 5475702579938239025u, 7520296082779572869u, 236889101279u }, + { 12039747373985783332u, 9854104766152464159u, 223407676067u }, + { 17572399137760898460u, 14169188802648310188u, 163534192089u }, + { 869552790852091236u, 2018609909210367042u, 217768113264u }, + { 4549585778048181804u, 8270271948267674359u, 112109429062u }, + { 4121918231767210357u, 12320338602894572099u, 70448332340u }, + { 1558397953312543179u, 17538536685990080547u, 52667886893u }, + { 6067524298738069781u, 15833914616956760474u, 45950765978u }, + { 5811095224555517056u, 6137696141415969855u, 154858358231u }, + { 6440894514158997188u, 9757490468419438919u, 215332725174u }, + { 4612748874388784257u, 3566639201356598903u, 182528954618u }, + { 9908101430749813367u, 9760900035773954449u, 250193347898u }, + { 15150017990912190499u, 3873778773990716438u, 58529139451u }, + { 17470426264690059239u, 2295668377270167832u, 251209997968u }, + { 9615161096851907726u, 1791721710912807593u, 144124448432u }, + { 9227932132124142224u, 10571009006922683279u, 176097129428u }, + { 4794113194321211621u, 9840791932778184867u, 212573055546u }, + { 391512698859146347u, 11525464956561274613u, 58533470399u }, + { 17209658878068655842u, 4435781488897895433u, 191624796707u }, + { 3077364726606876150u, 6395563367070996741u, 35240464196u }, + { 1952989567673965814u, 15538690795135662932u, 68346704184u }, + { 11172860676923186449u, 16294558813563371936u, 56842354115u }, + { 17338078544784947239u, 4942096228426070342u, 195883329803u }, + { 4593380466519703278u, 6910116424372647153u, 11267911573u }, + { 786884753602720052u, 17923400669760829478u, 149374598161u }, + { 17848875822468020539u, 4134686917293039955u, 17971629497u }, + { 2899664567187130618u, 16857102463116098681u, 185224141826u }, + { 2973178834961857409u, 11364321508775167451u, 2913825355u }, + { 11117453141176836727u, 7966947780972783899u, 75616061103u }, + { 7535238370146462002u, 11261055695926686278u, 175431889104u }, + { 10246175467290865448u, 9227040437353594058u, 208610463052u }, + { 1203128834127050464u, 7185344074282882061u, 76500198864u }, + { 14518048959078919658u, 14197856148610578032u, 208389518282u }, + { 1553474987376920024u, 885688687260429427u, 202769667324u }, + { 14956572380830948369u, 17407816160380305183u, 252048013279u }, + { 7594162606042048292u, 17812728703806357349u, 223943679604u }, + { 14461296147288811288u, 17120198191964319867u, 116965629957u }, + { 18305427728131488265u, 12091952048375408786u, 5928087803u }, + { 10253208939347909876u, 405056939269888281u, 251655506034u }, + { 6446028915490812012u, 12485440679452408690u, 114021958180u }, + { 12986063676957432257u, 8394369900823444407u, 36676837095u }, + { 9769714697972762807u, 2877421667354294258u, 231455059704u }, + { 97429465146664592u, 2676980714750756239u, 248155985341u }, + { 16395571728207795868u, 6119309228579057021u, 189145119415u }, + { 6331668478323650406u, 18203256146533333852u, 183331728417u }, + { 18129911846294207040u, 351919978865493747u, 33986800493u }, + { 9890094564876124160u, 5190010931882390570u, 109019077620u }, + { 12290856656987750400u, 6982466386088036604u, 244281351056u }, + { 8498454992640802816u, 4707293888784996898u, 144378520261u }, + { 5341660584200896512u, 690306801165964760u, 197255182913u }, + { 9288674231451648000u, 12456770961278956704u, 65037421606u }, + { 1152921504606846976u, 16946092489294063943u, 38675282906u }, + { 0u, 11098404173866185376u, 218918649514u }, + { 0u, 15152070965853306880u, 170601645695u }, + { 0u, 17370091362040414208u, 127821395412u }, + { 0u, 10141938552171134976u, 212941634539u }, + { 0u, 10586988556645826560u, 235549795590u }, + { 0u, 12169852093061922816u, 6573921799u }, + { 0u, 16717361816799281152u, 7659729003u }, + { 0u, 0u, 107906250000u }, + { 0u, 0u, 16000000000u }, + { 15212079674427582639u, 112u, 0u }, + { 10179808126814248333u, 112824648491u, 0u }, + { 6066766544199222848u, 2144184049294538808u, 6u }, + { 6625308131806923532u, 4108002197393276873u, 6116236450u }, + { 9122786786400665713u, 6446230217393892753u, 162222695245u }, + { 11932880778639151320u, 5571068025259989822u, 77349450840u }, + { 3884040911779255011u, 14804812668872528331u, 88302008202u }, + { 13441817515637357872u, 17369928488562523047u, 138802570502u }, + { 11628577856022352826u, 2967474173531035027u, 6941625710u }, + { 13296656925520243654u, 5291425437992807716u, 110160867097u }, + { 5020720704545399398u, 14219547193739388064u, 25286848747u }, + { 14121973606499014694u, 17720313647158217462u, 235770843197u }, + { 3642481034345420114u, 12334850628290578491u, 61960620127u }, + { 9845536238569696768u, 7818499847417334620u, 95668673592u }, + { 17655403572195686356u, 136007040922198372u, 56423841726u }, + { 8377495365136654029u, 8523477092112604449u, 190007372956u }, + { 13713682649609025426u, 367934822655966629u, 156462058619u }, + { 1367709905452854731u, 12964987687054730050u, 123019945786u }, + { 9981467701727208680u, 15267036012420885462u, 58702833390u }, + { 6277920117543306205u, 11142900264750765568u, 238827627680u }, + { 9344111460418701726u, 13680181547777718603u, 160604057833u }, + { 4918507011364617264u, 13001922925761426065u, 233741604127u }, + { 6554777203830755259u, 2397730045956515935u, 31704835654u }, + { 4198262173120265648u, 4482395522588406288u, 70129981206u }, + { 8080325935698446819u, 3255525722490493080u, 22242991148u }, + { 12797633874200091733u, 836222287193822098u, 44176482403u }, + { 8376502502208665497u, 420898743993182306u, 99045331701u }, + { 9891973386793349173u, 11652649973356574054u, 245022816966u }, + { 14877430279003795462u, 15058402726661910231u, 198631691420u }, + { 8759933935842067041u, 9600134495208339559u, 156816317647u }, + { 3753418510388703513u, 14626343323989004842u, 207520424333u }, + { 2961383332545305985u, 6813981265331086665u, 141792895660u }, + { 2497674184068629507u, 10281745288790487888u, 172369386664u }, + { 15244544070742305452u, 17569829347075761940u, 168557374528u }, + { 45953573565810823u, 7654580675237889478u, 64952462357u }, + { 3960077421351906445u, 16194838649686212364u, 21414955649u }, + { 3173330011013883118u, 6495102772252453635u, 129877923962u }, + { 11026266219545759160u, 14935159852819761348u, 122352100226u }, + { 8886757764964685632u, 17381879863441579697u, 130809636637u }, + { 13952322129918090479u, 9062335510435372583u, 29942273595u }, + { 4127250666614902202u, 7569219009130126626u, 59491270192u }, + { 17649958504065306911u, 12652124168176193362u, 48410328184u }, + { 13269305359002216873u, 8940200224697247767u, 120685873025u }, + { 16236593433831947843u, 5600570701927432884u, 129484649225u }, + { 13580930396682424057u, 2018432801986093157u, 9303607546u }, + { 421976357197961116u, 8235849749361824736u, 250109419461u }, + { 2962130818798626533u, 9705097287982370040u, 197446466309u }, + { 8234383947306356345u, 3517483139049842585u, 5526114378u }, + { 4380469931801381425u, 958281614186777760u, 74190683143u }, + { 15279887469027055916u, 7336473432636108950u, 7051948550u }, + { 8534542821713755552u, 12955383920176764423u, 6397711021u }, + { 7814613482565088782u, 10735469126281273789u, 173702312769u }, + { 13771954404705323224u, 8637888232514730693u, 65581970947u }, + { 4568173274762548144u, 6806336737533581000u, 3468260859u }, + { 8105292616250821343u, 16142569672872330321u, 251368972253u }, + { 2923678426777275612u, 8141285259947963513u, 221875090455u }, + { 1808633176918384049u, 5220241098754220797u, 23441339958u }, + { 18118642609460438969u, 154438799943119608u, 54282989837u }, + { 18138164175864360870u, 2226876628677628879u, 13008372144u }, + { 4031433690465792404u, 17219557081221357482u, 176120719223u }, + { 5012226396942308537u, 15401507148161015114u, 119933474059u }, + { 7889712298793536835u, 8842629766613985337u, 11834917375u }, + { 13500762396543628804u, 3180100571546071440u, 255479359920u }, + { 501020624068841347u, 7740848704392475044u, 176172393597u }, + { 8370653768288261750u, 2014314126623495998u, 125419632249u }, + { 647579990023635200u, 11209566016506885858u, 121109196187u }, + { 11106569307181154304u, 7117166613733441125u, 155607671791u }, + { 10797461613892861952u, 4197646860931880328u, 239385822375u }, + { 17627230675448889344u, 5487263271238026094u, 167227554892u }, + { 12197735707942322176u, 18148076225293562697u, 76297465137u }, + { 12871287735024877568u, 9127276943027950849u, 49983809183u }, + { 4611686018427387904u, 9691696125379324722u, 159494790674u }, + { 0u, 13102362262487705216u, 18525387899u }, + { 0u, 8929385439893192704u, 123710280481u }, + { 0u, 11891353410743566336u, 33484062954u }, + { 0u, 1587423090877399040u, 234644631560u }, + { 0u, 3489137423026225152u, 8086054378u }, + { 0u, 13046928120492326912u, 234189146518u }, + { 0u, 11529215046068469760u, 150707275390u }, + { 0u, 0u, 126625000000u }, + { 0u, 0u, 64000000000u }, + { 9451803574512021605u, 1721567u, 0u }, + { 8662766454758138424u, 1721567512383298u, 0u }, + { 9207992007314947035u, 6674960280855494694u, 93326u }, + { 17118602861291201802u, 16378845781483497510u, 142361850321u }, + { 11186560605745599967u, 17606907750956804392u, 209887899008u }, + { 10505549821532796847u, 13225609159240506969u, 128954472381u }, + { 18133667530488679216u, 2668084873338435252u, 189716961709u }, + { 10778135771244330799u, 14802814305275861366u, 173144637170u }, + { 17087985777033767391u, 8005510553372365574u, 242802462171u }, + { 2639185991757283040u, 12748500143273514429u, 219433979596u }, + { 5401720287293896400u, 10393733905569036127u, 204691097577u }, + { 9816495392633895233u, 603389089974790339u, 233563445444u }, + { 10042051500090034990u, 2033494532597735873u, 196032709788u }, + { 11629689537856384738u, 9204796763694620958u, 156110235959u }, + { 2099086731766010483u, 7826260310402107021u, 55498993032u }, + { 13665537898516458594u, 10122690201685169383u, 136424262421u }, + { 16375672064669490764u, 7438455564568110133u, 21548752135u }, + { 7770550512184564348u, 2805412574380520817u, 7403239484u }, + { 6197048880720627314u, 7250965427231182867u, 60152081720u }, + { 17963594118523106281u, 8136242944826085924u, 56393075623u }, + { 8255454642407818663u, 15357191647956011780u, 167441066613u }, + { 16744201957549498409u, 7369614426695395460u, 117832515027u }, + { 17442505417202859722u, 10886957545142526638u, 211399507598u }, + { 6383975767786687150u, 2030047207417538097u, 142590183151u }, + { 15245442964998335795u, 11557093828502314355u, 239110049079u }, + { 2910016124519524433u, 15201062539664128543u, 55626511311u }, + { 3847913871169988463u, 8846936323343880674u, 207824051251u }, + { 17210451512590059177u, 1485291750116245364u, 51479593379u }, + { 17899408909991454145u, 2076024439668322013u, 163080517827u }, + { 5920601932753251608u, 7029497773682748741u, 195112541510u }, + { 16521781895108979744u, 16333533921668749881u, 70381069837u }, + { 3697493405554698771u, 2065057316131928423u, 13885442648u }, + { 2816108280295732065u, 7800502648925570223u, 88111946981u }, + { 14251078772056398988u, 17011619967093802015u, 229422866095u }, + { 4543066550096031417u, 5368819344429198672u, 175922201766u }, + { 3099369389734296977u, 15598879366754275267u, 166291044279u }, + { 5243484113636490986u, 16393893486035835647u, 183845616944u }, + { 6823370511605197226u, 12042046205096920307u, 48888714746u }, + { 11396290277624641942u, 15437070428008474344u, 250652800632u }, + { 15934361408437566099u, 13704569163204647509u, 120836845264u }, + { 14939272230935131954u, 18192483750856993350u, 208742926182u }, + { 7500666177940329347u, 5152535865317963250u, 102986216520u }, + { 4806714453065462270u, 17512614083933854680u, 72279319528u }, + { 11557851247268441487u, 14481918350603613536u, 232949360711u }, + { 10655523157206817381u, 16124419709964004915u, 71785066366u }, + { 14769600176490881210u, 18088011566435813579u, 126874106543u }, + { 16688495540925795167u, 15008862380698848893u, 175980553071u }, + { 909793965395524173u, 18160498644611827812u, 111813632059u }, + { 12724590949761703756u, 3604680497457231965u, 59984482604u }, + { 10810739657314826395u, 5957615565551495921u, 44195410121u }, + { 2431218615388671301u, 17528455034961565995u, 201322962986u }, + { 4235001167959059286u, 8503772325120113809u, 42950219451u }, + { 6253317787396334247u, 8501492578048509537u, 187460990421u }, + { 9534525563070371898u, 2296237701094386060u, 213460866836u }, + { 8392783992374030728u, 3753593040591076946u, 20124479295u }, + { 16356257019231647540u, 8518075399775653155u, 63203482686u }, + { 12896334001521091877u, 12757855675959554597u, 62461765792u }, + { 1427636373320877084u, 121631169379748595u, 160691604742u }, + { 16116755731295043521u, 16679062494579173314u, 6006593638u }, + { 15945633911163986837u, 10739912744743898054u, 102904173789u }, + { 7510154241072743838u, 9367340677776287570u, 221582211836u }, + { 3571560509790395119u, 12227321512794715397u, 252507804555u }, + { 3576991649007035076u, 7241061891859170651u, 139662844427u }, + { 15509961892750732443u, 13148571323079237489u, 11392538751u }, + { 17789791359353349378u, 12509763434355012654u, 127712785479u }, + { 18331227331079738314u, 11812768946960181977u, 71678155634u }, + { 15386712883100476416u, 14170358803552564832u, 114640371487u }, + { 14082462055028752384u, 18179989524780635952u, 31768176689u }, + { 12919043128765186048u, 17091718978514754901u, 49985539206u }, + { 6125373368465096704u, 7394768384359232459u, 134926543942u }, + { 12335992698065387520u, 6778628272692852803u, 70400871197u }, + { 2774217370460225536u, 18193335045875234320u, 29367470174u }, + { 0u, 1378519212560967521u, 94986262669u }, + { 0u, 4677732610631043584u, 141074729676u }, + { 0u, 17296098591070486528u, 204253580392u }, + { 0u, 7343735382392963072u, 104937623383u }, + { 0u, 14525996728454217728u, 87398104692u }, + { 0u, 9691359370008330240u, 116787455860u }, + { 0u, 3044433348102455296u, 116525369644u }, + { 0u, 9223372036854775808u, 44165039062u }, + { 0u, 0u, 214500000000u }, + { 4962829537462579598u, 26u, 0u }, + { 5711259460785241095u, 26269035528u, 0u }, + { 13845894607204897444u, 7822291454600056379u, 1u }, + { 12116633056637003327u, 8201586317771250746u, 1424047269u }, + { 2965791047992089560u, 3278889188817135834u, 165444608885u }, + { 16021997451315962529u, 1710725240251040430u, 117177748939u }, + { 3942052271663803856u, 1850175733663425006u, 203092738601u }, + { 15669876414782439922u, 9147599666163914249u, 41100298227u }, + { 10594415915406145286u, 10221885933644344166u, 243495892371u }, + { 10789820553031921377u, 14901479793736678101u, 147554129546u }, + { 3989990218583987244u, 5181831442059703136u, 138807810838u }, + { 2937341169808224563u, 6396246577759793483u, 22280907645u }, + { 16267436558584536843u, 14167229556464870447u, 125346741221u }, + { 16221580362814625793u, 2969982933326311854u, 229768007053u }, + { 9695437602320209830u, 7892677766222018881u, 141161003097u }, + { 10729753156793715126u, 798698968922663621u, 89427862919u }, + { 2609241432056861929u, 15926812109043458972u, 135043297557u }, + { 8462663743997037565u, 8663842590352697437u, 21863394214u }, + { 14993422143908194069u, 17093523026636671168u, 166469667847u }, + { 1307443894537745373u, 839764004742743203u, 7926641740u }, + { 10017257439419829265u, 16894643909298232323u, 76045523697u }, + { 16754772009970777891u, 9066702926218949317u, 241915860481u }, + { 11722573031602862387u, 9119392417260546810u, 1491506950u }, + { 7363764277467092384u, 9723021096578315109u, 6494363253u }, + { 6733958494847390772u, 14787464248751217597u, 117527086029u }, + { 8799796600227451045u, 3733434565920249133u, 205801630043u }, + { 10512023194742249826u, 6643788868836820841u, 91202389893u }, + { 4572542132337197059u, 4729646697422664063u, 133360160516u }, + { 12600500455757416832u, 4090144564201555829u, 4256394661u }, + { 7873789864743195199u, 2109480737093400002u, 165221727181u }, + { 15724851676325671539u, 16577155033369419739u, 205114355179u }, + { 8748017220462413167u, 745377248603805917u, 235898649375u }, + { 14172589522760466448u, 11305561465807999667u, 31040406981u }, + { 9520545591489413768u, 2211245518782892177u, 197612875715u }, + { 12488551088392570222u, 14170095199249735666u, 195119871859u }, + { 1135798823651241684u, 17849973668116118927u, 115768162399u }, + { 11168725610120161972u, 9020960204585720001u, 95967649011u }, + { 9580104948718508826u, 10807134002871850916u, 243489027232u }, + { 16638722716909738765u, 3925122626254791201u, 160585855908u }, + { 15732724012348272797u, 17208463291312718997u, 164212781323u }, + { 12269722190021214142u, 5145077219589447653u, 11932872664u }, + { 13110655916311972587u, 17602397765035489468u, 216278915194u }, + { 6618112997062866867u, 16422643262490753377u, 122954227894u }, + { 8457936459015989309u, 2902509461400906224u, 182890273275u }, + { 3329167139937134914u, 3422418805967265206u, 251157345353u }, + { 5245511557216705097u, 4228874576277237392u, 73185529695u }, + { 5655931353280440466u, 2553488530807495751u, 95229247750u }, + { 604622132328697393u, 11546099176912486413u, 6138424890u }, + { 8673282619234652338u, 10460791037534167991u, 58625915290u }, + { 16929477433058445690u, 8127117908566000904u, 154567080618u }, + { 11036952409253549455u, 11541304458088287306u, 170440571944u }, + { 18199392190170386320u, 6249718665174839700u, 40625655368u }, + { 9683116091880335715u, 13102508413386290995u, 72338797927u }, + { 2993913337608915120u, 6274675218640661911u, 103710288404u }, + { 4490779842162392585u, 3404497118599817167u, 20340150825u }, + { 946849923353644618u, 11258566093988562335u, 41184558158u }, + { 3613491058474899828u, 16762592482501635397u, 78610328090u }, + { 14624054199004410935u, 5550125446725071998u, 26908701959u }, + { 12088470271991908244u, 6370033225258510318u, 7300872903u }, + { 10071980927725011290u, 1503521728674735398u, 199345320193u }, + { 2833441711428854664u, 4250415082606384364u, 1081506076u }, + { 17655572411864340446u, 6020091901030562974u, 28230415463u }, + { 4997642792058747802u, 16288222967151527138u, 103326349835u }, + { 11489200787635734848u, 6377016228656203782u, 11882986336u }, + { 12983586226429536913u, 8378856515587563750u, 96345698742u }, + { 12311150768725063152u, 15812881490200838483u, 182454218721u }, + { 8530052476845967905u, 4548570371183413652u, 225857218023u }, + { 6282736361499820264u, 16731431495283420383u, 231246578493u }, + { 11337164765929082880u, 14737727629551135532u, 61907012718u }, + { 8343856200414134272u, 12413722258104293893u, 110798933815u }, + { 17889330377156198400u, 800899742400762438u, 55672949232u }, + { 17730714064155312128u, 603197008376033550u, 240043416862u }, + { 7449235258647511040u, 6380777281587743935u, 30032699375u }, + { 9943947977234055168u, 10001440249018225388u, 239345902629u }, + { 0u, 5505914461980436708u, 37542179162u }, + { 0u, 1105464290051876864u, 90298476221u }, + { 0u, 4500443576769970176u, 189059927339u }, + { 0u, 2843045143185981440u, 43243969535u }, + { 0u, 660949699682893824u, 255154121786u }, + { 0u, 276549164618219520u, 58035830155u }, + { 0u, 4683743612465315840u, 139014991760u }, + { 0u, 0u, 144253906250u }, + { 0u, 0u, 74000000000u }, + { 12396245121240683569u, 400833u, 0u }, + { 10248996648596888561u, 400833672001794u, 0u }, + { 11257495103713935002u, 4370024159708535157u, 21729u }, + { 7555396579247433114u, 7166684413908503888u, 225236899484u }, + { 4805022328730367462u, 10217286283215687029u, 156388506740u }, + { 7056637817080232586u, 4767369911989629198u, 116553880199u }, + { 10811411483818434913u, 14407999214182082862u, 135258439640u }, + { 16962406704495245447u, 8472271297615317358u, 216781059202u }, + { 18074517319117194669u, 6236024012584764757u, 130459282747u }, + { 3702019776117654523u, 1951826556984620523u, 59338055539u }, + { 3551977551381082764u, 12357130551551830830u, 115105808729u }, + { 16442608985936005282u, 8927758011099278464u, 89669881389u }, + { 3580046275479139588u, 10199854049407140323u, 45483974731u }, + { 8737412692712715330u, 17895455027038549577u, 75552935195u }, + { 3082033243045084752u, 16539200343720527131u, 27970114560u }, + { 16401023756841128699u, 3536976106235802604u, 896591847u }, + { 7520296082779572869u, 16980391644793590751u, 231191739858u }, + { 9854104766152464159u, 10090294316609084067u, 210920508875u }, + { 14169188802648310188u, 17603457857266236889u, 203546995950u }, + { 2018609909210367042u, 11164962743035868272u, 238954285362u }, + { 8270271948267674359u, 1585686890718568774u, 50605253843u }, + { 12320338602894572099u, 10882524700472655412u, 211085960258u }, + { 17538536685990080547u, 2194808754940947757u, 66589942846u }, + { 15833914616956760474u, 274100791137209242u, 62118980821u }, + { 6137696141415969855u, 12203404582981010903u, 213014859033u }, + { 9757490468419438919u, 541940706340938166u, 25661547888u }, + { 3566639201356598903u, 10305434016011833594u, 112029378664u }, + { 9760900035773954449u, 7900783531944543546u, 104558658697u }, + { 3873778773990716438u, 8920818625012419323u, 137428302333u }, + { 2295668377270167832u, 12532363335400447632u, 253483598546u }, + { 1791721710912807593u, 13483507182924762800u, 210679380777u }, + { 10571009006922683279u, 415911049779278804u, 41730942389u }, + { 9840791932778184867u, 3441628281170127418u, 181022546583u }, + { 11525464956561274613u, 17830811568183566527u, 151186571042u }, + { 4435781488897895433u, 17897295813176613411u, 34966610231u }, + { 6395563367070996741u, 2086148701331574596u, 55970214350u }, + { 15538690795135662932u, 13015567826878853432u, 206113090347u }, + { 16294558813563371936u, 12944531121587846595u, 43705575345u }, + { 4942096228426070342u, 3534180912913737995u, 177701724438u }, + { 6910116424372647153u, 3447584022400118677u, 22191588331u }, + { 17923400669760829478u, 6375676813770849297u, 235186893904u }, + { 4134686917293039955u, 11580694081479200185u, 80345626132u }, + { 16857102463116098681u, 1872134358882196482u, 20627790684u }, + { 11364321508775167451u, 17602652840520938059u, 92101488606u }, + { 7966947780972783899u, 10331040597716338351u, 222954241722u }, + { 11261055695926686278u, 73785407041056976u, 186560046833u }, + { 9227040437353594058u, 17166209109167902028u, 241003999914u }, + { 7185344074282882061u, 8762475644006589904u, 170930582060u }, + { 14197856148610578032u, 8839001228645872586u, 44475014756u }, + { 885688687260429427u, 13558262784529110268u, 100479163216u }, + { 17407816160380305183u, 5640853896420358111u, 80734994898u }, + { 17812728703806357349u, 8459930353450835572u, 210305791302u }, + { 17120198191964319867u, 7643830211500171269u, 70458613743u }, + { 12091952048375408786u, 1308629115231236347u, 239414372866u }, + { 405056939269888281u, 8957268500971669618u, 2070940926u }, + { 12485440679452408690u, 7645679094277669412u, 254485574498u }, + { 8394369900823444407u, 3821107497040617191u, 98414473094u }, + { 2877421667354294258u, 8847137191985934072u, 134207142652u }, + { 2676980714750756239u, 3531126524756088253u, 252479604268u }, + { 6119309228579057021u, 8726915034124352183u, 44191422752u }, + { 18203256146533333852u, 17611136727168068641u, 32473087011u }, + { 351919978865493747u, 18017743272784259949u, 35954701634u }, + { 5190010931882390570u, 18113575006829616116u, 66976743819u }, + { 6982466386088036604u, 12805550441678740368u, 139981938868u }, + { 4707293888784996898u, 8061966093393027781u, 180694190280u }, + { 690306801165964760u, 11954593141554100801u, 200437040057u }, + { 12456770961278956704u, 14068656112359197734u, 185648059792u }, + { 16946092489294063943u, 895878255770467290u, 144762663376u }, + { 11098404173866185376u, 10319906489512197802u, 208048565657u }, + { 15152070965853306880u, 14551142616794302079u, 153559443251u }, + { 17370091362040414208u, 15933181735739307476u, 51788819021u }, + { 10141938552171134976u, 11524527334398983147u, 77863739512u }, + { 10586988556645826560u, 11828012606225556742u, 120624745878u }, + { 12169852093061922816u, 3556238869349799431u, 150641197848u }, + { 16717361816799281152u, 7403090230513381483u, 24192784095u }, + { 0u, 10172292854665622800u, 223401322325u }, + { 0u, 11240746576366182400u, 85551441100u }, + { 0u, 17021927826892259328u, 204609362092u }, + { 0u, 9046328496309141504u, 172922760556u }, + { 0u, 8038996803112140800u, 108490402450u }, + { 0u, 17098478935265509376u, 146435794889u }, + { 0u, 7205759403792793600u, 201926910400u }, + { 0u, 0u, 192390625000u }, + { 0u, 0u, 232000000000u }, + { 2144184049294538808u, 6u, 0u }, + { 4108002197393276873u, 6116236450u, 0u }, + { 6446230217393892753u, 6116236450222695245u, 0u }, + { 5571068025259989822u, 6240972538554414168u, 331561842u }, + { 14804812668872528331u, 4356262642990299018u, 114338323799u }, + { 17369928488562523047u, 1335108558830511366u, 87236153471u }, + { 2967474173531035027u, 18435704923261947246u, 127072376379u }, + { 5291425437992807716u, 8395401931972636441u, 59999401566u }, + { 14219547193739388064u, 12482665946362458347u, 94455115650u }, + { 17720313647158217462u, 16101242875289374781u, 130676686676u }, + { 12334850628290578491u, 4708983440241068127u, 84872850125u }, + { 7818499847417334620u, 14856666972541426744u, 205255274503u }, + { 136007040922198372u, 6938795288315789246u, 7805381530u }, + { 8523477092112604449u, 5556307628265073820u, 154376152846u }, + { 367934822655966629u, 1441404248927865979u, 14301208040u }, + { 12964987687054730050u, 16710378912353838906u, 232078138680u }, + { 15267036012420885462u, 18289940136919312110u, 56905871455u }, + { 11142900264750765568u, 10217414145292657824u, 95991499641u }, + { 13680181547777718603u, 12461165826430955753u, 121553887130u }, + { 13001922925761426065u, 662762458988270879u, 154675521153u }, + { 2397730045956515935u, 16488546856395302470u, 129035928424u }, + { 4482395522588406288u, 2612816787977180950u, 104893845916u }, + { 3255525722490493080u, 16446616379327454252u, 156141641081u }, + { 836222287193822098u, 7842178508581740643u, 121891572860u }, + { 420898743993182306u, 14779029861369369333u, 124425125348u }, + { 11652649973356574054u, 2697664446153849542u, 228801172814u }, + { 15058402726661910231u, 12135106444393649308u, 78146240682u }, + { 9600134495208339559u, 9550285041205189839u, 170657845438u }, + { 14626343323989004842u, 8790318168586740109u, 190517721989u }, + { 6813981265331086665u, 14038474217155846828u, 133476524102u }, + { 10281745288790487888u, 4263144264274812072u, 70761027212u }, + { 17569829347075761940u, 11940456333341715520u, 140231105513u }, + { 7654580675237889478u, 15751110736831573013u, 233647293434u }, + { 16194838649686212364u, 18384528705472318081u, 250853869423u }, + { 6495102772252453635u, 2393654818032310394u, 111996627298u }, + { 14935159852819761348u, 12812209822018626434u, 98129760287u }, + { 17381879863441579697u, 3110778569433458461u, 31694551286u }, + { 9062335510435372583u, 2860264756226872891u, 246168635644u }, + { 7569219009130126626u, 2384146980060315184u, 252155055263u }, + { 12652124168176193362u, 14117430062880324728u, 159129244866u }, + { 8940200224697247767u, 3769610173216737153u, 194765307417u }, + { 5600570701927432884u, 17731974340232672009u, 25204350976u }, + { 2018432801986093157u, 1971479303384713466u, 961252255u }, + { 8235849749361824736u, 3449462959779012549u, 159106874107u }, + { 9705097287982370040u, 13743454852043766533u, 251186995761u }, + { 3517483139049842585u, 7417711187131879498u, 49745034180u }, + { 958281614186777760u, 3650992383501007879u, 196402114929u }, + { 7336473432636108950u, 12838770342493958662u, 113197920693u }, + { 12955383920176764423u, 16025068246546338477u, 181695991134u }, + { 10735469126281273789u, 6579965938260177729u, 94868720690u }, + { 8637888232514730693u, 4742939430174291459u, 50356700668u }, + { 6806336737533581000u, 13062256857527449083u, 252257115261u }, + { 16142569672872330321u, 2301174570202439645u, 125708106363u }, + { 8141285259947963513u, 7638687886069412887u, 123124746923u }, + { 5220241098754220797u, 936322449610274358u, 171414094100u }, + { 154438799943119608u, 12926010544311283981u, 20050758141u }, + { 2226876628677628879u, 12647854908989899184u, 253700720435u }, + { 17219557081221357482u, 8862093163358513015u, 51685641588u }, + { 15401507148161015114u, 444784343917630731u, 116480415033u }, + { 8842629766613985337u, 11033952249213387263u, 57024111807u }, + { 3180100571546071440u, 18168634046363183536u, 191598151749u }, + { 7740848704392475044u, 3837904761417065597u, 69984923625u }, + { 2014314126623495998u, 111459007020906105u, 233208053234u }, + { 11209566016506885858u, 16191761957496794523u, 242006042204u }, + { 7117166613733441125u, 9856250800340378607u, 92877757174u }, + { 4197646860931880328u, 9491800102275105959u, 246534308426u }, + { 5487263271238026094u, 10777328578953608268u, 74514551514u }, + { 18148076225293562697u, 17424440628313779505u, 218584240152u }, + { 9127276943027950849u, 3285814872419755679u, 24944580819u }, + { 9691696125379324722u, 2824823424107240978u, 211178124381u }, + { 13102362262487705216u, 12271707680713669755u, 93153133984u }, + { 8929385439893192704u, 6951481875178001185u, 160665250606u }, + { 11891353410743566336u, 10202522487003824362u, 46376840587u }, + { 1587423090877399040u, 4834668463880990728u, 139553079852u }, + { 3489137423026225152u, 10871520987687904746u, 44262087902u }, + { 13046928120492326912u, 12057698794225322390u, 222589346333u }, + { 11529215046068469760u, 7263351819222681214u, 29653649161u }, + { 0u, 1778055686910650944u, 9393747091u }, + { 0u, 17108187120491986944u, 147096388591u }, + { 0u, 3067636961549221888u, 239927436682u }, + { 0u, 16702141595163557888u, 138166296932u }, + { 0u, 2432053749942845440u, 100905424910u }, + { 0u, 17791470327927144448u, 14131841897u }, + { 0u, 1152921504606846976u, 105964477539u }, + { 0u, 0u, 99062500000u }, + { 0u, 0u, 160000000000u }, + { 6674960280855494694u, 93326u, 0u }, + { 16378845781483497510u, 93326361850321u, 0u }, + { 17606907750956804392u, 4283581425266273664u, 5059u }, + { 13225609159240506969u, 6725911039793895357u, 195232213414u }, + { 2668084873338435252u, 1188689198788975021u, 166364612368u }, + { 14802814305275861366u, 10825527435847761650u, 16064438970u }, + { 8005510553372365574u, 3917696829526085083u, 186586853018u }, + { 12748500143273514429u, 12646861173976387276u, 154212378770u }, + { 10393733905569036127u, 18398576063183996905u, 146685587717u }, + { 603389089974790339u, 16919251228485834948u, 5997388806u }, + { 2033494532597735873u, 17296019588687185052u, 6917194446u }, + { 9204796763694620958u, 12365301604512770359u, 206937619100u }, + { 7826260310402107021u, 2814271599679204744u, 156670324343u }, + { 10122690201685169383u, 2154994415780170517u, 119152561969u }, + { 7438455564568110133u, 6717373824370072839u, 49116822481u }, + { 2805412574380520817u, 12709155755801344060u, 209364149564u }, + { 7250965427231182867u, 826847911966403896u, 60688964714u }, + { 8136242944826085924u, 2277322703890025383u, 106044823515u }, + { 15357191647956011780u, 2774508958389496437u, 219123453911u }, + { 7369614426695395460u, 245697774950120915u, 215150406432u }, + { 10886957545142526638u, 1268929063431863950u, 32013319303u }, + { 2030047207417538097u, 6735665673159411439u, 135068788782u }, + { 11557093828502314355u, 14734771742997073207u, 46365141167u }, + { 15201062539664128543u, 13683287077957612495u, 175798773576u }, + { 8846936323343880674u, 15370263741354826803u, 72741772478u }, + { 1485291750116245364u, 48035913070297507u, 190833223667u }, + { 2076024439668322013u, 1206547475966802115u, 243002604032u }, + { 7029497773682748741u, 13512340386605768006u, 65407069u }, + { 16333533921668749881u, 2325760467700278797u, 93732505440u }, + { 2065057316131928423u, 10848110652847753816u, 96126079727u }, + { 7800502648925570223u, 15846378960784301285u, 239588077256u }, + { 17011619967093802015u, 14121839924449844911u, 200859033924u }, + { 5368819344429198672u, 5147613424753296550u, 68765546476u }, + { 15598879366754275267u, 16817040482828810167u, 236279052682u }, + { 16393893486035835647u, 5773528746119363888u, 138911653591u }, + { 12042046205096920307u, 8716201595536184826u, 215312983620u }, + { 15437070428008474344u, 5259122109038474872u, 68472506235u }, + { 13704569163204647509u, 14744540084230155984u, 123285097580u }, + { 18192483750856993350u, 10719345477982635878u, 108799303119u }, + { 5152535865317963250u, 13698037261310555208u, 207581096882u }, + { 17512614083933854680u, 16141171632951976936u, 178742572087u }, + { 14481918350603613536u, 10060790174955808839u, 55875014667u }, + { 16124419709964004915u, 4250043307981877118u, 11545396528u }, + { 18088011566435813579u, 7075646198054337199u, 48230395309u }, + { 15008862380698848893u, 18141738384245531503u, 173383571548u }, + { 18160498644611827812u, 8174370508376809531u, 92983465608u }, + { 3604680497457231965u, 3581964982731575596u, 136443133513u }, + { 5957615565551495921u, 14798509948722114761u, 73194178710u }, + { 17528455034961565995u, 14713923334885122090u, 150802228831u }, + { 8503772325120113809u, 5042978054260414139u, 95797643382u }, + { 8501492578048509537u, 2052996319372883413u, 118273380388u }, + { 2296237701094386060u, 8825683007899981588u, 36111293153u }, + { 3753593040591076946u, 9992196755378745151u, 225478441234u }, + { 8518075399775653155u, 9301073417573669950u, 18541678071u }, + { 12757855675959554597u, 5331614769144850592u, 247504212200u }, + { 121631169379748595u, 14354009428310052102u, 232289027415u }, + { 16679062494579173314u, 5581221063029119078u, 87778132410u }, + { 10739912744743898054u, 1529260335339476189u, 186302558600u }, + { 9367340677776287570u, 16483061525949201148u, 136082901368u }, + { 12227321512794715397u, 14431217812333089675u, 120893548555u }, + { 7241061891859170651u, 3452349151135392267u, 11782317885u }, + { 13148571323079237489u, 9075317899834447999u, 61187152222u }, + { 12509763434355012654u, 2764331337978901575u, 94491973969u }, + { 11812768946960181977u, 1942890683708857202u, 81149854702u }, + { 14170358803552564832u, 165089169728028447u, 238105324315u }, + { 18179989524780635952u, 15193620741871233073u, 27008949501u }, + { 17091718978514754901u, 14995000835194145926u, 253823647830u }, + { 7394768384359232459u, 1788823614552255558u, 86812880624u }, + { 6778628272692852803u, 8384901184618498845u, 240096972322u }, + { 18193335045875234320u, 405511217862281310u, 34454546404u }, + { 1378519212560967521u, 3111530463755196557u, 228021982807u }, + { 4677732610631043584u, 7893558450035460812u, 87168676404u }, + { 17296098591070486528u, 156573858237402216u, 52427910661u }, + { 7343735382392963072u, 15915324019419451223u, 5008487885u }, + { 14525996728454217728u, 16293363012778802804u, 205862771443u }, + { 9691359370008330240u, 14342105318291351412u, 243883264978u }, + { 3044433348102455296u, 3788398842525387052u, 210777487087u }, + { 9223372036854775808u, 14118764407048307670u, 239205369512u }, + { 0u, 2705021334614720768u, 168765379752u }, + { 0u, 7017988973805568000u, 168146639500u }, + { 0u, 10956732053634154496u, 140380445944u }, + { 0u, 14657517938546835456u, 248593965634u }, + { 0u, 11268868284797157376u, 66794585639u }, + { 0u, 14600669991935148032u, 39610886573u }, + { 0u, 4611686018427387904u, 173791503906u }, + { 0u, 0u, 34250000000u }, + { 0u, 0u, 128000000000u }, + { 8201586317771250746u, 1424047269u, 0u }, + { 3278889188817135834u, 1424047269444608885u, 0u }, + { 1710725240251040430u, 3001188830946823627u, 77197757u }, + { 1850175733663425006u, 9732296932705387049u, 189162694772u }, + { 9147599666163914249u, 16337535782679529459u, 116527588873u }, + { 10221885933644344166u, 7969742269895046547u, 9885659589u }, + { 14901479793736678101u, 2923592083903829642u, 197432040594u }, + { 5181831442059703136u, 8144196241160608534u, 146158488244u }, + { 6396246577759793483u, 16431078457793424253u, 180441497762u }, + { 14167229556464870447u, 202362949592775653u, 162890730548u }, + { 2969982933326311854u, 8835125248522947981u, 52010970117u }, + { 7892677766222018881u, 7959873808777345113u, 5478953099u }, + { 798698968922663621u, 14929747122315126151u, 139431505623u }, + { 15926812109043458972u, 4310328817360515349u, 215809343213u }, + { 8663842590352697437u, 7294899422760201126u, 237233663393u }, + { 17093523026636671168u, 2047461597291187207u, 161395457290u }, + { 839764004742743203u, 10942374468813517900u, 10110993115u }, + { 16894643909298232323u, 10364795403063433969u, 219593187308u }, + { 9066702926218949317u, 12330859528790939137u, 236561876684u }, + { 9119392417260546810u, 8973160144879916806u, 204668457234u }, + { 9723021096578315109u, 2895354388547509877u, 18486435986u }, + { 14787464248751217597u, 16766844772497556429u, 146156957475u }, + { 3733434565920249133u, 7442407174620948827u, 35908932476u }, + { 6643788868836820841u, 6683013428676659077u, 124403453701u }, + { 4729646697422664063u, 16713703375071907588u, 5362286883u }, + { 4090144564201555829u, 8791044883080637861u, 35906051675u }, + { 2109480737093400002u, 602844107089214413u, 91476563498u }, + { 16577155033369419739u, 9754832281172880875u, 42032680244u }, + { 745377248603805917u, 10587846778003503903u, 52528810517u }, + { 11305561465807999667u, 17206244172922947013u, 21573968323u }, + { 2211245518782892177u, 11620628420699303875u, 195932752365u }, + { 14170095199249735666u, 17864732368219338611u, 237629955528u }, + { 17849973668116118927u, 4146383014621345887u, 200968449082u }, + { 9020960204585720001u, 11445705075042688243u, 58224775873u }, + { 10807134002871850916u, 7369147888966546592u, 193620472915u }, + { 3925122626254791201u, 9762476865090597796u, 83399482307u }, + { 17208463291312718997u, 5507001428194242827u, 195529224931u }, + { 5145077219589447653u, 11371471148365328344u, 227298535145u }, + { 17602397765035489468u, 3148788104946538618u, 233616448686u }, + { 16422643262490753377u, 3762722308424507574u, 174170696145u }, + { 2902509461400906224u, 1156171244825745915u, 209203977585u }, + { 3422418805967265206u, 14208921674868257865u, 113062676168u }, + { 4228874576277237392u, 7903080886897905503u, 200770267187u }, + { 2553488530807495751u, 6367240794154270982u, 51428426873u }, + { 11546099176912486413u, 1623672396662369850u, 121345168815u }, + { 10460791037534167991u, 18323231215381674394u, 175088019456u }, + { 8127117908566000904u, 9842279843006544554u, 993304354u }, + { 11541304458088287306u, 7376839231308610600u, 34533551059u }, + { 6249718665174839700u, 609751749293657672u, 211399899256u }, + { 13102508413386290995u, 10386457966860989799u, 120033054708u }, + { 6274675218640661911u, 11160336020836149780u, 244563051014u }, + { 3404497118599817167u, 17947559933847409193u, 6605003027u }, + { 11258566093988562335u, 10229787001712704590u, 19972939173u }, + { 16762592482501635397u, 10441677090043619866u, 165554557864u }, + { 5550125446725071998u, 4996681336392922375u, 168566044449u }, + { 6370033225258510318u, 124497102381021895u, 33270870638u }, + { 1503521728674735398u, 8180812057779384577u, 110006749001u }, + { 4250415082606384364u, 5294232873532946716u, 73443482710u }, + { 6020091901030562974u, 2885620189169448039u, 86287000939u }, + { 16288222967151527138u, 16662526875008170507u, 107156429783u }, + { 6377016228656203782u, 15663095032402672480u, 215903277391u }, + { 8378856515587563750u, 1824281504410546614u, 79849098083u }, + { 15812881490200838483u, 9506565509584809953u, 99098894498u }, + { 4548570371183413652u, 16941136942345070055u, 162515351948u }, + { 16731431495283420383u, 15924115693705937725u, 140918380873u }, + { 14737727629551135532u, 9247807690406628462u, 73863248041u }, + { 12413722258104293893u, 7993916633864834871u, 169501324659u }, + { 800899742400762438u, 1018504409177639408u, 115433351089u }, + { 603197008376033550u, 12097800686634130718u, 177055213234u }, + { 6380777281587743935u, 6221488888422637551u, 178655823089u }, + { 10001440249018225388u, 8229322865256080421u, 241337267588u }, + { 5505914461980436708u, 7927745108183101786u, 132446112486u }, + { 1105464290051876864u, 8488683721235326653u, 230429763923u }, + { 4500443576769970176u, 11165516518170922283u, 83460172466u }, + { 2843045143185981440u, 5463648141113596927u, 178605283863u }, + { 660949699682893824u, 3958440403860778042u, 23296184959u }, + { 276549164618219520u, 5091534813990256011u, 127214587484u }, + { 4683743612465315840u, 6100166970623291280u, 92276012655u }, + { 0u, 1913011027739012426u, 111330690714u }, + { 0u, 11310957650604221440u, 154103704535u }, + { 0u, 16303817257009020928u, 215613168242u }, + { 0u, 9090406322154766336u, 114883831704u }, + { 0u, 3003279315069566976u, 152492791914u }, + { 0u, 16582887146675765248u, 106162808097u }, + { 0u, 9691746398101307392u, 33898960113u }, + { 0u, 0u, 241525390625u }, + { 0u, 0u, 33000000000u }, +}; + +#endif // RYU_D2FIXED_FULL_TABLE_H diff --git a/mgist-postgis/ryu/d2s.c b/mgist-postgis/ryu/d2s.c new file mode 100644 index 0000000..be0ddb8 --- /dev/null +++ b/mgist-postgis/ryu/d2s.c @@ -0,0 +1,806 @@ +// Copyright 2018 Ulf Adams +// +// The contents of this file may be used under the terms of the Apache License, +// Version 2.0. +// +// (See accompanying file LICENSE-Apache or copy at +// http://www.apache.org/licenses/LICENSE-2.0) +// +// Alternatively, the contents of this file may be used under the terms of +// the Boost Software License, Version 1.0. +// (See accompanying file LICENSE-Boost or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Unless required by applicable law or agreed to in writing, this software +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. + +// Runtime compiler options: +// -DRYU_DEBUG Generate verbose debugging output to stdout. +// +// -DRYU_ONLY_64_BIT_OPS Avoid using uint128_t or 64-bit intrinsics. Slower, +// depending on your compiler. +// +// -DRYU_OPTIMIZE_SIZE Use smaller lookup tables. Instead of storing every +// required power of 5, only store every 26th entry, and compute +// intermediate values with a multiplication. This reduces the lookup table +// size by about 10x (only one case, and only double) at the cost of some +// performance. Currently requires MSVC intrinsics. + +#include "ryu.h" + +#include +#include +#include +#include +#include + +#ifdef RYU_DEBUG +#include +#include +#endif + +#include "common.h" +#include "digit_table.h" +#include "d2s_intrinsics.h" + +// Include either the small or the full lookup tables depending on the mode. +#if defined(RYU_OPTIMIZE_SIZE) +#include "d2s_small_table.h" +#else +#include "d2s_full_table.h" +#endif + +#define DOUBLE_MANTISSA_BITS 52 +#define DOUBLE_EXPONENT_BITS 11 +#define DOUBLE_BIAS 1023 + +// We need a 64x128-bit multiplication and a subsequent 128-bit shift. +// Multiplication: +// The 64-bit factor is variable and passed in, the 128-bit factor comes +// from a lookup table. We know that the 64-bit factor only has 55 +// significant bits (i.e., the 9 topmost bits are zeros). The 128-bit +// factor only has 124 significant bits (i.e., the 4 topmost bits are +// zeros). +// Shift: +// In principle, the multiplication result requires 55 + 124 = 179 bits to +// represent. However, we then shift this value to the right by j, which is +// at least j >= 115, so the result is guaranteed to fit into 179 - 115 = 64 +// bits. This means that we only need the topmost 64 significant bits of +// the 64x128-bit multiplication. +// +// There are several ways to do this: +// 1. Best case: the compiler exposes a 128-bit type. +// We perform two 64x64-bit multiplications, add the higher 64 bits of the +// lower result to the higher result, and shift by j - 64 bits. +// +// We explicitly cast from 64-bit to 128-bit, so the compiler can tell +// that these are only 64-bit inputs, and can map these to the best +// possible sequence of assembly instructions. +// x64 machines happen to have matching assembly instructions for +// 64x64-bit multiplications and 128-bit shifts. +// +// 2. Second best case: the compiler exposes intrinsics for the x64 assembly +// instructions mentioned in 1. +// +// 3. We only have 64x64 bit instructions that return the lower 64 bits of +// the result, i.e., we have to use plain C. +// Our inputs are less than the full width, so we have three options: +// a. Ignore this fact and just implement the intrinsics manually. +// b. Split both into 31-bit pieces, which guarantees no internal overflow, +// but requires extra work upfront (unless we change the lookup table). +// c. Split only the first factor into 31-bit pieces, which also guarantees +// no internal overflow, but requires extra work since the intermediate +// results are not perfectly aligned. +#if defined(HAS_UINT128) + +// Best case: use 128-bit type. +static inline uint64_t mulShift(const uint64_t m, const uint64_t* const mul, const int32_t j) { + const uint128_t b0 = ((uint128_t) m) * mul[0]; + const uint128_t b2 = ((uint128_t) m) * mul[1]; + return (uint64_t) (((b0 >> 64) + b2) >> (j - 64)); +} + +static inline uint64_t mulShiftAll(const uint64_t m, const uint64_t* const mul, const int32_t j, + uint64_t* const vp, uint64_t* const vm, const uint32_t mmShift) { +// m <<= 2; +// uint128_t b0 = ((uint128_t) m) * mul[0]; // 0 +// uint128_t b2 = ((uint128_t) m) * mul[1]; // 64 +// +// uint128_t hi = (b0 >> 64) + b2; +// uint128_t lo = b0 & 0xffffffffffffffffull; +// uint128_t factor = (((uint128_t) mul[1]) << 64) + mul[0]; +// uint128_t vpLo = lo + (factor << 1); +// *vp = (uint64_t) ((hi + (vpLo >> 64)) >> (j - 64)); +// uint128_t vmLo = lo - (factor << mmShift); +// *vm = (uint64_t) ((hi + (vmLo >> 64) - (((uint128_t) 1ull) << 64)) >> (j - 64)); +// return (uint64_t) (hi >> (j - 64)); + *vp = mulShift(4 * m + 2, mul, j); + *vm = mulShift(4 * m - 1 - mmShift, mul, j); + return mulShift(4 * m, mul, j); +} + +#elif defined(HAS_64_BIT_INTRINSICS) + +static inline uint64_t mulShift(const uint64_t m, const uint64_t* const mul, const int32_t j) { + // m is maximum 55 bits + uint64_t high1; // 128 + const uint64_t low1 = umul128(m, mul[1], &high1); // 64 + uint64_t high0; // 64 + umul128(m, mul[0], &high0); // 0 + const uint64_t sum = high0 + low1; + if (sum < high0) { + ++high1; // overflow into high1 + } + return shiftright128(sum, high1, j - 64); +} + +static inline uint64_t mulShiftAll(const uint64_t m, const uint64_t* const mul, const int32_t j, + uint64_t* const vp, uint64_t* const vm, const uint32_t mmShift) { + *vp = mulShift(4 * m + 2, mul, j); + *vm = mulShift(4 * m - 1 - mmShift, mul, j); + return mulShift(4 * m, mul, j); +} + +#else // !defined(HAS_UINT128) && !defined(HAS_64_BIT_INTRINSICS) + +static inline uint64_t mulShiftAll(uint64_t m, const uint64_t* const mul, const int32_t j, + uint64_t* const vp, uint64_t* const vm, const uint32_t mmShift) { + m <<= 1; + // m is maximum 55 bits + uint64_t tmp; + const uint64_t lo = umul128(m, mul[0], &tmp); + uint64_t hi; + const uint64_t mid = tmp + umul128(m, mul[1], &hi); + hi += mid < tmp; // overflow into hi + + const uint64_t lo2 = lo + mul[0]; + const uint64_t mid2 = mid + mul[1] + (lo2 < lo); + const uint64_t hi2 = hi + (mid2 < mid); + *vp = shiftright128(mid2, hi2, (uint32_t) (j - 64 - 1)); + + if (mmShift == 1) { + const uint64_t lo3 = lo - mul[0]; + const uint64_t mid3 = mid - mul[1] - (lo3 > lo); + const uint64_t hi3 = hi - (mid3 > mid); + *vm = shiftright128(mid3, hi3, (uint32_t) (j - 64 - 1)); + } else { + const uint64_t lo3 = lo + lo; + const uint64_t mid3 = mid + mid + (lo3 < lo); + const uint64_t hi3 = hi + hi + (mid3 < mid); + const uint64_t lo4 = lo3 - mul[0]; + const uint64_t mid4 = mid3 - mul[1] - (lo4 > lo3); + const uint64_t hi4 = hi3 - (mid4 > mid3); + *vm = shiftright128(mid4, hi4, (uint32_t) (j - 64)); + } + + return shiftright128(mid, hi, (uint32_t) (j - 64 - 1)); +} + +#endif // HAS_64_BIT_INTRINSICS + +static inline uint32_t decimalLength17(const uint64_t v) { + // This is slightly faster than a loop. + // The average output length is 16.38 digits, so we check high-to-low. + // Function precondition: v is not an 18, 19, or 20-digit number. + // (17 digits are sufficient for round-tripping.) + assert(v < 100000000000000000L); + if (v >= 10000000000000000L) { return 17; } + if (v >= 1000000000000000L) { return 16; } + if (v >= 100000000000000L) { return 15; } + if (v >= 10000000000000L) { return 14; } + if (v >= 1000000000000L) { return 13; } + if (v >= 100000000000L) { return 12; } + if (v >= 10000000000L) { return 11; } + if (v >= 1000000000L) { return 10; } + if (v >= 100000000L) { return 9; } + if (v >= 10000000L) { return 8; } + if (v >= 1000000L) { return 7; } + if (v >= 100000L) { return 6; } + if (v >= 10000L) { return 5; } + if (v >= 1000L) { return 4; } + if (v >= 100L) { return 3; } + if (v >= 10L) { return 2; } + return 1; +} + +// A floating decimal representing m * 10^e. +typedef struct floating_decimal_64 { + uint64_t mantissa; + // Decimal exponent's range is -324 to 308 + // inclusive, and can fit in a short if needed. + int32_t exponent; +} floating_decimal_64; + +static inline floating_decimal_64 d2d(const uint64_t ieeeMantissa, const uint32_t ieeeExponent) { + int32_t e2; + uint64_t m2; + if (ieeeExponent == 0) { + // We subtract 2 so that the bounds computation has 2 additional bits. + e2 = 1 - DOUBLE_BIAS - DOUBLE_MANTISSA_BITS - 2; + m2 = ieeeMantissa; + } else { + e2 = (int32_t) ieeeExponent - DOUBLE_BIAS - DOUBLE_MANTISSA_BITS - 2; + m2 = (1ull << DOUBLE_MANTISSA_BITS) | ieeeMantissa; + } + const bool even = (m2 & 1) == 0; + const bool acceptBounds = even; + +#ifdef RYU_DEBUG + printf("-> %" PRIu64 " * 2^%d\n", m2, e2 + 2); +#endif + + // Step 2: Determine the interval of valid decimal representations. + const uint64_t mv = 4 * m2; + // Implicit bool -> int conversion. True is 1, false is 0. + const uint32_t mmShift = ieeeMantissa != 0 || ieeeExponent <= 1; + // We would compute mp and mm like this: + // uint64_t mp = 4 * m2 + 2; + // uint64_t mm = mv - 1 - mmShift; + + // Step 3: Convert to a decimal power base using 128-bit arithmetic. + uint64_t vr, vp, vm; + int32_t e10; + bool vmIsTrailingZeros = false; + bool vrIsTrailingZeros = false; + if (e2 >= 0) { + // I tried special-casing q == 0, but there was no effect on performance. + // This expression is slightly faster than max(0, log10Pow2(e2) - 1). + const uint32_t q = log10Pow2(e2) - (e2 > 3); + e10 = (int32_t) q; + const int32_t k = DOUBLE_POW5_INV_BITCOUNT + pow5bits((int32_t) q) - 1; + const int32_t i = -e2 + (int32_t) q + k; +#if defined(RYU_OPTIMIZE_SIZE) + uint64_t pow5[2]; + double_computeInvPow5(q, pow5); + vr = mulShiftAll(m2, pow5, i, &vp, &vm, mmShift); +#else + vr = mulShiftAll(m2, DOUBLE_POW5_INV_SPLIT[q], i, &vp, &vm, mmShift); +#endif +#ifdef RYU_DEBUG + printf("%" PRIu64 " * 2^%d / 10^%u\n", mv, e2, q); + printf("V+=%" PRIu64 "\nV =%" PRIu64 "\nV-=%" PRIu64 "\n", vp, vr, vm); +#endif + if (q <= 21) { + // This should use q <= 22, but I think 21 is also safe. Smaller values + // may still be safe, but it's more difficult to reason about them. + // Only one of mp, mv, and mm can be a multiple of 5, if any. + const uint32_t mvMod5 = ((uint32_t) mv) - 5 * ((uint32_t) div5(mv)); + if (mvMod5 == 0) { + vrIsTrailingZeros = multipleOfPowerOf5(mv, q); + } else if (acceptBounds) { + // Same as min(e2 + (~mm & 1), pow5Factor(mm)) >= q + // <=> e2 + (~mm & 1) >= q && pow5Factor(mm) >= q + // <=> true && pow5Factor(mm) >= q, since e2 >= q. + vmIsTrailingZeros = multipleOfPowerOf5(mv - 1 - mmShift, q); + } else { + // Same as min(e2 + 1, pow5Factor(mp)) >= q. + vp -= multipleOfPowerOf5(mv + 2, q); + } + } + } else { + // This expression is slightly faster than max(0, log10Pow5(-e2) - 1). + const uint32_t q = log10Pow5(-e2) - (-e2 > 1); + e10 = (int32_t) q + e2; + const int32_t i = -e2 - (int32_t) q; + const int32_t k = pow5bits(i) - DOUBLE_POW5_BITCOUNT; + const int32_t j = (int32_t) q - k; +#if defined(RYU_OPTIMIZE_SIZE) + uint64_t pow5[2]; + double_computePow5(i, pow5); + vr = mulShiftAll(m2, pow5, j, &vp, &vm, mmShift); +#else + vr = mulShiftAll(m2, DOUBLE_POW5_SPLIT[i], j, &vp, &vm, mmShift); +#endif +#ifdef RYU_DEBUG + printf("%" PRIu64 " * 5^%d / 10^%u\n", mv, -e2, q); + printf("%u %d %d %d\n", q, i, k, j); + printf("V+=%" PRIu64 "\nV =%" PRIu64 "\nV-=%" PRIu64 "\n", vp, vr, vm); +#endif + if (q <= 1) { + // {vr,vp,vm} is trailing zeros if {mv,mp,mm} has at least q trailing 0 bits. + // mv = 4 * m2, so it always has at least two trailing 0 bits. + vrIsTrailingZeros = true; + if (acceptBounds) { + // mm = mv - 1 - mmShift, so it has 1 trailing 0 bit iff mmShift == 1. + vmIsTrailingZeros = mmShift == 1; + } else { + // mp = mv + 2, so it always has at least one trailing 0 bit. + --vp; + } + } else if (q < 63) { // TODO(ulfjack): Use a tighter bound here. + // We want to know if the full product has at least q trailing zeros. + // We need to compute min(p2(mv), p5(mv) - e2) >= q + // <=> p2(mv) >= q && p5(mv) - e2 >= q + // <=> p2(mv) >= q (because -e2 >= q) + vrIsTrailingZeros = multipleOfPowerOf2(mv, q); +#ifdef RYU_DEBUG + printf("vr is trailing zeros=%s\n", vrIsTrailingZeros ? "true" : "false"); +#endif + } + } +#ifdef RYU_DEBUG + printf("e10=%d\n", e10); + printf("V+=%" PRIu64 "\nV =%" PRIu64 "\nV-=%" PRIu64 "\n", vp, vr, vm); + printf("vm is trailing zeros=%s\n", vmIsTrailingZeros ? "true" : "false"); + printf("vr is trailing zeros=%s\n", vrIsTrailingZeros ? "true" : "false"); +#endif + + // Step 4: Find the shortest decimal representation in the interval of valid representations. + int32_t removed = 0; + uint8_t lastRemovedDigit = 0; + uint64_t output; + // On average, we remove ~2 digits. + if (vmIsTrailingZeros || vrIsTrailingZeros) { + // General case, which happens rarely (~0.7%). + for (;;) { + const uint64_t vpDiv10 = div10(vp); + const uint64_t vmDiv10 = div10(vm); + if (vpDiv10 <= vmDiv10) { + break; + } + const uint32_t vmMod10 = ((uint32_t) vm) - 10 * ((uint32_t) vmDiv10); + const uint64_t vrDiv10 = div10(vr); + const uint32_t vrMod10 = ((uint32_t) vr) - 10 * ((uint32_t) vrDiv10); + vmIsTrailingZeros &= vmMod10 == 0; + vrIsTrailingZeros &= lastRemovedDigit == 0; + lastRemovedDigit = (uint8_t) vrMod10; + vr = vrDiv10; + vp = vpDiv10; + vm = vmDiv10; + ++removed; + } +#ifdef RYU_DEBUG + printf("V+=%" PRIu64 "\nV =%" PRIu64 "\nV-=%" PRIu64 "\n", vp, vr, vm); + printf("d-10=%s\n", vmIsTrailingZeros ? "true" : "false"); +#endif + if (vmIsTrailingZeros) { + for (;;) { + const uint64_t vmDiv10 = div10(vm); + const uint32_t vmMod10 = ((uint32_t) vm) - 10 * ((uint32_t) vmDiv10); + if (vmMod10 != 0) { + break; + } + const uint64_t vpDiv10 = div10(vp); + const uint64_t vrDiv10 = div10(vr); + const uint32_t vrMod10 = ((uint32_t) vr) - 10 * ((uint32_t) vrDiv10); + vrIsTrailingZeros &= lastRemovedDigit == 0; + lastRemovedDigit = (uint8_t) vrMod10; + vr = vrDiv10; + vp = vpDiv10; + vm = vmDiv10; + ++removed; + } + } +#ifdef RYU_DEBUG + printf("%" PRIu64 " %d\n", vr, lastRemovedDigit); + printf("vr is trailing zeros=%s\n", vrIsTrailingZeros ? "true" : "false"); +#endif + if (vrIsTrailingZeros && lastRemovedDigit == 5 && vr % 2 == 0) { + // Round even if the exact number is .....50..0. + lastRemovedDigit = 4; + } + // We need to take vr + 1 if vr is outside bounds or we need to round up. + output = vr + ((vr == vm && (!acceptBounds || !vmIsTrailingZeros)) || lastRemovedDigit >= 5); + } else { + // Specialized for the common case (~99.3%). Percentages below are relative to this. + bool roundUp = false; + const uint64_t vpDiv100 = div100(vp); + const uint64_t vmDiv100 = div100(vm); + if (vpDiv100 > vmDiv100) { // Optimization: remove two digits at a time (~86.2%). + const uint64_t vrDiv100 = div100(vr); + const uint32_t vrMod100 = ((uint32_t) vr) - 100 * ((uint32_t) vrDiv100); + roundUp = vrMod100 >= 50; + vr = vrDiv100; + vp = vpDiv100; + vm = vmDiv100; + removed += 2; + } + // Loop iterations below (approximately), without optimization above: + // 0: 0.03%, 1: 13.8%, 2: 70.6%, 3: 14.0%, 4: 1.40%, 5: 0.14%, 6+: 0.02% + // Loop iterations below (approximately), with optimization above: + // 0: 70.6%, 1: 27.8%, 2: 1.40%, 3: 0.14%, 4+: 0.02% + for (;;) { + const uint64_t vpDiv10 = div10(vp); + const uint64_t vmDiv10 = div10(vm); + if (vpDiv10 <= vmDiv10) { + break; + } + const uint64_t vrDiv10 = div10(vr); + const uint32_t vrMod10 = ((uint32_t) vr) - 10 * ((uint32_t) vrDiv10); + roundUp = vrMod10 >= 5; + vr = vrDiv10; + vp = vpDiv10; + vm = vmDiv10; + ++removed; + } +#ifdef RYU_DEBUG + printf("%" PRIu64 " roundUp=%s\n", vr, roundUp ? "true" : "false"); + printf("vr is trailing zeros=%s\n", vrIsTrailingZeros ? "true" : "false"); +#endif + // We need to take vr + 1 if vr is outside bounds or we need to round up. + output = vr + (vr == vm || roundUp); + } + const int32_t exp = e10 + removed; + +#ifdef RYU_DEBUG + printf("V+=%" PRIu64 "\nV =%" PRIu64 "\nV-=%" PRIu64 "\n", vp, vr, vm); + printf("O=%" PRIu64 "\n", output); + printf("EXP=%d\n", exp); +#endif + + floating_decimal_64 fd; + fd.exponent = exp; + fd.mantissa = output; + return fd; +} + +static inline uint64_t +pow_10(const int32_t exp) +{ + static const uint64_t POW_TABLE[18] = { + 1ULL, + 10ULL, + 100ULL, + 1000ULL, + 10000ULL, + + 100000ULL, + 1000000ULL, + 10000000ULL, + 100000000ULL, + 1000000000ULL, + + 10000000000ULL, + 100000000000ULL, + 1000000000000ULL, + 10000000000000ULL, + 100000000000000ULL, + + 1000000000000000ULL, + 10000000000000000ULL, + 100000000000000000ULL + }; + assert(exp <= 17); + assert(exp >= 0); + return POW_TABLE[exp]; +} + +static inline int to_chars_uint64(uint64_t output, uint32_t olength, char* const result) +{ + uint32_t i = 0; + + // We prefer 32-bit operations, even on 64-bit platforms. + // We have at most 17 digits, and uint32_t can store 9 digits. + // If output doesn't fit into uint32_t, we cut off 8 digits, + // so the rest will fit into uint32_t. + if ((output >> 32) != 0) { + // Expensive 64-bit division. + const uint64_t q = div1e8(output); + uint32_t output2 = ((uint32_t) output) - 100000000 * ((uint32_t) q); + output = q; + + const uint32_t c = output2 % 10000; + output2 /= 10000; + const uint32_t d = output2 % 10000; + const uint32_t c0 = (c % 100) << 1; + const uint32_t c1 = (c / 100) << 1; + const uint32_t d0 = (d % 100) << 1; + const uint32_t d1 = (d / 100) << 1; + memcpy(result + olength - i - 2, DIGIT_TABLE + c0, 2); + memcpy(result + olength - i - 4, DIGIT_TABLE + c1, 2); + memcpy(result + olength - i - 6, DIGIT_TABLE + d0, 2); + memcpy(result + olength - i - 8, DIGIT_TABLE + d1, 2); + i += 8; + } + + uint32_t output2 = (uint32_t) output; + while (output2 >= 10000) + { + #ifdef __clang__ // https://bugs.llvm.org/show_bug.cgi?id=38217 + const uint32_t c = output2 - 10000 * (output2 / 10000); + #else + const uint32_t c = output2 % 10000; + #endif + output2 /= 10000; + const uint32_t c0 = (c % 100) << 1; + const uint32_t c1 = (c / 100) << 1; + memcpy(result + olength - i - 2, DIGIT_TABLE + c0, 2); + memcpy(result + olength - i - 4, DIGIT_TABLE + c1, 2); + i += 4; + } + + if (output2 >= 100) + { + #ifdef __clang__ // https://bugs.llvm.org/show_bug.cgi?id=38217 + const uint32_t c = (output2 % 100) << 1; + #else + const uint32_t c = (output2 - 100 * (output2 / 100)) << 1; + #endif + output2 /= 100; + memcpy(result + olength - i - 2, DIGIT_TABLE + c, 2); + i += 2; + } + if (output2 >= 10) + { + const uint32_t c = output2 << 1; + memcpy(result + olength - i - 2, DIGIT_TABLE + c, 2); + i += 2; + } else { + result[0] = (char) ('0' + output2); + i += 1; + } + + return i; +} + +static inline int to_chars_fixed(const floating_decimal_64 v, const bool sign, uint32_t precision, char* const result) +{ + uint64_t output = v.mantissa; + uint32_t olength = decimalLength17(output); + int32_t exp = v.exponent; + uint64_t integer_part; + uint32_t integer_part_length = 0; + uint64_t decimal_part; + uint32_t decimal_part_length = 0; + uint32_t trailing_integer_zeros = 0; + uint32_t leading_decimal_zeros = 0; + + if (exp >= 0) + { + integer_part = output; + integer_part_length = olength; + trailing_integer_zeros = exp; + decimal_part = 0; + } + else + { + /* Adapt the decimal digits to the desired precision */ + if (precision < (uint32_t) -exp) + { + int32_t digits_to_trim = -exp - precision; + if (digits_to_trim > (int32_t) olength) + { + output = 0; + exp = 0; + } + else + { + const uint64_t divisor = pow_10(digits_to_trim); + const uint64_t divisor_half = divisor / 2; + const uint64_t outputDiv = output / divisor; + const uint64_t remainder = output - outputDiv * divisor; + + output = outputDiv; + exp += digits_to_trim; + + if (remainder > divisor_half || (remainder == divisor_half && (output & 1))) + { + output++; + olength = decimalLength17(output); + } + else + { + olength -= digits_to_trim; + } + + while (output && output % 10 == 0) + { + output = div10(output); + exp++; + olength--; + } + } + } + + int32_t nexp = -exp; + if (exp >= 0) + { + integer_part = output; + integer_part_length = olength; + trailing_integer_zeros = exp; + decimal_part = 0; + } + else if (nexp < (int32_t) olength) + { + uint64_t p = pow_10(nexp); + integer_part = output / p; + decimal_part = output % p; + integer_part_length = olength - nexp; + decimal_part_length = olength - integer_part_length; + if (decimal_part < pow_10(decimal_part_length - 1)) + { + /* The decimal part had leading zeros (e.g. 123.0001) which were lost */ + decimal_part_length = decimalLength17(decimal_part); + leading_decimal_zeros = olength - integer_part_length - decimal_part_length; + } + } + else + { + integer_part = 0; + decimal_part = output; + decimal_part_length = olength; + leading_decimal_zeros = nexp - olength; + } + } + +#ifdef RYU_DEBUG + printf("DIGITS=%" PRIu64 "\n", v.mantissa); + printf("EXP=%d\n", v.exponent); + printf("INTEGER=%lu\n", integer_part); + printf("DECIMAL=%lu\n", decimal_part); + printf("EXTRA TRAILING ZEROS=%d\n", trailing_integer_zeros); + printf("EXTRA LEADING ZEROS=%d\n", leading_decimal_zeros); +#endif + + /* If we have removed all digits, it may happen that we have -0 and we want it to be just 0 */ + int index = 0; + if (sign && (integer_part || decimal_part)) + { + result[index++] = '-'; + } + + index += to_chars_uint64(integer_part, integer_part_length, &result[index]); + for (uint32_t i = 0; i < trailing_integer_zeros; i++) + result[index++] = '0'; + + if (decimal_part) + { + result[index++] = '.'; + for (uint32_t i = 0; i < leading_decimal_zeros; i++) + result[index++] = '0'; + index += to_chars_uint64(decimal_part, decimal_part_length, &result[index]); + } + + return index; +} + +static inline bool d2d_small_int(const uint64_t ieeeMantissa, const uint32_t ieeeExponent, + floating_decimal_64* const v) { + const uint64_t m2 = (1ull << DOUBLE_MANTISSA_BITS) | ieeeMantissa; + const int32_t e2 = (int32_t) ieeeExponent - DOUBLE_BIAS - DOUBLE_MANTISSA_BITS; + + if (e2 > 0) { + // f = m2 * 2^e2 >= 2^53 is an integer. + // Ignore this case for now. + return false; + } + + if (e2 < -52) { + // f < 1. + return false; + } + + // Since 2^52 <= m2 < 2^53 and 0 <= -e2 <= 52: 1 <= f = m2 / 2^-e2 < 2^53. + // Test if the lower -e2 bits of the significand are 0, i.e. whether the fraction is 0. + const uint64_t mask = (1ull << -e2) - 1; + const uint64_t fraction = m2 & mask; + if (fraction != 0) { + return false; + } + + // f is an integer in the range [1, 2^53). + // Note: mantissa might contain trailing (decimal) 0's. + // Note: since 2^53 < 10^16, there is no need to adjust decimalLength17(). + v->mantissa = m2 >> -e2; + v->exponent = 0; + return true; +} + +int d2sfixed_buffered_n(double f, uint32_t precision, char* result) { + // Step 1: Decode the floating-point number, and unify normalized and subnormal cases. + const uint64_t bits = double_to_bits(f); + +#ifdef RYU_DEBUG + printf("IN="); + for (int32_t bit = 63; bit >= 0; --bit) { + printf("%d", (int) ((bits >> bit) & 1)); + } + printf("\n"); +#endif + + // Decode bits into sign, mantissa, and exponent. + const bool ieeeSign = ((bits >> (DOUBLE_MANTISSA_BITS + DOUBLE_EXPONENT_BITS)) & 1) != 0; + const uint64_t ieeeMantissa = bits & ((1ull << DOUBLE_MANTISSA_BITS) - 1); + const uint32_t ieeeExponent = (uint32_t) ((bits >> DOUBLE_MANTISSA_BITS) & ((1u << DOUBLE_EXPONENT_BITS) - 1)); + // Case distinction; exit early for the easy cases. + if (ieeeExponent == ((1u << DOUBLE_EXPONENT_BITS) - 1u) || (ieeeExponent == 0 && ieeeMantissa == 0)) { + return copy_special_str(result, ieeeSign, ieeeExponent, ieeeMantissa); + } + + floating_decimal_64 v; + const bool isSmallInt = d2d_small_int(ieeeMantissa, ieeeExponent, &v); + if (isSmallInt) { + // For small integers in the range [1, 2^53), v.mantissa might contain trailing (decimal) zeros. + // For scientific notation we need to move these zeros into the exponent. + // (This is not needed for fixed-point notation, so it might be beneficial to trim + // trailing zeros in to_chars only if needed - once fixed-point notation output is implemented.) + for (;;) { + const uint64_t q = div10(v.mantissa); + const uint32_t r = ((uint32_t) v.mantissa) - 10 * ((uint32_t) q); + if (r != 0) { + break; + } + v.mantissa = q; + ++v.exponent; + } + } else { + v = d2d(ieeeMantissa, ieeeExponent); + } + + return to_chars_fixed(v, ieeeSign, precision, result); +} + +int d2sexp_buffered_n(double f, uint32_t precision, char* result) { + // Step 1: Decode the floating-point number, and unify normalized and subnormal cases. + const uint64_t bits = double_to_bits(f); + +#ifdef RYU_DEBUG + printf("IN="); + for (int32_t bit = 63; bit >= 0; --bit) { + printf("%d", (int) ((bits >> bit) & 1)); + } + printf("\n"); +#endif + + // Decode bits into sign, mantissa, and exponent. + const bool ieeeSign = ((bits >> (DOUBLE_MANTISSA_BITS + DOUBLE_EXPONENT_BITS)) & 1) != 0; + const uint64_t ieeeMantissa = bits & ((1ull << DOUBLE_MANTISSA_BITS) - 1); + const uint32_t ieeeExponent = (uint32_t) ((bits >> DOUBLE_MANTISSA_BITS) & ((1u << DOUBLE_EXPONENT_BITS) - 1)); + // Case distinction; exit early for the easy cases. + if (ieeeExponent == ((1u << DOUBLE_EXPONENT_BITS) - 1u) || (ieeeExponent == 0 && ieeeMantissa == 0)) { + return copy_special_str(result, ieeeSign, ieeeExponent, ieeeMantissa); + } + + floating_decimal_64 v; + const bool isSmallInt = d2d_small_int(ieeeMantissa, ieeeExponent, &v); + if (isSmallInt) { + // For small integers in the range [1, 2^53), v.mantissa might contain trailing (decimal) zeros. + // For scientific notation we need to move these zeros into the exponent. + // (This is not needed for fixed-point notation, so it might be beneficial to trim + // trailing zeros in to_chars only if needed - once fixed-point notation output is implemented.) + for (;;) { + const uint64_t q = div10(v.mantissa); + const uint32_t r = ((uint32_t) v.mantissa) - 10 * ((uint32_t) q); + if (r != 0) { + break; + } + v.mantissa = q; + ++v.exponent; + } + } else { + v = d2d(ieeeMantissa, ieeeExponent); + } + + // Print first the mantissa using the fixed point notation, then add the exponent manually + const int32_t olength = (int32_t) decimalLength17(v.mantissa); + const int32_t original_ieeeExponent = v.exponent + olength - 1; + v.exponent = 1 - olength; + int index = to_chars_fixed(v, ieeeSign, precision, result); + + // Print the exponent. + result[index++] = 'e'; + int32_t exp = original_ieeeExponent; + if (exp < 0) { + result[index++] = '-'; + exp = -exp; + } + else + { + result[index++] = '+'; + } + + if (exp >= 100) { + const int32_t c = exp % 10; + memcpy(result + index, DIGIT_TABLE + 2 * (exp / 10), 2); + result[index + 2] = (char) ('0' + c); + index += 3; + } else if (exp >= 10) { + memcpy(result + index, DIGIT_TABLE + 2 * exp, 2); + index += 2; + } else { + result[index++] = (char) ('0' + exp); + } + + return index; +} diff --git a/mgist-postgis/ryu/d2s_full_table.h b/mgist-postgis/ryu/d2s_full_table.h new file mode 100644 index 0000000..94b62d7 --- /dev/null +++ b/mgist-postgis/ryu/d2s_full_table.h @@ -0,0 +1,339 @@ +// Copyright 2018 Ulf Adams +// +// The contents of this file may be used under the terms of the Apache License, +// Version 2.0. +// +// (See accompanying file LICENSE-Apache or copy at +// http://www.apache.org/licenses/LICENSE-2.0) +// +// Alternatively, the contents of this file may be used under the terms of +// the Boost Software License, Version 1.0. +// (See accompanying file LICENSE-Boost or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Unless required by applicable law or agreed to in writing, this software +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. +#ifndef RYU_D2S_FULL_TABLE_H +#define RYU_D2S_FULL_TABLE_H + +// These tables are generated by PrintDoubleLookupTable. +#define DOUBLE_POW5_INV_BITCOUNT 125 +#define DOUBLE_POW5_BITCOUNT 125 + +static const uint64_t DOUBLE_POW5_INV_SPLIT[292][2] = { + { 1u, 2305843009213693952u }, { 11068046444225730970u, 1844674407370955161u }, + { 5165088340638674453u, 1475739525896764129u }, { 7821419487252849886u, 1180591620717411303u }, + { 8824922364862649494u, 1888946593147858085u }, { 7059937891890119595u, 1511157274518286468u }, + { 13026647942995916322u, 1208925819614629174u }, { 9774590264567735146u, 1934281311383406679u }, + { 11509021026396098440u, 1547425049106725343u }, { 16585914450600699399u, 1237940039285380274u }, + { 15469416676735388068u, 1980704062856608439u }, { 16064882156130220778u, 1584563250285286751u }, + { 9162556910162266299u, 1267650600228229401u }, { 7281393426775805432u, 2028240960365167042u }, + { 16893161185646375315u, 1622592768292133633u }, { 2446482504291369283u, 1298074214633706907u }, + { 7603720821608101175u, 2076918743413931051u }, { 2393627842544570617u, 1661534994731144841u }, + { 16672297533003297786u, 1329227995784915872u }, { 11918280793837635165u, 2126764793255865396u }, + { 5845275820328197809u, 1701411834604692317u }, { 15744267100488289217u, 1361129467683753853u }, + { 3054734472329800808u, 2177807148294006166u }, { 17201182836831481939u, 1742245718635204932u }, + { 6382248639981364905u, 1393796574908163946u }, { 2832900194486363201u, 2230074519853062314u }, + { 5955668970331000884u, 1784059615882449851u }, { 1075186361522890384u, 1427247692705959881u }, + { 12788344622662355584u, 2283596308329535809u }, { 13920024512871794791u, 1826877046663628647u }, + { 3757321980813615186u, 1461501637330902918u }, { 10384555214134712795u, 1169201309864722334u }, + { 5547241898389809503u, 1870722095783555735u }, { 4437793518711847602u, 1496577676626844588u }, + { 10928932444453298728u, 1197262141301475670u }, { 17486291911125277965u, 1915619426082361072u }, + { 6610335899416401726u, 1532495540865888858u }, { 12666966349016942027u, 1225996432692711086u }, + { 12888448528943286597u, 1961594292308337738u }, { 17689456452638449924u, 1569275433846670190u }, + { 14151565162110759939u, 1255420347077336152u }, { 7885109000409574610u, 2008672555323737844u }, + { 9997436015069570011u, 1606938044258990275u }, { 7997948812055656009u, 1285550435407192220u }, + { 12796718099289049614u, 2056880696651507552u }, { 2858676849947419045u, 1645504557321206042u }, + { 13354987924183666206u, 1316403645856964833u }, { 17678631863951955605u, 2106245833371143733u }, + { 3074859046935833515u, 1684996666696914987u }, { 13527933681774397782u, 1347997333357531989u }, + { 10576647446613305481u, 2156795733372051183u }, { 15840015586774465031u, 1725436586697640946u }, + { 8982663654677661702u, 1380349269358112757u }, { 18061610662226169046u, 2208558830972980411u }, + { 10759939715039024913u, 1766847064778384329u }, { 12297300586773130254u, 1413477651822707463u }, + { 15986332124095098083u, 2261564242916331941u }, { 9099716884534168143u, 1809251394333065553u }, + { 14658471137111155161u, 1447401115466452442u }, { 4348079280205103483u, 1157920892373161954u }, + { 14335624477811986218u, 1852673427797059126u }, { 7779150767507678651u, 1482138742237647301u }, + { 2533971799264232598u, 1185710993790117841u }, { 15122401323048503126u, 1897137590064188545u }, + { 12097921058438802501u, 1517710072051350836u }, { 5988988032009131678u, 1214168057641080669u }, + { 16961078480698431330u, 1942668892225729070u }, { 13568862784558745064u, 1554135113780583256u }, + { 7165741412905085728u, 1243308091024466605u }, { 11465186260648137165u, 1989292945639146568u }, + { 16550846638002330379u, 1591434356511317254u }, { 16930026125143774626u, 1273147485209053803u }, + { 4951948911778577463u, 2037035976334486086u }, { 272210314680951647u, 1629628781067588869u }, + { 3907117066486671641u, 1303703024854071095u }, { 6251387306378674625u, 2085924839766513752u }, + { 16069156289328670670u, 1668739871813211001u }, { 9165976216721026213u, 1334991897450568801u }, + { 7286864317269821294u, 2135987035920910082u }, { 16897537898041588005u, 1708789628736728065u }, + { 13518030318433270404u, 1367031702989382452u }, { 6871453250525591353u, 2187250724783011924u }, + { 9186511415162383406u, 1749800579826409539u }, { 11038557946871817048u, 1399840463861127631u }, + { 10282995085511086630u, 2239744742177804210u }, { 8226396068408869304u, 1791795793742243368u }, + { 13959814484210916090u, 1433436634993794694u }, { 11267656730511734774u, 2293498615990071511u }, + { 5324776569667477496u, 1834798892792057209u }, { 7949170070475892320u, 1467839114233645767u }, + { 17427382500606444826u, 1174271291386916613u }, { 5747719112518849781u, 1878834066219066582u }, + { 15666221734240810795u, 1503067252975253265u }, { 12532977387392648636u, 1202453802380202612u }, + { 5295368560860596524u, 1923926083808324180u }, { 4236294848688477220u, 1539140867046659344u }, + { 7078384693692692099u, 1231312693637327475u }, { 11325415509908307358u, 1970100309819723960u }, + { 9060332407926645887u, 1576080247855779168u }, { 14626963555825137356u, 1260864198284623334u }, + { 12335095245094488799u, 2017382717255397335u }, { 9868076196075591040u, 1613906173804317868u }, + { 15273158586344293478u, 1291124939043454294u }, { 13369007293925138595u, 2065799902469526871u }, + { 7005857020398200553u, 1652639921975621497u }, { 16672732060544291412u, 1322111937580497197u }, + { 11918976037903224966u, 2115379100128795516u }, { 5845832015580669650u, 1692303280103036413u }, + { 12055363241948356366u, 1353842624082429130u }, { 841837113407818570u, 2166148198531886609u }, + { 4362818505468165179u, 1732918558825509287u }, { 14558301248600263113u, 1386334847060407429u }, + { 12225235553534690011u, 2218135755296651887u }, { 2401490813343931363u, 1774508604237321510u }, + { 1921192650675145090u, 1419606883389857208u }, { 17831303500047873437u, 2271371013423771532u }, + { 6886345170554478103u, 1817096810739017226u }, { 1819727321701672159u, 1453677448591213781u }, + { 16213177116328979020u, 1162941958872971024u }, { 14873036941900635463u, 1860707134196753639u }, + { 15587778368262418694u, 1488565707357402911u }, { 8780873879868024632u, 1190852565885922329u }, + { 2981351763563108441u, 1905364105417475727u }, { 13453127855076217722u, 1524291284333980581u }, + { 7073153469319063855u, 1219433027467184465u }, { 11317045550910502167u, 1951092843947495144u }, + { 12742985255470312057u, 1560874275157996115u }, { 10194388204376249646u, 1248699420126396892u }, + { 1553625868034358140u, 1997919072202235028u }, { 8621598323911307159u, 1598335257761788022u }, + { 17965325103354776697u, 1278668206209430417u }, { 13987124906400001422u, 2045869129935088668u }, + { 121653480894270168u, 1636695303948070935u }, { 97322784715416134u, 1309356243158456748u }, + { 14913111714512307107u, 2094969989053530796u }, { 8241140556867935363u, 1675975991242824637u }, + { 17660958889720079260u, 1340780792994259709u }, { 17189487779326395846u, 2145249268790815535u }, + { 13751590223461116677u, 1716199415032652428u }, { 18379969808252713988u, 1372959532026121942u }, + { 14650556434236701088u, 2196735251241795108u }, { 652398703163629901u, 1757388200993436087u }, + { 11589965406756634890u, 1405910560794748869u }, { 7475898206584884855u, 2249456897271598191u }, + { 2291369750525997561u, 1799565517817278553u }, { 9211793429904618695u, 1439652414253822842u }, + { 18428218302589300235u, 2303443862806116547u }, { 7363877012587619542u, 1842755090244893238u }, + { 13269799239553916280u, 1474204072195914590u }, { 10615839391643133024u, 1179363257756731672u }, + { 2227947767661371545u, 1886981212410770676u }, { 16539753473096738529u, 1509584969928616540u }, + { 13231802778477390823u, 1207667975942893232u }, { 6413489186596184024u, 1932268761508629172u }, + { 16198837793502678189u, 1545815009206903337u }, { 5580372605318321905u, 1236652007365522670u }, + { 8928596168509315048u, 1978643211784836272u }, { 18210923379033183008u, 1582914569427869017u }, + { 7190041073742725760u, 1266331655542295214u }, { 436019273762630246u, 2026130648867672343u }, + { 7727513048493924843u, 1620904519094137874u }, { 9871359253537050198u, 1296723615275310299u }, + { 4726128361433549347u, 2074757784440496479u }, { 7470251503888749801u, 1659806227552397183u }, + { 13354898832594820487u, 1327844982041917746u }, { 13989140502667892133u, 2124551971267068394u }, + { 14880661216876224029u, 1699641577013654715u }, { 11904528973500979224u, 1359713261610923772u }, + { 4289851098633925465u, 2175541218577478036u }, { 18189276137874781665u, 1740432974861982428u }, + { 3483374466074094362u, 1392346379889585943u }, { 1884050330976640656u, 2227754207823337509u }, + { 5196589079523222848u, 1782203366258670007u }, { 15225317707844309248u, 1425762693006936005u }, + { 5913764258841343181u, 2281220308811097609u }, { 8420360221814984868u, 1824976247048878087u }, + { 17804334621677718864u, 1459980997639102469u }, { 17932816512084085415u, 1167984798111281975u }, + { 10245762345624985047u, 1868775676978051161u }, { 4507261061758077715u, 1495020541582440929u }, + { 7295157664148372495u, 1196016433265952743u }, { 7982903447895485668u, 1913626293225524389u }, + { 10075671573058298858u, 1530901034580419511u }, { 4371188443704728763u, 1224720827664335609u }, + { 14372599139411386667u, 1959553324262936974u }, { 15187428126271019657u, 1567642659410349579u }, + { 15839291315758726049u, 1254114127528279663u }, { 3206773216762499739u, 2006582604045247462u }, + { 13633465017635730761u, 1605266083236197969u }, { 14596120828850494932u, 1284212866588958375u }, + { 4907049252451240275u, 2054740586542333401u }, { 236290587219081897u, 1643792469233866721u }, + { 14946427728742906810u, 1315033975387093376u }, { 16535586736504830250u, 2104054360619349402u }, + { 5849771759720043554u, 1683243488495479522u }, { 15747863852001765813u, 1346594790796383617u }, + { 10439186904235184007u, 2154551665274213788u }, { 15730047152871967852u, 1723641332219371030u }, + { 12584037722297574282u, 1378913065775496824u }, { 9066413911450387881u, 2206260905240794919u }, + { 10942479943902220628u, 1765008724192635935u }, { 8753983955121776503u, 1412006979354108748u }, + { 10317025513452932081u, 2259211166966573997u }, { 874922781278525018u, 1807368933573259198u }, + { 8078635854506640661u, 1445895146858607358u }, { 13841606313089133175u, 1156716117486885886u }, + { 14767872471458792434u, 1850745787979017418u }, { 746251532941302978u, 1480596630383213935u }, + { 597001226353042382u, 1184477304306571148u }, { 15712597221132509104u, 1895163686890513836u }, + { 8880728962164096960u, 1516130949512411069u }, { 10793931984473187891u, 1212904759609928855u }, + { 17270291175157100626u, 1940647615375886168u }, { 2748186495899949531u, 1552518092300708935u }, + { 2198549196719959625u, 1242014473840567148u }, { 18275073973719576693u, 1987223158144907436u }, + { 10930710364233751031u, 1589778526515925949u }, { 12433917106128911148u, 1271822821212740759u }, + { 8826220925580526867u, 2034916513940385215u }, { 7060976740464421494u, 1627933211152308172u }, + { 16716827836597268165u, 1302346568921846537u }, { 11989529279587987770u, 2083754510274954460u }, + { 9591623423670390216u, 1667003608219963568u }, { 15051996368420132820u, 1333602886575970854u }, + { 13015147745246481542u, 2133764618521553367u }, { 3033420566713364587u, 1707011694817242694u }, + { 6116085268112601993u, 1365609355853794155u }, { 9785736428980163188u, 2184974969366070648u }, + { 15207286772667951197u, 1747979975492856518u }, { 1097782973908629988u, 1398383980394285215u }, + { 1756452758253807981u, 2237414368630856344u }, { 5094511021344956708u, 1789931494904685075u }, + { 4075608817075965366u, 1431945195923748060u }, { 6520974107321544586u, 2291112313477996896u }, + { 1527430471115325346u, 1832889850782397517u }, { 12289990821117991246u, 1466311880625918013u }, + { 17210690286378213644u, 1173049504500734410u }, { 9090360384495590213u, 1876879207201175057u }, + { 18340334751822203140u, 1501503365760940045u }, { 14672267801457762512u, 1201202692608752036u }, + { 16096930852848599373u, 1921924308174003258u }, { 1809498238053148529u, 1537539446539202607u }, + { 12515645034668249793u, 1230031557231362085u }, { 1578287981759648052u, 1968050491570179337u }, + { 12330676829633449412u, 1574440393256143469u }, { 13553890278448669853u, 1259552314604914775u }, + { 3239480371808320148u, 2015283703367863641u }, { 17348979556414297411u, 1612226962694290912u }, + { 6500486015647617283u, 1289781570155432730u }, { 10400777625036187652u, 2063650512248692368u }, + { 15699319729512770768u, 1650920409798953894u }, { 16248804598352126938u, 1320736327839163115u }, + { 7551343283653851484u, 2113178124542660985u }, { 6041074626923081187u, 1690542499634128788u }, + { 12211557331022285596u, 1352433999707303030u }, { 1091747655926105338u, 2163894399531684849u }, + { 4562746939482794594u, 1731115519625347879u }, { 7339546366328145998u, 1384892415700278303u }, + { 8053925371383123274u, 2215827865120445285u }, { 6443140297106498619u, 1772662292096356228u }, + { 12533209867169019542u, 1418129833677084982u }, { 5295740528502789974u, 2269007733883335972u }, + { 15304638867027962949u, 1815206187106668777u }, { 4865013464138549713u, 1452164949685335022u }, + { 14960057215536570740u, 1161731959748268017u }, { 9178696285890871890u, 1858771135597228828u }, + { 14721654658196518159u, 1487016908477783062u }, { 4398626097073393881u, 1189613526782226450u }, + { 7037801755317430209u, 1903381642851562320u }, { 5630241404253944167u, 1522705314281249856u }, + { 814844308661245011u, 1218164251424999885u }, { 1303750893857992017u, 1949062802279999816u }, + { 15800395974054034906u, 1559250241823999852u }, { 5261619149759407279u, 1247400193459199882u }, + { 12107939454356961969u, 1995840309534719811u }, { 5997002748743659252u, 1596672247627775849u }, + { 8486951013736837725u, 1277337798102220679u }, { 2511075177753209390u, 2043740476963553087u }, + { 13076906586428298482u, 1634992381570842469u }, { 14150874083884549109u, 1307993905256673975u }, + { 4194654460505726958u, 2092790248410678361u }, { 18113118827372222859u, 1674232198728542688u }, + { 3422448617672047318u, 1339385758982834151u }, { 16543964232501006678u, 2143017214372534641u }, + { 9545822571258895019u, 1714413771498027713u }, { 15015355686490936662u, 1371531017198422170u }, + { 5577825024675947042u, 2194449627517475473u }, { 11840957649224578280u, 1755559702013980378u }, + { 16851463748863483271u, 1404447761611184302u }, { 12204946739213931940u, 2247116418577894884u }, + { 13453306206113055875u, 1797693134862315907u }, { 3383947335406624054u, 1438154507889852726u } +}; + +static const uint64_t DOUBLE_POW5_SPLIT[326][2] = { + { 0u, 1152921504606846976u }, { 0u, 1441151880758558720u }, + { 0u, 1801439850948198400u }, { 0u, 2251799813685248000u }, + { 0u, 1407374883553280000u }, { 0u, 1759218604441600000u }, + { 0u, 2199023255552000000u }, { 0u, 1374389534720000000u }, + { 0u, 1717986918400000000u }, { 0u, 2147483648000000000u }, + { 0u, 1342177280000000000u }, { 0u, 1677721600000000000u }, + { 0u, 2097152000000000000u }, { 0u, 1310720000000000000u }, + { 0u, 1638400000000000000u }, { 0u, 2048000000000000000u }, + { 0u, 1280000000000000000u }, { 0u, 1600000000000000000u }, + { 0u, 2000000000000000000u }, { 0u, 1250000000000000000u }, + { 0u, 1562500000000000000u }, { 0u, 1953125000000000000u }, + { 0u, 1220703125000000000u }, { 0u, 1525878906250000000u }, + { 0u, 1907348632812500000u }, { 0u, 1192092895507812500u }, + { 0u, 1490116119384765625u }, { 4611686018427387904u, 1862645149230957031u }, + { 9799832789158199296u, 1164153218269348144u }, { 12249790986447749120u, 1455191522836685180u }, + { 15312238733059686400u, 1818989403545856475u }, { 14528612397897220096u, 2273736754432320594u }, + { 13692068767113150464u, 1421085471520200371u }, { 12503399940464050176u, 1776356839400250464u }, + { 15629249925580062720u, 2220446049250313080u }, { 9768281203487539200u, 1387778780781445675u }, + { 7598665485932036096u, 1734723475976807094u }, { 274959820560269312u, 2168404344971008868u }, + { 9395221924704944128u, 1355252715606880542u }, { 2520655369026404352u, 1694065894508600678u }, + { 12374191248137781248u, 2117582368135750847u }, { 14651398557727195136u, 1323488980084844279u }, + { 13702562178731606016u, 1654361225106055349u }, { 3293144668132343808u, 2067951531382569187u }, + { 18199116482078572544u, 1292469707114105741u }, { 8913837547316051968u, 1615587133892632177u }, + { 15753982952572452864u, 2019483917365790221u }, { 12152082354571476992u, 1262177448353618888u }, + { 15190102943214346240u, 1577721810442023610u }, { 9764256642163156992u, 1972152263052529513u }, + { 17631875447420442880u, 1232595164407830945u }, { 8204786253993389888u, 1540743955509788682u }, + { 1032610780636961552u, 1925929944387235853u }, { 2951224747111794922u, 1203706215242022408u }, + { 3689030933889743652u, 1504632769052528010u }, { 13834660704216955373u, 1880790961315660012u }, + { 17870034976990372916u, 1175494350822287507u }, { 17725857702810578241u, 1469367938527859384u }, + { 3710578054803671186u, 1836709923159824231u }, { 26536550077201078u, 2295887403949780289u }, + { 11545800389866720434u, 1434929627468612680u }, { 14432250487333400542u, 1793662034335765850u }, + { 8816941072311974870u, 2242077542919707313u }, { 17039803216263454053u, 1401298464324817070u }, + { 12076381983474541759u, 1751623080406021338u }, { 5872105442488401391u, 2189528850507526673u }, + { 15199280947623720629u, 1368455531567204170u }, { 9775729147674874978u, 1710569414459005213u }, + { 16831347453020981627u, 2138211768073756516u }, { 1296220121283337709u, 1336382355046097823u }, + { 15455333206886335848u, 1670477943807622278u }, { 10095794471753144002u, 2088097429759527848u }, + { 6309871544845715001u, 1305060893599704905u }, { 12499025449484531656u, 1631326116999631131u }, + { 11012095793428276666u, 2039157646249538914u }, { 11494245889320060820u, 1274473528905961821u }, + { 532749306367912313u, 1593091911132452277u }, { 5277622651387278295u, 1991364888915565346u }, + { 7910200175544436838u, 1244603055572228341u }, { 14499436237857933952u, 1555753819465285426u }, + { 8900923260467641632u, 1944692274331606783u }, { 12480606065433357876u, 1215432671457254239u }, + { 10989071563364309441u, 1519290839321567799u }, { 9124653435777998898u, 1899113549151959749u }, + { 8008751406574943263u, 1186945968219974843u }, { 5399253239791291175u, 1483682460274968554u }, + { 15972438586593889776u, 1854603075343710692u }, { 759402079766405302u, 1159126922089819183u }, + { 14784310654990170340u, 1448908652612273978u }, { 9257016281882937117u, 1811135815765342473u }, + { 16182956370781059300u, 2263919769706678091u }, { 7808504722524468110u, 1414949856066673807u }, + { 5148944884728197234u, 1768687320083342259u }, { 1824495087482858639u, 2210859150104177824u }, + { 1140309429676786649u, 1381786968815111140u }, { 1425386787095983311u, 1727233711018888925u }, + { 6393419502297367043u, 2159042138773611156u }, { 13219259225790630210u, 1349401336733506972u }, + { 16524074032238287762u, 1686751670916883715u }, { 16043406521870471799u, 2108439588646104644u }, + { 803757039314269066u, 1317774742903815403u }, { 14839754354425000045u, 1647218428629769253u }, + { 4714634887749086344u, 2059023035787211567u }, { 9864175832484260821u, 1286889397367007229u }, + { 16941905809032713930u, 1608611746708759036u }, { 2730638187581340797u, 2010764683385948796u }, + { 10930020904093113806u, 1256727927116217997u }, { 18274212148543780162u, 1570909908895272496u }, + { 4396021111970173586u, 1963637386119090621u }, { 5053356204195052443u, 1227273366324431638u }, + { 15540067292098591362u, 1534091707905539547u }, { 14813398096695851299u, 1917614634881924434u }, + { 13870059828862294966u, 1198509146801202771u }, { 12725888767650480803u, 1498136433501503464u }, + { 15907360959563101004u, 1872670541876879330u }, { 14553786618154326031u, 1170419088673049581u }, + { 4357175217410743827u, 1463023860841311977u }, { 10058155040190817688u, 1828779826051639971u }, + { 7961007781811134206u, 2285974782564549964u }, { 14199001900486734687u, 1428734239102843727u }, + { 13137066357181030455u, 1785917798878554659u }, { 11809646928048900164u, 2232397248598193324u }, + { 16604401366885338411u, 1395248280373870827u }, { 16143815690179285109u, 1744060350467338534u }, + { 10956397575869330579u, 2180075438084173168u }, { 6847748484918331612u, 1362547148802608230u }, + { 17783057643002690323u, 1703183936003260287u }, { 17617136035325974999u, 2128979920004075359u }, + { 17928239049719816230u, 1330612450002547099u }, { 17798612793722382384u, 1663265562503183874u }, + { 13024893955298202172u, 2079081953128979843u }, { 5834715712847682405u, 1299426220705612402u }, + { 16516766677914378815u, 1624282775882015502u }, { 11422586310538197711u, 2030353469852519378u }, + { 11750802462513761473u, 1268970918657824611u }, { 10076817059714813937u, 1586213648322280764u }, + { 12596021324643517422u, 1982767060402850955u }, { 5566670318688504437u, 1239229412751781847u }, + { 2346651879933242642u, 1549036765939727309u }, { 7545000868343941206u, 1936295957424659136u }, + { 4715625542714963254u, 1210184973390411960u }, { 5894531928393704067u, 1512731216738014950u }, + { 16591536947346905892u, 1890914020922518687u }, { 17287239619732898039u, 1181821263076574179u }, + { 16997363506238734644u, 1477276578845717724u }, { 2799960309088866689u, 1846595723557147156u }, + { 10973347230035317489u, 1154122327223216972u }, { 13716684037544146861u, 1442652909029021215u }, + { 12534169028502795672u, 1803316136286276519u }, { 11056025267201106687u, 2254145170357845649u }, + { 18439230838069161439u, 1408840731473653530u }, { 13825666510731675991u, 1761050914342066913u }, + { 3447025083132431277u, 2201313642927583642u }, { 6766076695385157452u, 1375821026829739776u }, + { 8457595869231446815u, 1719776283537174720u }, { 10571994836539308519u, 2149720354421468400u }, + { 6607496772837067824u, 1343575221513417750u }, { 17482743002901110588u, 1679469026891772187u }, + { 17241742735199000331u, 2099336283614715234u }, { 15387775227926763111u, 1312085177259197021u }, + { 5399660979626290177u, 1640106471573996277u }, { 11361262242960250625u, 2050133089467495346u }, + { 11712474920277544544u, 1281333180917184591u }, { 10028907631919542777u, 1601666476146480739u }, + { 7924448521472040567u, 2002083095183100924u }, { 14176152362774801162u, 1251301934489438077u }, + { 3885132398186337741u, 1564127418111797597u }, { 9468101516160310080u, 1955159272639746996u }, + { 15140935484454969608u, 1221974545399841872u }, { 479425281859160394u, 1527468181749802341u }, + { 5210967620751338397u, 1909335227187252926u }, { 17091912818251750210u, 1193334516992033078u }, + { 12141518985959911954u, 1491668146240041348u }, { 15176898732449889943u, 1864585182800051685u }, + { 11791404716994875166u, 1165365739250032303u }, { 10127569877816206054u, 1456707174062540379u }, + { 8047776328842869663u, 1820883967578175474u }, { 836348374198811271u, 2276104959472719343u }, + { 7440246761515338900u, 1422565599670449589u }, { 13911994470321561530u, 1778206999588061986u }, + { 8166621051047176104u, 2222758749485077483u }, { 2798295147690791113u, 1389224218428173427u }, + { 17332926989895652603u, 1736530273035216783u }, { 17054472718942177850u, 2170662841294020979u }, + { 8353202440125167204u, 1356664275808763112u }, { 10441503050156459005u, 1695830344760953890u }, + { 3828506775840797949u, 2119787930951192363u }, { 86973725686804766u, 1324867456844495227u }, + { 13943775212390669669u, 1656084321055619033u }, { 3594660960206173375u, 2070105401319523792u }, + { 2246663100128858359u, 1293815875824702370u }, { 12031700912015848757u, 1617269844780877962u }, + { 5816254103165035138u, 2021587305976097453u }, { 5941001823691840913u, 1263492066235060908u }, + { 7426252279614801142u, 1579365082793826135u }, { 4671129331091113523u, 1974206353492282669u }, + { 5225298841145639904u, 1233878970932676668u }, { 6531623551432049880u, 1542348713665845835u }, + { 3552843420862674446u, 1927935892082307294u }, { 16055585193321335241u, 1204959932551442058u }, + { 10846109454796893243u, 1506199915689302573u }, { 18169322836923504458u, 1882749894611628216u }, + { 11355826773077190286u, 1176718684132267635u }, { 9583097447919099954u, 1470898355165334544u }, + { 11978871809898874942u, 1838622943956668180u }, { 14973589762373593678u, 2298278679945835225u }, + { 2440964573842414192u, 1436424174966147016u }, { 3051205717303017741u, 1795530218707683770u }, + { 13037379183483547984u, 2244412773384604712u }, { 8148361989677217490u, 1402757983365377945u }, + { 14797138505523909766u, 1753447479206722431u }, { 13884737113477499304u, 2191809349008403039u }, + { 15595489723564518921u, 1369880843130251899u }, { 14882676136028260747u, 1712351053912814874u }, + { 9379973133180550126u, 2140438817391018593u }, { 17391698254306313589u, 1337774260869386620u }, + { 3292878744173340370u, 1672217826086733276u }, { 4116098430216675462u, 2090272282608416595u }, + { 266718509671728212u, 1306420176630260372u }, { 333398137089660265u, 1633025220787825465u }, + { 5028433689789463235u, 2041281525984781831u }, { 10060300083759496378u, 1275800953740488644u }, + { 12575375104699370472u, 1594751192175610805u }, { 1884160825592049379u, 1993438990219513507u }, + { 17318501580490888525u, 1245899368887195941u }, { 7813068920331446945u, 1557374211108994927u }, + { 5154650131986920777u, 1946717763886243659u }, { 915813323278131534u, 1216698602428902287u }, + { 14979824709379828129u, 1520873253036127858u }, { 9501408849870009354u, 1901091566295159823u }, + { 12855909558809837702u, 1188182228934474889u }, { 2234828893230133415u, 1485227786168093612u }, + { 2793536116537666769u, 1856534732710117015u }, { 8663489100477123587u, 1160334207943823134u }, + { 1605989338741628675u, 1450417759929778918u }, { 11230858710281811652u, 1813022199912223647u }, + { 9426887369424876662u, 2266277749890279559u }, { 12809333633531629769u, 1416423593681424724u }, + { 16011667041914537212u, 1770529492101780905u }, { 6179525747111007803u, 2213161865127226132u }, + { 13085575628799155685u, 1383226165704516332u }, { 16356969535998944606u, 1729032707130645415u }, + { 15834525901571292854u, 2161290883913306769u }, { 2979049660840976177u, 1350806802445816731u }, + { 17558870131333383934u, 1688508503057270913u }, { 8113529608884566205u, 2110635628821588642u }, + { 9682642023980241782u, 1319147268013492901u }, { 16714988548402690132u, 1648934085016866126u }, + { 11670363648648586857u, 2061167606271082658u }, { 11905663298832754689u, 1288229753919426661u }, + { 1047021068258779650u, 1610287192399283327u }, { 15143834390605638274u, 2012858990499104158u }, + { 4853210475701136017u, 1258036869061940099u }, { 1454827076199032118u, 1572546086327425124u }, + { 1818533845248790147u, 1965682607909281405u }, { 3442426662494187794u, 1228551629943300878u }, + { 13526405364972510550u, 1535689537429126097u }, { 3072948650933474476u, 1919611921786407622u }, + { 15755650962115585259u, 1199757451116504763u }, { 15082877684217093670u, 1499696813895630954u }, + { 9630225068416591280u, 1874621017369538693u }, { 8324733676974063502u, 1171638135855961683u }, + { 5794231077790191473u, 1464547669819952104u }, { 7242788847237739342u, 1830684587274940130u }, + { 18276858095901949986u, 2288355734093675162u }, { 16034722328366106645u, 1430222333808546976u }, + { 1596658836748081690u, 1787777917260683721u }, { 6607509564362490017u, 2234722396575854651u }, + { 1823850468512862308u, 1396701497859909157u }, { 6891499104068465790u, 1745876872324886446u }, + { 17837745916940358045u, 2182346090406108057u }, { 4231062170446641922u, 1363966306503817536u }, + { 5288827713058302403u, 1704957883129771920u }, { 6611034641322878003u, 2131197353912214900u }, + { 13355268687681574560u, 1331998346195134312u }, { 16694085859601968200u, 1664997932743917890u }, + { 11644235287647684442u, 2081247415929897363u }, { 4971804045566108824u, 1300779634956185852u }, + { 6214755056957636030u, 1625974543695232315u }, { 3156757802769657134u, 2032468179619040394u }, + { 6584659645158423613u, 1270292612261900246u }, { 17454196593302805324u, 1587865765327375307u }, + { 17206059723201118751u, 1984832206659219134u }, { 6142101308573311315u, 1240520129162011959u }, + { 3065940617289251240u, 1550650161452514949u }, { 8444111790038951954u, 1938312701815643686u }, + { 665883850346957067u, 1211445438634777304u }, { 832354812933696334u, 1514306798293471630u }, + { 10263815553021896226u, 1892883497866839537u }, { 17944099766707154901u, 1183052186166774710u }, + { 13206752671529167818u, 1478815232708468388u }, { 16508440839411459773u, 1848519040885585485u }, + { 12623618533845856310u, 1155324400553490928u }, { 15779523167307320387u, 1444155500691863660u }, + { 1277659885424598868u, 1805194375864829576u }, { 1597074856780748586u, 2256492969831036970u }, + { 5609857803915355770u, 1410308106144398106u }, { 16235694291748970521u, 1762885132680497632u }, + { 1847873790976661535u, 2203606415850622041u }, { 12684136165428883219u, 1377254009906638775u }, + { 11243484188358716120u, 1721567512383298469u }, { 219297180166231438u, 2151959390479123087u }, + { 7054589765244976505u, 1344974619049451929u }, { 13429923224983608535u, 1681218273811814911u }, + { 12175718012802122765u, 2101522842264768639u }, { 14527352785642408584u, 1313451776415480399u }, + { 13547504963625622826u, 1641814720519350499u }, { 12322695186104640628u, 2052268400649188124u }, + { 16925056528170176201u, 1282667750405742577u }, { 7321262604930556539u, 1603334688007178222u }, + { 18374950293017971482u, 2004168360008972777u }, { 4566814905495150320u, 1252605225005607986u }, + { 14931890668723713708u, 1565756531257009982u }, { 9441491299049866327u, 1957195664071262478u }, + { 1289246043478778550u, 1223247290044539049u }, { 6223243572775861092u, 1529059112555673811u }, + { 3167368447542438461u, 1911323890694592264u }, { 1979605279714024038u, 1194577431684120165u }, + { 7086192618069917952u, 1493221789605150206u }, { 18081112809442173248u, 1866527237006437757u }, + { 13606538515115052232u, 1166579523129023598u }, { 7784801107039039482u, 1458224403911279498u }, + { 507629346944023544u, 1822780504889099373u }, { 5246222702107417334u, 2278475631111374216u }, + { 3278889188817135834u, 1424047269444608885u }, { 8710297504448807696u, 1780059086805761106u } +}; + +#endif // RYU_D2S_FULL_TABLE_H diff --git a/mgist-postgis/ryu/d2s_intrinsics.h b/mgist-postgis/ryu/d2s_intrinsics.h new file mode 100644 index 0000000..3875c6d --- /dev/null +++ b/mgist-postgis/ryu/d2s_intrinsics.h @@ -0,0 +1,222 @@ +// Copyright 2018 Ulf Adams +// +// The contents of this file may be used under the terms of the Apache License, +// Version 2.0. +// +// (See accompanying file LICENSE-Apache or copy at +// http://www.apache.org/licenses/LICENSE-2.0) +// +// Alternatively, the contents of this file may be used under the terms of +// the Boost Software License, Version 1.0. +// (See accompanying file LICENSE-Boost or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Unless required by applicable law or agreed to in writing, this software +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. +#ifndef RYU_D2S_INTRINSICS_H +#define RYU_D2S_INTRINSICS_H + +#include +#include + +// Defines RYU_32_BIT_PLATFORM if applicable. +#include "common.h" + +// ABSL avoids uint128_t on Win32 even if __SIZEOF_INT128__ is defined. +// Let's do the same for now. +#if defined(__SIZEOF_INT128__) && !defined(_MSC_VER) && !defined(RYU_ONLY_64_BIT_OPS) +#define HAS_UINT128 +#elif defined(_MSC_VER) && !defined(RYU_ONLY_64_BIT_OPS) && defined(_M_X64) +#define HAS_64_BIT_INTRINSICS +#endif + +#if defined(HAS_UINT128) +typedef __uint128_t uint128_t; +#endif + +#if defined(HAS_64_BIT_INTRINSICS) + +#include + +static inline uint64_t umul128(const uint64_t a, const uint64_t b, uint64_t* const productHi) { + return _umul128(a, b, productHi); +} + +static inline uint64_t shiftright128(const uint64_t lo, const uint64_t hi, const uint32_t dist) { + // For the __shiftright128 intrinsic, the shift value is always + // modulo 64. + // In the current implementation of the double-precision version + // of Ryu, the shift value is always < 64. (In the case + // RYU_OPTIMIZE_SIZE == 0, the shift value is in the range [49, 58]. + // Otherwise in the range [2, 59].) + // Check this here in case a future change requires larger shift + // values. In this case this function needs to be adjusted. + assert(dist < 64); + return __shiftright128(lo, hi, (unsigned char) dist); +} + +#else // defined(HAS_64_BIT_INTRINSICS) + +static inline uint64_t umul128(const uint64_t a, const uint64_t b, uint64_t* const productHi) { + // The casts here help MSVC to avoid calls to the __allmul library function. + const uint32_t aLo = (uint32_t)a; + const uint32_t aHi = (uint32_t)(a >> 32); + const uint32_t bLo = (uint32_t)b; + const uint32_t bHi = (uint32_t)(b >> 32); + + const uint64_t b00 = (uint64_t)aLo * bLo; + const uint64_t b01 = (uint64_t)aLo * bHi; + const uint64_t b10 = (uint64_t)aHi * bLo; + const uint64_t b11 = (uint64_t)aHi * bHi; + + const uint32_t b00Lo = (uint32_t)b00; + const uint32_t b00Hi = (uint32_t)(b00 >> 32); + + const uint64_t mid1 = b10 + b00Hi; + const uint32_t mid1Lo = (uint32_t)(mid1); + const uint32_t mid1Hi = (uint32_t)(mid1 >> 32); + + const uint64_t mid2 = b01 + mid1Lo; + const uint32_t mid2Lo = (uint32_t)(mid2); + const uint32_t mid2Hi = (uint32_t)(mid2 >> 32); + + const uint64_t pHi = b11 + mid1Hi + mid2Hi; + const uint64_t pLo = ((uint64_t)mid2Lo << 32) | b00Lo; + + *productHi = pHi; + return pLo; +} + +static inline uint64_t shiftright128(const uint64_t lo, const uint64_t hi, const uint32_t dist) { + // We don't need to handle the case dist >= 64 here (see above). + assert(dist < 64); +#if defined(RYU_OPTIMIZE_SIZE) || !defined(RYU_32_BIT_PLATFORM) + assert(dist > 0); + return (hi << (64 - dist)) | (lo >> dist); +#else + // Avoid a 64-bit shift by taking advantage of the range of shift values. + assert(dist >= 32); + return (hi << (64 - dist)) | ((uint32_t)(lo >> 32) >> (dist - 32)); +#endif +} + +#endif // defined(HAS_64_BIT_INTRINSICS) + +#if defined(RYU_32_BIT_PLATFORM) + +// Returns the high 64 bits of the 128-bit product of a and b. +static inline uint64_t umulh(const uint64_t a, const uint64_t b) { + // Reuse the umul128 implementation. + // Optimizers will likely eliminate the instructions used to compute the + // low part of the product. + uint64_t hi; + umul128(a, b, &hi); + return hi; +} + +// On 32-bit platforms, compilers typically generate calls to library +// functions for 64-bit divisions, even if the divisor is a constant. +// +// E.g.: +// https://bugs.llvm.org/show_bug.cgi?id=37932 +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=17958 +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=37443 +// +// The functions here perform division-by-constant using multiplications +// in the same way as 64-bit compilers would do. +// +// NB: +// The multipliers and shift values are the ones generated by clang x64 +// for expressions like x/5, x/10, etc. + +static inline uint64_t div5(const uint64_t x) { + return umulh(x, 0xCCCCCCCCCCCCCCCDu) >> 2; +} + +static inline uint64_t div10(const uint64_t x) { + return umulh(x, 0xCCCCCCCCCCCCCCCDu) >> 3; +} + +static inline uint64_t div100(const uint64_t x) { + return umulh(x >> 2, 0x28F5C28F5C28F5C3u) >> 2; +} + +static inline uint64_t div1e8(const uint64_t x) { + return umulh(x, 0xABCC77118461CEFDu) >> 26; +} + +static inline uint64_t div1e9(const uint64_t x) { + return umulh(x >> 9, 0x44B82FA09B5A53u) >> 11; +} + +static inline uint32_t mod1e9(const uint64_t x) { + // Avoid 64-bit math as much as possible. + // Returning (uint32_t) (x - 1000000000 * div1e9(x)) would + // perform 32x64-bit multiplication and 64-bit subtraction. + // x and 1000000000 * div1e9(x) are guaranteed to differ by + // less than 10^9, so their highest 32 bits must be identical, + // so we can truncate both sides to uint32_t before subtracting. + // We can also simplify (uint32_t) (1000000000 * div1e9(x)). + // We can truncate before multiplying instead of after, as multiplying + // the highest 32 bits of div1e9(x) can't affect the lowest 32 bits. + return ((uint32_t) x) - 1000000000 * ((uint32_t) div1e9(x)); +} + +#else // defined(RYU_32_BIT_PLATFORM) + +static inline uint64_t div5(const uint64_t x) { + return x / 5; +} + +static inline uint64_t div10(const uint64_t x) { + return x / 10; +} + +static inline uint64_t div100(const uint64_t x) { + return x / 100; +} + +static inline uint64_t div1e8(const uint64_t x) { + return x / 100000000; +} + +static inline uint64_t div1e9(const uint64_t x) { + return x / 1000000000; +} + +static inline uint32_t mod1e9(const uint64_t x) { + return (uint32_t) (x - 1000000000 * div1e9(x)); +} + +#endif // defined(RYU_32_BIT_PLATFORM) + +static inline uint32_t pow5Factor(uint64_t value) { + uint32_t count = 0; + for (;;) { + assert(value != 0); + const uint64_t q = div5(value); + const uint32_t r = ((uint32_t) value) - 5 * ((uint32_t) q); + if (r != 0) { + break; + } + value = q; + ++count; + } + return count; +} + +// Returns true if value is divisible by 5^p. +static inline bool multipleOfPowerOf5(const uint64_t value, const uint32_t p) { + // I tried a case distinction on p, but there was no performance difference. + return pow5Factor(value) >= p; +} + +// Returns true if value is divisible by 2^p. +static inline bool multipleOfPowerOf2(const uint64_t value, const uint32_t p) { + assert(value != 0); + // return __builtin_ctzll(value) >= p; + return (value & ((1ull << p) - 1)) == 0; +} + +#endif // RYU_D2S_INTRINSICS_H diff --git a/mgist-postgis/ryu/digit_table.h b/mgist-postgis/ryu/digit_table.h new file mode 100644 index 0000000..02219bc --- /dev/null +++ b/mgist-postgis/ryu/digit_table.h @@ -0,0 +1,35 @@ +// Copyright 2018 Ulf Adams +// +// The contents of this file may be used under the terms of the Apache License, +// Version 2.0. +// +// (See accompanying file LICENSE-Apache or copy at +// http://www.apache.org/licenses/LICENSE-2.0) +// +// Alternatively, the contents of this file may be used under the terms of +// the Boost Software License, Version 1.0. +// (See accompanying file LICENSE-Boost or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Unless required by applicable law or agreed to in writing, this software +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. +#ifndef RYU_DIGIT_TABLE_H +#define RYU_DIGIT_TABLE_H + +// A table of all two-digit numbers. This is used to speed up decimal digit +// generation by copying pairs of digits into the final output. +static const char DIGIT_TABLE[200] = { + '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9', + '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9', + '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9', + '3','0','3','1','3','2','3','3','3','4','3','5','3','6','3','7','3','8','3','9', + '4','0','4','1','4','2','4','3','4','4','4','5','4','6','4','7','4','8','4','9', + '5','0','5','1','5','2','5','3','5','4','5','5','5','6','5','7','5','8','5','9', + '6','0','6','1','6','2','6','3','6','4','6','5','6','6','6','7','6','8','6','9', + '7','0','7','1','7','2','7','3','7','4','7','5','7','6','7','7','7','8','7','9', + '8','0','8','1','8','2','8','3','8','4','8','5','8','6','8','7','8','8','8','9', + '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9' +}; + +#endif // RYU_DIGIT_TABLE_H diff --git a/mgist-postgis/ryu/ryu.h b/mgist-postgis/ryu/ryu.h new file mode 100644 index 0000000..7f767ca --- /dev/null +++ b/mgist-postgis/ryu/ryu.h @@ -0,0 +1,41 @@ +// Copyright 2018 Ulf Adams +// +// The contents of this file may be used under the terms of the Apache License, +// Version 2.0. +// +// (See accompanying file LICENSE-Apache or copy at +// http://www.apache.org/licenses/LICENSE-2.0) +// +// Alternatively, the contents of this file may be used under the terms of +// the Boost Software License, Version 1.0. +// (See accompanying file LICENSE-Boost or copy at +// https://www.boost.org/LICENSE_1_0.txt) +// +// Unless required by applicable law or agreed to in writing, this software +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. +#ifndef RYU_H +#define RYU_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Print the shortest representation of a double using fixed notation + * Only works for numbers smaller than 1e+17 (absolute value) + * Precision limits the amount of digits of the decimal part + */ +int d2sfixed_buffered_n(double f, uint32_t precision, char* result); + +/* Print the shortest representation of a double using scientific notation + * Precision limits the amount of digits of the decimal part + */ +int d2sexp_buffered_n(double f, uint32_t precision, char* result); + +#ifdef __cplusplus +} +#endif + +#endif // RYU_H