diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d7ef65a..0130419 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,90 +1,90 @@ -name: Build -'on': - - push -jobs: - ubuntu-20-04-build-gcc: - runs-on: ubuntu-20.04 - steps: - - name: Checkout repository code - uses: actions/checkout@v2 - - name: Checkout submodules - run: git submodule update --init --recursive - - name: install dependencies - run: sudo apt install -y cmake gcc g++ libpng-dev libcurl4-gnutls-dev - - name: cmake - run: mkdir build && cd build && cmake .. - - name: make - run: cd build && make - ubuntu-latest-build-gcc: - runs-on: ubuntu-latest - steps: - - name: Checkout repository code - uses: actions/checkout@v2 - - name: Checkout submodules - run: git submodule update --init --recursive - - name: install dependencies - run: sudo apt install -y cmake gcc g++ libpng-dev libcurl4-gnutls-dev - - name: cmake - run: mkdir build && cd build && cmake .. - - name: make - run: cd build && make - ubuntu-20-04-build-clang: - runs-on: ubuntu-20.04 - steps: - - name: Checkout repository code - uses: actions/checkout@v2 - - name: Checkout submodules - run: git submodule update --init --recursive - - name: install dependencies - run: sudo apt install -y cmake clang libomp-dev libpng-dev libcurl4-gnutls-dev - - name: cmake - run: mkdir build && cd build && cmake .. - shell: bash - env: - CC: clang - CXX: clang++ - - name: make - run: cd build && make - ubuntu-latest-build-clang: - runs-on: ubuntu-latest - steps: - - name: Checkout repository code - uses: actions/checkout@v2 - - name: Checkout submodules - run: git submodule update --init --recursive - - name: install dependencies - run: sudo apt install -y cmake clang libomp-dev libpng-dev libcurl4-gnutls-dev - - name: cmake - run: mkdir build && cd build && cmake .. - shell: bash - env: - CC: clang - CXX: clang++ - - name: make - run: cd build && make - macos-latest-build: - runs-on: macOS-latest - steps: - - name: Checkout repository code - uses: actions/checkout@v2 - - name: Checkout submodules - run: git submodule update --init --recursive - - name: install dependencies - run: brew install cmake libpng curl - - name: cmake - run: mkdir build && cd build && cmake .. - - name: make - run: cd build && make - macos-12-build: - runs-on: macOS-12 - steps: - - name: Checkout repository code - uses: actions/checkout@v2 - - name: Checkout submodules - run: git submodule update --init --recursive - - name: install dependencies - run: brew install cmake libpng curl - - name: cmake - run: mkdir build && cd build && cmake .. - - name: make - run: cd build && make +name: Build +'on': + - push +jobs: + ubuntu-20-04-build-gcc: + runs-on: ubuntu-20.04 + steps: + - name: Checkout repository code + uses: actions/checkout@v2 + - name: Checkout submodules + run: git submodule update --init --recursive + - name: install dependencies + run: sudo apt install -y cmake gcc g++ libpng-dev libcurl4-gnutls-dev + - name: cmake + run: mkdir build && cd build && cmake .. + - name: make + run: cd build && make + ubuntu-latest-build-gcc: + runs-on: ubuntu-latest + steps: + - name: Checkout repository code + uses: actions/checkout@v2 + - name: Checkout submodules + run: git submodule update --init --recursive + - name: install dependencies + run: sudo apt install -y cmake gcc g++ libpng-dev libcurl4-gnutls-dev + - name: cmake + run: mkdir build && cd build && cmake .. + - name: make + run: cd build && make + ubuntu-20-04-build-clang: + runs-on: ubuntu-20.04 + steps: + - name: Checkout repository code + uses: actions/checkout@v2 + - name: Checkout submodules + run: git submodule update --init --recursive + - name: install dependencies + run: sudo apt install -y cmake clang libomp-dev libpng-dev libcurl4-gnutls-dev + - name: cmake + run: mkdir build && cd build && cmake .. + shell: bash + env: + CC: clang + CXX: clang++ + - name: make + run: cd build && make + ubuntu-latest-build-clang: + runs-on: ubuntu-latest + steps: + - name: Checkout repository code + uses: actions/checkout@v2 + - name: Checkout submodules + run: git submodule update --init --recursive + - name: install dependencies + run: sudo apt install -y cmake clang libomp-dev libpng-dev libcurl4-gnutls-dev + - name: cmake + run: mkdir build && cd build && cmake .. + shell: bash + env: + CC: clang + CXX: clang++ + - name: make + run: cd build && make + macos-latest-build: + runs-on: macOS-latest + steps: + - name: Checkout repository code + uses: actions/checkout@v2 + - name: Checkout submodules + run: git submodule update --init --recursive + - name: install dependencies + run: brew install cmake libpng curl + - name: cmake + run: mkdir build && cd build && cmake .. + - name: make + run: cd build && make + macos-12-build: + runs-on: macOS-12 + steps: + - name: Checkout repository code + uses: actions/checkout@v2 + - name: Checkout submodules + run: git submodule update --init --recursive + - name: install dependencies + run: brew install cmake libpng curl + - name: cmake + run: mkdir build && cd build && cmake .. + - name: make + run: cd build && make diff --git a/.gitignore b/.gitignore index 4ac8c02..e56ee48 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -build/ -*.log -*.swp -*.swo +build/ +*.log +*.swp +*.swo diff --git a/.gitmodules b/.gitmodules index 57cef77..0ab5849 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "src/util"] - path = src/util - url = https://github.com/ad-freiburg/util +[submodule "src/util"] + path = src/util + url = https://github.com/ad-freiburg/util diff --git a/CMakeLists.txt b/CMakeLists.txt index b2c6bb5..44b349f 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,81 +1,81 @@ -cmake_minimum_required (VERSION 2.8) - -project (qlever-petrimaps) - -# only change first char -if (CMAKE_BUILD_TYPE) - string(SUBSTRING ${CMAKE_BUILD_TYPE} 0 1 FIRST_CHAR) - string(TOUPPER ${FIRST_CHAR} FIRST_CHAR) - string(REGEX REPLACE "^.(.*)" "${FIRST_CHAR}\\1" CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}") -endif() - -# old solution -#if (CMAKE_BUILD_TYPE) -# string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE) -#endif() - -enable_testing() - -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") -set(EXECUTABLE_OUTPUT_PATH "${CMAKE_SOURCE_DIR}/build") - - -find_package(OpenMP) - -# set compiler flags, see http://stackoverflow.com/questions/7724569/debug-vs-release-in-cmake -set(CMAKE_CXX_FLAGS "-march=native -Ofast -fno-signed-zeros -fno-trapping-math -Wall -Wno-format-extra-args -Wextra -Wformat-nonliteral -Wformat-security -Wformat=2 -Wextra -Wno-implicit-fallthrough -Wno-narrowing -pedantic") -set(CMAKE_C_FLAGS "-march=native -Ofast -fno-signed-zeros -fno-trapping-math -Wall -Wno-format-extra-args -Wextra -Wformat-nonliteral -Wformat-security -Wformat=2 -Wextra -Wno-implicit-fallthrough -Wno-narrowing -pedantic") - -if (OPENMP_FOUND) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") -endif() - -if (PNG_FOUND) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") -endif() - -set(CMAKE_CXX_FLAGS_DEBUG "-Og -g -DLOGLEVEL=3") -set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS} -DLOGLEVEL=2") -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -DLOGLEVEL=2") -set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS} -g -DLOGLEVEL=3") - -# export compile commands to tools like clang -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -# Compiler-specific C++11 activation. -if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") - execute_process( - COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) - if ((GCC_VERSION VERSION_GREATER 4.8 OR GCC_VERSION VERSION_EQUAL 4.8)) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - else () - message(FATAL_ERROR "${PROJECT_NAME} requires g++ 4.8 or greater!") - endif () -elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") - #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -std=c++11") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") -else () - message(FATAL_ERROR "Your C++ compiler does not support C++11.") -endif () - -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - -# http://brianmilco.blogspot.de/2012/11/cmake-automatically-use-git-tags-as.html -include(GetGitRevisionDescription) -git_get_tag(VERSION_GIT) -get_git_is_dirty(VERSION_GIT_IS_DIRTY) -if ("${VERSION_GIT_IS_DIRTY}" STREQUAL "") - set(VERSION_GIT_FULL "${VERSION_GIT}") -else() - set(VERSION_GIT_FULL "${VERSION_GIT}-${VERSION_GIT_IS_DIRTY}") -endif() - -add_subdirectory(src) - -# install target -install( - FILES build/qlever-mapui DESTINATION bin - PERMISSIONS OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE -) +cmake_minimum_required (VERSION 2.8) + +project (qlever-petrimaps) + +# only change first char +if (CMAKE_BUILD_TYPE) + string(SUBSTRING ${CMAKE_BUILD_TYPE} 0 1 FIRST_CHAR) + string(TOUPPER ${FIRST_CHAR} FIRST_CHAR) + string(REGEX REPLACE "^.(.*)" "${FIRST_CHAR}\\1" CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}") +endif() + +# old solution +#if (CMAKE_BUILD_TYPE) +# string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE) +#endif() + +enable_testing() + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") +set(EXECUTABLE_OUTPUT_PATH "${CMAKE_SOURCE_DIR}/build") + + +find_package(OpenMP) + +# set compiler flags, see http://stackoverflow.com/questions/7724569/debug-vs-release-in-cmake +set(CMAKE_CXX_FLAGS "-march=native -Ofast -fno-signed-zeros -fno-trapping-math -Wall -Wno-format-extra-args -Wextra -Wformat-nonliteral -Wformat-security -Wformat=2 -Wextra -Wno-implicit-fallthrough -Wno-narrowing -pedantic") +set(CMAKE_C_FLAGS "-march=native -Ofast -fno-signed-zeros -fno-trapping-math -Wall -Wno-format-extra-args -Wextra -Wformat-nonliteral -Wformat-security -Wformat=2 -Wextra -Wno-implicit-fallthrough -Wno-narrowing -pedantic") + +if (OPENMP_FOUND) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") +endif() + +if (PNG_FOUND) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") +endif() + +set(CMAKE_CXX_FLAGS_DEBUG "-Og -g -DLOGLEVEL=3") +set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS} -DLOGLEVEL=2") +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -DLOGLEVEL=2") +set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS} -g -DLOGLEVEL=3") + +# export compile commands to tools like clang +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Compiler-specific C++11 activation. +if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") + execute_process( + COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) + if ((GCC_VERSION VERSION_GREATER 4.8 OR GCC_VERSION VERSION_EQUAL 4.8)) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + else () + message(FATAL_ERROR "${PROJECT_NAME} requires g++ 4.8 or greater!") + endif () +elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") + #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -std=c++11") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +else () + message(FATAL_ERROR "Your C++ compiler does not support C++11.") +endif () + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + +# http://brianmilco.blogspot.de/2012/11/cmake-automatically-use-git-tags-as.html +include(GetGitRevisionDescription) +git_get_tag(VERSION_GIT) +get_git_is_dirty(VERSION_GIT_IS_DIRTY) +if ("${VERSION_GIT_IS_DIRTY}" STREQUAL "") + set(VERSION_GIT_FULL "${VERSION_GIT}") +else() + set(VERSION_GIT_FULL "${VERSION_GIT}-${VERSION_GIT_IS_DIRTY}") +endif() + +add_subdirectory(src) + +# install target +install( + FILES build/qlever-mapui DESTINATION bin + PERMISSIONS OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE +) diff --git a/Dockerfile b/Dockerfile index 3360a83..83454a9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,27 +1,27 @@ -FROM ubuntu:20.04 - -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update \ - && apt-get install --no-install-recommends -y\ - ca-certificates \ - make \ - cmake \ - xxd \ - # careful, OpenSSL is not thread safe, you MUST use GnuTLS - libcurl4-gnutls-dev \ - default-jre \ - libpng-dev \ - libomp-dev \ - g++ - -COPY CMakeLists.txt / -ADD cmake /cmake -ADD src /src -ADD web /web - -RUN mkdir build && cd build && cmake .. && make -j8 - -WORKDIR / - -ENTRYPOINT ["./build/petrimaps"] +FROM ubuntu:20.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update \ + && apt-get install --no-install-recommends -y\ + ca-certificates \ + make \ + cmake \ + xxd \ + # careful, OpenSSL is not thread safe, you MUST use GnuTLS + libcurl4-gnutls-dev \ + default-jre \ + libpng-dev \ + libomp-dev \ + g++ + +COPY CMakeLists.txt / +ADD cmake /cmake +ADD src /src +ADD web /web + +RUN mkdir build && cd build && cmake .. && make -j8 + +WORKDIR / + +ENTRYPOINT ["./build/petrimaps"] diff --git a/README.md b/README.md index 9f61ed6..2aab8d8 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,64 @@ -[![Build](https://github.com/ad-freiburg/qlever-petrimaps/actions/workflows/build.yml/badge.svg)](https://github.com/ad-freiburg/qlever-petrimaps/actions/workflows/build.yml) -# QLever petrimaps - -Visualization of geospatial SPARQL query results for QLever on a map, either as individual objeckts or as a heatmap. Can handle hundreds of millions of result rows, tested with over 200 million results (all streets in OSM). Implemented as a middle-end / middleware. -This is still very much hacked-together, use with care. - -Examples: [All railway lines in OSM](https://qlever.cs.uni-freiburg.de/mapui-petri/?query=PREFIX%20osm%3A%20%3Chttps%3A%2F%2Fwww.openstreetmap.org%2F%3E%0APREFIX%20rdf%3A%20%3Chttp%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23%3E%0APREFIX%20geo%3A%20%3Chttp%3A%2F%2Fwww.opengis.net%2Font%2Fgeosparql%23%3E%0APREFIX%20osmkey%3A%20%3Chttps%3A%2F%2Fwww.openstreetmap.org%2Fwiki%2FKey%3A%3E%0ASELECT%20%3Fosm_id%20%3Fgeometry%20WHERE%20%7B%0A%20%20%3Fosm_id%20osmkey%3Arailway%20%3Frail%20.%0A%20%20%3Fosm_id%20rdf%3Atype%20osm%3Away%20.%0A%20%20%3Fosm_id%20geo%3AhasGeometry%20%3Fgeometry%20.%0A%7D&backend=https%3A%2F%2Fqlever.cs.uni-freiburg.de%2Fapi%2Fosm-planet), [All buildings in Germany in OSM](https://qlever.cs.uni-freiburg.de/mapui-petri/?query=PREFIX%20osm2rdf%3A%20%3Chttps%3A%2F%2Fosm2rdf.cs.uni-freiburg.de%2Frdf%23%3EPREFIX%20geo%3A%20%3Chttp%3A%2F%2Fwww.opengis.net%2Font%2Fgeosparql%23%3E%20PREFIX%20osmkey%3A%20%3Chttps%3A%2F%2Fwww.openstreetmap.org%2Fwiki%2FKey%3A%3E%20PREFIX%20ogc%3A%20%3Chttp%3A%2F%2Fwww.opengis.net%2Frdf%23%3E%20PREFIX%20osmrel%3A%20%3Chttps%3A%2F%2Fwww.openstreetmap.org%2Frelation%2F%3E%20SELECT%20%3Fosm_id%20%3Fhasgeometry%20WHERE%20%7B%20%7B%20osmrel%3A51477%20osm2rdf%3Acontains_area%2B%20%3Fqlm_i%20.%20%3Fqlm_i%20osm2rdf%3Acontains_nonarea%20%3Fosm_id%20.%20%3Fosm_id%20geo%3AhasGeometry%20%3Fhasgeometry%20.%20%3Fosm_id%20osmkey%3Abuilding%20%3Fbuilding%20%7D%20UNION%20%7B%20%7B%20osmrel%3A51477%20osm2rdf%3Acontains_area%2B%20%3Fosm_id%20.%20%3Fosm_id%20geo%3AhasGeometry%20%3Fhasgeometry%20.%20%3Fosm_id%20osmkey%3Abuilding%20%3Fbuilding%20%7D%20UNION%20%7B%20osmrel%3A51477%20osm2rdf%3Acontains_nonarea%20%3Fosm_id%20.%20%3Fosm_id%20geo%3AhasGeometry%20%3Fhasgeometry%20.%20%3Fosm_id%20osmkey%3Abuilding%20%3Fbuilding%20%7D%20%7D%20%7D&backend=https%3A%2F%2Fqlever.cs.uni-freiburg.de%2Fapi%2Fosm-planet) - -## Requirements -* gcc > 5.0 || clang > 3.9 -* xxd -* libcurl -* libpng (for PNG rendering) -* Java Runtime Environment (for compiling the JS of the web frontend) - -## Optional Requirements -* zlib (for gzip compression) -* OpenMP - -## Installation - -Compile yourself: - - $ git clone https://github.com/ad-freiburg/qlever-petrimaps - $ cd qlever-petrimaps - $ mkdir -p build && cd build - $ cmake .. - $ make - -via Docker: - - $ docker build -t petrimaps . - -## Usage - -To start: - - $ petrimaps [-p ] [-m ] - -Requests can be send via the `?query` get parameter. -The QLever backend to use must be specified via the `?backend` get parameter. - -**IMPORTANT**: the tool currently expects the geometry to be the last selected column. - - PREFIX geo: - PREFIX osmkey: - SELECT ?osm_id ?geometry WHERE { - ?osm_id osmkey:building ?building . - ?osm_id geo:hasGeometry ?geometry . - } - -## Cache + Memory Management - -The tool caches query results and memory usage will thus slowly build up. There is a primitive memory limit which can be set via the `-m` parameter (in GB). By default, 90% of the available system memory are used. - -If a query runs out of memory, you can clear all existing caches by requesting - - /clearsession - -`/clearsessions` will also work. Optionally, you can specify the session id via `?id='. - -## Disk Cache - -If `-c` specifies a serialization cache directory, the complete geometries downloaded from a QLever backend will be serialized to disk and re-used on later startups. This significantly speeds up the loading times. +[![Build](https://github.com/ad-freiburg/qlever-petrimaps/actions/workflows/build.yml/badge.svg)](https://github.com/ad-freiburg/qlever-petrimaps/actions/workflows/build.yml) +# QLever petrimaps + +Visualization of geospatial SPARQL query results for QLever on a map, either as individual objeckts or as a heatmap. Can handle hundreds of millions of result rows, tested with over 200 million results (all streets in OSM). Implemented as a middle-end / middleware. +This is still very much hacked-together, use with care. + +Examples: [All railway lines in OSM](https://qlever.cs.uni-freiburg.de/mapui-petri/?query=PREFIX%20osm%3A%20%3Chttps%3A%2F%2Fwww.openstreetmap.org%2F%3E%0APREFIX%20rdf%3A%20%3Chttp%3A%2F%2Fwww.w3.org%2F1999%2F02%2F22-rdf-syntax-ns%23%3E%0APREFIX%20geo%3A%20%3Chttp%3A%2F%2Fwww.opengis.net%2Font%2Fgeosparql%23%3E%0APREFIX%20osmkey%3A%20%3Chttps%3A%2F%2Fwww.openstreetmap.org%2Fwiki%2FKey%3A%3E%0ASELECT%20%3Fosm_id%20%3Fgeometry%20WHERE%20%7B%0A%20%20%3Fosm_id%20osmkey%3Arailway%20%3Frail%20.%0A%20%20%3Fosm_id%20rdf%3Atype%20osm%3Away%20.%0A%20%20%3Fosm_id%20geo%3AhasGeometry%20%3Fgeometry%20.%0A%7D&backend=https%3A%2F%2Fqlever.cs.uni-freiburg.de%2Fapi%2Fosm-planet), [All buildings in Germany in OSM](https://qlever.cs.uni-freiburg.de/mapui-petri/?query=PREFIX%20osm2rdf%3A%20%3Chttps%3A%2F%2Fosm2rdf.cs.uni-freiburg.de%2Frdf%23%3EPREFIX%20geo%3A%20%3Chttp%3A%2F%2Fwww.opengis.net%2Font%2Fgeosparql%23%3E%20PREFIX%20osmkey%3A%20%3Chttps%3A%2F%2Fwww.openstreetmap.org%2Fwiki%2FKey%3A%3E%20PREFIX%20ogc%3A%20%3Chttp%3A%2F%2Fwww.opengis.net%2Frdf%23%3E%20PREFIX%20osmrel%3A%20%3Chttps%3A%2F%2Fwww.openstreetmap.org%2Frelation%2F%3E%20SELECT%20%3Fosm_id%20%3Fhasgeometry%20WHERE%20%7B%20%7B%20osmrel%3A51477%20osm2rdf%3Acontains_area%2B%20%3Fqlm_i%20.%20%3Fqlm_i%20osm2rdf%3Acontains_nonarea%20%3Fosm_id%20.%20%3Fosm_id%20geo%3AhasGeometry%20%3Fhasgeometry%20.%20%3Fosm_id%20osmkey%3Abuilding%20%3Fbuilding%20%7D%20UNION%20%7B%20%7B%20osmrel%3A51477%20osm2rdf%3Acontains_area%2B%20%3Fosm_id%20.%20%3Fosm_id%20geo%3AhasGeometry%20%3Fhasgeometry%20.%20%3Fosm_id%20osmkey%3Abuilding%20%3Fbuilding%20%7D%20UNION%20%7B%20osmrel%3A51477%20osm2rdf%3Acontains_nonarea%20%3Fosm_id%20.%20%3Fosm_id%20geo%3AhasGeometry%20%3Fhasgeometry%20.%20%3Fosm_id%20osmkey%3Abuilding%20%3Fbuilding%20%7D%20%7D%20%7D&backend=https%3A%2F%2Fqlever.cs.uni-freiburg.de%2Fapi%2Fosm-planet) + +## Requirements +* gcc > 5.0 || clang > 3.9 +* xxd +* libcurl +* libpng (for PNG rendering) +* Java Runtime Environment (for compiling the JS of the web frontend) + +## Optional Requirements +* zlib (for gzip compression) +* OpenMP + +## Installation + +Compile yourself: + + $ git clone https://github.com/ad-freiburg/qlever-petrimaps + $ cd qlever-petrimaps + $ mkdir -p build && cd build + $ cmake .. + $ make + +via Docker: + + $ docker build -t petrimaps . + +## Usage + +To start: + + $ petrimaps [-p ] [-m ] + +Requests can be send via the `?query` get parameter. +The QLever backend to use must be specified via the `?backend` get parameter. + +**IMPORTANT**: the tool currently expects the geometry to be the last selected column. + + PREFIX geo: + PREFIX osmkey: + SELECT ?osm_id ?geometry WHERE { + ?osm_id osmkey:building ?building . + ?osm_id geo:hasGeometry ?geometry . + } + +## Cache + Memory Management + +The tool caches query results and memory usage will thus slowly build up. There is a primitive memory limit which can be set via the `-m` parameter (in GB). By default, 90% of the available system memory are used. + +If a query runs out of memory, you can clear all existing caches by requesting + + /clearsession + +`/clearsessions` will also work. Optionally, you can specify the session id via `?id='. + +## Disk Cache + +If `-c` specifies a serialization cache directory, the complete geometries downloaded from a QLever backend will be serialized to disk and re-used on later startups. This significantly speeds up the loading times. diff --git a/cmake/GetGitRevisionDescription.cmake b/cmake/GetGitRevisionDescription.cmake index db1397f..15d6e90 100755 --- a/cmake/GetGitRevisionDescription.cmake +++ b/cmake/GetGitRevisionDescription.cmake @@ -1,151 +1,151 @@ -# - Returns a version string from Git -# -# These functions force a re-configure on each git commit so that you can -# trust the values of the variables in your build system. -# -# get_git_head_revision( [ ...]) -# -# Returns the refspec and sha hash of the current head revision -# -# git_describe( [ ...]) -# -# Returns the results of git describe on the source tree, and adjusting -# the output so that it tests false if an error occurs. -# -# git_get_exact_tag( [ ...]) -# -# Returns the results of git describe --exact-match on the source tree, -# and adjusting the output so that it tests false if there was no exact -# matching tag. -# -# Requires CMake 2.6 or newer (uses the 'function' command) -# -# Original Author: -# 2009-2010 Ryan Pavlik -# http://academic.cleardefinition.com -# Iowa State University HCI Graduate Program/VRAC -# -# Copyright Iowa State University 2009-2010. -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE_1_0.txt or copy at -# http://www.boost.org/LICENSE_1_0.txt) - -if(__get_git_revision_description) - return() -endif() -set(__get_git_revision_description YES) - -# We must run the following at "include" time, not at function call time, -# to find the path to this module rather than the path to a calling list file -get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) - -function(get_git_head_revision _refspecvar _hashvar) - set(GIT_PARENT_DIR "${CMAKE_SOURCE_DIR}") - set(GIT_DIR "${GIT_PARENT_DIR}/.git") - while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories - set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") - get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) - if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) - # We have reached the root directory, we are not in git - set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) - set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) - return() - endif() - set(GIT_DIR "${GIT_PARENT_DIR}/.git") - endwhile() - set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") - if(NOT EXISTS "${GIT_DATA}") - file(MAKE_DIRECTORY "${GIT_DATA}") - endif() - - if(NOT EXISTS "${GIT_DIR}/HEAD") - return() - endif() - set(HEAD_FILE "${GIT_DATA}/HEAD") - configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) - - configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" - "${GIT_DATA}/grabRef.cmake" - @ONLY) - include("${GIT_DATA}/grabRef.cmake") - - set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) - set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) -endfunction() - -function(get_git_is_dirty _var) - if(NOT GIT_FOUND) - find_package(Git QUIET) - endif() - - execute_process(COMMAND - "${GIT_EXECUTABLE}" - diff-index --name-only HEAD -- - WORKING_DIRECTORY - "${CMAKE_SOURCE_DIR}" - RESULT_VARIABLE - res - OUTPUT_VARIABLE - out - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if(NOT res EQUAL 0) - set(out "${out}-${res}-NOTFOUND") - endif() - - if (NOT "${out}" STREQUAL "") - set(IS_DIRTY "dirty") - else() - set(IS_DIRTY "") - endif() - set(${_var} "${IS_DIRTY}" PARENT_SCOPE) -endfunction() - -function(git_describe _var) - if(NOT GIT_FOUND) - find_package(Git QUIET) - endif() - get_git_head_revision(refspec hash) - if(NOT GIT_FOUND) - set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) - return() - endif() - if(NOT hash) - set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) - return() - endif() - - # TODO sanitize - #if((${ARGN}" MATCHES "&&") OR - # (ARGN MATCHES "||") OR - # (ARGN MATCHES "\\;")) - # message("Please report the following error to the project!") - # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") - #endif() - - #message(STATUS "Arguments to execute_process: ${ARGN}") - - execute_process(COMMAND - "${GIT_EXECUTABLE}" - describe - ${hash} - ${ARGN} - WORKING_DIRECTORY - "${CMAKE_SOURCE_DIR}" - RESULT_VARIABLE - res - OUTPUT_VARIABLE - out - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if(NOT res EQUAL 0) - set(out "${out}-${res}-NOTFOUND") - endif() - - set(${_var} "${out}" PARENT_SCOPE) -endfunction() - -function(git_get_tag _var) - git_describe(out --tags ${ARGN}) - set(${_var} "${out}" PARENT_SCOPE) -endfunction() +# - Returns a version string from Git +# +# These functions force a re-configure on each git commit so that you can +# trust the values of the variables in your build system. +# +# get_git_head_revision( [ ...]) +# +# Returns the refspec and sha hash of the current head revision +# +# git_describe( [ ...]) +# +# Returns the results of git describe on the source tree, and adjusting +# the output so that it tests false if an error occurs. +# +# git_get_exact_tag( [ ...]) +# +# Returns the results of git describe --exact-match on the source tree, +# and adjusting the output so that it tests false if there was no exact +# matching tag. +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright Iowa State University 2009-2010. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +if(__get_git_revision_description) + return() +endif() +set(__get_git_revision_description YES) + +# We must run the following at "include" time, not at function call time, +# to find the path to this module rather than the path to a calling list file +get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) + +function(get_git_head_revision _refspecvar _hashvar) + set(GIT_PARENT_DIR "${CMAKE_SOURCE_DIR}") + set(GIT_DIR "${GIT_PARENT_DIR}/.git") + while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories + set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") + get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) + if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) + # We have reached the root directory, we are not in git + set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) + set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) + return() + endif() + set(GIT_DIR "${GIT_PARENT_DIR}/.git") + endwhile() + set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") + if(NOT EXISTS "${GIT_DATA}") + file(MAKE_DIRECTORY "${GIT_DATA}") + endif() + + if(NOT EXISTS "${GIT_DIR}/HEAD") + return() + endif() + set(HEAD_FILE "${GIT_DATA}/HEAD") + configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) + + configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" + "${GIT_DATA}/grabRef.cmake" + @ONLY) + include("${GIT_DATA}/grabRef.cmake") + + set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) + set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) +endfunction() + +function(get_git_is_dirty _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + + execute_process(COMMAND + "${GIT_EXECUTABLE}" + diff-index --name-only HEAD -- + WORKING_DIRECTORY + "${CMAKE_SOURCE_DIR}" + RESULT_VARIABLE + res + OUTPUT_VARIABLE + out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + if (NOT "${out}" STREQUAL "") + set(IS_DIRTY "dirty") + else() + set(IS_DIRTY "") + endif() + set(${_var} "${IS_DIRTY}" PARENT_SCOPE) +endfunction() + +function(git_describe _var) + if(NOT GIT_FOUND) + find_package(Git QUIET) + endif() + get_git_head_revision(refspec hash) + if(NOT GIT_FOUND) + set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) + return() + endif() + if(NOT hash) + set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) + return() + endif() + + # TODO sanitize + #if((${ARGN}" MATCHES "&&") OR + # (ARGN MATCHES "||") OR + # (ARGN MATCHES "\\;")) + # message("Please report the following error to the project!") + # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") + #endif() + + #message(STATUS "Arguments to execute_process: ${ARGN}") + + execute_process(COMMAND + "${GIT_EXECUTABLE}" + describe + ${hash} + ${ARGN} + WORKING_DIRECTORY + "${CMAKE_SOURCE_DIR}" + RESULT_VARIABLE + res + OUTPUT_VARIABLE + out + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT res EQUAL 0) + set(out "${out}-${res}-NOTFOUND") + endif() + + set(${_var} "${out}" PARENT_SCOPE) +endfunction() + +function(git_get_tag _var) + git_describe(out --tags ${ARGN}) + set(${_var} "${out}" PARENT_SCOPE) +endfunction() diff --git a/cmake/GetGitRevisionDescription.cmake.in b/cmake/GetGitRevisionDescription.cmake.in index 888ce13..c5fefa3 100755 --- a/cmake/GetGitRevisionDescription.cmake.in +++ b/cmake/GetGitRevisionDescription.cmake.in @@ -1,38 +1,38 @@ -# -# Internal file for GetGitRevisionDescription.cmake -# -# Requires CMake 2.6 or newer (uses the 'function' command) -# -# Original Author: -# 2009-2010 Ryan Pavlik -# http://academic.cleardefinition.com -# Iowa State University HCI Graduate Program/VRAC -# -# Copyright Iowa State University 2009-2010. -# Distributed under the Boost Software License, Version 1.0. -# (See accompanying file LICENSE_1_0.txt or copy at -# http://www.boost.org/LICENSE_1_0.txt) - -set(HEAD_HASH) - -file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) - -string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) -if(HEAD_CONTENTS MATCHES "ref") - # named branch - string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") - if(EXISTS "@GIT_DIR@/${HEAD_REF}") - configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) - elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}") - configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) - set(HEAD_HASH "${HEAD_REF}") - endif() -else() - # detached HEAD - configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) -endif() - -if(NOT HEAD_HASH) - file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) - string(STRIP "${HEAD_HASH}" HEAD_HASH) -endif() +# +# Internal file for GetGitRevisionDescription.cmake +# +# Requires CMake 2.6 or newer (uses the 'function' command) +# +# Original Author: +# 2009-2010 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright Iowa State University 2009-2010. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +set(HEAD_HASH) + +file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) + +string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) +if(HEAD_CONTENTS MATCHES "ref") + # named branch + string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") + if(EXISTS "@GIT_DIR@/${HEAD_REF}") + configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + elseif(EXISTS "@GIT_DIR@/logs/${HEAD_REF}") + configure_file("@GIT_DIR@/logs/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) + set(HEAD_HASH "${HEAD_REF}") + endif() +else() + # detached HEAD + configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) +endif() + +if(NOT HEAD_HASH) + file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) + string(STRIP "${HEAD_HASH}" HEAD_HASH) +endif() diff --git a/cmake/MacPlistMacros.cmake b/cmake/MacPlistMacros.cmake index b123872..3bd7273 100755 --- a/cmake/MacPlistMacros.cmake +++ b/cmake/MacPlistMacros.cmake @@ -1,12 +1,12 @@ -# Mac Plist Macros - -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) +# Mac Plist Macros + +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) diff --git a/src/3rdparty/CMakeLists.txt b/src/3rdparty/CMakeLists.txt index 196e531..f6f90d1 100755 --- a/src/3rdparty/CMakeLists.txt +++ b/src/3rdparty/CMakeLists.txt @@ -1,3 +1,3 @@ -file(GLOB_RECURSE 3RDPARTY_SRC *.c) - -add_library(3rdparty_dep ${3RDPARTY_SRC}) +file(GLOB_RECURSE 3RDPARTY_SRC *.c) + +add_library(3rdparty_dep ${3RDPARTY_SRC}) diff --git a/src/3rdparty/colorschemes/Spectral.c b/src/3rdparty/colorschemes/Spectral.c index 58966cc..b1d39a4 100644 --- a/src/3rdparty/colorschemes/Spectral.c +++ b/src/3rdparty/colorschemes/Spectral.c @@ -1,58 +1,58 @@ -/* heatmap - High performance heatmap creation in C. - * - * The MIT License (MIT) - * - * Copyright (c) 2013 Lucas Beyer - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "3rdparty/heatmap.h" -#include "3rdparty/colorschemes/Spectral.h" - -static const unsigned char discrete_data[] = { - 0, 0, 0, 0, 94, 79, 162, 255, 50, 136, 189, 255, 102, 194, 165, 255, 171, 221, 164, 255, 230, 245, 152, 255, 241, 243, 167, 255, 254, 224, 144, 255, 253, 174, 97, 255, 244, 109, 67, 255, 213, 62, 79, 255, 158, 1, 66, 255 -}; -static const heatmap_colorscheme_t discrete = { discrete_data, sizeof(discrete_data)/sizeof(discrete_data[0]/4) }; -const heatmap_colorscheme_t* heatmap_cs_Spectral_discrete = &discrete; - -static const unsigned char soft_data[] = { - 0, 0, 0, 0, 94, 79, 162, 0, 93, 79, 162, 7, 92, 80, 163, 14, 92, 81, 163, 22, 91, 81, 164, 29, 91, 82, 164, 37, 90, 82, 165, 44, 89, 83, 165, 52, 89, 84, 166, 59, 88, 84, 166, 67, 88, 85, 167, 74, 87, 86, 167, 82, 86, 86, 167, 89, 86, 87, 168, 97, 85, 88, 168, 104, 85, 88, 169, 112, 84, 89, 169, 119, 83, 89, 170, 127, 83, 90, 170, 134, 82, 91, 171, 141, 82, 91, 171, 149, 81, 92, 171, 156, 80, 93, 172, 164, 80, 93, 172, 171, 79, 94, 173, 179, 79, 94, 173, 186, 78, 95, 173, 194, 77, 96, 174, 201, 77, 96, 174, 209, 76, 97, 174, 216, 76, 97, 175, 224, 75, 98, 175, 231, 74, 99, 176, 239, 74, 99, 176, 246, 73, 100, 176, 254, 72, 100, 177, 255, 72, 101, 177, 255, 71, 101, 177, 255, 71, 102, 178, 255, 70, 103, 178, 255, 70, 103, 178, 255, 69, 104, 178, 255, 68, 104, 179, 255, 68, 105, 179, 255, 67, 105, 179, 255, 67, 106, 180, 255, 66, 107, 180, 255, 65, 107, 180, 255, 65, 108, 180, 255, 64, 108, 181, 255, 64, 109, 181, 255, 63, 109, 181, 255, 63, 110, 181, 255, 62, 110, 182, 255, 62, 111, 182, 255, 61, 112, 182, 255, 61, 112, 182, 255, 60, 113, 183, 255, 60, 113, 183, 255, 59, 114, 183, 255, 59, 114, 183, 255, 58, 115, 183, 255, 58, 115, 184, 255, 57, 116, 184, 255, 57, 116, 184, 255, 56, 117, 184, 255, 56, 117, 184, 255, 55, 118, 185, 255, 55, 118, 185, 255, 55, 119, 185, 255, 54, 119, 185, 255, 54, 120, 185, 255, 53, 121, 185, 255, 53, 121, 186, 255, 53, 122, 186, 255, 52, 122, 186, 255, 52, 123, 186, 255, 52, 123, 186, 255, 51, 124, 186, 255, 51, 124, 186, 255, 51, 125, 187, 255, 51, 125, 187, 255, 50, 126, 187, 255, 50, 126, 187, 255, 50, 127, 187, 255, 50, 127, 187, 255, 50, 128, 187, 255, 50, 128, 187, 255, 49, 129, 187, 255, 49, 129, 188, 255, 49, 130, 188, 255, 49, 130, 188, 255, 49, 131, 188, 255, 49, 131, 188, 255, 49, 131, 188, 255, 49, 132, 188, 255, 49, 132, 188, 255, 49, 133, 188, 255, 49, 133, 188, 255, 49, 134, 188, 255, 49, 134, 188, 255, 49, 135, 188, 255, 49, 135, 188, 255, 49, 136, 189, 255, 47, 137, 189, 255, 45, 137, 189, 255, 44, 138, 189, 255, 42, 139, 190, 255, 40, 139, 190, 255, 38, 140, 190, 255, 37, 141, 190, 255, 35, 141, 190, 255, 33, 142, 190, 255, 31, 143, 191, 255, 29, 143, 191, 255, 27, 144, 191, 255, 25, 145, 191, 255, 23, 145, 191, 255, 20, 146, 191, 255, 18, 147, 191, 255, 15, 147, 191, 255, 13, 148, 191, 255, 9, 149, 191, 255, 6, 149, 191, 255, 3, 150, 191, 255, 0, 151, 191, 255, 0, 151, 191, 255, 0, 152, 191, 255, 0, 153, 191, 255, 0, 153, 191, 255, 0, 154, 191, 255, 0, 155, 191, 255, 0, 155, 191, 255, 0, 156, 191, 255, 0, 156, 191, 255, 0, 157, 190, 255, 0, 158, 190, 255, 0, 158, 190, 255, 0, 159, 190, 255, 0, 159, 190, 255, 0, 160, 190, 255, 0, 161, 189, 255, 0, 161, 189, 255, 0, 162, 189, 255, 0, 162, 189, 255, 0, 163, 189, 255, 0, 164, 188, 255, 0, 164, 188, 255, 0, 165, 188, 255, 0, 165, 188, 255, 0, 166, 187, 255, 0, 167, 187, 255, 0, 167, 187, 255, 0, 168, 186, 255, 0, 168, 186, 255, 3, 169, 186, 255, 6, 169, 185, 255, 9, 170, 185, 255, 13, 170, 185, 255, 16, 171, 184, 255, 18, 172, 184, 255, 21, 172, 184, 255, 23, 173, 183, 255, 26, 173, 183, 255, 28, 174, 183, 255, 30, 174, 182, 255, 33, 175, 182, 255, 35, 175, 181, 255, 37, 176, 181, 255, 39, 176, 181, 255, 41, 177, 180, 255, 43, 177, 180, 255, 45, 178, 179, 255, 47, 179, 179, 255, 48, 179, 179, 255, 50, 180, 178, 255, 52, 180, 178, 255, 54, 181, 177, 255, 56, 181, 177, 255, 58, 182, 176, 255, 59, 182, 176, 255, 61, 183, 176, 255, 63, 183, 175, 255, 65, 184, 175, 255, 67, 184, 174, 255, 68, 185, 174, 255, 70, 185, 173, 255, 72, 185, 173, 255, 73, 186, 172, 255, 75, 186, 172, 255, 77, 187, 171, 255, 79, 187, 171, 255, 80, 188, 170, 255, 82, 188, 170, 255, 84, 189, 170, 255, 85, 189, 169, 255, 87, 190, 169, 255, 89, 190, 168, 255, 90, 191, 168, 255, 92, 191, 167, 255, 94, 191, 167, 255, 95, 192, 166, 255, 97, 192, 166, 255, 99, 193, 165, 255, 100, 193, 165, 255, 102, 194, 164, 255, 102, 194, 164, 255, 103, 194, 164, 255, 104, 194, 164, 255, 104, 195, 164, 255, 105, 195, 164, 255, 106, 195, 164, 255, 106, 196, 164, 255, 107, 196, 164, 255, 108, 196, 164, 255, 108, 196, 164, 255, 109, 197, 164, 255, 110, 197, 164, 255, 110, 197, 164, 255, 111, 197, 164, 255, 112, 198, 164, 255, 112, 198, 164, 255, 113, 198, 164, 255, 114, 199, 164, 255, 114, 199, 164, 255, 115, 199, 164, 255, 116, 199, 164, 255, 116, 200, 164, 255, 117, 200, 164, 255, 118, 200, 164, 255, 118, 200, 164, 255, 119, 201, 164, 255, 120, 201, 164, 255, 120, 201, 164, 255, 121, 202, 164, 255, 122, 202, 164, 255, 123, 202, 164, 255, 123, 202, 164, 255, 124, 203, 164, 255, 125, 203, 164, 255, 125, 203, 163, 255, 126, 203, 163, 255, 127, 204, 163, 255, 127, 204, 163, 255, 128, 204, 163, 255, 129, 205, 163, 255, 129, 205, 163, 255, 130, 205, 163, 255, 131, 205, 163, 255, 131, 206, 163, 255, 132, 206, 163, 255, 133, 206, 163, 255, 133, 206, 163, 255, 134, 207, 163, 255, 135, 207, 163, 255, 135, 207, 163, 255, 136, 207, 163, 255, 137, 208, 163, 255, 137, 208, 163, 255, 138, 208, 163, 255, 139, 209, 163, 255, 139, 209, 163, 255, 140, 209, 163, 255, 141, 209, 163, 255, 141, 210, 163, 255, 142, 210, 163, 255, 143, 210, 163, 255, 143, 210, 163, 255, 144, 211, 163, 255, 145, 211, 163, 255, 145, 211, 163, 255, 146, 211, 163, 255, 147, 212, 163, 255, 147, 212, 163, 255, 148, 212, 163, 255, 149, 212, 163, 255, 149, 213, 163, 255, 150, 213, 163, 255, 151, 213, 163, 255, 151, 213, 163, 255, 152, 214, 163, 255, 153, 214, 163, 255, 153, 214, 163, 255, 154, 214, 163, 255, 155, 215, 163, 255, 155, 215, 163, 255, 156, 215, 163, 255, 157, 215, 163, 255, 157, 216, 163, 255, 158, 216, 163, 255, 159, 216, 163, 255, 160, 216, 163, 255, 160, 217, 163, 255, 161, 217, 163, 255, 162, 217, 163, 255, 162, 217, 163, 255, 163, 218, 163, 255, 164, 218, 163, 255, 164, 218, 163, 255, 165, 218, 163, 255, 166, 219, 163, 255, 166, 219, 163, 255, 167, 219, 163, 255, 168, 219, 163, 255, 168, 220, 163, 255, 169, 220, 163, 255, 170, 220, 163, 255, 170, 220, 163, 255, 171, 221, 163, 255, 171, 221, 163, 255, 172, 221, 163, 255, 172, 222, 163, 255, 173, 222, 163, 255, 173, 222, 163, 255, 174, 222, 163, 255, 174, 223, 163, 255, 175, 223, 163, 255, 175, 223, 162, 255, 176, 223, 162, 255, 176, 224, 162, 255, 177, 224, 162, 255, 177, 224, 162, 255, 178, 224, 162, 255, 178, 225, 162, 255, 179, 225, 162, 255, 179, 225, 162, 255, 180, 225, 161, 255, 180, 226, 161, 255, 181, 226, 161, 255, 181, 226, 161, 255, 182, 226, 161, 255, 182, 227, 161, 255, 183, 227, 161, 255, 183, 227, 161, 255, 184, 227, 161, 255, 184, 228, 160, 255, 185, 228, 160, 255, 185, 228, 160, 255, 186, 228, 160, 255, 186, 229, 160, 255, 187, 229, 160, 255, 188, 229, 160, 255, 188, 229, 160, 255, 189, 230, 159, 255, 189, 230, 159, 255, 190, 230, 159, 255, 190, 230, 159, 255, 191, 231, 159, 255, 191, 231, 159, 255, 192, 231, 159, 255, 193, 231, 159, 255, 193, 232, 158, 255, 194, 232, 158, 255, 194, 232, 158, 255, 195, 232, 158, 255, 195, 233, 158, 255, 196, 233, 158, 255, 197, 233, 158, 255, 197, 233, 158, 255, 198, 233, 158, 255, 198, 234, 157, 255, 199, 234, 157, 255, 199, 234, 157, 255, 200, 234, 157, 255, 201, 235, 157, 255, 201, 235, 157, 255, 202, 235, 157, 255, 202, 235, 157, 255, 203, 236, 156, 255, 204, 236, 156, 255, 204, 236, 156, 255, 205, 236, 156, 255, 205, 236, 156, 255, 206, 237, 156, 255, 207, 237, 156, 255, 207, 237, 156, 255, 208, 237, 155, 255, 208, 238, 155, 255, 209, 238, 155, 255, 210, 238, 155, 255, 210, 238, 155, 255, 211, 238, 155, 255, 212, 239, 155, 255, 212, 239, 155, 255, 213, 239, 154, 255, 213, 239, 154, 255, 214, 240, 154, 255, 215, 240, 154, 255, 215, 240, 154, 255, 216, 240, 154, 255, 217, 240, 154, 255, 217, 241, 154, 255, 218, 241, 153, 255, 219, 241, 153, 255, 219, 241, 153, 255, 220, 242, 153, 255, 220, 242, 153, 255, 221, 242, 153, 255, 222, 242, 153, 255, 222, 242, 153, 255, 223, 243, 153, 255, 224, 243, 152, 255, 224, 243, 152, 255, 225, 243, 152, 255, 226, 243, 152, 255, 226, 244, 152, 255, 227, 244, 152, 255, 228, 244, 152, 255, 228, 244, 152, 255, 229, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 154, 255, 231, 244, 154, 255, 232, 244, 154, 255, 232, 244, 154, 255, 232, 244, 154, 255, 232, 244, 154, 255, 232, 244, 154, 255, 232, 244, 155, 255, 232, 244, 155, 255, 232, 244, 155, 255, 232, 244, 155, 255, 233, 244, 155, 255, 233, 244, 155, 255, 233, 244, 155, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 234, 244, 156, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 158, 255, 235, 244, 158, 255, 235, 244, 158, 255, 235, 244, 158, 255, 235, 244, 158, 255, 235, 243, 158, 255, 235, 243, 158, 255, 235, 243, 159, 255, 235, 243, 159, 255, 235, 243, 159, 255, 236, 243, 159, 255, 236, 243, 159, 255, 236, 243, 159, 255, 236, 243, 159, 255, 236, 243, 160, 255, 236, 243, 160, 255, 236, 243, 160, 255, 236, 243, 160, 255, 236, 243, 160, 255, 237, 243, 160, 255, 237, 243, 160, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 162, 255, 237, 243, 162, 255, 238, 243, 162, 255, 238, 243, 162, 255, 238, 243, 162, 255, 238, 243, 162, 255, 238, 243, 162, 255, 238, 243, 163, 255, 238, 243, 163, 255, 238, 243, 163, 255, 238, 243, 163, 255, 238, 243, 163, 255, 239, 243, 163, 255, 239, 243, 163, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 165, 255, 239, 243, 165, 255, 239, 243, 165, 255, 240, 243, 165, 255, 240, 243, 165, 255, 240, 243, 165, 255, 240, 243, 165, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 241, 242, 166, 255, 241, 242, 166, 255, 241, 242, 166, 255, 241, 242, 165, 255, 241, 242, 165, 255, 241, 241, 165, 255, 241, 241, 165, 255, 241, 241, 164, 255, 242, 241, 164, 255, 242, 241, 164, 255, 242, 241, 164, 255, 242, 240, 163, 255, 242, 240, 163, 255, 242, 240, 163, 255, 242, 240, 163, 255, 242, 240, 162, 255, 243, 239, 162, 255, 243, 239, 162, 255, 243, 239, 162, 255, 243, 239, 161, 255, 243, 239, 161, 255, 243, 239, 161, 255, 243, 238, 161, 255, 243, 238, 160, 255, 244, 238, 160, 255, 244, 238, 160, 255, 244, 238, 160, 255, 244, 238, 159, 255, 244, 237, 159, 255, 244, 237, 159, 255, 244, 237, 159, 255, 244, 237, 159, 255, 245, 237, 158, 255, 245, 236, 158, 255, 245, 236, 158, 255, 245, 236, 158, 255, 245, 236, 157, 255, 245, 236, 157, 255, 245, 236, 157, 255, 245, 235, 157, 255, 246, 235, 156, 255, 246, 235, 156, 255, 246, 235, 156, 255, 246, 235, 156, 255, 246, 234, 155, 255, 246, 234, 155, 255, 246, 234, 155, 255, 246, 234, 155, 255, 247, 234, 155, 255, 247, 233, 154, 255, 247, 233, 154, 255, 247, 233, 154, 255, 247, 233, 154, 255, 247, 233, 153, 255, 247, 233, 153, 255, 247, 232, 153, 255, 248, 232, 153, 255, 248, 232, 153, 255, 248, 232, 152, 255, 248, 232, 152, 255, 248, 231, 152, 255, 248, 231, 152, 255, 248, 231, 151, 255, 248, 231, 151, 255, 249, 231, 151, 255, 249, 231, 151, 255, 249, 230, 151, 255, 249, 230, 150, 255, 249, 230, 150, 255, 249, 230, 150, 255, 249, 230, 150, 255, 250, 229, 150, 255, 250, 229, 149, 255, 250, 229, 149, 255, 250, 229, 149, 255, 250, 229, 149, 255, 250, 228, 148, 255, 250, 228, 148, 255, 250, 228, 148, 255, 251, 228, 148, 255, 251, 228, 148, 255, 251, 227, 147, 255, 251, 227, 147, 255, 251, 227, 147, 255, 251, 227, 147, 255, 251, 227, 147, 255, 251, 226, 146, 255, 252, 226, 146, 255, 252, 226, 146, 255, 252, 226, 146, 255, 252, 226, 146, 255, 252, 226, 145, 255, 252, 225, 145, 255, 252, 225, 145, 255, 253, 225, 145, 255, 253, 225, 145, 255, 253, 225, 145, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 223, 143, 255, 253, 223, 143, 255, 253, 222, 142, 255, 253, 222, 142, 255, 253, 221, 141, 255, 253, 221, 140, 255, 253, 220, 140, 255, 253, 220, 139, 255, 253, 220, 139, 255, 253, 219, 138, 255, 253, 219, 138, 255, 253, 218, 137, 255, 253, 218, 137, 255, 253, 217, 136, 255, 253, 217, 136, 255, 253, 216, 135, 255, 253, 216, 134, 255, 253, 215, 134, 255, 253, 215, 133, 255, 253, 214, 133, 255, 253, 214, 132, 255, 253, 213, 132, 255, 253, 213, 131, 255, 253, 212, 131, 255, 253, 212, 130, 255, 253, 212, 130, 255, 253, 211, 129, 255, 253, 211, 129, 255, 253, 210, 128, 255, 253, 210, 128, 255, 253, 209, 127, 255, 253, 209, 127, 255, 253, 208, 126, 255, 253, 208, 126, 255, 253, 207, 125, 255, 253, 207, 125, 255, 253, 206, 124, 255, 253, 206, 124, 255, 253, 205, 123, 255, 253, 205, 123, 255, 253, 204, 122, 255, 253, 204, 122, 255, 253, 203, 121, 255, 253, 203, 121, 255, 253, 202, 120, 255, 253, 202, 120, 255, 253, 201, 119, 255, 253, 201, 119, 255, 253, 200, 118, 255, 253, 200, 118, 255, 253, 200, 118, 255, 253, 199, 117, 255, 253, 199, 117, 255, 253, 198, 116, 255, 253, 198, 116, 255, 253, 197, 115, 255, 253, 197, 115, 255, 253, 196, 114, 255, 253, 196, 114, 255, 253, 195, 113, 255, 253, 195, 113, 255, 253, 194, 113, 255, 253, 194, 112, 255, 253, 193, 112, 255, 253, 193, 111, 255, 253, 192, 111, 255, 253, 192, 110, 255, 253, 191, 110, 255, 253, 191, 110, 255, 253, 190, 109, 255, 253, 190, 109, 255, 253, 189, 108, 255, 253, 189, 108, 255, 253, 188, 107, 255, 253, 188, 107, 255, 253, 187, 107, 255, 253, 187, 106, 255, 253, 186, 106, 255, 253, 186, 105, 255, 253, 185, 105, 255, 253, 185, 105, 255, 253, 184, 104, 255, 253, 184, 104, 255, 253, 183, 103, 255, 253, 183, 103, 255, 253, 182, 103, 255, 253, 182, 102, 255, 253, 181, 102, 255, 253, 181, 101, 255, 253, 180, 101, 255, 253, 180, 101, 255, 253, 179, 100, 255, 253, 179, 100, 255, 253, 178, 100, 255, 253, 178, 99, 255, 253, 177, 99, 255, 253, 176, 99, 255, 253, 176, 98, 255, 253, 175, 98, 255, 253, 175, 97, 255, 253, 174, 97, 255, 253, 174, 97, 255, 252, 173, 96, 255, 252, 173, 96, 255, 252, 172, 96, 255, 252, 172, 95, 255, 252, 171, 95, 255, 252, 170, 94, 255, 252, 170, 94, 255, 252, 169, 93, 255, 252, 169, 93, 255, 252, 168, 93, 255, 252, 167, 92, 255, 252, 167, 92, 255, 252, 166, 91, 255, 252, 166, 91, 255, 251, 165, 90, 255, 251, 165, 90, 255, 251, 164, 90, 255, 251, 163, 89, 255, 251, 163, 89, 255, 251, 162, 89, 255, 251, 162, 88, 255, 251, 161, 88, 255, 251, 160, 87, 255, 251, 160, 87, 255, 251, 159, 87, 255, 251, 159, 86, 255, 251, 158, 86, 255, 251, 157, 85, 255, 250, 157, 85, 255, 250, 156, 85, 255, 250, 155, 84, 255, 250, 155, 84, 255, 250, 154, 84, 255, 250, 154, 83, 255, 250, 153, 83, 255, 250, 152, 83, 255, 250, 152, 82, 255, 250, 151, 82, 255, 250, 151, 82, 255, 250, 150, 81, 255, 250, 149, 81, 255, 249, 149, 81, 255, 249, 148, 80, 255, 249, 147, 80, 255, 249, 147, 80, 255, 249, 146, 79, 255, 249, 146, 79, 255, 249, 145, 79, 255, 249, 144, 78, 255, 249, 144, 78, 255, 249, 143, 78, 255, 249, 142, 78, 255, 249, 142, 77, 255, 248, 141, 77, 255, 248, 141, 77, 255, 248, 140, 76, 255, 248, 139, 76, 255, 248, 139, 76, 255, 248, 138, 76, 255, 248, 137, 75, 255, 248, 137, 75, 255, 248, 136, 75, 255, 248, 135, 74, 255, 248, 135, 74, 255, 247, 134, 74, 255, 247, 134, 74, 255, 247, 133, 73, 255, 247, 132, 73, 255, 247, 132, 73, 255, 247, 131, 73, 255, 247, 130, 72, 255, 247, 130, 72, 255, 247, 129, 72, 255, 247, 128, 72, 255, 247, 128, 72, 255, 246, 127, 71, 255, 246, 126, 71, 255, 246, 126, 71, 255, 246, 125, 71, 255, 246, 124, 70, 255, 246, 124, 70, 255, 246, 123, 70, 255, 246, 122, 70, 255, 246, 122, 70, 255, 246, 121, 69, 255, 245, 120, 69, 255, 245, 120, 69, 255, 245, 119, 69, 255, 245, 118, 69, 255, 245, 118, 69, 255, 245, 117, 68, 255, 245, 116, 68, 255, 245, 116, 68, 255, 245, 115, 68, 255, 244, 114, 68, 255, 244, 114, 68, 255, 244, 113, 67, 255, 244, 112, 67, 255, 244, 111, 67, 255, 244, 111, 67, 255, 244, 110, 67, 255, 244, 109, 67, 255, 244, 109, 67, 255, 243, 108, 67, 255, 243, 108, 67, 255, 243, 107, 67, 255, 243, 107, 67, 255, 242, 106, 67, 255, 242, 106, 67, 255, 242, 105, 67, 255, 242, 105, 67, 255, 241, 104, 68, 255, 241, 104, 68, 255, 241, 103, 68, 255, 241, 103, 68, 255, 240, 102, 68, 255, 240, 102, 68, 255, 240, 101, 68, 255, 240, 101, 68, 255, 239, 101, 69, 255, 239, 100, 69, 255, 239, 100, 69, 255, 239, 99, 69, 255, 238, 99, 69, 255, 238, 98, 69, 255, 238, 98, 69, 255, 237, 97, 69, 255, 237, 97, 70, 255, 237, 96, 70, 255, 237, 96, 70, 255, 236, 95, 70, 255, 236, 95, 70, 255, 236, 94, 70, 255, 236, 94, 70, 255, 235, 93, 70, 255, 235, 93, 71, 255, 235, 93, 71, 255, 234, 92, 71, 255, 234, 92, 71, 255, 234, 91, 71, 255, 234, 91, 71, 255, 233, 90, 71, 255, 233, 90, 71, 255, 233, 89, 72, 255, 232, 89, 72, 255, 232, 88, 72, 255, 232, 88, 72, 255, 232, 87, 72, 255, 231, 87, 72, 255, 231, 87, 72, 255, 231, 86, 72, 255, 230, 86, 73, 255, 230, 85, 73, 255, 230, 85, 73, 255, 229, 84, 73, 255, 229, 84, 73, 255, 229, 83, 73, 255, 229, 83, 73, 255, 228, 82, 73, 255, 228, 82, 73, 255, 228, 81, 74, 255, 227, 81, 74, 255, 227, 81, 74, 255, 227, 80, 74, 255, 226, 80, 74, 255, 226, 79, 74, 255, 226, 79, 74, 255, 225, 78, 74, 255, 225, 78, 75, 255, 225, 77, 75, 255, 224, 77, 75, 255, 224, 76, 75, 255, 224, 76, 75, 255, 223, 76, 75, 255, 223, 75, 75, 255, 223, 75, 75, 255, 222, 74, 75, 255, 222, 74, 76, 255, 222, 73, 76, 255, 221, 73, 76, 255, 221, 72, 76, 255, 221, 72, 76, 255, 220, 72, 76, 255, 220, 71, 76, 255, 220, 71, 76, 255, 219, 70, 76, 255, 219, 70, 77, 255, 219, 69, 77, 255, 218, 69, 77, 255, 218, 68, 77, 255, 218, 68, 77, 255, 217, 68, 77, 255, 217, 67, 77, 255, 217, 67, 77, 255, 216, 66, 77, 255, 216, 66, 78, 255, 216, 65, 78, 255, 215, 65, 78, 255, 215, 64, 78, 255, 215, 64, 78, 255, 214, 64, 78, 255, 214, 63, 78, 255, 213, 63, 78, 255, 213, 62, 78, 255, 213, 62, 78, 255, 212, 61, 78, 255, 212, 61, 78, 255, 211, 60, 78, 255, 211, 60, 78, 255, 210, 59, 78, 255, 210, 59, 78, 255, 209, 58, 78, 255, 209, 58, 78, 255, 208, 57, 78, 255, 208, 57, 77, 255, 207, 56, 77, 255, 207, 56, 77, 255, 206, 55, 77, 255, 205, 55, 77, 255, 205, 55, 77, 255, 204, 54, 77, 255, 204, 54, 77, 255, 203, 53, 77, 255, 203, 53, 76, 255, 202, 52, 76, 255, 202, 52, 76, 255, 201, 51, 76, 255, 201, 51, 76, 255, 200, 50, 76, 255, 200, 50, 76, 255, 199, 49, 76, 255, 199, 49, 76, 255, 198, 48, 75, 255, 197, 48, 75, 255, 197, 47, 75, 255, 196, 47, 75, 255, 196, 46, 75, 255, 195, 46, 75, 255, 195, 45, 75, 255, 194, 45, 75, 255, 194, 44, 74, 255, 193, 44, 74, 255, 193, 43, 74, 255, 192, 43, 74, 255, 192, 42, 74, 255, 191, 42, 74, 255, 191, 41, 74, 255, 190, 41, 74, 255, 189, 40, 73, 255, 189, 39, 73, 255, 188, 39, 73, 255, 188, 38, 73, 255, 187, 38, 73, 255, 187, 37, 73, 255, 186, 37, 73, 255, 186, 36, 73, 255, 185, 36, 72, 255, 185, 35, 72, 255, 184, 35, 72, 255, 184, 34, 72, 255, 183, 34, 72, 255, 182, 33, 72, 255, 182, 33, 72, 255, 181, 32, 72, 255, 181, 31, 71, 255, 180, 31, 71, 255, 180, 30, 71, 255, 179, 30, 71, 255, 179, 29, 71, 255, 178, 29, 71, 255, 178, 28, 71, 255, 177, 27, 71, 255, 177, 27, 70, 255, 176, 26, 70, 255, 175, 26, 70, 255, 175, 25, 70, 255, 174, 25, 70, 255, 174, 24, 70, 255, 173, 23, 70, 255, 173, 23, 69, 255, 172, 22, 69, 255, 172, 21, 69, 255, 171, 21, 69, 255, 171, 20, 69, 255, 170, 19, 69, 255, 169, 19, 69, 255, 169, 18, 68, 255, 168, 17, 68, 255, 168, 17, 68, 255, 167, 16, 68, 255, 167, 15, 68, 255, 166, 14, 68, 255, 166, 14, 68, 255, 165, 13, 68, 255, 165, 12, 67, 255, 164, 11, 67, 255, 164, 10, 67, 255, 163, 9, 67, 255, 162, 8, 67, 255, 162, 7, 67, 255, 161, 6, 67, 255, 161, 6, 66, 255, 160, 5, 66, 255, 160, 4, 66, 255, 159, 3, 66, 255, 159, 2, 66, 255, 158, 1, 66, 255 -}; -static const heatmap_colorscheme_t soft = { soft_data, sizeof(soft_data)/sizeof(soft_data[0]/4) }; -const heatmap_colorscheme_t* heatmap_cs_Spectral_soft = &soft; - -static const unsigned char mixed_data[] = { - 0, 0, 0, 0, 94, 79, 162, 0, 93, 79, 162, 7, 93, 80, 162, 14, 92, 80, 163, 22, 92, 81, 163, 29, 91, 81, 164, 37, 91, 82, 164, 44, 90, 82, 164, 52, 90, 83, 165, 59, 89, 83, 165, 67, 89, 84, 166, 74, 88, 84, 166, 82, 88, 85, 166, 89, 87, 85, 167, 97, 87, 86, 167, 104, 86, 86, 167, 112, 86, 87, 168, 119, 85, 87, 168, 127, 85, 88, 168, 134, 84, 88, 169, 141, 84, 89, 169, 149, 83, 89, 169, 156, 83, 90, 170, 164, 83, 90, 170, 171, 82, 91, 170, 179, 82, 91, 171, 186, 81, 92, 171, 194, 81, 92, 171, 201, 80, 93, 172, 209, 80, 93, 172, 216, 79, 94, 172, 224, 79, 94, 172, 231, 78, 95, 173, 239, 78, 95, 173, 246, 77, 95, 173, 254, 77, 96, 174, 255, 76, 96, 174, 255, 76, 97, 174, 255, 75, 97, 174, 255, 75, 98, 175, 255, 74, 98, 175, 255, 74, 99, 175, 255, 73, 99, 175, 255, 73, 100, 176, 255, 72, 100, 176, 255, 72, 101, 176, 255, 72, 101, 176, 255, 71, 101, 176, 255, 71, 102, 177, 255, 70, 102, 177, 255, 70, 103, 177, 255, 69, 103, 177, 255, 60, 115, 183, 255, 60, 115, 183, 255, 59, 116, 183, 255, 59, 116, 183, 255, 58, 117, 184, 255, 58, 117, 184, 255, 58, 118, 184, 255, 57, 118, 184, 255, 57, 118, 184, 255, 56, 119, 184, 255, 56, 119, 185, 255, 56, 120, 185, 255, 55, 120, 185, 255, 55, 121, 185, 255, 55, 121, 185, 255, 54, 121, 185, 255, 54, 122, 186, 255, 54, 122, 186, 255, 53, 123, 186, 255, 53, 123, 186, 255, 53, 124, 186, 255, 52, 124, 186, 255, 52, 124, 186, 255, 52, 125, 186, 255, 52, 125, 187, 255, 51, 126, 187, 255, 51, 126, 187, 255, 51, 126, 187, 255, 51, 127, 187, 255, 50, 127, 187, 255, 50, 128, 187, 255, 50, 128, 187, 255, 50, 128, 187, 255, 50, 129, 187, 255, 50, 129, 188, 255, 50, 130, 188, 255, 49, 130, 188, 255, 49, 130, 188, 255, 49, 131, 188, 255, 49, 131, 188, 255, 49, 132, 188, 255, 49, 132, 188, 255, 49, 132, 188, 255, 49, 133, 188, 255, 49, 133, 188, 255, 49, 133, 188, 255, 49, 134, 188, 255, 49, 134, 188, 255, 49, 135, 188, 255, 49, 135, 188, 255, 49, 135, 188, 255, 49, 136, 189, 255, 47, 136, 189, 255, 46, 137, 189, 255, 45, 138, 189, 255, 43, 138, 189, 255, 42, 139, 190, 255, 41, 139, 190, 255, 39, 140, 190, 255, 38, 140, 190, 255, 36, 141, 190, 255, 35, 141, 190, 255, 33, 142, 190, 255, 31, 142, 190, 255, 30, 143, 190, 255, 28, 143, 190, 255, 26, 144, 191, 255, 24, 145, 191, 255, 22, 145, 191, 255, 20, 146, 191, 255, 17, 146, 191, 255, 15, 147, 191, 255, 12, 147, 191, 255, 10, 148, 191, 255, 10, 148, 191, 255, 10, 149, 191, 255, 10, 149, 191, 255, 10, 150, 191, 255, 10, 150, 190, 255, 10, 151, 190, 255, 10, 151, 190, 255, 10, 152, 190, 255, 10, 152, 190, 255, 10, 153, 190, 255, 10, 153, 190, 255, 10, 154, 190, 255, 10, 154, 190, 255, 10, 155, 190, 255, 10, 155, 189, 255, 10, 156, 189, 255, 10, 156, 189, 255, 10, 157, 189, 255, 10, 157, 189, 255, 10, 158, 189, 255, 10, 158, 188, 255, 10, 158, 188, 255, 10, 159, 188, 255, 10, 159, 188, 255, 10, 160, 188, 255, 10, 160, 187, 255, 10, 161, 187, 255, 10, 161, 187, 255, 20, 173, 182, 255, 22, 174, 182, 255, 25, 174, 181, 255, 28, 175, 181, 255, 30, 175, 181, 255, 33, 176, 180, 255, 35, 176, 180, 255, 37, 176, 180, 255, 39, 177, 180, 255, 41, 177, 179, 255, 43, 178, 179, 255, 45, 178, 179, 255, 46, 179, 178, 255, 48, 179, 178, 255, 50, 179, 178, 255, 51, 180, 177, 255, 53, 180, 177, 255, 54, 181, 177, 255, 56, 181, 176, 255, 58, 182, 176, 255, 59, 182, 176, 255, 61, 182, 175, 255, 62, 183, 175, 255, 64, 183, 175, 255, 65, 184, 174, 255, 66, 184, 174, 255, 68, 184, 174, 255, 69, 185, 173, 255, 71, 185, 173, 255, 72, 186, 173, 255, 74, 186, 172, 255, 75, 186, 172, 255, 76, 187, 171, 255, 78, 187, 171, 255, 79, 187, 171, 255, 80, 188, 170, 255, 82, 188, 170, 255, 83, 189, 170, 255, 85, 189, 169, 255, 86, 189, 169, 255, 87, 190, 169, 255, 89, 190, 168, 255, 90, 190, 168, 255, 91, 191, 167, 255, 93, 191, 167, 255, 94, 191, 167, 255, 95, 192, 166, 255, 97, 192, 166, 255, 98, 193, 166, 255, 99, 193, 165, 255, 100, 193, 165, 255, 102, 194, 164, 255, 102, 194, 164, 255, 103, 194, 164, 255, 103, 194, 164, 255, 104, 194, 164, 255, 104, 195, 164, 255, 105, 195, 164, 255, 105, 195, 164, 255, 106, 195, 164, 255, 106, 196, 164, 255, 107, 196, 164, 255, 108, 196, 164, 255, 108, 196, 164, 255, 109, 196, 164, 255, 109, 197, 164, 255, 110, 197, 164, 255, 110, 197, 164, 255, 111, 197, 164, 255, 111, 198, 164, 255, 112, 198, 164, 255, 112, 198, 164, 255, 113, 198, 164, 255, 113, 198, 164, 255, 114, 199, 164, 255, 115, 199, 164, 255, 115, 199, 164, 255, 116, 199, 164, 255, 116, 200, 164, 255, 117, 200, 164, 255, 117, 200, 164, 255, 118, 200, 164, 255, 118, 200, 164, 255, 119, 201, 164, 255, 119, 201, 164, 255, 120, 201, 164, 255, 120, 201, 164, 255, 121, 201, 164, 255, 122, 202, 164, 255, 122, 202, 164, 255, 123, 202, 164, 255, 123, 202, 164, 255, 124, 203, 164, 255, 124, 203, 164, 255, 125, 203, 164, 255, 125, 203, 164, 255, 126, 203, 164, 255, 126, 204, 164, 255, 127, 204, 164, 255, 127, 204, 163, 255, 128, 204, 163, 255, 129, 204, 163, 255, 143, 210, 163, 255, 143, 210, 163, 255, 144, 210, 163, 255, 144, 211, 163, 255, 145, 211, 163, 255, 146, 211, 163, 255, 146, 211, 163, 255, 147, 212, 163, 255, 147, 212, 163, 255, 148, 212, 163, 255, 148, 212, 163, 255, 149, 212, 163, 255, 149, 213, 163, 255, 150, 213, 163, 255, 150, 213, 163, 255, 151, 213, 163, 255, 151, 213, 163, 255, 152, 214, 163, 255, 153, 214, 163, 255, 153, 214, 163, 255, 154, 214, 163, 255, 154, 214, 163, 255, 155, 215, 163, 255, 155, 215, 163, 255, 156, 215, 163, 255, 156, 215, 163, 255, 157, 215, 163, 255, 157, 216, 163, 255, 158, 216, 163, 255, 158, 216, 163, 255, 159, 216, 163, 255, 160, 216, 163, 255, 160, 217, 163, 255, 161, 217, 163, 255, 161, 217, 163, 255, 162, 217, 163, 255, 162, 217, 163, 255, 163, 218, 163, 255, 163, 218, 163, 255, 164, 218, 163, 255, 164, 218, 163, 255, 165, 218, 163, 255, 166, 219, 163, 255, 166, 219, 163, 255, 167, 219, 163, 255, 167, 219, 163, 255, 168, 219, 163, 255, 168, 220, 163, 255, 169, 220, 163, 255, 169, 220, 163, 255, 170, 220, 163, 255, 170, 220, 163, 255, 171, 221, 163, 255, 171, 221, 163, 255, 172, 221, 163, 255, 172, 221, 163, 255, 172, 222, 163, 255, 173, 222, 163, 255, 173, 222, 163, 255, 173, 222, 163, 255, 174, 222, 163, 255, 174, 223, 163, 255, 175, 223, 163, 255, 175, 223, 163, 255, 175, 223, 162, 255, 176, 223, 162, 255, 176, 224, 162, 255, 177, 224, 162, 255, 177, 224, 162, 255, 177, 224, 162, 255, 178, 224, 162, 255, 178, 225, 162, 255, 179, 225, 162, 255, 179, 225, 162, 255, 179, 225, 162, 255, 180, 225, 161, 255, 180, 226, 161, 255, 181, 226, 161, 255, 181, 226, 161, 255, 182, 226, 161, 255, 182, 226, 161, 255, 182, 227, 161, 255, 183, 227, 161, 255, 183, 227, 161, 255, 184, 227, 161, 255, 184, 227, 160, 255, 185, 228, 160, 255, 185, 228, 160, 255, 185, 228, 160, 255, 186, 228, 160, 255, 186, 228, 160, 255, 187, 229, 160, 255, 187, 229, 160, 255, 188, 229, 160, 255, 188, 229, 160, 255, 189, 229, 159, 255, 189, 230, 159, 255, 189, 230, 159, 255, 190, 230, 159, 255, 190, 230, 159, 255, 191, 230, 159, 255, 191, 231, 159, 255, 192, 231, 159, 255, 204, 236, 156, 255, 205, 236, 156, 255, 205, 236, 156, 255, 205, 236, 156, 255, 206, 236, 156, 255, 206, 237, 156, 255, 207, 237, 156, 255, 207, 237, 156, 255, 208, 237, 156, 255, 208, 237, 155, 255, 209, 238, 155, 255, 209, 238, 155, 255, 210, 238, 155, 255, 210, 238, 155, 255, 211, 238, 155, 255, 211, 238, 155, 255, 212, 239, 155, 255, 212, 239, 155, 255, 213, 239, 155, 255, 213, 239, 154, 255, 214, 239, 154, 255, 214, 240, 154, 255, 215, 240, 154, 255, 215, 240, 154, 255, 216, 240, 154, 255, 216, 240, 154, 255, 217, 240, 154, 255, 217, 241, 154, 255, 218, 241, 154, 255, 218, 241, 153, 255, 219, 241, 153, 255, 219, 241, 153, 255, 220, 241, 153, 255, 220, 242, 153, 255, 221, 242, 153, 255, 221, 242, 153, 255, 222, 242, 153, 255, 222, 242, 153, 255, 223, 242, 153, 255, 223, 243, 153, 255, 224, 243, 152, 255, 224, 243, 152, 255, 225, 243, 152, 255, 225, 243, 152, 255, 226, 243, 152, 255, 227, 244, 152, 255, 227, 244, 152, 255, 228, 244, 152, 255, 228, 244, 152, 255, 229, 244, 152, 255, 229, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 154, 255, 231, 244, 154, 255, 231, 244, 154, 255, 232, 244, 154, 255, 232, 244, 154, 255, 232, 244, 154, 255, 232, 244, 154, 255, 232, 244, 154, 255, 232, 244, 154, 255, 232, 244, 155, 255, 232, 244, 155, 255, 232, 244, 155, 255, 232, 244, 155, 255, 233, 244, 155, 255, 233, 244, 155, 255, 233, 244, 155, 255, 233, 244, 155, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 234, 244, 156, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 236, 243, 160, 255, 237, 243, 160, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 162, 255, 237, 243, 162, 255, 237, 243, 162, 255, 238, 243, 162, 255, 238, 243, 162, 255, 238, 243, 162, 255, 238, 243, 162, 255, 238, 243, 162, 255, 238, 243, 163, 255, 238, 243, 163, 255, 238, 243, 163, 255, 238, 243, 163, 255, 238, 243, 163, 255, 238, 243, 163, 255, 238, 243, 163, 255, 239, 243, 163, 255, 239, 243, 163, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 165, 255, 239, 243, 165, 255, 239, 243, 165, 255, 240, 243, 165, 255, 240, 243, 165, 255, 240, 243, 165, 255, 240, 243, 165, 255, 240, 243, 165, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 241, 242, 166, 255, 241, 242, 166, 255, 241, 242, 166, 255, 241, 242, 166, 255, 241, 242, 165, 255, 241, 242, 165, 255, 241, 242, 165, 255, 241, 241, 165, 255, 241, 241, 165, 255, 241, 241, 164, 255, 242, 241, 164, 255, 242, 241, 164, 255, 242, 241, 164, 255, 242, 241, 164, 255, 242, 240, 163, 255, 242, 240, 163, 255, 242, 240, 163, 255, 242, 240, 163, 255, 242, 240, 163, 255, 242, 240, 162, 255, 243, 240, 162, 255, 243, 239, 162, 255, 243, 239, 162, 255, 243, 239, 162, 255, 243, 239, 161, 255, 243, 239, 161, 255, 243, 239, 161, 255, 243, 239, 161, 255, 243, 238, 161, 255, 243, 238, 161, 255, 244, 238, 160, 255, 244, 238, 160, 255, 244, 238, 160, 255, 244, 238, 160, 255, 244, 238, 160, 255, 244, 237, 159, 255, 244, 237, 159, 255, 244, 237, 159, 255, 244, 237, 159, 255, 244, 237, 159, 255, 245, 237, 158, 255, 245, 236, 158, 255, 245, 236, 158, 255, 245, 236, 158, 255, 245, 236, 158, 255, 245, 236, 157, 255, 245, 236, 157, 255, 245, 236, 157, 255, 245, 235, 157, 255, 245, 235, 157, 255, 246, 235, 157, 255, 248, 231, 152, 255, 248, 231, 152, 255, 248, 231, 151, 255, 249, 231, 151, 255, 249, 231, 151, 255, 249, 230, 151, 255, 249, 230, 151, 255, 249, 230, 151, 255, 249, 230, 150, 255, 249, 230, 150, 255, 249, 230, 150, 255, 249, 230, 150, 255, 249, 229, 150, 255, 250, 229, 150, 255, 250, 229, 149, 255, 250, 229, 149, 255, 250, 229, 149, 255, 250, 229, 149, 255, 250, 229, 149, 255, 250, 228, 148, 255, 250, 228, 148, 255, 250, 228, 148, 255, 251, 228, 148, 255, 251, 228, 148, 255, 251, 228, 148, 255, 251, 227, 147, 255, 251, 227, 147, 255, 251, 227, 147, 255, 251, 227, 147, 255, 251, 227, 147, 255, 251, 227, 147, 255, 251, 227, 147, 255, 252, 226, 146, 255, 252, 226, 146, 255, 252, 226, 146, 255, 252, 226, 146, 255, 252, 226, 146, 255, 252, 226, 146, 255, 252, 225, 145, 255, 252, 225, 145, 255, 252, 225, 145, 255, 253, 225, 145, 255, 253, 225, 145, 255, 253, 225, 145, 255, 253, 225, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 223, 143, 255, 253, 223, 143, 255, 253, 223, 142, 255, 253, 222, 142, 255, 253, 222, 141, 255, 253, 221, 141, 255, 253, 221, 141, 255, 253, 221, 140, 255, 253, 220, 140, 255, 253, 220, 139, 255, 253, 220, 139, 255, 253, 219, 138, 255, 253, 219, 138, 255, 253, 218, 138, 255, 253, 218, 137, 255, 253, 218, 137, 255, 253, 217, 136, 255, 253, 217, 136, 255, 253, 217, 135, 255, 253, 216, 135, 255, 253, 216, 135, 255, 253, 215, 134, 255, 253, 215, 134, 255, 253, 215, 133, 255, 253, 214, 133, 255, 253, 214, 133, 255, 253, 214, 132, 255, 253, 213, 132, 255, 253, 213, 131, 255, 253, 212, 131, 255, 253, 212, 131, 255, 253, 212, 130, 255, 253, 211, 130, 255, 253, 211, 129, 255, 253, 211, 129, 255, 253, 210, 129, 255, 253, 210, 128, 255, 253, 209, 128, 255, 253, 209, 127, 255, 253, 209, 127, 255, 253, 208, 127, 255, 253, 208, 126, 255, 253, 207, 126, 255, 253, 207, 125, 255, 253, 207, 125, 255, 253, 206, 125, 255, 253, 206, 124, 255, 253, 205, 124, 255, 253, 205, 123, 255, 253, 205, 123, 255, 253, 204, 123, 255, 253, 194, 113, 255, 253, 194, 113, 255, 253, 193, 112, 255, 253, 193, 112, 255, 253, 192, 112, 255, 253, 192, 111, 255, 253, 192, 111, 255, 253, 191, 110, 255, 253, 191, 110, 255, 253, 190, 110, 255, 253, 190, 109, 255, 253, 190, 109, 255, 253, 189, 109, 255, 253, 189, 108, 255, 253, 188, 108, 255, 253, 188, 108, 255, 253, 188, 107, 255, 253, 187, 107, 255, 253, 187, 107, 255, 253, 186, 106, 255, 253, 186, 106, 255, 253, 186, 106, 255, 253, 185, 105, 255, 253, 185, 105, 255, 253, 184, 105, 255, 253, 184, 104, 255, 253, 184, 104, 255, 253, 183, 104, 255, 253, 183, 103, 255, 253, 182, 103, 255, 253, 182, 103, 255, 253, 182, 102, 255, 253, 181, 102, 255, 253, 181, 102, 255, 253, 180, 101, 255, 253, 180, 101, 255, 253, 180, 101, 255, 253, 179, 100, 255, 253, 179, 100, 255, 253, 178, 100, 255, 253, 178, 100, 255, 253, 178, 99, 255, 253, 177, 99, 255, 253, 177, 99, 255, 253, 176, 98, 255, 253, 176, 98, 255, 253, 175, 98, 255, 253, 175, 98, 255, 253, 175, 97, 255, 253, 174, 97, 255, 253, 174, 97, 255, 252, 173, 96, 255, 252, 173, 96, 255, 252, 172, 96, 255, 252, 172, 95, 255, 252, 172, 95, 255, 252, 171, 95, 255, 252, 171, 94, 255, 252, 170, 94, 255, 252, 170, 94, 255, 252, 169, 93, 255, 252, 169, 93, 255, 252, 168, 93, 255, 252, 168, 92, 255, 252, 167, 92, 255, 252, 167, 92, 255, 252, 166, 91, 255, 252, 166, 91, 255, 252, 165, 91, 255, 251, 165, 90, 255, 251, 164, 90, 255, 251, 164, 90, 255, 251, 163, 89, 255, 251, 163, 89, 255, 251, 162, 89, 255, 251, 162, 89, 255, 251, 162, 88, 255, 251, 161, 88, 255, 251, 161, 88, 255, 251, 160, 87, 255, 251, 160, 87, 255, 251, 159, 87, 255, 251, 159, 87, 255, 251, 158, 86, 255, 251, 158, 86, 255, 251, 157, 86, 255, 250, 157, 85, 255, 250, 156, 85, 255, 250, 156, 85, 255, 250, 155, 85, 255, 250, 155, 84, 255, 250, 154, 84, 255, 250, 154, 84, 255, 250, 153, 84, 255, 250, 153, 83, 255, 250, 152, 83, 255, 250, 152, 83, 255, 250, 151, 83, 255, 250, 151, 82, 255, 250, 150, 82, 255, 250, 150, 82, 255, 249, 149, 82, 255, 248, 136, 75, 255, 248, 135, 75, 255, 247, 135, 75, 255, 247, 134, 75, 255, 247, 134, 74, 255, 247, 133, 74, 255, 247, 133, 74, 255, 247, 132, 74, 255, 247, 132, 74, 255, 247, 131, 73, 255, 247, 131, 73, 255, 247, 130, 73, 255, 247, 130, 73, 255, 247, 129, 72, 255, 247, 129, 72, 255, 247, 128, 72, 255, 246, 128, 72, 255, 246, 127, 72, 255, 246, 126, 71, 255, 246, 126, 71, 255, 246, 125, 71, 255, 246, 125, 71, 255, 246, 124, 71, 255, 246, 124, 71, 255, 246, 123, 70, 255, 246, 123, 70, 255, 246, 122, 70, 255, 246, 122, 70, 255, 246, 121, 70, 255, 245, 121, 70, 255, 245, 120, 69, 255, 245, 120, 69, 255, 245, 119, 69, 255, 245, 119, 69, 255, 245, 118, 69, 255, 245, 117, 69, 255, 245, 117, 68, 255, 245, 116, 68, 255, 245, 116, 68, 255, 245, 115, 68, 255, 245, 115, 68, 255, 244, 114, 68, 255, 244, 114, 68, 255, 244, 113, 67, 255, 244, 113, 67, 255, 244, 112, 67, 255, 244, 111, 67, 255, 244, 111, 67, 255, 244, 110, 67, 255, 244, 110, 67, 255, 244, 109, 67, 255, 244, 109, 67, 255, 243, 108, 67, 255, 243, 108, 67, 255, 243, 107, 67, 255, 243, 107, 67, 255, 243, 107, 67, 255, 242, 106, 67, 255, 242, 106, 67, 255, 242, 106, 67, 255, 242, 105, 67, 255, 242, 105, 67, 255, 241, 104, 68, 255, 241, 104, 68, 255, 241, 104, 68, 255, 241, 103, 68, 255, 241, 103, 68, 255, 240, 102, 68, 255, 240, 102, 68, 255, 240, 102, 68, 255, 240, 101, 68, 255, 240, 101, 68, 255, 239, 101, 69, 255, 239, 100, 69, 255, 239, 100, 69, 255, 239, 99, 69, 255, 238, 99, 69, 255, 238, 99, 69, 255, 238, 98, 69, 255, 238, 98, 69, 255, 238, 98, 69, 255, 237, 97, 69, 255, 237, 97, 70, 255, 237, 96, 70, 255, 237, 96, 70, 255, 236, 96, 70, 255, 236, 95, 70, 255, 236, 95, 70, 255, 236, 95, 70, 255, 236, 94, 70, 255, 235, 94, 70, 255, 235, 94, 70, 255, 235, 93, 71, 255, 235, 93, 71, 255, 234, 92, 71, 255, 234, 92, 71, 255, 234, 92, 71, 255, 234, 91, 71, 255, 233, 91, 71, 255, 233, 91, 71, 255, 233, 90, 71, 255, 233, 90, 71, 255, 233, 89, 72, 255, 226, 80, 74, 255, 226, 79, 74, 255, 226, 79, 74, 255, 225, 79, 74, 255, 225, 78, 74, 255, 225, 78, 74, 255, 225, 77, 75, 255, 224, 77, 75, 255, 224, 77, 75, 255, 224, 76, 75, 255, 224, 76, 75, 255, 223, 76, 75, 255, 223, 75, 75, 255, 223, 75, 75, 255, 223, 75, 75, 255, 222, 74, 75, 255, 222, 74, 75, 255, 222, 73, 76, 255, 222, 73, 76, 255, 221, 73, 76, 255, 221, 72, 76, 255, 221, 72, 76, 255, 220, 72, 76, 255, 220, 71, 76, 255, 220, 71, 76, 255, 220, 71, 76, 255, 219, 70, 76, 255, 219, 70, 76, 255, 219, 70, 77, 255, 219, 69, 77, 255, 218, 69, 77, 255, 218, 68, 77, 255, 218, 68, 77, 255, 217, 68, 77, 255, 217, 67, 77, 255, 217, 67, 77, 255, 217, 67, 77, 255, 216, 66, 77, 255, 216, 66, 77, 255, 216, 66, 78, 255, 216, 65, 78, 255, 215, 65, 78, 255, 215, 65, 78, 255, 215, 64, 78, 255, 214, 64, 78, 255, 214, 63, 78, 255, 214, 63, 78, 255, 214, 63, 78, 255, 213, 62, 78, 255, 213, 62, 78, 255, 213, 62, 78, 255, 212, 61, 78, 255, 212, 61, 78, 255, 211, 61, 78, 255, 211, 60, 78, 255, 211, 60, 78, 255, 210, 59, 78, 255, 210, 59, 78, 255, 209, 59, 78, 255, 209, 58, 78, 255, 209, 58, 78, 255, 208, 57, 78, 255, 208, 57, 77, 255, 207, 57, 77, 255, 207, 56, 77, 255, 206, 56, 77, 255, 206, 56, 77, 255, 206, 55, 77, 255, 205, 55, 77, 255, 205, 54, 77, 255, 204, 54, 77, 255, 204, 54, 77, 255, 203, 53, 77, 255, 203, 53, 76, 255, 203, 52, 76, 255, 202, 52, 76, 255, 202, 52, 76, 255, 201, 51, 76, 255, 201, 51, 76, 255, 200, 50, 76, 255, 200, 50, 76, 255, 200, 50, 76, 255, 199, 49, 76, 255, 199, 49, 76, 255, 198, 48, 75, 255, 198, 48, 75, 255, 198, 48, 75, 255, 197, 47, 75, 255, 197, 47, 75, 255, 196, 46, 75, 255, 196, 46, 75, 255, 195, 46, 75, 255, 195, 45, 75, 255, 195, 45, 75, 255, 194, 44, 74, 255, 194, 44, 74, 255, 193, 43, 74, 255, 193, 43, 74, 255, 192, 43, 74, 255, 192, 42, 74, 255, 192, 42, 74, 255, 191, 41, 74, 255, 180, 29, 71, 255, 179, 28, 71, 255, 179, 28, 71, 255, 178, 27, 71, 255, 178, 27, 71, 255, 177, 27, 71, 255, 177, 26, 70, 255, 177, 26, 70, 255, 176, 25, 70, 255, 176, 25, 70, 255, 175, 24, 70, 255, 175, 24, 70, 255, 174, 23, 70, 255, 174, 23, 70, 255, 174, 23, 70, 255, 173, 22, 70, 255, 173, 22, 69, 255, 172, 21, 69, 255, 172, 21, 69, 255, 171, 20, 69, 255, 171, 20, 69, 255, 171, 19, 69, 255, 170, 19, 69, 255, 170, 18, 69, 255, 169, 18, 69, 255, 169, 17, 68, 255, 168, 17, 68, 255, 168, 16, 68, 255, 168, 16, 68, 255, 167, 15, 68, 255, 167, 14, 68, 255, 166, 14, 68, 255, 166, 13, 68, 255, 165, 13, 68, 255, 165, 12, 67, 255, 164, 12, 67, 255, 164, 11, 67, 255, 164, 10, 67, 255, 163, 10, 67, 255, 163, 9, 67, 255, 162, 8, 67, 255, 162, 7, 67, 255, 161, 7, 67, 255, 161, 6, 66, 255, 161, 5, 66, 255, 160, 5, 66, 255, 160, 4, 66, 255, 159, 3, 66, 255, 159, 2, 66, 255, 158, 2, 66, 255, 158, 1, 66, 255 -}; -static const heatmap_colorscheme_t mixed = { mixed_data, sizeof(mixed_data)/sizeof(mixed_data[0]/4) }; -const heatmap_colorscheme_t* heatmap_cs_Spectral_mixed = &mixed; - -static const unsigned char mixed_exp_data[] = { - 0, 0, 0, 0, 94, 79, 162, 0, 89, 84, 165, 24, 84, 89, 169, 49, 79, 93, 172, 74, 75, 98, 175, 99, 70, 102, 177, 124, 57, 118, 184, 149, 54, 122, 186, 174, 51, 126, 187, 199, 50, 129, 188, 224, 49, 133, 188, 249, 46, 137, 189, 255, 33, 142, 190, 255, 15, 147, 191, 255, 10, 151, 190, 255, 10, 155, 189, 255, 10, 159, 188, 255, 30, 175, 181, 255, 47, 179, 178, 255, 60, 182, 175, 255, 73, 186, 172, 255, 84, 189, 169, 255, 95, 192, 166, 255, 103, 194, 164, 255, 108, 196, 164, 255, 112, 198, 164, 255, 116, 200, 164, 255, 121, 201, 164, 255, 125, 203, 164, 255, 129, 205, 163, 255, 147, 212, 163, 255, 151, 213, 163, 255, 155, 215, 163, 255, 159, 216, 163, 255, 163, 218, 163, 255, 167, 219, 163, 255, 171, 221, 163, 255, 174, 222, 163, 255, 176, 224, 162, 255, 179, 225, 162, 255, 182, 227, 161, 255, 185, 228, 160, 255, 188, 229, 160, 255, 191, 231, 159, 255, 206, 237, 156, 255, 210, 238, 155, 255, 213, 239, 154, 255, 216, 240, 154, 255, 219, 241, 153, 255, 223, 242, 153, 255, 226, 243, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 231, 244, 153, 255, 231, 244, 154, 255, 232, 244, 154, 255, 232, 244, 155, 255, 233, 244, 156, 255, 234, 244, 156, 255, 234, 244, 157, 255, 237, 243, 161, 255, 237, 243, 161, 255, 238, 243, 162, 255, 238, 243, 163, 255, 239, 243, 163, 255, 239, 243, 164, 255, 239, 243, 165, 255, 240, 243, 165, 255, 240, 243, 166, 255, 241, 242, 166, 255, 241, 241, 165, 255, 242, 241, 164, 255, 242, 240, 163, 255, 243, 239, 162, 255, 243, 238, 161, 255, 244, 238, 160, 255, 244, 237, 159, 255, 245, 236, 158, 255, 245, 236, 157, 255, 248, 231, 152, 255, 249, 230, 151, 255, 249, 230, 150, 255, 250, 229, 149, 255, 250, 228, 148, 255, 251, 227, 147, 255, 251, 227, 147, 255, 252, 226, 146, 255, 252, 225, 145, 255, 253, 225, 145, 255, 253, 224, 144, 255, 253, 223, 143, 255, 253, 221, 141, 255, 253, 220, 139, 255, 253, 218, 137, 255, 253, 217, 135, 255, 253, 215, 134, 255, 253, 213, 132, 255, 253, 212, 130, 255, 253, 210, 129, 255, 253, 209, 127, 255, 253, 207, 126, 255, 253, 206, 124, 255, 253, 204, 123, 255, 253, 193, 112, 255, 253, 191, 110, 255, 253, 190, 109, 255, 253, 188, 108, 255, 253, 187, 106, 255, 253, 185, 105, 255, 253, 184, 104, 255, 253, 182, 103, 255, 253, 181, 102, 255, 253, 179, 101, 255, 253, 178, 100, 255, 253, 177, 99, 255, 253, 175, 98, 255, 253, 174, 97, 255, 252, 172, 95, 255, 252, 171, 94, 255, 252, 169, 93, 255, 252, 167, 92, 255, 252, 166, 91, 255, 251, 164, 90, 255, 251, 163, 89, 255, 251, 161, 88, 255, 251, 160, 87, 255, 251, 158, 86, 255, 250, 157, 86, 255, 250, 155, 85, 255, 250, 154, 84, 255, 250, 152, 83, 255, 250, 151, 82, 255, 250, 149, 82, 255, 248, 135, 75, 255, 247, 134, 74, 255, 247, 132, 74, 255, 247, 131, 73, 255, 247, 129, 73, 255, 247, 128, 72, 255, 246, 127, 72, 255, 246, 125, 71, 255, 246, 124, 71, 255, 246, 122, 70, 255, 245, 121, 70, 255, 245, 120, 69, 255, 245, 118, 69, 255, 245, 117, 68, 255, 245, 116, 68, 255, 244, 114, 68, 255, 244, 113, 67, 255, 244, 111, 67, 255, 244, 110, 67, 255, 244, 109, 67, 255, 243, 108, 67, 255, 243, 107, 67, 255, 242, 106, 67, 255, 242, 105, 67, 255, 241, 104, 68, 255, 241, 103, 68, 255, 240, 103, 68, 255, 240, 102, 68, 255, 240, 101, 68, 255, 239, 100, 69, 255, 239, 99, 69, 255, 238, 99, 69, 255, 238, 98, 69, 255, 237, 97, 70, 255, 237, 96, 70, 255, 236, 96, 70, 255, 236, 95, 70, 255, 236, 94, 70, 255, 235, 93, 70, 255, 235, 93, 71, 255, 234, 92, 71, 255, 234, 91, 71, 255, 233, 91, 71, 255, 233, 90, 71, 255, 226, 80, 74, 255, 226, 79, 74, 255, 225, 79, 74, 255, 225, 78, 74, 255, 224, 77, 75, 255, 224, 77, 75, 255, 224, 76, 75, 255, 223, 75, 75, 255, 223, 75, 75, 255, 222, 74, 75, 255, 222, 74, 76, 255, 221, 73, 76, 255, 221, 72, 76, 255, 221, 72, 76, 255, 220, 71, 76, 255, 220, 71, 76, 255, 219, 70, 76, 255, 219, 70, 77, 255, 219, 69, 77, 255, 218, 69, 77, 255, 218, 68, 77, 255, 217, 68, 77, 255, 217, 67, 77, 255, 217, 67, 77, 255, 216, 66, 77, 255, 216, 66, 78, 255, 215, 65, 78, 255, 215, 65, 78, 255, 215, 64, 78, 255, 214, 64, 78, 255, 214, 63, 78, 255, 213, 63, 78, 255, 213, 62, 78, 255, 213, 62, 78, 255, 212, 61, 78, 255, 212, 61, 78, 255, 211, 60, 78, 255, 211, 60, 78, 255, 210, 59, 78, 255, 210, 59, 78, 255, 209, 58, 78, 255, 209, 58, 78, 255, 208, 58, 78, 255, 208, 57, 78, 255, 207, 57, 77, 255, 207, 56, 77, 255, 206, 56, 77, 255, 206, 55, 77, 255, 205, 55, 77, 255, 205, 55, 77, 255, 205, 54, 77, 255, 204, 54, 77, 255, 204, 53, 77, 255, 203, 53, 77, 255, 203, 53, 76, 255, 202, 52, 76, 255, 202, 52, 76, 255, 202, 51, 76, 255, 201, 51, 76, 255, 201, 51, 76, 255, 200, 50, 76, 255, 200, 50, 76, 255, 200, 49, 76, 255, 199, 49, 76, 255, 199, 49, 76, 255, 198, 48, 75, 255, 198, 48, 75, 255, 198, 48, 75, 255, 197, 47, 75, 255, 197, 47, 75, 255, 197, 47, 75, 255, 196, 46, 75, 255, 196, 46, 75, 255, 196, 46, 75, 255, 195, 45, 75, 255, 195, 45, 75, 255, 194, 45, 75, 255, 194, 44, 74, 255, 194, 44, 74, 255, 194, 44, 74, 255, 193, 43, 74, 255, 193, 43, 74, 255, 193, 43, 74, 255, 192, 43, 74, 255, 192, 42, 74, 255, 192, 42, 74, 255, 191, 42, 74, 255, 191, 41, 74, 255, 191, 41, 74, 255, 179, 29, 71, 255, 179, 28, 71, 255, 179, 28, 71, 255, 179, 28, 71, 255, 178, 27, 71, 255, 178, 27, 71, 255, 178, 27, 71, 255, 178, 27, 71, 255, 177, 26, 70, 255, 177, 26, 70, 255, 177, 26, 70, 255, 176, 26, 70, 255, 176, 25, 70, 255, 176, 25, 70, 255, 176, 25, 70, 255, 176, 25, 70, 255, 175, 24, 70, 255, 175, 24, 70, 255, 175, 24, 70, 255, 175, 24, 70, 255, 174, 23, 70, 255, 174, 23, 70, 255, 174, 23, 70, 255, 174, 23, 70, 255, 173, 22, 70, 255, 173, 22, 70, 255, 173, 22, 69, 255, 173, 22, 69, 255, 173, 21, 69, 255, 172, 21, 69, 255, 172, 21, 69, 255, 172, 21, 69, 255, 172, 21, 69, 255, 172, 20, 69, 255, 171, 20, 69, 255, 171, 20, 69, 255, 171, 20, 69, 255, 171, 20, 69, 255, 171, 19, 69, 255, 170, 19, 69, 255, 170, 19, 69, 255, 170, 19, 69, 255, 170, 19, 69, 255, 170, 18, 69, 255, 170, 18, 69, 255, 169, 18, 69, 255, 169, 18, 69, 255, 169, 18, 69, 255, 169, 17, 68, 255, 169, 17, 68, 255, 169, 17, 68, 255, 168, 17, 68, 255, 168, 17, 68, 255, 168, 16, 68, 255, 168, 16, 68, 255, 168, 16, 68, 255, 168, 16, 68, 255, 168, 16, 68, 255, 167, 16, 68, 255, 167, 15, 68, 255, 167, 15, 68, 255, 167, 15, 68, 255, 167, 15, 68, 255, 167, 15, 68, 255, 167, 14, 68, 255, 166, 14, 68, 255, 166, 14, 68, 255, 166, 14, 68, 255, 166, 14, 68, 255, 166, 14, 68, 255, 166, 13, 68, 255, 166, 13, 68, 255, 166, 13, 68, 255, 166, 13, 68, 255, 165, 13, 68, 255, 165, 13, 68, 255, 165, 13, 68, 255, 165, 12, 67, 255, 165, 12, 67, 255, 165, 12, 67, 255, 165, 12, 67, 255, 165, 12, 67, 255, 165, 12, 67, 255, 164, 11, 67, 255, 164, 11, 67, 255, 164, 11, 67, 255, 164, 11, 67, 255, 164, 11, 67, 255, 164, 11, 67, 255, 164, 11, 67, 255, 164, 10, 67, 255, 164, 10, 67, 255, 164, 10, 67, 255, 163, 10, 67, 255, 163, 10, 67, 255, 163, 10, 67, 255, 163, 10, 67, 255, 163, 10, 67, 255, 163, 9, 67, 255, 163, 9, 67, 255, 163, 9, 67, 255, 163, 9, 67, 255, 163, 9, 67, 255, 163, 9, 67, 255, 163, 9, 67, 255, 162, 8, 67, 255, 162, 8, 67, 255, 162, 8, 67, 255, 162, 8, 67, 255, 162, 8, 67, 255, 162, 8, 67, 255, 162, 8, 67, 255, 162, 8, 67, 255, 162, 8, 67, 255, 162, 7, 67, 255, 162, 7, 67, 255, 162, 7, 67, 255, 162, 7, 67, 255, 162, 7, 67, 255, 162, 7, 67, 255, 161, 7, 67, 255, 161, 7, 67, 255, 161, 7, 67, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 5, 66, 255, 161, 5, 66, 255, 161, 5, 66, 255, 161, 5, 66, 255, 160, 5, 66, 255, 160, 5, 66, 255, 160, 5, 66, 255, 160, 5, 66, 255, 160, 5, 66, 255, 160, 5, 66, 255, 160, 5, 66, 255, 160, 5, 66, 255, 160, 5, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 159, 4, 66, 255, 159, 4, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 157, 1, 66, 255, 157, 1, 66, 255, 157, 1, 66, 255, 157, 1, 66, 255, 157, 1, 66, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255 -}; -static const heatmap_colorscheme_t mixed_exp = { mixed_exp_data, sizeof(mixed_exp_data)/sizeof(mixed_exp_data[0]/4) }; -const heatmap_colorscheme_t* heatmap_cs_Spectral_mixed_exp = &mixed_exp; - -#ifdef __cplusplus -} -#endif +/* heatmap - High performance heatmap creation in C. + * + * The MIT License (MIT) + * + * Copyright (c) 2013 Lucas Beyer + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "3rdparty/heatmap.h" +#include "3rdparty/colorschemes/Spectral.h" + +static const unsigned char discrete_data[] = { + 0, 0, 0, 0, 94, 79, 162, 255, 50, 136, 189, 255, 102, 194, 165, 255, 171, 221, 164, 255, 230, 245, 152, 255, 241, 243, 167, 255, 254, 224, 144, 255, 253, 174, 97, 255, 244, 109, 67, 255, 213, 62, 79, 255, 158, 1, 66, 255 +}; +static const heatmap_colorscheme_t discrete = { discrete_data, sizeof(discrete_data)/sizeof(discrete_data[0]/4) }; +const heatmap_colorscheme_t* heatmap_cs_Spectral_discrete = &discrete; + +static const unsigned char soft_data[] = { + 0, 0, 0, 0, 94, 79, 162, 0, 93, 79, 162, 7, 92, 80, 163, 14, 92, 81, 163, 22, 91, 81, 164, 29, 91, 82, 164, 37, 90, 82, 165, 44, 89, 83, 165, 52, 89, 84, 166, 59, 88, 84, 166, 67, 88, 85, 167, 74, 87, 86, 167, 82, 86, 86, 167, 89, 86, 87, 168, 97, 85, 88, 168, 104, 85, 88, 169, 112, 84, 89, 169, 119, 83, 89, 170, 127, 83, 90, 170, 134, 82, 91, 171, 141, 82, 91, 171, 149, 81, 92, 171, 156, 80, 93, 172, 164, 80, 93, 172, 171, 79, 94, 173, 179, 79, 94, 173, 186, 78, 95, 173, 194, 77, 96, 174, 201, 77, 96, 174, 209, 76, 97, 174, 216, 76, 97, 175, 224, 75, 98, 175, 231, 74, 99, 176, 239, 74, 99, 176, 246, 73, 100, 176, 254, 72, 100, 177, 255, 72, 101, 177, 255, 71, 101, 177, 255, 71, 102, 178, 255, 70, 103, 178, 255, 70, 103, 178, 255, 69, 104, 178, 255, 68, 104, 179, 255, 68, 105, 179, 255, 67, 105, 179, 255, 67, 106, 180, 255, 66, 107, 180, 255, 65, 107, 180, 255, 65, 108, 180, 255, 64, 108, 181, 255, 64, 109, 181, 255, 63, 109, 181, 255, 63, 110, 181, 255, 62, 110, 182, 255, 62, 111, 182, 255, 61, 112, 182, 255, 61, 112, 182, 255, 60, 113, 183, 255, 60, 113, 183, 255, 59, 114, 183, 255, 59, 114, 183, 255, 58, 115, 183, 255, 58, 115, 184, 255, 57, 116, 184, 255, 57, 116, 184, 255, 56, 117, 184, 255, 56, 117, 184, 255, 55, 118, 185, 255, 55, 118, 185, 255, 55, 119, 185, 255, 54, 119, 185, 255, 54, 120, 185, 255, 53, 121, 185, 255, 53, 121, 186, 255, 53, 122, 186, 255, 52, 122, 186, 255, 52, 123, 186, 255, 52, 123, 186, 255, 51, 124, 186, 255, 51, 124, 186, 255, 51, 125, 187, 255, 51, 125, 187, 255, 50, 126, 187, 255, 50, 126, 187, 255, 50, 127, 187, 255, 50, 127, 187, 255, 50, 128, 187, 255, 50, 128, 187, 255, 49, 129, 187, 255, 49, 129, 188, 255, 49, 130, 188, 255, 49, 130, 188, 255, 49, 131, 188, 255, 49, 131, 188, 255, 49, 131, 188, 255, 49, 132, 188, 255, 49, 132, 188, 255, 49, 133, 188, 255, 49, 133, 188, 255, 49, 134, 188, 255, 49, 134, 188, 255, 49, 135, 188, 255, 49, 135, 188, 255, 49, 136, 189, 255, 47, 137, 189, 255, 45, 137, 189, 255, 44, 138, 189, 255, 42, 139, 190, 255, 40, 139, 190, 255, 38, 140, 190, 255, 37, 141, 190, 255, 35, 141, 190, 255, 33, 142, 190, 255, 31, 143, 191, 255, 29, 143, 191, 255, 27, 144, 191, 255, 25, 145, 191, 255, 23, 145, 191, 255, 20, 146, 191, 255, 18, 147, 191, 255, 15, 147, 191, 255, 13, 148, 191, 255, 9, 149, 191, 255, 6, 149, 191, 255, 3, 150, 191, 255, 0, 151, 191, 255, 0, 151, 191, 255, 0, 152, 191, 255, 0, 153, 191, 255, 0, 153, 191, 255, 0, 154, 191, 255, 0, 155, 191, 255, 0, 155, 191, 255, 0, 156, 191, 255, 0, 156, 191, 255, 0, 157, 190, 255, 0, 158, 190, 255, 0, 158, 190, 255, 0, 159, 190, 255, 0, 159, 190, 255, 0, 160, 190, 255, 0, 161, 189, 255, 0, 161, 189, 255, 0, 162, 189, 255, 0, 162, 189, 255, 0, 163, 189, 255, 0, 164, 188, 255, 0, 164, 188, 255, 0, 165, 188, 255, 0, 165, 188, 255, 0, 166, 187, 255, 0, 167, 187, 255, 0, 167, 187, 255, 0, 168, 186, 255, 0, 168, 186, 255, 3, 169, 186, 255, 6, 169, 185, 255, 9, 170, 185, 255, 13, 170, 185, 255, 16, 171, 184, 255, 18, 172, 184, 255, 21, 172, 184, 255, 23, 173, 183, 255, 26, 173, 183, 255, 28, 174, 183, 255, 30, 174, 182, 255, 33, 175, 182, 255, 35, 175, 181, 255, 37, 176, 181, 255, 39, 176, 181, 255, 41, 177, 180, 255, 43, 177, 180, 255, 45, 178, 179, 255, 47, 179, 179, 255, 48, 179, 179, 255, 50, 180, 178, 255, 52, 180, 178, 255, 54, 181, 177, 255, 56, 181, 177, 255, 58, 182, 176, 255, 59, 182, 176, 255, 61, 183, 176, 255, 63, 183, 175, 255, 65, 184, 175, 255, 67, 184, 174, 255, 68, 185, 174, 255, 70, 185, 173, 255, 72, 185, 173, 255, 73, 186, 172, 255, 75, 186, 172, 255, 77, 187, 171, 255, 79, 187, 171, 255, 80, 188, 170, 255, 82, 188, 170, 255, 84, 189, 170, 255, 85, 189, 169, 255, 87, 190, 169, 255, 89, 190, 168, 255, 90, 191, 168, 255, 92, 191, 167, 255, 94, 191, 167, 255, 95, 192, 166, 255, 97, 192, 166, 255, 99, 193, 165, 255, 100, 193, 165, 255, 102, 194, 164, 255, 102, 194, 164, 255, 103, 194, 164, 255, 104, 194, 164, 255, 104, 195, 164, 255, 105, 195, 164, 255, 106, 195, 164, 255, 106, 196, 164, 255, 107, 196, 164, 255, 108, 196, 164, 255, 108, 196, 164, 255, 109, 197, 164, 255, 110, 197, 164, 255, 110, 197, 164, 255, 111, 197, 164, 255, 112, 198, 164, 255, 112, 198, 164, 255, 113, 198, 164, 255, 114, 199, 164, 255, 114, 199, 164, 255, 115, 199, 164, 255, 116, 199, 164, 255, 116, 200, 164, 255, 117, 200, 164, 255, 118, 200, 164, 255, 118, 200, 164, 255, 119, 201, 164, 255, 120, 201, 164, 255, 120, 201, 164, 255, 121, 202, 164, 255, 122, 202, 164, 255, 123, 202, 164, 255, 123, 202, 164, 255, 124, 203, 164, 255, 125, 203, 164, 255, 125, 203, 163, 255, 126, 203, 163, 255, 127, 204, 163, 255, 127, 204, 163, 255, 128, 204, 163, 255, 129, 205, 163, 255, 129, 205, 163, 255, 130, 205, 163, 255, 131, 205, 163, 255, 131, 206, 163, 255, 132, 206, 163, 255, 133, 206, 163, 255, 133, 206, 163, 255, 134, 207, 163, 255, 135, 207, 163, 255, 135, 207, 163, 255, 136, 207, 163, 255, 137, 208, 163, 255, 137, 208, 163, 255, 138, 208, 163, 255, 139, 209, 163, 255, 139, 209, 163, 255, 140, 209, 163, 255, 141, 209, 163, 255, 141, 210, 163, 255, 142, 210, 163, 255, 143, 210, 163, 255, 143, 210, 163, 255, 144, 211, 163, 255, 145, 211, 163, 255, 145, 211, 163, 255, 146, 211, 163, 255, 147, 212, 163, 255, 147, 212, 163, 255, 148, 212, 163, 255, 149, 212, 163, 255, 149, 213, 163, 255, 150, 213, 163, 255, 151, 213, 163, 255, 151, 213, 163, 255, 152, 214, 163, 255, 153, 214, 163, 255, 153, 214, 163, 255, 154, 214, 163, 255, 155, 215, 163, 255, 155, 215, 163, 255, 156, 215, 163, 255, 157, 215, 163, 255, 157, 216, 163, 255, 158, 216, 163, 255, 159, 216, 163, 255, 160, 216, 163, 255, 160, 217, 163, 255, 161, 217, 163, 255, 162, 217, 163, 255, 162, 217, 163, 255, 163, 218, 163, 255, 164, 218, 163, 255, 164, 218, 163, 255, 165, 218, 163, 255, 166, 219, 163, 255, 166, 219, 163, 255, 167, 219, 163, 255, 168, 219, 163, 255, 168, 220, 163, 255, 169, 220, 163, 255, 170, 220, 163, 255, 170, 220, 163, 255, 171, 221, 163, 255, 171, 221, 163, 255, 172, 221, 163, 255, 172, 222, 163, 255, 173, 222, 163, 255, 173, 222, 163, 255, 174, 222, 163, 255, 174, 223, 163, 255, 175, 223, 163, 255, 175, 223, 162, 255, 176, 223, 162, 255, 176, 224, 162, 255, 177, 224, 162, 255, 177, 224, 162, 255, 178, 224, 162, 255, 178, 225, 162, 255, 179, 225, 162, 255, 179, 225, 162, 255, 180, 225, 161, 255, 180, 226, 161, 255, 181, 226, 161, 255, 181, 226, 161, 255, 182, 226, 161, 255, 182, 227, 161, 255, 183, 227, 161, 255, 183, 227, 161, 255, 184, 227, 161, 255, 184, 228, 160, 255, 185, 228, 160, 255, 185, 228, 160, 255, 186, 228, 160, 255, 186, 229, 160, 255, 187, 229, 160, 255, 188, 229, 160, 255, 188, 229, 160, 255, 189, 230, 159, 255, 189, 230, 159, 255, 190, 230, 159, 255, 190, 230, 159, 255, 191, 231, 159, 255, 191, 231, 159, 255, 192, 231, 159, 255, 193, 231, 159, 255, 193, 232, 158, 255, 194, 232, 158, 255, 194, 232, 158, 255, 195, 232, 158, 255, 195, 233, 158, 255, 196, 233, 158, 255, 197, 233, 158, 255, 197, 233, 158, 255, 198, 233, 158, 255, 198, 234, 157, 255, 199, 234, 157, 255, 199, 234, 157, 255, 200, 234, 157, 255, 201, 235, 157, 255, 201, 235, 157, 255, 202, 235, 157, 255, 202, 235, 157, 255, 203, 236, 156, 255, 204, 236, 156, 255, 204, 236, 156, 255, 205, 236, 156, 255, 205, 236, 156, 255, 206, 237, 156, 255, 207, 237, 156, 255, 207, 237, 156, 255, 208, 237, 155, 255, 208, 238, 155, 255, 209, 238, 155, 255, 210, 238, 155, 255, 210, 238, 155, 255, 211, 238, 155, 255, 212, 239, 155, 255, 212, 239, 155, 255, 213, 239, 154, 255, 213, 239, 154, 255, 214, 240, 154, 255, 215, 240, 154, 255, 215, 240, 154, 255, 216, 240, 154, 255, 217, 240, 154, 255, 217, 241, 154, 255, 218, 241, 153, 255, 219, 241, 153, 255, 219, 241, 153, 255, 220, 242, 153, 255, 220, 242, 153, 255, 221, 242, 153, 255, 222, 242, 153, 255, 222, 242, 153, 255, 223, 243, 153, 255, 224, 243, 152, 255, 224, 243, 152, 255, 225, 243, 152, 255, 226, 243, 152, 255, 226, 244, 152, 255, 227, 244, 152, 255, 228, 244, 152, 255, 228, 244, 152, 255, 229, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 154, 255, 231, 244, 154, 255, 232, 244, 154, 255, 232, 244, 154, 255, 232, 244, 154, 255, 232, 244, 154, 255, 232, 244, 154, 255, 232, 244, 155, 255, 232, 244, 155, 255, 232, 244, 155, 255, 232, 244, 155, 255, 233, 244, 155, 255, 233, 244, 155, 255, 233, 244, 155, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 234, 244, 156, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 158, 255, 235, 244, 158, 255, 235, 244, 158, 255, 235, 244, 158, 255, 235, 244, 158, 255, 235, 243, 158, 255, 235, 243, 158, 255, 235, 243, 159, 255, 235, 243, 159, 255, 235, 243, 159, 255, 236, 243, 159, 255, 236, 243, 159, 255, 236, 243, 159, 255, 236, 243, 159, 255, 236, 243, 160, 255, 236, 243, 160, 255, 236, 243, 160, 255, 236, 243, 160, 255, 236, 243, 160, 255, 237, 243, 160, 255, 237, 243, 160, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 162, 255, 237, 243, 162, 255, 238, 243, 162, 255, 238, 243, 162, 255, 238, 243, 162, 255, 238, 243, 162, 255, 238, 243, 162, 255, 238, 243, 163, 255, 238, 243, 163, 255, 238, 243, 163, 255, 238, 243, 163, 255, 238, 243, 163, 255, 239, 243, 163, 255, 239, 243, 163, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 165, 255, 239, 243, 165, 255, 239, 243, 165, 255, 240, 243, 165, 255, 240, 243, 165, 255, 240, 243, 165, 255, 240, 243, 165, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 241, 242, 166, 255, 241, 242, 166, 255, 241, 242, 166, 255, 241, 242, 165, 255, 241, 242, 165, 255, 241, 241, 165, 255, 241, 241, 165, 255, 241, 241, 164, 255, 242, 241, 164, 255, 242, 241, 164, 255, 242, 241, 164, 255, 242, 240, 163, 255, 242, 240, 163, 255, 242, 240, 163, 255, 242, 240, 163, 255, 242, 240, 162, 255, 243, 239, 162, 255, 243, 239, 162, 255, 243, 239, 162, 255, 243, 239, 161, 255, 243, 239, 161, 255, 243, 239, 161, 255, 243, 238, 161, 255, 243, 238, 160, 255, 244, 238, 160, 255, 244, 238, 160, 255, 244, 238, 160, 255, 244, 238, 159, 255, 244, 237, 159, 255, 244, 237, 159, 255, 244, 237, 159, 255, 244, 237, 159, 255, 245, 237, 158, 255, 245, 236, 158, 255, 245, 236, 158, 255, 245, 236, 158, 255, 245, 236, 157, 255, 245, 236, 157, 255, 245, 236, 157, 255, 245, 235, 157, 255, 246, 235, 156, 255, 246, 235, 156, 255, 246, 235, 156, 255, 246, 235, 156, 255, 246, 234, 155, 255, 246, 234, 155, 255, 246, 234, 155, 255, 246, 234, 155, 255, 247, 234, 155, 255, 247, 233, 154, 255, 247, 233, 154, 255, 247, 233, 154, 255, 247, 233, 154, 255, 247, 233, 153, 255, 247, 233, 153, 255, 247, 232, 153, 255, 248, 232, 153, 255, 248, 232, 153, 255, 248, 232, 152, 255, 248, 232, 152, 255, 248, 231, 152, 255, 248, 231, 152, 255, 248, 231, 151, 255, 248, 231, 151, 255, 249, 231, 151, 255, 249, 231, 151, 255, 249, 230, 151, 255, 249, 230, 150, 255, 249, 230, 150, 255, 249, 230, 150, 255, 249, 230, 150, 255, 250, 229, 150, 255, 250, 229, 149, 255, 250, 229, 149, 255, 250, 229, 149, 255, 250, 229, 149, 255, 250, 228, 148, 255, 250, 228, 148, 255, 250, 228, 148, 255, 251, 228, 148, 255, 251, 228, 148, 255, 251, 227, 147, 255, 251, 227, 147, 255, 251, 227, 147, 255, 251, 227, 147, 255, 251, 227, 147, 255, 251, 226, 146, 255, 252, 226, 146, 255, 252, 226, 146, 255, 252, 226, 146, 255, 252, 226, 146, 255, 252, 226, 145, 255, 252, 225, 145, 255, 252, 225, 145, 255, 253, 225, 145, 255, 253, 225, 145, 255, 253, 225, 145, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 223, 143, 255, 253, 223, 143, 255, 253, 222, 142, 255, 253, 222, 142, 255, 253, 221, 141, 255, 253, 221, 140, 255, 253, 220, 140, 255, 253, 220, 139, 255, 253, 220, 139, 255, 253, 219, 138, 255, 253, 219, 138, 255, 253, 218, 137, 255, 253, 218, 137, 255, 253, 217, 136, 255, 253, 217, 136, 255, 253, 216, 135, 255, 253, 216, 134, 255, 253, 215, 134, 255, 253, 215, 133, 255, 253, 214, 133, 255, 253, 214, 132, 255, 253, 213, 132, 255, 253, 213, 131, 255, 253, 212, 131, 255, 253, 212, 130, 255, 253, 212, 130, 255, 253, 211, 129, 255, 253, 211, 129, 255, 253, 210, 128, 255, 253, 210, 128, 255, 253, 209, 127, 255, 253, 209, 127, 255, 253, 208, 126, 255, 253, 208, 126, 255, 253, 207, 125, 255, 253, 207, 125, 255, 253, 206, 124, 255, 253, 206, 124, 255, 253, 205, 123, 255, 253, 205, 123, 255, 253, 204, 122, 255, 253, 204, 122, 255, 253, 203, 121, 255, 253, 203, 121, 255, 253, 202, 120, 255, 253, 202, 120, 255, 253, 201, 119, 255, 253, 201, 119, 255, 253, 200, 118, 255, 253, 200, 118, 255, 253, 200, 118, 255, 253, 199, 117, 255, 253, 199, 117, 255, 253, 198, 116, 255, 253, 198, 116, 255, 253, 197, 115, 255, 253, 197, 115, 255, 253, 196, 114, 255, 253, 196, 114, 255, 253, 195, 113, 255, 253, 195, 113, 255, 253, 194, 113, 255, 253, 194, 112, 255, 253, 193, 112, 255, 253, 193, 111, 255, 253, 192, 111, 255, 253, 192, 110, 255, 253, 191, 110, 255, 253, 191, 110, 255, 253, 190, 109, 255, 253, 190, 109, 255, 253, 189, 108, 255, 253, 189, 108, 255, 253, 188, 107, 255, 253, 188, 107, 255, 253, 187, 107, 255, 253, 187, 106, 255, 253, 186, 106, 255, 253, 186, 105, 255, 253, 185, 105, 255, 253, 185, 105, 255, 253, 184, 104, 255, 253, 184, 104, 255, 253, 183, 103, 255, 253, 183, 103, 255, 253, 182, 103, 255, 253, 182, 102, 255, 253, 181, 102, 255, 253, 181, 101, 255, 253, 180, 101, 255, 253, 180, 101, 255, 253, 179, 100, 255, 253, 179, 100, 255, 253, 178, 100, 255, 253, 178, 99, 255, 253, 177, 99, 255, 253, 176, 99, 255, 253, 176, 98, 255, 253, 175, 98, 255, 253, 175, 97, 255, 253, 174, 97, 255, 253, 174, 97, 255, 252, 173, 96, 255, 252, 173, 96, 255, 252, 172, 96, 255, 252, 172, 95, 255, 252, 171, 95, 255, 252, 170, 94, 255, 252, 170, 94, 255, 252, 169, 93, 255, 252, 169, 93, 255, 252, 168, 93, 255, 252, 167, 92, 255, 252, 167, 92, 255, 252, 166, 91, 255, 252, 166, 91, 255, 251, 165, 90, 255, 251, 165, 90, 255, 251, 164, 90, 255, 251, 163, 89, 255, 251, 163, 89, 255, 251, 162, 89, 255, 251, 162, 88, 255, 251, 161, 88, 255, 251, 160, 87, 255, 251, 160, 87, 255, 251, 159, 87, 255, 251, 159, 86, 255, 251, 158, 86, 255, 251, 157, 85, 255, 250, 157, 85, 255, 250, 156, 85, 255, 250, 155, 84, 255, 250, 155, 84, 255, 250, 154, 84, 255, 250, 154, 83, 255, 250, 153, 83, 255, 250, 152, 83, 255, 250, 152, 82, 255, 250, 151, 82, 255, 250, 151, 82, 255, 250, 150, 81, 255, 250, 149, 81, 255, 249, 149, 81, 255, 249, 148, 80, 255, 249, 147, 80, 255, 249, 147, 80, 255, 249, 146, 79, 255, 249, 146, 79, 255, 249, 145, 79, 255, 249, 144, 78, 255, 249, 144, 78, 255, 249, 143, 78, 255, 249, 142, 78, 255, 249, 142, 77, 255, 248, 141, 77, 255, 248, 141, 77, 255, 248, 140, 76, 255, 248, 139, 76, 255, 248, 139, 76, 255, 248, 138, 76, 255, 248, 137, 75, 255, 248, 137, 75, 255, 248, 136, 75, 255, 248, 135, 74, 255, 248, 135, 74, 255, 247, 134, 74, 255, 247, 134, 74, 255, 247, 133, 73, 255, 247, 132, 73, 255, 247, 132, 73, 255, 247, 131, 73, 255, 247, 130, 72, 255, 247, 130, 72, 255, 247, 129, 72, 255, 247, 128, 72, 255, 247, 128, 72, 255, 246, 127, 71, 255, 246, 126, 71, 255, 246, 126, 71, 255, 246, 125, 71, 255, 246, 124, 70, 255, 246, 124, 70, 255, 246, 123, 70, 255, 246, 122, 70, 255, 246, 122, 70, 255, 246, 121, 69, 255, 245, 120, 69, 255, 245, 120, 69, 255, 245, 119, 69, 255, 245, 118, 69, 255, 245, 118, 69, 255, 245, 117, 68, 255, 245, 116, 68, 255, 245, 116, 68, 255, 245, 115, 68, 255, 244, 114, 68, 255, 244, 114, 68, 255, 244, 113, 67, 255, 244, 112, 67, 255, 244, 111, 67, 255, 244, 111, 67, 255, 244, 110, 67, 255, 244, 109, 67, 255, 244, 109, 67, 255, 243, 108, 67, 255, 243, 108, 67, 255, 243, 107, 67, 255, 243, 107, 67, 255, 242, 106, 67, 255, 242, 106, 67, 255, 242, 105, 67, 255, 242, 105, 67, 255, 241, 104, 68, 255, 241, 104, 68, 255, 241, 103, 68, 255, 241, 103, 68, 255, 240, 102, 68, 255, 240, 102, 68, 255, 240, 101, 68, 255, 240, 101, 68, 255, 239, 101, 69, 255, 239, 100, 69, 255, 239, 100, 69, 255, 239, 99, 69, 255, 238, 99, 69, 255, 238, 98, 69, 255, 238, 98, 69, 255, 237, 97, 69, 255, 237, 97, 70, 255, 237, 96, 70, 255, 237, 96, 70, 255, 236, 95, 70, 255, 236, 95, 70, 255, 236, 94, 70, 255, 236, 94, 70, 255, 235, 93, 70, 255, 235, 93, 71, 255, 235, 93, 71, 255, 234, 92, 71, 255, 234, 92, 71, 255, 234, 91, 71, 255, 234, 91, 71, 255, 233, 90, 71, 255, 233, 90, 71, 255, 233, 89, 72, 255, 232, 89, 72, 255, 232, 88, 72, 255, 232, 88, 72, 255, 232, 87, 72, 255, 231, 87, 72, 255, 231, 87, 72, 255, 231, 86, 72, 255, 230, 86, 73, 255, 230, 85, 73, 255, 230, 85, 73, 255, 229, 84, 73, 255, 229, 84, 73, 255, 229, 83, 73, 255, 229, 83, 73, 255, 228, 82, 73, 255, 228, 82, 73, 255, 228, 81, 74, 255, 227, 81, 74, 255, 227, 81, 74, 255, 227, 80, 74, 255, 226, 80, 74, 255, 226, 79, 74, 255, 226, 79, 74, 255, 225, 78, 74, 255, 225, 78, 75, 255, 225, 77, 75, 255, 224, 77, 75, 255, 224, 76, 75, 255, 224, 76, 75, 255, 223, 76, 75, 255, 223, 75, 75, 255, 223, 75, 75, 255, 222, 74, 75, 255, 222, 74, 76, 255, 222, 73, 76, 255, 221, 73, 76, 255, 221, 72, 76, 255, 221, 72, 76, 255, 220, 72, 76, 255, 220, 71, 76, 255, 220, 71, 76, 255, 219, 70, 76, 255, 219, 70, 77, 255, 219, 69, 77, 255, 218, 69, 77, 255, 218, 68, 77, 255, 218, 68, 77, 255, 217, 68, 77, 255, 217, 67, 77, 255, 217, 67, 77, 255, 216, 66, 77, 255, 216, 66, 78, 255, 216, 65, 78, 255, 215, 65, 78, 255, 215, 64, 78, 255, 215, 64, 78, 255, 214, 64, 78, 255, 214, 63, 78, 255, 213, 63, 78, 255, 213, 62, 78, 255, 213, 62, 78, 255, 212, 61, 78, 255, 212, 61, 78, 255, 211, 60, 78, 255, 211, 60, 78, 255, 210, 59, 78, 255, 210, 59, 78, 255, 209, 58, 78, 255, 209, 58, 78, 255, 208, 57, 78, 255, 208, 57, 77, 255, 207, 56, 77, 255, 207, 56, 77, 255, 206, 55, 77, 255, 205, 55, 77, 255, 205, 55, 77, 255, 204, 54, 77, 255, 204, 54, 77, 255, 203, 53, 77, 255, 203, 53, 76, 255, 202, 52, 76, 255, 202, 52, 76, 255, 201, 51, 76, 255, 201, 51, 76, 255, 200, 50, 76, 255, 200, 50, 76, 255, 199, 49, 76, 255, 199, 49, 76, 255, 198, 48, 75, 255, 197, 48, 75, 255, 197, 47, 75, 255, 196, 47, 75, 255, 196, 46, 75, 255, 195, 46, 75, 255, 195, 45, 75, 255, 194, 45, 75, 255, 194, 44, 74, 255, 193, 44, 74, 255, 193, 43, 74, 255, 192, 43, 74, 255, 192, 42, 74, 255, 191, 42, 74, 255, 191, 41, 74, 255, 190, 41, 74, 255, 189, 40, 73, 255, 189, 39, 73, 255, 188, 39, 73, 255, 188, 38, 73, 255, 187, 38, 73, 255, 187, 37, 73, 255, 186, 37, 73, 255, 186, 36, 73, 255, 185, 36, 72, 255, 185, 35, 72, 255, 184, 35, 72, 255, 184, 34, 72, 255, 183, 34, 72, 255, 182, 33, 72, 255, 182, 33, 72, 255, 181, 32, 72, 255, 181, 31, 71, 255, 180, 31, 71, 255, 180, 30, 71, 255, 179, 30, 71, 255, 179, 29, 71, 255, 178, 29, 71, 255, 178, 28, 71, 255, 177, 27, 71, 255, 177, 27, 70, 255, 176, 26, 70, 255, 175, 26, 70, 255, 175, 25, 70, 255, 174, 25, 70, 255, 174, 24, 70, 255, 173, 23, 70, 255, 173, 23, 69, 255, 172, 22, 69, 255, 172, 21, 69, 255, 171, 21, 69, 255, 171, 20, 69, 255, 170, 19, 69, 255, 169, 19, 69, 255, 169, 18, 68, 255, 168, 17, 68, 255, 168, 17, 68, 255, 167, 16, 68, 255, 167, 15, 68, 255, 166, 14, 68, 255, 166, 14, 68, 255, 165, 13, 68, 255, 165, 12, 67, 255, 164, 11, 67, 255, 164, 10, 67, 255, 163, 9, 67, 255, 162, 8, 67, 255, 162, 7, 67, 255, 161, 6, 67, 255, 161, 6, 66, 255, 160, 5, 66, 255, 160, 4, 66, 255, 159, 3, 66, 255, 159, 2, 66, 255, 158, 1, 66, 255 +}; +static const heatmap_colorscheme_t soft = { soft_data, sizeof(soft_data)/sizeof(soft_data[0]/4) }; +const heatmap_colorscheme_t* heatmap_cs_Spectral_soft = &soft; + +static const unsigned char mixed_data[] = { + 0, 0, 0, 0, 94, 79, 162, 0, 93, 79, 162, 7, 93, 80, 162, 14, 92, 80, 163, 22, 92, 81, 163, 29, 91, 81, 164, 37, 91, 82, 164, 44, 90, 82, 164, 52, 90, 83, 165, 59, 89, 83, 165, 67, 89, 84, 166, 74, 88, 84, 166, 82, 88, 85, 166, 89, 87, 85, 167, 97, 87, 86, 167, 104, 86, 86, 167, 112, 86, 87, 168, 119, 85, 87, 168, 127, 85, 88, 168, 134, 84, 88, 169, 141, 84, 89, 169, 149, 83, 89, 169, 156, 83, 90, 170, 164, 83, 90, 170, 171, 82, 91, 170, 179, 82, 91, 171, 186, 81, 92, 171, 194, 81, 92, 171, 201, 80, 93, 172, 209, 80, 93, 172, 216, 79, 94, 172, 224, 79, 94, 172, 231, 78, 95, 173, 239, 78, 95, 173, 246, 77, 95, 173, 254, 77, 96, 174, 255, 76, 96, 174, 255, 76, 97, 174, 255, 75, 97, 174, 255, 75, 98, 175, 255, 74, 98, 175, 255, 74, 99, 175, 255, 73, 99, 175, 255, 73, 100, 176, 255, 72, 100, 176, 255, 72, 101, 176, 255, 72, 101, 176, 255, 71, 101, 176, 255, 71, 102, 177, 255, 70, 102, 177, 255, 70, 103, 177, 255, 69, 103, 177, 255, 60, 115, 183, 255, 60, 115, 183, 255, 59, 116, 183, 255, 59, 116, 183, 255, 58, 117, 184, 255, 58, 117, 184, 255, 58, 118, 184, 255, 57, 118, 184, 255, 57, 118, 184, 255, 56, 119, 184, 255, 56, 119, 185, 255, 56, 120, 185, 255, 55, 120, 185, 255, 55, 121, 185, 255, 55, 121, 185, 255, 54, 121, 185, 255, 54, 122, 186, 255, 54, 122, 186, 255, 53, 123, 186, 255, 53, 123, 186, 255, 53, 124, 186, 255, 52, 124, 186, 255, 52, 124, 186, 255, 52, 125, 186, 255, 52, 125, 187, 255, 51, 126, 187, 255, 51, 126, 187, 255, 51, 126, 187, 255, 51, 127, 187, 255, 50, 127, 187, 255, 50, 128, 187, 255, 50, 128, 187, 255, 50, 128, 187, 255, 50, 129, 187, 255, 50, 129, 188, 255, 50, 130, 188, 255, 49, 130, 188, 255, 49, 130, 188, 255, 49, 131, 188, 255, 49, 131, 188, 255, 49, 132, 188, 255, 49, 132, 188, 255, 49, 132, 188, 255, 49, 133, 188, 255, 49, 133, 188, 255, 49, 133, 188, 255, 49, 134, 188, 255, 49, 134, 188, 255, 49, 135, 188, 255, 49, 135, 188, 255, 49, 135, 188, 255, 49, 136, 189, 255, 47, 136, 189, 255, 46, 137, 189, 255, 45, 138, 189, 255, 43, 138, 189, 255, 42, 139, 190, 255, 41, 139, 190, 255, 39, 140, 190, 255, 38, 140, 190, 255, 36, 141, 190, 255, 35, 141, 190, 255, 33, 142, 190, 255, 31, 142, 190, 255, 30, 143, 190, 255, 28, 143, 190, 255, 26, 144, 191, 255, 24, 145, 191, 255, 22, 145, 191, 255, 20, 146, 191, 255, 17, 146, 191, 255, 15, 147, 191, 255, 12, 147, 191, 255, 10, 148, 191, 255, 10, 148, 191, 255, 10, 149, 191, 255, 10, 149, 191, 255, 10, 150, 191, 255, 10, 150, 190, 255, 10, 151, 190, 255, 10, 151, 190, 255, 10, 152, 190, 255, 10, 152, 190, 255, 10, 153, 190, 255, 10, 153, 190, 255, 10, 154, 190, 255, 10, 154, 190, 255, 10, 155, 190, 255, 10, 155, 189, 255, 10, 156, 189, 255, 10, 156, 189, 255, 10, 157, 189, 255, 10, 157, 189, 255, 10, 158, 189, 255, 10, 158, 188, 255, 10, 158, 188, 255, 10, 159, 188, 255, 10, 159, 188, 255, 10, 160, 188, 255, 10, 160, 187, 255, 10, 161, 187, 255, 10, 161, 187, 255, 20, 173, 182, 255, 22, 174, 182, 255, 25, 174, 181, 255, 28, 175, 181, 255, 30, 175, 181, 255, 33, 176, 180, 255, 35, 176, 180, 255, 37, 176, 180, 255, 39, 177, 180, 255, 41, 177, 179, 255, 43, 178, 179, 255, 45, 178, 179, 255, 46, 179, 178, 255, 48, 179, 178, 255, 50, 179, 178, 255, 51, 180, 177, 255, 53, 180, 177, 255, 54, 181, 177, 255, 56, 181, 176, 255, 58, 182, 176, 255, 59, 182, 176, 255, 61, 182, 175, 255, 62, 183, 175, 255, 64, 183, 175, 255, 65, 184, 174, 255, 66, 184, 174, 255, 68, 184, 174, 255, 69, 185, 173, 255, 71, 185, 173, 255, 72, 186, 173, 255, 74, 186, 172, 255, 75, 186, 172, 255, 76, 187, 171, 255, 78, 187, 171, 255, 79, 187, 171, 255, 80, 188, 170, 255, 82, 188, 170, 255, 83, 189, 170, 255, 85, 189, 169, 255, 86, 189, 169, 255, 87, 190, 169, 255, 89, 190, 168, 255, 90, 190, 168, 255, 91, 191, 167, 255, 93, 191, 167, 255, 94, 191, 167, 255, 95, 192, 166, 255, 97, 192, 166, 255, 98, 193, 166, 255, 99, 193, 165, 255, 100, 193, 165, 255, 102, 194, 164, 255, 102, 194, 164, 255, 103, 194, 164, 255, 103, 194, 164, 255, 104, 194, 164, 255, 104, 195, 164, 255, 105, 195, 164, 255, 105, 195, 164, 255, 106, 195, 164, 255, 106, 196, 164, 255, 107, 196, 164, 255, 108, 196, 164, 255, 108, 196, 164, 255, 109, 196, 164, 255, 109, 197, 164, 255, 110, 197, 164, 255, 110, 197, 164, 255, 111, 197, 164, 255, 111, 198, 164, 255, 112, 198, 164, 255, 112, 198, 164, 255, 113, 198, 164, 255, 113, 198, 164, 255, 114, 199, 164, 255, 115, 199, 164, 255, 115, 199, 164, 255, 116, 199, 164, 255, 116, 200, 164, 255, 117, 200, 164, 255, 117, 200, 164, 255, 118, 200, 164, 255, 118, 200, 164, 255, 119, 201, 164, 255, 119, 201, 164, 255, 120, 201, 164, 255, 120, 201, 164, 255, 121, 201, 164, 255, 122, 202, 164, 255, 122, 202, 164, 255, 123, 202, 164, 255, 123, 202, 164, 255, 124, 203, 164, 255, 124, 203, 164, 255, 125, 203, 164, 255, 125, 203, 164, 255, 126, 203, 164, 255, 126, 204, 164, 255, 127, 204, 164, 255, 127, 204, 163, 255, 128, 204, 163, 255, 129, 204, 163, 255, 143, 210, 163, 255, 143, 210, 163, 255, 144, 210, 163, 255, 144, 211, 163, 255, 145, 211, 163, 255, 146, 211, 163, 255, 146, 211, 163, 255, 147, 212, 163, 255, 147, 212, 163, 255, 148, 212, 163, 255, 148, 212, 163, 255, 149, 212, 163, 255, 149, 213, 163, 255, 150, 213, 163, 255, 150, 213, 163, 255, 151, 213, 163, 255, 151, 213, 163, 255, 152, 214, 163, 255, 153, 214, 163, 255, 153, 214, 163, 255, 154, 214, 163, 255, 154, 214, 163, 255, 155, 215, 163, 255, 155, 215, 163, 255, 156, 215, 163, 255, 156, 215, 163, 255, 157, 215, 163, 255, 157, 216, 163, 255, 158, 216, 163, 255, 158, 216, 163, 255, 159, 216, 163, 255, 160, 216, 163, 255, 160, 217, 163, 255, 161, 217, 163, 255, 161, 217, 163, 255, 162, 217, 163, 255, 162, 217, 163, 255, 163, 218, 163, 255, 163, 218, 163, 255, 164, 218, 163, 255, 164, 218, 163, 255, 165, 218, 163, 255, 166, 219, 163, 255, 166, 219, 163, 255, 167, 219, 163, 255, 167, 219, 163, 255, 168, 219, 163, 255, 168, 220, 163, 255, 169, 220, 163, 255, 169, 220, 163, 255, 170, 220, 163, 255, 170, 220, 163, 255, 171, 221, 163, 255, 171, 221, 163, 255, 172, 221, 163, 255, 172, 221, 163, 255, 172, 222, 163, 255, 173, 222, 163, 255, 173, 222, 163, 255, 173, 222, 163, 255, 174, 222, 163, 255, 174, 223, 163, 255, 175, 223, 163, 255, 175, 223, 163, 255, 175, 223, 162, 255, 176, 223, 162, 255, 176, 224, 162, 255, 177, 224, 162, 255, 177, 224, 162, 255, 177, 224, 162, 255, 178, 224, 162, 255, 178, 225, 162, 255, 179, 225, 162, 255, 179, 225, 162, 255, 179, 225, 162, 255, 180, 225, 161, 255, 180, 226, 161, 255, 181, 226, 161, 255, 181, 226, 161, 255, 182, 226, 161, 255, 182, 226, 161, 255, 182, 227, 161, 255, 183, 227, 161, 255, 183, 227, 161, 255, 184, 227, 161, 255, 184, 227, 160, 255, 185, 228, 160, 255, 185, 228, 160, 255, 185, 228, 160, 255, 186, 228, 160, 255, 186, 228, 160, 255, 187, 229, 160, 255, 187, 229, 160, 255, 188, 229, 160, 255, 188, 229, 160, 255, 189, 229, 159, 255, 189, 230, 159, 255, 189, 230, 159, 255, 190, 230, 159, 255, 190, 230, 159, 255, 191, 230, 159, 255, 191, 231, 159, 255, 192, 231, 159, 255, 204, 236, 156, 255, 205, 236, 156, 255, 205, 236, 156, 255, 205, 236, 156, 255, 206, 236, 156, 255, 206, 237, 156, 255, 207, 237, 156, 255, 207, 237, 156, 255, 208, 237, 156, 255, 208, 237, 155, 255, 209, 238, 155, 255, 209, 238, 155, 255, 210, 238, 155, 255, 210, 238, 155, 255, 211, 238, 155, 255, 211, 238, 155, 255, 212, 239, 155, 255, 212, 239, 155, 255, 213, 239, 155, 255, 213, 239, 154, 255, 214, 239, 154, 255, 214, 240, 154, 255, 215, 240, 154, 255, 215, 240, 154, 255, 216, 240, 154, 255, 216, 240, 154, 255, 217, 240, 154, 255, 217, 241, 154, 255, 218, 241, 154, 255, 218, 241, 153, 255, 219, 241, 153, 255, 219, 241, 153, 255, 220, 241, 153, 255, 220, 242, 153, 255, 221, 242, 153, 255, 221, 242, 153, 255, 222, 242, 153, 255, 222, 242, 153, 255, 223, 242, 153, 255, 223, 243, 153, 255, 224, 243, 152, 255, 224, 243, 152, 255, 225, 243, 152, 255, 225, 243, 152, 255, 226, 243, 152, 255, 227, 244, 152, 255, 227, 244, 152, 255, 228, 244, 152, 255, 228, 244, 152, 255, 229, 244, 152, 255, 229, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 230, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 153, 255, 231, 244, 154, 255, 231, 244, 154, 255, 231, 244, 154, 255, 232, 244, 154, 255, 232, 244, 154, 255, 232, 244, 154, 255, 232, 244, 154, 255, 232, 244, 154, 255, 232, 244, 154, 255, 232, 244, 155, 255, 232, 244, 155, 255, 232, 244, 155, 255, 232, 244, 155, 255, 233, 244, 155, 255, 233, 244, 155, 255, 233, 244, 155, 255, 233, 244, 155, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 233, 244, 156, 255, 234, 244, 156, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 234, 244, 157, 255, 236, 243, 160, 255, 237, 243, 160, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 161, 255, 237, 243, 162, 255, 237, 243, 162, 255, 237, 243, 162, 255, 238, 243, 162, 255, 238, 243, 162, 255, 238, 243, 162, 255, 238, 243, 162, 255, 238, 243, 162, 255, 238, 243, 163, 255, 238, 243, 163, 255, 238, 243, 163, 255, 238, 243, 163, 255, 238, 243, 163, 255, 238, 243, 163, 255, 238, 243, 163, 255, 239, 243, 163, 255, 239, 243, 163, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 164, 255, 239, 243, 165, 255, 239, 243, 165, 255, 239, 243, 165, 255, 240, 243, 165, 255, 240, 243, 165, 255, 240, 243, 165, 255, 240, 243, 165, 255, 240, 243, 165, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 240, 243, 166, 255, 241, 242, 166, 255, 241, 242, 166, 255, 241, 242, 166, 255, 241, 242, 166, 255, 241, 242, 165, 255, 241, 242, 165, 255, 241, 242, 165, 255, 241, 241, 165, 255, 241, 241, 165, 255, 241, 241, 164, 255, 242, 241, 164, 255, 242, 241, 164, 255, 242, 241, 164, 255, 242, 241, 164, 255, 242, 240, 163, 255, 242, 240, 163, 255, 242, 240, 163, 255, 242, 240, 163, 255, 242, 240, 163, 255, 242, 240, 162, 255, 243, 240, 162, 255, 243, 239, 162, 255, 243, 239, 162, 255, 243, 239, 162, 255, 243, 239, 161, 255, 243, 239, 161, 255, 243, 239, 161, 255, 243, 239, 161, 255, 243, 238, 161, 255, 243, 238, 161, 255, 244, 238, 160, 255, 244, 238, 160, 255, 244, 238, 160, 255, 244, 238, 160, 255, 244, 238, 160, 255, 244, 237, 159, 255, 244, 237, 159, 255, 244, 237, 159, 255, 244, 237, 159, 255, 244, 237, 159, 255, 245, 237, 158, 255, 245, 236, 158, 255, 245, 236, 158, 255, 245, 236, 158, 255, 245, 236, 158, 255, 245, 236, 157, 255, 245, 236, 157, 255, 245, 236, 157, 255, 245, 235, 157, 255, 245, 235, 157, 255, 246, 235, 157, 255, 248, 231, 152, 255, 248, 231, 152, 255, 248, 231, 151, 255, 249, 231, 151, 255, 249, 231, 151, 255, 249, 230, 151, 255, 249, 230, 151, 255, 249, 230, 151, 255, 249, 230, 150, 255, 249, 230, 150, 255, 249, 230, 150, 255, 249, 230, 150, 255, 249, 229, 150, 255, 250, 229, 150, 255, 250, 229, 149, 255, 250, 229, 149, 255, 250, 229, 149, 255, 250, 229, 149, 255, 250, 229, 149, 255, 250, 228, 148, 255, 250, 228, 148, 255, 250, 228, 148, 255, 251, 228, 148, 255, 251, 228, 148, 255, 251, 228, 148, 255, 251, 227, 147, 255, 251, 227, 147, 255, 251, 227, 147, 255, 251, 227, 147, 255, 251, 227, 147, 255, 251, 227, 147, 255, 251, 227, 147, 255, 252, 226, 146, 255, 252, 226, 146, 255, 252, 226, 146, 255, 252, 226, 146, 255, 252, 226, 146, 255, 252, 226, 146, 255, 252, 225, 145, 255, 252, 225, 145, 255, 252, 225, 145, 255, 253, 225, 145, 255, 253, 225, 145, 255, 253, 225, 145, 255, 253, 225, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 223, 143, 255, 253, 223, 143, 255, 253, 223, 142, 255, 253, 222, 142, 255, 253, 222, 141, 255, 253, 221, 141, 255, 253, 221, 141, 255, 253, 221, 140, 255, 253, 220, 140, 255, 253, 220, 139, 255, 253, 220, 139, 255, 253, 219, 138, 255, 253, 219, 138, 255, 253, 218, 138, 255, 253, 218, 137, 255, 253, 218, 137, 255, 253, 217, 136, 255, 253, 217, 136, 255, 253, 217, 135, 255, 253, 216, 135, 255, 253, 216, 135, 255, 253, 215, 134, 255, 253, 215, 134, 255, 253, 215, 133, 255, 253, 214, 133, 255, 253, 214, 133, 255, 253, 214, 132, 255, 253, 213, 132, 255, 253, 213, 131, 255, 253, 212, 131, 255, 253, 212, 131, 255, 253, 212, 130, 255, 253, 211, 130, 255, 253, 211, 129, 255, 253, 211, 129, 255, 253, 210, 129, 255, 253, 210, 128, 255, 253, 209, 128, 255, 253, 209, 127, 255, 253, 209, 127, 255, 253, 208, 127, 255, 253, 208, 126, 255, 253, 207, 126, 255, 253, 207, 125, 255, 253, 207, 125, 255, 253, 206, 125, 255, 253, 206, 124, 255, 253, 205, 124, 255, 253, 205, 123, 255, 253, 205, 123, 255, 253, 204, 123, 255, 253, 194, 113, 255, 253, 194, 113, 255, 253, 193, 112, 255, 253, 193, 112, 255, 253, 192, 112, 255, 253, 192, 111, 255, 253, 192, 111, 255, 253, 191, 110, 255, 253, 191, 110, 255, 253, 190, 110, 255, 253, 190, 109, 255, 253, 190, 109, 255, 253, 189, 109, 255, 253, 189, 108, 255, 253, 188, 108, 255, 253, 188, 108, 255, 253, 188, 107, 255, 253, 187, 107, 255, 253, 187, 107, 255, 253, 186, 106, 255, 253, 186, 106, 255, 253, 186, 106, 255, 253, 185, 105, 255, 253, 185, 105, 255, 253, 184, 105, 255, 253, 184, 104, 255, 253, 184, 104, 255, 253, 183, 104, 255, 253, 183, 103, 255, 253, 182, 103, 255, 253, 182, 103, 255, 253, 182, 102, 255, 253, 181, 102, 255, 253, 181, 102, 255, 253, 180, 101, 255, 253, 180, 101, 255, 253, 180, 101, 255, 253, 179, 100, 255, 253, 179, 100, 255, 253, 178, 100, 255, 253, 178, 100, 255, 253, 178, 99, 255, 253, 177, 99, 255, 253, 177, 99, 255, 253, 176, 98, 255, 253, 176, 98, 255, 253, 175, 98, 255, 253, 175, 98, 255, 253, 175, 97, 255, 253, 174, 97, 255, 253, 174, 97, 255, 252, 173, 96, 255, 252, 173, 96, 255, 252, 172, 96, 255, 252, 172, 95, 255, 252, 172, 95, 255, 252, 171, 95, 255, 252, 171, 94, 255, 252, 170, 94, 255, 252, 170, 94, 255, 252, 169, 93, 255, 252, 169, 93, 255, 252, 168, 93, 255, 252, 168, 92, 255, 252, 167, 92, 255, 252, 167, 92, 255, 252, 166, 91, 255, 252, 166, 91, 255, 252, 165, 91, 255, 251, 165, 90, 255, 251, 164, 90, 255, 251, 164, 90, 255, 251, 163, 89, 255, 251, 163, 89, 255, 251, 162, 89, 255, 251, 162, 89, 255, 251, 162, 88, 255, 251, 161, 88, 255, 251, 161, 88, 255, 251, 160, 87, 255, 251, 160, 87, 255, 251, 159, 87, 255, 251, 159, 87, 255, 251, 158, 86, 255, 251, 158, 86, 255, 251, 157, 86, 255, 250, 157, 85, 255, 250, 156, 85, 255, 250, 156, 85, 255, 250, 155, 85, 255, 250, 155, 84, 255, 250, 154, 84, 255, 250, 154, 84, 255, 250, 153, 84, 255, 250, 153, 83, 255, 250, 152, 83, 255, 250, 152, 83, 255, 250, 151, 83, 255, 250, 151, 82, 255, 250, 150, 82, 255, 250, 150, 82, 255, 249, 149, 82, 255, 248, 136, 75, 255, 248, 135, 75, 255, 247, 135, 75, 255, 247, 134, 75, 255, 247, 134, 74, 255, 247, 133, 74, 255, 247, 133, 74, 255, 247, 132, 74, 255, 247, 132, 74, 255, 247, 131, 73, 255, 247, 131, 73, 255, 247, 130, 73, 255, 247, 130, 73, 255, 247, 129, 72, 255, 247, 129, 72, 255, 247, 128, 72, 255, 246, 128, 72, 255, 246, 127, 72, 255, 246, 126, 71, 255, 246, 126, 71, 255, 246, 125, 71, 255, 246, 125, 71, 255, 246, 124, 71, 255, 246, 124, 71, 255, 246, 123, 70, 255, 246, 123, 70, 255, 246, 122, 70, 255, 246, 122, 70, 255, 246, 121, 70, 255, 245, 121, 70, 255, 245, 120, 69, 255, 245, 120, 69, 255, 245, 119, 69, 255, 245, 119, 69, 255, 245, 118, 69, 255, 245, 117, 69, 255, 245, 117, 68, 255, 245, 116, 68, 255, 245, 116, 68, 255, 245, 115, 68, 255, 245, 115, 68, 255, 244, 114, 68, 255, 244, 114, 68, 255, 244, 113, 67, 255, 244, 113, 67, 255, 244, 112, 67, 255, 244, 111, 67, 255, 244, 111, 67, 255, 244, 110, 67, 255, 244, 110, 67, 255, 244, 109, 67, 255, 244, 109, 67, 255, 243, 108, 67, 255, 243, 108, 67, 255, 243, 107, 67, 255, 243, 107, 67, 255, 243, 107, 67, 255, 242, 106, 67, 255, 242, 106, 67, 255, 242, 106, 67, 255, 242, 105, 67, 255, 242, 105, 67, 255, 241, 104, 68, 255, 241, 104, 68, 255, 241, 104, 68, 255, 241, 103, 68, 255, 241, 103, 68, 255, 240, 102, 68, 255, 240, 102, 68, 255, 240, 102, 68, 255, 240, 101, 68, 255, 240, 101, 68, 255, 239, 101, 69, 255, 239, 100, 69, 255, 239, 100, 69, 255, 239, 99, 69, 255, 238, 99, 69, 255, 238, 99, 69, 255, 238, 98, 69, 255, 238, 98, 69, 255, 238, 98, 69, 255, 237, 97, 69, 255, 237, 97, 70, 255, 237, 96, 70, 255, 237, 96, 70, 255, 236, 96, 70, 255, 236, 95, 70, 255, 236, 95, 70, 255, 236, 95, 70, 255, 236, 94, 70, 255, 235, 94, 70, 255, 235, 94, 70, 255, 235, 93, 71, 255, 235, 93, 71, 255, 234, 92, 71, 255, 234, 92, 71, 255, 234, 92, 71, 255, 234, 91, 71, 255, 233, 91, 71, 255, 233, 91, 71, 255, 233, 90, 71, 255, 233, 90, 71, 255, 233, 89, 72, 255, 226, 80, 74, 255, 226, 79, 74, 255, 226, 79, 74, 255, 225, 79, 74, 255, 225, 78, 74, 255, 225, 78, 74, 255, 225, 77, 75, 255, 224, 77, 75, 255, 224, 77, 75, 255, 224, 76, 75, 255, 224, 76, 75, 255, 223, 76, 75, 255, 223, 75, 75, 255, 223, 75, 75, 255, 223, 75, 75, 255, 222, 74, 75, 255, 222, 74, 75, 255, 222, 73, 76, 255, 222, 73, 76, 255, 221, 73, 76, 255, 221, 72, 76, 255, 221, 72, 76, 255, 220, 72, 76, 255, 220, 71, 76, 255, 220, 71, 76, 255, 220, 71, 76, 255, 219, 70, 76, 255, 219, 70, 76, 255, 219, 70, 77, 255, 219, 69, 77, 255, 218, 69, 77, 255, 218, 68, 77, 255, 218, 68, 77, 255, 217, 68, 77, 255, 217, 67, 77, 255, 217, 67, 77, 255, 217, 67, 77, 255, 216, 66, 77, 255, 216, 66, 77, 255, 216, 66, 78, 255, 216, 65, 78, 255, 215, 65, 78, 255, 215, 65, 78, 255, 215, 64, 78, 255, 214, 64, 78, 255, 214, 63, 78, 255, 214, 63, 78, 255, 214, 63, 78, 255, 213, 62, 78, 255, 213, 62, 78, 255, 213, 62, 78, 255, 212, 61, 78, 255, 212, 61, 78, 255, 211, 61, 78, 255, 211, 60, 78, 255, 211, 60, 78, 255, 210, 59, 78, 255, 210, 59, 78, 255, 209, 59, 78, 255, 209, 58, 78, 255, 209, 58, 78, 255, 208, 57, 78, 255, 208, 57, 77, 255, 207, 57, 77, 255, 207, 56, 77, 255, 206, 56, 77, 255, 206, 56, 77, 255, 206, 55, 77, 255, 205, 55, 77, 255, 205, 54, 77, 255, 204, 54, 77, 255, 204, 54, 77, 255, 203, 53, 77, 255, 203, 53, 76, 255, 203, 52, 76, 255, 202, 52, 76, 255, 202, 52, 76, 255, 201, 51, 76, 255, 201, 51, 76, 255, 200, 50, 76, 255, 200, 50, 76, 255, 200, 50, 76, 255, 199, 49, 76, 255, 199, 49, 76, 255, 198, 48, 75, 255, 198, 48, 75, 255, 198, 48, 75, 255, 197, 47, 75, 255, 197, 47, 75, 255, 196, 46, 75, 255, 196, 46, 75, 255, 195, 46, 75, 255, 195, 45, 75, 255, 195, 45, 75, 255, 194, 44, 74, 255, 194, 44, 74, 255, 193, 43, 74, 255, 193, 43, 74, 255, 192, 43, 74, 255, 192, 42, 74, 255, 192, 42, 74, 255, 191, 41, 74, 255, 180, 29, 71, 255, 179, 28, 71, 255, 179, 28, 71, 255, 178, 27, 71, 255, 178, 27, 71, 255, 177, 27, 71, 255, 177, 26, 70, 255, 177, 26, 70, 255, 176, 25, 70, 255, 176, 25, 70, 255, 175, 24, 70, 255, 175, 24, 70, 255, 174, 23, 70, 255, 174, 23, 70, 255, 174, 23, 70, 255, 173, 22, 70, 255, 173, 22, 69, 255, 172, 21, 69, 255, 172, 21, 69, 255, 171, 20, 69, 255, 171, 20, 69, 255, 171, 19, 69, 255, 170, 19, 69, 255, 170, 18, 69, 255, 169, 18, 69, 255, 169, 17, 68, 255, 168, 17, 68, 255, 168, 16, 68, 255, 168, 16, 68, 255, 167, 15, 68, 255, 167, 14, 68, 255, 166, 14, 68, 255, 166, 13, 68, 255, 165, 13, 68, 255, 165, 12, 67, 255, 164, 12, 67, 255, 164, 11, 67, 255, 164, 10, 67, 255, 163, 10, 67, 255, 163, 9, 67, 255, 162, 8, 67, 255, 162, 7, 67, 255, 161, 7, 67, 255, 161, 6, 66, 255, 161, 5, 66, 255, 160, 5, 66, 255, 160, 4, 66, 255, 159, 3, 66, 255, 159, 2, 66, 255, 158, 2, 66, 255, 158, 1, 66, 255 +}; +static const heatmap_colorscheme_t mixed = { mixed_data, sizeof(mixed_data)/sizeof(mixed_data[0]/4) }; +const heatmap_colorscheme_t* heatmap_cs_Spectral_mixed = &mixed; + +static const unsigned char mixed_exp_data[] = { + 0, 0, 0, 0, 94, 79, 162, 0, 89, 84, 165, 24, 84, 89, 169, 49, 79, 93, 172, 74, 75, 98, 175, 99, 70, 102, 177, 124, 57, 118, 184, 149, 54, 122, 186, 174, 51, 126, 187, 199, 50, 129, 188, 224, 49, 133, 188, 249, 46, 137, 189, 255, 33, 142, 190, 255, 15, 147, 191, 255, 10, 151, 190, 255, 10, 155, 189, 255, 10, 159, 188, 255, 30, 175, 181, 255, 47, 179, 178, 255, 60, 182, 175, 255, 73, 186, 172, 255, 84, 189, 169, 255, 95, 192, 166, 255, 103, 194, 164, 255, 108, 196, 164, 255, 112, 198, 164, 255, 116, 200, 164, 255, 121, 201, 164, 255, 125, 203, 164, 255, 129, 205, 163, 255, 147, 212, 163, 255, 151, 213, 163, 255, 155, 215, 163, 255, 159, 216, 163, 255, 163, 218, 163, 255, 167, 219, 163, 255, 171, 221, 163, 255, 174, 222, 163, 255, 176, 224, 162, 255, 179, 225, 162, 255, 182, 227, 161, 255, 185, 228, 160, 255, 188, 229, 160, 255, 191, 231, 159, 255, 206, 237, 156, 255, 210, 238, 155, 255, 213, 239, 154, 255, 216, 240, 154, 255, 219, 241, 153, 255, 223, 242, 153, 255, 226, 243, 152, 255, 230, 244, 152, 255, 230, 244, 152, 255, 231, 244, 153, 255, 231, 244, 154, 255, 232, 244, 154, 255, 232, 244, 155, 255, 233, 244, 156, 255, 234, 244, 156, 255, 234, 244, 157, 255, 237, 243, 161, 255, 237, 243, 161, 255, 238, 243, 162, 255, 238, 243, 163, 255, 239, 243, 163, 255, 239, 243, 164, 255, 239, 243, 165, 255, 240, 243, 165, 255, 240, 243, 166, 255, 241, 242, 166, 255, 241, 241, 165, 255, 242, 241, 164, 255, 242, 240, 163, 255, 243, 239, 162, 255, 243, 238, 161, 255, 244, 238, 160, 255, 244, 237, 159, 255, 245, 236, 158, 255, 245, 236, 157, 255, 248, 231, 152, 255, 249, 230, 151, 255, 249, 230, 150, 255, 250, 229, 149, 255, 250, 228, 148, 255, 251, 227, 147, 255, 251, 227, 147, 255, 252, 226, 146, 255, 252, 225, 145, 255, 253, 225, 145, 255, 253, 224, 144, 255, 253, 223, 143, 255, 253, 221, 141, 255, 253, 220, 139, 255, 253, 218, 137, 255, 253, 217, 135, 255, 253, 215, 134, 255, 253, 213, 132, 255, 253, 212, 130, 255, 253, 210, 129, 255, 253, 209, 127, 255, 253, 207, 126, 255, 253, 206, 124, 255, 253, 204, 123, 255, 253, 193, 112, 255, 253, 191, 110, 255, 253, 190, 109, 255, 253, 188, 108, 255, 253, 187, 106, 255, 253, 185, 105, 255, 253, 184, 104, 255, 253, 182, 103, 255, 253, 181, 102, 255, 253, 179, 101, 255, 253, 178, 100, 255, 253, 177, 99, 255, 253, 175, 98, 255, 253, 174, 97, 255, 252, 172, 95, 255, 252, 171, 94, 255, 252, 169, 93, 255, 252, 167, 92, 255, 252, 166, 91, 255, 251, 164, 90, 255, 251, 163, 89, 255, 251, 161, 88, 255, 251, 160, 87, 255, 251, 158, 86, 255, 250, 157, 86, 255, 250, 155, 85, 255, 250, 154, 84, 255, 250, 152, 83, 255, 250, 151, 82, 255, 250, 149, 82, 255, 248, 135, 75, 255, 247, 134, 74, 255, 247, 132, 74, 255, 247, 131, 73, 255, 247, 129, 73, 255, 247, 128, 72, 255, 246, 127, 72, 255, 246, 125, 71, 255, 246, 124, 71, 255, 246, 122, 70, 255, 245, 121, 70, 255, 245, 120, 69, 255, 245, 118, 69, 255, 245, 117, 68, 255, 245, 116, 68, 255, 244, 114, 68, 255, 244, 113, 67, 255, 244, 111, 67, 255, 244, 110, 67, 255, 244, 109, 67, 255, 243, 108, 67, 255, 243, 107, 67, 255, 242, 106, 67, 255, 242, 105, 67, 255, 241, 104, 68, 255, 241, 103, 68, 255, 240, 103, 68, 255, 240, 102, 68, 255, 240, 101, 68, 255, 239, 100, 69, 255, 239, 99, 69, 255, 238, 99, 69, 255, 238, 98, 69, 255, 237, 97, 70, 255, 237, 96, 70, 255, 236, 96, 70, 255, 236, 95, 70, 255, 236, 94, 70, 255, 235, 93, 70, 255, 235, 93, 71, 255, 234, 92, 71, 255, 234, 91, 71, 255, 233, 91, 71, 255, 233, 90, 71, 255, 226, 80, 74, 255, 226, 79, 74, 255, 225, 79, 74, 255, 225, 78, 74, 255, 224, 77, 75, 255, 224, 77, 75, 255, 224, 76, 75, 255, 223, 75, 75, 255, 223, 75, 75, 255, 222, 74, 75, 255, 222, 74, 76, 255, 221, 73, 76, 255, 221, 72, 76, 255, 221, 72, 76, 255, 220, 71, 76, 255, 220, 71, 76, 255, 219, 70, 76, 255, 219, 70, 77, 255, 219, 69, 77, 255, 218, 69, 77, 255, 218, 68, 77, 255, 217, 68, 77, 255, 217, 67, 77, 255, 217, 67, 77, 255, 216, 66, 77, 255, 216, 66, 78, 255, 215, 65, 78, 255, 215, 65, 78, 255, 215, 64, 78, 255, 214, 64, 78, 255, 214, 63, 78, 255, 213, 63, 78, 255, 213, 62, 78, 255, 213, 62, 78, 255, 212, 61, 78, 255, 212, 61, 78, 255, 211, 60, 78, 255, 211, 60, 78, 255, 210, 59, 78, 255, 210, 59, 78, 255, 209, 58, 78, 255, 209, 58, 78, 255, 208, 58, 78, 255, 208, 57, 78, 255, 207, 57, 77, 255, 207, 56, 77, 255, 206, 56, 77, 255, 206, 55, 77, 255, 205, 55, 77, 255, 205, 55, 77, 255, 205, 54, 77, 255, 204, 54, 77, 255, 204, 53, 77, 255, 203, 53, 77, 255, 203, 53, 76, 255, 202, 52, 76, 255, 202, 52, 76, 255, 202, 51, 76, 255, 201, 51, 76, 255, 201, 51, 76, 255, 200, 50, 76, 255, 200, 50, 76, 255, 200, 49, 76, 255, 199, 49, 76, 255, 199, 49, 76, 255, 198, 48, 75, 255, 198, 48, 75, 255, 198, 48, 75, 255, 197, 47, 75, 255, 197, 47, 75, 255, 197, 47, 75, 255, 196, 46, 75, 255, 196, 46, 75, 255, 196, 46, 75, 255, 195, 45, 75, 255, 195, 45, 75, 255, 194, 45, 75, 255, 194, 44, 74, 255, 194, 44, 74, 255, 194, 44, 74, 255, 193, 43, 74, 255, 193, 43, 74, 255, 193, 43, 74, 255, 192, 43, 74, 255, 192, 42, 74, 255, 192, 42, 74, 255, 191, 42, 74, 255, 191, 41, 74, 255, 191, 41, 74, 255, 179, 29, 71, 255, 179, 28, 71, 255, 179, 28, 71, 255, 179, 28, 71, 255, 178, 27, 71, 255, 178, 27, 71, 255, 178, 27, 71, 255, 178, 27, 71, 255, 177, 26, 70, 255, 177, 26, 70, 255, 177, 26, 70, 255, 176, 26, 70, 255, 176, 25, 70, 255, 176, 25, 70, 255, 176, 25, 70, 255, 176, 25, 70, 255, 175, 24, 70, 255, 175, 24, 70, 255, 175, 24, 70, 255, 175, 24, 70, 255, 174, 23, 70, 255, 174, 23, 70, 255, 174, 23, 70, 255, 174, 23, 70, 255, 173, 22, 70, 255, 173, 22, 70, 255, 173, 22, 69, 255, 173, 22, 69, 255, 173, 21, 69, 255, 172, 21, 69, 255, 172, 21, 69, 255, 172, 21, 69, 255, 172, 21, 69, 255, 172, 20, 69, 255, 171, 20, 69, 255, 171, 20, 69, 255, 171, 20, 69, 255, 171, 20, 69, 255, 171, 19, 69, 255, 170, 19, 69, 255, 170, 19, 69, 255, 170, 19, 69, 255, 170, 19, 69, 255, 170, 18, 69, 255, 170, 18, 69, 255, 169, 18, 69, 255, 169, 18, 69, 255, 169, 18, 69, 255, 169, 17, 68, 255, 169, 17, 68, 255, 169, 17, 68, 255, 168, 17, 68, 255, 168, 17, 68, 255, 168, 16, 68, 255, 168, 16, 68, 255, 168, 16, 68, 255, 168, 16, 68, 255, 168, 16, 68, 255, 167, 16, 68, 255, 167, 15, 68, 255, 167, 15, 68, 255, 167, 15, 68, 255, 167, 15, 68, 255, 167, 15, 68, 255, 167, 14, 68, 255, 166, 14, 68, 255, 166, 14, 68, 255, 166, 14, 68, 255, 166, 14, 68, 255, 166, 14, 68, 255, 166, 13, 68, 255, 166, 13, 68, 255, 166, 13, 68, 255, 166, 13, 68, 255, 165, 13, 68, 255, 165, 13, 68, 255, 165, 13, 68, 255, 165, 12, 67, 255, 165, 12, 67, 255, 165, 12, 67, 255, 165, 12, 67, 255, 165, 12, 67, 255, 165, 12, 67, 255, 164, 11, 67, 255, 164, 11, 67, 255, 164, 11, 67, 255, 164, 11, 67, 255, 164, 11, 67, 255, 164, 11, 67, 255, 164, 11, 67, 255, 164, 10, 67, 255, 164, 10, 67, 255, 164, 10, 67, 255, 163, 10, 67, 255, 163, 10, 67, 255, 163, 10, 67, 255, 163, 10, 67, 255, 163, 10, 67, 255, 163, 9, 67, 255, 163, 9, 67, 255, 163, 9, 67, 255, 163, 9, 67, 255, 163, 9, 67, 255, 163, 9, 67, 255, 163, 9, 67, 255, 162, 8, 67, 255, 162, 8, 67, 255, 162, 8, 67, 255, 162, 8, 67, 255, 162, 8, 67, 255, 162, 8, 67, 255, 162, 8, 67, 255, 162, 8, 67, 255, 162, 8, 67, 255, 162, 7, 67, 255, 162, 7, 67, 255, 162, 7, 67, 255, 162, 7, 67, 255, 162, 7, 67, 255, 162, 7, 67, 255, 161, 7, 67, 255, 161, 7, 67, 255, 161, 7, 67, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 6, 66, 255, 161, 5, 66, 255, 161, 5, 66, 255, 161, 5, 66, 255, 161, 5, 66, 255, 160, 5, 66, 255, 160, 5, 66, 255, 160, 5, 66, 255, 160, 5, 66, 255, 160, 5, 66, 255, 160, 5, 66, 255, 160, 5, 66, 255, 160, 5, 66, 255, 160, 5, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 160, 4, 66, 255, 159, 4, 66, 255, 159, 4, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 3, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 159, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 2, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 158, 1, 66, 255, 157, 1, 66, 255, 157, 1, 66, 255, 157, 1, 66, 255, 157, 1, 66, 255, 157, 1, 66, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255, 157, 1, 65, 255 +}; +static const heatmap_colorscheme_t mixed_exp = { mixed_exp_data, sizeof(mixed_exp_data)/sizeof(mixed_exp_data[0]/4) }; +const heatmap_colorscheme_t* heatmap_cs_Spectral_mixed_exp = &mixed_exp; + +#ifdef __cplusplus +} +#endif diff --git a/src/3rdparty/colorschemes/Spectral.h b/src/3rdparty/colorschemes/Spectral.h index 5f62f49..1bf3d20 100644 --- a/src/3rdparty/colorschemes/Spectral.h +++ b/src/3rdparty/colorschemes/Spectral.h @@ -1,46 +1,46 @@ -/* heatmap - High performance heatmap creation in C. - * - * The MIT License (MIT) - * - * Copyright (c) 2013 Lucas Beyer - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#ifndef _HEATMAP_COLORSCHEMES_SPECTRAL_H -#define _HEATMAP_COLORSCHEMES_SPECTRAL_H - -#ifdef __cplusplus -extern "C" { -#endif - -/* This one has only N discrete colors. */ -extern const heatmap_colorscheme_t* heatmap_cs_Spectral_discrete; -/* This is a very soft gradient along abovementioned discrete colors. */ -extern const heatmap_colorscheme_t* heatmap_cs_Spectral_soft; -/* This is a mix of the above two. Makes for a pretty result in many cases. */ -extern const heatmap_colorscheme_t* heatmap_cs_Spectral_mixed; -/* An exponential version of the default mix of the above two. */ -/* Use this if your maximum is very "spiked". */ -extern const heatmap_colorscheme_t* heatmap_cs_Spectral_mixed_exp; - -#ifdef __cplusplus -} -#endif - -#endif /* _HEATMAP_COLORSCHEMES_SPECTRAL_H */ +/* heatmap - High performance heatmap creation in C. + * + * The MIT License (MIT) + * + * Copyright (c) 2013 Lucas Beyer + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _HEATMAP_COLORSCHEMES_SPECTRAL_H +#define _HEATMAP_COLORSCHEMES_SPECTRAL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* This one has only N discrete colors. */ +extern const heatmap_colorscheme_t* heatmap_cs_Spectral_discrete; +/* This is a very soft gradient along abovementioned discrete colors. */ +extern const heatmap_colorscheme_t* heatmap_cs_Spectral_soft; +/* This is a mix of the above two. Makes for a pretty result in many cases. */ +extern const heatmap_colorscheme_t* heatmap_cs_Spectral_mixed; +/* An exponential version of the default mix of the above two. */ +/* Use this if your maximum is very "spiked". */ +extern const heatmap_colorscheme_t* heatmap_cs_Spectral_mixed_exp; + +#ifdef __cplusplus +} +#endif + +#endif /* _HEATMAP_COLORSCHEMES_SPECTRAL_H */ diff --git a/src/3rdparty/heatmap.c b/src/3rdparty/heatmap.c index 96f5afc..32c2ba8 100644 --- a/src/3rdparty/heatmap.c +++ b/src/3rdparty/heatmap.c @@ -1,326 +1,326 @@ -/* heatmap - High performance heatmap creation in C. - * - * The MIT License (MIT) - * - * Copyright (c) 2013 Lucas Beyer - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#include "heatmap.h" - -#include -#include /* malloc, calloc, free */ -#include /* memcpy, memset */ -#include /* sqrtf */ -#include /* assert, #define NDEBUG to ignore. */ - -/* Having a default stamp ready makes it easier for simple usage of the library - * since there is no need to create a new stamp. - */ -static float stamp_default_4_data[] = { - 0.0f , 0.0f , 0.1055728f, 0.1753789f, 0.2f, 0.1753789f, 0.1055728f, 0.0f , 0.0f , - 0.0f , 0.1514719f, 0.2788897f, 0.3675445f, 0.4f, 0.3675445f, 0.2788897f, 0.1514719f, 0.0f , - 0.1055728f, 0.2788897f, 0.4343146f, 0.5527864f, 0.6f, 0.5527864f, 0.4343146f, 0.2788897f, 0.1055728f, - 0.1753789f, 0.3675445f, 0.5527864f, 0.7171573f, 0.8f, 0.7171573f, 0.5527864f, 0.3675445f, 0.1753789f, - 0.2f , 0.4f , 0.6f , 0.8f , 1.0f, 0.8f , 0.6f , 0.4f , 0.2f , - 0.1753789f, 0.3675445f, 0.5527864f, 0.7171573f, 0.8f, 0.7171573f, 0.5527864f, 0.3675445f, 0.1753789f, - 0.1055728f, 0.2788897f, 0.4343146f, 0.5527864f, 0.6f, 0.5527864f, 0.4343146f, 0.2788897f, 0.1055728f, - 0.0f , 0.1514719f, 0.2788897f, 0.3675445f, 0.4f, 0.3675445f, 0.2788897f, 0.1514719f, 0.0f , - 0.0f , 0.0f , 0.1055728f, 0.1753789f, 0.2f, 0.1753789f, 0.1055728f, 0.0f , 0.0f , -}; - -static heatmap_stamp_t stamp_default_4 = { - stamp_default_4_data, 9, 9 -}; - -void heatmap_init(heatmap_t* hm, unsigned w, unsigned h) -{ - memset(hm, 0, sizeof(heatmap_t)); - hm->buf = (float*)calloc(w*h, sizeof(float)); - hm->w = w; - hm->h = h; -} - -heatmap_t* heatmap_new(unsigned w, unsigned h) -{ - heatmap_t* hm = (heatmap_t*)malloc(sizeof(heatmap_t)); - heatmap_init(hm, w, h); - return hm; -} - -void heatmap_free(heatmap_t* h) -{ - free(h->buf); - free(h); -} - -void heatmap_add_point(heatmap_t* h, unsigned x, unsigned y) -{ - heatmap_add_point_with_stamp(h, x, y, &stamp_default_4); -} - -void heatmap_add_point_with_stamp(heatmap_t* h, unsigned x, unsigned y, const heatmap_stamp_t* stamp) -{ - /* I'm still unsure whether we want this to be an assert or not... */ - if(x >= h->w || y >= h->h) - return; - - /* I hate you, C */ - { - /* Note: the order of operations is important, since we're computing with unsigned! */ - - /* These are [first, last) pairs in the STAMP's pixels. */ - const unsigned x0 = x < stamp->w/2 ? (stamp->w/2 - x) : 0; - const unsigned y0 = y < stamp->h/2 ? (stamp->h/2 - y) : 0; - const unsigned x1 = (x + stamp->w/2) < h->w ? stamp->w : stamp->w/2 + (h->w - x); - const unsigned y1 = (y + stamp->h/2) < h->h ? stamp->h : stamp->h/2 + (h->h - y); - - unsigned iy; - - for(iy = y0 ; iy < y1 ; ++iy) { - /* TODO: could it be clearer by using separate vars and computing a ystep? */ - float* line = h->buf + ((y + iy) - stamp->h/2)*h->w + (x + x0) - stamp->w/2; - const float* stampline = stamp->buf + iy*stamp->w + x0; - - unsigned ix; - for(ix = x0 ; ix < x1 ; ++ix, ++line, ++stampline) { - /* TODO: Let's actually accept negatives and try out funky stamps. */ - /* Note that that might mess with the max though. */ - /* And that we'll have to clamp the bottom to 0 when rendering. */ - assert(*stampline >= 0.0f); - - *line += *stampline; - if(*line > h->max) {h->max = *line;} - - assert(*line >= 0.0f); - } - } - } /* I hate you very much! */ -} - -void heatmap_add_weighted_point(heatmap_t* h, unsigned x, unsigned y, float w) -{ - heatmap_add_weighted_point_with_stamp(h, x, y, w, &stamp_default_4); -} - -/* Initial timings do show a difference large enough (~10% slower without FMA) - * that we do care about splitting the implementation, - * even though JUST A SINGLE LINE OF CODE has changed! - * And I don't want to spoil the readability by using macro-trickery to avoid duplication. - * sad :-( - */ -void heatmap_add_weighted_point_with_stamp(heatmap_t* h, unsigned x, unsigned y, float w, const heatmap_stamp_t* stamp) -{ - /* I'm still unsure whether we want this to be an assert or not... */ - if(x >= h->w || y >= h->h) - return; - - /* Currently, negative weights are not supported as they mess with the max. */ - assert(w >= 0.0f); - - /* I hate you, C */ - { - /* Note: the order of operations is important, since we're computing with unsigned! */ - - /* These are [first, last) pairs in the STAMP's pixels. */ - const unsigned x0 = x < stamp->w/2 ? (stamp->w/2 - x) : 0; - const unsigned y0 = y < stamp->h/2 ? (stamp->h/2 - y) : 0; - const unsigned x1 = (x + stamp->w/2) < h->w ? stamp->w : stamp->w/2 + (h->w - x); - const unsigned y1 = (y + stamp->h/2) < h->h ? stamp->h : stamp->h/2 + (h->h - y); - - unsigned iy; - - for(iy = y0 ; iy < y1 ; ++iy) { - /* TODO: could it be clearer by using separate vars and computing a ystep? */ - float* line = h->buf + ((y + iy) - stamp->h/2)*h->w + (x + x0) - stamp->w/2; - const float* stampline = stamp->buf + iy*stamp->w + x0; - - unsigned ix; - for(ix = x0 ; ix < x1 ; ++ix, ++line, ++stampline) { - /* TODO: see unweighted function */ - assert(*stampline >= 0.0f); - - *line += *stampline * w; - if(*line > h->max) {h->max = *line;} - - assert(*line >= 0.0f); - } - } - } /* I hate you very much! */ -} - -unsigned char* heatmap_render_default_to(const heatmap_t* h, unsigned char* colorbuf) -{ - return heatmap_render_to(h, heatmap_cs_default, colorbuf); -} - -unsigned char* heatmap_render_to(const heatmap_t* h, const heatmap_colorscheme_t* colorscheme, unsigned char* colorbuf) -{ - /* TODO: Time whether it makes a noticeable difference to inline that code - * here and drop the saturation step. - */ - /* If the heatmap is empty, h->max (and thus the saturation value) is 0.0, resulting in a 0-by-0 division. - * In that case, we should set the saturation to anything but 0, since we want the result of the division to be 0. - * Also, a comparison to exact 0.0f (as opposed to 1e-14) is OK, since we only do division. - */ - return heatmap_render_saturated_to(h, colorscheme, h->max > 0.0f ? h->max : 1.0f, colorbuf); -} - -unsigned char* heatmap_render_saturated_to(const heatmap_t* h, const heatmap_colorscheme_t* colorscheme, float saturation, unsigned char* colorbuf) -{ - unsigned y; - assert(saturation > 0.0f); - - /* For convenience, if no buffer is given, malloc a new one. */ - if(!colorbuf) { - colorbuf = (unsigned char*)malloc(h->w*h->h*4); - if(!colorbuf) { - return 0; - } - } - - /* TODO: could actually even flatten this loop before parallelizing it. */ - /* I.e., to go i = 0 ; i < h*w since I don't have any padding! (yet?) */ - for(y = 0 ; y < h->h ; ++y) { - float* bufline = h->buf + y*h->w; - unsigned char* colorline = colorbuf + 4*y*h->w; - - unsigned x; - for(x = 0 ; x < h->w ; ++x) { - /* Saturate the heat value to the given saturation, and then - * normalize by that. - */ - const float val = (*bufline > saturation ? saturation : *bufline)/saturation; - - /* We add 0.5 in order to do real rounding, not just dropping the - * decimal part. That way we are certain the highest value in the - * colorscheme is actually used. - */ - const size_t idx = (size_t)((float)(colorscheme->ncolors-1)*val + 0.5f); - - // dont copy zero values - if (*(colorscheme->colors + idx*4 + 3) > 0) { - /* Just copy over the color from the colorscheme. */ - memcpy(colorline, colorscheme->colors + idx*4, 4); - } - colorline += 4; - - ++bufline; - } - } - - return colorbuf; -} - -void heatmap_stamp_init(heatmap_stamp_t* stamp, unsigned w, unsigned h, float* data) -{ - if(stamp) { - memset(stamp, 0, sizeof(heatmap_stamp_t)); - stamp->w = w; - stamp->h = h; - stamp->buf = data; - } -} - -heatmap_stamp_t* heatmap_stamp_new_with(unsigned w, unsigned h, float* data) -{ - heatmap_stamp_t* stamp = (heatmap_stamp_t*)malloc(sizeof(heatmap_stamp_t)); - heatmap_stamp_init(stamp, w, h, data); - return stamp; -} - -heatmap_stamp_t* heatmap_stamp_load(unsigned w, unsigned h, float* data) -{ - float* copy = (float*)malloc(sizeof(float)*w*h); - memcpy(copy, data, sizeof(float)*w*h); - return heatmap_stamp_new_with(w, h, copy); -} - -static float linear_dist(float dist) -{ - return dist; -} - -heatmap_stamp_t* heatmap_stamp_gen(unsigned r) -{ - return heatmap_stamp_gen_nonlinear(r, linear_dist); -} - -heatmap_stamp_t* heatmap_stamp_gen_nonlinear(unsigned r, float (*distshape)(float)) -{ - unsigned y; - unsigned d = 2*r+1; - - float* stamp = (float*)calloc(d*d, sizeof(float)); - if(!stamp) - return 0; - - for(y = 0 ; y < d ; ++y) { - float* line = stamp + y*d; - unsigned x; - for(x = 0 ; x < d ; ++x, ++line) { - const float dist = sqrtf((float)((x-r)*(x-r) + (y-r)*(y-r)))/(float)(r+1); - const float ds = (*distshape)(dist); - /* This doesn't generate optimal assembly, but meh, it's readable. */ - const float clamped_ds = ds > 1.0f ? 1.0f - : ds < 0.0f ? 0.0f - : ds; - *line = 1.0f - clamped_ds; - } - } - - return heatmap_stamp_new_with(d, d, stamp); -} - -void heatmap_stamp_free(heatmap_stamp_t* s) -{ - free(s->buf); - free(s); -} - -heatmap_colorscheme_t* heatmap_colorscheme_load(const unsigned char* in_colors, size_t ncolors) -{ - heatmap_colorscheme_t* cs = (heatmap_colorscheme_t*)calloc(1, sizeof(heatmap_colorscheme_t)); - unsigned char* colors = (unsigned char*)malloc(4*ncolors); - - if(!cs || !colors) { - free(cs); - free(colors); - return 0; - } - - memcpy(colors, in_colors, 4*ncolors); - - cs->colors = colors; - cs->ncolors = ncolors; - return cs; -} - -void heatmap_colorscheme_free(heatmap_colorscheme_t* cs) -{ - /* ehhh, const_cast<>! */ - free((void*)cs->colors); - free(cs); -} - -/* Sorry dynamic wordwarp editor users! But you deserve no better anyways... */ -static const unsigned char mixed_data[] = {0, 0, 0, 0, 94, 79, 162, 0, 93, 79, 162, 7, 93, 80, 162, 14, 92, 80, 163, 22, 92, 81, 163, 29, 91, 81, 164, 37, 91, 82, 164, 44, 90, 82, 164, 52, 90, 83, 165, 59, 89, 83, 165, 67, 89, 84, 166, 74, 88, 84, 166, 82, 88, 85, 166, 89, 87, 85, 167, 97, 87, 86, 167, 104, 86, 86, 167, 112, 86, 87, 168, 119, 85, 87, 168, 127, 85, 88, 168, 134, 84, 88, 169, 141, 84, 89, 169, 149, 83, 89, 169, 156, 83, 90, 170, 164, 83, 90, 170, 171, 82, 91, 170, 179, 82, 91, 171, 186, 81, 92, 171, 194, 81, 92, 171, 201, 80, 93, 172, 209, 80, 93, 172, 216, 79, 94, 172, 224, 79, 94, 172, 231, 78, 95, 173, 239, 78, 95, 173, 246, 77, 95, 173, 254, 77, 96, 174, 255, 76, 96, 174, 255, 76, 97, 174, 255, 75, 97, 174, 255, 75, 98, 175, 255, 74, 98, 175, 255, 74, 99, 175, 255, 73, 99, 175, 255, 73, 100, 176, 255, 72, 100, 176, 255, 72, 101, 176, 255, 72, 101, 176, 255, 71, 101, 176, 255, 71, 102, 177, 255, 70, 102, 177, 255, 70, 103, 177, 255, 69, 103, 177, 255, 60, 115, 183, 255, 60, 115, 183, 255, 59, 116, 183, 255, 59, 116, 183, 255, 58, 117, 184, 255, 58, 117, 184, 255, 58, 118, 184, 255, 57, 118, 184, 255, 57, 118, 184, 255, 56, 119, 184, 255, 56, 119, 185, 255, 56, 120, 185, 255, 55, 120, 185, 255, 55, 121, 185, 255, 55, 121, 185, 255, 54, 121, 185, 255, 54, 122, 186, 255, 54, 122, 186, 255, 53, 123, 186, 255, 53, 123, 186, 255, 53, 124, 186, 255, 52, 124, 186, 255, 52, 124, 186, 255, 52, 125, 186, 255, 52, 125, 187, 255, 51, 126, 187, 255, 51, 126, 187, 255, 51, 126, 187, 255, 51, 127, 187, 255, 50, 127, 187, 255, 50, 128, 187, 255, 50, 128, 187, 255, 50, 128, 187, 255, 50, 129, 187, 255, 50, 129, 188, 255, 50, 130, 188, 255, 49, 130, 188, 255, 49, 130, 188, 255, 49, 131, 188, 255, 49, 131, 188, 255, 49, 132, 188, 255, 49, 132, 188, 255, 49, 132, 188, 255, 49, 133, 188, 255, 49, 133, 188, 255, 49, 133, 188, 255, 49, 134, 188, 255, 49, 134, 188, 255, 49, 135, 188, 255, 49, 135, 188, 255, 49, 135, 188, 255, 49, 136, 189, 255, 47, 136, 189, 255, 46, 137, 189, 255, 45, 138, 189, 255, 43, 138, 189, 255, 42, 139, 190, 255, 41, 139, 190, 255, 39, 140, 190, 255, 38, 140, 190, 255, 36, 141, 190, 255, 35, 141, 190, 255, 33, 142, 190, 255, 31, 142, 190, 255, 30, 143, 190, 255, 28, 143, 190, 255, 26, 144, 191, 255, 24, 145, 191, 255, 22, 145, 191, 255, 20, 146, 191, 255, 17, 146, 191, 255, 15, 147, 191, 255, 12, 147, 191, 255, 10, 148, 191, 255, 10, 148, 191, 255, 10, 149, 191, 255, 10, 149, 191, 255, 10, 150, 191, 255, 10, 150, 190, 255, 10, 151, 190, 255, 10, 151, 190, 255, 10, 152, 190, 255, 10, 152, 190, 255, 10, 153, 190, 255, 10, 153, 190, 255, 10, 154, 190, 255, 10, 154, 190, 255, 10, 155, 190, 255, 10, 155, 189, 255, 10, 156, 189, 255, 10, 156, 189, 255, 10, 157, 189, 255, 10, 157, 189, 255, 10, 158, 189, 255, 10, 158, 188, 255, 10, 158, 188, 255, 10, 159, 188, 255, 10, 159, 188, 255, 10, 160, 188, 255, 10, 160, 187, 255, 10, 161, 187, 255, 10, 161, 187, 255, 20, 173, 182, 255, 22, 174, 182, 255, 25, 174, 181, 255, 28, 175, 181, 255, 30, 175, 181, 255, 33, 176, 180, 255, 35, 176, 180, 255, 37, 176, 180, 255, 39, 177, 180, 255, 41, 177, 179, 255, 43, 178, 179, 255, 45, 178, 179, 255, 46, 179, 178, 255, 48, 179, 178, 255, 50, 179, 178, 255, 51, 180, 177, 255, 53, 180, 177, 255, 54, 181, 177, 255, 56, 181, 176, 255, 58, 182, 176, 255, 59, 182, 176, 255, 61, 182, 175, 255, 62, 183, 175, 255, 64, 183, 175, 255, 65, 184, 174, 255, 66, 184, 174, 255, 68, 184, 174, 255, 69, 185, 173, 255, 71, 185, 173, 255, 72, 186, 173, 255, 74, 186, 172, 255, 75, 186, 172, 255, 76, 187, 171, 255, 78, 187, 171, 255, 79, 187, 171, 255, 80, 188, 170, 255, 82, 188, 170, 255, 83, 189, 170, 255, 85, 189, 169, 255, 86, 189, 169, 255, 87, 190, 169, 255, 89, 190, 168, 255, 90, 190, 168, 255, 91, 191, 167, 255, 93, 191, 167, 255, 94, 191, 167, 255, 95, 192, 166, 255, 97, 192, 166, 255, 98, 193, 166, 255, 99, 193, 165, 255, 100, 193, 165, 255, 102, 194, 164, 255, 102, 194, 164, 255, 103, 194, 164, 255, 103, 194, 164, 255, 104, 194, 164, 255, 104, 195, 164, 255, 105, 195, 164, 255, 105, 195, 164, 255, 106, 195, 164, 255, 106, 196, 164, 255, 107, 196, 164, 255, 108, 196, 164, 255, 108, 196, 164, 255, 109, 196, 164, 255, 109, 197, 164, 255, 110, 197, 164, 255, 110, 197, 164, 255, 111, 197, 164, 255, 111, 198, 164, 255, 112, 198, 164, 255, 112, 198, 164, 255, 113, 198, 164, 255, 113, 198, 164, 255, 114, 199, 164, 255, 115, 199, 164, 255, 115, 199, 164, 255, 116, 199, 164, 255, 116, 200, 164, 255, 117, 200, 164, 255, 117, 200, 164, 255, 118, 200, 164, 255, 118, 200, 164, 255, 119, 201, 164, 255, 119, 201, 164, 255, 120, 201, 164, 255, 120, 201, 164, 255, 121, 201, 164, 255, 122, 202, 164, 255, 122, 202, 164, 255, 123, 202, 164, 255, 123, 202, 164, 255, 124, 203, 164, 255, 124, 203, 164, 255, 125, 203, 164, 255, 125, 203, 164, 255, 126, 203, 164, 255, 126, 204, 164, 255, 127, 204, 164, 255, 127, 204, 163, 255, 128, 204, 163, 255, 129, 204, 163, 255, 143, 210, 163, 255, 143, 210, 163, 255, 144, 210, 163, 255, 144, 211, 163, 255, 145, 211, 163, 255, 146, 211, 163, 255, 146, 211, 163, 255, 147, 212, 163, 255, 147, 212, 163, 255, 148, 212, 163, 255, 148, 212, 163, 255, 149, 212, 163, 255, 149, 213, 163, 255, 150, 213, 163, 255, 150, 213, 163, 255, 151, 213, 163, 255, 151, 213, 163, 255, 152, 214, 163, 255, 153, 214, 163, 255, 153, 214, 163, 255, 154, 214, 163, 255, 154, 214, 163, 255, 155, 215, 163, 255, 155, 215, 163, 255, 156, 215, 163, 255, 156, 215, 163, 255, 157, 215, 163, 255, 157, 216, 163, 255, 158, 216, 163, 255, 158, 216, 163, 255, 159, 216, 163, 255, 160, 216, 163, 255, 160, 217, 163, 255, 161, 217, 163, 255, 161, 217, 163, 255, 162, 217, 163, 255, 162, 217, 163, 255, 163, 218, 163, 255, 163, 218, 163, 255, 164, 218, 163, 255, 164, 218, 163, 255, 165, 218, 163, 255, 166, 219, 163, 255, 166, 219, 163, 255, 167, 219, 163, 255, 167, 219, 163, 255, 168, 219, 163, 255, 168, 220, 163, 255, 169, 220, 163, 255, 169, 220, 163, 255, 170, 220, 163, 255, 170, 220, 163, 255, 171, 221, 163, 255, 171, 221, 163, 255, 172, 221, 163, 255, 172, 221, 163, 255, 172, 222, 163, 255, 173, 222, 163, 255, 173, 222, 163, 255, 173, 222, 163, 255, 174, 222, 163, 255, 174, 223, 163, 255, 175, 223, 163, 255, 175, 223, 163, 255, 175, 223, 162, 255, 176, 223, 162, 255, 176, 224, 162, 255, 177, 224, 162, 255, 177, 224, 162, 255, 177, 224, 162, 255, 178, 224, 162, 255, 178, 225, 162, 255, 179, 225, 162, 255, 179, 225, 162, 255, 179, 225, 162, 255, 180, 225, 161, 255, 180, 226, 161, 255, 181, 226, 161, 255, 181, 226, 161, 255, 182, 226, 161, 255, 182, 226, 161, 255, 182, 227, 161, 255, 183, 227, 161, 255, 183, 227, 161, 255, 184, 227, 161, 255, 184, 227, 160, 255, 185, 228, 160, 255, 185, 228, 160, 255, 185, 228, 160, 255, 186, 228, 160, 255, 186, 228, 160, 255, 187, 229, 160, 255, 187, 229, 160, 255, 188, 229, 160, 255, 188, 229, 160, 255, 189, 229, 159, 255, 189, 230, 159, 255, 189, 230, 159, 255, 190, 230, 159, 255, 190, 230, 159, 255, 191, 230, 159, 255, 191, 231, 159, 255, 192, 231, 159, 255, 204, 236, 156, 255, 205, 236, 156, 255, 205, 236, 156, 255, 205, 236, 156, 255, 206, 236, 156, 255, 206, 237, 156, 255, 207, 237, 156, 255, 207, 237, 156, 255, 208, 237, 156, 255, 208, 237, 155, 255, 209, 238, 155, 255, 209, 238, 155, 255, 210, 238, 155, 255, 210, 238, 155, 255, 211, 238, 155, 255, 211, 238, 155, 255, 212, 239, 155, 255, 212, 239, 155, 255, 213, 239, 155, 255, 213, 239, 154, 255, 214, 239, 154, 255, 214, 240, 154, 255, 215, 240, 154, 255, 215, 240, 154, 255, 216, 240, 154, 255, 216, 240, 154, 255, 217, 240, 154, 255, 217, 241, 154, 255, 218, 241, 154, 255, 218, 241, 153, 255, 219, 241, 153, 255, 219, 241, 153, 255, 220, 241, 153, 255, 220, 242, 153, 255, 221, 242, 153, 255, 221, 242, 153, 255, 222, 242, 153, 255, 222, 242, 153, 255, 223, 242, 153, 255, 223, 243, 153, 255, 224, 243, 152, 255, 224, 243, 152, 255, 225, 243, 152, 255, 225, 243, 152, 255, 226, 243, 152, 255, 227, 244, 152, 255, 227, 244, 152, 255, 228, 244, 152, 255, 228, 244, 152, 255, 229, 244, 152, 255, 229, 244, 152, 255, 230, 244, 151, 255, 230, 244, 151, 255, 230, 244, 151, 255, 230, 244, 151, 255, 230, 244, 151, 255, 230, 244, 151, 255, 230, 244, 151, 255, 230, 244, 151, 255, 230, 244, 151, 255, 231, 244, 151, 255, 231, 244, 151, 255, 231, 244, 151, 255, 231, 243, 151, 255, 231, 243, 151, 255, 231, 243, 151, 255, 231, 243, 151, 255, 231, 243, 150, 255, 231, 243, 150, 255, 232, 243, 150, 255, 232, 243, 150, 255, 232, 243, 150, 255, 232, 243, 150, 255, 232, 243, 150, 255, 232, 243, 150, 255, 232, 243, 150, 255, 232, 242, 150, 255, 232, 242, 150, 255, 233, 242, 150, 255, 233, 242, 150, 255, 233, 242, 150, 255, 233, 242, 150, 255, 233, 242, 150, 255, 233, 242, 150, 255, 233, 242, 150, 255, 233, 242, 149, 255, 233, 242, 149, 255, 234, 242, 149, 255, 234, 241, 149, 255, 234, 241, 149, 255, 234, 241, 149, 255, 234, 241, 149, 255, 234, 241, 149, 255, 234, 241, 149, 255, 234, 241, 149, 255, 234, 241, 149, 255, 235, 241, 149, 255, 235, 241, 149, 255, 235, 241, 149, 255, 235, 241, 149, 255, 235, 240, 149, 255, 235, 240, 149, 255, 235, 240, 149, 255, 235, 240, 149, 255, 235, 240, 149, 255, 236, 240, 148, 255, 236, 240, 148, 255, 236, 240, 148, 255, 236, 240, 148, 255, 236, 240, 148, 255, 236, 240, 148, 255, 236, 240, 148, 255, 236, 239, 148, 255, 236, 239, 148, 255, 236, 239, 148, 255, 237, 239, 148, 255, 237, 239, 148, 255, 237, 239, 148, 255, 237, 239, 148, 255, 237, 239, 148, 255, 237, 239, 148, 255, 237, 239, 148, 255, 237, 239, 148, 255, 237, 239, 148, 255, 237, 238, 148, 255, 238, 238, 148, 255, 238, 238, 148, 255, 238, 238, 148, 255, 238, 238, 148, 255, 238, 238, 147, 255, 238, 238, 147, 255, 238, 238, 147, 255, 238, 238, 147, 255, 238, 238, 147, 255, 238, 238, 147, 255, 239, 238, 147, 255, 239, 237, 147, 255, 239, 237, 147, 255, 239, 237, 147, 255, 239, 237, 147, 255, 239, 237, 147, 255, 239, 237, 147, 255, 239, 237, 147, 255, 239, 237, 147, 255, 239, 237, 147, 255, 240, 237, 147, 255, 240, 237, 147, 255, 240, 237, 147, 255, 240, 236, 147, 255, 240, 236, 147, 255, 240, 236, 147, 255, 240, 236, 147, 255, 240, 236, 147, 255, 245, 232, 145, 255, 245, 232, 145, 255, 245, 232, 145, 255, 245, 232, 145, 255, 245, 232, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 247, 231, 145, 255, 247, 230, 145, 255, 247, 230, 145, 255, 247, 230, 145, 255, 247, 230, 145, 255, 247, 230, 144, 255, 247, 230, 144, 255, 247, 230, 144, 255, 247, 230, 144, 255, 247, 230, 144, 255, 247, 230, 144, 255, 248, 230, 144, 255, 248, 230, 144, 255, 248, 229, 144, 255, 248, 229, 144, 255, 248, 229, 144, 255, 248, 229, 144, 255, 248, 229, 144, 255, 248, 229, 144, 255, 248, 229, 144, 255, 248, 229, 144, 255, 248, 229, 144, 255, 248, 229, 144, 255, 249, 229, 144, 255, 249, 229, 144, 255, 249, 229, 144, 255, 249, 228, 144, 255, 249, 228, 144, 255, 249, 228, 144, 255, 249, 228, 144, 255, 249, 228, 144, 255, 249, 228, 144, 255, 249, 228, 144, 255, 249, 228, 144, 255, 249, 228, 144, 255, 250, 228, 144, 255, 250, 228, 144, 255, 250, 228, 144, 255, 250, 227, 144, 255, 250, 227, 144, 255, 250, 227, 144, 255, 250, 227, 144, 255, 250, 227, 144, 255, 250, 227, 144, 255, 250, 227, 144, 255, 250, 227, 144, 255, 250, 227, 144, 255, 251, 227, 144, 255, 251, 227, 144, 255, 251, 227, 144, 255, 251, 226, 144, 255, 251, 226, 144, 255, 251, 226, 144, 255, 251, 226, 144, 255, 251, 226, 144, 255, 251, 226, 144, 255, 251, 226, 144, 255, 251, 226, 144, 255, 251, 226, 144, 255, 251, 226, 144, 255, 252, 226, 144, 255, 252, 226, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 253, 225, 144, 255, 253, 225, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 223, 143, 255, 253, 223, 143, 255, 253, 223, 142, 255, 253, 222, 142, 255, 253, 222, 141, 255, 253, 221, 141, 255, 253, 221, 141, 255, 253, 221, 140, 255, 253, 220, 140, 255, 253, 220, 139, 255, 253, 220, 139, 255, 253, 219, 138, 255, 253, 219, 138, 255, 253, 218, 138, 255, 253, 218, 137, 255, 253, 218, 137, 255, 253, 217, 136, 255, 253, 217, 136, 255, 253, 217, 135, 255, 253, 216, 135, 255, 253, 216, 135, 255, 253, 215, 134, 255, 253, 215, 134, 255, 253, 215, 133, 255, 253, 214, 133, 255, 253, 214, 133, 255, 253, 214, 132, 255, 253, 213, 132, 255, 253, 213, 131, 255, 253, 212, 131, 255, 253, 212, 131, 255, 253, 212, 130, 255, 253, 211, 130, 255, 253, 211, 129, 255, 253, 211, 129, 255, 253, 210, 129, 255, 253, 210, 128, 255, 253, 209, 128, 255, 253, 209, 127, 255, 253, 209, 127, 255, 253, 208, 127, 255, 253, 208, 126, 255, 253, 207, 126, 255, 253, 207, 125, 255, 253, 207, 125, 255, 253, 206, 125, 255, 253, 206, 124, 255, 253, 205, 124, 255, 253, 205, 123, 255, 253, 205, 123, 255, 253, 204, 123, 255, 253, 194, 113, 255, 253, 194, 113, 255, 253, 193, 112, 255, 253, 193, 112, 255, 253, 192, 112, 255, 253, 192, 111, 255, 253, 192, 111, 255, 253, 191, 110, 255, 253, 191, 110, 255, 253, 190, 110, 255, 253, 190, 109, 255, 253, 190, 109, 255, 253, 189, 109, 255, 253, 189, 108, 255, 253, 188, 108, 255, 253, 188, 108, 255, 253, 188, 107, 255, 253, 187, 107, 255, 253, 187, 107, 255, 253, 186, 106, 255, 253, 186, 106, 255, 253, 186, 106, 255, 253, 185, 105, 255, 253, 185, 105, 255, 253, 184, 105, 255, 253, 184, 104, 255, 253, 184, 104, 255, 253, 183, 104, 255, 253, 183, 103, 255, 253, 182, 103, 255, 253, 182, 103, 255, 253, 182, 102, 255, 253, 181, 102, 255, 253, 181, 102, 255, 253, 180, 101, 255, 253, 180, 101, 255, 253, 180, 101, 255, 253, 179, 100, 255, 253, 179, 100, 255, 253, 178, 100, 255, 253, 178, 100, 255, 253, 178, 99, 255, 253, 177, 99, 255, 253, 177, 99, 255, 253, 176, 98, 255, 253, 176, 98, 255, 253, 175, 98, 255, 253, 175, 98, 255, 253, 175, 97, 255, 253, 174, 97, 255, 253, 174, 97, 255, 252, 173, 96, 255, 252, 173, 96, 255, 252, 172, 96, 255, 252, 172, 95, 255, 252, 172, 95, 255, 252, 171, 95, 255, 252, 171, 94, 255, 252, 170, 94, 255, 252, 170, 94, 255, 252, 169, 93, 255, 252, 169, 93, 255, 252, 168, 93, 255, 252, 168, 92, 255, 252, 167, 92, 255, 252, 167, 92, 255, 252, 166, 91, 255, 252, 166, 91, 255, 252, 165, 91, 255, 251, 165, 90, 255, 251, 164, 90, 255, 251, 164, 90, 255, 251, 163, 89, 255, 251, 163, 89, 255, 251, 162, 89, 255, 251, 162, 89, 255, 251, 162, 88, 255, 251, 161, 88, 255, 251, 161, 88, 255, 251, 160, 87, 255, 251, 160, 87, 255, 251, 159, 87, 255, 251, 159, 87, 255, 251, 158, 86, 255, 251, 158, 86, 255, 251, 157, 86, 255, 250, 157, 85, 255, 250, 156, 85, 255, 250, 156, 85, 255, 250, 155, 85, 255, 250, 155, 84, 255, 250, 154, 84, 255, 250, 154, 84, 255, 250, 153, 84, 255, 250, 153, 83, 255, 250, 152, 83, 255, 250, 152, 83, 255, 250, 151, 83, 255, 250, 151, 82, 255, 250, 150, 82, 255, 250, 150, 82, 255, 249, 149, 82, 255, 248, 136, 75, 255, 248, 135, 75, 255, 247, 135, 75, 255, 247, 134, 75, 255, 247, 134, 74, 255, 247, 133, 74, 255, 247, 133, 74, 255, 247, 132, 74, 255, 247, 132, 74, 255, 247, 131, 73, 255, 247, 131, 73, 255, 247, 130, 73, 255, 247, 130, 73, 255, 247, 129, 72, 255, 247, 129, 72, 255, 247, 128, 72, 255, 246, 128, 72, 255, 246, 127, 72, 255, 246, 126, 71, 255, 246, 126, 71, 255, 246, 125, 71, 255, 246, 125, 71, 255, 246, 124, 71, 255, 246, 124, 71, 255, 246, 123, 70, 255, 246, 123, 70, 255, 246, 122, 70, 255, 246, 122, 70, 255, 246, 121, 70, 255, 245, 121, 70, 255, 245, 120, 69, 255, 245, 120, 69, 255, 245, 119, 69, 255, 245, 119, 69, 255, 245, 118, 69, 255, 245, 117, 69, 255, 245, 117, 68, 255, 245, 116, 68, 255, 245, 116, 68, 255, 245, 115, 68, 255, 245, 115, 68, 255, 244, 114, 68, 255, 244, 114, 68, 255, 244, 113, 67, 255, 244, 113, 67, 255, 244, 112, 67, 255, 244, 111, 67, 255, 244, 111, 67, 255, 244, 110, 67, 255, 244, 110, 67, 255, 244, 109, 67, 255, 244, 109, 67, 255, 243, 108, 67, 255, 243, 108, 67, 255, 243, 107, 67, 255, 243, 107, 67, 255, 243, 107, 67, 255, 242, 106, 67, 255, 242, 106, 67, 255, 242, 106, 67, 255, 242, 105, 67, 255, 242, 105, 67, 255, 241, 104, 68, 255, 241, 104, 68, 255, 241, 104, 68, 255, 241, 103, 68, 255, 241, 103, 68, 255, 240, 102, 68, 255, 240, 102, 68, 255, 240, 102, 68, 255, 240, 101, 68, 255, 240, 101, 68, 255, 239, 101, 69, 255, 239, 100, 69, 255, 239, 100, 69, 255, 239, 99, 69, 255, 238, 99, 69, 255, 238, 99, 69, 255, 238, 98, 69, 255, 238, 98, 69, 255, 238, 98, 69, 255, 237, 97, 69, 255, 237, 97, 70, 255, 237, 96, 70, 255, 237, 96, 70, 255, 236, 96, 70, 255, 236, 95, 70, 255, 236, 95, 70, 255, 236, 95, 70, 255, 236, 94, 70, 255, 235, 94, 70, 255, 235, 94, 70, 255, 235, 93, 71, 255, 235, 93, 71, 255, 234, 92, 71, 255, 234, 92, 71, 255, 234, 92, 71, 255, 234, 91, 71, 255, 233, 91, 71, 255, 233, 91, 71, 255, 233, 90, 71, 255, 233, 90, 71, 255, 233, 89, 72, 255, 226, 80, 74, 255, 226, 79, 74, 255, 226, 79, 74, 255, 225, 79, 74, 255, 225, 78, 74, 255, 225, 78, 74, 255, 225, 77, 75, 255, 224, 77, 75, 255, 224, 77, 75, 255, 224, 76, 75, 255, 224, 76, 75, 255, 223, 76, 75, 255, 223, 75, 75, 255, 223, 75, 75, 255, 223, 75, 75, 255, 222, 74, 75, 255, 222, 74, 75, 255, 222, 73, 76, 255, 222, 73, 76, 255, 221, 73, 76, 255, 221, 72, 76, 255, 221, 72, 76, 255, 220, 72, 76, 255, 220, 71, 76, 255, 220, 71, 76, 255, 220, 71, 76, 255, 219, 70, 76, 255, 219, 70, 76, 255, 219, 70, 77, 255, 219, 69, 77, 255, 218, 69, 77, 255, 218, 68, 77, 255, 218, 68, 77, 255, 217, 68, 77, 255, 217, 67, 77, 255, 217, 67, 77, 255, 217, 67, 77, 255, 216, 66, 77, 255, 216, 66, 77, 255, 216, 66, 78, 255, 216, 65, 78, 255, 215, 65, 78, 255, 215, 65, 78, 255, 215, 64, 78, 255, 214, 64, 78, 255, 214, 63, 78, 255, 214, 63, 78, 255, 214, 63, 78, 255, 213, 62, 78, 255, 213, 62, 78, 255, 213, 62, 78, 255, 212, 61, 78, 255, 212, 61, 78, 255, 211, 61, 78, 255, 211, 60, 78, 255, 211, 60, 78, 255, 210, 59, 78, 255, 210, 59, 78, 255, 209, 59, 78, 255, 209, 58, 78, 255, 209, 58, 78, 255, 208, 57, 78, 255, 208, 57, 77, 255, 207, 57, 77, 255, 207, 56, 77, 255, 206, 56, 77, 255, 206, 56, 77, 255, 206, 55, 77, 255, 205, 55, 77, 255, 205, 54, 77, 255, 204, 54, 77, 255, 204, 54, 77, 255, 203, 53, 77, 255, 203, 53, 76, 255, 203, 52, 76, 255, 202, 52, 76, 255, 202, 52, 76, 255, 201, 51, 76, 255, 201, 51, 76, 255, 200, 50, 76, 255, 200, 50, 76, 255, 200, 50, 76, 255, 199, 49, 76, 255, 199, 49, 76, 255, 198, 48, 75, 255, 198, 48, 75, 255, 198, 48, 75, 255, 197, 47, 75, 255, 197, 47, 75, 255, 196, 46, 75, 255, 196, 46, 75, 255, 195, 46, 75, 255, 195, 45, 75, 255, 195, 45, 75, 255, 194, 44, 74, 255, 194, 44, 74, 255, 193, 43, 74, 255, 193, 43, 74, 255, 192, 43, 74, 255, 192, 42, 74, 255, 192, 42, 74, 255, 191, 41, 74, 255, 180, 29, 71, 255, 179, 28, 71, 255, 179, 28, 71, 255, 178, 27, 71, 255, 178, 27, 71, 255, 177, 27, 71, 255, 177, 26, 70, 255, 177, 26, 70, 255, 176, 25, 70, 255, 176, 25, 70, 255, 175, 24, 70, 255, 175, 24, 70, 255, 174, 23, 70, 255, 174, 23, 70, 255, 174, 23, 70, 255, 173, 22, 70, 255, 173, 22, 69, 255, 172, 21, 69, 255, 172, 21, 69, 255, 171, 20, 69, 255, 171, 20, 69, 255, 171, 19, 69, 255, 170, 19, 69, 255, 170, 18, 69, 255, 169, 18, 69, 255, 169, 17, 68, 255, 168, 17, 68, 255, 168, 16, 68, 255, 168, 16, 68, 255, 167, 15, 68, 255, 167, 14, 68, 255, 166, 14, 68, 255, 166, 13, 68, 255, 165, 13, 68, 255, 165, 12, 67, 255, 164, 12, 67, 255, 164, 11, 67, 255, 164, 10, 67, 255, 163, 10, 67, 255, 163, 9, 67, 255, 162, 8, 67, 255, 162, 7, 67, 255, 161, 7, 67, 255, 161, 6, 66, 255, 161, 5, 66, 255, 160, 5, 66, 255, 160, 4, 66, 255, 159, 3, 66, 255, 159, 2, 66, 255, 158, 2, 66, 255, 158, 1, 66, 255}; -static const heatmap_colorscheme_t cs_spectral_mixed = { mixed_data, sizeof(mixed_data)/sizeof(mixed_data[0])/4 }; -const heatmap_colorscheme_t* heatmap_cs_default = &cs_spectral_mixed; - +/* heatmap - High performance heatmap creation in C. + * + * The MIT License (MIT) + * + * Copyright (c) 2013 Lucas Beyer + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "heatmap.h" + +#include +#include /* malloc, calloc, free */ +#include /* memcpy, memset */ +#include /* sqrtf */ +#include /* assert, #define NDEBUG to ignore. */ + +/* Having a default stamp ready makes it easier for simple usage of the library + * since there is no need to create a new stamp. + */ +static float stamp_default_4_data[] = { + 0.0f , 0.0f , 0.1055728f, 0.1753789f, 0.2f, 0.1753789f, 0.1055728f, 0.0f , 0.0f , + 0.0f , 0.1514719f, 0.2788897f, 0.3675445f, 0.4f, 0.3675445f, 0.2788897f, 0.1514719f, 0.0f , + 0.1055728f, 0.2788897f, 0.4343146f, 0.5527864f, 0.6f, 0.5527864f, 0.4343146f, 0.2788897f, 0.1055728f, + 0.1753789f, 0.3675445f, 0.5527864f, 0.7171573f, 0.8f, 0.7171573f, 0.5527864f, 0.3675445f, 0.1753789f, + 0.2f , 0.4f , 0.6f , 0.8f , 1.0f, 0.8f , 0.6f , 0.4f , 0.2f , + 0.1753789f, 0.3675445f, 0.5527864f, 0.7171573f, 0.8f, 0.7171573f, 0.5527864f, 0.3675445f, 0.1753789f, + 0.1055728f, 0.2788897f, 0.4343146f, 0.5527864f, 0.6f, 0.5527864f, 0.4343146f, 0.2788897f, 0.1055728f, + 0.0f , 0.1514719f, 0.2788897f, 0.3675445f, 0.4f, 0.3675445f, 0.2788897f, 0.1514719f, 0.0f , + 0.0f , 0.0f , 0.1055728f, 0.1753789f, 0.2f, 0.1753789f, 0.1055728f, 0.0f , 0.0f , +}; + +static heatmap_stamp_t stamp_default_4 = { + stamp_default_4_data, 9, 9 +}; + +void heatmap_init(heatmap_t* hm, unsigned w, unsigned h) +{ + memset(hm, 0, sizeof(heatmap_t)); + hm->buf = (float*)calloc(w*h, sizeof(float)); + hm->w = w; + hm->h = h; +} + +heatmap_t* heatmap_new(unsigned w, unsigned h) +{ + heatmap_t* hm = (heatmap_t*)malloc(sizeof(heatmap_t)); + heatmap_init(hm, w, h); + return hm; +} + +void heatmap_free(heatmap_t* h) +{ + free(h->buf); + free(h); +} + +void heatmap_add_point(heatmap_t* h, unsigned x, unsigned y) +{ + heatmap_add_point_with_stamp(h, x, y, &stamp_default_4); +} + +void heatmap_add_point_with_stamp(heatmap_t* h, unsigned x, unsigned y, const heatmap_stamp_t* stamp) +{ + /* I'm still unsure whether we want this to be an assert or not... */ + if(x >= h->w || y >= h->h) + return; + + /* I hate you, C */ + { + /* Note: the order of operations is important, since we're computing with unsigned! */ + + /* These are [first, last) pairs in the STAMP's pixels. */ + const unsigned x0 = x < stamp->w/2 ? (stamp->w/2 - x) : 0; + const unsigned y0 = y < stamp->h/2 ? (stamp->h/2 - y) : 0; + const unsigned x1 = (x + stamp->w/2) < h->w ? stamp->w : stamp->w/2 + (h->w - x); + const unsigned y1 = (y + stamp->h/2) < h->h ? stamp->h : stamp->h/2 + (h->h - y); + + unsigned iy; + + for(iy = y0 ; iy < y1 ; ++iy) { + /* TODO: could it be clearer by using separate vars and computing a ystep? */ + float* line = h->buf + ((y + iy) - stamp->h/2)*h->w + (x + x0) - stamp->w/2; + const float* stampline = stamp->buf + iy*stamp->w + x0; + + unsigned ix; + for(ix = x0 ; ix < x1 ; ++ix, ++line, ++stampline) { + /* TODO: Let's actually accept negatives and try out funky stamps. */ + /* Note that that might mess with the max though. */ + /* And that we'll have to clamp the bottom to 0 when rendering. */ + assert(*stampline >= 0.0f); + + *line += *stampline; + if(*line > h->max) {h->max = *line;} + + assert(*line >= 0.0f); + } + } + } /* I hate you very much! */ +} + +void heatmap_add_weighted_point(heatmap_t* h, unsigned x, unsigned y, float w) +{ + heatmap_add_weighted_point_with_stamp(h, x, y, w, &stamp_default_4); +} + +/* Initial timings do show a difference large enough (~10% slower without FMA) + * that we do care about splitting the implementation, + * even though JUST A SINGLE LINE OF CODE has changed! + * And I don't want to spoil the readability by using macro-trickery to avoid duplication. + * sad :-( + */ +void heatmap_add_weighted_point_with_stamp(heatmap_t* h, unsigned x, unsigned y, float w, const heatmap_stamp_t* stamp) +{ + /* I'm still unsure whether we want this to be an assert or not... */ + if(x >= h->w || y >= h->h) + return; + + /* Currently, negative weights are not supported as they mess with the max. */ + assert(w >= 0.0f); + + /* I hate you, C */ + { + /* Note: the order of operations is important, since we're computing with unsigned! */ + + /* These are [first, last) pairs in the STAMP's pixels. */ + const unsigned x0 = x < stamp->w/2 ? (stamp->w/2 - x) : 0; + const unsigned y0 = y < stamp->h/2 ? (stamp->h/2 - y) : 0; + const unsigned x1 = (x + stamp->w/2) < h->w ? stamp->w : stamp->w/2 + (h->w - x); + const unsigned y1 = (y + stamp->h/2) < h->h ? stamp->h : stamp->h/2 + (h->h - y); + + unsigned iy; + + for(iy = y0 ; iy < y1 ; ++iy) { + /* TODO: could it be clearer by using separate vars and computing a ystep? */ + float* line = h->buf + ((y + iy) - stamp->h/2)*h->w + (x + x0) - stamp->w/2; + const float* stampline = stamp->buf + iy*stamp->w + x0; + + unsigned ix; + for(ix = x0 ; ix < x1 ; ++ix, ++line, ++stampline) { + /* TODO: see unweighted function */ + assert(*stampline >= 0.0f); + + *line += *stampline * w; + if(*line > h->max) {h->max = *line;} + + assert(*line >= 0.0f); + } + } + } /* I hate you very much! */ +} + +unsigned char* heatmap_render_default_to(const heatmap_t* h, unsigned char* colorbuf) +{ + return heatmap_render_to(h, heatmap_cs_default, colorbuf); +} + +unsigned char* heatmap_render_to(const heatmap_t* h, const heatmap_colorscheme_t* colorscheme, unsigned char* colorbuf) +{ + /* TODO: Time whether it makes a noticeable difference to inline that code + * here and drop the saturation step. + */ + /* If the heatmap is empty, h->max (and thus the saturation value) is 0.0, resulting in a 0-by-0 division. + * In that case, we should set the saturation to anything but 0, since we want the result of the division to be 0. + * Also, a comparison to exact 0.0f (as opposed to 1e-14) is OK, since we only do division. + */ + return heatmap_render_saturated_to(h, colorscheme, h->max > 0.0f ? h->max : 1.0f, colorbuf); +} + +unsigned char* heatmap_render_saturated_to(const heatmap_t* h, const heatmap_colorscheme_t* colorscheme, float saturation, unsigned char* colorbuf) +{ + unsigned y; + assert(saturation > 0.0f); + + /* For convenience, if no buffer is given, malloc a new one. */ + if(!colorbuf) { + colorbuf = (unsigned char*)malloc(h->w*h->h*4); + if(!colorbuf) { + return 0; + } + } + + /* TODO: could actually even flatten this loop before parallelizing it. */ + /* I.e., to go i = 0 ; i < h*w since I don't have any padding! (yet?) */ + for(y = 0 ; y < h->h ; ++y) { + float* bufline = h->buf + y*h->w; + unsigned char* colorline = colorbuf + 4*y*h->w; + + unsigned x; + for(x = 0 ; x < h->w ; ++x) { + /* Saturate the heat value to the given saturation, and then + * normalize by that. + */ + const float val = (*bufline > saturation ? saturation : *bufline)/saturation; + + /* We add 0.5 in order to do real rounding, not just dropping the + * decimal part. That way we are certain the highest value in the + * colorscheme is actually used. + */ + const size_t idx = (size_t)((float)(colorscheme->ncolors-1)*val + 0.5f); + + // dont copy zero values + if (*(colorscheme->colors + idx*4 + 3) > 0) { + /* Just copy over the color from the colorscheme. */ + memcpy(colorline, colorscheme->colors + idx*4, 4); + } + colorline += 4; + + ++bufline; + } + } + + return colorbuf; +} + +void heatmap_stamp_init(heatmap_stamp_t* stamp, unsigned w, unsigned h, float* data) +{ + if(stamp) { + memset(stamp, 0, sizeof(heatmap_stamp_t)); + stamp->w = w; + stamp->h = h; + stamp->buf = data; + } +} + +heatmap_stamp_t* heatmap_stamp_new_with(unsigned w, unsigned h, float* data) +{ + heatmap_stamp_t* stamp = (heatmap_stamp_t*)malloc(sizeof(heatmap_stamp_t)); + heatmap_stamp_init(stamp, w, h, data); + return stamp; +} + +heatmap_stamp_t* heatmap_stamp_load(unsigned w, unsigned h, float* data) +{ + float* copy = (float*)malloc(sizeof(float)*w*h); + memcpy(copy, data, sizeof(float)*w*h); + return heatmap_stamp_new_with(w, h, copy); +} + +static float linear_dist(float dist) +{ + return dist; +} + +heatmap_stamp_t* heatmap_stamp_gen(unsigned r) +{ + return heatmap_stamp_gen_nonlinear(r, linear_dist); +} + +heatmap_stamp_t* heatmap_stamp_gen_nonlinear(unsigned r, float (*distshape)(float)) +{ + unsigned y; + unsigned d = 2*r+1; + + float* stamp = (float*)calloc(d*d, sizeof(float)); + if(!stamp) + return 0; + + for(y = 0 ; y < d ; ++y) { + float* line = stamp + y*d; + unsigned x; + for(x = 0 ; x < d ; ++x, ++line) { + const float dist = sqrtf((float)((x-r)*(x-r) + (y-r)*(y-r)))/(float)(r+1); + const float ds = (*distshape)(dist); + /* This doesn't generate optimal assembly, but meh, it's readable. */ + const float clamped_ds = ds > 1.0f ? 1.0f + : ds < 0.0f ? 0.0f + : ds; + *line = 1.0f - clamped_ds; + } + } + + return heatmap_stamp_new_with(d, d, stamp); +} + +void heatmap_stamp_free(heatmap_stamp_t* s) +{ + free(s->buf); + free(s); +} + +heatmap_colorscheme_t* heatmap_colorscheme_load(const unsigned char* in_colors, size_t ncolors) +{ + heatmap_colorscheme_t* cs = (heatmap_colorscheme_t*)calloc(1, sizeof(heatmap_colorscheme_t)); + unsigned char* colors = (unsigned char*)malloc(4*ncolors); + + if(!cs || !colors) { + free(cs); + free(colors); + return 0; + } + + memcpy(colors, in_colors, 4*ncolors); + + cs->colors = colors; + cs->ncolors = ncolors; + return cs; +} + +void heatmap_colorscheme_free(heatmap_colorscheme_t* cs) +{ + /* ehhh, const_cast<>! */ + free((void*)cs->colors); + free(cs); +} + +/* Sorry dynamic wordwarp editor users! But you deserve no better anyways... */ +static const unsigned char mixed_data[] = {0, 0, 0, 0, 94, 79, 162, 0, 93, 79, 162, 7, 93, 80, 162, 14, 92, 80, 163, 22, 92, 81, 163, 29, 91, 81, 164, 37, 91, 82, 164, 44, 90, 82, 164, 52, 90, 83, 165, 59, 89, 83, 165, 67, 89, 84, 166, 74, 88, 84, 166, 82, 88, 85, 166, 89, 87, 85, 167, 97, 87, 86, 167, 104, 86, 86, 167, 112, 86, 87, 168, 119, 85, 87, 168, 127, 85, 88, 168, 134, 84, 88, 169, 141, 84, 89, 169, 149, 83, 89, 169, 156, 83, 90, 170, 164, 83, 90, 170, 171, 82, 91, 170, 179, 82, 91, 171, 186, 81, 92, 171, 194, 81, 92, 171, 201, 80, 93, 172, 209, 80, 93, 172, 216, 79, 94, 172, 224, 79, 94, 172, 231, 78, 95, 173, 239, 78, 95, 173, 246, 77, 95, 173, 254, 77, 96, 174, 255, 76, 96, 174, 255, 76, 97, 174, 255, 75, 97, 174, 255, 75, 98, 175, 255, 74, 98, 175, 255, 74, 99, 175, 255, 73, 99, 175, 255, 73, 100, 176, 255, 72, 100, 176, 255, 72, 101, 176, 255, 72, 101, 176, 255, 71, 101, 176, 255, 71, 102, 177, 255, 70, 102, 177, 255, 70, 103, 177, 255, 69, 103, 177, 255, 60, 115, 183, 255, 60, 115, 183, 255, 59, 116, 183, 255, 59, 116, 183, 255, 58, 117, 184, 255, 58, 117, 184, 255, 58, 118, 184, 255, 57, 118, 184, 255, 57, 118, 184, 255, 56, 119, 184, 255, 56, 119, 185, 255, 56, 120, 185, 255, 55, 120, 185, 255, 55, 121, 185, 255, 55, 121, 185, 255, 54, 121, 185, 255, 54, 122, 186, 255, 54, 122, 186, 255, 53, 123, 186, 255, 53, 123, 186, 255, 53, 124, 186, 255, 52, 124, 186, 255, 52, 124, 186, 255, 52, 125, 186, 255, 52, 125, 187, 255, 51, 126, 187, 255, 51, 126, 187, 255, 51, 126, 187, 255, 51, 127, 187, 255, 50, 127, 187, 255, 50, 128, 187, 255, 50, 128, 187, 255, 50, 128, 187, 255, 50, 129, 187, 255, 50, 129, 188, 255, 50, 130, 188, 255, 49, 130, 188, 255, 49, 130, 188, 255, 49, 131, 188, 255, 49, 131, 188, 255, 49, 132, 188, 255, 49, 132, 188, 255, 49, 132, 188, 255, 49, 133, 188, 255, 49, 133, 188, 255, 49, 133, 188, 255, 49, 134, 188, 255, 49, 134, 188, 255, 49, 135, 188, 255, 49, 135, 188, 255, 49, 135, 188, 255, 49, 136, 189, 255, 47, 136, 189, 255, 46, 137, 189, 255, 45, 138, 189, 255, 43, 138, 189, 255, 42, 139, 190, 255, 41, 139, 190, 255, 39, 140, 190, 255, 38, 140, 190, 255, 36, 141, 190, 255, 35, 141, 190, 255, 33, 142, 190, 255, 31, 142, 190, 255, 30, 143, 190, 255, 28, 143, 190, 255, 26, 144, 191, 255, 24, 145, 191, 255, 22, 145, 191, 255, 20, 146, 191, 255, 17, 146, 191, 255, 15, 147, 191, 255, 12, 147, 191, 255, 10, 148, 191, 255, 10, 148, 191, 255, 10, 149, 191, 255, 10, 149, 191, 255, 10, 150, 191, 255, 10, 150, 190, 255, 10, 151, 190, 255, 10, 151, 190, 255, 10, 152, 190, 255, 10, 152, 190, 255, 10, 153, 190, 255, 10, 153, 190, 255, 10, 154, 190, 255, 10, 154, 190, 255, 10, 155, 190, 255, 10, 155, 189, 255, 10, 156, 189, 255, 10, 156, 189, 255, 10, 157, 189, 255, 10, 157, 189, 255, 10, 158, 189, 255, 10, 158, 188, 255, 10, 158, 188, 255, 10, 159, 188, 255, 10, 159, 188, 255, 10, 160, 188, 255, 10, 160, 187, 255, 10, 161, 187, 255, 10, 161, 187, 255, 20, 173, 182, 255, 22, 174, 182, 255, 25, 174, 181, 255, 28, 175, 181, 255, 30, 175, 181, 255, 33, 176, 180, 255, 35, 176, 180, 255, 37, 176, 180, 255, 39, 177, 180, 255, 41, 177, 179, 255, 43, 178, 179, 255, 45, 178, 179, 255, 46, 179, 178, 255, 48, 179, 178, 255, 50, 179, 178, 255, 51, 180, 177, 255, 53, 180, 177, 255, 54, 181, 177, 255, 56, 181, 176, 255, 58, 182, 176, 255, 59, 182, 176, 255, 61, 182, 175, 255, 62, 183, 175, 255, 64, 183, 175, 255, 65, 184, 174, 255, 66, 184, 174, 255, 68, 184, 174, 255, 69, 185, 173, 255, 71, 185, 173, 255, 72, 186, 173, 255, 74, 186, 172, 255, 75, 186, 172, 255, 76, 187, 171, 255, 78, 187, 171, 255, 79, 187, 171, 255, 80, 188, 170, 255, 82, 188, 170, 255, 83, 189, 170, 255, 85, 189, 169, 255, 86, 189, 169, 255, 87, 190, 169, 255, 89, 190, 168, 255, 90, 190, 168, 255, 91, 191, 167, 255, 93, 191, 167, 255, 94, 191, 167, 255, 95, 192, 166, 255, 97, 192, 166, 255, 98, 193, 166, 255, 99, 193, 165, 255, 100, 193, 165, 255, 102, 194, 164, 255, 102, 194, 164, 255, 103, 194, 164, 255, 103, 194, 164, 255, 104, 194, 164, 255, 104, 195, 164, 255, 105, 195, 164, 255, 105, 195, 164, 255, 106, 195, 164, 255, 106, 196, 164, 255, 107, 196, 164, 255, 108, 196, 164, 255, 108, 196, 164, 255, 109, 196, 164, 255, 109, 197, 164, 255, 110, 197, 164, 255, 110, 197, 164, 255, 111, 197, 164, 255, 111, 198, 164, 255, 112, 198, 164, 255, 112, 198, 164, 255, 113, 198, 164, 255, 113, 198, 164, 255, 114, 199, 164, 255, 115, 199, 164, 255, 115, 199, 164, 255, 116, 199, 164, 255, 116, 200, 164, 255, 117, 200, 164, 255, 117, 200, 164, 255, 118, 200, 164, 255, 118, 200, 164, 255, 119, 201, 164, 255, 119, 201, 164, 255, 120, 201, 164, 255, 120, 201, 164, 255, 121, 201, 164, 255, 122, 202, 164, 255, 122, 202, 164, 255, 123, 202, 164, 255, 123, 202, 164, 255, 124, 203, 164, 255, 124, 203, 164, 255, 125, 203, 164, 255, 125, 203, 164, 255, 126, 203, 164, 255, 126, 204, 164, 255, 127, 204, 164, 255, 127, 204, 163, 255, 128, 204, 163, 255, 129, 204, 163, 255, 143, 210, 163, 255, 143, 210, 163, 255, 144, 210, 163, 255, 144, 211, 163, 255, 145, 211, 163, 255, 146, 211, 163, 255, 146, 211, 163, 255, 147, 212, 163, 255, 147, 212, 163, 255, 148, 212, 163, 255, 148, 212, 163, 255, 149, 212, 163, 255, 149, 213, 163, 255, 150, 213, 163, 255, 150, 213, 163, 255, 151, 213, 163, 255, 151, 213, 163, 255, 152, 214, 163, 255, 153, 214, 163, 255, 153, 214, 163, 255, 154, 214, 163, 255, 154, 214, 163, 255, 155, 215, 163, 255, 155, 215, 163, 255, 156, 215, 163, 255, 156, 215, 163, 255, 157, 215, 163, 255, 157, 216, 163, 255, 158, 216, 163, 255, 158, 216, 163, 255, 159, 216, 163, 255, 160, 216, 163, 255, 160, 217, 163, 255, 161, 217, 163, 255, 161, 217, 163, 255, 162, 217, 163, 255, 162, 217, 163, 255, 163, 218, 163, 255, 163, 218, 163, 255, 164, 218, 163, 255, 164, 218, 163, 255, 165, 218, 163, 255, 166, 219, 163, 255, 166, 219, 163, 255, 167, 219, 163, 255, 167, 219, 163, 255, 168, 219, 163, 255, 168, 220, 163, 255, 169, 220, 163, 255, 169, 220, 163, 255, 170, 220, 163, 255, 170, 220, 163, 255, 171, 221, 163, 255, 171, 221, 163, 255, 172, 221, 163, 255, 172, 221, 163, 255, 172, 222, 163, 255, 173, 222, 163, 255, 173, 222, 163, 255, 173, 222, 163, 255, 174, 222, 163, 255, 174, 223, 163, 255, 175, 223, 163, 255, 175, 223, 163, 255, 175, 223, 162, 255, 176, 223, 162, 255, 176, 224, 162, 255, 177, 224, 162, 255, 177, 224, 162, 255, 177, 224, 162, 255, 178, 224, 162, 255, 178, 225, 162, 255, 179, 225, 162, 255, 179, 225, 162, 255, 179, 225, 162, 255, 180, 225, 161, 255, 180, 226, 161, 255, 181, 226, 161, 255, 181, 226, 161, 255, 182, 226, 161, 255, 182, 226, 161, 255, 182, 227, 161, 255, 183, 227, 161, 255, 183, 227, 161, 255, 184, 227, 161, 255, 184, 227, 160, 255, 185, 228, 160, 255, 185, 228, 160, 255, 185, 228, 160, 255, 186, 228, 160, 255, 186, 228, 160, 255, 187, 229, 160, 255, 187, 229, 160, 255, 188, 229, 160, 255, 188, 229, 160, 255, 189, 229, 159, 255, 189, 230, 159, 255, 189, 230, 159, 255, 190, 230, 159, 255, 190, 230, 159, 255, 191, 230, 159, 255, 191, 231, 159, 255, 192, 231, 159, 255, 204, 236, 156, 255, 205, 236, 156, 255, 205, 236, 156, 255, 205, 236, 156, 255, 206, 236, 156, 255, 206, 237, 156, 255, 207, 237, 156, 255, 207, 237, 156, 255, 208, 237, 156, 255, 208, 237, 155, 255, 209, 238, 155, 255, 209, 238, 155, 255, 210, 238, 155, 255, 210, 238, 155, 255, 211, 238, 155, 255, 211, 238, 155, 255, 212, 239, 155, 255, 212, 239, 155, 255, 213, 239, 155, 255, 213, 239, 154, 255, 214, 239, 154, 255, 214, 240, 154, 255, 215, 240, 154, 255, 215, 240, 154, 255, 216, 240, 154, 255, 216, 240, 154, 255, 217, 240, 154, 255, 217, 241, 154, 255, 218, 241, 154, 255, 218, 241, 153, 255, 219, 241, 153, 255, 219, 241, 153, 255, 220, 241, 153, 255, 220, 242, 153, 255, 221, 242, 153, 255, 221, 242, 153, 255, 222, 242, 153, 255, 222, 242, 153, 255, 223, 242, 153, 255, 223, 243, 153, 255, 224, 243, 152, 255, 224, 243, 152, 255, 225, 243, 152, 255, 225, 243, 152, 255, 226, 243, 152, 255, 227, 244, 152, 255, 227, 244, 152, 255, 228, 244, 152, 255, 228, 244, 152, 255, 229, 244, 152, 255, 229, 244, 152, 255, 230, 244, 151, 255, 230, 244, 151, 255, 230, 244, 151, 255, 230, 244, 151, 255, 230, 244, 151, 255, 230, 244, 151, 255, 230, 244, 151, 255, 230, 244, 151, 255, 230, 244, 151, 255, 231, 244, 151, 255, 231, 244, 151, 255, 231, 244, 151, 255, 231, 243, 151, 255, 231, 243, 151, 255, 231, 243, 151, 255, 231, 243, 151, 255, 231, 243, 150, 255, 231, 243, 150, 255, 232, 243, 150, 255, 232, 243, 150, 255, 232, 243, 150, 255, 232, 243, 150, 255, 232, 243, 150, 255, 232, 243, 150, 255, 232, 243, 150, 255, 232, 242, 150, 255, 232, 242, 150, 255, 233, 242, 150, 255, 233, 242, 150, 255, 233, 242, 150, 255, 233, 242, 150, 255, 233, 242, 150, 255, 233, 242, 150, 255, 233, 242, 150, 255, 233, 242, 149, 255, 233, 242, 149, 255, 234, 242, 149, 255, 234, 241, 149, 255, 234, 241, 149, 255, 234, 241, 149, 255, 234, 241, 149, 255, 234, 241, 149, 255, 234, 241, 149, 255, 234, 241, 149, 255, 234, 241, 149, 255, 235, 241, 149, 255, 235, 241, 149, 255, 235, 241, 149, 255, 235, 241, 149, 255, 235, 240, 149, 255, 235, 240, 149, 255, 235, 240, 149, 255, 235, 240, 149, 255, 235, 240, 149, 255, 236, 240, 148, 255, 236, 240, 148, 255, 236, 240, 148, 255, 236, 240, 148, 255, 236, 240, 148, 255, 236, 240, 148, 255, 236, 240, 148, 255, 236, 239, 148, 255, 236, 239, 148, 255, 236, 239, 148, 255, 237, 239, 148, 255, 237, 239, 148, 255, 237, 239, 148, 255, 237, 239, 148, 255, 237, 239, 148, 255, 237, 239, 148, 255, 237, 239, 148, 255, 237, 239, 148, 255, 237, 239, 148, 255, 237, 238, 148, 255, 238, 238, 148, 255, 238, 238, 148, 255, 238, 238, 148, 255, 238, 238, 148, 255, 238, 238, 147, 255, 238, 238, 147, 255, 238, 238, 147, 255, 238, 238, 147, 255, 238, 238, 147, 255, 238, 238, 147, 255, 239, 238, 147, 255, 239, 237, 147, 255, 239, 237, 147, 255, 239, 237, 147, 255, 239, 237, 147, 255, 239, 237, 147, 255, 239, 237, 147, 255, 239, 237, 147, 255, 239, 237, 147, 255, 239, 237, 147, 255, 240, 237, 147, 255, 240, 237, 147, 255, 240, 237, 147, 255, 240, 236, 147, 255, 240, 236, 147, 255, 240, 236, 147, 255, 240, 236, 147, 255, 240, 236, 147, 255, 245, 232, 145, 255, 245, 232, 145, 255, 245, 232, 145, 255, 245, 232, 145, 255, 245, 232, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 246, 231, 145, 255, 247, 231, 145, 255, 247, 230, 145, 255, 247, 230, 145, 255, 247, 230, 145, 255, 247, 230, 145, 255, 247, 230, 144, 255, 247, 230, 144, 255, 247, 230, 144, 255, 247, 230, 144, 255, 247, 230, 144, 255, 247, 230, 144, 255, 248, 230, 144, 255, 248, 230, 144, 255, 248, 229, 144, 255, 248, 229, 144, 255, 248, 229, 144, 255, 248, 229, 144, 255, 248, 229, 144, 255, 248, 229, 144, 255, 248, 229, 144, 255, 248, 229, 144, 255, 248, 229, 144, 255, 248, 229, 144, 255, 249, 229, 144, 255, 249, 229, 144, 255, 249, 229, 144, 255, 249, 228, 144, 255, 249, 228, 144, 255, 249, 228, 144, 255, 249, 228, 144, 255, 249, 228, 144, 255, 249, 228, 144, 255, 249, 228, 144, 255, 249, 228, 144, 255, 249, 228, 144, 255, 250, 228, 144, 255, 250, 228, 144, 255, 250, 228, 144, 255, 250, 227, 144, 255, 250, 227, 144, 255, 250, 227, 144, 255, 250, 227, 144, 255, 250, 227, 144, 255, 250, 227, 144, 255, 250, 227, 144, 255, 250, 227, 144, 255, 250, 227, 144, 255, 251, 227, 144, 255, 251, 227, 144, 255, 251, 227, 144, 255, 251, 226, 144, 255, 251, 226, 144, 255, 251, 226, 144, 255, 251, 226, 144, 255, 251, 226, 144, 255, 251, 226, 144, 255, 251, 226, 144, 255, 251, 226, 144, 255, 251, 226, 144, 255, 251, 226, 144, 255, 252, 226, 144, 255, 252, 226, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 252, 225, 144, 255, 253, 225, 144, 255, 253, 225, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 224, 144, 255, 253, 223, 143, 255, 253, 223, 143, 255, 253, 223, 142, 255, 253, 222, 142, 255, 253, 222, 141, 255, 253, 221, 141, 255, 253, 221, 141, 255, 253, 221, 140, 255, 253, 220, 140, 255, 253, 220, 139, 255, 253, 220, 139, 255, 253, 219, 138, 255, 253, 219, 138, 255, 253, 218, 138, 255, 253, 218, 137, 255, 253, 218, 137, 255, 253, 217, 136, 255, 253, 217, 136, 255, 253, 217, 135, 255, 253, 216, 135, 255, 253, 216, 135, 255, 253, 215, 134, 255, 253, 215, 134, 255, 253, 215, 133, 255, 253, 214, 133, 255, 253, 214, 133, 255, 253, 214, 132, 255, 253, 213, 132, 255, 253, 213, 131, 255, 253, 212, 131, 255, 253, 212, 131, 255, 253, 212, 130, 255, 253, 211, 130, 255, 253, 211, 129, 255, 253, 211, 129, 255, 253, 210, 129, 255, 253, 210, 128, 255, 253, 209, 128, 255, 253, 209, 127, 255, 253, 209, 127, 255, 253, 208, 127, 255, 253, 208, 126, 255, 253, 207, 126, 255, 253, 207, 125, 255, 253, 207, 125, 255, 253, 206, 125, 255, 253, 206, 124, 255, 253, 205, 124, 255, 253, 205, 123, 255, 253, 205, 123, 255, 253, 204, 123, 255, 253, 194, 113, 255, 253, 194, 113, 255, 253, 193, 112, 255, 253, 193, 112, 255, 253, 192, 112, 255, 253, 192, 111, 255, 253, 192, 111, 255, 253, 191, 110, 255, 253, 191, 110, 255, 253, 190, 110, 255, 253, 190, 109, 255, 253, 190, 109, 255, 253, 189, 109, 255, 253, 189, 108, 255, 253, 188, 108, 255, 253, 188, 108, 255, 253, 188, 107, 255, 253, 187, 107, 255, 253, 187, 107, 255, 253, 186, 106, 255, 253, 186, 106, 255, 253, 186, 106, 255, 253, 185, 105, 255, 253, 185, 105, 255, 253, 184, 105, 255, 253, 184, 104, 255, 253, 184, 104, 255, 253, 183, 104, 255, 253, 183, 103, 255, 253, 182, 103, 255, 253, 182, 103, 255, 253, 182, 102, 255, 253, 181, 102, 255, 253, 181, 102, 255, 253, 180, 101, 255, 253, 180, 101, 255, 253, 180, 101, 255, 253, 179, 100, 255, 253, 179, 100, 255, 253, 178, 100, 255, 253, 178, 100, 255, 253, 178, 99, 255, 253, 177, 99, 255, 253, 177, 99, 255, 253, 176, 98, 255, 253, 176, 98, 255, 253, 175, 98, 255, 253, 175, 98, 255, 253, 175, 97, 255, 253, 174, 97, 255, 253, 174, 97, 255, 252, 173, 96, 255, 252, 173, 96, 255, 252, 172, 96, 255, 252, 172, 95, 255, 252, 172, 95, 255, 252, 171, 95, 255, 252, 171, 94, 255, 252, 170, 94, 255, 252, 170, 94, 255, 252, 169, 93, 255, 252, 169, 93, 255, 252, 168, 93, 255, 252, 168, 92, 255, 252, 167, 92, 255, 252, 167, 92, 255, 252, 166, 91, 255, 252, 166, 91, 255, 252, 165, 91, 255, 251, 165, 90, 255, 251, 164, 90, 255, 251, 164, 90, 255, 251, 163, 89, 255, 251, 163, 89, 255, 251, 162, 89, 255, 251, 162, 89, 255, 251, 162, 88, 255, 251, 161, 88, 255, 251, 161, 88, 255, 251, 160, 87, 255, 251, 160, 87, 255, 251, 159, 87, 255, 251, 159, 87, 255, 251, 158, 86, 255, 251, 158, 86, 255, 251, 157, 86, 255, 250, 157, 85, 255, 250, 156, 85, 255, 250, 156, 85, 255, 250, 155, 85, 255, 250, 155, 84, 255, 250, 154, 84, 255, 250, 154, 84, 255, 250, 153, 84, 255, 250, 153, 83, 255, 250, 152, 83, 255, 250, 152, 83, 255, 250, 151, 83, 255, 250, 151, 82, 255, 250, 150, 82, 255, 250, 150, 82, 255, 249, 149, 82, 255, 248, 136, 75, 255, 248, 135, 75, 255, 247, 135, 75, 255, 247, 134, 75, 255, 247, 134, 74, 255, 247, 133, 74, 255, 247, 133, 74, 255, 247, 132, 74, 255, 247, 132, 74, 255, 247, 131, 73, 255, 247, 131, 73, 255, 247, 130, 73, 255, 247, 130, 73, 255, 247, 129, 72, 255, 247, 129, 72, 255, 247, 128, 72, 255, 246, 128, 72, 255, 246, 127, 72, 255, 246, 126, 71, 255, 246, 126, 71, 255, 246, 125, 71, 255, 246, 125, 71, 255, 246, 124, 71, 255, 246, 124, 71, 255, 246, 123, 70, 255, 246, 123, 70, 255, 246, 122, 70, 255, 246, 122, 70, 255, 246, 121, 70, 255, 245, 121, 70, 255, 245, 120, 69, 255, 245, 120, 69, 255, 245, 119, 69, 255, 245, 119, 69, 255, 245, 118, 69, 255, 245, 117, 69, 255, 245, 117, 68, 255, 245, 116, 68, 255, 245, 116, 68, 255, 245, 115, 68, 255, 245, 115, 68, 255, 244, 114, 68, 255, 244, 114, 68, 255, 244, 113, 67, 255, 244, 113, 67, 255, 244, 112, 67, 255, 244, 111, 67, 255, 244, 111, 67, 255, 244, 110, 67, 255, 244, 110, 67, 255, 244, 109, 67, 255, 244, 109, 67, 255, 243, 108, 67, 255, 243, 108, 67, 255, 243, 107, 67, 255, 243, 107, 67, 255, 243, 107, 67, 255, 242, 106, 67, 255, 242, 106, 67, 255, 242, 106, 67, 255, 242, 105, 67, 255, 242, 105, 67, 255, 241, 104, 68, 255, 241, 104, 68, 255, 241, 104, 68, 255, 241, 103, 68, 255, 241, 103, 68, 255, 240, 102, 68, 255, 240, 102, 68, 255, 240, 102, 68, 255, 240, 101, 68, 255, 240, 101, 68, 255, 239, 101, 69, 255, 239, 100, 69, 255, 239, 100, 69, 255, 239, 99, 69, 255, 238, 99, 69, 255, 238, 99, 69, 255, 238, 98, 69, 255, 238, 98, 69, 255, 238, 98, 69, 255, 237, 97, 69, 255, 237, 97, 70, 255, 237, 96, 70, 255, 237, 96, 70, 255, 236, 96, 70, 255, 236, 95, 70, 255, 236, 95, 70, 255, 236, 95, 70, 255, 236, 94, 70, 255, 235, 94, 70, 255, 235, 94, 70, 255, 235, 93, 71, 255, 235, 93, 71, 255, 234, 92, 71, 255, 234, 92, 71, 255, 234, 92, 71, 255, 234, 91, 71, 255, 233, 91, 71, 255, 233, 91, 71, 255, 233, 90, 71, 255, 233, 90, 71, 255, 233, 89, 72, 255, 226, 80, 74, 255, 226, 79, 74, 255, 226, 79, 74, 255, 225, 79, 74, 255, 225, 78, 74, 255, 225, 78, 74, 255, 225, 77, 75, 255, 224, 77, 75, 255, 224, 77, 75, 255, 224, 76, 75, 255, 224, 76, 75, 255, 223, 76, 75, 255, 223, 75, 75, 255, 223, 75, 75, 255, 223, 75, 75, 255, 222, 74, 75, 255, 222, 74, 75, 255, 222, 73, 76, 255, 222, 73, 76, 255, 221, 73, 76, 255, 221, 72, 76, 255, 221, 72, 76, 255, 220, 72, 76, 255, 220, 71, 76, 255, 220, 71, 76, 255, 220, 71, 76, 255, 219, 70, 76, 255, 219, 70, 76, 255, 219, 70, 77, 255, 219, 69, 77, 255, 218, 69, 77, 255, 218, 68, 77, 255, 218, 68, 77, 255, 217, 68, 77, 255, 217, 67, 77, 255, 217, 67, 77, 255, 217, 67, 77, 255, 216, 66, 77, 255, 216, 66, 77, 255, 216, 66, 78, 255, 216, 65, 78, 255, 215, 65, 78, 255, 215, 65, 78, 255, 215, 64, 78, 255, 214, 64, 78, 255, 214, 63, 78, 255, 214, 63, 78, 255, 214, 63, 78, 255, 213, 62, 78, 255, 213, 62, 78, 255, 213, 62, 78, 255, 212, 61, 78, 255, 212, 61, 78, 255, 211, 61, 78, 255, 211, 60, 78, 255, 211, 60, 78, 255, 210, 59, 78, 255, 210, 59, 78, 255, 209, 59, 78, 255, 209, 58, 78, 255, 209, 58, 78, 255, 208, 57, 78, 255, 208, 57, 77, 255, 207, 57, 77, 255, 207, 56, 77, 255, 206, 56, 77, 255, 206, 56, 77, 255, 206, 55, 77, 255, 205, 55, 77, 255, 205, 54, 77, 255, 204, 54, 77, 255, 204, 54, 77, 255, 203, 53, 77, 255, 203, 53, 76, 255, 203, 52, 76, 255, 202, 52, 76, 255, 202, 52, 76, 255, 201, 51, 76, 255, 201, 51, 76, 255, 200, 50, 76, 255, 200, 50, 76, 255, 200, 50, 76, 255, 199, 49, 76, 255, 199, 49, 76, 255, 198, 48, 75, 255, 198, 48, 75, 255, 198, 48, 75, 255, 197, 47, 75, 255, 197, 47, 75, 255, 196, 46, 75, 255, 196, 46, 75, 255, 195, 46, 75, 255, 195, 45, 75, 255, 195, 45, 75, 255, 194, 44, 74, 255, 194, 44, 74, 255, 193, 43, 74, 255, 193, 43, 74, 255, 192, 43, 74, 255, 192, 42, 74, 255, 192, 42, 74, 255, 191, 41, 74, 255, 180, 29, 71, 255, 179, 28, 71, 255, 179, 28, 71, 255, 178, 27, 71, 255, 178, 27, 71, 255, 177, 27, 71, 255, 177, 26, 70, 255, 177, 26, 70, 255, 176, 25, 70, 255, 176, 25, 70, 255, 175, 24, 70, 255, 175, 24, 70, 255, 174, 23, 70, 255, 174, 23, 70, 255, 174, 23, 70, 255, 173, 22, 70, 255, 173, 22, 69, 255, 172, 21, 69, 255, 172, 21, 69, 255, 171, 20, 69, 255, 171, 20, 69, 255, 171, 19, 69, 255, 170, 19, 69, 255, 170, 18, 69, 255, 169, 18, 69, 255, 169, 17, 68, 255, 168, 17, 68, 255, 168, 16, 68, 255, 168, 16, 68, 255, 167, 15, 68, 255, 167, 14, 68, 255, 166, 14, 68, 255, 166, 13, 68, 255, 165, 13, 68, 255, 165, 12, 67, 255, 164, 12, 67, 255, 164, 11, 67, 255, 164, 10, 67, 255, 163, 10, 67, 255, 163, 9, 67, 255, 162, 8, 67, 255, 162, 7, 67, 255, 161, 7, 67, 255, 161, 6, 66, 255, 161, 5, 66, 255, 160, 5, 66, 255, 160, 4, 66, 255, 159, 3, 66, 255, 159, 2, 66, 255, 158, 2, 66, 255, 158, 1, 66, 255}; +static const heatmap_colorscheme_t cs_spectral_mixed = { mixed_data, sizeof(mixed_data)/sizeof(mixed_data[0])/4 }; +const heatmap_colorscheme_t* heatmap_cs_default = &cs_spectral_mixed; + diff --git a/src/3rdparty/heatmap.h b/src/3rdparty/heatmap.h index 90110fd..e4f3b6b 100644 --- a/src/3rdparty/heatmap.h +++ b/src/3rdparty/heatmap.h @@ -1,180 +1,180 @@ -/* heatmap - High performance heatmap creation in C. - * - * The MIT License (MIT) - * - * Copyright (c) 2013 Lucas Beyer - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of - * this software and associated documentation files (the "Software"), to deal in - * the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of - * the Software, and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS - * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR - * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER - * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#ifndef _HEATMAP_H -#define _HEATMAP_H - -/* Necessary for size_t */ -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* Maybe make an opaque type out of this. But then again, - * I'm assuming the users of this lib are not stupid here. - * If you mess with the internals and things break, blame yourself. - */ -typedef struct { - float* buf; /* Contains the heat value of every heatmap pixel. */ - float max; /* The highest heat in the whole map. Used for normalization. */ - unsigned w, h; /* Pixel-dimension of the heatmap. */ -} heatmap_t; - -/* A stamp is "stamped" (added) onto the heatmap for every datapoint which - * is seen. This is usually something spheric, but there are no limits to your - * artistic freedom! - */ -typedef struct { - float* buf; /* The stampdata which is added onto the heatmap. */ - unsigned w, h; /* The size (in pixel) of the stamp. */ -} heatmap_stamp_t; - -/* A colorscheme is used to transform the heatmap's heat values (floats) - * into an actual colorful heatmap. - * Maybe counterintuitively, the coldest color comes first (stored at index 0) - * and the hottest color comes last (stored at (ncolors-1)*4). - * Note that one color is made up of FOUR chars, since it is RGBA. - * You probably want the very first color to be (0,0,0,0) such that the heatmap - * is transparent where there was no data and you can overlay it onto - * another image, like a world map. - */ -typedef struct { - const unsigned char* colors; /* Color values in RGBA. */ - size_t ncolors; /* Amount of colors (not amount of bytes or array size). */ -} heatmap_colorscheme_t; - -/* Creates a new heatmap of given size. */ -heatmap_t* heatmap_new(unsigned w, unsigned h); -/* Frees up all memory taken by the heatmap. */ -void heatmap_free(heatmap_t* h); - -/* Adds a single point to the heatmap using the default stamp. */ -void heatmap_add_point(heatmap_t* h, unsigned x, unsigned y); -/* Adds a single point to the heatmap using a given stamp. */ -void heatmap_add_point_with_stamp(heatmap_t* h, unsigned x, unsigned y, const heatmap_stamp_t* stamp); - -/* Adds a single weighted point to the heatmap using the default stamp. */ -void heatmap_add_weighted_point(heatmap_t* h, unsigned x, unsigned y, float w); -/* Adds a single weighted point to the heatmap using a given stamp. */ -void heatmap_add_weighted_point_with_stamp(heatmap_t* h, unsigned x, unsigned y, float w, const heatmap_stamp_t* stamp); - -/* Renders an image of the heatmap into the given colorbuf. - * - * colorbuf: A buffer large enough to hold 4*heatmap_width*heatmap_height - * unsigned chars. These chars are the RGBA values of the pixels. - * - * If colorbuf is NULL, a new large enough buffer will be malloc'd. - * - * return: A pointer to the given colorbuf is returned. It is the caller's - * responsibility to free that buffer. If no colorbuf is given (NULL), - * a newly malloc'd buffer is returned. This buffer needs to be free'd - * by the caller whenever it is not used anymore. - */ -unsigned char* heatmap_render_default_to(const heatmap_t* h, unsigned char* colorbuf); - -/* Renders an RGB image of the heatmap into the given colorbuf, - * using a given colorscheme. - * - * colorscheme: See the description of heatmap_colorscheme_t for more details. - * - * For details on the colorbuf and the return value, refer to the documentation - * of `heatmap_render_default_to`. - */ -unsigned char* heatmap_render_to(const heatmap_t* h, const heatmap_colorscheme_t* colorscheme, unsigned char* colorbuf); - -/* Renders an RGB image of the heatmap into the given colorbuf, - * using a given colorscheme. - * - * colorscheme: See the description of heatmap_colorscheme_t for more details. - * - * saturation: The heatmap will be truncated at the given heat value, meaning - * all spots hotter than `saturation` will be assigned the same - * color as the hottest color on the scale. - * - * For details on the colorbuf and the return value, refer to the documentation - * of `heatmap_render_default_to`. - */ -unsigned char* heatmap_render_saturated_to(const heatmap_t* h, const heatmap_colorscheme_t* colorscheme, float saturation, unsigned char* colorbuf); - -/* Creates a new stamp COPYING the given w*h floats in data. - * - * w, h: The width/height of the stamp, in pixels. - * data: exactly w*h float values which will be added to the heatmap centered - * around every datapoint drawn onto the heatmap. - * - * For more information about stamps, read `heatmap_stamp_t`'s documentation. - */ -heatmap_stamp_t* heatmap_stamp_load(unsigned w, unsigned h, float* data); - -/* Generates a default round stamp of a given radius. This means the stamp will - * have a size of 2*radius+1 square. The default stamp is just a spherical - * gradient around the center. - * - * For more information about stamps, read `heatmap_stamp_t`'s documentation. - */ -heatmap_stamp_t* heatmap_stamp_gen(unsigned radius); - -/* Generates a stamp just like `heatmap_stamp_gen` but calls the given - * `distshape` function in order to determine the value of every single pixel. - * - * distshape: Function which gets called for every pixel. The only argument - * given to that function is the distance from the centre, 0 being - * exactly on the centre and 1 being one-behind the radius. - * One minus the returned value, clamped to [0,1] will be the - * pixel's value. - * - * For more information about stamps, read `heatmap_stamp_t`'s documentation. - */ -heatmap_stamp_t* heatmap_stamp_gen_nonlinear(unsigned radius, float (*distshape)(float)); - -/* Frees up all memory taken by the stamp. */ -void heatmap_stamp_free(heatmap_stamp_t* s); - -/* Create a new colorscheme using a COPY of the given `ncolors` `colors`. - * - * colors: a buffer containing RGBA colors to use when rendering the heatmap. - * - * For more information about colorschemes, read `heatmap_colorscheme_t`'s - * documentation. - */ -heatmap_colorscheme_t* heatmap_colorscheme_load(const unsigned char* colors, size_t ncolors); - -/* Frees up all memory taken by the colorscheme. */ -void heatmap_colorscheme_free(heatmap_colorscheme_t* cs); - -extern const heatmap_colorscheme_t* heatmap_cs_default; - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#ifdef __cplusplus -/* C++ wrapper API, but TODO: is this even necessary? */ -namespace heatmap { -} -#endif - -#endif /* _HEATMAP_H */ - +/* heatmap - High performance heatmap creation in C. + * + * The MIT License (MIT) + * + * Copyright (c) 2013 Lucas Beyer + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _HEATMAP_H +#define _HEATMAP_H + +/* Necessary for size_t */ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Maybe make an opaque type out of this. But then again, + * I'm assuming the users of this lib are not stupid here. + * If you mess with the internals and things break, blame yourself. + */ +typedef struct { + float* buf; /* Contains the heat value of every heatmap pixel. */ + float max; /* The highest heat in the whole map. Used for normalization. */ + unsigned w, h; /* Pixel-dimension of the heatmap. */ +} heatmap_t; + +/* A stamp is "stamped" (added) onto the heatmap for every datapoint which + * is seen. This is usually something spheric, but there are no limits to your + * artistic freedom! + */ +typedef struct { + float* buf; /* The stampdata which is added onto the heatmap. */ + unsigned w, h; /* The size (in pixel) of the stamp. */ +} heatmap_stamp_t; + +/* A colorscheme is used to transform the heatmap's heat values (floats) + * into an actual colorful heatmap. + * Maybe counterintuitively, the coldest color comes first (stored at index 0) + * and the hottest color comes last (stored at (ncolors-1)*4). + * Note that one color is made up of FOUR chars, since it is RGBA. + * You probably want the very first color to be (0,0,0,0) such that the heatmap + * is transparent where there was no data and you can overlay it onto + * another image, like a world map. + */ +typedef struct { + const unsigned char* colors; /* Color values in RGBA. */ + size_t ncolors; /* Amount of colors (not amount of bytes or array size). */ +} heatmap_colorscheme_t; + +/* Creates a new heatmap of given size. */ +heatmap_t* heatmap_new(unsigned w, unsigned h); +/* Frees up all memory taken by the heatmap. */ +void heatmap_free(heatmap_t* h); + +/* Adds a single point to the heatmap using the default stamp. */ +void heatmap_add_point(heatmap_t* h, unsigned x, unsigned y); +/* Adds a single point to the heatmap using a given stamp. */ +void heatmap_add_point_with_stamp(heatmap_t* h, unsigned x, unsigned y, const heatmap_stamp_t* stamp); + +/* Adds a single weighted point to the heatmap using the default stamp. */ +void heatmap_add_weighted_point(heatmap_t* h, unsigned x, unsigned y, float w); +/* Adds a single weighted point to the heatmap using a given stamp. */ +void heatmap_add_weighted_point_with_stamp(heatmap_t* h, unsigned x, unsigned y, float w, const heatmap_stamp_t* stamp); + +/* Renders an image of the heatmap into the given colorbuf. + * + * colorbuf: A buffer large enough to hold 4*heatmap_width*heatmap_height + * unsigned chars. These chars are the RGBA values of the pixels. + * + * If colorbuf is NULL, a new large enough buffer will be malloc'd. + * + * return: A pointer to the given colorbuf is returned. It is the caller's + * responsibility to free that buffer. If no colorbuf is given (NULL), + * a newly malloc'd buffer is returned. This buffer needs to be free'd + * by the caller whenever it is not used anymore. + */ +unsigned char* heatmap_render_default_to(const heatmap_t* h, unsigned char* colorbuf); + +/* Renders an RGB image of the heatmap into the given colorbuf, + * using a given colorscheme. + * + * colorscheme: See the description of heatmap_colorscheme_t for more details. + * + * For details on the colorbuf and the return value, refer to the documentation + * of `heatmap_render_default_to`. + */ +unsigned char* heatmap_render_to(const heatmap_t* h, const heatmap_colorscheme_t* colorscheme, unsigned char* colorbuf); + +/* Renders an RGB image of the heatmap into the given colorbuf, + * using a given colorscheme. + * + * colorscheme: See the description of heatmap_colorscheme_t for more details. + * + * saturation: The heatmap will be truncated at the given heat value, meaning + * all spots hotter than `saturation` will be assigned the same + * color as the hottest color on the scale. + * + * For details on the colorbuf and the return value, refer to the documentation + * of `heatmap_render_default_to`. + */ +unsigned char* heatmap_render_saturated_to(const heatmap_t* h, const heatmap_colorscheme_t* colorscheme, float saturation, unsigned char* colorbuf); + +/* Creates a new stamp COPYING the given w*h floats in data. + * + * w, h: The width/height of the stamp, in pixels. + * data: exactly w*h float values which will be added to the heatmap centered + * around every datapoint drawn onto the heatmap. + * + * For more information about stamps, read `heatmap_stamp_t`'s documentation. + */ +heatmap_stamp_t* heatmap_stamp_load(unsigned w, unsigned h, float* data); + +/* Generates a default round stamp of a given radius. This means the stamp will + * have a size of 2*radius+1 square. The default stamp is just a spherical + * gradient around the center. + * + * For more information about stamps, read `heatmap_stamp_t`'s documentation. + */ +heatmap_stamp_t* heatmap_stamp_gen(unsigned radius); + +/* Generates a stamp just like `heatmap_stamp_gen` but calls the given + * `distshape` function in order to determine the value of every single pixel. + * + * distshape: Function which gets called for every pixel. The only argument + * given to that function is the distance from the centre, 0 being + * exactly on the centre and 1 being one-behind the radius. + * One minus the returned value, clamped to [0,1] will be the + * pixel's value. + * + * For more information about stamps, read `heatmap_stamp_t`'s documentation. + */ +heatmap_stamp_t* heatmap_stamp_gen_nonlinear(unsigned radius, float (*distshape)(float)); + +/* Frees up all memory taken by the stamp. */ +void heatmap_stamp_free(heatmap_stamp_t* s); + +/* Create a new colorscheme using a COPY of the given `ncolors` `colors`. + * + * colors: a buffer containing RGBA colors to use when rendering the heatmap. + * + * For more information about colorschemes, read `heatmap_colorscheme_t`'s + * documentation. + */ +heatmap_colorscheme_t* heatmap_colorscheme_load(const unsigned char* colors, size_t ncolors); + +/* Frees up all memory taken by the colorscheme. */ +void heatmap_colorscheme_free(heatmap_colorscheme_t* cs); + +extern const heatmap_colorscheme_t* heatmap_cs_default; + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#ifdef __cplusplus +/* C++ wrapper API, but TODO: is this even necessary? */ +namespace heatmap { +} +#endif + +#endif /* _HEATMAP_H */ + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 967425e..fba3111 100755 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,9 +1,9 @@ -set(QLEVER_PETRIMAPS_INCLUDE_DIR ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_BINARY_DIR}) - -include_directories( - ${QLEVER_PETRIMAPS_INCLUDE_DIR} -) - -add_subdirectory(util) -add_subdirectory(3rdparty) -add_subdirectory(qlever-petrimaps) +set(QLEVER_PETRIMAPS_INCLUDE_DIR ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_BINARY_DIR}) + +include_directories( + ${QLEVER_PETRIMAPS_INCLUDE_DIR} +) + +add_subdirectory(util) +add_subdirectory(3rdparty) +add_subdirectory(qlever-petrimaps) diff --git a/src/qlever-petrimaps/CMakeLists.txt b/src/qlever-petrimaps/CMakeLists.txt index 444ceb5..d484866 100755 --- a/src/qlever-petrimaps/CMakeLists.txt +++ b/src/qlever-petrimaps/CMakeLists.txt @@ -1,44 +1,44 @@ -file(GLOB_RECURSE QLEVER_PETRIMAPS_SRC *.cpp) -find_package(PNG REQUIRED) - - -set(qlever_petrimaps_main PetriMapsMain.cpp) - -list(REMOVE_ITEM QLEVER_PETRIMAPS_SRC ${qlever_petrimaps_main}) - -message("PNG INCLUDE:" ${PNG_INCLUDE_DIRS} "A") - -include_directories( - ${QLEVER_PETRIMAPS_INCLUDE_DIR} - ${PNG_INCLUDE_DIRS}_ -) - -add_executable(petrimaps ${qlever_petrimaps_main}) -add_library(qlever_petrimaps_dep ${QLEVER_PETRIMAPS_SRC}) - -add_custom_command( - OUTPUT index.h - COMMAND cd ${PROJECT_BINARY_DIR}/../web/ && xxd -i index.html > ${CMAKE_CURRENT_BINARY_DIR}/index.h - DEPENDS "${PROJECT_BINARY_DIR}/../web/index.html" - VERBATIM -) - -add_custom_command( - OUTPUT style.h - COMMAND cd ${PROJECT_BINARY_DIR}/../web/ && sed -e "s/^\\s*//g" -e "s/\\s\\+/ /g" style.css | tr -d '\\n' > build.css && xxd -i build.css > ${CMAKE_CURRENT_BINARY_DIR}/style.h - DEPENDS "${PROJECT_BINARY_DIR}/../web/style.css" - VERBATIM -) - -add_custom_command( - OUTPUT build.h - COMMAND cd ${PROJECT_BINARY_DIR}/../web/ && java -jar closurec/compiler.jar -W QUIET -O SIMPLE leaflet.js NonTiledLayer.js script.js > build.js && xxd -i build.js > ${CMAKE_CURRENT_BINARY_DIR}/build.h && rm build.js - DEPENDS "${PROJECT_BINARY_DIR}/../web/script.js" "${PROJECT_BINARY_DIR}/../web/leaflet.js" "${PROJECT_BINARY_DIR}/../web/leaflet-heat.js" - VERBATIM -) - -add_custom_target(htmlfiles DEPENDS index.h build.h style.h) - -add_dependencies(qlever_petrimaps_dep htmlfiles) - -target_link_libraries(petrimaps qlever_petrimaps_dep 3rdparty_dep util ${PNG_LIBRARIES} -lpthread -lcurl) +file(GLOB_RECURSE QLEVER_PETRIMAPS_SRC *.cpp) +find_package(PNG REQUIRED) + + +set(qlever_petrimaps_main PetriMapsMain.cpp) + +list(REMOVE_ITEM QLEVER_PETRIMAPS_SRC ${qlever_petrimaps_main}) + +message("PNG INCLUDE:" ${PNG_INCLUDE_DIRS} "A") + +include_directories( + ${QLEVER_PETRIMAPS_INCLUDE_DIR} + ${PNG_INCLUDE_DIRS}_ +) + +add_executable(petrimaps ${qlever_petrimaps_main}) +add_library(qlever_petrimaps_dep ${QLEVER_PETRIMAPS_SRC}) + +add_custom_command( + OUTPUT index.h + COMMAND cd ${PROJECT_BINARY_DIR}/../web/ && xxd -i index.html > ${CMAKE_CURRENT_BINARY_DIR}/index.h + DEPENDS "${PROJECT_BINARY_DIR}/../web/index.html" + VERBATIM +) + +add_custom_command( + OUTPUT style.h + COMMAND cd ${PROJECT_BINARY_DIR}/../web/ && sed -e "s/^\\s*//g" -e "s/\\s\\+/ /g" style.css | tr -d '\\n' > build.css && xxd -i build.css > ${CMAKE_CURRENT_BINARY_DIR}/style.h + DEPENDS "${PROJECT_BINARY_DIR}/../web/style.css" + VERBATIM +) + +add_custom_command( + OUTPUT build.h + COMMAND cd ${PROJECT_BINARY_DIR}/../web/ && java -jar closurec/compiler.jar -W QUIET -O SIMPLE leaflet.js NonTiledLayer.js script.js > build.js && xxd -i build.js > ${CMAKE_CURRENT_BINARY_DIR}/build.h && rm build.js + DEPENDS "${PROJECT_BINARY_DIR}/../web/script.js" "${PROJECT_BINARY_DIR}/../web/leaflet.js" "${PROJECT_BINARY_DIR}/../web/leaflet-heat.js" + VERBATIM +) + +add_custom_target(htmlfiles DEPENDS index.h build.h style.h) + +add_dependencies(qlever_petrimaps_dep htmlfiles) + +target_link_libraries(petrimaps qlever_petrimaps_dep 3rdparty_dep util ${PNG_LIBRARIES} -lpthread -lcurl) diff --git a/src/qlever-petrimaps/GeomCache.cpp b/src/qlever-petrimaps/GeomCache.cpp index c4a667e..d03aefb 100644 --- a/src/qlever-petrimaps/GeomCache.cpp +++ b/src/qlever-petrimaps/GeomCache.cpp @@ -1,1163 +1,1163 @@ -// Copyright 2022, University of Freiburg, -// Chair of Algorithms and Data Structures. -// Authors: Patrick Brosi - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include "qlever-petrimaps/GeomCache.h" -#include "qlever-petrimaps/Misc.h" -#include "qlever-petrimaps/server/Requestor.h" -#include "util/Misc.h" -#include "util/geo/Geo.h" -#include "util/geo/PolyLine.h" -#include "util/log/Log.h" - -using petrimaps::GeomCache; -using util::geo::DPoint; -using util::geo::FPoint; -using util::geo::latLngToWebMerc; - -const static std::string QUERY = - "PREFIX geo: " - "SELECT DISTINCT ?geometry WHERE {" - " ?osm_id geo:hasGeometry ?geometry " - " } INTERNAL SORT BY ?geometry"; - -const static std::string COUNT_QUERY = - "PREFIX geo: " - "SELECT (COUNT(?geometry) as ?count) WHERE { " - "SELECT DISTINCT ?geometry WHERE {" - " ?osm_id geo:hasGeometry ?geometry " - "} INTERNAL SORT BY ?geometry }"; - -const static std::string QUERY_WD = - "PREFIX wdt: " - "SELECT DISTINCT ?coord WHERE {" - " ?ob wdt:P625 ?coord" - "} INTERNAL SORT BY ?coord"; - -const static std::string COUNT_QUERY_WD = - "PREFIX wdt: " - "SELECT (COUNT(?coord) as ?count) WHERE { " - "SELECT DISTINCT ?coord WHERE {" - " ?ob wdt:P625 ?coord" - "} INTERNAL SORT BY ?coord }"; - -// _____________________________________________________________________________ -const std::string& GeomCache::getQuery(const std::string& backendUrl) const { - bool is_wd = util::endsWith(backendUrl, "wikidata") || - util::endsWith(backendUrl, "dblp-plus"); - return is_wd ? QUERY_WD : QUERY; -} - -// _____________________________________________________________________________ -const std::string& GeomCache::getCountQuery( - const std::string& backendUrl) const { - bool is_wd = util::endsWith(backendUrl, "wikidata") || - util::endsWith(backendUrl, "dblp-plus"); - return is_wd ? COUNT_QUERY_WD : COUNT_QUERY; -} - -// _____________________________________________________________________________ -size_t GeomCache::writeCbString(void* contents, size_t size, size_t nmemb, - void* userp) { - ((std::string*)userp)->append((char*)contents, size * nmemb); - return size * nmemb; -} - -// _____________________________________________________________________________ -size_t GeomCache::writeCb(void* contents, size_t size, size_t nmemb, - void* userp) { - size_t realsize = size * nmemb; - try { - static_cast(userp)->parse(static_cast(contents), - realsize); - } catch (...) { - static_cast(userp)->_exceptionPtr = std::current_exception(); - return CURLE_WRITE_ERROR; - } - return realsize; -} - -// _____________________________________________________________________________ -size_t GeomCache::writeCbIds(void* contents, size_t size, size_t nmemb, - void* userp) { - size_t realsize = size * nmemb; - try { - static_cast(userp)->parseIds(static_cast(contents), - realsize); - } catch (...) { - static_cast(userp)->_exceptionPtr = std::current_exception(); - return CURLE_WRITE_ERROR; - } - return realsize; -} - -// _____________________________________________________________________________ -size_t GeomCache::writeCbCount(void* contents, size_t size, size_t nmemb, - void* userp) { - size_t realsize = size * nmemb; - try { - static_cast(userp)->parseCount( - static_cast(contents), realsize); - } catch (...) { - static_cast(userp)->_exceptionPtr = std::current_exception(); - return CURLE_WRITE_ERROR; - } - return realsize; -} - -// _____________________________________________________________________________ -void GeomCache::parse(const char* c, size_t size) { - _loadStatusStage = _LoadStatusStages::Parse; - - const char* start = c; - while (c < start + size) { - if (_raw.size() < 10000) _raw.push_back(*c); - switch (_state) { - case IN_HEADER: - if (*c == '\n') { - _state = IN_ROW; - c++; - continue; - } else { - c++; - continue; - } - case IN_ROW: - if (*c == '\t' || *c == '\n') { - // bool isGeom = util::endsWith( - // _dangling, "^^"); - - bool isGeom = true; - - auto p = _dangling.rfind("\"POINT(", 0); - - // if the previous was not a multi geometry, and if the strings - // match exactly, re-use the geometry - if (isGeom && _prev == _dangling && _lastQidToId.qid == 0) { - IdMapping idm{0, _lastQidToId.id}; - _lastQidToId = idm; - _qidToIdF.write(reinterpret_cast(&idm), - sizeof(IdMapping)); - _qidToIdFSize++; - } else if (isGeom && p != std::string::npos) { - _curUniqueGeom++; - p += 7; - auto point = parsePoint(_dangling, p); - if (pointValid(point)) { - _pointsF.write(reinterpret_cast(&point), - sizeof(util::geo::FPoint)); - _pointsFSize++; - IdMapping idm{0, _pointsFSize - 1}; - _lastQidToId = idm; - _qidToIdF.write(reinterpret_cast(&idm), - sizeof(IdMapping)); - _qidToIdFSize++; - } else { - IdMapping idm{0, std::numeric_limits::max()}; - _lastQidToId = idm; - _qidToIdF.write(reinterpret_cast(&idm), - sizeof(IdMapping)); - _qidToIdFSize++; - } - } else if (isGeom && (p = _dangling.rfind("\"LINESTRING(", 0)) != - std::string::npos) { - _curUniqueGeom++; - p += 12; - const auto& line = parseLineString(_dangling, p); - if (line.size() == 0) { - IdMapping idm{0, std::numeric_limits::max()}; - _lastQidToId = idm; - _qidToIdF.write(reinterpret_cast(&idm), - sizeof(IdMapping)); - _qidToIdFSize++; - } else { - _linesF.write(reinterpret_cast(&_linePointsFSize), - sizeof(size_t)); - _linesFSize++; - insertLine(line, false); - - IdMapping idm{0, I_OFFSET + _linesFSize - 1}; - _lastQidToId = idm; - _qidToIdF.write(reinterpret_cast(&idm), - sizeof(IdMapping)); - _qidToIdFSize++; - } - } else if (isGeom && (p = _dangling.rfind("\"MULTILINESTRING(", 0)) != - std::string::npos) { - _curUniqueGeom++; - p += 17; - size_t i = 0; - while ((p = _dangling.find("(", p + 1)) != std::string::npos) { - const auto& line = parseLineString(_dangling, p + 1); - if (line.size() == 0) { - if (i == 0) { - IdMapping idm{0, std::numeric_limits::max()}; - _lastQidToId = idm; - _qidToIdF.write(reinterpret_cast(&idm), - sizeof(IdMapping)); - _qidToIdFSize++; - } - } else { - _linesF.write(reinterpret_cast(&_linePointsFSize), - sizeof(size_t)); - _linesFSize++; - insertLine(line, false); - - IdMapping idm{i == 0 ? 0 : 1, I_OFFSET + _linesFSize - 1}; - _lastQidToId = idm; - _qidToIdF.write(reinterpret_cast(&idm), - sizeof(IdMapping)); - _qidToIdFSize++; - } - i++; - } - if (i == 0) { - IdMapping idm{0, std::numeric_limits::max()}; - _lastQidToId = idm; - _qidToIdF.write(reinterpret_cast(&idm), - sizeof(IdMapping)); - _qidToIdFSize++; - } - } else if (isGeom && (p = _dangling.rfind("\"POLYGON(", 0)) != - std::string::npos) { - _curUniqueGeom++; - p += 9; - size_t i = 0; - while ((p = _dangling.find("(", p + 1)) != std::string::npos) { - const auto& line = parseLineString(_dangling, p + 1); - if (line.size() == 0) { - if (i == 0) { - IdMapping idm{0, std::numeric_limits::max()}; - _lastQidToId = idm; - _qidToIdF.write(reinterpret_cast(&idm), - sizeof(IdMapping)); - _qidToIdFSize++; - } - } else { - _linesF.write(reinterpret_cast(&_linePointsFSize), - sizeof(size_t)); - _linesFSize++; - insertLine(line, true); - - IdMapping idm{i == 0 ? 0 : 1, I_OFFSET + _linesFSize - 1}; - _lastQidToId = idm; - _qidToIdF.write(reinterpret_cast(&idm), - sizeof(IdMapping)); - _qidToIdFSize++; - } - i++; - } - if (i == 0) { - IdMapping idm{0, std::numeric_limits::max()}; - _lastQidToId = idm; - _qidToIdF.write(reinterpret_cast(&idm), - sizeof(IdMapping)); - _qidToIdFSize++; - } - } else if (isGeom && (p = _dangling.rfind("\"MULTIPOLYGON(", 0)) != - std::string::npos) { - _curUniqueGeom++; - p += 13; - size_t i = 0; - while ((p = _dangling.find("(", p + 1)) != std::string::npos) { - if (_dangling[p + 1] == '(') p++; - const auto& line = parseLineString(_dangling, p + 1); - if (line.size() == 0) { - if (i == 0) { - IdMapping idm{0, std::numeric_limits::max()}; - _lastQidToId = idm; - _qidToIdF.write(reinterpret_cast(&idm), - sizeof(IdMapping)); - _qidToIdFSize++; - } - } else { - _linesF.write(reinterpret_cast(&_linePointsFSize), - sizeof(size_t)); - _linesFSize++; - insertLine(line, true); - - IdMapping idm{i == 0 ? 0 : 1, I_OFFSET + _linesFSize - 1}; - _lastQidToId = idm; - _qidToIdF.write(reinterpret_cast(&idm), - sizeof(IdMapping)); - _qidToIdFSize++; - } - i++; - } - if (i == 0) { - IdMapping idm{0, std::numeric_limits::max()}; - _lastQidToId = idm; - _qidToIdF.write(reinterpret_cast(&idm), - sizeof(IdMapping)); - _qidToIdFSize++; - } - } else { - IdMapping idm{0, std::numeric_limits::max()}; - _lastQidToId = idm; - _qidToIdF.write(reinterpret_cast(&idm), - sizeof(IdMapping)); - _qidToIdFSize++; - } - - if (*c == '\n') { - _curRow++; - if (_curRow % 1000000 == 0) { - LOG(INFO) << "[GEOMCACHE] " - << "@ row " << _curRow << " (" << std::fixed - << std::setprecision(2) << getLoadStatusPercent() - << "%, " << _pointsFSize << " points, " << _linesFSize - << " (open) polygons)"; - } - _prev = _dangling; - _dangling.clear(); - c++; - continue; - } else { - _prev = _dangling; - _dangling.clear(); - c++; - continue; - } - } - - _dangling += toupper(*c); - c++; - - break; - default: - break; - } - } -} - -// _____________________________________________________________________________ -double GeomCache::getLoadStatusPercent(bool total) { - /* - There are 2 loading stages: Parse, afterwards ParseIds. - Because ParseIds is usually pretty short, we merge the progress of both stages - to one total progress. Progress is calculated by _curRow / _totalSize, which - are handled by each stage individually. - */ - if (_totalSize == 0) { - return 0.0; - } - - if (!total) { - return std::atomic(_curRow) / static_cast(_totalSize) * - 100.0; - } - - double parsePercent = 95.0; - double parseIdsPercent = 5.0; - double totalPercent = 0.0; - switch (_loadStatusStage) { - case _LoadStatusStages::Parse: - totalPercent = std::atomic(_curRow) / - static_cast(_totalSize) * parsePercent; - break; - case _LoadStatusStages::ParseIds: - totalPercent = parsePercent; - totalPercent += std::atomic(_curRow) / - static_cast(_totalSize) * parseIdsPercent; - break; - } - - return totalPercent; -} - -// _____________________________________________________________________________ -int GeomCache::getLoadStatusStage() { - return _loadStatusStage; -} - -// _____________________________________________________________________________ -void GeomCache::parseIds(const char* c, size_t size) { - _loadStatusStage = _LoadStatusStages::ParseIds; - - size_t lastQid = -1; - for (size_t i = 0; i < size; i++) { - if (_raw.size() < 10000) _raw.push_back(c[i]); - _curId.bytes[_curByte] = c[i]; - _curByte = (_curByte + 1) % 8; - - if (_curByte == 0) { - if (_curRow % 1000000 == 0) { - LOG(INFO) << "[GEOMCACHE] " - << "@ row " << _curRow << " (" << std::fixed - << std::setprecision(2) << getLoadStatusPercent() - << "%, " << _pointsFSize << " points, " << _linesFSize - << " (open) polygons)"; - } - - if (_curRow < _qidToId.size() && _qidToId[_curRow].qid == 0) { - // if we have two consecutive and equivalent QLever ids, the geometry - // was returned multiple times in the fill query. This can happen if the - // same WKT string is used in multiple distinct objects, but then stored - // in qlever using the same internal qlever ID. To avoid a false multi- - // plication of results (all geoms of matching qlever ID are joined), we - // set such repeated qlever IDs to an unnsed dummy value. - // NOTE: because of the DISTNCT queries above, this should never happen - if (lastQid == _curId.val) { - LOG(WARN) << "Found duplicate internal qlever ID " << _curId.val - << " for row " << _curRow - << ", ignoring this geometry duplicate!"; - _qidToId[_curRow].qid = -1; - } else { - _qidToId[_curRow].qid = _curId.val; - } - lastQid = _curId.val; - if (_curId.val > _maxQid) _maxQid = _curId.val; - } else { - LOG(WARN) << "The results for the binary IDs are out of sync."; - LOG(WARN) << "_curRow: " << _curRow - << " _qleverIdInt.size: " << _qidToId.size() - << " cur val: " << _qidToId[_curRow].qid; - } - - // if a qlever entity contained multiple geometries (MULTILINESTRING, - // MULTIPOLYGON, MULTIPOINT), they appear consecutively in - // _qidToId; continuation geometries are marked by a - // preliminary qlever ID of 1, while the first geometry always has a - // preliminary id of 0 - while (_curRow < _qidToId.size() - 1 && _qidToId[_curRow + 1].qid == 1) { - _qidToId[++_curRow].qid = _curId.val; - } - - _curRow++; - } - } -} - -// _____________________________________________________________________________ -void GeomCache::parseCount(const char* c, size_t size) { - for (size_t i = 0; i < size; i++) { - if (_raw.size() < 10000) _raw.push_back(c[i]); - if (c[i] == '\n') _state = IN_ROW; - if (_state == IN_ROW) _dangling += c[i]; - } -} - -// _____________________________________________________________________________ -size_t GeomCache::requestSize() { - _state = IN_HEADER; - _dangling.clear(); - _dangling.reserve(10000); - _raw.clear(); - _raw.reserve(10000); - - CURLcode res; - char errbuf[CURL_ERROR_SIZE]; - - if (_curl) { - auto qUrl = queryUrl(getCountQuery(_backendUrl), 0, 1); - curl_easy_setopt(_curl, CURLOPT_URL, qUrl.c_str()); - curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, GeomCache::writeCbCount); - curl_easy_setopt(_curl, CURLOPT_WRITEDATA, this); - curl_easy_setopt(_curl, CURLOPT_ERRORBUFFER, errbuf); - curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYPEER, false); - curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYHOST, false); - curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, 0); - - // set headers - struct curl_slist* headers = 0; - headers = curl_slist_append(headers, "Accept: text/tab-separated-values"); - curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, headers); - - // accept any compression supported - curl_easy_setopt(_curl, CURLOPT_ACCEPT_ENCODING, ""); - res = curl_easy_perform(_curl); - - long httpCode = 0; - curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, &httpCode); - - curl_slist_free_all(headers); - - if (httpCode != 200) { - std::stringstream ss; - ss << "QLever backend returned status code " << httpCode - << " during count query"; - ss << "\n"; - ss << _raw; - throw std::runtime_error(ss.str()); - } - - if (_exceptionPtr) std::rethrow_exception(_exceptionPtr); - } else { - LOG(ERROR) << "[GEOMCACHE] Failed to perform curl request."; - return -1; - } - - // check if there was an error - if (res != CURLE_OK) { - size_t len = strlen(errbuf); - if (len > 0) { - LOG(ERROR) << "[GEOMCACHE] " << errbuf; - } else { - LOG(ERROR) << "[GEOMCACHE] " << curl_easy_strerror(res); - } - } - - std::istringstream iss(_dangling); - size_t ret; - iss >> ret; - return ret; -} - -// _____________________________________________________________________________ -void GeomCache::requestPart(size_t offset) { - _state = IN_HEADER; - _dangling.clear(); - _dangling.reserve(10000); - _raw.clear(); - _raw.reserve(10000); - - CURLcode res; - char errbuf[CURL_ERROR_SIZE]; - - if (_curl) { - auto qUrl = queryUrl(getQuery(_backendUrl), offset, 1000000); - curl_easy_setopt(_curl, CURLOPT_URL, qUrl.c_str()); - curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, GeomCache::writeCb); - curl_easy_setopt(_curl, CURLOPT_WRITEDATA, this); - curl_easy_setopt(_curl, CURLOPT_ERRORBUFFER, errbuf); - curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYPEER, false); - curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYHOST, false); - curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, 0); - - // set headers - struct curl_slist* headers = 0; - headers = curl_slist_append(headers, "Accept: text/tab-separated-values"); - curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, headers); - - // accept any compression supported - curl_easy_setopt(_curl, CURLOPT_ACCEPT_ENCODING, ""); - res = curl_easy_perform(_curl); - - long httpCode = 0; - curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, &httpCode); - - curl_slist_free_all(headers); - - if (httpCode != 200) { - std::stringstream ss; - ss << "QLever backend returned status code " << httpCode - << " during query (offset=" << offset << ")"; - ss << "\n"; - ss << _raw; - throw std::runtime_error(ss.str()); - } - - if (_exceptionPtr) std::rethrow_exception(_exceptionPtr); - } else { - LOG(ERROR) << "[GEOMCACHE] Failed to perform curl request."; - return; - } - - // check if there was an error - if (res != CURLE_OK) { - size_t len = strlen(errbuf); - if (len > 0) { - LOG(ERROR) << "[GEOMCACHE] " << errbuf; - } else { - LOG(ERROR) << "[GEOMCACHE] " << curl_easy_strerror(res); - } - } -} - -// _____________________________________________________________________________ -void GeomCache::request() { - _totalSize = requestSize(); - - if (_totalSize == 0) { - throw std::runtime_error( - "Could not determine number of rows, or number of rows was 0"); - } - - _state = IN_HEADER; - _points.clear(); - _lines.clear(); - _linePoints.clear(); - _qidToId.clear(); - - _lastQidToId = {-1, -1}; - - _raw.clear(); - _raw.reserve(100000); - - char* pointsFName = strdup("pointsXXXXXX"); - int i = mkstemp(pointsFName); - if (i == -1) throw std::runtime_error("Could not create temporary file"); - _pointsF.open(pointsFName, std::ios::out | std::ios::in | std::ios::binary); - - char* linePointsFName = strdup("linepointsXXXXXX"); - i = mkstemp(linePointsFName); - if (i == -1) throw std::runtime_error("Could not create temporary file"); - _linePointsF.open(linePointsFName, - std::ios::out | std::ios::in | std::ios::binary); - - char* linesFName = strdup("linesXXXXXX"); - i = mkstemp(linesFName); - if (i == -1) throw std::runtime_error("Could not create temporary file"); - _linesF.open(linesFName, std::ios::out | std::ios::in | std::ios::binary); - - char* qidToIdFName = strdup("qidtoidXXXXXX"); - i = mkstemp(qidToIdFName); - if (i == -1) throw std::runtime_error("Could not create temporary file"); - _qidToIdF.open(qidToIdFName, std::ios::out | std::ios::in | std::ios::binary); - - // immediately unlink - unlink(pointsFName); - unlink(linePointsFName); - unlink(linesFName); - unlink(qidToIdFName); - - free(pointsFName); - free(linePointsFName); - free(linesFName); - free(qidToIdFName); - - _pointsFSize = 0; - _linePointsFSize = 0; - _linesFSize = 0; - _qidToIdFSize = 0; - - _curRow = 0; - _curUniqueGeom = 0; - - size_t lastNum = -1; - - LOG(INFO) << "[GEOMCACHE] Total request size: " << _totalSize; - LOG(INFO) << "[GEOMCACHE] Query is:\n" << getQuery(_backendUrl); - - while (lastNum != 0) { - size_t offset = _curRow; - requestPart(offset); - lastNum = _curRow - offset; - } - - if (i == -1) throw std::runtime_error("Could not create temporary file"); - - LOG(INFO) << "[GEOMCACHE] Building vectors..."; - - _points.resize(_pointsFSize); - _pointsF.seekg(0); - _pointsF.read(reinterpret_cast(&_points[0]), - sizeof(util::geo::FPoint) * _pointsFSize); - _pointsF.close(); - - _linePoints.resize(_linePointsFSize); - _linePointsF.seekg(0); - _linePointsF.read(reinterpret_cast(&_linePoints[0]), - sizeof(util::geo::Point) * _linePointsFSize); - _linePointsF.close(); - - _lines.resize(_linesFSize); - _linesF.seekg(0); - _linesF.read(reinterpret_cast(&_lines[0]), - sizeof(size_t) * _linesFSize); - _linesF.close(); - - _qidToId.resize(_qidToIdFSize); - _qidToIdF.seekg(0); - _qidToIdF.read(reinterpret_cast(&_qidToId[0]), - sizeof(IdMapping) * _qidToIdFSize); - _qidToIdF.close(); - - LOG(INFO) << "[GEOMCACHE] Done"; - LOG(INFO) << "[GEOMCACHE] Received " << _curUniqueGeom << " unique geoms"; - LOG(INFO) << "[GEOMCACHE] Received " << _points.size() << " points and " - << _lines.size() << " lines"; -} - -// _____________________________________________________________________________ -void GeomCache::requestIds() { - _curByte = 0; - _curRow = 0; - _curUniqueGeom = 0; - _maxQid = 0; - _exceptionPtr = 0; - - LOG(INFO) << "[GEOMCACHE] Query is " << getQuery(_backendUrl); - - if (_curl) { - auto qUrl = queryUrl(getQuery(_backendUrl), 0, MAXROWS); - LOG(INFO) << "[GEOMCACHE] Binary ID query URL is " << qUrl; - curl_easy_setopt(_curl, CURLOPT_URL, qUrl.c_str()); - curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, GeomCache::writeCbIds); - curl_easy_setopt(_curl, CURLOPT_WRITEDATA, this); - curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYPEER, false); - curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYHOST, false); - curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, 0); - - // set headers - struct curl_slist* headers = 0; - headers = curl_slist_append(headers, "Accept: application/octet-stream"); - curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, headers); - - // accept any compression supported - curl_easy_setopt(_curl, CURLOPT_ACCEPT_ENCODING, ""); - curl_easy_perform(_curl); - - long httpCode = 0; - curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, &httpCode); - - curl_slist_free_all(headers); - - if (httpCode != 200) { - std::stringstream ss; - ss << "QLever backend returned status code " << httpCode; - ss << "\n"; - ss << _raw; - throw std::runtime_error(ss.str()); - } - - if (_exceptionPtr) std::rethrow_exception(_exceptionPtr); - } else { - LOG(ERROR) << "[GEOMCACHE] Failed to perform curl request."; - } - - LOG(INFO) << "[GEOMCACHE] Received " << _curRow << " rows"; - LOG(INFO) << "[GEOMCACHE] Max QLever id was " << _maxQid; - LOG(INFO) << "[GEOMCACHE] Done"; - - // sorting by qlever id - LOG(INFO) << "[GEOMCACHE] Sorting results by qlever ID..."; - std::stable_sort(_qidToId.begin(), _qidToId.end()); - LOG(INFO) << "[GEOMCACHE] ... done"; -} - -// _____________________________________________________________________________ -std::string GeomCache::queryUrl(std::string query, size_t offset, - size_t limit) const { - std::stringstream ss; - - if (util::toLower(query).find("limit") == std::string::npos) { - query += " LIMIT " + std::to_string(limit); - } - - if (util::toLower(query).find("offset") == std::string::npos) { - query += " OFFSET " + std::to_string(offset); - } - - auto esc = curl_easy_escape(_curl, query.c_str(), query.size()); - - ss << _backendUrl << "/?send=" << std::to_string(MAXROWS) << "&query=" << esc; - - curl_free(esc); - - return ss.str(); -} - -// _____________________________________________________________________________ -bool GeomCache::pointValid(const FPoint& p) { - if (p.getY() > std::numeric_limits::max()) return false; - if (p.getY() < std::numeric_limits::lowest()) return false; - if (p.getX() > std::numeric_limits::max()) return false; - if (p.getX() < std::numeric_limits::lowest()) return false; - - return true; -} - -// _____________________________________________________________________________ -bool GeomCache::pointValid(const DPoint& p) { - if (p.getY() > std::numeric_limits::max()) return false; - if (p.getY() < std::numeric_limits::lowest()) return false; - if (p.getX() > std::numeric_limits::max()) return false; - if (p.getX() < std::numeric_limits::lowest()) return false; - - return true; -} - -// _____________________________________________________________________________ -util::geo::DLine GeomCache::parseLineString(const std::string& a, - size_t p) const { - util::geo::DLine line; - line.reserve(2); - auto end = memchr(a.c_str() + p, ')', a.size() - p); - assert(end); - - while (true) { - auto point = latLngToWebMerc(DPoint( - util::atof(a.c_str() + p, 10), - util::atof( - static_cast(memchr(a.c_str() + p, ' ', a.size() - p)) + - 1, - 10))); - - if (pointValid(point)) line.push_back(point); - - auto n = memchr(a.c_str() + p, ',', a.size() - p); - if (!n || n > end) break; - p = static_cast(n) - a.c_str() + 1; - } - - // the 200 is the THRESHOLD from Server.cpp - // return util::geo::densify(util::geo::simplify(line, 3), 200 * 3); - return util::geo::densify(line, 200 * 3); -} - -// _____________________________________________________________________________ -util::geo::FPoint GeomCache::parsePoint(const std::string& a, size_t p) const { - auto point = latLngToWebMerc(FPoint( - util::atof(a.c_str() + p, 10), - util::atof( - static_cast(memchr(a.c_str() + p, ' ', a.size() - p)) + - 1, - 10))); - - return point; -} - -// _____________________________________________________________________________ -std::pair>, size_t> -GeomCache::getRelObjects(const std::vector& ids) const { - // (geom id, result row) - std::vector> ret; - - // in most cases, the return size will be exactly the size of the ids set - ret.reserve(ids.size()); - - // only counts multi-geometries once - size_t numObjects = 0; - - size_t i = 0; - size_t j = 0; - - while (i < ids.size() && j < _qidToId.size()) { - if (ids[i].qid == _qidToId[j].qid) { - size_t prefJ = j; - - while (j < _qidToId.size() && ids[i].qid == _qidToId[j].qid) { - if (ret.size() == 0 || ret.back().second != ids[i].id) numObjects++; - ret.push_back({_qidToId[j].id, ids[i].id}); - j++; - } - - j = prefJ; - i++; - } else if (ids[i].qid < _qidToId[j].qid) { - i++; - } else { - size_t gallop = 1; - do { - if (j + gallop >= _qidToId.size()) { - j = std::lower_bound(_qidToId.begin() + j + gallop / 2, - _qidToId.end(), ids[i]) - - _qidToId.begin(); - break; - } - - if (_qidToId[j + gallop].qid >= ids[i].qid) { - j = std::lower_bound(_qidToId.begin() + j + gallop / 2, - _qidToId.begin() + j + gallop, ids[i]) - - _qidToId.begin(); - break; - } - - gallop *= 2; - - } while (true); - } - } - - return {ret, numObjects}; -} - -// _____________________________________________________________________________ -void GeomCache::insertLine(const util::geo::DLine& l, bool isArea) { - // we also add the line's bounding box here to also - // compress that - const auto& bbox = util::geo::getBoundingBox(l); - - int16_t mainX = (bbox.getLowerLeft().getX() * 10.0) / M_COORD_GRANULARITY; - int16_t mainY = (bbox.getLowerLeft().getY() * 10.0) / M_COORD_GRANULARITY; - - if (mainX != 0 || mainY != 0) { - util::geo::Point p{mCoord(mainX), mCoord(mainY)}; - _linePointsF.write(reinterpret_cast(&p), - sizeof(util::geo::Point)); - _linePointsFSize++; - } - - // add bounding box lower left - int16_t minorXLoc = - (bbox.getLowerLeft().getX() * 10.0) - mainX * M_COORD_GRANULARITY; - int16_t minorYLoc = - (bbox.getLowerLeft().getY() * 10.0) - mainY * M_COORD_GRANULARITY; - - util::geo::Point p{minorXLoc, minorYLoc}; - _linePointsF.write(reinterpret_cast(&p), - sizeof(util::geo::Point)); - _linePointsFSize++; - - // add bounding box upper left - int16_t mainXLoc = (bbox.getUpperRight().getX() * 10.0) / M_COORD_GRANULARITY; - int16_t mainYLoc = (bbox.getUpperRight().getY() * 10.0) / M_COORD_GRANULARITY; - minorXLoc = - (bbox.getUpperRight().getX() * 10.0) - mainXLoc * M_COORD_GRANULARITY; - minorYLoc = - (bbox.getUpperRight().getY() * 10.0) - mainYLoc * M_COORD_GRANULARITY; - if (mainXLoc != mainX || mainYLoc != mainY) { - mainX = mainXLoc; - mainY = mainYLoc; - - util::geo::Point p{mCoord(mainX), mCoord(mainY)}; - _linePointsF.write(reinterpret_cast(&p), - sizeof(util::geo::Point)); - _linePointsFSize++; - } - p = util::geo::Point{minorXLoc, minorYLoc}; - _linePointsF.write(reinterpret_cast(&p), - sizeof(util::geo::Point)); - _linePointsFSize++; - - // add line points - for (const auto& p : l) { - mainXLoc = (p.getX() * 10.0) / M_COORD_GRANULARITY; - mainYLoc = (p.getY() * 10.0) / M_COORD_GRANULARITY; - - if (mainXLoc != mainX || mainYLoc != mainY) { - mainX = mainXLoc; - mainY = mainYLoc; - - util::geo::Point p{mCoord(mainX), mCoord(mainY)}; - _linePointsF.write(reinterpret_cast(&p), - sizeof(util::geo::Point)); - _linePointsFSize++; - } - - int16_t minorXLoc = (p.getX() * 10.0) - mainXLoc * M_COORD_GRANULARITY; - int16_t minorYLoc = (p.getY() * 10.0) - mainYLoc * M_COORD_GRANULARITY; - - util::geo::Point pp{minorXLoc, minorYLoc}; - _linePointsF.write(reinterpret_cast(&pp), - sizeof(util::geo::Point)); - _linePointsFSize++; - } - - // if we have an area, we end in a major coord (which is not possible for - // other types) - if (isArea) { - util::geo::Point p{mCoord(0), mCoord(0)}; - _linePointsF.write(reinterpret_cast(&p), - sizeof(util::geo::Point)); - _linePointsFSize++; - } -} - -// _____________________________________________________________________________ -util::geo::DBox GeomCache::getLineBBox(size_t lid) const { - util::geo::DBox ret; - size_t start = getLine(lid); - - bool s = false; - - double mainX = 0; - double mainY = 0; - for (size_t i = start; i < start + 4; i++) { - // extract real geom - const auto& cur = _linePoints[i]; - - if (isMCoord(cur.getX())) { - mainX = rmCoord(cur.getX()); - mainY = rmCoord(cur.getY()); - continue; - } - - util::geo::DPoint curP((mainX * M_COORD_GRANULARITY + cur.getX()) / 10.0, - (mainY * M_COORD_GRANULARITY + cur.getY()) / 10.0); - - if (!s) { - ret.setLowerLeft(curP); - s = true; - } else { - ret.setUpperRight(curP); - return ret; - } - } - - return ret; -} - -// _____________________________________________________________________________ -std::string GeomCache::indexHashFromDisk(const std::string& fname) { - std::ifstream f(fname, std::ios::binary); - char tmp[100]; - f.read(tmp, 100); - tmp[99] = 0; - - return util::trim(tmp); -} - -// _____________________________________________________________________________ -void GeomCache::fromDisk(const std::string& fname) { - _points.clear(); - _linePoints.clear(); - _lines.clear(); - - std::ifstream f(fname, std::ios::binary); - - // load hash - char tmp[100]; - f.read(tmp, 100); - tmp[99] = 0; - - _indexHash = util::trim(tmp); - - size_t numPoints; - f.read(reinterpret_cast(&numPoints), sizeof(size_t)); - _points.resize(numPoints); - f.read(reinterpret_cast(&_points[0]), - sizeof(util::geo::FPoint) * numPoints); - - f.read(reinterpret_cast(&numPoints), sizeof(size_t)); - _linePoints.resize(numPoints); - f.read(reinterpret_cast(&_linePoints[0]), - sizeof(util::geo::Point) * numPoints); - - f.read(reinterpret_cast(&numPoints), sizeof(size_t)); - _lines.resize(numPoints); - f.read(reinterpret_cast(&_lines[0]), sizeof(size_t) * numPoints); - - f.read(reinterpret_cast(&numPoints), sizeof(size_t)); - _qidToId.resize(numPoints); - f.read(reinterpret_cast(&_qidToId[0]), sizeof(IdMapping) * numPoints); - - f.close(); -} - -// _____________________________________________________________________________ -void GeomCache::serializeToDisk(const std::string& fname) const { - std::ofstream f; - f.open(fname); - - std::string h = _indexHash; - h.insert(h.end(), 99 - h.size(), ' '); - - // null byte is 100 - assert(h.size() == 99); - f.write(h.c_str(), 100); - - size_t num = _points.size(); - f.write(reinterpret_cast(&num), sizeof(size_t)); - f.write(reinterpret_cast(&_points[0]), - sizeof(util::geo::FPoint) * num); - - num = _linePoints.size(); - f.write(reinterpret_cast(&num), sizeof(size_t)); - f.write(reinterpret_cast(&_linePoints[0]), - sizeof(util::geo::Point) * num); - - num = _lines.size(); - f.write(reinterpret_cast(&num), sizeof(size_t)); - f.write(reinterpret_cast(&_lines[0]), sizeof(size_t) * num); - - num = _qidToId.size(); - f.write(reinterpret_cast(&num), sizeof(size_t)); - f.write(reinterpret_cast(&_qidToId[0]), sizeof(IdMapping) * num); - - f.close(); -} - -// _____________________________________________________________________________ -std::string GeomCache::requestIndexHash() { - CURLcode res; - char errbuf[CURL_ERROR_SIZE]; - std::string response; - - if (_curl) { - std::string url = _backendUrl + "/?cmd=get-index-id"; - curl_easy_setopt(_curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, GeomCache::writeCbString); - curl_easy_setopt(_curl, CURLOPT_WRITEDATA, &response); - curl_easy_setopt(_curl, CURLOPT_ERRORBUFFER, errbuf); - curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYPEER, false); - curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYHOST, false); - curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, 0); - - // accept any compression supported - curl_easy_setopt(_curl, CURLOPT_ACCEPT_ENCODING, ""); - res = curl_easy_perform(_curl); - - if (res != CURLE_OK) { - size_t len = strlen(errbuf); - if (len > 0) { - LOG(ERROR) << "[GEOMCACHE] " << errbuf; - } else { - LOG(ERROR) << "[GEOMCACHE] " << curl_easy_strerror(res); - } - - return ""; - } - - long httpCode = 0; - curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, &httpCode); - - if (httpCode != 200) { - LOG(WARN) << "QLever backend returned status code " << httpCode - << " for index hash."; - return ""; - } - - return response; - } else { - LOG(ERROR) << "[GEOMCACHE] Failed to perform curl request for index hash."; - return ""; - } -} - -// _____________________________________________________________________________ -void GeomCache::load(const std::string& cacheDir) { - std::lock_guard guard(_m); - - if (_ready) { - auto indexHash = requestIndexHash(); - if (_indexHash == indexHash) return; - LOG(INFO) << "Loaded index hash (" << _indexHash - << ") and remote index hash (" << indexHash << ") dont match."; - _ready = false; - } - - if (cacheDir.size()) { - std::string backend = getBackendURL(); - util::replaceAll(backend, "/", "_"); - std::string cacheFile = cacheDir + "/" + backend; - auto indexHash = requestIndexHash(); - if (access(cacheFile.c_str(), F_OK) != -1 && - indexHash == indexHashFromDisk(cacheFile)) { - LOG(INFO) << "Reading from cache file " << cacheFile << "..."; - fromDisk(cacheFile); - LOG(INFO) << "done ..."; - } else { - if (access(cacheDir.c_str(), W_OK) != 0) { - std::stringstream ss; - ss << "No write access to cache dir " << cacheDir; - throw std::runtime_error(ss.str()); - } - _indexHash = requestIndexHash(); - LOG(INFO) << "Index hash is '" << _indexHash << "'"; - request(); - requestIds(); - LOG(INFO) << "Serializing to cache file " << cacheFile << "..."; - serializeToDisk(cacheFile); - LOG(INFO) << "done ..."; - } - } else { - _indexHash = requestIndexHash(); - LOG(INFO) << "Index hash is '" << _indexHash << "'"; - request(); - requestIds(); - } - - _ready = true; -} +// Copyright 2022, University of Freiburg, +// Chair of Algorithms and Data Structures. +// Authors: Patrick Brosi + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "qlever-petrimaps/GeomCache.h" +#include "qlever-petrimaps/Misc.h" +#include "qlever-petrimaps/server/Requestor.h" +#include "util/Misc.h" +#include "util/geo/Geo.h" +#include "util/geo/PolyLine.h" +#include "util/log/Log.h" + +using petrimaps::GeomCache; +using util::geo::DPoint; +using util::geo::FPoint; +using util::geo::latLngToWebMerc; + +const static std::string QUERY = + "PREFIX geo: " + "SELECT DISTINCT ?geometry WHERE {" + " ?osm_id geo:hasGeometry ?geometry " + " } INTERNAL SORT BY ?geometry"; + +const static std::string COUNT_QUERY = + "PREFIX geo: " + "SELECT (COUNT(?geometry) as ?count) WHERE { " + "SELECT DISTINCT ?geometry WHERE {" + " ?osm_id geo:hasGeometry ?geometry " + "} INTERNAL SORT BY ?geometry }"; + +const static std::string QUERY_WD = + "PREFIX wdt: " + "SELECT DISTINCT ?coord WHERE {" + " ?ob wdt:P625 ?coord" + "} INTERNAL SORT BY ?coord"; + +const static std::string COUNT_QUERY_WD = + "PREFIX wdt: " + "SELECT (COUNT(?coord) as ?count) WHERE { " + "SELECT DISTINCT ?coord WHERE {" + " ?ob wdt:P625 ?coord" + "} INTERNAL SORT BY ?coord }"; + +// _____________________________________________________________________________ +const std::string& GeomCache::getQuery(const std::string& backendUrl) const { + bool is_wd = util::endsWith(backendUrl, "wikidata") || + util::endsWith(backendUrl, "dblp-plus"); + return is_wd ? QUERY_WD : QUERY; +} + +// _____________________________________________________________________________ +const std::string& GeomCache::getCountQuery( + const std::string& backendUrl) const { + bool is_wd = util::endsWith(backendUrl, "wikidata") || + util::endsWith(backendUrl, "dblp-plus"); + return is_wd ? COUNT_QUERY_WD : COUNT_QUERY; +} + +// _____________________________________________________________________________ +size_t GeomCache::writeCbString(void* contents, size_t size, size_t nmemb, + void* userp) { + ((std::string*)userp)->append((char*)contents, size * nmemb); + return size * nmemb; +} + +// _____________________________________________________________________________ +size_t GeomCache::writeCb(void* contents, size_t size, size_t nmemb, + void* userp) { + size_t realsize = size * nmemb; + try { + static_cast(userp)->parse(static_cast(contents), + realsize); + } catch (...) { + static_cast(userp)->_exceptionPtr = std::current_exception(); + return CURLE_WRITE_ERROR; + } + return realsize; +} + +// _____________________________________________________________________________ +size_t GeomCache::writeCbIds(void* contents, size_t size, size_t nmemb, + void* userp) { + size_t realsize = size * nmemb; + try { + static_cast(userp)->parseIds(static_cast(contents), + realsize); + } catch (...) { + static_cast(userp)->_exceptionPtr = std::current_exception(); + return CURLE_WRITE_ERROR; + } + return realsize; +} + +// _____________________________________________________________________________ +size_t GeomCache::writeCbCount(void* contents, size_t size, size_t nmemb, + void* userp) { + size_t realsize = size * nmemb; + try { + static_cast(userp)->parseCount( + static_cast(contents), realsize); + } catch (...) { + static_cast(userp)->_exceptionPtr = std::current_exception(); + return CURLE_WRITE_ERROR; + } + return realsize; +} + +// _____________________________________________________________________________ +void GeomCache::parse(const char* c, size_t size) { + _loadStatusStage = _LoadStatusStages::Parse; + + const char* start = c; + while (c < start + size) { + if (_raw.size() < 10000) _raw.push_back(*c); + switch (_state) { + case IN_HEADER: + if (*c == '\n') { + _state = IN_ROW; + c++; + continue; + } else { + c++; + continue; + } + case IN_ROW: + if (*c == '\t' || *c == '\n') { + // bool isGeom = util::endsWith( + // _dangling, "^^"); + + bool isGeom = true; + + auto p = _dangling.rfind("\"POINT(", 0); + + // if the previous was not a multi geometry, and if the strings + // match exactly, re-use the geometry + if (isGeom && _prev == _dangling && _lastQidToId.qid == 0) { + IdMapping idm{0, _lastQidToId.id}; + _lastQidToId = idm; + _qidToIdF.write(reinterpret_cast(&idm), + sizeof(IdMapping)); + _qidToIdFSize++; + } else if (isGeom && p != std::string::npos) { + _curUniqueGeom++; + p += 7; + auto point = parsePoint(_dangling, p); + if (pointValid(point)) { + _pointsF.write(reinterpret_cast(&point), + sizeof(util::geo::FPoint)); + _pointsFSize++; + IdMapping idm{0, _pointsFSize - 1}; + _lastQidToId = idm; + _qidToIdF.write(reinterpret_cast(&idm), + sizeof(IdMapping)); + _qidToIdFSize++; + } else { + IdMapping idm{0, std::numeric_limits::max()}; + _lastQidToId = idm; + _qidToIdF.write(reinterpret_cast(&idm), + sizeof(IdMapping)); + _qidToIdFSize++; + } + } else if (isGeom && (p = _dangling.rfind("\"LINESTRING(", 0)) != + std::string::npos) { + _curUniqueGeom++; + p += 12; + const auto& line = parseLineString(_dangling, p); + if (line.size() == 0) { + IdMapping idm{0, std::numeric_limits::max()}; + _lastQidToId = idm; + _qidToIdF.write(reinterpret_cast(&idm), + sizeof(IdMapping)); + _qidToIdFSize++; + } else { + _linesF.write(reinterpret_cast(&_linePointsFSize), + sizeof(size_t)); + _linesFSize++; + insertLine(line, false); + + IdMapping idm{0, I_OFFSET + _linesFSize - 1}; + _lastQidToId = idm; + _qidToIdF.write(reinterpret_cast(&idm), + sizeof(IdMapping)); + _qidToIdFSize++; + } + } else if (isGeom && (p = _dangling.rfind("\"MULTILINESTRING(", 0)) != + std::string::npos) { + _curUniqueGeom++; + p += 17; + size_t i = 0; + while ((p = _dangling.find("(", p + 1)) != std::string::npos) { + const auto& line = parseLineString(_dangling, p + 1); + if (line.size() == 0) { + if (i == 0) { + IdMapping idm{0, std::numeric_limits::max()}; + _lastQidToId = idm; + _qidToIdF.write(reinterpret_cast(&idm), + sizeof(IdMapping)); + _qidToIdFSize++; + } + } else { + _linesF.write(reinterpret_cast(&_linePointsFSize), + sizeof(size_t)); + _linesFSize++; + insertLine(line, false); + + IdMapping idm{i == 0 ? 0 : 1, I_OFFSET + _linesFSize - 1}; + _lastQidToId = idm; + _qidToIdF.write(reinterpret_cast(&idm), + sizeof(IdMapping)); + _qidToIdFSize++; + } + i++; + } + if (i == 0) { + IdMapping idm{0, std::numeric_limits::max()}; + _lastQidToId = idm; + _qidToIdF.write(reinterpret_cast(&idm), + sizeof(IdMapping)); + _qidToIdFSize++; + } + } else if (isGeom && (p = _dangling.rfind("\"POLYGON(", 0)) != + std::string::npos) { + _curUniqueGeom++; + p += 9; + size_t i = 0; + while ((p = _dangling.find("(", p + 1)) != std::string::npos) { + const auto& line = parseLineString(_dangling, p + 1); + if (line.size() == 0) { + if (i == 0) { + IdMapping idm{0, std::numeric_limits::max()}; + _lastQidToId = idm; + _qidToIdF.write(reinterpret_cast(&idm), + sizeof(IdMapping)); + _qidToIdFSize++; + } + } else { + _linesF.write(reinterpret_cast(&_linePointsFSize), + sizeof(size_t)); + _linesFSize++; + insertLine(line, true); + + IdMapping idm{i == 0 ? 0 : 1, I_OFFSET + _linesFSize - 1}; + _lastQidToId = idm; + _qidToIdF.write(reinterpret_cast(&idm), + sizeof(IdMapping)); + _qidToIdFSize++; + } + i++; + } + if (i == 0) { + IdMapping idm{0, std::numeric_limits::max()}; + _lastQidToId = idm; + _qidToIdF.write(reinterpret_cast(&idm), + sizeof(IdMapping)); + _qidToIdFSize++; + } + } else if (isGeom && (p = _dangling.rfind("\"MULTIPOLYGON(", 0)) != + std::string::npos) { + _curUniqueGeom++; + p += 13; + size_t i = 0; + while ((p = _dangling.find("(", p + 1)) != std::string::npos) { + if (_dangling[p + 1] == '(') p++; + const auto& line = parseLineString(_dangling, p + 1); + if (line.size() == 0) { + if (i == 0) { + IdMapping idm{0, std::numeric_limits::max()}; + _lastQidToId = idm; + _qidToIdF.write(reinterpret_cast(&idm), + sizeof(IdMapping)); + _qidToIdFSize++; + } + } else { + _linesF.write(reinterpret_cast(&_linePointsFSize), + sizeof(size_t)); + _linesFSize++; + insertLine(line, true); + + IdMapping idm{i == 0 ? 0 : 1, I_OFFSET + _linesFSize - 1}; + _lastQidToId = idm; + _qidToIdF.write(reinterpret_cast(&idm), + sizeof(IdMapping)); + _qidToIdFSize++; + } + i++; + } + if (i == 0) { + IdMapping idm{0, std::numeric_limits::max()}; + _lastQidToId = idm; + _qidToIdF.write(reinterpret_cast(&idm), + sizeof(IdMapping)); + _qidToIdFSize++; + } + } else { + IdMapping idm{0, std::numeric_limits::max()}; + _lastQidToId = idm; + _qidToIdF.write(reinterpret_cast(&idm), + sizeof(IdMapping)); + _qidToIdFSize++; + } + + if (*c == '\n') { + _curRow++; + if (_curRow % 1000000 == 0) { + LOG(INFO) << "[GEOMCACHE] " + << "@ row " << _curRow << " (" << std::fixed + << std::setprecision(2) << getLoadStatusPercent() + << "%, " << _pointsFSize << " points, " << _linesFSize + << " (open) polygons)"; + } + _prev = _dangling; + _dangling.clear(); + c++; + continue; + } else { + _prev = _dangling; + _dangling.clear(); + c++; + continue; + } + } + + _dangling += toupper(*c); + c++; + + break; + default: + break; + } + } +} + +// _____________________________________________________________________________ +double GeomCache::getLoadStatusPercent(bool total) { + /* + There are 2 loading stages: Parse, afterwards ParseIds. + Because ParseIds is usually pretty short, we merge the progress of both stages + to one total progress. Progress is calculated by _curRow / _totalSize, which + are handled by each stage individually. + */ + if (_totalSize == 0) { + return 0.0; + } + + if (!total) { + return std::atomic(_curRow) / static_cast(_totalSize) * + 100.0; + } + + double parsePercent = 95.0; + double parseIdsPercent = 5.0; + double totalPercent = 0.0; + switch (_loadStatusStage) { + case _LoadStatusStages::Parse: + totalPercent = std::atomic(_curRow) / + static_cast(_totalSize) * parsePercent; + break; + case _LoadStatusStages::ParseIds: + totalPercent = parsePercent; + totalPercent += std::atomic(_curRow) / + static_cast(_totalSize) * parseIdsPercent; + break; + } + + return totalPercent; +} + +// _____________________________________________________________________________ +int GeomCache::getLoadStatusStage() { + return _loadStatusStage; +} + +// _____________________________________________________________________________ +void GeomCache::parseIds(const char* c, size_t size) { + _loadStatusStage = _LoadStatusStages::ParseIds; + + size_t lastQid = -1; + for (size_t i = 0; i < size; i++) { + if (_raw.size() < 10000) _raw.push_back(c[i]); + _curId.bytes[_curByte] = c[i]; + _curByte = (_curByte + 1) % 8; + + if (_curByte == 0) { + if (_curRow % 1000000 == 0) { + LOG(INFO) << "[GEOMCACHE] " + << "@ row " << _curRow << " (" << std::fixed + << std::setprecision(2) << getLoadStatusPercent() + << "%, " << _pointsFSize << " points, " << _linesFSize + << " (open) polygons)"; + } + + if (_curRow < _qidToId.size() && _qidToId[_curRow].qid == 0) { + // if we have two consecutive and equivalent QLever ids, the geometry + // was returned multiple times in the fill query. This can happen if the + // same WKT string is used in multiple distinct objects, but then stored + // in qlever using the same internal qlever ID. To avoid a false multi- + // plication of results (all geoms of matching qlever ID are joined), we + // set such repeated qlever IDs to an unnsed dummy value. + // NOTE: because of the DISTNCT queries above, this should never happen + if (lastQid == _curId.val) { + LOG(WARN) << "Found duplicate internal qlever ID " << _curId.val + << " for row " << _curRow + << ", ignoring this geometry duplicate!"; + _qidToId[_curRow].qid = -1; + } else { + _qidToId[_curRow].qid = _curId.val; + } + lastQid = _curId.val; + if (_curId.val > _maxQid) _maxQid = _curId.val; + } else { + LOG(WARN) << "The results for the binary IDs are out of sync."; + LOG(WARN) << "_curRow: " << _curRow + << " _qleverIdInt.size: " << _qidToId.size() + << " cur val: " << _qidToId[_curRow].qid; + } + + // if a qlever entity contained multiple geometries (MULTILINESTRING, + // MULTIPOLYGON, MULTIPOINT), they appear consecutively in + // _qidToId; continuation geometries are marked by a + // preliminary qlever ID of 1, while the first geometry always has a + // preliminary id of 0 + while (_curRow < _qidToId.size() - 1 && _qidToId[_curRow + 1].qid == 1) { + _qidToId[++_curRow].qid = _curId.val; + } + + _curRow++; + } + } +} + +// _____________________________________________________________________________ +void GeomCache::parseCount(const char* c, size_t size) { + for (size_t i = 0; i < size; i++) { + if (_raw.size() < 10000) _raw.push_back(c[i]); + if (c[i] == '\n') _state = IN_ROW; + if (_state == IN_ROW) _dangling += c[i]; + } +} + +// _____________________________________________________________________________ +size_t GeomCache::requestSize() { + _state = IN_HEADER; + _dangling.clear(); + _dangling.reserve(10000); + _raw.clear(); + _raw.reserve(10000); + + CURLcode res; + char errbuf[CURL_ERROR_SIZE]; + + if (_curl) { + auto qUrl = queryUrl(getCountQuery(_backendUrl), 0, 1); + curl_easy_setopt(_curl, CURLOPT_URL, qUrl.c_str()); + curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, GeomCache::writeCbCount); + curl_easy_setopt(_curl, CURLOPT_WRITEDATA, this); + curl_easy_setopt(_curl, CURLOPT_ERRORBUFFER, errbuf); + curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYPEER, false); + curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYHOST, false); + curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, 0); + + // set headers + struct curl_slist* headers = 0; + headers = curl_slist_append(headers, "Accept: text/tab-separated-values"); + curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, headers); + + // accept any compression supported + curl_easy_setopt(_curl, CURLOPT_ACCEPT_ENCODING, ""); + res = curl_easy_perform(_curl); + + long httpCode = 0; + curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, &httpCode); + + curl_slist_free_all(headers); + + if (httpCode != 200) { + std::stringstream ss; + ss << "QLever backend returned status code " << httpCode + << " during count query"; + ss << "\n"; + ss << _raw; + throw std::runtime_error(ss.str()); + } + + if (_exceptionPtr) std::rethrow_exception(_exceptionPtr); + } else { + LOG(ERROR) << "[GEOMCACHE] Failed to perform curl request."; + return -1; + } + + // check if there was an error + if (res != CURLE_OK) { + size_t len = strlen(errbuf); + if (len > 0) { + LOG(ERROR) << "[GEOMCACHE] " << errbuf; + } else { + LOG(ERROR) << "[GEOMCACHE] " << curl_easy_strerror(res); + } + } + + std::istringstream iss(_dangling); + size_t ret; + iss >> ret; + return ret; +} + +// _____________________________________________________________________________ +void GeomCache::requestPart(size_t offset) { + _state = IN_HEADER; + _dangling.clear(); + _dangling.reserve(10000); + _raw.clear(); + _raw.reserve(10000); + + CURLcode res; + char errbuf[CURL_ERROR_SIZE]; + + if (_curl) { + auto qUrl = queryUrl(getQuery(_backendUrl), offset, 1000000); + curl_easy_setopt(_curl, CURLOPT_URL, qUrl.c_str()); + curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, GeomCache::writeCb); + curl_easy_setopt(_curl, CURLOPT_WRITEDATA, this); + curl_easy_setopt(_curl, CURLOPT_ERRORBUFFER, errbuf); + curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYPEER, false); + curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYHOST, false); + curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, 0); + + // set headers + struct curl_slist* headers = 0; + headers = curl_slist_append(headers, "Accept: text/tab-separated-values"); + curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, headers); + + // accept any compression supported + curl_easy_setopt(_curl, CURLOPT_ACCEPT_ENCODING, ""); + res = curl_easy_perform(_curl); + + long httpCode = 0; + curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, &httpCode); + + curl_slist_free_all(headers); + + if (httpCode != 200) { + std::stringstream ss; + ss << "QLever backend returned status code " << httpCode + << " during query (offset=" << offset << ")"; + ss << "\n"; + ss << _raw; + throw std::runtime_error(ss.str()); + } + + if (_exceptionPtr) std::rethrow_exception(_exceptionPtr); + } else { + LOG(ERROR) << "[GEOMCACHE] Failed to perform curl request."; + return; + } + + // check if there was an error + if (res != CURLE_OK) { + size_t len = strlen(errbuf); + if (len > 0) { + LOG(ERROR) << "[GEOMCACHE] " << errbuf; + } else { + LOG(ERROR) << "[GEOMCACHE] " << curl_easy_strerror(res); + } + } +} + +// _____________________________________________________________________________ +void GeomCache::request() { + _totalSize = requestSize(); + + if (_totalSize == 0) { + throw std::runtime_error( + "Could not determine number of rows, or number of rows was 0"); + } + + _state = IN_HEADER; + _points.clear(); + _lines.clear(); + _linePoints.clear(); + _qidToId.clear(); + + _lastQidToId = {-1, -1}; + + _raw.clear(); + _raw.reserve(100000); + + char* pointsFName = strdup("pointsXXXXXX"); + int i = mkstemp(pointsFName); + if (i == -1) throw std::runtime_error("Could not create temporary file"); + _pointsF.open(pointsFName, std::ios::out | std::ios::in | std::ios::binary); + + char* linePointsFName = strdup("linepointsXXXXXX"); + i = mkstemp(linePointsFName); + if (i == -1) throw std::runtime_error("Could not create temporary file"); + _linePointsF.open(linePointsFName, + std::ios::out | std::ios::in | std::ios::binary); + + char* linesFName = strdup("linesXXXXXX"); + i = mkstemp(linesFName); + if (i == -1) throw std::runtime_error("Could not create temporary file"); + _linesF.open(linesFName, std::ios::out | std::ios::in | std::ios::binary); + + char* qidToIdFName = strdup("qidtoidXXXXXX"); + i = mkstemp(qidToIdFName); + if (i == -1) throw std::runtime_error("Could not create temporary file"); + _qidToIdF.open(qidToIdFName, std::ios::out | std::ios::in | std::ios::binary); + + // immediately unlink + unlink(pointsFName); + unlink(linePointsFName); + unlink(linesFName); + unlink(qidToIdFName); + + free(pointsFName); + free(linePointsFName); + free(linesFName); + free(qidToIdFName); + + _pointsFSize = 0; + _linePointsFSize = 0; + _linesFSize = 0; + _qidToIdFSize = 0; + + _curRow = 0; + _curUniqueGeom = 0; + + size_t lastNum = -1; + + LOG(INFO) << "[GEOMCACHE] Total request size: " << _totalSize; + LOG(INFO) << "[GEOMCACHE] Query is:\n" << getQuery(_backendUrl); + + while (lastNum != 0) { + size_t offset = _curRow; + requestPart(offset); + lastNum = _curRow - offset; + } + + if (i == -1) throw std::runtime_error("Could not create temporary file"); + + LOG(INFO) << "[GEOMCACHE] Building vectors..."; + + _points.resize(_pointsFSize); + _pointsF.seekg(0); + _pointsF.read(reinterpret_cast(&_points[0]), + sizeof(util::geo::FPoint) * _pointsFSize); + _pointsF.close(); + + _linePoints.resize(_linePointsFSize); + _linePointsF.seekg(0); + _linePointsF.read(reinterpret_cast(&_linePoints[0]), + sizeof(util::geo::Point) * _linePointsFSize); + _linePointsF.close(); + + _lines.resize(_linesFSize); + _linesF.seekg(0); + _linesF.read(reinterpret_cast(&_lines[0]), + sizeof(size_t) * _linesFSize); + _linesF.close(); + + _qidToId.resize(_qidToIdFSize); + _qidToIdF.seekg(0); + _qidToIdF.read(reinterpret_cast(&_qidToId[0]), + sizeof(IdMapping) * _qidToIdFSize); + _qidToIdF.close(); + + LOG(INFO) << "[GEOMCACHE] Done"; + LOG(INFO) << "[GEOMCACHE] Received " << _curUniqueGeom << " unique geoms"; + LOG(INFO) << "[GEOMCACHE] Received " << _points.size() << " points and " + << _lines.size() << " lines"; +} + +// _____________________________________________________________________________ +void GeomCache::requestIds() { + _curByte = 0; + _curRow = 0; + _curUniqueGeom = 0; + _maxQid = 0; + _exceptionPtr = 0; + + LOG(INFO) << "[GEOMCACHE] Query is " << getQuery(_backendUrl); + + if (_curl) { + auto qUrl = queryUrl(getQuery(_backendUrl), 0, MAXROWS); + LOG(INFO) << "[GEOMCACHE] Binary ID query URL is " << qUrl; + curl_easy_setopt(_curl, CURLOPT_URL, qUrl.c_str()); + curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, GeomCache::writeCbIds); + curl_easy_setopt(_curl, CURLOPT_WRITEDATA, this); + curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYPEER, false); + curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYHOST, false); + curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, 0); + + // set headers + struct curl_slist* headers = 0; + headers = curl_slist_append(headers, "Accept: application/octet-stream"); + curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, headers); + + // accept any compression supported + curl_easy_setopt(_curl, CURLOPT_ACCEPT_ENCODING, ""); + curl_easy_perform(_curl); + + long httpCode = 0; + curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, &httpCode); + + curl_slist_free_all(headers); + + if (httpCode != 200) { + std::stringstream ss; + ss << "QLever backend returned status code " << httpCode; + ss << "\n"; + ss << _raw; + throw std::runtime_error(ss.str()); + } + + if (_exceptionPtr) std::rethrow_exception(_exceptionPtr); + } else { + LOG(ERROR) << "[GEOMCACHE] Failed to perform curl request."; + } + + LOG(INFO) << "[GEOMCACHE] Received " << _curRow << " rows"; + LOG(INFO) << "[GEOMCACHE] Max QLever id was " << _maxQid; + LOG(INFO) << "[GEOMCACHE] Done"; + + // sorting by qlever id + LOG(INFO) << "[GEOMCACHE] Sorting results by qlever ID..."; + std::stable_sort(_qidToId.begin(), _qidToId.end()); + LOG(INFO) << "[GEOMCACHE] ... done"; +} + +// _____________________________________________________________________________ +std::string GeomCache::queryUrl(std::string query, size_t offset, + size_t limit) const { + std::stringstream ss; + + if (util::toLower(query).find("limit") == std::string::npos) { + query += " LIMIT " + std::to_string(limit); + } + + if (util::toLower(query).find("offset") == std::string::npos) { + query += " OFFSET " + std::to_string(offset); + } + + auto esc = curl_easy_escape(_curl, query.c_str(), query.size()); + + ss << _backendUrl << "/?send=" << std::to_string(MAXROWS) << "&query=" << esc; + + curl_free(esc); + + return ss.str(); +} + +// _____________________________________________________________________________ +bool GeomCache::pointValid(const FPoint& p) { + if (p.getY() > std::numeric_limits::max()) return false; + if (p.getY() < std::numeric_limits::lowest()) return false; + if (p.getX() > std::numeric_limits::max()) return false; + if (p.getX() < std::numeric_limits::lowest()) return false; + + return true; +} + +// _____________________________________________________________________________ +bool GeomCache::pointValid(const DPoint& p) { + if (p.getY() > std::numeric_limits::max()) return false; + if (p.getY() < std::numeric_limits::lowest()) return false; + if (p.getX() > std::numeric_limits::max()) return false; + if (p.getX() < std::numeric_limits::lowest()) return false; + + return true; +} + +// _____________________________________________________________________________ +util::geo::DLine GeomCache::parseLineString(const std::string& a, + size_t p) const { + util::geo::DLine line; + line.reserve(2); + auto end = memchr(a.c_str() + p, ')', a.size() - p); + assert(end); + + while (true) { + auto point = latLngToWebMerc(DPoint( + util::atof(a.c_str() + p, 10), + util::atof( + static_cast(memchr(a.c_str() + p, ' ', a.size() - p)) + + 1, + 10))); + + if (pointValid(point)) line.push_back(point); + + auto n = memchr(a.c_str() + p, ',', a.size() - p); + if (!n || n > end) break; + p = static_cast(n) - a.c_str() + 1; + } + + // the 200 is the THRESHOLD from Server.cpp + // return util::geo::densify(util::geo::simplify(line, 3), 200 * 3); + return util::geo::densify(line, 200 * 3); +} + +// _____________________________________________________________________________ +util::geo::FPoint GeomCache::parsePoint(const std::string& a, size_t p) const { + auto point = latLngToWebMerc(FPoint( + util::atof(a.c_str() + p, 10), + util::atof( + static_cast(memchr(a.c_str() + p, ' ', a.size() - p)) + + 1, + 10))); + + return point; +} + +// _____________________________________________________________________________ +std::pair>, size_t> +GeomCache::getRelObjects(const std::vector& ids) const { + // (geom id, result row) + std::vector> ret; + + // in most cases, the return size will be exactly the size of the ids set + ret.reserve(ids.size()); + + // only counts multi-geometries once + size_t numObjects = 0; + + size_t i = 0; + size_t j = 0; + + while (i < ids.size() && j < _qidToId.size()) { + if (ids[i].qid == _qidToId[j].qid) { + size_t prefJ = j; + + while (j < _qidToId.size() && ids[i].qid == _qidToId[j].qid) { + if (ret.size() == 0 || ret.back().second != ids[i].id) numObjects++; + ret.push_back({_qidToId[j].id, ids[i].id}); + j++; + } + + j = prefJ; + i++; + } else if (ids[i].qid < _qidToId[j].qid) { + i++; + } else { + size_t gallop = 1; + do { + if (j + gallop >= _qidToId.size()) { + j = std::lower_bound(_qidToId.begin() + j + gallop / 2, + _qidToId.end(), ids[i]) - + _qidToId.begin(); + break; + } + + if (_qidToId[j + gallop].qid >= ids[i].qid) { + j = std::lower_bound(_qidToId.begin() + j + gallop / 2, + _qidToId.begin() + j + gallop, ids[i]) - + _qidToId.begin(); + break; + } + + gallop *= 2; + + } while (true); + } + } + + return {ret, numObjects}; +} + +// _____________________________________________________________________________ +void GeomCache::insertLine(const util::geo::DLine& l, bool isArea) { + // we also add the line's bounding box here to also + // compress that + const auto& bbox = util::geo::getBoundingBox(l); + + int16_t mainX = (bbox.getLowerLeft().getX() * 10.0) / M_COORD_GRANULARITY; + int16_t mainY = (bbox.getLowerLeft().getY() * 10.0) / M_COORD_GRANULARITY; + + if (mainX != 0 || mainY != 0) { + util::geo::Point p{mCoord(mainX), mCoord(mainY)}; + _linePointsF.write(reinterpret_cast(&p), + sizeof(util::geo::Point)); + _linePointsFSize++; + } + + // add bounding box lower left + int16_t minorXLoc = + (bbox.getLowerLeft().getX() * 10.0) - mainX * M_COORD_GRANULARITY; + int16_t minorYLoc = + (bbox.getLowerLeft().getY() * 10.0) - mainY * M_COORD_GRANULARITY; + + util::geo::Point p{minorXLoc, minorYLoc}; + _linePointsF.write(reinterpret_cast(&p), + sizeof(util::geo::Point)); + _linePointsFSize++; + + // add bounding box upper left + int16_t mainXLoc = (bbox.getUpperRight().getX() * 10.0) / M_COORD_GRANULARITY; + int16_t mainYLoc = (bbox.getUpperRight().getY() * 10.0) / M_COORD_GRANULARITY; + minorXLoc = + (bbox.getUpperRight().getX() * 10.0) - mainXLoc * M_COORD_GRANULARITY; + minorYLoc = + (bbox.getUpperRight().getY() * 10.0) - mainYLoc * M_COORD_GRANULARITY; + if (mainXLoc != mainX || mainYLoc != mainY) { + mainX = mainXLoc; + mainY = mainYLoc; + + util::geo::Point p{mCoord(mainX), mCoord(mainY)}; + _linePointsF.write(reinterpret_cast(&p), + sizeof(util::geo::Point)); + _linePointsFSize++; + } + p = util::geo::Point{minorXLoc, minorYLoc}; + _linePointsF.write(reinterpret_cast(&p), + sizeof(util::geo::Point)); + _linePointsFSize++; + + // add line points + for (const auto& p : l) { + mainXLoc = (p.getX() * 10.0) / M_COORD_GRANULARITY; + mainYLoc = (p.getY() * 10.0) / M_COORD_GRANULARITY; + + if (mainXLoc != mainX || mainYLoc != mainY) { + mainX = mainXLoc; + mainY = mainYLoc; + + util::geo::Point p{mCoord(mainX), mCoord(mainY)}; + _linePointsF.write(reinterpret_cast(&p), + sizeof(util::geo::Point)); + _linePointsFSize++; + } + + int16_t minorXLoc = (p.getX() * 10.0) - mainXLoc * M_COORD_GRANULARITY; + int16_t minorYLoc = (p.getY() * 10.0) - mainYLoc * M_COORD_GRANULARITY; + + util::geo::Point pp{minorXLoc, minorYLoc}; + _linePointsF.write(reinterpret_cast(&pp), + sizeof(util::geo::Point)); + _linePointsFSize++; + } + + // if we have an area, we end in a major coord (which is not possible for + // other types) + if (isArea) { + util::geo::Point p{mCoord(0), mCoord(0)}; + _linePointsF.write(reinterpret_cast(&p), + sizeof(util::geo::Point)); + _linePointsFSize++; + } +} + +// _____________________________________________________________________________ +util::geo::DBox GeomCache::getLineBBox(size_t lid) const { + util::geo::DBox ret; + size_t start = getLine(lid); + + bool s = false; + + double mainX = 0; + double mainY = 0; + for (size_t i = start; i < start + 4; i++) { + // extract real geom + const auto& cur = _linePoints[i]; + + if (isMCoord(cur.getX())) { + mainX = rmCoord(cur.getX()); + mainY = rmCoord(cur.getY()); + continue; + } + + util::geo::DPoint curP((mainX * M_COORD_GRANULARITY + cur.getX()) / 10.0, + (mainY * M_COORD_GRANULARITY + cur.getY()) / 10.0); + + if (!s) { + ret.setLowerLeft(curP); + s = true; + } else { + ret.setUpperRight(curP); + return ret; + } + } + + return ret; +} + +// _____________________________________________________________________________ +std::string GeomCache::indexHashFromDisk(const std::string& fname) { + std::ifstream f(fname, std::ios::binary); + char tmp[100]; + f.read(tmp, 100); + tmp[99] = 0; + + return util::trim(tmp); +} + +// _____________________________________________________________________________ +void GeomCache::fromDisk(const std::string& fname) { + _points.clear(); + _linePoints.clear(); + _lines.clear(); + + std::ifstream f(fname, std::ios::binary); + + // load hash + char tmp[100]; + f.read(tmp, 100); + tmp[99] = 0; + + _indexHash = util::trim(tmp); + + size_t numPoints; + f.read(reinterpret_cast(&numPoints), sizeof(size_t)); + _points.resize(numPoints); + f.read(reinterpret_cast(&_points[0]), + sizeof(util::geo::FPoint) * numPoints); + + f.read(reinterpret_cast(&numPoints), sizeof(size_t)); + _linePoints.resize(numPoints); + f.read(reinterpret_cast(&_linePoints[0]), + sizeof(util::geo::Point) * numPoints); + + f.read(reinterpret_cast(&numPoints), sizeof(size_t)); + _lines.resize(numPoints); + f.read(reinterpret_cast(&_lines[0]), sizeof(size_t) * numPoints); + + f.read(reinterpret_cast(&numPoints), sizeof(size_t)); + _qidToId.resize(numPoints); + f.read(reinterpret_cast(&_qidToId[0]), sizeof(IdMapping) * numPoints); + + f.close(); +} + +// _____________________________________________________________________________ +void GeomCache::serializeToDisk(const std::string& fname) const { + std::ofstream f; + f.open(fname); + + std::string h = _indexHash; + h.insert(h.end(), 99 - h.size(), ' '); + + // null byte is 100 + assert(h.size() == 99); + f.write(h.c_str(), 100); + + size_t num = _points.size(); + f.write(reinterpret_cast(&num), sizeof(size_t)); + f.write(reinterpret_cast(&_points[0]), + sizeof(util::geo::FPoint) * num); + + num = _linePoints.size(); + f.write(reinterpret_cast(&num), sizeof(size_t)); + f.write(reinterpret_cast(&_linePoints[0]), + sizeof(util::geo::Point) * num); + + num = _lines.size(); + f.write(reinterpret_cast(&num), sizeof(size_t)); + f.write(reinterpret_cast(&_lines[0]), sizeof(size_t) * num); + + num = _qidToId.size(); + f.write(reinterpret_cast(&num), sizeof(size_t)); + f.write(reinterpret_cast(&_qidToId[0]), sizeof(IdMapping) * num); + + f.close(); +} + +// _____________________________________________________________________________ +std::string GeomCache::requestIndexHash() { + CURLcode res; + char errbuf[CURL_ERROR_SIZE]; + std::string response; + + if (_curl) { + std::string url = _backendUrl + "/?cmd=get-index-id"; + curl_easy_setopt(_curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, GeomCache::writeCbString); + curl_easy_setopt(_curl, CURLOPT_WRITEDATA, &response); + curl_easy_setopt(_curl, CURLOPT_ERRORBUFFER, errbuf); + curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYPEER, false); + curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYHOST, false); + curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, 0); + + // accept any compression supported + curl_easy_setopt(_curl, CURLOPT_ACCEPT_ENCODING, ""); + res = curl_easy_perform(_curl); + + if (res != CURLE_OK) { + size_t len = strlen(errbuf); + if (len > 0) { + LOG(ERROR) << "[GEOMCACHE] " << errbuf; + } else { + LOG(ERROR) << "[GEOMCACHE] " << curl_easy_strerror(res); + } + + return ""; + } + + long httpCode = 0; + curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, &httpCode); + + if (httpCode != 200) { + LOG(WARN) << "QLever backend returned status code " << httpCode + << " for index hash."; + return ""; + } + + return response; + } else { + LOG(ERROR) << "[GEOMCACHE] Failed to perform curl request for index hash."; + return ""; + } +} + +// _____________________________________________________________________________ +void GeomCache::load(const std::string& cacheDir) { + std::lock_guard guard(_m); + + if (_ready) { + auto indexHash = requestIndexHash(); + if (_indexHash == indexHash) return; + LOG(INFO) << "Loaded index hash (" << _indexHash + << ") and remote index hash (" << indexHash << ") dont match."; + _ready = false; + } + + if (cacheDir.size()) { + std::string backend = getBackendURL(); + util::replaceAll(backend, "/", "_"); + std::string cacheFile = cacheDir + "/" + backend; + auto indexHash = requestIndexHash(); + if (access(cacheFile.c_str(), F_OK) != -1 && + indexHash == indexHashFromDisk(cacheFile)) { + LOG(INFO) << "Reading from cache file " << cacheFile << "..."; + fromDisk(cacheFile); + LOG(INFO) << "done ..."; + } else { + if (access(cacheDir.c_str(), W_OK) != 0) { + std::stringstream ss; + ss << "No write access to cache dir " << cacheDir; + throw std::runtime_error(ss.str()); + } + _indexHash = requestIndexHash(); + LOG(INFO) << "Index hash is '" << _indexHash << "'"; + request(); + requestIds(); + LOG(INFO) << "Serializing to cache file " << cacheFile << "..."; + serializeToDisk(cacheFile); + LOG(INFO) << "done ..."; + } + } else { + _indexHash = requestIndexHash(); + LOG(INFO) << "Index hash is '" << _indexHash << "'"; + request(); + requestIds(); + } + + _ready = true; +} diff --git a/src/qlever-petrimaps/GeomCache.h b/src/qlever-petrimaps/GeomCache.h index 71072ec..019beb8 100644 --- a/src/qlever-petrimaps/GeomCache.h +++ b/src/qlever-petrimaps/GeomCache.h @@ -1,165 +1,165 @@ -// Copyright 2022, University of Freiburg, -// Chair of Algorithms and Data Structures. -// Authors: Patrick Brosi - -#ifndef PETRIMAPS_GEOMCACHE_H_ -#define PETRIMAPS_GEOMCACHE_H_ - -#include - -#include -#include -#include -#include -#include -#include - -#include "qlever-petrimaps/Misc.h" -#include "util/geo/Geo.h" - -namespace petrimaps { - -class GeomCache { - public: - GeomCache() : _backendUrl(""), _curl(0) {} - explicit GeomCache(const std::string& backendUrl) - : _backendUrl(backendUrl), - _curl(curl_easy_init()) {} - - GeomCache& operator=(GeomCache&& o) { - _backendUrl = o._backendUrl; - _curl = curl_easy_init(); - _lines = std::move(o._lines); - _linePoints = std::move(o._linePoints); - _points = std::move(o._points); - _dangling = o._dangling; - _state = o._state; - return *this; - }; - - ~GeomCache() { - if (_curl) curl_easy_cleanup(_curl); - } - - bool ready() const { - _m.lock(); - bool ready = _ready; - _m.unlock(); - return ready; - } - - void load(const std::string& cacheFile); - - void request(); - size_t requestSize(); - void requestPart(size_t offset); - - void requestIds(); - - void parse(const char*, size_t size); - void parseIds(const char*, size_t size); - void parseCount(const char*, size_t size); - - std::pair>, size_t> getRelObjects( - const std::vector& id) const; - - const std::string& getBackendURL() const { return _backendUrl; } - - const std::vector& getPoints() const { return _points; } - - const std::vector>& getLinePoints() const { - return _linePoints; - } - - const std::vector& getLines() const { return _lines; } - - util::geo::FBox getPointBBox(size_t id) const { - return util::geo::getBoundingBox(_points[id]); - } - util::geo::DBox getLineBBox(size_t id) const; - - void serializeToDisk(const std::string& fname) const; - - void fromDisk(const std::string& fname); - - size_t getLine(ID_TYPE id) const { return _lines[id]; } - - size_t getLineEnd(ID_TYPE id) const { - return id + 1 < _lines.size() ? _lines[id + 1] : _linePoints.size(); - } - - double getLoadStatusPercent(bool total); - double getLoadStatusPercent() { return getLoadStatusPercent(false); }; - int getLoadStatusStage(); - - private: - std::string _backendUrl; - CURL* _curl; - - uint8_t _curByte; - ID _curId; - QLEVER_ID_TYPE _maxQid; - size_t _curRow, _curUniqueGeom; - - enum _LoadStatusStages {Parse = 1, ParseIds}; - _LoadStatusStages _loadStatusStage = Parse; - - static size_t writeCb(void* contents, size_t size, size_t nmemb, void* userp); - static size_t writeCbIds(void* contents, size_t size, size_t nmemb, - void* userp); - static size_t writeCbCount(void* contents, size_t size, size_t nmemb, - void* userp); - static size_t writeCbString(void* contents, size_t size, size_t nmemb, - void* userp); - - // Get the right SPARQL query for the given backend. - const std::string& getQuery(const std::string& backendUrl) const; - const std::string& getCountQuery(const std::string& backendUrl) const; - - std::string requestIndexHash(); - - std::string queryUrl(std::string query, size_t offset, size_t limit) const; - - util::geo::DLine parseLineString(const std::string& a, size_t p) const; - util::geo::FPoint parsePoint(const std::string& a, size_t p) const; - - static bool pointValid(const util::geo::FPoint& p); - static bool pointValid(const util::geo::DPoint& p); - - void insertLine(const util::geo::DLine& l, bool isArea); - - std::string indexHashFromDisk(const std::string& fname); - - std::vector _points; - std::vector> _linePoints; - std::vector _lines; - - size_t _pointsFSize; - size_t _linePointsFSize; - size_t _linesFSize; - size_t _qidToIdFSize; - - std::fstream _pointsF; - std::fstream _linePointsF; - std::fstream _linesF; - std::fstream _qidToIdF; - - size_t _totalSize = 0; - - IdMapping _lastQidToId; - - std::vector _qidToId; - - std::string _dangling, _prev, _raw; - ParseState _state; - - std::exception_ptr _exceptionPtr; - - mutable std::mutex _m; - bool _ready = false; - - std::string _indexHash; -}; -} // namespace petrimaps - -#endif // PETRIMAPS_GEOMCACHE_H_ +// Copyright 2022, University of Freiburg, +// Chair of Algorithms and Data Structures. +// Authors: Patrick Brosi + +#ifndef PETRIMAPS_GEOMCACHE_H_ +#define PETRIMAPS_GEOMCACHE_H_ + +#include + +#include +#include +#include +#include +#include +#include + +#include "qlever-petrimaps/Misc.h" +#include "util/geo/Geo.h" + +namespace petrimaps { + +class GeomCache { + public: + GeomCache() : _backendUrl(""), _curl(0) {} + explicit GeomCache(const std::string& backendUrl) + : _backendUrl(backendUrl), + _curl(curl_easy_init()) {} + + GeomCache& operator=(GeomCache&& o) { + _backendUrl = o._backendUrl; + _curl = curl_easy_init(); + _lines = std::move(o._lines); + _linePoints = std::move(o._linePoints); + _points = std::move(o._points); + _dangling = o._dangling; + _state = o._state; + return *this; + }; + + ~GeomCache() { + if (_curl) curl_easy_cleanup(_curl); + } + + bool ready() const { + _m.lock(); + bool ready = _ready; + _m.unlock(); + return ready; + } + + void load(const std::string& cacheFile); + + void request(); + size_t requestSize(); + void requestPart(size_t offset); + + void requestIds(); + + void parse(const char*, size_t size); + void parseIds(const char*, size_t size); + void parseCount(const char*, size_t size); + + std::pair>, size_t> getRelObjects( + const std::vector& id) const; + + const std::string& getBackendURL() const { return _backendUrl; } + + const std::vector& getPoints() const { return _points; } + + const std::vector>& getLinePoints() const { + return _linePoints; + } + + const std::vector& getLines() const { return _lines; } + + util::geo::FBox getPointBBox(size_t id) const { + return util::geo::getBoundingBox(_points[id]); + } + util::geo::DBox getLineBBox(size_t id) const; + + void serializeToDisk(const std::string& fname) const; + + void fromDisk(const std::string& fname); + + size_t getLine(ID_TYPE id) const { return _lines[id]; } + + size_t getLineEnd(ID_TYPE id) const { + return id + 1 < _lines.size() ? _lines[id + 1] : _linePoints.size(); + } + + double getLoadStatusPercent(bool total); + double getLoadStatusPercent() { return getLoadStatusPercent(false); }; + int getLoadStatusStage(); + + private: + std::string _backendUrl; + CURL* _curl; + + uint8_t _curByte; + ID _curId; + QLEVER_ID_TYPE _maxQid; + size_t _curRow, _curUniqueGeom; + + enum _LoadStatusStages {Parse = 1, ParseIds}; + _LoadStatusStages _loadStatusStage = Parse; + + static size_t writeCb(void* contents, size_t size, size_t nmemb, void* userp); + static size_t writeCbIds(void* contents, size_t size, size_t nmemb, + void* userp); + static size_t writeCbCount(void* contents, size_t size, size_t nmemb, + void* userp); + static size_t writeCbString(void* contents, size_t size, size_t nmemb, + void* userp); + + // Get the right SPARQL query for the given backend. + const std::string& getQuery(const std::string& backendUrl) const; + const std::string& getCountQuery(const std::string& backendUrl) const; + + std::string requestIndexHash(); + + std::string queryUrl(std::string query, size_t offset, size_t limit) const; + + util::geo::DLine parseLineString(const std::string& a, size_t p) const; + util::geo::FPoint parsePoint(const std::string& a, size_t p) const; + + static bool pointValid(const util::geo::FPoint& p); + static bool pointValid(const util::geo::DPoint& p); + + void insertLine(const util::geo::DLine& l, bool isArea); + + std::string indexHashFromDisk(const std::string& fname); + + std::vector _points; + std::vector> _linePoints; + std::vector _lines; + + size_t _pointsFSize; + size_t _linePointsFSize; + size_t _linesFSize; + size_t _qidToIdFSize; + + std::fstream _pointsF; + std::fstream _linePointsF; + std::fstream _linesF; + std::fstream _qidToIdF; + + size_t _totalSize = 0; + + IdMapping _lastQidToId; + + std::vector _qidToId; + + std::string _dangling, _prev, _raw; + ParseState _state; + + std::exception_ptr _exceptionPtr; + + mutable std::mutex _m; + bool _ready = false; + + std::string _indexHash; +}; +} // namespace petrimaps + +#endif // PETRIMAPS_GEOMCACHE_H_ diff --git a/src/qlever-petrimaps/Grid.h b/src/qlever-petrimaps/Grid.h index 4243868..f4b0c06 100644 --- a/src/qlever-petrimaps/Grid.h +++ b/src/qlever-petrimaps/Grid.h @@ -1,108 +1,108 @@ -// Copyright 2016, University of Freiburg, -// Chair of Algorithms and Data Structures. -// Author: Patrick Brosi - -#ifndef PETRIMAPS_GRID_H_ -#define PETRIMAPS_GRID_H_ - -#include -#include -#include -#include "util/geo/Geo.h" - -namespace petrimaps { - -class GridException : public std::runtime_error { - public: - GridException(std::string const& msg) : std::runtime_error(msg) {} -}; - -template -class Grid { - public: - Grid(const Grid&) = delete; - Grid(Grid&& o) - : _width(o._width), - _height(o._height), - _cellWidth(o._cellWidth), - _cellHeight(o._cellHeight), - _bb(o._bb), - _xWidth(o._xWidth), - _yHeight(o._yHeight), - _grid(o._grid) { - o._grid = 0; - } - - Grid& operator=(Grid&& o) { - _width = o._width; - _height = o._height; - _cellWidth = o._cellWidth; - _cellHeight = o._cellHeight; - _bb = o._bb; - _xWidth = o._xWidth; - _yHeight = o._yHeight; - _grid = o._grid; - o._grid = 0; - - return *this; - }; - - // initialization of a point grid with cell width w and cell height h - // that covers the area of bounding box bbox - Grid(double w, double h, const util::geo::Box& bbox); - - // the empty grid - Grid(); - - ~Grid() { - if (!_grid) return; - for (size_t i = 0; i < _xWidth * _yHeight; i++) { - if (!_grid[i]) continue; - delete _grid[i]; - } - delete[] _grid; - } - - // add object t to this grid - void add(const util::geo::Box& box, const V& val); - void add(const util::geo::Point& box, const V& val); - void add(size_t x, size_t y, V val); - - void get(const util::geo::Box& btbox, std::unordered_set* s) const; - void get(size_t x, size_t y, std::unordered_set* s) const; - void get(const util::geo::Box& btbox, std::vector* s) const; - void get(size_t x, size_t y, std::vector* s) const; - const std::vector* getCell(size_t x, size_t y) const; - - size_t getXWidth() const; - size_t getYHeight() const; - - double getCellWidth() const { return _cellWidth; } - double getCellHeight() const { return _cellHeight; } - - size_t getCellXFromX(double lon) const; - size_t getCellYFromY(double lat) const; - - util::geo::Box getBox(size_t x, size_t y) const; - util::geo::Box getBBox() const { return _bb; } - - private: - double _width; - double _height; - - double _cellWidth; - double _cellHeight; - - util::geo::Box _bb; - - size_t _xWidth; - size_t _yHeight; - - std::vector** _grid; -}; - -#include "qlever-petrimaps/Grid.tpp" - -} // namespace petrimaps - -#endif // PETRIMAPS_GRID_H_ +// Copyright 2016, University of Freiburg, +// Chair of Algorithms and Data Structures. +// Author: Patrick Brosi + +#ifndef PETRIMAPS_GRID_H_ +#define PETRIMAPS_GRID_H_ + +#include +#include +#include +#include "util/geo/Geo.h" + +namespace petrimaps { + +class GridException : public std::runtime_error { + public: + GridException(std::string const& msg) : std::runtime_error(msg) {} +}; + +template +class Grid { + public: + Grid(const Grid&) = delete; + Grid(Grid&& o) + : _width(o._width), + _height(o._height), + _cellWidth(o._cellWidth), + _cellHeight(o._cellHeight), + _bb(o._bb), + _xWidth(o._xWidth), + _yHeight(o._yHeight), + _grid(o._grid) { + o._grid = 0; + } + + Grid& operator=(Grid&& o) { + _width = o._width; + _height = o._height; + _cellWidth = o._cellWidth; + _cellHeight = o._cellHeight; + _bb = o._bb; + _xWidth = o._xWidth; + _yHeight = o._yHeight; + _grid = o._grid; + o._grid = 0; + + return *this; + }; + + // initialization of a point grid with cell width w and cell height h + // that covers the area of bounding box bbox + Grid(double w, double h, const util::geo::Box& bbox); + + // the empty grid + Grid(); + + ~Grid() { + if (!_grid) return; + for (size_t i = 0; i < _xWidth * _yHeight; i++) { + if (!_grid[i]) continue; + delete _grid[i]; + } + delete[] _grid; + } + + // add object t to this grid + void add(const util::geo::Box& box, const V& val); + void add(const util::geo::Point& box, const V& val); + void add(size_t x, size_t y, V val); + + void get(const util::geo::Box& btbox, std::unordered_set* s) const; + void get(size_t x, size_t y, std::unordered_set* s) const; + void get(const util::geo::Box& btbox, std::vector* s) const; + void get(size_t x, size_t y, std::vector* s) const; + const std::vector* getCell(size_t x, size_t y) const; + + size_t getXWidth() const; + size_t getYHeight() const; + + double getCellWidth() const { return _cellWidth; } + double getCellHeight() const { return _cellHeight; } + + size_t getCellXFromX(double lon) const; + size_t getCellYFromY(double lat) const; + + util::geo::Box getBox(size_t x, size_t y) const; + util::geo::Box getBBox() const { return _bb; } + + private: + double _width; + double _height; + + double _cellWidth; + double _cellHeight; + + util::geo::Box _bb; + + size_t _xWidth; + size_t _yHeight; + + std::vector** _grid; +}; + +#include "qlever-petrimaps/Grid.tpp" + +} // namespace petrimaps + +#endif // PETRIMAPS_GRID_H_ diff --git a/src/qlever-petrimaps/Grid.tpp b/src/qlever-petrimaps/Grid.tpp index efcad8a..264e81f 100644 --- a/src/qlever-petrimaps/Grid.tpp +++ b/src/qlever-petrimaps/Grid.tpp @@ -1,153 +1,153 @@ -// Copyright 2017, University of Freiburg, -// Chair of Algorithms and Data Structures. -// Author: Patrick Brosi - -// _____________________________________________________________________________ -template -Grid::Grid() - : _width(0), - _height(0), - _cellWidth(0), - _cellHeight(0), - _xWidth(0), - _yHeight(0), - _grid(0) {} - -// _____________________________________________________________________________ -template -Grid::Grid(double w, double h, const util::geo::Box& bbox) - : _cellWidth(fabs(w)), _cellHeight(fabs(h)), _bb(bbox), _grid() { - _width = bbox.getUpperRight().getX() - bbox.getLowerLeft().getX(); - _height = bbox.getUpperRight().getY() - bbox.getLowerLeft().getY(); - - if (_width < 0 || _height < 0) { - _width = 0; - _height = 0; - _xWidth = 0; - _yHeight = 0; - return; - } - - _xWidth = ceil(_width / _cellWidth); - _yHeight = ceil(_height / _cellHeight); - - // resize rows - _grid = new std::vector*[_xWidth * _yHeight]; - memset(_grid, 0, _xWidth * _yHeight * sizeof(std::vector*)); -} - -// _____________________________________________________________________________ -template -void Grid::add(const util::geo::Point& p, const V& val) { - add(getCellXFromX(p.getX()), getCellYFromY(p.getY()), val); -} - -// _____________________________________________________________________________ -template -void Grid::add(const util::geo::Box& box, const V& val) { - size_t swX = getCellXFromX(box.getLowerLeft().getX()); - size_t swY = getCellYFromY(box.getLowerLeft().getY()); - - size_t neX = getCellXFromX(box.getUpperRight().getX()); - size_t neY = getCellYFromY(box.getUpperRight().getY()); - - for (size_t x = swX; x <= neX && x < _xWidth; x++) { - for (size_t y = swY; y <= neY && y < _yHeight; y++) { - add(x, y, val); - } - } -} - -// _____________________________________________________________________________ -template -void Grid::add(size_t x, size_t y, V val) { - if (x >= _xWidth || y >= _yHeight) return; - if (!_grid[y * _xWidth + x]) _grid[y * _xWidth + x] = new std::vector(); - _grid[y * _xWidth + x]->push_back(val); -} - -// _____________________________________________________________________________ -template -void Grid::get(const util::geo::Box& box, - std::vector* s) const { - size_t swX = getCellXFromX(box.getLowerLeft().getX()); - size_t swY = getCellYFromY(box.getLowerLeft().getY()); - - size_t neX = getCellXFromX(box.getUpperRight().getX()); - size_t neY = getCellYFromY(box.getUpperRight().getY()); - - for (size_t x = swX; x <= neX && x < _xWidth; x++) - for (size_t y = swY; y <= neY && y < _yHeight; y++) get(x, y, s); -} - -// _____________________________________________________________________________ -template -void Grid::get(const util::geo::Box& box, - std::unordered_set* s) const { - size_t swX = getCellXFromX(box.getLowerLeft().getX()); - size_t swY = getCellYFromY(box.getLowerLeft().getY()); - - size_t neX = getCellXFromX(box.getUpperRight().getX()); - size_t neY = getCellYFromY(box.getUpperRight().getY()); - - for (size_t x = swX; x <= neX && x < _xWidth; x++) - for (size_t y = swY; y <= neY && y < _yHeight; y++) get(x, y, s); -} - -// _____________________________________________________________________________ -template -void Grid::get(size_t x, size_t y, std::unordered_set* s) const { - if (!_grid[y * _xWidth + x]) return; - s->insert(_grid[y * _xWidth + x]->begin(), _grid[y * _xWidth + x]->end()); -} - -// _____________________________________________________________________________ -template -void Grid::get(size_t x, size_t y, std::vector* s) const { - if (!_grid[y * _xWidth + x]) return; - s->insert(s->end(), _grid[y * _xWidth + x]->begin(), _grid[y * _xWidth + x]->end()); -} - -// _____________________________________________________________________________ -template -const std::vector* Grid::getCell(size_t x, size_t y) const { - return _grid[y * _xWidth + x]; -} - -// _____________________________________________________________________________ -template -util::geo::Box Grid::getBox(size_t x, size_t y) const { - util::geo::Point sw(_bb.getLowerLeft().getX() + x * _cellWidth, - _bb.getLowerLeft().getY() + y * _cellHeight); - util::geo::Point ne(_bb.getLowerLeft().getX() + (x + 1) * _cellWidth, - _bb.getLowerLeft().getY() + (y + 1) * _cellHeight); - return util::geo::Box(sw, ne); -} - -// _____________________________________________________________________________ -template -size_t Grid::getCellXFromX(double x) const { - float dist = x - _bb.getLowerLeft().getX(); - if (dist < 0) return 0; - return dist / _cellWidth; -} - -// _____________________________________________________________________________ -template -size_t Grid::getCellYFromY(double y) const { - float dist = y - _bb.getLowerLeft().getY(); - if (dist < 0) return 0; - return dist / _cellHeight; -} - -// _____________________________________________________________________________ -template -size_t Grid::getXWidth() const { - return _xWidth; -} - -// _____________________________________________________________________________ -template -size_t Grid::getYHeight() const { - return _yHeight; -} +// Copyright 2017, University of Freiburg, +// Chair of Algorithms and Data Structures. +// Author: Patrick Brosi + +// _____________________________________________________________________________ +template +Grid::Grid() + : _width(0), + _height(0), + _cellWidth(0), + _cellHeight(0), + _xWidth(0), + _yHeight(0), + _grid(0) {} + +// _____________________________________________________________________________ +template +Grid::Grid(double w, double h, const util::geo::Box& bbox) + : _cellWidth(fabs(w)), _cellHeight(fabs(h)), _bb(bbox), _grid() { + _width = bbox.getUpperRight().getX() - bbox.getLowerLeft().getX(); + _height = bbox.getUpperRight().getY() - bbox.getLowerLeft().getY(); + + if (_width < 0 || _height < 0) { + _width = 0; + _height = 0; + _xWidth = 0; + _yHeight = 0; + return; + } + + _xWidth = ceil(_width / _cellWidth); + _yHeight = ceil(_height / _cellHeight); + + // resize rows + _grid = new std::vector*[_xWidth * _yHeight]; + memset(_grid, 0, _xWidth * _yHeight * sizeof(std::vector*)); +} + +// _____________________________________________________________________________ +template +void Grid::add(const util::geo::Point& p, const V& val) { + add(getCellXFromX(p.getX()), getCellYFromY(p.getY()), val); +} + +// _____________________________________________________________________________ +template +void Grid::add(const util::geo::Box& box, const V& val) { + size_t swX = getCellXFromX(box.getLowerLeft().getX()); + size_t swY = getCellYFromY(box.getLowerLeft().getY()); + + size_t neX = getCellXFromX(box.getUpperRight().getX()); + size_t neY = getCellYFromY(box.getUpperRight().getY()); + + for (size_t x = swX; x <= neX && x < _xWidth; x++) { + for (size_t y = swY; y <= neY && y < _yHeight; y++) { + add(x, y, val); + } + } +} + +// _____________________________________________________________________________ +template +void Grid::add(size_t x, size_t y, V val) { + if (x >= _xWidth || y >= _yHeight) return; + if (!_grid[y * _xWidth + x]) _grid[y * _xWidth + x] = new std::vector(); + _grid[y * _xWidth + x]->push_back(val); +} + +// _____________________________________________________________________________ +template +void Grid::get(const util::geo::Box& box, + std::vector* s) const { + size_t swX = getCellXFromX(box.getLowerLeft().getX()); + size_t swY = getCellYFromY(box.getLowerLeft().getY()); + + size_t neX = getCellXFromX(box.getUpperRight().getX()); + size_t neY = getCellYFromY(box.getUpperRight().getY()); + + for (size_t x = swX; x <= neX && x < _xWidth; x++) + for (size_t y = swY; y <= neY && y < _yHeight; y++) get(x, y, s); +} + +// _____________________________________________________________________________ +template +void Grid::get(const util::geo::Box& box, + std::unordered_set* s) const { + size_t swX = getCellXFromX(box.getLowerLeft().getX()); + size_t swY = getCellYFromY(box.getLowerLeft().getY()); + + size_t neX = getCellXFromX(box.getUpperRight().getX()); + size_t neY = getCellYFromY(box.getUpperRight().getY()); + + for (size_t x = swX; x <= neX && x < _xWidth; x++) + for (size_t y = swY; y <= neY && y < _yHeight; y++) get(x, y, s); +} + +// _____________________________________________________________________________ +template +void Grid::get(size_t x, size_t y, std::unordered_set* s) const { + if (!_grid[y * _xWidth + x]) return; + s->insert(_grid[y * _xWidth + x]->begin(), _grid[y * _xWidth + x]->end()); +} + +// _____________________________________________________________________________ +template +void Grid::get(size_t x, size_t y, std::vector* s) const { + if (!_grid[y * _xWidth + x]) return; + s->insert(s->end(), _grid[y * _xWidth + x]->begin(), _grid[y * _xWidth + x]->end()); +} + +// _____________________________________________________________________________ +template +const std::vector* Grid::getCell(size_t x, size_t y) const { + return _grid[y * _xWidth + x]; +} + +// _____________________________________________________________________________ +template +util::geo::Box Grid::getBox(size_t x, size_t y) const { + util::geo::Point sw(_bb.getLowerLeft().getX() + x * _cellWidth, + _bb.getLowerLeft().getY() + y * _cellHeight); + util::geo::Point ne(_bb.getLowerLeft().getX() + (x + 1) * _cellWidth, + _bb.getLowerLeft().getY() + (y + 1) * _cellHeight); + return util::geo::Box(sw, ne); +} + +// _____________________________________________________________________________ +template +size_t Grid::getCellXFromX(double x) const { + float dist = x - _bb.getLowerLeft().getX(); + if (dist < 0) return 0; + return dist / _cellWidth; +} + +// _____________________________________________________________________________ +template +size_t Grid::getCellYFromY(double y) const { + float dist = y - _bb.getLowerLeft().getY(); + if (dist < 0) return 0; + return dist / _cellHeight; +} + +// _____________________________________________________________________________ +template +size_t Grid::getXWidth() const { + return _xWidth; +} + +// _____________________________________________________________________________ +template +size_t Grid::getYHeight() const { + return _yHeight; +} diff --git a/src/qlever-petrimaps/Misc.cpp b/src/qlever-petrimaps/Misc.cpp index 7e3378c..23453d0 100644 --- a/src/qlever-petrimaps/Misc.cpp +++ b/src/qlever-petrimaps/Misc.cpp @@ -1,250 +1,250 @@ -// Copyright 2022, University of Freiburg, -// Chair of Algorithms and Data Structures. -// Authors: Patrick Brosi - -#include - -#include -#include -#include - -#include "qlever-petrimaps/Misc.h" -#include "util/log/Log.h" - -using petrimaps::RequestReader; - -// _____________________________________________________________________________ -void RequestReader::requestIds(const std::string& query) { - CURLcode res; - char errbuf[CURL_ERROR_SIZE]; - - _raw.clear(); - _raw.reserve(10000); - - if (_curl) { - auto url = queryUrl(query); - curl_easy_setopt(_curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, RequestReader::writeCbIds); - curl_easy_setopt(_curl, CURLOPT_WRITEDATA, this); - curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYPEER, false); - curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYHOST, false); - curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, 0); - - // set headers - struct curl_slist* headers = 0; - headers = curl_slist_append(headers, "Accept: application/octet-stream"); - curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, headers); - - // accept any compression supported - curl_easy_setopt(_curl, CURLOPT_ACCEPT_ENCODING, ""); - curl_easy_setopt(_curl, CURLOPT_ERRORBUFFER, errbuf); - res = curl_easy_perform(_curl); - - long httpCode = 0; - curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, &httpCode); - - curl_slist_free_all(headers); - - if (httpCode != 200) { - std::stringstream ss; - ss << "QLever backend returned status code " << httpCode; - ss << "\n"; - ss << _raw; - throw std::runtime_error(ss.str()); - } - - if (exceptionPtr) std::rethrow_exception(exceptionPtr); - - } else { - LOG(ERROR) << "[REQUESTREADER] Failed to perform curl request."; - return; - } - - if (res != CURLE_OK) { - std::stringstream ss; - ss << "QLever backend request failed: "; - size_t len = strlen(errbuf); - if (len > 0) { - LOG(ERROR) << "[REQUESTREADER] " << errbuf; - ss << errbuf; - } else { - LOG(ERROR) << "[REQUESTREADER] " << curl_easy_strerror(res); - ss << curl_easy_strerror(res); - } - - throw std::runtime_error(ss.str()); - } -} - -// _____________________________________________________________________________ -void RequestReader::requestRows(const std::string& query) { - return requestRows(query, RequestReader::writeCb, this); -} - -// _____________________________________________________________________________ -void RequestReader::requestRows(const std::string& query, - size_t (*writeCb)(void*, size_t, size_t, - void*), void* ptr) { - CURLcode res; - char errbuf[CURL_ERROR_SIZE]; - - _raw.clear(); - _raw.reserve(10000); - - if (_curl) { - auto url = queryUrl(query); - curl_easy_setopt(_curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, writeCb); - curl_easy_setopt(_curl, CURLOPT_WRITEDATA, ptr); - curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYPEER, false); - curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYHOST, false); - curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, 0); - - // set headers - struct curl_slist* headers = 0; - headers = curl_slist_append(headers, "Accept: text/tab-separated-values"); - curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, headers); - - // accept any compression supported - curl_easy_setopt(_curl, CURLOPT_ACCEPT_ENCODING, ""); - curl_easy_setopt(_curl, CURLOPT_ERRORBUFFER, errbuf); - res = curl_easy_perform(_curl); - - long httpCode = 0; - curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, &httpCode); - - curl_slist_free_all(headers); - - if (httpCode != 200) { - std::stringstream ss; - ss << "QLever backend returned status code " << httpCode; - ss << "\n"; - ss << _raw; - throw std::runtime_error(ss.str()); - } - - if (exceptionPtr) std::rethrow_exception(exceptionPtr); - } else { - LOG(ERROR) << "[REQUESTREADER] Failed to perform curl request."; - return; - } - - if (res != CURLE_OK) { - std::stringstream ss; - ss << "QLever backend request failed: "; - size_t len = strlen(errbuf); - if (len > 0) { - LOG(ERROR) << "[REQUESTREADER] " << errbuf; - ss << errbuf; - } else { - LOG(ERROR) << "[REQUESTREADER] " << curl_easy_strerror(res); - ss << curl_easy_strerror(res); - } - - throw std::runtime_error(ss.str()); - } -} - -// _____________________________________________________________________________ -std::string RequestReader::queryUrl(const std::string& query) const { - auto escStr = curl_easy_escape(_curl, query.c_str(), query.size()); - std::string esc = escStr; - curl_free(escStr); - - return _backendUrl + "/?send=18446744073709551615" + "&query=" + esc; -} - -// _____________________________________________________________________________ -size_t RequestReader::writeCb(void* contents, size_t size, size_t nmemb, - void* userp) { - size_t realsize = size * nmemb; - try { - static_cast(userp)->parse( - static_cast(contents), realsize); - } catch (...) { - static_cast(userp)->exceptionPtr = std::current_exception(); - return CURLE_WRITE_ERROR; - } - - return realsize; -} - -// _____________________________________________________________________________ -size_t RequestReader::writeCbIds(void* contents, size_t size, size_t nmemb, - void* userp) { - size_t realsize = size * nmemb; - try { - static_cast(userp)->parseIds( - static_cast(contents), realsize); - } catch (...) { - static_cast(userp)->exceptionPtr = std::current_exception(); - return CURLE_WRITE_ERROR; - } - - return realsize; -} - -// _____________________________________________________________________________ -void RequestReader::parseIds(const char* c, size_t size) { - // TODO: just a rough approximation - checkMem(size, _maxMemory); - - for (size_t i = 0; i < size; i++) { - if (_raw.size() < 10000) _raw.push_back(c[i]); - _curId.bytes[_curByte] = c[i]; - _curByte = (_curByte + 1) % 8; - - if (_curByte == 0) { - ids.push_back({_curId.val, ids.size()}); - } - } -} - -// _____________________________________________________________________________ -void RequestReader::parse(const char* c, size_t size) { - // TODO: just a rough approximation - checkMem(size, _maxMemory); - - const char* start = c; - while (c < start + size) { - if (_raw.size() < 10000) _raw.push_back(*c); - switch (_state) { - case IN_HEADER: - if (*c == '\t' || *c == '\n') { - _colNames.push_back(_dangling); - _dangling.clear(); - } - - if (*c == '\n') { - _curRow++; - _state = IN_ROW; - c++; - } else { - if (*c != '\t') _dangling += *c; - c++; - continue; - } - case IN_ROW: - if (*c == '\t' || *c == '\n') { - curCols.push_back({_colNames[_curCol], _dangling}); - - if (*c == '\n') { - _curRow++; - rows.push_back(curCols); - curCols = {}; - _curCol = 0; - } else { - _curCol++; - } - _dangling = ""; - c++; - continue; - } - - _dangling += *c; - c++; - - break; - } - } -} +// Copyright 2022, University of Freiburg, +// Chair of Algorithms and Data Structures. +// Authors: Patrick Brosi + +#include + +#include +#include +#include + +#include "qlever-petrimaps/Misc.h" +#include "util/log/Log.h" + +using petrimaps::RequestReader; + +// _____________________________________________________________________________ +void RequestReader::requestIds(const std::string& query) { + CURLcode res; + char errbuf[CURL_ERROR_SIZE]; + + _raw.clear(); + _raw.reserve(10000); + + if (_curl) { + auto url = queryUrl(query); + curl_easy_setopt(_curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, RequestReader::writeCbIds); + curl_easy_setopt(_curl, CURLOPT_WRITEDATA, this); + curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYPEER, false); + curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYHOST, false); + curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, 0); + + // set headers + struct curl_slist* headers = 0; + headers = curl_slist_append(headers, "Accept: application/octet-stream"); + curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, headers); + + // accept any compression supported + curl_easy_setopt(_curl, CURLOPT_ACCEPT_ENCODING, ""); + curl_easy_setopt(_curl, CURLOPT_ERRORBUFFER, errbuf); + res = curl_easy_perform(_curl); + + long httpCode = 0; + curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, &httpCode); + + curl_slist_free_all(headers); + + if (httpCode != 200) { + std::stringstream ss; + ss << "QLever backend returned status code " << httpCode; + ss << "\n"; + ss << _raw; + throw std::runtime_error(ss.str()); + } + + if (exceptionPtr) std::rethrow_exception(exceptionPtr); + + } else { + LOG(ERROR) << "[REQUESTREADER] Failed to perform curl request."; + return; + } + + if (res != CURLE_OK) { + std::stringstream ss; + ss << "QLever backend request failed: "; + size_t len = strlen(errbuf); + if (len > 0) { + LOG(ERROR) << "[REQUESTREADER] " << errbuf; + ss << errbuf; + } else { + LOG(ERROR) << "[REQUESTREADER] " << curl_easy_strerror(res); + ss << curl_easy_strerror(res); + } + + throw std::runtime_error(ss.str()); + } +} + +// _____________________________________________________________________________ +void RequestReader::requestRows(const std::string& query) { + return requestRows(query, RequestReader::writeCb, this); +} + +// _____________________________________________________________________________ +void RequestReader::requestRows(const std::string& query, + size_t (*writeCb)(void*, size_t, size_t, + void*), void* ptr) { + CURLcode res; + char errbuf[CURL_ERROR_SIZE]; + + _raw.clear(); + _raw.reserve(10000); + + if (_curl) { + auto url = queryUrl(query); + curl_easy_setopt(_curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(_curl, CURLOPT_WRITEFUNCTION, writeCb); + curl_easy_setopt(_curl, CURLOPT_WRITEDATA, ptr); + curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYPEER, false); + curl_easy_setopt(_curl, CURLOPT_SSL_VERIFYHOST, false); + curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, 0); + + // set headers + struct curl_slist* headers = 0; + headers = curl_slist_append(headers, "Accept: text/tab-separated-values"); + curl_easy_setopt(_curl, CURLOPT_HTTPHEADER, headers); + + // accept any compression supported + curl_easy_setopt(_curl, CURLOPT_ACCEPT_ENCODING, ""); + curl_easy_setopt(_curl, CURLOPT_ERRORBUFFER, errbuf); + res = curl_easy_perform(_curl); + + long httpCode = 0; + curl_easy_getinfo(_curl, CURLINFO_RESPONSE_CODE, &httpCode); + + curl_slist_free_all(headers); + + if (httpCode != 200) { + std::stringstream ss; + ss << "QLever backend returned status code " << httpCode; + ss << "\n"; + ss << _raw; + throw std::runtime_error(ss.str()); + } + + if (exceptionPtr) std::rethrow_exception(exceptionPtr); + } else { + LOG(ERROR) << "[REQUESTREADER] Failed to perform curl request."; + return; + } + + if (res != CURLE_OK) { + std::stringstream ss; + ss << "QLever backend request failed: "; + size_t len = strlen(errbuf); + if (len > 0) { + LOG(ERROR) << "[REQUESTREADER] " << errbuf; + ss << errbuf; + } else { + LOG(ERROR) << "[REQUESTREADER] " << curl_easy_strerror(res); + ss << curl_easy_strerror(res); + } + + throw std::runtime_error(ss.str()); + } +} + +// _____________________________________________________________________________ +std::string RequestReader::queryUrl(const std::string& query) const { + auto escStr = curl_easy_escape(_curl, query.c_str(), query.size()); + std::string esc = escStr; + curl_free(escStr); + + return _backendUrl + "/?send=18446744073709551615" + "&query=" + esc; +} + +// _____________________________________________________________________________ +size_t RequestReader::writeCb(void* contents, size_t size, size_t nmemb, + void* userp) { + size_t realsize = size * nmemb; + try { + static_cast(userp)->parse( + static_cast(contents), realsize); + } catch (...) { + static_cast(userp)->exceptionPtr = std::current_exception(); + return CURLE_WRITE_ERROR; + } + + return realsize; +} + +// _____________________________________________________________________________ +size_t RequestReader::writeCbIds(void* contents, size_t size, size_t nmemb, + void* userp) { + size_t realsize = size * nmemb; + try { + static_cast(userp)->parseIds( + static_cast(contents), realsize); + } catch (...) { + static_cast(userp)->exceptionPtr = std::current_exception(); + return CURLE_WRITE_ERROR; + } + + return realsize; +} + +// _____________________________________________________________________________ +void RequestReader::parseIds(const char* c, size_t size) { + // TODO: just a rough approximation + checkMem(size, _maxMemory); + + for (size_t i = 0; i < size; i++) { + if (_raw.size() < 10000) _raw.push_back(c[i]); + _curId.bytes[_curByte] = c[i]; + _curByte = (_curByte + 1) % 8; + + if (_curByte == 0) { + ids.push_back({_curId.val, ids.size()}); + } + } +} + +// _____________________________________________________________________________ +void RequestReader::parse(const char* c, size_t size) { + // TODO: just a rough approximation + checkMem(size, _maxMemory); + + const char* start = c; + while (c < start + size) { + if (_raw.size() < 10000) _raw.push_back(*c); + switch (_state) { + case IN_HEADER: + if (*c == '\t' || *c == '\n') { + _colNames.push_back(_dangling); + _dangling.clear(); + } + + if (*c == '\n') { + _curRow++; + _state = IN_ROW; + c++; + } else { + if (*c != '\t') _dangling += *c; + c++; + continue; + } + case IN_ROW: + if (*c == '\t' || *c == '\n') { + curCols.push_back({_colNames[_curCol], _dangling}); + + if (*c == '\n') { + _curRow++; + rows.push_back(curCols); + curCols = {}; + _curCol = 0; + } else { + _curCol++; + } + _dangling = ""; + c++; + continue; + } + + _dangling += *c; + c++; + + break; + } + } +} diff --git a/src/qlever-petrimaps/Misc.h b/src/qlever-petrimaps/Misc.h index eee20c7..ea0f39e 100644 --- a/src/qlever-petrimaps/Misc.h +++ b/src/qlever-petrimaps/Misc.h @@ -1,134 +1,134 @@ -// Copyright 2022, University of Freiburg, -// Chair of Algorithms and Data Structures. -// Authors: Patrick Brosi - -#include -#include - -#include -#include -#include -#include - -#include "util/Misc.h" - -#ifndef PETRIMAPS_MISC_H_ -#define PETRIMAPS_MISC_H_ - -#define ID_TYPE uint32_t -// #define QLEVER_ID_TYPE size_t -#define QLEVER_ID_TYPE uint32_t - -const static ID_TYPE I_OFFSET = 500000000; -const static size_t MAXROWS = 18446744073709551615u; - -// major coordinates will fit into 2^15, as coordinates go from -// -200375083.427892 to +200375083.427892 -const static int16_t M_COORD_GRANULARITY = 12230; -const static int16_t M_COORD_OFFSET = 16384; - -namespace petrimaps { - -enum ParseState { IN_HEADER, IN_ROW }; - -struct IdMapping { - QLEVER_ID_TYPE qid; - ID_TYPE id; -}; - -union ID { - uint64_t val; - uint8_t bytes[8]; -}; - -inline bool operator<(const IdMapping& lh, const IdMapping& rh) { - if (lh.qid < rh.qid) return true; - // if (lh.qid == rh.qid && lh.id < rh.id) return true; - return false; -} - -inline int16_t mCoord(int16_t c) { - if (c < 0) return c - M_COORD_OFFSET; - return c + M_COORD_OFFSET; -} - -inline int16_t rmCoord(int16_t c) { - if (c < -M_COORD_OFFSET) return c + M_COORD_OFFSET; - return c - M_COORD_OFFSET; -} - -inline int16_t isMCoord(int16_t c) { - return c < -M_COORD_OFFSET || c >= M_COORD_OFFSET; -} - -class OutOfMemoryError : public std::exception { - public: - explicit OutOfMemoryError(size_t want, size_t have, size_t max) { - std::stringstream ss; - ss << "Out of memory, "; - ss << "want: " << want << " bytes, already used: " << have << " of " << max - << " bytes"; - - _msg = ss.str(); - } - - const char* what() const noexcept { return _msg.c_str(); } - - private: - std::string _msg; -}; - -inline void checkMem(size_t want, size_t max) { - size_t currentSize = util::getCurrentRSS(); - - if (currentSize + want > max) { - throw OutOfMemoryError(want, currentSize, max); - } -} - -struct RequestReader { - explicit RequestReader(const std::string& backendUrl, size_t maxMemory) - : _backendUrl(backendUrl), - _curl(curl_easy_init()), - _maxMemory(maxMemory) {} - ~RequestReader() { - if (_curl) curl_easy_cleanup(_curl); - } - - void requestIds(const std::string& qurl); - void requestRows(const std::string& qurl); - void requestRows(const std::string& query, - size_t (*writeCb)(void*, size_t, size_t, void*), void* ptr); - void parse(const char*, size_t size); - void parseIds(const char*, size_t size); - - static size_t writeCb(void* contents, size_t size, size_t nmemb, void* userp); - static size_t writeCbIds(void* contents, size_t size, size_t nmemb, - void* userp); - - std::string queryUrl(const std::string& query) const; - - std::string _backendUrl; - CURL* _curl; - - std::vector _colNames; - size_t _curCol = 0; - size_t _curRow = 0; - - std::string _dangling, _raw; - ParseState _state = IN_HEADER; - - std::vector>> rows; - std::vector> curCols; - - uint8_t _curByte = 0; - ID _curId; - size_t _received = 0; - std::vector ids; - size_t _maxMemory; - std::exception_ptr exceptionPtr; -}; - -} // namespace petrimaps - -#endif // PETRIMAPS_MISC_H_ +// Copyright 2022, University of Freiburg, +// Chair of Algorithms and Data Structures. +// Authors: Patrick Brosi + +#include +#include + +#include +#include +#include +#include + +#include "util/Misc.h" + +#ifndef PETRIMAPS_MISC_H_ +#define PETRIMAPS_MISC_H_ + +#define ID_TYPE uint32_t +// #define QLEVER_ID_TYPE size_t +#define QLEVER_ID_TYPE uint32_t + +const static ID_TYPE I_OFFSET = 500000000; +const static size_t MAXROWS = 18446744073709551615u; + +// major coordinates will fit into 2^15, as coordinates go from +// -200375083.427892 to +200375083.427892 +const static int16_t M_COORD_GRANULARITY = 12230; +const static int16_t M_COORD_OFFSET = 16384; + +namespace petrimaps { + +enum ParseState { IN_HEADER, IN_ROW }; + +struct IdMapping { + QLEVER_ID_TYPE qid; + ID_TYPE id; +}; + +union ID { + uint64_t val; + uint8_t bytes[8]; +}; + +inline bool operator<(const IdMapping& lh, const IdMapping& rh) { + if (lh.qid < rh.qid) return true; + // if (lh.qid == rh.qid && lh.id < rh.id) return true; + return false; +} + +inline int16_t mCoord(int16_t c) { + if (c < 0) return c - M_COORD_OFFSET; + return c + M_COORD_OFFSET; +} + +inline int16_t rmCoord(int16_t c) { + if (c < -M_COORD_OFFSET) return c + M_COORD_OFFSET; + return c - M_COORD_OFFSET; +} + +inline int16_t isMCoord(int16_t c) { + return c < -M_COORD_OFFSET || c >= M_COORD_OFFSET; +} + +class OutOfMemoryError : public std::exception { + public: + explicit OutOfMemoryError(size_t want, size_t have, size_t max) { + std::stringstream ss; + ss << "Out of memory, "; + ss << "want: " << want << " bytes, already used: " << have << " of " << max + << " bytes"; + + _msg = ss.str(); + } + + const char* what() const noexcept { return _msg.c_str(); } + + private: + std::string _msg; +}; + +inline void checkMem(size_t want, size_t max) { + size_t currentSize = util::getCurrentRSS(); + + if (currentSize + want > max) { + throw OutOfMemoryError(want, currentSize, max); + } +} + +struct RequestReader { + explicit RequestReader(const std::string& backendUrl, size_t maxMemory) + : _backendUrl(backendUrl), + _curl(curl_easy_init()), + _maxMemory(maxMemory) {} + ~RequestReader() { + if (_curl) curl_easy_cleanup(_curl); + } + + void requestIds(const std::string& qurl); + void requestRows(const std::string& qurl); + void requestRows(const std::string& query, + size_t (*writeCb)(void*, size_t, size_t, void*), void* ptr); + void parse(const char*, size_t size); + void parseIds(const char*, size_t size); + + static size_t writeCb(void* contents, size_t size, size_t nmemb, void* userp); + static size_t writeCbIds(void* contents, size_t size, size_t nmemb, + void* userp); + + std::string queryUrl(const std::string& query) const; + + std::string _backendUrl; + CURL* _curl; + + std::vector _colNames; + size_t _curCol = 0; + size_t _curRow = 0; + + std::string _dangling, _raw; + ParseState _state = IN_HEADER; + + std::vector>> rows; + std::vector> curCols; + + uint8_t _curByte = 0; + ID _curId; + size_t _received = 0; + std::vector ids; + size_t _maxMemory; + std::exception_ptr exceptionPtr; +}; + +} // namespace petrimaps + +#endif // PETRIMAPS_MISC_H_ diff --git a/src/qlever-petrimaps/PetriMapsMain.cpp b/src/qlever-petrimaps/PetriMapsMain.cpp index 50ecfb3..9bd93df 100755 --- a/src/qlever-petrimaps/PetriMapsMain.cpp +++ b/src/qlever-petrimaps/PetriMapsMain.cpp @@ -1,93 +1,93 @@ -// Copyright 2022, University of Freiburg, -// Chair of Algorithms and Data Structures. -// Authors: Patrick Brosi - -#include - -#include - -#include "qlever-petrimaps/server/Server.h" -#include "util/Misc.h" -#include "util/http/Server.h" -#include "util/log/Log.h" - -using petrimaps::Server; - -// _____________________________________________________________________________ -void printHelp(int argc, char** argv) { - UNUSED(argc); - std::cout << "Usage: " << argv[0] - << " [-p ] [-m ] [-c ] [--help] [-h]" - << "\n"; - std::cout - << "\nAllowed arguments:\n -p Port for server to listen to " - "(default: 9090)" - << "\n -m Max memory in GB (default: 90% of system RAM)" - << "\n -c cache dir (default: none)" - << "\n -t request cache lifetime (default: 360)\n"; -} - -// _____________________________________________________________________________ -int main(int argc, char** argv) { - // disable output buffering for standard output - setbuf(stdout, NULL); - - // initialize randomness - srand(time(NULL) + rand()); // NOLINT - - // init CURL - curl_global_init(CURL_GLOBAL_DEFAULT); - - // default port - int port = 9090; - int cacheLifetime = 6 * 60; - double maxMemoryGB = - (sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGE_SIZE) * 0.9) / 1000000000; - std::string cacheDir; - - for (int i = 1; i < argc; i++) { - std::string cur = argv[i]; - if (cur == "-h" || cur == "--help") { - printHelp(argc, argv); - exit(0); - } else if (cur == "-p") { - if (++i >= argc) { - LOG(ERROR) << "Missing argument for port (-p)."; - exit(1); - } - port = atoi(argv[i]); - } else if (cur == "-m") { - if (++i >= argc) { - LOG(ERROR) << "Missing argument for max memory (-m)."; - exit(1); - } - maxMemoryGB = atof(argv[i]); - } else if (cur == "-c") { - if (++i >= argc) { - LOG(ERROR) << "Missing argument for cache dir (-c)."; - exit(1); - } - cacheDir = argv[i]; - } else if (cur == "-t") { - if (++i >= argc) { - LOG(ERROR) << "Missing argument for cache lifetime (-t)."; - exit(1); - } - cacheLifetime = atof(argv[i]); - } - } - - if (cacheDir.size() && access(cacheDir.c_str(), W_OK) != 0) { - std::stringstream ss; - ss << "No write access to cache dir " << cacheDir; - throw std::runtime_error(ss.str()); - } - - LOG(INFO) << "Starting server..."; - LOG(INFO) << "Max memory is " << maxMemoryGB << " GB..."; - Server serv(maxMemoryGB * 1000000000, cacheDir, cacheLifetime); - - LOG(INFO) << "Listening on port " << port; - util::http::HttpServer(port, &serv, std::thread::hardware_concurrency()) - .run(); -} +// Copyright 2022, University of Freiburg, +// Chair of Algorithms and Data Structures. +// Authors: Patrick Brosi + +#include + +#include + +#include "qlever-petrimaps/server/Server.h" +#include "util/Misc.h" +#include "util/http/Server.h" +#include "util/log/Log.h" + +using petrimaps::Server; + +// _____________________________________________________________________________ +void printHelp(int argc, char** argv) { + UNUSED(argc); + std::cout << "Usage: " << argv[0] + << " [-p ] [-m ] [-c ] [--help] [-h]" + << "\n"; + std::cout + << "\nAllowed arguments:\n -p Port for server to listen to " + "(default: 9090)" + << "\n -m Max memory in GB (default: 90% of system RAM)" + << "\n -c cache dir (default: none)" + << "\n -t request cache lifetime (default: 360)\n"; +} + +// _____________________________________________________________________________ +int main(int argc, char** argv) { + // disable output buffering for standard output + setbuf(stdout, NULL); + + // initialize randomness + srand(time(NULL) + rand()); // NOLINT + + // init CURL + curl_global_init(CURL_GLOBAL_DEFAULT); + + // default port + int port = 9090; + int cacheLifetime = 6 * 60; + double maxMemoryGB = + (sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGE_SIZE) * 0.9) / 1000000000; + std::string cacheDir; + + for (int i = 1; i < argc; i++) { + std::string cur = argv[i]; + if (cur == "-h" || cur == "--help") { + printHelp(argc, argv); + exit(0); + } else if (cur == "-p") { + if (++i >= argc) { + LOG(ERROR) << "Missing argument for port (-p)."; + exit(1); + } + port = atoi(argv[i]); + } else if (cur == "-m") { + if (++i >= argc) { + LOG(ERROR) << "Missing argument for max memory (-m)."; + exit(1); + } + maxMemoryGB = atof(argv[i]); + } else if (cur == "-c") { + if (++i >= argc) { + LOG(ERROR) << "Missing argument for cache dir (-c)."; + exit(1); + } + cacheDir = argv[i]; + } else if (cur == "-t") { + if (++i >= argc) { + LOG(ERROR) << "Missing argument for cache lifetime (-t)."; + exit(1); + } + cacheLifetime = atof(argv[i]); + } + } + + if (cacheDir.size() && access(cacheDir.c_str(), W_OK) != 0) { + std::stringstream ss; + ss << "No write access to cache dir " << cacheDir; + throw std::runtime_error(ss.str()); + } + + LOG(INFO) << "Starting server..."; + LOG(INFO) << "Max memory is " << maxMemoryGB << " GB..."; + Server serv(maxMemoryGB * 1000000000, cacheDir, cacheLifetime); + + LOG(INFO) << "Listening on port " << port; + util::http::HttpServer(port, &serv, std::thread::hardware_concurrency()) + .run(); +} diff --git a/src/qlever-petrimaps/server/Requestor.cpp b/src/qlever-petrimaps/server/Requestor.cpp index a73b79e..a421b40 100644 --- a/src/qlever-petrimaps/server/Requestor.cpp +++ b/src/qlever-petrimaps/server/Requestor.cpp @@ -1,781 +1,781 @@ -// Copyright 2022, University of Freiburg, -// Chair of Algorithms and Data Structures. -// Authors: Patrick Brosi - -#include -#include -#include -#include -#include - -#include "qlever-petrimaps/Misc.h" -#include "qlever-petrimaps/server/Requestor.h" -#include "util/Misc.h" -#include "util/geo/Geo.h" -#include "util/geo/PolyLine.h" -#include "util/log/Log.h" -#ifdef _OPENMP -#include -#else -#define omp_get_thread_num() 0 -#endif - -using petrimaps::GeomCache; -using petrimaps::Requestor; -using petrimaps::RequestReader; -using petrimaps::ResObj; - -// _____________________________________________________________________________ -void Requestor::request(const std::string& qry) { - std::lock_guard guard(_m); - - if (_ready) { - // nothing to do - return; - } - - if (!_cache->ready()) { - throw std::runtime_error("Geom cache not ready"); - } - - _query = qry; - _ready = false; - _objects.clear(); - _clusterObjects.clear(); - - RequestReader reader(_cache->getBackendURL(), _maxMemory); - //_query = qry; - - LOG(INFO) << "[REQUESTOR] Requesting IDs for query " << qry; - reader.requestIds(prepQuery(qry)); - - LOG(INFO) << "[REQUESTOR] Done, have " << reader.ids.size() - << " ids in total."; - - // join with geoms from GeomCache - - // sort by qlever id - LOG(INFO) << "[REQUESTOR] Sorting results by qlever ID..."; - std::sort(reader.ids.begin(), reader.ids.end()); - LOG(INFO) << "[REQUESTOR] ... done"; - - LOG(INFO) << "[REQUESTOR] Retrieving geoms from cache..."; - - // (geom id, result row) - const auto& ret = _cache->getRelObjects(reader.ids); - _objects = ret.first; - _numObjects = ret.second; - LOG(INFO) << "[REQUESTOR] ... done, got " << _objects.size() << " objects."; - - LOG(INFO) << "[REQUESTOR] Calculating bounding box of result..."; - - size_t NUM_THREADS = std::thread::hardware_concurrency(); - - std::vector pointBoxes(NUM_THREADS); - std::vector lineBoxes(NUM_THREADS); - std::vector numLines(NUM_THREADS, 0); - util::geo::FBox pointBbox; - util::geo::DBox lineBbox; - size_t batch = ceil(static_cast(_objects.size()) / NUM_THREADS); - -#pragma omp parallel for num_threads(NUM_THREADS) schedule(static) - for (size_t t = 0; t < NUM_THREADS; t++) { - for (size_t i = batch * t; i < batch * (t + 1) && i < _objects.size(); - i++) { - auto geomId = _objects[i].first; - - if (geomId < I_OFFSET) { - auto pId = geomId; - pointBoxes[t] = - util::geo::extendBox(_cache->getPoints()[pId], pointBoxes[t]); - } else if (geomId < std::numeric_limits::max()) { - auto lId = geomId - I_OFFSET; - - lineBoxes[t] = - util::geo::extendBox(_cache->getLineBBox(lId), lineBoxes[t]); - numLines[t]++; - } - } - } - - for (const auto& box : pointBoxes) { - pointBbox = util::geo::extendBox(box, pointBbox); - } - - for (const auto& box : lineBoxes) { - lineBbox = util::geo::extendBox(box, lineBbox); - } - - // to avoid zero area boxes if only one point is requested - pointBbox = util::geo::pad(pointBbox, 1); - lineBbox = util::geo::pad(lineBbox, 1); - - LOG(INFO) << "[REQUESTOR] ... done"; - - LOG(INFO) << "[REQUESTOR] Point BBox: " << util::geo::getWKT(pointBbox); - LOG(INFO) << "[REQUESTOR] Line BBox: " << util::geo::getWKT(lineBbox); - LOG(INFO) << "[REQUESTOR] Building grid..."; - - double GRID_SIZE = 65536; - - double pw = - pointBbox.getUpperRight().getX() - pointBbox.getLowerLeft().getX(); - double ph = - pointBbox.getUpperRight().getY() - pointBbox.getLowerLeft().getY(); - - // estimate memory consumption of empty grid - double pxWidth = fmax(0, ceil(pw / GRID_SIZE)); - double pyHeight = fmax(0, ceil(ph / GRID_SIZE)); - - double lw = lineBbox.getUpperRight().getX() - lineBbox.getLowerLeft().getX(); - double lh = lineBbox.getUpperRight().getY() - lineBbox.getLowerLeft().getY(); - - // estimate memory consumption of empty grid - double lxWidth = fmax(0, ceil(lw / GRID_SIZE)); - double lyHeight = fmax(0, ceil(lh / GRID_SIZE)); - - LOG(INFO) << "[REQUESTOR] (" << pxWidth << "x" << pyHeight - << " cell point grid)"; - LOG(INFO) << "[REQUESTOR] (" << lxWidth << "x" << lyHeight - << " cell line grid)"; - - checkMem(8 * (pxWidth * pyHeight), _maxMemory); - checkMem(8 * (lxWidth * lyHeight), _maxMemory); - checkMem(8 * (lxWidth * lyHeight), _maxMemory); - - util::geo::FBox fLineBbox = { - {lineBbox.getLowerLeft().getX(), lineBbox.getLowerLeft().getY()}, - {lineBbox.getUpperRight().getX(), lineBbox.getUpperRight().getY()}}; - - _pgrid = petrimaps::Grid(GRID_SIZE, GRID_SIZE, pointBbox); - _lgrid = petrimaps::Grid(GRID_SIZE, GRID_SIZE, fLineBbox); - _lpgrid = petrimaps::Grid, float>( - GRID_SIZE, GRID_SIZE, fLineBbox); - - std::exception_ptr ePtr; - -#pragma omp parallel sections - { -#pragma omp section - { - size_t j = _objects.size(); - - for (size_t i = 0; i < _objects.size(); i++) { - const auto& p = _objects[i]; - auto geomId = p.first; - if (geomId >= I_OFFSET) continue; - - size_t clusterI = 0; - // cluster if they have same geometry, don't do for multigeoms - while (i < _objects.size() - 1 && geomId == _objects[i + 1].first && - p.second != _objects[i + 1].second) { - clusterI++; - i++; - } - - if (clusterI > 0) { - for (size_t m = 0; m < clusterI; m++) { - const auto& p = _objects[i - m]; - auto geomId = p.first; - _pgrid.add(_cache->getPoints()[geomId], j); - _clusterObjects.push_back({i - m, {m, clusterI}}); - j++; - } - } else { - _pgrid.add(_cache->getPoints()[geomId], i); - } - - // every 100000 objects, check memory... - if (i % 100000 == 0) { - try { - checkMem(1, _maxMemory); - } catch (...) { -#pragma omp critical - { ePtr = std::current_exception(); } - break; - } - } - } - } - -#pragma omp section - { - size_t i = 0; - for (const auto& l : _objects) { - if (l.first >= I_OFFSET && - l.first < std::numeric_limits::max()) { - auto geomId = l.first - I_OFFSET; - auto box = _cache->getLineBBox(geomId); - util::geo::FBox fbox = { - {box.getLowerLeft().getX(), box.getLowerLeft().getY()}, - {box.getUpperRight().getX(), box.getUpperRight().getY()}}; - _lgrid.add(fbox, i); - } - i++; - - // every 100000 objects, check memory... - if (i % 100000 == 0) { - try { - checkMem(1, _maxMemory); - } catch (...) { -#pragma omp critical - { ePtr = std::current_exception(); } - break; - } - } - } - } - -#pragma omp section - { - size_t i = 0; - for (const auto& l : _objects) { - if (l.first >= I_OFFSET && - l.first < std::numeric_limits::max()) { - auto geomId = l.first - I_OFFSET; - - size_t start = _cache->getLine(geomId); - size_t end = _cache->getLineEnd(geomId); - - double mainX = 0; - double mainY = 0; - - size_t gi = 0; - - uint8_t lastX = 0; - uint8_t lastY = 0; - - for (size_t li = start; li < end; li++) { - const auto& cur = _cache->getLinePoints()[li]; - - if (isMCoord(cur.getX())) { - mainX = rmCoord(cur.getX()); - mainY = rmCoord(cur.getY()); - continue; - } - - // skip bounding box at beginning - if (++gi < 3) continue; - - // extract real geometry - util::geo::FPoint curP( - (mainX * M_COORD_GRANULARITY + cur.getX()) / 10.0, - (mainY * M_COORD_GRANULARITY + cur.getY()) / 10.0); - - size_t cellX = _lpgrid.getCellXFromX(curP.getX()); - size_t cellY = _lpgrid.getCellYFromY(curP.getY()); - - uint8_t sX = - (curP.getX() - _lpgrid.getBBox().getLowerLeft().getX() + - cellX * _lpgrid.getCellWidth()) / - 256; - uint8_t sY = - (curP.getY() - _lpgrid.getBBox().getLowerLeft().getY() + - cellY * _lpgrid.getCellHeight()) / - 256; - - if (gi == 3 || lastX != sX || lastY != sY) { - _lpgrid.add(cellX, cellY, {sX, sY}); - lastX = sX; - lastY = sY; - } - } - } - i++; - - // every 100000 objects, check memory... - if (i % 100000 == 0) { - try { - checkMem(1, _maxMemory); - } catch (...) { -#pragma omp critical - { ePtr = std::current_exception(); } - break; - } - } - } - } - } - - if (ePtr) { - std::rethrow_exception(ePtr); - } - - _ready = true; - - LOG(INFO) << "[REQUESTOR] ...done"; -} - -// _____________________________________________________________________________ -std::vector> Requestor::requestRow( - uint64_t row) const { - if (!_cache->ready()) { - throw std::runtime_error("Geom cache not ready"); - } - RequestReader reader(_cache->getBackendURL(), _maxMemory); - LOG(INFO) << "[REQUESTOR] Requesting single row " << row << " for query " - << _query; - auto query = prepQueryRow(_query, row); - - LOG(INFO) << "[REQUESTOR] Row query is " << query; - - reader.requestRows(query); - - if (reader.rows.size() == 0) return {}; - - return reader.rows[0]; -} - -// _____________________________________________________________________________ -void Requestor::requestRows( - std::function< - void(std::vector>>)> - cb) const { - if (!_cache->ready()) { - throw std::runtime_error("Geom cache not ready"); - } - RequestReader reader(_cache->getBackendURL(), _maxMemory); - LOG(INFO) << "[REQUESTOR] Requesting rows for query " << _query; - - ReaderCbPair cbPair{&reader, cb}; - - reader.requestRows( - _query, - [](void* contents, size_t size, size_t nmemb, void* ptr) { - size_t realsize = size * nmemb; - auto pr = static_cast(ptr); - try { - // clear rows - pr->reader->rows = {}; - pr->reader->parse(static_cast(contents), realsize); - pr->cb(pr->reader->rows); - } catch (...) { - pr->reader->exceptionPtr = std::current_exception(); - return static_cast(CURLE_WRITE_ERROR); - } - - return realsize; - }, - &cbPair); -} - -// _____________________________________________________________________________ -std::string Requestor::prepQuery(std::string query) const { - // only use last column - std::regex expr("select[^{]*(\\?[A-Z0-9_\\-+]*)+[^{]*\\s*\\{", - std::regex_constants::icase); - - // only remove columns the first (=outer) SELECT statement - query = std::regex_replace(query, expr, "SELECT $1 WHERE {$&", - std::regex_constants::format_first_only) + "}"; - - if (util::toLower(query).find("limit") == std::string::npos) { - query += " LIMIT 18446744073709551615"; - } - - return query; -} - -// _____________________________________________________________________________ -std::string Requestor::prepQueryRow(std::string query, uint64_t row) const { - // replace first select - std::regex expr("select[^{]*\\?[A-Z0-9_\\-+]*+[^{]*\\s*\\{", - std::regex_constants::icase); - - query = std::regex_replace(query, expr, "SELECT * {$&", - std::regex_constants::format_first_only) + "}"; - query += "OFFSET " + std::to_string(row) + " LIMIT 1"; - return query; -} - -// _____________________________________________________________________________ -const ResObj Requestor::getNearest(util::geo::DPoint rp, double rad, double res, - util::geo::FBox fullbox) const { - if (!_cache->ready()) { - throw std::runtime_error("Geom cache not ready"); - } - auto box = pad(getBoundingBox(rp), rad); - auto fbox = pad(getBoundingBox(util::geo::FPoint(rp.getX(), rp.getY())), rad); - - auto frp = util::geo::FPoint{rp.getX(), rp.getY()}; - - size_t NUM_THREADS = std::thread::hardware_concurrency(); - - size_t nearest = 0; - double dBest = std::numeric_limits::max(); - std::vector nearestVec(NUM_THREADS, 0); - std::vector dBestVec(NUM_THREADS, std::numeric_limits::max()); - - std::vector nearestLVec(NUM_THREADS, 0); - std::vector dBestLVec(NUM_THREADS, - std::numeric_limits::max()); - size_t nearestL = 0; - double dBestL = std::numeric_limits::max(); -#pragma omp parallel sections - { -#pragma omp section - { - // points - - std::vector ret; - - if (res > 0) - _pgrid.get(fullbox, &ret); - else - _pgrid.get(fbox, &ret); - -#pragma omp parallel for num_threads(NUM_THREADS) schedule(static) - for (size_t idx = 0; idx < ret.size(); idx++) { - auto i = ret[idx]; - util::geo::FPoint p; - if (i >= _objects.size()) { - size_t cid = i - _objects.size(); - p = clusterGeom(cid, res); - } else { - p = _cache->getPoints()[_objects[i].first]; - } - - if (!util::geo::contains(p, fbox)) continue; - - double d = util::geo::dist(p, frp); - - if (d < dBestVec[omp_get_thread_num()]) { - nearestVec[omp_get_thread_num()] = i; - dBestVec[omp_get_thread_num()] = d; - } - } - } - -#pragma omp section - { - // lines - std::vector retL; - _lgrid.get(fbox, &retL); - -#pragma omp parallel for num_threads(NUM_THREADS) schedule(static) - for (size_t idx = 0; idx < retL.size(); idx++) { - const auto& i = retL[idx]; - auto lBox = _cache->getLineBBox(_objects[i].first - I_OFFSET); - if (!util::geo::intersects(lBox, box)) continue; - - size_t start = _cache->getLine(_objects[i].first - I_OFFSET); - size_t end = _cache->getLineEnd(_objects[i].first - I_OFFSET); - - // TODO _____________________ own function - double d = std::numeric_limits::infinity(); - - util::geo::DPoint curPa, curPb; - int s = 0; - - size_t gi = 0; - - double mainX = 0; - double mainY = 0; - - bool isArea = Requestor::isArea(_objects[i].first - I_OFFSET); - - util::geo::DLine areaBorder; - - for (size_t i = start; i < end; i++) { - // extract real geom - const auto& cur = _cache->getLinePoints()[i]; - - if (isMCoord(cur.getX())) { - mainX = rmCoord(cur.getX()); - mainY = rmCoord(cur.getY()); - continue; - } - - // skip bounding box at beginning - gi++; - if (gi < 3) continue; - - // extract real geometry - util::geo::DPoint curP( - (mainX * M_COORD_GRANULARITY + cur.getX()) / 10.0, - (mainY * M_COORD_GRANULARITY + cur.getY()) / 10.0); - - if (isArea) areaBorder.push_back(curP); - - if (s == 0) { - curPa = curP; - s++; - } else if (s == 1) { - curPb = curP; - s++; - } - - if (s == 2) { - s = 1; - double dTmp = util::geo::distToSegment(curPa, curPb, rp); - if (dTmp < 0.0001) { - d = 0; - break; - } - curPa = curPb; - if (dTmp < d) d = dTmp; - } - } - // TODO _____________________ own function - - if (isArea) { - if (util::geo::contains(rp, util::geo::DPolygon(areaBorder))) { - // set it to rad/4 - this allows selecting smaller objects - // inside the polgon - d = rad / 4; - } - } - - if (d < dBestLVec[omp_get_thread_num()]) { - nearestLVec[omp_get_thread_num()] = i; - dBestLVec[omp_get_thread_num()] = d; - } - } - } - } - - // join threads - for (size_t i = 0; i < NUM_THREADS; i++) { - if (dBestVec[i] < dBest) { - dBest = dBestVec[i]; - nearest = nearestVec[i]; - } - - if (dBestLVec[i] < dBestL) { - dBestL = dBestLVec[i]; - nearestL = nearestLVec[i]; - } - } - - if (dBest < rad && dBest <= dBestL) { - size_t row = 0; - if (nearest >= _objects.size()) - row = _objects[_clusterObjects[nearest - _objects.size()].first].second; - else - row = _objects[nearest].second; - - return {true, - nearest >= _objects.size() ? nearest - _objects.size() : nearest, - geomPointGeoms(nearest, res), - requestRow(row), - {}, - {}}; - } - - if (dBestL < rad && dBestL <= dBest) { - size_t lineId = _objects[nearestL].first - I_OFFSET; - - bool isArea = Requestor::isArea(lineId); - - const auto& dline = extractLineGeom(lineId); - - if (isArea && util::geo::contains(rp, util::geo::DPolygon(dline))) { - return {true, nearestL, - {frp}, requestRow(_objects[nearestL].second), - {}, geomPolyGeoms(nearestL, rad / 10)}; - } else { - if (isArea) { - auto p = util::geo::PolyLine(dline).projectOn(rp).p; - auto fp = util::geo::FPoint(p.getX(), p.getY()); - return {true, nearestL, - {fp}, requestRow(_objects[nearestL].second), - {}, geomPolyGeoms(nearestL, rad / 10)}; - } else { - auto p = util::geo::PolyLine(dline).projectOn(rp).p; - auto fp = util::geo::FPoint(p.getX(), p.getY()); - return {true, - nearestL, - {fp}, - requestRow(_objects[nearestL].second), - geomLineGeoms(nearestL, rad / 10), - {}}; - } - } - } - - return {false, 0, {{0, 0}}, {}, {}, {}}; -} - -// _____________________________________________________________________________ -const ResObj Requestor::getGeom(size_t id, double rad) const { - if (!_cache->ready()) { - throw std::runtime_error("Geom cache not ready"); - } - auto obj = _objects[id]; - - if (obj.first >= I_OFFSET) { - size_t lineId = obj.first - I_OFFSET; - - bool isArea = Requestor::isArea(lineId); - - if (isArea) { - return {true, id, {{0, 0}}, {}, {}, geomPolyGeoms(id, rad / 10)}; - } else { - return {true, id, {{0, 0}}, {}, geomLineGeoms(id, rad / 10), {}}; - } - } else { - return {true, id, geomPointGeoms(id), {}, {}, {}}; - } -} - -// _____________________________________________________________________________ -util::geo::DLine Requestor::extractLineGeom(size_t lineId) const { - util::geo::DLine dline; - - size_t start = _cache->getLine(lineId); - size_t end = _cache->getLineEnd(lineId); - - double mainX = 0; - double mainY = 0; - - size_t gi = 0; - - for (size_t i = start; i < end; i++) { - // extract real geom - const auto& cur = _cache->getLinePoints()[i]; - - if (isMCoord(cur.getX())) { - mainX = rmCoord(cur.getX()); - mainY = rmCoord(cur.getY()); - continue; - } - - // skip bounding box at beginning - gi++; - if (gi < 3) continue; - - util::geo::DPoint curP((mainX * M_COORD_GRANULARITY + cur.getX()) / 10.0, - (mainY * M_COORD_GRANULARITY + cur.getY()) / 10.0); - dline.push_back(curP); - } - - return dline; -} - -// _____________________________________________________________________________ -bool Requestor::isArea(size_t lineId) const { - size_t end = _cache->getLineEnd(lineId); - - return isMCoord(_cache->getLinePoints()[end - 1].getX()); -} - -// _____________________________________________________________________________ -util::geo::MultiLine Requestor::geomLineGeoms(size_t oid, - double eps) const { - std::vector polys; - - // catch multigeometries - for (size_t i = oid; - i < _objects.size() && _objects[i].second == _objects[oid].second; i++) { - if (_objects[oid].first < I_OFFSET) continue; - const auto& fline = extractLineGeom(_objects[i].first - I_OFFSET); - polys.push_back(util::geo::simplify(fline, eps)); - } - - for (size_t i = oid - 1; - i < _objects.size() && _objects[i].second == _objects[oid].second; i--) { - if (_objects[oid].first < I_OFFSET) continue; - const auto& fline = extractLineGeom(_objects[i].first - I_OFFSET); - polys.push_back(util::geo::simplify(fline, eps)); - } - - return polys; -} - -// _____________________________________________________________________________ -util::geo::MultiPoint Requestor::geomPointGeoms(size_t oid) const { - return geomPointGeoms(oid, -1); -} - -// _____________________________________________________________________________ -util::geo::MultiPoint Requestor::geomPointGeoms(size_t oid, - double res) const { - std::vector points; - - if (!(res < 0) && oid >= _objects.size()) { - return {clusterGeom(oid - _objects.size(), res)}; - } - - if (oid >= _objects.size()) { - oid = _clusterObjects[oid - _objects.size()].first; - } - - // catch multigeometries - for (size_t i = oid; - i < _objects.size() && _objects[i].second == _objects[oid].second; i++) { - if (_objects[oid].first >= I_OFFSET) continue; - points.push_back(_cache->getPoints()[_objects[i].first]); - } - - for (size_t i = oid - 1; - i < _objects.size() && _objects[i].second == _objects[oid].second; i--) { - if (_objects[oid].first >= I_OFFSET) continue; - points.push_back(_cache->getPoints()[_objects[i].first]); - } - - return points; -} - -// _____________________________________________________________________________ -util::geo::MultiPolygon Requestor::geomPolyGeoms(size_t oid, - double eps) const { - std::vector polys; - - // catch multigeometries - for (size_t i = oid; - i < _objects.size() && _objects[i].second == _objects[oid].second; i++) { - if (_objects[oid].first < I_OFFSET) continue; - const auto& dline = extractLineGeom(_objects[i].first - I_OFFSET); - polys.push_back(util::geo::DPolygon(util::geo::simplify(dline, eps))); - } - - for (size_t i = oid - 1; - i < _objects.size() && _objects[i].second == _objects[oid].second; i--) { - if (_objects[oid].first < I_OFFSET) continue; - const auto& dline = extractLineGeom(_objects[i].first - I_OFFSET); - polys.push_back(util::geo::DPolygon(util::geo::simplify(dline, eps))); - } - - return polys; -} - -// _____________________________________________________________________________ -util::geo::FPoint Requestor::clusterGeom(size_t cid, double res) const { - size_t oid = _clusterObjects[cid].first; - const auto& pp = _cache->getPoints()[_objects[oid].first]; - - if (res < 0) return {pp}; - - size_t num = _clusterObjects[cid].second.first; - size_t tot = _clusterObjects[cid].second.second; - - double a = 25; - double b = 6; - - if (tot > a) { - double rad = 2 * a; - - int row = ((-a - b / 2.0) + sqrt((a + b / 2.0) * (a + b / 2.0) + - 2.0 * b * (std::max(0.0, num - a + 2)))) / - b; - - double g = b * ((row * row + row) / 2.0); - - double relpos = num - (a * row + (g - row * b)); - double tot = a + row * b; - - double x = pp.getX() + (rad + row * 13.0) * res * - sin(relpos * (2.0 * 3.14159265359 / tot)); - double y = pp.getY() + (rad + row * 13.0) * res * - cos(relpos * (2.0 * 3.14159265359 / tot)); - - return util::geo::FPoint{x, y}; - } else { - float rad = 2 * tot; - - float x = pp.getX() + rad * res * sin(num * (2 * 3.14159265359 / tot)); - float y = pp.getY() + rad * res * cos(num * (2 * 3.14159265359 / tot)); - - return util::geo::FPoint{x, y}; - } -} +// Copyright 2022, University of Freiburg, +// Chair of Algorithms and Data Structures. +// Authors: Patrick Brosi + +#include +#include +#include +#include +#include + +#include "qlever-petrimaps/Misc.h" +#include "qlever-petrimaps/server/Requestor.h" +#include "util/Misc.h" +#include "util/geo/Geo.h" +#include "util/geo/PolyLine.h" +#include "util/log/Log.h" +#ifdef _OPENMP +#include +#else +#define omp_get_thread_num() 0 +#endif + +using petrimaps::GeomCache; +using petrimaps::Requestor; +using petrimaps::RequestReader; +using petrimaps::ResObj; + +// _____________________________________________________________________________ +void Requestor::request(const std::string& qry) { + std::lock_guard guard(_m); + + if (_ready) { + // nothing to do + return; + } + + if (!_cache->ready()) { + throw std::runtime_error("Geom cache not ready"); + } + + _query = qry; + _ready = false; + _objects.clear(); + _clusterObjects.clear(); + + RequestReader reader(_cache->getBackendURL(), _maxMemory); + //_query = qry; + + LOG(INFO) << "[REQUESTOR] Requesting IDs for query " << qry; + reader.requestIds(prepQuery(qry)); + + LOG(INFO) << "[REQUESTOR] Done, have " << reader.ids.size() + << " ids in total."; + + // join with geoms from GeomCache + + // sort by qlever id + LOG(INFO) << "[REQUESTOR] Sorting results by qlever ID..."; + std::sort(reader.ids.begin(), reader.ids.end()); + LOG(INFO) << "[REQUESTOR] ... done"; + + LOG(INFO) << "[REQUESTOR] Retrieving geoms from cache..."; + + // (geom id, result row) + const auto& ret = _cache->getRelObjects(reader.ids); + _objects = ret.first; + _numObjects = ret.second; + LOG(INFO) << "[REQUESTOR] ... done, got " << _objects.size() << " objects."; + + LOG(INFO) << "[REQUESTOR] Calculating bounding box of result..."; + + size_t NUM_THREADS = std::thread::hardware_concurrency(); + + std::vector pointBoxes(NUM_THREADS); + std::vector lineBoxes(NUM_THREADS); + std::vector numLines(NUM_THREADS, 0); + util::geo::FBox pointBbox; + util::geo::DBox lineBbox; + size_t batch = ceil(static_cast(_objects.size()) / NUM_THREADS); + +#pragma omp parallel for num_threads(NUM_THREADS) schedule(static) + for (size_t t = 0; t < NUM_THREADS; t++) { + for (size_t i = batch * t; i < batch * (t + 1) && i < _objects.size(); + i++) { + auto geomId = _objects[i].first; + + if (geomId < I_OFFSET) { + auto pId = geomId; + pointBoxes[t] = + util::geo::extendBox(_cache->getPoints()[pId], pointBoxes[t]); + } else if (geomId < std::numeric_limits::max()) { + auto lId = geomId - I_OFFSET; + + lineBoxes[t] = + util::geo::extendBox(_cache->getLineBBox(lId), lineBoxes[t]); + numLines[t]++; + } + } + } + + for (const auto& box : pointBoxes) { + pointBbox = util::geo::extendBox(box, pointBbox); + } + + for (const auto& box : lineBoxes) { + lineBbox = util::geo::extendBox(box, lineBbox); + } + + // to avoid zero area boxes if only one point is requested + pointBbox = util::geo::pad(pointBbox, 1); + lineBbox = util::geo::pad(lineBbox, 1); + + LOG(INFO) << "[REQUESTOR] ... done"; + + LOG(INFO) << "[REQUESTOR] Point BBox: " << util::geo::getWKT(pointBbox); + LOG(INFO) << "[REQUESTOR] Line BBox: " << util::geo::getWKT(lineBbox); + LOG(INFO) << "[REQUESTOR] Building grid..."; + + double GRID_SIZE = 65536; + + double pw = + pointBbox.getUpperRight().getX() - pointBbox.getLowerLeft().getX(); + double ph = + pointBbox.getUpperRight().getY() - pointBbox.getLowerLeft().getY(); + + // estimate memory consumption of empty grid + double pxWidth = fmax(0, ceil(pw / GRID_SIZE)); + double pyHeight = fmax(0, ceil(ph / GRID_SIZE)); + + double lw = lineBbox.getUpperRight().getX() - lineBbox.getLowerLeft().getX(); + double lh = lineBbox.getUpperRight().getY() - lineBbox.getLowerLeft().getY(); + + // estimate memory consumption of empty grid + double lxWidth = fmax(0, ceil(lw / GRID_SIZE)); + double lyHeight = fmax(0, ceil(lh / GRID_SIZE)); + + LOG(INFO) << "[REQUESTOR] (" << pxWidth << "x" << pyHeight + << " cell point grid)"; + LOG(INFO) << "[REQUESTOR] (" << lxWidth << "x" << lyHeight + << " cell line grid)"; + + checkMem(8 * (pxWidth * pyHeight), _maxMemory); + checkMem(8 * (lxWidth * lyHeight), _maxMemory); + checkMem(8 * (lxWidth * lyHeight), _maxMemory); + + util::geo::FBox fLineBbox = { + {lineBbox.getLowerLeft().getX(), lineBbox.getLowerLeft().getY()}, + {lineBbox.getUpperRight().getX(), lineBbox.getUpperRight().getY()}}; + + _pgrid = petrimaps::Grid(GRID_SIZE, GRID_SIZE, pointBbox); + _lgrid = petrimaps::Grid(GRID_SIZE, GRID_SIZE, fLineBbox); + _lpgrid = petrimaps::Grid, float>( + GRID_SIZE, GRID_SIZE, fLineBbox); + + std::exception_ptr ePtr; + +#pragma omp parallel sections + { +#pragma omp section + { + size_t j = _objects.size(); + + for (size_t i = 0; i < _objects.size(); i++) { + const auto& p = _objects[i]; + auto geomId = p.first; + if (geomId >= I_OFFSET) continue; + + size_t clusterI = 0; + // cluster if they have same geometry, don't do for multigeoms + while (i < _objects.size() - 1 && geomId == _objects[i + 1].first && + p.second != _objects[i + 1].second) { + clusterI++; + i++; + } + + if (clusterI > 0) { + for (size_t m = 0; m < clusterI; m++) { + const auto& p = _objects[i - m]; + auto geomId = p.first; + _pgrid.add(_cache->getPoints()[geomId], j); + _clusterObjects.push_back({i - m, {m, clusterI}}); + j++; + } + } else { + _pgrid.add(_cache->getPoints()[geomId], i); + } + + // every 100000 objects, check memory... + if (i % 100000 == 0) { + try { + checkMem(1, _maxMemory); + } catch (...) { +#pragma omp critical + { ePtr = std::current_exception(); } + break; + } + } + } + } + +#pragma omp section + { + size_t i = 0; + for (const auto& l : _objects) { + if (l.first >= I_OFFSET && + l.first < std::numeric_limits::max()) { + auto geomId = l.first - I_OFFSET; + auto box = _cache->getLineBBox(geomId); + util::geo::FBox fbox = { + {box.getLowerLeft().getX(), box.getLowerLeft().getY()}, + {box.getUpperRight().getX(), box.getUpperRight().getY()}}; + _lgrid.add(fbox, i); + } + i++; + + // every 100000 objects, check memory... + if (i % 100000 == 0) { + try { + checkMem(1, _maxMemory); + } catch (...) { +#pragma omp critical + { ePtr = std::current_exception(); } + break; + } + } + } + } + +#pragma omp section + { + size_t i = 0; + for (const auto& l : _objects) { + if (l.first >= I_OFFSET && + l.first < std::numeric_limits::max()) { + auto geomId = l.first - I_OFFSET; + + size_t start = _cache->getLine(geomId); + size_t end = _cache->getLineEnd(geomId); + + double mainX = 0; + double mainY = 0; + + size_t gi = 0; + + uint8_t lastX = 0; + uint8_t lastY = 0; + + for (size_t li = start; li < end; li++) { + const auto& cur = _cache->getLinePoints()[li]; + + if (isMCoord(cur.getX())) { + mainX = rmCoord(cur.getX()); + mainY = rmCoord(cur.getY()); + continue; + } + + // skip bounding box at beginning + if (++gi < 3) continue; + + // extract real geometry + util::geo::FPoint curP( + (mainX * M_COORD_GRANULARITY + cur.getX()) / 10.0, + (mainY * M_COORD_GRANULARITY + cur.getY()) / 10.0); + + size_t cellX = _lpgrid.getCellXFromX(curP.getX()); + size_t cellY = _lpgrid.getCellYFromY(curP.getY()); + + uint8_t sX = + (curP.getX() - _lpgrid.getBBox().getLowerLeft().getX() + + cellX * _lpgrid.getCellWidth()) / + 256; + uint8_t sY = + (curP.getY() - _lpgrid.getBBox().getLowerLeft().getY() + + cellY * _lpgrid.getCellHeight()) / + 256; + + if (gi == 3 || lastX != sX || lastY != sY) { + _lpgrid.add(cellX, cellY, {sX, sY}); + lastX = sX; + lastY = sY; + } + } + } + i++; + + // every 100000 objects, check memory... + if (i % 100000 == 0) { + try { + checkMem(1, _maxMemory); + } catch (...) { +#pragma omp critical + { ePtr = std::current_exception(); } + break; + } + } + } + } + } + + if (ePtr) { + std::rethrow_exception(ePtr); + } + + _ready = true; + + LOG(INFO) << "[REQUESTOR] ...done"; +} + +// _____________________________________________________________________________ +std::vector> Requestor::requestRow( + uint64_t row) const { + if (!_cache->ready()) { + throw std::runtime_error("Geom cache not ready"); + } + RequestReader reader(_cache->getBackendURL(), _maxMemory); + LOG(INFO) << "[REQUESTOR] Requesting single row " << row << " for query " + << _query; + auto query = prepQueryRow(_query, row); + + LOG(INFO) << "[REQUESTOR] Row query is " << query; + + reader.requestRows(query); + + if (reader.rows.size() == 0) return {}; + + return reader.rows[0]; +} + +// _____________________________________________________________________________ +void Requestor::requestRows( + std::function< + void(std::vector>>)> + cb) const { + if (!_cache->ready()) { + throw std::runtime_error("Geom cache not ready"); + } + RequestReader reader(_cache->getBackendURL(), _maxMemory); + LOG(INFO) << "[REQUESTOR] Requesting rows for query " << _query; + + ReaderCbPair cbPair{&reader, cb}; + + reader.requestRows( + _query, + [](void* contents, size_t size, size_t nmemb, void* ptr) { + size_t realsize = size * nmemb; + auto pr = static_cast(ptr); + try { + // clear rows + pr->reader->rows = {}; + pr->reader->parse(static_cast(contents), realsize); + pr->cb(pr->reader->rows); + } catch (...) { + pr->reader->exceptionPtr = std::current_exception(); + return static_cast(CURLE_WRITE_ERROR); + } + + return realsize; + }, + &cbPair); +} + +// _____________________________________________________________________________ +std::string Requestor::prepQuery(std::string query) const { + // only use last column + std::regex expr("select[^{]*(\\?[A-Z0-9_\\-+]*)+[^{]*\\s*\\{", + std::regex_constants::icase); + + // only remove columns the first (=outer) SELECT statement + query = std::regex_replace(query, expr, "SELECT $1 WHERE {$&", + std::regex_constants::format_first_only) + "}"; + + if (util::toLower(query).find("limit") == std::string::npos) { + query += " LIMIT 18446744073709551615"; + } + + return query; +} + +// _____________________________________________________________________________ +std::string Requestor::prepQueryRow(std::string query, uint64_t row) const { + // replace first select + std::regex expr("select[^{]*\\?[A-Z0-9_\\-+]*+[^{]*\\s*\\{", + std::regex_constants::icase); + + query = std::regex_replace(query, expr, "SELECT * {$&", + std::regex_constants::format_first_only) + "}"; + query += "OFFSET " + std::to_string(row) + " LIMIT 1"; + return query; +} + +// _____________________________________________________________________________ +const ResObj Requestor::getNearest(util::geo::DPoint rp, double rad, double res, + util::geo::FBox fullbox) const { + if (!_cache->ready()) { + throw std::runtime_error("Geom cache not ready"); + } + auto box = pad(getBoundingBox(rp), rad); + auto fbox = pad(getBoundingBox(util::geo::FPoint(rp.getX(), rp.getY())), rad); + + auto frp = util::geo::FPoint{rp.getX(), rp.getY()}; + + size_t NUM_THREADS = std::thread::hardware_concurrency(); + + size_t nearest = 0; + double dBest = std::numeric_limits::max(); + std::vector nearestVec(NUM_THREADS, 0); + std::vector dBestVec(NUM_THREADS, std::numeric_limits::max()); + + std::vector nearestLVec(NUM_THREADS, 0); + std::vector dBestLVec(NUM_THREADS, + std::numeric_limits::max()); + size_t nearestL = 0; + double dBestL = std::numeric_limits::max(); +#pragma omp parallel sections + { +#pragma omp section + { + // points + + std::vector ret; + + if (res > 0) + _pgrid.get(fullbox, &ret); + else + _pgrid.get(fbox, &ret); + +#pragma omp parallel for num_threads(NUM_THREADS) schedule(static) + for (size_t idx = 0; idx < ret.size(); idx++) { + auto i = ret[idx]; + util::geo::FPoint p; + if (i >= _objects.size()) { + size_t cid = i - _objects.size(); + p = clusterGeom(cid, res); + } else { + p = _cache->getPoints()[_objects[i].first]; + } + + if (!util::geo::contains(p, fbox)) continue; + + double d = util::geo::dist(p, frp); + + if (d < dBestVec[omp_get_thread_num()]) { + nearestVec[omp_get_thread_num()] = i; + dBestVec[omp_get_thread_num()] = d; + } + } + } + +#pragma omp section + { + // lines + std::vector retL; + _lgrid.get(fbox, &retL); + +#pragma omp parallel for num_threads(NUM_THREADS) schedule(static) + for (size_t idx = 0; idx < retL.size(); idx++) { + const auto& i = retL[idx]; + auto lBox = _cache->getLineBBox(_objects[i].first - I_OFFSET); + if (!util::geo::intersects(lBox, box)) continue; + + size_t start = _cache->getLine(_objects[i].first - I_OFFSET); + size_t end = _cache->getLineEnd(_objects[i].first - I_OFFSET); + + // TODO _____________________ own function + double d = std::numeric_limits::infinity(); + + util::geo::DPoint curPa, curPb; + int s = 0; + + size_t gi = 0; + + double mainX = 0; + double mainY = 0; + + bool isArea = Requestor::isArea(_objects[i].first - I_OFFSET); + + util::geo::DLine areaBorder; + + for (size_t i = start; i < end; i++) { + // extract real geom + const auto& cur = _cache->getLinePoints()[i]; + + if (isMCoord(cur.getX())) { + mainX = rmCoord(cur.getX()); + mainY = rmCoord(cur.getY()); + continue; + } + + // skip bounding box at beginning + gi++; + if (gi < 3) continue; + + // extract real geometry + util::geo::DPoint curP( + (mainX * M_COORD_GRANULARITY + cur.getX()) / 10.0, + (mainY * M_COORD_GRANULARITY + cur.getY()) / 10.0); + + if (isArea) areaBorder.push_back(curP); + + if (s == 0) { + curPa = curP; + s++; + } else if (s == 1) { + curPb = curP; + s++; + } + + if (s == 2) { + s = 1; + double dTmp = util::geo::distToSegment(curPa, curPb, rp); + if (dTmp < 0.0001) { + d = 0; + break; + } + curPa = curPb; + if (dTmp < d) d = dTmp; + } + } + // TODO _____________________ own function + + if (isArea) { + if (util::geo::contains(rp, util::geo::DPolygon(areaBorder))) { + // set it to rad/4 - this allows selecting smaller objects + // inside the polgon + d = rad / 4; + } + } + + if (d < dBestLVec[omp_get_thread_num()]) { + nearestLVec[omp_get_thread_num()] = i; + dBestLVec[omp_get_thread_num()] = d; + } + } + } + } + + // join threads + for (size_t i = 0; i < NUM_THREADS; i++) { + if (dBestVec[i] < dBest) { + dBest = dBestVec[i]; + nearest = nearestVec[i]; + } + + if (dBestLVec[i] < dBestL) { + dBestL = dBestLVec[i]; + nearestL = nearestLVec[i]; + } + } + + if (dBest < rad && dBest <= dBestL) { + size_t row = 0; + if (nearest >= _objects.size()) + row = _objects[_clusterObjects[nearest - _objects.size()].first].second; + else + row = _objects[nearest].second; + + return {true, + nearest >= _objects.size() ? nearest - _objects.size() : nearest, + geomPointGeoms(nearest, res), + requestRow(row), + {}, + {}}; + } + + if (dBestL < rad && dBestL <= dBest) { + size_t lineId = _objects[nearestL].first - I_OFFSET; + + bool isArea = Requestor::isArea(lineId); + + const auto& dline = extractLineGeom(lineId); + + if (isArea && util::geo::contains(rp, util::geo::DPolygon(dline))) { + return {true, nearestL, + {frp}, requestRow(_objects[nearestL].second), + {}, geomPolyGeoms(nearestL, rad / 10)}; + } else { + if (isArea) { + auto p = util::geo::PolyLine(dline).projectOn(rp).p; + auto fp = util::geo::FPoint(p.getX(), p.getY()); + return {true, nearestL, + {fp}, requestRow(_objects[nearestL].second), + {}, geomPolyGeoms(nearestL, rad / 10)}; + } else { + auto p = util::geo::PolyLine(dline).projectOn(rp).p; + auto fp = util::geo::FPoint(p.getX(), p.getY()); + return {true, + nearestL, + {fp}, + requestRow(_objects[nearestL].second), + geomLineGeoms(nearestL, rad / 10), + {}}; + } + } + } + + return {false, 0, {{0, 0}}, {}, {}, {}}; +} + +// _____________________________________________________________________________ +const ResObj Requestor::getGeom(size_t id, double rad) const { + if (!_cache->ready()) { + throw std::runtime_error("Geom cache not ready"); + } + auto obj = _objects[id]; + + if (obj.first >= I_OFFSET) { + size_t lineId = obj.first - I_OFFSET; + + bool isArea = Requestor::isArea(lineId); + + if (isArea) { + return {true, id, {{0, 0}}, {}, {}, geomPolyGeoms(id, rad / 10)}; + } else { + return {true, id, {{0, 0}}, {}, geomLineGeoms(id, rad / 10), {}}; + } + } else { + return {true, id, geomPointGeoms(id), {}, {}, {}}; + } +} + +// _____________________________________________________________________________ +util::geo::DLine Requestor::extractLineGeom(size_t lineId) const { + util::geo::DLine dline; + + size_t start = _cache->getLine(lineId); + size_t end = _cache->getLineEnd(lineId); + + double mainX = 0; + double mainY = 0; + + size_t gi = 0; + + for (size_t i = start; i < end; i++) { + // extract real geom + const auto& cur = _cache->getLinePoints()[i]; + + if (isMCoord(cur.getX())) { + mainX = rmCoord(cur.getX()); + mainY = rmCoord(cur.getY()); + continue; + } + + // skip bounding box at beginning + gi++; + if (gi < 3) continue; + + util::geo::DPoint curP((mainX * M_COORD_GRANULARITY + cur.getX()) / 10.0, + (mainY * M_COORD_GRANULARITY + cur.getY()) / 10.0); + dline.push_back(curP); + } + + return dline; +} + +// _____________________________________________________________________________ +bool Requestor::isArea(size_t lineId) const { + size_t end = _cache->getLineEnd(lineId); + + return isMCoord(_cache->getLinePoints()[end - 1].getX()); +} + +// _____________________________________________________________________________ +util::geo::MultiLine Requestor::geomLineGeoms(size_t oid, + double eps) const { + std::vector polys; + + // catch multigeometries + for (size_t i = oid; + i < _objects.size() && _objects[i].second == _objects[oid].second; i++) { + if (_objects[oid].first < I_OFFSET) continue; + const auto& fline = extractLineGeom(_objects[i].first - I_OFFSET); + polys.push_back(util::geo::simplify(fline, eps)); + } + + for (size_t i = oid - 1; + i < _objects.size() && _objects[i].second == _objects[oid].second; i--) { + if (_objects[oid].first < I_OFFSET) continue; + const auto& fline = extractLineGeom(_objects[i].first - I_OFFSET); + polys.push_back(util::geo::simplify(fline, eps)); + } + + return polys; +} + +// _____________________________________________________________________________ +util::geo::MultiPoint Requestor::geomPointGeoms(size_t oid) const { + return geomPointGeoms(oid, -1); +} + +// _____________________________________________________________________________ +util::geo::MultiPoint Requestor::geomPointGeoms(size_t oid, + double res) const { + std::vector points; + + if (!(res < 0) && oid >= _objects.size()) { + return {clusterGeom(oid - _objects.size(), res)}; + } + + if (oid >= _objects.size()) { + oid = _clusterObjects[oid - _objects.size()].first; + } + + // catch multigeometries + for (size_t i = oid; + i < _objects.size() && _objects[i].second == _objects[oid].second; i++) { + if (_objects[oid].first >= I_OFFSET) continue; + points.push_back(_cache->getPoints()[_objects[i].first]); + } + + for (size_t i = oid - 1; + i < _objects.size() && _objects[i].second == _objects[oid].second; i--) { + if (_objects[oid].first >= I_OFFSET) continue; + points.push_back(_cache->getPoints()[_objects[i].first]); + } + + return points; +} + +// _____________________________________________________________________________ +util::geo::MultiPolygon Requestor::geomPolyGeoms(size_t oid, + double eps) const { + std::vector polys; + + // catch multigeometries + for (size_t i = oid; + i < _objects.size() && _objects[i].second == _objects[oid].second; i++) { + if (_objects[oid].first < I_OFFSET) continue; + const auto& dline = extractLineGeom(_objects[i].first - I_OFFSET); + polys.push_back(util::geo::DPolygon(util::geo::simplify(dline, eps))); + } + + for (size_t i = oid - 1; + i < _objects.size() && _objects[i].second == _objects[oid].second; i--) { + if (_objects[oid].first < I_OFFSET) continue; + const auto& dline = extractLineGeom(_objects[i].first - I_OFFSET); + polys.push_back(util::geo::DPolygon(util::geo::simplify(dline, eps))); + } + + return polys; +} + +// _____________________________________________________________________________ +util::geo::FPoint Requestor::clusterGeom(size_t cid, double res) const { + size_t oid = _clusterObjects[cid].first; + const auto& pp = _cache->getPoints()[_objects[oid].first]; + + if (res < 0) return {pp}; + + size_t num = _clusterObjects[cid].second.first; + size_t tot = _clusterObjects[cid].second.second; + + double a = 25; + double b = 6; + + if (tot > a) { + double rad = 2 * a; + + int row = ((-a - b / 2.0) + sqrt((a + b / 2.0) * (a + b / 2.0) + + 2.0 * b * (std::max(0.0, num - a + 2)))) / + b; + + double g = b * ((row * row + row) / 2.0); + + double relpos = num - (a * row + (g - row * b)); + double tot = a + row * b; + + double x = pp.getX() + (rad + row * 13.0) * res * + sin(relpos * (2.0 * 3.14159265359 / tot)); + double y = pp.getY() + (rad + row * 13.0) * res * + cos(relpos * (2.0 * 3.14159265359 / tot)); + + return util::geo::FPoint{x, y}; + } else { + float rad = 2 * tot; + + float x = pp.getX() + rad * res * sin(num * (2 * 3.14159265359 / tot)); + float y = pp.getY() + rad * res * cos(num * (2 * 3.14159265359 / tot)); + + return util::geo::FPoint{x, y}; + } +} diff --git a/src/qlever-petrimaps/server/Requestor.h b/src/qlever-petrimaps/server/Requestor.h index 805800d..54123e4 100644 --- a/src/qlever-petrimaps/server/Requestor.h +++ b/src/qlever-petrimaps/server/Requestor.h @@ -1,146 +1,146 @@ -// Copyright 2022, University of Freiburg, -// Chair of Algorithms and Data Structures. -// Authors: Patrick Brosi - -#ifndef PETRIMAPS_SERVER_REQUESTOR_H_ -#define PETRIMAPS_SERVER_REQUESTOR_H_ - -#include -#include -#include -#include -#include -#include -#include - -#include "qlever-petrimaps/GeomCache.h" -#include "qlever-petrimaps/Grid.h" -#include "qlever-petrimaps/Misc.h" -#include "util/geo/Geo.h" - -namespace petrimaps { - -struct ResObj { - bool has; - size_t id; - std::vector pos; - std::vector> cols; - - // the geometry - std::vector line; - std::vector poly; -}; - -struct ReaderCbPair { - RequestReader* reader; - std::function>>)> - cb; -}; - -class Requestor { - public: - Requestor() : _maxMemory(-1) {} - Requestor(std::shared_ptr cache, size_t maxMemory) - : _cache(cache), - _maxMemory(maxMemory), - _createdAt(std::chrono::system_clock::now()) {} - - void request(const std::string& query); - - std::vector> requestRow( - uint64_t row) const; - - void requestRows( - std::function< - void(std::vector>>)> - cb) const; - - const petrimaps::Grid& getPointGrid() const { return _pgrid; } - - const petrimaps::Grid& getLineGrid() const { return _lgrid; } - - const petrimaps::Grid, float>& getLinePointGrid() - const { - return _lpgrid; - } - - const std::vector>& getObjects() const { - return _objects; - } - - const std::vector>>& getClusters() const { - return _clusterObjects; - } - - const util::geo::FPoint& getPoint(ID_TYPE id) const { - return _cache->getPoints()[id]; - } - - size_t getLine(ID_TYPE id) const { return _cache->getLine(id); } - - size_t getLineEnd(ID_TYPE id) const { return _cache->getLineEnd(id); } - - const std::vector>& getLinePoints() const { - return _cache->getLinePoints(); - } - - util::geo::DBox getLineBBox(ID_TYPE id) const { - return _cache->getLineBBox(id); - } - - const ResObj getNearest(util::geo::DPoint p, double rad, double res, util::geo::FBox box) const; - - const ResObj getGeom(size_t id, double rad) const; - - util::geo::MultiPolygon geomPolyGeoms(size_t oid, double eps) const; - util::geo::MultiLine geomLineGeoms(size_t oid, double eps) const; - util::geo::MultiPoint geomPointGeoms(size_t oid, double res) const; - util::geo::MultiPoint geomPointGeoms(size_t oid) const; - - util::geo::DLine extractLineGeom(size_t lineId) const; - bool isArea(size_t lineId) const; - - size_t getNumObjects() const { return _numObjects; } - util::geo::FPoint clusterGeom(size_t cid, double res) const; - - std::chrono::time_point createdAt() const { - return _createdAt; - } - - bool ready() const { - _m.lock(); - bool ready = _ready; - _m.unlock(); - return ready; - } - - private: - std::string _backendUrl; - - std::shared_ptr _cache; - - size_t _maxMemory; - - std::string prepQuery(std::string query) const; - std::string prepQueryRow(std::string query, uint64_t row) const; - - std::string _query; - - mutable std::mutex _m; - - std::vector> _objects; - std::vector>> _clusterObjects; - size_t _numObjects = 0; - - petrimaps::Grid _pgrid; - petrimaps::Grid _lgrid; - petrimaps::Grid, float> _lpgrid; - - bool _ready = false; - - std::chrono::time_point _createdAt; -}; -} // namespace petrimaps - -#endif // MAPUI_SERVER_REQUESTOR_H_ +// Copyright 2022, University of Freiburg, +// Chair of Algorithms and Data Structures. +// Authors: Patrick Brosi + +#ifndef PETRIMAPS_SERVER_REQUESTOR_H_ +#define PETRIMAPS_SERVER_REQUESTOR_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "qlever-petrimaps/GeomCache.h" +#include "qlever-petrimaps/Grid.h" +#include "qlever-petrimaps/Misc.h" +#include "util/geo/Geo.h" + +namespace petrimaps { + +struct ResObj { + bool has; + size_t id; + std::vector pos; + std::vector> cols; + + // the geometry + std::vector line; + std::vector poly; +}; + +struct ReaderCbPair { + RequestReader* reader; + std::function>>)> + cb; +}; + +class Requestor { + public: + Requestor() : _maxMemory(-1) {} + Requestor(std::shared_ptr cache, size_t maxMemory) + : _cache(cache), + _maxMemory(maxMemory), + _createdAt(std::chrono::system_clock::now()) {} + + void request(const std::string& query); + + std::vector> requestRow( + uint64_t row) const; + + void requestRows( + std::function< + void(std::vector>>)> + cb) const; + + const petrimaps::Grid& getPointGrid() const { return _pgrid; } + + const petrimaps::Grid& getLineGrid() const { return _lgrid; } + + const petrimaps::Grid, float>& getLinePointGrid() + const { + return _lpgrid; + } + + const std::vector>& getObjects() const { + return _objects; + } + + const std::vector>>& getClusters() const { + return _clusterObjects; + } + + const util::geo::FPoint& getPoint(ID_TYPE id) const { + return _cache->getPoints()[id]; + } + + size_t getLine(ID_TYPE id) const { return _cache->getLine(id); } + + size_t getLineEnd(ID_TYPE id) const { return _cache->getLineEnd(id); } + + const std::vector>& getLinePoints() const { + return _cache->getLinePoints(); + } + + util::geo::DBox getLineBBox(ID_TYPE id) const { + return _cache->getLineBBox(id); + } + + const ResObj getNearest(util::geo::DPoint p, double rad, double res, util::geo::FBox box) const; + + const ResObj getGeom(size_t id, double rad) const; + + util::geo::MultiPolygon geomPolyGeoms(size_t oid, double eps) const; + util::geo::MultiLine geomLineGeoms(size_t oid, double eps) const; + util::geo::MultiPoint geomPointGeoms(size_t oid, double res) const; + util::geo::MultiPoint geomPointGeoms(size_t oid) const; + + util::geo::DLine extractLineGeom(size_t lineId) const; + bool isArea(size_t lineId) const; + + size_t getNumObjects() const { return _numObjects; } + util::geo::FPoint clusterGeom(size_t cid, double res) const; + + std::chrono::time_point createdAt() const { + return _createdAt; + } + + bool ready() const { + _m.lock(); + bool ready = _ready; + _m.unlock(); + return ready; + } + + private: + std::string _backendUrl; + + std::shared_ptr _cache; + + size_t _maxMemory; + + std::string prepQuery(std::string query) const; + std::string prepQueryRow(std::string query, uint64_t row) const; + + std::string _query; + + mutable std::mutex _m; + + std::vector> _objects; + std::vector>> _clusterObjects; + size_t _numObjects = 0; + + petrimaps::Grid _pgrid; + petrimaps::Grid _lgrid; + petrimaps::Grid, float> _lpgrid; + + bool _ready = false; + + std::chrono::time_point _createdAt; +}; +} // namespace petrimaps + +#endif // MAPUI_SERVER_REQUESTOR_H_ diff --git a/src/qlever-petrimaps/server/Server.cpp b/src/qlever-petrimaps/server/Server.cpp index 345efe6..ec6ad92 100755 --- a/src/qlever-petrimaps/server/Server.cpp +++ b/src/qlever-petrimaps/server/Server.cpp @@ -1,1233 +1,1233 @@ -// Copyright 2022, University of Freiburg, -// Chair of Algorithms and Data Structures. -// Authors: Patrick Brosi - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "3rdparty/heatmap.h" -#include "3rdparty/colorschemes/Spectral.h" -#include "qlever-petrimaps/build.h" -#include "qlever-petrimaps/index.h" -#include "qlever-petrimaps/server/Requestor.h" -#include "qlever-petrimaps/server/Server.h" -#include "qlever-petrimaps/style.h" -#include "util/Misc.h" -#include "util/String.h" -#include "util/geo/Geo.h" -#include "util/geo/output/GeoJsonOutput.cpp" -#include "util/http/Server.h" -#include "util/log/Log.h" -#ifdef _OPENMP -#include -#else -#define omp_get_thread_num() 0 -#endif - -using petrimaps::Params; -using petrimaps::Server; -using util::geo::contains; -using util::geo::densify; -using util::geo::DLine; -using util::geo::DPoint; -using util::geo::extendBox; -using util::geo::intersection; -using util::geo::intersects; -using util::geo::LineSegment; -using util::geo::webMercToLatLng; - -const static double THRESHOLD = 200; - -// _____________________________________________________________________________ -Server::Server(size_t maxMemory, const std::string& cacheDir, int cacheLifetime) - : _maxMemory(maxMemory), - _cacheDir(cacheDir), - _cacheLifetime(cacheLifetime) { - std::thread t(&Server::clearOldSessions, this); - t.detach(); -} - -// _____________________________________________________________________________ -util::http::Answer Server::handle(const util::http::Req& req, int con) const { - UNUSED(con); - util::http::Answer a; - try { - Params params; - auto cmd = parseUrl(req.url, req.payload, ¶ms); - - if (cmd == "/") { - a = util::http::Answer( - "200 OK", - std::string(index_html, - index_html + sizeof index_html / sizeof index_html[0])); - a.params["Content-Type"] = "text/html; charset=utf-8"; - } else if (cmd == "/query") { - a = handleQueryReq(params); - } else if (cmd == "/geojson") { - a = handleGeoJSONReq(params); - } else if (cmd == "/clearsession") { - a = handleClearSessReq(params); - } else if (cmd == "/clearsessions") { - a = handleClearSessReq(params); - } else if (cmd == "/load") { - a = handleLoadReq(params); - } else if (cmd == "/pos") { - a = handlePosReq(params); - } else if (cmd == "/export") { - a = handleExportReq(params, con); - } else if (cmd == "/loadstatus") { - a = handleLoadStatusReq(params); - } else if (cmd == "/build.js") { - a = util::http::Answer( - "200 OK", std::string(build_js, build_js + sizeof build_js / - sizeof build_js[0])); - a.params["Content-Type"] = "application/javascript; charset=utf-8"; - a.params["Cache-Control"] = "public, max-age=10000"; - } else if (cmd == "/build.css") { - a = util::http::Answer( - "200 OK", - std::string(build_css, - build_css + sizeof build_css / sizeof build_css[0])); - a.params["Content-Type"] = "text/css; charset=utf-8"; - a.params["Cache-Control"] = "public, max-age=10000"; - } else if (cmd == "/heatmap") { - a = handleHeatMapReq(params, con); - } else { - a = util::http::Answer("404 Not Found", "dunno"); - } - } catch (const std::runtime_error& e) { - a = util::http::Answer("400 Bad Request", e.what()); - } catch (const std::invalid_argument& e) { - a = util::http::Answer("400 Bad Request", e.what()); - } catch (const std::exception& e) { - a = util::http::Answer("500 Internal Server Error", e.what()); - } catch (...) { - a = util::http::Answer("500 Internal Server Error", - "Internal Server Error."); - } - - a.params["Access-Control-Allow-Origin"] = "*"; - a.params["Server"] = "qlever-petrimaps"; - - return a; -} - -// _____________________________________________________________________________ -util::http::Answer Server::handleHeatMapReq(const Params& pars, - int sock) const { - if (pars.count("width") == 0 || pars.find("width")->second.empty()) - throw std::invalid_argument("No width (?width=) specified."); - if (pars.count("height") == 0 || pars.find("height")->second.empty()) - throw std::invalid_argument("No height (?height=) specified."); - - if (pars.count("bbox") == 0 || pars.find("bbox")->second.empty()) - throw std::invalid_argument("No bbox specified."); - auto box = util::split(pars.find("bbox")->second, ','); - - if (pars.count("layers") == 0 || pars.find("layers")->second.empty()) - throw std::invalid_argument("No bbox specified."); - std::string id = pars.find("layers")->second; - - MapStyle style = HEATMAP; - if (pars.count("styles") != 0 && !pars.find("styles")->second.empty()) { - if (pars.find("styles")->second == "objects") style = OBJECTS; - } - - if (box.size() != 4) throw std::invalid_argument("Invalid request."); - - std::shared_ptr r; - - { - std::lock_guard guard(_m); - bool has = _rs.count(id); - if (!has) { - throw std::invalid_argument("Session not found"); - } - r = _rs[id]; - } - - LOG(INFO) << "[SERVER] Begin heat for session " << id; - - double x1 = std::atof(box[0].c_str()); - double y1 = std::atof(box[1].c_str()); - double x2 = std::atof(box[2].c_str()); - double y2 = std::atof(box[3].c_str()); - - double mercW = fabs(x2 - x1); - double mercH = fabs(y2 - y1); - - auto bbox = DBox({x1, y1}, {x2, y2}); - auto fbbox = FBox({x1, y1}, {x2, y2}); - - int w = atoi(pars.find("width")->second.c_str()); - int h = atoi(pars.find("height")->second.c_str()); - - double res = mercH / h; - - heatmap_t* hm = heatmap_new(w, h); - - double realCellSize = r->getPointGrid().getCellWidth(); - double virtCellSize = res * 2.5; - - size_t NUM_THREADS = std::thread::hardware_concurrency(); - - size_t subCellSize = (size_t)ceil(realCellSize / virtCellSize); - - LOG(INFO) << "[SERVER] Query resolution: " << res; - LOG(INFO) << "[SERVER] Virt cell size: " << virtCellSize; - LOG(INFO) << "[SERVER] Num virt cells: " << subCellSize * subCellSize; - - std::vector image(w * h * 4); - - std::vector> points(NUM_THREADS); - std::vector> points2(NUM_THREADS); - - // initialize vectors to 0 - for (size_t i = 0; i < NUM_THREADS; i++) points2[i].resize(w * h, 0); - - if (intersects(r->getPointGrid().getBBox(), fbbox)) { - LOG(INFO) << "[SERVER] Looking up display points..."; - if (res < THRESHOLD) { - std::vector ret; - - // duplicates are not possible with points - r->getPointGrid().get(fbbox, &ret); - - for (size_t j = 0; j < ret.size(); j++) { - size_t i = ret[j]; - - const auto& objs = r->getObjects(); - - if (i >= objs.size() && style == OBJECTS) { - size_t cid = i - objs.size(); - const auto& p = r->getPoint(objs[r->getClusters()[cid].first].first); - - if (!contains(p, fbbox)) continue; - - const auto& cp = r->clusterGeom(cid, res); - - int px = ((cp.getX() - bbox.getLowerLeft().getX()) / mercW) * w; - int py = h - ((cp.getY() - bbox.getLowerLeft().getY()) / mercH) * h; - - int ppx = ((p.getX() - bbox.getLowerLeft().getX()) / mercW) * w; - int ppy = h - ((p.getY() - bbox.getLowerLeft().getY()) / mercH) * h; - - drawPoint(points[0], points2[0], px, py, w, h, style); - drawLine(image.data(), ppx, ppy, px, py, w, h); - } else { - if (i >= objs.size()) i = r->getClusters()[i - objs.size()].first; - const auto& p = r->getPoint(objs[i].first); - if (!contains(p, fbbox)) continue; - - int px = ((p.getX() - bbox.getLowerLeft().getX()) / mercW) * w; - int py = h - ((p.getY() - bbox.getLowerLeft().getY()) / mercH) * h; - - drawPoint(points[0], points2[0], px, py, w, h, style); - } - } - } else { - // they intersect, we checked this above - auto iBox = intersection(r->getPointGrid().getBBox(), fbbox); - const auto& grid = r->getPointGrid(); - -#pragma omp parallel for num_threads(NUM_THREADS) schedule(static) - for (size_t x = grid.getCellXFromX(iBox.getLowerLeft().getX()); - x <= grid.getCellXFromX(iBox.getUpperRight().getX()); x++) { - for (size_t y = grid.getCellYFromY(iBox.getLowerLeft().getY()); - y <= grid.getCellYFromY(iBox.getUpperRight().getY()); y++) { - if (x >= grid.getXWidth() || y >= grid.getYHeight()) { - continue; - } - - auto cell = grid.getCell(x, y); - if (!cell || cell->size() == 0) continue; - const auto& cellBox = grid.getBox(x, y); - - if (subCellSize == 1) { - int px = - ((cellBox.getLowerLeft().getX() - bbox.getLowerLeft().getX()) / - mercW) * - w; - int py = - h - - ((cellBox.getLowerLeft().getY() - bbox.getLowerLeft().getY()) / - mercH) * - h; - - drawPoint(points[omp_get_thread_num()], - points2[omp_get_thread_num()], px, py, w, h, style); - } else { - for (auto i : *cell) { - if (i >= r->getObjects().size()) { - assert(i - r->getObjects().size() < r->getClusters().size()); - i = r->getClusters()[i - r->getObjects().size()].first; - } - assert(i < r->getObjects().size()); - const auto& p = r->getPoint(r->getObjects()[i].first); - - int px = ((p.getX() - bbox.getLowerLeft().getX()) / mercW) * w; - int py = - h - ((p.getY() - bbox.getLowerLeft().getY()) / mercH) * h; - drawPoint(points[omp_get_thread_num()], - points2[omp_get_thread_num()], px, py, w, h, style); - } - } - } - } - } - } - - // LINES - - const auto& lgrid = r->getLineGrid(); - - if (intersects(lgrid.getBBox(), fbbox)) { - LOG(INFO) << "[SERVER] Looking up display lines..."; - if (res < THRESHOLD) { - std::vector ret; - - lgrid.get(fbbox, &ret); - - // sort to avoid duplicates - std::sort(ret.begin(), ret.end()); - - for (size_t idx = 0; idx < ret.size(); idx++) { - if (idx > 0 && ret[idx] == ret[idx - 1]) continue; - auto lid = r->getObjects()[ret[idx]].first; - const auto& lbox = r->getLineBBox(lid - I_OFFSET); - if (!intersects(lbox, bbox)) continue; - - uint8_t gi = 0; - - size_t start = r->getLine(lid - I_OFFSET); - size_t end = r->getLineEnd(lid - I_OFFSET); - - // ___________________________________ - bool isects = false; - - DPoint curPa, curPb; - int s = 0; - - double mainX = 0; - double mainY = 0; - for (size_t i = start; i < end; i++) { - // extract real geom - const auto& cur = r->getLinePoints()[i]; - - if (isMCoord(cur.getX())) { - mainX = rmCoord(cur.getX()); - mainY = rmCoord(cur.getY()); - continue; - } - - // skip bounding box at beginning - gi++; - if (gi < 3) continue; - - // extract real geometry - const DPoint curP((mainX * M_COORD_GRANULARITY + cur.getX()) / 10.0, - (mainY * M_COORD_GRANULARITY + cur.getY()) / 10.0); - if (s == 0) { - curPa = curP; - s++; - } else if (s == 1) { - curPb = curP; - s++; - } - - if (s == 2) { - s = 1; - if (intersects(LineSegment(curPa, curPb), bbox)) { - isects = true; - break; - } - curPa = curPb; - } - } - // ___________________________________ - - if (!isects) continue; - - mainX = 0; - mainY = 0; - - DLine extrLine; - extrLine.reserve(end - start); - - gi = 0; - - for (size_t i = start; i < end; i++) { - // extract real geom - const auto& cur = r->getLinePoints()[i]; - - if (isMCoord(cur.getX())) { - mainX = rmCoord(cur.getX()); - mainY = rmCoord(cur.getY()); - continue; - } - - // skip bounding box at beginning - gi++; - if (gi < 3) continue; - - DPoint p((mainX * M_COORD_GRANULARITY + cur.getX()) / 10.0, - (mainY * M_COORD_GRANULARITY + cur.getY()) / 10.0); - extrLine.push_back(p); - } - - // the factor depends on the render thickness of the line, make - // this configurable! - const auto& denseLine = densify(extrLine, res); - - for (const auto& p : denseLine) { - int px = ((p.getX() - bbox.getLowerLeft().getX()) / mercW) * w; - int py = h - ((p.getY() - bbox.getLowerLeft().getY()) / mercH) * h; - - if (px >= 0 && py >= 0 && px < w && py < h) { - if (points2[0][w * py + px] == 0) points[0].push_back(w * py + px); - points2[0][py * w + px] += 1; - } - } - } - } else { - const auto& lpgrid = r->getLinePointGrid(); - auto iBox = intersection(lpgrid.getBBox(), fbbox); - -#pragma omp parallel for num_threads(NUM_THREADS) schedule(static) - for (size_t x = lpgrid.getCellXFromX(iBox.getLowerLeft().getX()); - x <= lpgrid.getCellXFromX(iBox.getUpperRight().getX()); x++) { - for (size_t y = lpgrid.getCellYFromY(iBox.getLowerLeft().getY()); - y <= lpgrid.getCellYFromY(iBox.getUpperRight().getY()); y++) { - if (x >= lpgrid.getXWidth() || y >= lpgrid.getYHeight()) continue; - - auto cell = lpgrid.getCell(x, y); - if (!cell || cell->size() == 0) continue; - const auto& cellBox = lpgrid.getBox(x, y); - - if (subCellSize == 1) { - int px = - ((cellBox.getLowerLeft().getX() - bbox.getLowerLeft().getX()) / - mercW) * - w; - int py = - h - - ((cellBox.getLowerLeft().getY() - bbox.getLowerLeft().getY()) / - mercH) * - h; - if (px >= 0 && py >= 0 && px < w && py < h) { - if (points2[omp_get_thread_num()][w * py + px] == 0) - points[omp_get_thread_num()].push_back(w * py + px); - points2[omp_get_thread_num()][py * w + px] += cell->size(); - } - } else { - for (const auto& p : *cell) { - int px = ((cellBox.getLowerLeft().getX() + p.getX() * 256 - - bbox.getLowerLeft().getX()) / - mercW) * - w; - int py = h - ((cellBox.getLowerLeft().getY() + p.getY() * 256 - - bbox.getLowerLeft().getY()) / - mercH) * - h; - if (px >= 0 && py >= 0 && px < w && py < h) { - if (points2[omp_get_thread_num()][w * py + px] == 0) - points[omp_get_thread_num()].push_back(w * py + px); - points2[omp_get_thread_num()][py * w + px] += 1; - } - } - } - } - } - } - } - - LOG(INFO) << "[SERVER] Adding points to heatmap..."; - - if (style == OBJECTS) { - auto stamp = heatmap_stamp_gen(3); - for (size_t i = 0; i < NUM_THREADS; i++) { - for (const auto& p : points[i]) { - size_t y = p / w; - size_t x = p - (y * w); - if (points2[i][p] > 0) - heatmap_add_weighted_point_with_stamp(hm, x, y, 1, stamp); - } - } - heatmap_stamp_free(stamp); - } else { - for (size_t i = 0; i < NUM_THREADS; i++) { - for (const auto& p : points[i]) { - size_t y = p / w; - size_t x = p - (y * w); - if (points2[i][p] > 0) - heatmap_add_weighted_point(hm, x, y, points2[i][p]); - } - } - } - - LOG(INFO) << "[SERVER] ...done"; - LOG(INFO) << "[SERVER] Rendering heatmap..."; - - if (style == OBJECTS) { - static const unsigned char discrete_data[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 51, 136, 255, 16, 51, 136, - 255, 32, 51, 136, 255, 64, 51, 136, 255, 128, 51, 136, 255, 160, - 51, 136, 255, 192, 51, 136, 255, 224, 51, 136, 255, 255}; - static const heatmap_colorscheme_t discrete = { - discrete_data, sizeof(discrete_data) / sizeof(discrete_data[0] / 4)}; - - heatmap_render_saturated_to(hm, &discrete, 1, &image[0]); - } else { - heatmap_render_to(hm, heatmap_cs_Spectral_mixed_exp, &image[0]); - } - - heatmap_free(hm); - - LOG(INFO) << "[SERVER] ...done"; - LOG(INFO) << "[SERVER] Generating PNG..."; - - auto aw = util::http::Answer("200 OK", ""); - aw.params["Content-Type"] = "image/png"; - aw.params["Content-Encoding"] = "identity"; - aw.params["Server"] = "qlever-petrimaps"; - aw.raw = true; - - // we do not set the Content-Length header here, but serve until - // we are done. In particular, we do not need to send our data in chunks, as - // specified by https://www.rfc-editor.org/rfc/rfc7230#section-3.3.3 - // point 7 - - std::stringstream ss; - ss << "HTTP/1.1 200 OK" << aw.status << "\r\n"; - for (const auto& kv : aw.params) - ss << kv.first << ": " << kv.second << "\r\n"; - - ss << "\r\n"; - - std::string buff = ss.str(); - - size_t writes = 0; - - while (writes != buff.size()) { - int64_t out = write(sock, buff.c_str() + writes, buff.size() - writes); - if (out < 0) { - if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) continue; - throw std::runtime_error("Failed to write to socket"); - } - writes += out; - } - - writePNG(&image[0], w, h, sock); - - LOG(INFO) << "[SERVER] ...done"; - - return aw; -} - -// _____________________________________________________________________________ -util::http::Answer Server::handleGeoJSONReq(const Params& pars) const { - if (pars.count("id") == 0 || pars.find("id")->second.empty()) - throw std::invalid_argument("No session id (?id=) specified."); - auto id = pars.find("id")->second; - - if (pars.count("rad") == 0 || pars.find("rad")->second.empty()) - throw std::invalid_argument("No rad (?rad=) specified."); - auto rad = std::atof(pars.find("rad")->second.c_str()); - - if (pars.count("gid") == 0 || pars.find("gid")->second.empty()) - throw std::invalid_argument("No geom id (?gid=) specified."); - auto gid = std::atoi(pars.find("gid")->second.c_str()); - - bool noExport = pars.count("export") == 0 || - pars.find("export")->second.empty() || - !std::atoi(pars.find("export")->second.c_str()); - - LOG(INFO) << "[SERVER] GeoJSON request for " << gid; - - std::shared_ptr reqor; - - { - std::lock_guard guard(_m); - bool has = _rs.count(id); - if (!has) { - throw std::invalid_argument("Session not found"); - } - reqor = _rs[id]; - } - - if (!reqor->ready()) { - throw std::invalid_argument("Session not ready."); - } - // as soon as we are ready, the reqor can be read concurrently - - auto res = reqor->getGeom(gid, rad); - - util::json::Val dict; - - if (!noExport) { - for (auto col : reqor->requestRow(reqor->getObjects()[gid].second)) { - dict.dict[col.first] = col.second; - } - } - - std::stringstream json; - - if (res.poly.size()) { - GeoJsonOutput out(json); - out.printLatLng(res.poly, dict); - } else if (res.line.size()) { - GeoJsonOutput out(json); - out.printLatLng(res.line, dict); - } else { - GeoJsonOutput out(json); - out.printLatLng(res.pos, dict); - } - - auto answ = util::http::Answer("200 OK", json.str()); - answ.params["Content-Type"] = "application/json; charset=utf-8"; - - if (!noExport) { - answ.params["Content-Disposition"] = "attachment;filename:\"export.json\""; - } - - return answ; -} - -// _____________________________________________________________________________ -util::http::Answer Server::handlePosReq(const Params& pars) const { - if (pars.count("x") == 0 || pars.find("x")->second.empty()) - throw std::invalid_argument("No x coord (?x=) specified."); - float x = std::atof(pars.find("x")->second.c_str()); - - if (pars.count("y") == 0 || pars.find("y")->second.empty()) - throw std::invalid_argument("No y coord (?y=) specified."); - float y = std::atof(pars.find("y")->second.c_str()); - - if (pars.count("id") == 0 || pars.find("id")->second.empty()) - throw std::invalid_argument("No session id (?id=) specified."); - auto id = pars.find("id")->second; - - if (pars.count("rad") == 0 || pars.find("rad")->second.empty()) - throw std::invalid_argument("No rad (?rad=) specified."); - auto rad = std::atof(pars.find("rad")->second.c_str()); - - if (pars.count("width") == 0 || pars.find("width")->second.empty()) - throw std::invalid_argument("No width (?width=) specified."); - if (pars.count("height") == 0 || pars.find("height")->second.empty()) - throw std::invalid_argument("No height (?height=) specified."); - - if (pars.count("bbox") == 0 || pars.find("bbox")->second.empty()) - throw std::invalid_argument("No bbox specified."); - auto box = util::split(pars.find("bbox")->second, ','); - - MapStyle style = HEATMAP; - if (pars.count("styles") != 0 && !pars.find("styles")->second.empty()) { - if (pars.find("styles")->second == "objects") style = OBJECTS; - } - - if (box.size() != 4) throw std::invalid_argument("Invalid request."); - - double x1 = std::atof(box[0].c_str()); - double y1 = std::atof(box[1].c_str()); - double x2 = std::atof(box[2].c_str()); - double y2 = std::atof(box[3].c_str()); - double mercH = fabs(y2 - y1); - - auto fbbox = FBox({x1, y1}, {x2, y2}); - - int h = atoi(pars.find("height")->second.c_str()); - - double reso = mercH / h; - - // res of -1 means dont render clusters - if (style == HEATMAP || reso >= THRESHOLD) reso = -1; - - LOG(INFO) << "[SERVER] Click at " << x << ", " << y; - - std::shared_ptr reqor; - - { - std::lock_guard guard(_m); - bool has = _rs.count(id); - if (!has) { - throw std::invalid_argument("Session not found"); - } - reqor = _rs[id]; - } - - if (!reqor->ready()) { - throw std::invalid_argument("Session not ready."); - } - // as soon as we are ready, the reqor can be read concurrently - - auto res = reqor->getNearest({x, y}, rad, reso, fbbox); - - std::stringstream json; - - json << "["; - - if (res.has) { - json << "{\"id\" :" << res.id; - json << ",\"attrs\" : ["; - - bool first = true; - - for (const auto& kv : res.cols) { - if (!first) { - json << ","; - } - json << "[\"" << util::jsonStringEscape(kv.first) << "\",\"" - << util::jsonStringEscape(kv.second) << "\"]"; - - first = false; - } - - auto ll = - webMercToLatLng(res.pos.front().getX(), res.pos.front().getY()); - - json << "]"; - json << std::setprecision(10) << ",\"ll\":{\"lat\" : " << ll.getY() - << ",\"lng\":" << ll.getX() << "}"; - - if (res.poly.size()) { - json << ",\"geom\":"; - GeoJsonOutput out(json); - out.printLatLng(res.poly, {}); - } else if (res.line.size()) { - json << ",\"geom\":"; - GeoJsonOutput out(json); - out.printLatLng(res.line, {}); - } else { - json << ",\"geom\":"; - GeoJsonOutput out(json); - out.printLatLng(res.pos, {}); - } - - json << "}"; - } - - json << "]"; - - auto answ = util::http::Answer("200 OK", json.str()); - answ.params["Content-Type"] = "application/json; charset=utf-8"; - - return answ; -} - -// _____________________________________________________________________________ -util::http::Answer Server::handleClearSessReq(const Params& pars) const { - std::string id; - if (pars.count("id") != 0 && !pars.find("id")->second.empty()) - id = pars.find("id")->second; - - { - std::lock_guard guard(_m); - if (id.size()) - clearSession(id); - else - clearSessions(); - } - - auto answ = util::http::Answer("200 OK", "{}"); - answ.params["Content-Type"] = "application/json; charset=utf-8"; - - return answ; -} - -// _____________________________________________________________________________ -util::http::Answer Server::handleLoadReq(const Params& pars) const { - if (pars.count("backend") == 0 || pars.find("backend")->second.empty()) - throw std::invalid_argument("No backend (?backend=) specified."); - auto backend = pars.find("backend")->second; - - LOG(INFO) << "[SERVER] Queried backend is " << backend; - - createCache(backend); - loadCache(backend); - - auto answ = util::http::Answer("200 OK", "{}"); - answ.params["Content-Type"] = "application/json; charset=utf-8"; - return answ; -} - -// _____________________________________________________________________________ -util::http::Answer Server::handleQueryReq(const Params& pars) const { - if (pars.count("query") == 0 || pars.find("query")->second.empty()) - throw std::invalid_argument("No query (?q=) specified."); - if (pars.count("backend") == 0 || pars.find("backend")->second.empty()) - throw std::invalid_argument("No backend (?backend=) specified."); - auto query = pars.find("query")->second; - auto backend = pars.find("backend")->second; - - LOG(INFO) << "[SERVER] Queried backend is " << backend; - LOG(INFO) << "[SERVER] Query is:\n" << query; - - createCache(backend); - loadCache(backend); - - std::string queryId = backend + "$" + query; - - std::shared_ptr reqor; - std::string sessionId; - - { - std::lock_guard guard(_m); - if (_queryCache.count(queryId)) { - sessionId = _queryCache[queryId]; - reqor = _rs[sessionId]; - } else { - reqor = std::shared_ptr( - new Requestor(_caches[backend], _maxMemory)); - - sessionId = getSessionId(); - - _rs[sessionId] = reqor; - _queryCache[queryId] = sessionId; - } - } - - try { - reqor->request(query); - } catch (OutOfMemoryError& ex) { - LOG(ERROR) << ex.what() << backend; - - // delete cache, is now in unready state - { - std::lock_guard guard(_m); - clearSession(sessionId); - } - - auto answ = util::http::Answer("406 Not Acceptable", ex.what()); - answ.params["Content-Type"] = "application/json; charset=utf-8"; - return answ; - } - - auto bbox = reqor->getPointGrid().getBBox(); - bbox = extendBox(reqor->getLineGrid().getBBox(), bbox); - - size_t numObjs = reqor->getNumObjects(); - - auto ll = bbox.getLowerLeft(); - auto ur = bbox.getUpperRight(); - - double llX = ll.getX(); - double llY = ll.getY(); - double urX = ur.getX(); - double urY = ur.getY(); - - std::stringstream json; - json << std::fixed << "{\"qid\" : \"" << sessionId << "\",\"bounds\":[[" - << llX << "," << llY << "],[" << urX << "," << urY << "]]" - << ",\"numobjects\":" << numObjs << "}"; - - auto answ = util::http::Answer("200 OK", json.str()); - answ.params["Content-Type"] = "application/json; charset=utf-8"; - - return answ; -} - -// _____________________________________________________________________________ -std::string Server::parseUrl(std::string u, std::string pl, - std::map* params) { - auto parts = util::split(u, '?'); - - if (parts.size() > 1) { - auto kvs = util::split(parts[1], '&'); - for (const auto& kv : kvs) { - auto kvp = util::split(kv, '='); - if (kvp.size() == 1) kvp.push_back(""); - (*params)[util::urlDecode(kvp[0])] = util::urlDecode(kvp[1]); - } - } - - // also parse post data - auto kvs = util::split(pl, '&'); - for (const auto& kv : kvs) { - auto kvp = util::split(kv, '='); - if (kvp.size() == 1) kvp.push_back(""); - (*params)[util::urlDecode(kvp[0])] = util::urlDecode(kvp[1]); - } - - return util::urlDecode(parts.front()); -} - -// _____________________________________________________________________________ -inline void pngWriteCb(png_structp png_ptr, png_bytep data, png_size_t length) { - int sock = *((int*)png_get_io_ptr(png_ptr)); - - size_t writes = 0; - - while (writes != length) { - int64_t out = - write(sock, reinterpret_cast(data) + writes, length - writes); - if (out < 0) { - if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) continue; - break; - } - writes += out; - } -} - -// _____________________________________________________________________________ -inline void pngWarnCb(png_structp, png_const_charp error_msg) { - LOG(WARN) << "[SERVER] (libpng) " << error_msg; -} - -// _____________________________________________________________________________ -inline void pngErrorCb(png_structp, png_const_charp error_msg) { - throw std::runtime_error(error_msg); -} - -// _____________________________________________________________________________ -void Server::writePNG(const unsigned char* data, size_t w, size_t h, int sock) { - png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, - pngErrorCb, pngWarnCb); - if (!png_ptr) return; - - png_infop info_ptr = png_create_info_struct(png_ptr); - if (!info_ptr) { - png_destroy_write_struct(&png_ptr, (png_infopp) nullptr); - return; - } - - if (setjmp(png_jmpbuf(png_ptr))) { - png_destroy_write_struct(&png_ptr, &info_ptr); - return; - } - - png_set_write_fn(png_ptr, &sock, pngWriteCb, 0); - - png_set_filter(png_ptr, 0, PNG_FILTER_NONE | PNG_FILTER_VALUE_NONE); - png_set_compression_level(png_ptr, 7); - - static const int bit_depth = 8; - static const int color_type = PNG_COLOR_TYPE_RGB_ALPHA; - static const int interlace_type = PNG_INTERLACE_NONE; - png_set_IHDR(png_ptr, info_ptr, w, h, bit_depth, color_type, interlace_type, - PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - - png_bytep* row_pointers = - (png_byte**)png_malloc(png_ptr, h * sizeof(png_bytep)); - - for (size_t y = 0; y < h; ++y) { - row_pointers[y] = const_cast(data + y * w * 4); - } - - png_set_rows(png_ptr, info_ptr, row_pointers); - png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr); - - png_free(png_ptr, row_pointers); - png_destroy_write_struct(&png_ptr, &info_ptr); -} - -// _____________________________________________________________________________ -void Server::clearSession(const std::string& id) const { - if (_rs.count(id)) { - LOG(INFO) << "[SERVER] Clearing session " << id; - _rs.erase(id); - - for (auto it = _queryCache.cbegin(); it != _queryCache.cend();) { - if (it->second == id) { - it = _queryCache.erase(it); - } else { - ++it; - } - } - } -} - -// _____________________________________________________________________________ -void Server::clearSessions() const { - LOG(INFO) << "[SERVER] Clearing all sessions..."; - _rs.clear(); - _queryCache.clear(); -} - -// _____________________________________________________________________________ -void Server::clearOldSessions() const { - while (true) { - std::this_thread::sleep_for(std::chrono::minutes(_cacheLifetime)); - - std::vector toDel; - - for (const auto& i : _rs) { - if (std::chrono::duration_cast( - std::chrono::system_clock::now() - i.second->createdAt()) - .count() >= 1) { - toDel.push_back(i.first); - } - } - - std::lock_guard guard(_m); - for (const auto& id : toDel) { - clearSession(id); - } - } -} - -// _____________________________________________________________________________ -util::http::Answer Server::handleExportReq(const Params& pars, int sock) const { - // ignore SIGPIPE - signal(SIGPIPE, SIG_IGN); - - auto aw = util::http::Answer("200 OK", ""); - - if (pars.count("id") == 0 || pars.find("id")->second.empty()) - throw std::invalid_argument("No session id (?id=) specified."); - auto id = pars.find("id")->second; - - std::shared_ptr reqor; - - { - std::lock_guard guard(_m); - bool has = _rs.count(id); - if (!has) { - throw std::invalid_argument("Session not found"); - } - reqor = _rs[id]; - } - - if (!reqor->ready()) { - throw std::invalid_argument("Session not ready."); - } - // as soon as we are ready, the reqor can be read concurrently - - aw.params["Content-Encoding"] = "identity"; - aw.params["Content-Type"] = "application/json"; - aw.params["Content-Disposition"] = "attachment;filename:\"export.json\""; - aw.params["Server"] = "qlever-petrimaps"; - - // we do not set the Content-Length header here, but serve until - // we are done. In particular, we do not need to send our data in chunks, as - // specified by https://www.rfc-editor.org/rfc/rfc7230#section-3.3.3 - // point 7 - - std::stringstream ss; - ss << "HTTP/1.1 200 OK" << aw.status << "\r\n"; - for (const auto& kv : aw.params) - ss << kv.first << ": " << kv.second << "\r\n"; - - ss << "\r\n"; - ss << "{\"type\":\"FeatureCollection\",\"features\":["; - - std::string buff = ss.str(); - - size_t writes = 0; - - while (writes != buff.size()) { - int64_t out = write(sock, buff.c_str() + writes, buff.size() - writes); - if (out < 0) { - if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) continue; - throw std::runtime_error("Failed to write to socket"); - } - writes += out; - } - - bool first = false; - - reqor->requestRows( - [sock, &first]( - std::vector>> rows) { - std::stringstream ss; - ss << std::setprecision(10); - - util::json::Val dict; - - for (const auto& row : rows) { - // skip last entry, which is the WKT - for (size_t i = 0; i < row.size() - 1; i++) { - dict.dict[row[i].first] = row[i].second; - } - - GeoJsonOutput geoJsonOut(ss, true); - - std::string wkt = row[row.size() - 1].second; - if (wkt.size()) wkt[0] = ' '; // drop " at beginning - - try { - auto geom = util::geo::polygonFromWKT(wkt); - if (first) ss << ","; - geoJsonOut.print(geom, dict); - first = true; - } catch (std::runtime_error& e) { - } - try { - auto geom = util::geo::multiPolygonFromWKT(wkt); - if (first) ss << ","; - geoJsonOut.print(geom, dict); - first = true; - } catch (std::runtime_error& e) { - } - try { - auto geom = util::geo::pointFromWKT(wkt); - if (first) ss << ","; - geoJsonOut.print(geom, dict); - first = true; - } catch (std::runtime_error& e) { - } - try { - auto geom = util::geo::lineFromWKT(wkt); - if (first) ss << ","; - geoJsonOut.print(geom, dict); - first = true; - } catch (std::runtime_error& e) { - } - ss << "\n"; - } - - std::string buff = ss.str(); - - size_t writes = 0; - - while (writes != buff.size()) { - int64_t out = - write(sock, buff.c_str() + writes, buff.size() - writes); - if (out < 0) { - if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) - continue; - throw std::runtime_error("Failed to write to socket"); - } - writes += out; - } - }); - - buff = "]}"; - writes = 0; - - while (writes != buff.size()) { - int64_t out = write(sock, buff.c_str() + writes, buff.size() - writes); - if (out < 0) { - if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) continue; - throw std::runtime_error("Failed to write to socket"); - } - writes += out; - } - - aw.raw = true; - return aw; -} - -// _____________________________________________________________________________ -util::http::Answer Server::handleLoadStatusReq(const Params& pars) const { - if (pars.count("backend") == 0 || pars.find("backend")->second.empty()) - throw std::invalid_argument("No backend (?backend=) specified."); - auto backend = pars.find("backend")->second; - createCache(backend); - std::shared_ptr cache = _caches[backend]; - double loadStatusPercent = cache->getLoadStatusPercent(true); - int loadStatusStage = cache->getLoadStatusStage(); - - std::stringstream json; - json << "{\"percent\": " << loadStatusPercent - << ", \"stage\": " << loadStatusStage << "}"; - util::http::Answer ans = util::http::Answer("200 OK", json.str()); - - return ans; -} - -// _____________________________________________________________________________ -void Server::drawPoint(std::vector& points, - std::vector& points2, int px, int py, int w, - int h, MapStyle style) const { - if (style == OBJECTS) { - // for the raw style, increase the size of the points a bit - for (int x = px - 2; x < px + 2; x++) { - for (int y = py - 2; y < py + 2; y++) { - if (x >= 0 && y >= 0 && x < w && y < h) { - if (points2[w * y + x] == 0) points.push_back(w * y + x); - points2[w * y + x] += 1; - } - } - } - } else { - if (px >= 0 && py >= 0 && px < w && py < h) { - if (points2[w * py + px] == 0) points.push_back(w * py + px); - points2[w * py + px] += 1; - } - } -} - -// _____________________________________________________________________________ -std::string Server::getSessionId() const { - std::random_device dev; - std::mt19937 rng(dev()); - std::uniform_int_distribution d( - 1, std::numeric_limits::max()); - - return std::to_string(d(rng)); -} - -void Server::createCache(const std::string& backend) const { - std::shared_ptr cache; - - { - std::lock_guard guard(_m); - if (_caches.count(backend)) { - cache = _caches[backend]; - } else { - cache = std::shared_ptr(new GeomCache(backend)); - _caches[backend] = cache; - } - } -} - -// _____________________________________________________________________________ -void Server::loadCache(const std::string& backend) const { - // std::shared_ptr reqor; - std::shared_ptr cache = _caches[backend]; - - try { - cache->load(_cacheDir); - } catch (...) { - std::lock_guard guard(_m); - - auto it = _caches.find(backend); - if (it != _caches.end()) _caches.erase(it); - - throw; - } -} - -// _____________________________________________________________________________ -void Server::drawLine(unsigned char* image, int x0, int y0, int x1, int y1, - int w, int h) const { - // Bresenham - int dx = abs(x1 - x0); - int sx = x0 < x1 ? 1 : -1; - int dy = -abs(y1 - y0); - int sy = y0 < y1 ? 1 : -1; - int error = dx + dy; - - while (true) { - if (x0 >= 0 && y0 >= 0 && x0 < w && y0 < h) { - size_t coord = y0 * w * 4 + x0 * 4; - image[coord] = 51; - image[coord + 1] = 136; - image[coord + 2] = 255; - image[coord + 3] = 150; - } - - if (x0 == x1 && y0 == y1) break; - - if (2 * error >= dy) { - if (x0 == x1) break; - error += dy; - x0 += sx; - } - if (2 * error <= dx) { - if (y0 == y1) break; - error += dx; - y0 += sy; - } - } -} +// Copyright 2022, University of Freiburg, +// Chair of Algorithms and Data Structures. +// Authors: Patrick Brosi + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "3rdparty/heatmap.h" +#include "3rdparty/colorschemes/Spectral.h" +#include "qlever-petrimaps/build.h" +#include "qlever-petrimaps/index.h" +#include "qlever-petrimaps/server/Requestor.h" +#include "qlever-petrimaps/server/Server.h" +#include "qlever-petrimaps/style.h" +#include "util/Misc.h" +#include "util/String.h" +#include "util/geo/Geo.h" +#include "util/geo/output/GeoJsonOutput.cpp" +#include "util/http/Server.h" +#include "util/log/Log.h" +#ifdef _OPENMP +#include +#else +#define omp_get_thread_num() 0 +#endif + +using petrimaps::Params; +using petrimaps::Server; +using util::geo::contains; +using util::geo::densify; +using util::geo::DLine; +using util::geo::DPoint; +using util::geo::extendBox; +using util::geo::intersection; +using util::geo::intersects; +using util::geo::LineSegment; +using util::geo::webMercToLatLng; + +const static double THRESHOLD = 200; + +// _____________________________________________________________________________ +Server::Server(size_t maxMemory, const std::string& cacheDir, int cacheLifetime) + : _maxMemory(maxMemory), + _cacheDir(cacheDir), + _cacheLifetime(cacheLifetime) { + std::thread t(&Server::clearOldSessions, this); + t.detach(); +} + +// _____________________________________________________________________________ +util::http::Answer Server::handle(const util::http::Req& req, int con) const { + UNUSED(con); + util::http::Answer a; + try { + Params params; + auto cmd = parseUrl(req.url, req.payload, ¶ms); + + if (cmd == "/") { + a = util::http::Answer( + "200 OK", + std::string(index_html, + index_html + sizeof index_html / sizeof index_html[0])); + a.params["Content-Type"] = "text/html; charset=utf-8"; + } else if (cmd == "/query") { + a = handleQueryReq(params); + } else if (cmd == "/geojson") { + a = handleGeoJSONReq(params); + } else if (cmd == "/clearsession") { + a = handleClearSessReq(params); + } else if (cmd == "/clearsessions") { + a = handleClearSessReq(params); + } else if (cmd == "/load") { + a = handleLoadReq(params); + } else if (cmd == "/pos") { + a = handlePosReq(params); + } else if (cmd == "/export") { + a = handleExportReq(params, con); + } else if (cmd == "/loadstatus") { + a = handleLoadStatusReq(params); + } else if (cmd == "/build.js") { + a = util::http::Answer( + "200 OK", std::string(build_js, build_js + sizeof build_js / + sizeof build_js[0])); + a.params["Content-Type"] = "application/javascript; charset=utf-8"; + a.params["Cache-Control"] = "public, max-age=10000"; + } else if (cmd == "/build.css") { + a = util::http::Answer( + "200 OK", + std::string(build_css, + build_css + sizeof build_css / sizeof build_css[0])); + a.params["Content-Type"] = "text/css; charset=utf-8"; + a.params["Cache-Control"] = "public, max-age=10000"; + } else if (cmd == "/heatmap") { + a = handleHeatMapReq(params, con); + } else { + a = util::http::Answer("404 Not Found", "dunno"); + } + } catch (const std::runtime_error& e) { + a = util::http::Answer("400 Bad Request", e.what()); + } catch (const std::invalid_argument& e) { + a = util::http::Answer("400 Bad Request", e.what()); + } catch (const std::exception& e) { + a = util::http::Answer("500 Internal Server Error", e.what()); + } catch (...) { + a = util::http::Answer("500 Internal Server Error", + "Internal Server Error."); + } + + a.params["Access-Control-Allow-Origin"] = "*"; + a.params["Server"] = "qlever-petrimaps"; + + return a; +} + +// _____________________________________________________________________________ +util::http::Answer Server::handleHeatMapReq(const Params& pars, + int sock) const { + if (pars.count("width") == 0 || pars.find("width")->second.empty()) + throw std::invalid_argument("No width (?width=) specified."); + if (pars.count("height") == 0 || pars.find("height")->second.empty()) + throw std::invalid_argument("No height (?height=) specified."); + + if (pars.count("bbox") == 0 || pars.find("bbox")->second.empty()) + throw std::invalid_argument("No bbox specified."); + auto box = util::split(pars.find("bbox")->second, ','); + + if (pars.count("layers") == 0 || pars.find("layers")->second.empty()) + throw std::invalid_argument("No bbox specified."); + std::string id = pars.find("layers")->second; + + MapStyle style = HEATMAP; + if (pars.count("styles") != 0 && !pars.find("styles")->second.empty()) { + if (pars.find("styles")->second == "objects") style = OBJECTS; + } + + if (box.size() != 4) throw std::invalid_argument("Invalid request."); + + std::shared_ptr r; + + { + std::lock_guard guard(_m); + bool has = _rs.count(id); + if (!has) { + throw std::invalid_argument("Session not found"); + } + r = _rs[id]; + } + + LOG(INFO) << "[SERVER] Begin heat for session " << id; + + double x1 = std::atof(box[0].c_str()); + double y1 = std::atof(box[1].c_str()); + double x2 = std::atof(box[2].c_str()); + double y2 = std::atof(box[3].c_str()); + + double mercW = fabs(x2 - x1); + double mercH = fabs(y2 - y1); + + auto bbox = DBox({x1, y1}, {x2, y2}); + auto fbbox = FBox({x1, y1}, {x2, y2}); + + int w = atoi(pars.find("width")->second.c_str()); + int h = atoi(pars.find("height")->second.c_str()); + + double res = mercH / h; + + heatmap_t* hm = heatmap_new(w, h); + + double realCellSize = r->getPointGrid().getCellWidth(); + double virtCellSize = res * 2.5; + + size_t NUM_THREADS = std::thread::hardware_concurrency(); + + size_t subCellSize = (size_t)ceil(realCellSize / virtCellSize); + + LOG(INFO) << "[SERVER] Query resolution: " << res; + LOG(INFO) << "[SERVER] Virt cell size: " << virtCellSize; + LOG(INFO) << "[SERVER] Num virt cells: " << subCellSize * subCellSize; + + std::vector image(w * h * 4); + + std::vector> points(NUM_THREADS); + std::vector> points2(NUM_THREADS); + + // initialize vectors to 0 + for (size_t i = 0; i < NUM_THREADS; i++) points2[i].resize(w * h, 0); + + if (intersects(r->getPointGrid().getBBox(), fbbox)) { + LOG(INFO) << "[SERVER] Looking up display points..."; + if (res < THRESHOLD) { + std::vector ret; + + // duplicates are not possible with points + r->getPointGrid().get(fbbox, &ret); + + for (size_t j = 0; j < ret.size(); j++) { + size_t i = ret[j]; + + const auto& objs = r->getObjects(); + + if (i >= objs.size() && style == OBJECTS) { + size_t cid = i - objs.size(); + const auto& p = r->getPoint(objs[r->getClusters()[cid].first].first); + + if (!contains(p, fbbox)) continue; + + const auto& cp = r->clusterGeom(cid, res); + + int px = ((cp.getX() - bbox.getLowerLeft().getX()) / mercW) * w; + int py = h - ((cp.getY() - bbox.getLowerLeft().getY()) / mercH) * h; + + int ppx = ((p.getX() - bbox.getLowerLeft().getX()) / mercW) * w; + int ppy = h - ((p.getY() - bbox.getLowerLeft().getY()) / mercH) * h; + + drawPoint(points[0], points2[0], px, py, w, h, style); + drawLine(image.data(), ppx, ppy, px, py, w, h); + } else { + if (i >= objs.size()) i = r->getClusters()[i - objs.size()].first; + const auto& p = r->getPoint(objs[i].first); + if (!contains(p, fbbox)) continue; + + int px = ((p.getX() - bbox.getLowerLeft().getX()) / mercW) * w; + int py = h - ((p.getY() - bbox.getLowerLeft().getY()) / mercH) * h; + + drawPoint(points[0], points2[0], px, py, w, h, style); + } + } + } else { + // they intersect, we checked this above + auto iBox = intersection(r->getPointGrid().getBBox(), fbbox); + const auto& grid = r->getPointGrid(); + +#pragma omp parallel for num_threads(NUM_THREADS) schedule(static) + for (size_t x = grid.getCellXFromX(iBox.getLowerLeft().getX()); + x <= grid.getCellXFromX(iBox.getUpperRight().getX()); x++) { + for (size_t y = grid.getCellYFromY(iBox.getLowerLeft().getY()); + y <= grid.getCellYFromY(iBox.getUpperRight().getY()); y++) { + if (x >= grid.getXWidth() || y >= grid.getYHeight()) { + continue; + } + + auto cell = grid.getCell(x, y); + if (!cell || cell->size() == 0) continue; + const auto& cellBox = grid.getBox(x, y); + + if (subCellSize == 1) { + int px = + ((cellBox.getLowerLeft().getX() - bbox.getLowerLeft().getX()) / + mercW) * + w; + int py = + h - + ((cellBox.getLowerLeft().getY() - bbox.getLowerLeft().getY()) / + mercH) * + h; + + drawPoint(points[omp_get_thread_num()], + points2[omp_get_thread_num()], px, py, w, h, style); + } else { + for (auto i : *cell) { + if (i >= r->getObjects().size()) { + assert(i - r->getObjects().size() < r->getClusters().size()); + i = r->getClusters()[i - r->getObjects().size()].first; + } + assert(i < r->getObjects().size()); + const auto& p = r->getPoint(r->getObjects()[i].first); + + int px = ((p.getX() - bbox.getLowerLeft().getX()) / mercW) * w; + int py = + h - ((p.getY() - bbox.getLowerLeft().getY()) / mercH) * h; + drawPoint(points[omp_get_thread_num()], + points2[omp_get_thread_num()], px, py, w, h, style); + } + } + } + } + } + } + + // LINES + + const auto& lgrid = r->getLineGrid(); + + if (intersects(lgrid.getBBox(), fbbox)) { + LOG(INFO) << "[SERVER] Looking up display lines..."; + if (res < THRESHOLD) { + std::vector ret; + + lgrid.get(fbbox, &ret); + + // sort to avoid duplicates + std::sort(ret.begin(), ret.end()); + + for (size_t idx = 0; idx < ret.size(); idx++) { + if (idx > 0 && ret[idx] == ret[idx - 1]) continue; + auto lid = r->getObjects()[ret[idx]].first; + const auto& lbox = r->getLineBBox(lid - I_OFFSET); + if (!intersects(lbox, bbox)) continue; + + uint8_t gi = 0; + + size_t start = r->getLine(lid - I_OFFSET); + size_t end = r->getLineEnd(lid - I_OFFSET); + + // ___________________________________ + bool isects = false; + + DPoint curPa, curPb; + int s = 0; + + double mainX = 0; + double mainY = 0; + for (size_t i = start; i < end; i++) { + // extract real geom + const auto& cur = r->getLinePoints()[i]; + + if (isMCoord(cur.getX())) { + mainX = rmCoord(cur.getX()); + mainY = rmCoord(cur.getY()); + continue; + } + + // skip bounding box at beginning + gi++; + if (gi < 3) continue; + + // extract real geometry + const DPoint curP((mainX * M_COORD_GRANULARITY + cur.getX()) / 10.0, + (mainY * M_COORD_GRANULARITY + cur.getY()) / 10.0); + if (s == 0) { + curPa = curP; + s++; + } else if (s == 1) { + curPb = curP; + s++; + } + + if (s == 2) { + s = 1; + if (intersects(LineSegment(curPa, curPb), bbox)) { + isects = true; + break; + } + curPa = curPb; + } + } + // ___________________________________ + + if (!isects) continue; + + mainX = 0; + mainY = 0; + + DLine extrLine; + extrLine.reserve(end - start); + + gi = 0; + + for (size_t i = start; i < end; i++) { + // extract real geom + const auto& cur = r->getLinePoints()[i]; + + if (isMCoord(cur.getX())) { + mainX = rmCoord(cur.getX()); + mainY = rmCoord(cur.getY()); + continue; + } + + // skip bounding box at beginning + gi++; + if (gi < 3) continue; + + DPoint p((mainX * M_COORD_GRANULARITY + cur.getX()) / 10.0, + (mainY * M_COORD_GRANULARITY + cur.getY()) / 10.0); + extrLine.push_back(p); + } + + // the factor depends on the render thickness of the line, make + // this configurable! + const auto& denseLine = densify(extrLine, res); + + for (const auto& p : denseLine) { + int px = ((p.getX() - bbox.getLowerLeft().getX()) / mercW) * w; + int py = h - ((p.getY() - bbox.getLowerLeft().getY()) / mercH) * h; + + if (px >= 0 && py >= 0 && px < w && py < h) { + if (points2[0][w * py + px] == 0) points[0].push_back(w * py + px); + points2[0][py * w + px] += 1; + } + } + } + } else { + const auto& lpgrid = r->getLinePointGrid(); + auto iBox = intersection(lpgrid.getBBox(), fbbox); + +#pragma omp parallel for num_threads(NUM_THREADS) schedule(static) + for (size_t x = lpgrid.getCellXFromX(iBox.getLowerLeft().getX()); + x <= lpgrid.getCellXFromX(iBox.getUpperRight().getX()); x++) { + for (size_t y = lpgrid.getCellYFromY(iBox.getLowerLeft().getY()); + y <= lpgrid.getCellYFromY(iBox.getUpperRight().getY()); y++) { + if (x >= lpgrid.getXWidth() || y >= lpgrid.getYHeight()) continue; + + auto cell = lpgrid.getCell(x, y); + if (!cell || cell->size() == 0) continue; + const auto& cellBox = lpgrid.getBox(x, y); + + if (subCellSize == 1) { + int px = + ((cellBox.getLowerLeft().getX() - bbox.getLowerLeft().getX()) / + mercW) * + w; + int py = + h - + ((cellBox.getLowerLeft().getY() - bbox.getLowerLeft().getY()) / + mercH) * + h; + if (px >= 0 && py >= 0 && px < w && py < h) { + if (points2[omp_get_thread_num()][w * py + px] == 0) + points[omp_get_thread_num()].push_back(w * py + px); + points2[omp_get_thread_num()][py * w + px] += cell->size(); + } + } else { + for (const auto& p : *cell) { + int px = ((cellBox.getLowerLeft().getX() + p.getX() * 256 - + bbox.getLowerLeft().getX()) / + mercW) * + w; + int py = h - ((cellBox.getLowerLeft().getY() + p.getY() * 256 - + bbox.getLowerLeft().getY()) / + mercH) * + h; + if (px >= 0 && py >= 0 && px < w && py < h) { + if (points2[omp_get_thread_num()][w * py + px] == 0) + points[omp_get_thread_num()].push_back(w * py + px); + points2[omp_get_thread_num()][py * w + px] += 1; + } + } + } + } + } + } + } + + LOG(INFO) << "[SERVER] Adding points to heatmap..."; + + if (style == OBJECTS) { + auto stamp = heatmap_stamp_gen(3); + for (size_t i = 0; i < NUM_THREADS; i++) { + for (const auto& p : points[i]) { + size_t y = p / w; + size_t x = p - (y * w); + if (points2[i][p] > 0) + heatmap_add_weighted_point_with_stamp(hm, x, y, 1, stamp); + } + } + heatmap_stamp_free(stamp); + } else { + for (size_t i = 0; i < NUM_THREADS; i++) { + for (const auto& p : points[i]) { + size_t y = p / w; + size_t x = p - (y * w); + if (points2[i][p] > 0) + heatmap_add_weighted_point(hm, x, y, points2[i][p]); + } + } + } + + LOG(INFO) << "[SERVER] ...done"; + LOG(INFO) << "[SERVER] Rendering heatmap..."; + + if (style == OBJECTS) { + static const unsigned char discrete_data[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 51, 136, 255, 16, 51, 136, + 255, 32, 51, 136, 255, 64, 51, 136, 255, 128, 51, 136, 255, 160, + 51, 136, 255, 192, 51, 136, 255, 224, 51, 136, 255, 255}; + static const heatmap_colorscheme_t discrete = { + discrete_data, sizeof(discrete_data) / sizeof(discrete_data[0] / 4)}; + + heatmap_render_saturated_to(hm, &discrete, 1, &image[0]); + } else { + heatmap_render_to(hm, heatmap_cs_Spectral_mixed_exp, &image[0]); + } + + heatmap_free(hm); + + LOG(INFO) << "[SERVER] ...done"; + LOG(INFO) << "[SERVER] Generating PNG..."; + + auto aw = util::http::Answer("200 OK", ""); + aw.params["Content-Type"] = "image/png"; + aw.params["Content-Encoding"] = "identity"; + aw.params["Server"] = "qlever-petrimaps"; + aw.raw = true; + + // we do not set the Content-Length header here, but serve until + // we are done. In particular, we do not need to send our data in chunks, as + // specified by https://www.rfc-editor.org/rfc/rfc7230#section-3.3.3 + // point 7 + + std::stringstream ss; + ss << "HTTP/1.1 200 OK" << aw.status << "\r\n"; + for (const auto& kv : aw.params) + ss << kv.first << ": " << kv.second << "\r\n"; + + ss << "\r\n"; + + std::string buff = ss.str(); + + size_t writes = 0; + + while (writes != buff.size()) { + int64_t out = write(sock, buff.c_str() + writes, buff.size() - writes); + if (out < 0) { + if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) continue; + throw std::runtime_error("Failed to write to socket"); + } + writes += out; + } + + writePNG(&image[0], w, h, sock); + + LOG(INFO) << "[SERVER] ...done"; + + return aw; +} + +// _____________________________________________________________________________ +util::http::Answer Server::handleGeoJSONReq(const Params& pars) const { + if (pars.count("id") == 0 || pars.find("id")->second.empty()) + throw std::invalid_argument("No session id (?id=) specified."); + auto id = pars.find("id")->second; + + if (pars.count("rad") == 0 || pars.find("rad")->second.empty()) + throw std::invalid_argument("No rad (?rad=) specified."); + auto rad = std::atof(pars.find("rad")->second.c_str()); + + if (pars.count("gid") == 0 || pars.find("gid")->second.empty()) + throw std::invalid_argument("No geom id (?gid=) specified."); + auto gid = std::atoi(pars.find("gid")->second.c_str()); + + bool noExport = pars.count("export") == 0 || + pars.find("export")->second.empty() || + !std::atoi(pars.find("export")->second.c_str()); + + LOG(INFO) << "[SERVER] GeoJSON request for " << gid; + + std::shared_ptr reqor; + + { + std::lock_guard guard(_m); + bool has = _rs.count(id); + if (!has) { + throw std::invalid_argument("Session not found"); + } + reqor = _rs[id]; + } + + if (!reqor->ready()) { + throw std::invalid_argument("Session not ready."); + } + // as soon as we are ready, the reqor can be read concurrently + + auto res = reqor->getGeom(gid, rad); + + util::json::Val dict; + + if (!noExport) { + for (auto col : reqor->requestRow(reqor->getObjects()[gid].second)) { + dict.dict[col.first] = col.second; + } + } + + std::stringstream json; + + if (res.poly.size()) { + GeoJsonOutput out(json); + out.printLatLng(res.poly, dict); + } else if (res.line.size()) { + GeoJsonOutput out(json); + out.printLatLng(res.line, dict); + } else { + GeoJsonOutput out(json); + out.printLatLng(res.pos, dict); + } + + auto answ = util::http::Answer("200 OK", json.str()); + answ.params["Content-Type"] = "application/json; charset=utf-8"; + + if (!noExport) { + answ.params["Content-Disposition"] = "attachment;filename:\"export.json\""; + } + + return answ; +} + +// _____________________________________________________________________________ +util::http::Answer Server::handlePosReq(const Params& pars) const { + if (pars.count("x") == 0 || pars.find("x")->second.empty()) + throw std::invalid_argument("No x coord (?x=) specified."); + float x = std::atof(pars.find("x")->second.c_str()); + + if (pars.count("y") == 0 || pars.find("y")->second.empty()) + throw std::invalid_argument("No y coord (?y=) specified."); + float y = std::atof(pars.find("y")->second.c_str()); + + if (pars.count("id") == 0 || pars.find("id")->second.empty()) + throw std::invalid_argument("No session id (?id=) specified."); + auto id = pars.find("id")->second; + + if (pars.count("rad") == 0 || pars.find("rad")->second.empty()) + throw std::invalid_argument("No rad (?rad=) specified."); + auto rad = std::atof(pars.find("rad")->second.c_str()); + + if (pars.count("width") == 0 || pars.find("width")->second.empty()) + throw std::invalid_argument("No width (?width=) specified."); + if (pars.count("height") == 0 || pars.find("height")->second.empty()) + throw std::invalid_argument("No height (?height=) specified."); + + if (pars.count("bbox") == 0 || pars.find("bbox")->second.empty()) + throw std::invalid_argument("No bbox specified."); + auto box = util::split(pars.find("bbox")->second, ','); + + MapStyle style = HEATMAP; + if (pars.count("styles") != 0 && !pars.find("styles")->second.empty()) { + if (pars.find("styles")->second == "objects") style = OBJECTS; + } + + if (box.size() != 4) throw std::invalid_argument("Invalid request."); + + double x1 = std::atof(box[0].c_str()); + double y1 = std::atof(box[1].c_str()); + double x2 = std::atof(box[2].c_str()); + double y2 = std::atof(box[3].c_str()); + double mercH = fabs(y2 - y1); + + auto fbbox = FBox({x1, y1}, {x2, y2}); + + int h = atoi(pars.find("height")->second.c_str()); + + double reso = mercH / h; + + // res of -1 means dont render clusters + if (style == HEATMAP || reso >= THRESHOLD) reso = -1; + + LOG(INFO) << "[SERVER] Click at " << x << ", " << y; + + std::shared_ptr reqor; + + { + std::lock_guard guard(_m); + bool has = _rs.count(id); + if (!has) { + throw std::invalid_argument("Session not found"); + } + reqor = _rs[id]; + } + + if (!reqor->ready()) { + throw std::invalid_argument("Session not ready."); + } + // as soon as we are ready, the reqor can be read concurrently + + auto res = reqor->getNearest({x, y}, rad, reso, fbbox); + + std::stringstream json; + + json << "["; + + if (res.has) { + json << "{\"id\" :" << res.id; + json << ",\"attrs\" : ["; + + bool first = true; + + for (const auto& kv : res.cols) { + if (!first) { + json << ","; + } + json << "[\"" << util::jsonStringEscape(kv.first) << "\",\"" + << util::jsonStringEscape(kv.second) << "\"]"; + + first = false; + } + + auto ll = + webMercToLatLng(res.pos.front().getX(), res.pos.front().getY()); + + json << "]"; + json << std::setprecision(10) << ",\"ll\":{\"lat\" : " << ll.getY() + << ",\"lng\":" << ll.getX() << "}"; + + if (res.poly.size()) { + json << ",\"geom\":"; + GeoJsonOutput out(json); + out.printLatLng(res.poly, {}); + } else if (res.line.size()) { + json << ",\"geom\":"; + GeoJsonOutput out(json); + out.printLatLng(res.line, {}); + } else { + json << ",\"geom\":"; + GeoJsonOutput out(json); + out.printLatLng(res.pos, {}); + } + + json << "}"; + } + + json << "]"; + + auto answ = util::http::Answer("200 OK", json.str()); + answ.params["Content-Type"] = "application/json; charset=utf-8"; + + return answ; +} + +// _____________________________________________________________________________ +util::http::Answer Server::handleClearSessReq(const Params& pars) const { + std::string id; + if (pars.count("id") != 0 && !pars.find("id")->second.empty()) + id = pars.find("id")->second; + + { + std::lock_guard guard(_m); + if (id.size()) + clearSession(id); + else + clearSessions(); + } + + auto answ = util::http::Answer("200 OK", "{}"); + answ.params["Content-Type"] = "application/json; charset=utf-8"; + + return answ; +} + +// _____________________________________________________________________________ +util::http::Answer Server::handleLoadReq(const Params& pars) const { + if (pars.count("backend") == 0 || pars.find("backend")->second.empty()) + throw std::invalid_argument("No backend (?backend=) specified."); + auto backend = pars.find("backend")->second; + + LOG(INFO) << "[SERVER] Queried backend is " << backend; + + createCache(backend); + loadCache(backend); + + auto answ = util::http::Answer("200 OK", "{}"); + answ.params["Content-Type"] = "application/json; charset=utf-8"; + return answ; +} + +// _____________________________________________________________________________ +util::http::Answer Server::handleQueryReq(const Params& pars) const { + if (pars.count("query") == 0 || pars.find("query")->second.empty()) + throw std::invalid_argument("No query (?q=) specified."); + if (pars.count("backend") == 0 || pars.find("backend")->second.empty()) + throw std::invalid_argument("No backend (?backend=) specified."); + auto query = pars.find("query")->second; + auto backend = pars.find("backend")->second; + + LOG(INFO) << "[SERVER] Queried backend is " << backend; + LOG(INFO) << "[SERVER] Query is:\n" << query; + + createCache(backend); + loadCache(backend); + + std::string queryId = backend + "$" + query; + + std::shared_ptr reqor; + std::string sessionId; + + { + std::lock_guard guard(_m); + if (_queryCache.count(queryId)) { + sessionId = _queryCache[queryId]; + reqor = _rs[sessionId]; + } else { + reqor = std::shared_ptr( + new Requestor(_caches[backend], _maxMemory)); + + sessionId = getSessionId(); + + _rs[sessionId] = reqor; + _queryCache[queryId] = sessionId; + } + } + + try { + reqor->request(query); + } catch (OutOfMemoryError& ex) { + LOG(ERROR) << ex.what() << backend; + + // delete cache, is now in unready state + { + std::lock_guard guard(_m); + clearSession(sessionId); + } + + auto answ = util::http::Answer("406 Not Acceptable", ex.what()); + answ.params["Content-Type"] = "application/json; charset=utf-8"; + return answ; + } + + auto bbox = reqor->getPointGrid().getBBox(); + bbox = extendBox(reqor->getLineGrid().getBBox(), bbox); + + size_t numObjs = reqor->getNumObjects(); + + auto ll = bbox.getLowerLeft(); + auto ur = bbox.getUpperRight(); + + double llX = ll.getX(); + double llY = ll.getY(); + double urX = ur.getX(); + double urY = ur.getY(); + + std::stringstream json; + json << std::fixed << "{\"qid\" : \"" << sessionId << "\",\"bounds\":[[" + << llX << "," << llY << "],[" << urX << "," << urY << "]]" + << ",\"numobjects\":" << numObjs << "}"; + + auto answ = util::http::Answer("200 OK", json.str()); + answ.params["Content-Type"] = "application/json; charset=utf-8"; + + return answ; +} + +// _____________________________________________________________________________ +std::string Server::parseUrl(std::string u, std::string pl, + std::map* params) { + auto parts = util::split(u, '?'); + + if (parts.size() > 1) { + auto kvs = util::split(parts[1], '&'); + for (const auto& kv : kvs) { + auto kvp = util::split(kv, '='); + if (kvp.size() == 1) kvp.push_back(""); + (*params)[util::urlDecode(kvp[0])] = util::urlDecode(kvp[1]); + } + } + + // also parse post data + auto kvs = util::split(pl, '&'); + for (const auto& kv : kvs) { + auto kvp = util::split(kv, '='); + if (kvp.size() == 1) kvp.push_back(""); + (*params)[util::urlDecode(kvp[0])] = util::urlDecode(kvp[1]); + } + + return util::urlDecode(parts.front()); +} + +// _____________________________________________________________________________ +inline void pngWriteCb(png_structp png_ptr, png_bytep data, png_size_t length) { + int sock = *((int*)png_get_io_ptr(png_ptr)); + + size_t writes = 0; + + while (writes != length) { + int64_t out = + write(sock, reinterpret_cast(data) + writes, length - writes); + if (out < 0) { + if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) continue; + break; + } + writes += out; + } +} + +// _____________________________________________________________________________ +inline void pngWarnCb(png_structp, png_const_charp error_msg) { + LOG(WARN) << "[SERVER] (libpng) " << error_msg; +} + +// _____________________________________________________________________________ +inline void pngErrorCb(png_structp, png_const_charp error_msg) { + throw std::runtime_error(error_msg); +} + +// _____________________________________________________________________________ +void Server::writePNG(const unsigned char* data, size_t w, size_t h, int sock) { + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, + pngErrorCb, pngWarnCb); + if (!png_ptr) return; + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr, (png_infopp) nullptr); + return; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return; + } + + png_set_write_fn(png_ptr, &sock, pngWriteCb, 0); + + png_set_filter(png_ptr, 0, PNG_FILTER_NONE | PNG_FILTER_VALUE_NONE); + png_set_compression_level(png_ptr, 7); + + static const int bit_depth = 8; + static const int color_type = PNG_COLOR_TYPE_RGB_ALPHA; + static const int interlace_type = PNG_INTERLACE_NONE; + png_set_IHDR(png_ptr, info_ptr, w, h, bit_depth, color_type, interlace_type, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + png_bytep* row_pointers = + (png_byte**)png_malloc(png_ptr, h * sizeof(png_bytep)); + + for (size_t y = 0; y < h; ++y) { + row_pointers[y] = const_cast(data + y * w * 4); + } + + png_set_rows(png_ptr, info_ptr, row_pointers); + png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr); + + png_free(png_ptr, row_pointers); + png_destroy_write_struct(&png_ptr, &info_ptr); +} + +// _____________________________________________________________________________ +void Server::clearSession(const std::string& id) const { + if (_rs.count(id)) { + LOG(INFO) << "[SERVER] Clearing session " << id; + _rs.erase(id); + + for (auto it = _queryCache.cbegin(); it != _queryCache.cend();) { + if (it->second == id) { + it = _queryCache.erase(it); + } else { + ++it; + } + } + } +} + +// _____________________________________________________________________________ +void Server::clearSessions() const { + LOG(INFO) << "[SERVER] Clearing all sessions..."; + _rs.clear(); + _queryCache.clear(); +} + +// _____________________________________________________________________________ +void Server::clearOldSessions() const { + while (true) { + std::this_thread::sleep_for(std::chrono::minutes(_cacheLifetime)); + + std::vector toDel; + + for (const auto& i : _rs) { + if (std::chrono::duration_cast( + std::chrono::system_clock::now() - i.second->createdAt()) + .count() >= 1) { + toDel.push_back(i.first); + } + } + + std::lock_guard guard(_m); + for (const auto& id : toDel) { + clearSession(id); + } + } +} + +// _____________________________________________________________________________ +util::http::Answer Server::handleExportReq(const Params& pars, int sock) const { + // ignore SIGPIPE + signal(SIGPIPE, SIG_IGN); + + auto aw = util::http::Answer("200 OK", ""); + + if (pars.count("id") == 0 || pars.find("id")->second.empty()) + throw std::invalid_argument("No session id (?id=) specified."); + auto id = pars.find("id")->second; + + std::shared_ptr reqor; + + { + std::lock_guard guard(_m); + bool has = _rs.count(id); + if (!has) { + throw std::invalid_argument("Session not found"); + } + reqor = _rs[id]; + } + + if (!reqor->ready()) { + throw std::invalid_argument("Session not ready."); + } + // as soon as we are ready, the reqor can be read concurrently + + aw.params["Content-Encoding"] = "identity"; + aw.params["Content-Type"] = "application/json"; + aw.params["Content-Disposition"] = "attachment;filename:\"export.json\""; + aw.params["Server"] = "qlever-petrimaps"; + + // we do not set the Content-Length header here, but serve until + // we are done. In particular, we do not need to send our data in chunks, as + // specified by https://www.rfc-editor.org/rfc/rfc7230#section-3.3.3 + // point 7 + + std::stringstream ss; + ss << "HTTP/1.1 200 OK" << aw.status << "\r\n"; + for (const auto& kv : aw.params) + ss << kv.first << ": " << kv.second << "\r\n"; + + ss << "\r\n"; + ss << "{\"type\":\"FeatureCollection\",\"features\":["; + + std::string buff = ss.str(); + + size_t writes = 0; + + while (writes != buff.size()) { + int64_t out = write(sock, buff.c_str() + writes, buff.size() - writes); + if (out < 0) { + if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) continue; + throw std::runtime_error("Failed to write to socket"); + } + writes += out; + } + + bool first = false; + + reqor->requestRows( + [sock, &first]( + std::vector>> rows) { + std::stringstream ss; + ss << std::setprecision(10); + + util::json::Val dict; + + for (const auto& row : rows) { + // skip last entry, which is the WKT + for (size_t i = 0; i < row.size() - 1; i++) { + dict.dict[row[i].first] = row[i].second; + } + + GeoJsonOutput geoJsonOut(ss, true); + + std::string wkt = row[row.size() - 1].second; + if (wkt.size()) wkt[0] = ' '; // drop " at beginning + + try { + auto geom = util::geo::polygonFromWKT(wkt); + if (first) ss << ","; + geoJsonOut.print(geom, dict); + first = true; + } catch (std::runtime_error& e) { + } + try { + auto geom = util::geo::multiPolygonFromWKT(wkt); + if (first) ss << ","; + geoJsonOut.print(geom, dict); + first = true; + } catch (std::runtime_error& e) { + } + try { + auto geom = util::geo::pointFromWKT(wkt); + if (first) ss << ","; + geoJsonOut.print(geom, dict); + first = true; + } catch (std::runtime_error& e) { + } + try { + auto geom = util::geo::lineFromWKT(wkt); + if (first) ss << ","; + geoJsonOut.print(geom, dict); + first = true; + } catch (std::runtime_error& e) { + } + ss << "\n"; + } + + std::string buff = ss.str(); + + size_t writes = 0; + + while (writes != buff.size()) { + int64_t out = + write(sock, buff.c_str() + writes, buff.size() - writes); + if (out < 0) { + if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) + continue; + throw std::runtime_error("Failed to write to socket"); + } + writes += out; + } + }); + + buff = "]}"; + writes = 0; + + while (writes != buff.size()) { + int64_t out = write(sock, buff.c_str() + writes, buff.size() - writes); + if (out < 0) { + if (errno == EWOULDBLOCK || errno == EAGAIN || errno == EINTR) continue; + throw std::runtime_error("Failed to write to socket"); + } + writes += out; + } + + aw.raw = true; + return aw; +} + +// _____________________________________________________________________________ +util::http::Answer Server::handleLoadStatusReq(const Params& pars) const { + if (pars.count("backend") == 0 || pars.find("backend")->second.empty()) + throw std::invalid_argument("No backend (?backend=) specified."); + auto backend = pars.find("backend")->second; + createCache(backend); + std::shared_ptr cache = _caches[backend]; + double loadStatusPercent = cache->getLoadStatusPercent(true); + int loadStatusStage = cache->getLoadStatusStage(); + + std::stringstream json; + json << "{\"percent\": " << loadStatusPercent + << ", \"stage\": " << loadStatusStage << "}"; + util::http::Answer ans = util::http::Answer("200 OK", json.str()); + + return ans; +} + +// _____________________________________________________________________________ +void Server::drawPoint(std::vector& points, + std::vector& points2, int px, int py, int w, + int h, MapStyle style) const { + if (style == OBJECTS) { + // for the raw style, increase the size of the points a bit + for (int x = px - 2; x < px + 2; x++) { + for (int y = py - 2; y < py + 2; y++) { + if (x >= 0 && y >= 0 && x < w && y < h) { + if (points2[w * y + x] == 0) points.push_back(w * y + x); + points2[w * y + x] += 1; + } + } + } + } else { + if (px >= 0 && py >= 0 && px < w && py < h) { + if (points2[w * py + px] == 0) points.push_back(w * py + px); + points2[w * py + px] += 1; + } + } +} + +// _____________________________________________________________________________ +std::string Server::getSessionId() const { + std::random_device dev; + std::mt19937 rng(dev()); + std::uniform_int_distribution d( + 1, std::numeric_limits::max()); + + return std::to_string(d(rng)); +} + +void Server::createCache(const std::string& backend) const { + std::shared_ptr cache; + + { + std::lock_guard guard(_m); + if (_caches.count(backend)) { + cache = _caches[backend]; + } else { + cache = std::shared_ptr(new GeomCache(backend)); + _caches[backend] = cache; + } + } +} + +// _____________________________________________________________________________ +void Server::loadCache(const std::string& backend) const { + // std::shared_ptr reqor; + std::shared_ptr cache = _caches[backend]; + + try { + cache->load(_cacheDir); + } catch (...) { + std::lock_guard guard(_m); + + auto it = _caches.find(backend); + if (it != _caches.end()) _caches.erase(it); + + throw; + } +} + +// _____________________________________________________________________________ +void Server::drawLine(unsigned char* image, int x0, int y0, int x1, int y1, + int w, int h) const { + // Bresenham + int dx = abs(x1 - x0); + int sx = x0 < x1 ? 1 : -1; + int dy = -abs(y1 - y0); + int sy = y0 < y1 ? 1 : -1; + int error = dx + dy; + + while (true) { + if (x0 >= 0 && y0 >= 0 && x0 < w && y0 < h) { + size_t coord = y0 * w * 4 + x0 * 4; + image[coord] = 51; + image[coord + 1] = 136; + image[coord + 2] = 255; + image[coord + 3] = 150; + } + + if (x0 == x1 && y0 == y1) break; + + if (2 * error >= dy) { + if (x0 == x1) break; + error += dy; + x0 += sx; + } + if (2 * error <= dx) { + if (y0 == y1) break; + error += dx; + y0 += sy; + } + } +} diff --git a/src/qlever-petrimaps/server/Server.h b/src/qlever-petrimaps/server/Server.h index 14e0a5f..ac14293 100755 --- a/src/qlever-petrimaps/server/Server.h +++ b/src/qlever-petrimaps/server/Server.h @@ -1,75 +1,75 @@ -// Copyright 2022, University of Freiburg, -// Chair of Algorithms and Data Structures. -// Authors: Patrick Brosi - -#ifndef PETRIMAPS_SERVER_SERVER_H_ -#define PETRIMAPS_SERVER_SERVER_H_ - -#include -#include -#include -#include -#include - -#include "qlever-petrimaps/GeomCache.h" -#include "qlever-petrimaps/server/Requestor.h" -#include "util/http/Server.h" - -namespace petrimaps { - -typedef std::map Params; - -enum MapStyle { HEATMAP, OBJECTS }; - -class Server : public util::http::Handler { - public: - explicit Server(size_t maxMemory, const std::string& cacheDir, - int cacheLifetime); - - virtual util::http::Answer handle(const util::http::Req& request, - int connection) const; - - private: - static std::string parseUrl(std::string u, std::string pl, Params* params); - - util::http::Answer handleHeatMapReq(const Params& pars, int sock) const; - util::http::Answer handleQueryReq(const Params& pars) const; - util::http::Answer handleGeoJSONReq(const Params& pars) const; - util::http::Answer handleClearSessReq(const Params& pars) const; - util::http::Answer handlePosReq(const Params& pars) const; - util::http::Answer handleLoadReq(const Params& pars) const; - - util::http::Answer handleExportReq(const Params& pars, int sock) const; - util::http::Answer handleLoadStatusReq(const Params& pars) const; - - void createCache(const std::string& backend) const; - void loadCache(const std::string& backend) const; - - void clearSession(const std::string& id) const; - void clearSessions() const; - void clearOldSessions() const; - - std::string getSessionId() const; - - static void writePNG(const unsigned char* data, size_t w, size_t h, int sock); - - void drawPoint(std::vector& points, std::vector& points2, - int px, int py, int w, int h, MapStyle style) const; - void drawLine(unsigned char* image, int x0, int y0, int x1, int y1, int w, int h) const; - - - size_t _maxMemory; - - std::string _cacheDir; - - int _cacheLifetime; - - mutable std::mutex _m; - - mutable std::map> _caches; - mutable std::map> _rs; - mutable std::map _queryCache; -}; -} // namespace petrimaps - -#endif // PETRIMAPS_SERVER_SERVER_H_ +// Copyright 2022, University of Freiburg, +// Chair of Algorithms and Data Structures. +// Authors: Patrick Brosi + +#ifndef PETRIMAPS_SERVER_SERVER_H_ +#define PETRIMAPS_SERVER_SERVER_H_ + +#include +#include +#include +#include +#include + +#include "qlever-petrimaps/GeomCache.h" +#include "qlever-petrimaps/server/Requestor.h" +#include "util/http/Server.h" + +namespace petrimaps { + +typedef std::map Params; + +enum MapStyle { HEATMAP, OBJECTS }; + +class Server : public util::http::Handler { + public: + explicit Server(size_t maxMemory, const std::string& cacheDir, + int cacheLifetime); + + virtual util::http::Answer handle(const util::http::Req& request, + int connection) const; + + private: + static std::string parseUrl(std::string u, std::string pl, Params* params); + + util::http::Answer handleHeatMapReq(const Params& pars, int sock) const; + util::http::Answer handleQueryReq(const Params& pars) const; + util::http::Answer handleGeoJSONReq(const Params& pars) const; + util::http::Answer handleClearSessReq(const Params& pars) const; + util::http::Answer handlePosReq(const Params& pars) const; + util::http::Answer handleLoadReq(const Params& pars) const; + + util::http::Answer handleExportReq(const Params& pars, int sock) const; + util::http::Answer handleLoadStatusReq(const Params& pars) const; + + void createCache(const std::string& backend) const; + void loadCache(const std::string& backend) const; + + void clearSession(const std::string& id) const; + void clearSessions() const; + void clearOldSessions() const; + + std::string getSessionId() const; + + static void writePNG(const unsigned char* data, size_t w, size_t h, int sock); + + void drawPoint(std::vector& points, std::vector& points2, + int px, int py, int w, int h, MapStyle style) const; + void drawLine(unsigned char* image, int x0, int y0, int x1, int y1, int w, int h) const; + + + size_t _maxMemory; + + std::string _cacheDir; + + int _cacheLifetime; + + mutable std::mutex _m; + + mutable std::map> _caches; + mutable std::map> _rs; + mutable std::map _queryCache; +}; +} // namespace petrimaps + +#endif // PETRIMAPS_SERVER_SERVER_H_ diff --git a/web/NonTiledLayer.js b/web/NonTiledLayer.js index 0a74674..7a474bc 100644 --- a/web/NonTiledLayer.js +++ b/web/NonTiledLayer.js @@ -1,5 +1,5 @@ -/*! leaflet.nontiledlayer - v1.0.9 - 2021-03-20 */ - - -!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,(b.L||(b.L={})).NonTiledLayer=a()}}(function(){return function(){function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){return e(b[g][1][a]||a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g=1.3?"crs":"srs";this.wmsParams[b]=this._crs.code,c.NonTiledLayer.prototype.onAdd.call(this,a)},getImageUrl:function(a,b,d){var e=this.wmsParams;e.width=b,e.height=d;var f=this._crs.project(a.getNorthWest()),g=this._crs.project(a.getSouthEast()),h=this._wmsUrl,i=i=(this._wmsVersion>=1.3&&this._crs===c.CRS.EPSG4326?[g.y,f.x,f.y,g.x]:[f.x,g.y,g.x,f.y]).join(",");return h+c.Util.getParamString(this.wmsParams,h,this.options.uppercase)+(this.options.uppercase?"&BBOX=":"&bbox=")+i},setParams:function(a,b){return c.extend(this.wmsParams,a),b||this.redraw(),this}}),c.nonTiledLayer.wms=function(a,b){return new c.NonTiledLayer.WMS(a,b)},b.exports=c.NonTiledLayer.WMS}).call(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],2:[function(a,b,c){(function(a){(function(){"use strict";var c="undefined"!=typeof window?window.L:void 0!==a?a.L:null;c.NonTiledLayer=(c.Layer||c.Class).extend({includes:c.Evented||c.Mixin.Events,emptyImageUrl:"data:image/gif;base64,R0lGODlhAQABAHAAACH5BAUAAAAALAAAAAABAAEAAAICRAEAOw==",options:{attribution:"",opacity:1,zIndex:void 0,minZoom:0,maxZoom:18,pointerEvents:null,errorImageUrl:"data:image/gif;base64,R0lGODlhAQABAHAAACH5BAUAAAAALAAAAAABAAEAAAICRAEAOw==",bounds:c.latLngBounds([-85.05,-180],[85.05,180]),useCanvas:void 0,detectRetina:!1},key:"",initialize:function(a){c.setOptions(this,a)},onAdd:function(a){this._map=a,void 0===this._zoomAnimated&&(this._zoomAnimated=c.DomUtil.TRANSITION&&c.Browser.any3d&&!c.Browser.mobileOpera&&this._map.options.zoomAnimation),c.version<"1.0"&&this._map.on(this.getEvents(),this),this._div||(this._div=c.DomUtil.create("div","leaflet-image-layer"),this.options.pointerEvents&&(this._div.style["pointer-events"]=this.options.pointerEvents),void 0!==this.options.zIndex&&(this._div.style.zIndex=this.options.zIndex),void 0!==this.options.opacity&&(this._div.style.opacity=this.options.opacity)),this.getPane().appendChild(this._div);var b=!!window.HTMLCanvasElement;void 0===this.options.useCanvas?this._useCanvas=b:this._useCanvas=this.options.useCanvas,this._useCanvas?(this._bufferCanvas=this._initCanvas(),this._currentCanvas=this._initCanvas()):(this._bufferImage=this._initImage(),this._currentImage=this._initImage()),this._update()},getPane:function(){return c.Layer?c.Layer.prototype.getPane.call(this):(this.options.pane?this._pane=this.options.pane:this._pane=this._map.getPanes().overlayPane,this._pane)},onRemove:function(a){c.version<"1.0"&&this._map.off(this.getEvents(),this),this.getPane().removeChild(this._div),this._useCanvas?(this._div.removeChild(this._bufferCanvas),this._div.removeChild(this._currentCanvas)):(this._div.removeChild(this._bufferImage),this._div.removeChild(this._currentImage))},addTo:function(a){return a.addLayer(this),this},_setZoom:function(){this._useCanvas?(this._currentCanvas._bounds&&this._resetImageScale(this._currentCanvas,!0),this._bufferCanvas._bounds&&this._resetImageScale(this._bufferCanvas)):(this._currentImage._bounds&&this._resetImageScale(this._currentImage,!0),this._bufferImage._bounds&&this._resetImageScale(this._bufferImage))},getEvents:function(){var a={moveend:this._update};return this._zoomAnimated&&(a.zoomanim=this._animateZoom),c.version>="1.0"&&(a.zoom=this._setZoom),a},getElement:function(){return this._div},setOpacity:function(a){return this.options.opacity=a,this._div&&c.DomUtil.setOpacity(this._div,this.options.opacity),this},setZIndex:function(a){return a&&(this.options.zIndex=a,this._div&&(this._div.style.zIndex=a)),this},bringToFront:function(){return this._div&&this.getPane().appendChild(this._div),this},bringToBack:function(){return this._div&&this.getPane().insertBefore(this._div,this.getPane().firstChild),this},getAttribution:function(){return this.options.attribution},_initCanvas:function(){var a=c.DomUtil.create("canvas","leaflet-image-layer");return this._div.appendChild(a),a._image=new Image,this._ctx=a.getContext("2d"),this.options.crossOrigin&&(a._image.crossOrigin=this.options.crossOrigin),this._map.options.zoomAnimation&&c.Browser.any3d?c.DomUtil.addClass(a,"leaflet-zoom-animated"):c.DomUtil.addClass(a,"leaflet-zoom-hide"),c.extend(a._image,{onload:c.bind(this._onImageLoad,this),onerror:c.bind(this._onImageError,this)}),a},_initImage:function(){var a=c.DomUtil.create("img","leaflet-image-layer");return this.options.crossOrigin&&(a.crossOrigin=this.options.crossOrigin),this._div.appendChild(a),this._map.options.zoomAnimation&&c.Browser.any3d?c.DomUtil.addClass(a,"leaflet-zoom-animated"):c.DomUtil.addClass(a,"leaflet-zoom-hide"),c.extend(a,{galleryimg:"no",onselectstart:c.Util.falseFn,onmousemove:c.Util.falseFn,onload:c.bind(this._onImageLoad,this),onerror:c.bind(this._onImageError,this)}),a},redraw:function(){return this._map&&this._update(),this},_animateZoom:function(a){this._useCanvas?(this._currentCanvas._bounds&&this._animateImage(this._currentCanvas,a),this._bufferCanvas._bounds&&this._animateImage(this._bufferCanvas,a)):(this._currentImage._bounds&&this._animateImage(this._currentImage,a),this._bufferImage._bounds&&this._animateImage(this._bufferImage,a))},_animateImage:function(a,b){if(void 0===c.DomUtil.setTransform){var d=this._map,e=a._scale*d.getZoomScale(b.zoom),f=a._bounds.getNorthWest(),g=a._bounds.getSouthEast(),h=d._latLngToNewLayerPoint(f,b.zoom,b.center),i=d._latLngToNewLayerPoint(g,b.zoom,b.center)._subtract(h),j=h._add(i._multiplyBy(.5*(1-1/e)));a.style[c.DomUtil.TRANSFORM]=c.DomUtil.getTranslateString(j)+" scale("+e+") "}else{var d=this._map,e=a._scale*a._sscale*d.getZoomScale(b.zoom),f=a._bounds.getNorthWest(),g=a._bounds.getSouthEast(),h=d._latLngToNewLayerPoint(f,b.zoom,b.center);c.DomUtil.setTransform(a,h,e)}a._lastScale=e},_resetImageScale:function(a,b){var d=new c.Bounds(this._map.latLngToLayerPoint(a._bounds.getNorthWest()),this._map.latLngToLayerPoint(a._bounds.getSouthEast())),e=a._orgBounds.getSize().y,f=d.getSize().y,g=f/e;a._sscale=g,c.DomUtil.setTransform(a,d.min,g)},_resetImage:function(a){var b=new c.Bounds(this._map.latLngToLayerPoint(a._bounds.getNorthWest()),this._map.latLngToLayerPoint(a._bounds.getSouthEast())),d=b.getSize();c.DomUtil.setPosition(a,b.min),a._orgBounds=b,a._sscale=1,this._useCanvas?(a.width=d.x,a.height=d.y):(a.style.width=d.x+"px",a.style.height=d.y+"px")},_getClippedBounds:function(){var a=this._map.getBounds(),b=a.getSouth(),d=a.getNorth(),e=a.getWest(),f=a.getEast(),g=this.options.bounds.getSouth(),h=this.options.bounds.getNorth(),i=this.options.bounds.getWest(),j=this.options.bounds.getEast();bh&&(d=h),ej&&(f=j);var k=new c.LatLng(d,e),l=new c.LatLng(b,f);return new c.LatLngBounds(k,l)},_getImageScale:function(){return this.options.detectRetina&&c.Browser.retina?2:1},_update:function(){var a,b=this._getClippedBounds(),d=this._map.latLngToContainerPoint(b.getNorthWest()),e=this._map.latLngToContainerPoint(b.getSouthEast()),f=e.x-d.x,g=e.y-d.y;if(this._useCanvas?(this._bufferCanvas._scale=this._bufferCanvas._lastScale,this._currentCanvas._scale=this._currentCanvas._lastScale=1,this._bufferCanvas._sscale=1,this._currentCanvas._bounds=b,this._resetImage(this._currentCanvas),a=this._currentCanvas._image,c.DomUtil.setOpacity(a,0)):(this._bufferImage._scale=this._bufferImage._lastScale,this._currentImage._scale=this._currentImage._lastScale=1,this._bufferImage._sscale=1,this._currentImage._bounds=b,this._resetImage(this._currentImage),a=this._currentImage,c.DomUtil.setOpacity(a,0)),this._map.getZoom()this.options.maxZoom||f<32||g<32)return this._div.style.visibility="hidden",a.src=this.emptyImageUrl,this.key=a.key="",void(a.tag=null);this.fire("loading"),f*=this._getImageScale(),g*=this._getImageScale(),this.key=b.getNorthWest()+", "+b.getSouthEast()+", "+f+", "+g,this.getImageUrl?(a.src=this.getImageUrl(b,f,g),a.key=this.key):this.getImageUrlAsync(b,f,g,this.key,function(b,c,d){a.key=b,a.src=c,a.tag=d})},_onImageError:function(a){this.fire("error",a),c.DomUtil.addClass(a.target,"invalid"),a.target.src!==this.options.errorImageUrl&&(a.target.src=this.options.errorImageUrl)},_onImageLoad:function(a){(a.target.src===this.options.errorImageUrl||(c.DomUtil.removeClass(a.target,"invalid"),a.target.key&&a.target.key===this.key))&&(this._onImageDone(a),this.fire("load",a))},_onImageDone:function(a){if(this._useCanvas)this._renderCanvas(a);else{c.DomUtil.setOpacity(this._currentImage,1),c.DomUtil.setOpacity(this._bufferImage,0),this._addInteraction&&this._currentImage.tag&&this._addInteraction(this._currentImage.tag);var b=this._bufferImage;this._bufferImage=this._currentImage,this._currentImage=b}""!==a.target.key&&(this._div.style.visibility="visible")},_renderCanvas:function(a){this._currentCanvas.getContext("2d").drawImage(this._currentCanvas._image,0,0,this._currentCanvas.width,this._currentCanvas.height),c.DomUtil.setOpacity(this._currentCanvas,1),c.DomUtil.setOpacity(this._bufferCanvas,0),this._addInteraction&&this._currentCanvas._image.tag&&this._addInteraction(this._currentCanvas._image.tag);var b=this._bufferCanvas;this._bufferCanvas=this._currentCanvas,this._currentCanvas=b}}),c.nonTiledLayer=function(){return new c.NonTiledLayer},b.exports=c.NonTiledLayer}).call(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[2,1])(2)}); +/*! leaflet.nontiledlayer - v1.0.9 - 2021-03-20 */ + + +!function(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;b="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,(b.L||(b.L={})).NonTiledLayer=a()}}(function(){return function(){function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){return e(b[g][1][a]||a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g=1.3?"crs":"srs";this.wmsParams[b]=this._crs.code,c.NonTiledLayer.prototype.onAdd.call(this,a)},getImageUrl:function(a,b,d){var e=this.wmsParams;e.width=b,e.height=d;var f=this._crs.project(a.getNorthWest()),g=this._crs.project(a.getSouthEast()),h=this._wmsUrl,i=i=(this._wmsVersion>=1.3&&this._crs===c.CRS.EPSG4326?[g.y,f.x,f.y,g.x]:[f.x,g.y,g.x,f.y]).join(",");return h+c.Util.getParamString(this.wmsParams,h,this.options.uppercase)+(this.options.uppercase?"&BBOX=":"&bbox=")+i},setParams:function(a,b){return c.extend(this.wmsParams,a),b||this.redraw(),this}}),c.nonTiledLayer.wms=function(a,b){return new c.NonTiledLayer.WMS(a,b)},b.exports=c.NonTiledLayer.WMS}).call(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],2:[function(a,b,c){(function(a){(function(){"use strict";var c="undefined"!=typeof window?window.L:void 0!==a?a.L:null;c.NonTiledLayer=(c.Layer||c.Class).extend({includes:c.Evented||c.Mixin.Events,emptyImageUrl:"data:image/gif;base64,R0lGODlhAQABAHAAACH5BAUAAAAALAAAAAABAAEAAAICRAEAOw==",options:{attribution:"",opacity:1,zIndex:void 0,minZoom:0,maxZoom:18,pointerEvents:null,errorImageUrl:"data:image/gif;base64,R0lGODlhAQABAHAAACH5BAUAAAAALAAAAAABAAEAAAICRAEAOw==",bounds:c.latLngBounds([-85.05,-180],[85.05,180]),useCanvas:void 0,detectRetina:!1},key:"",initialize:function(a){c.setOptions(this,a)},onAdd:function(a){this._map=a,void 0===this._zoomAnimated&&(this._zoomAnimated=c.DomUtil.TRANSITION&&c.Browser.any3d&&!c.Browser.mobileOpera&&this._map.options.zoomAnimation),c.version<"1.0"&&this._map.on(this.getEvents(),this),this._div||(this._div=c.DomUtil.create("div","leaflet-image-layer"),this.options.pointerEvents&&(this._div.style["pointer-events"]=this.options.pointerEvents),void 0!==this.options.zIndex&&(this._div.style.zIndex=this.options.zIndex),void 0!==this.options.opacity&&(this._div.style.opacity=this.options.opacity)),this.getPane().appendChild(this._div);var b=!!window.HTMLCanvasElement;void 0===this.options.useCanvas?this._useCanvas=b:this._useCanvas=this.options.useCanvas,this._useCanvas?(this._bufferCanvas=this._initCanvas(),this._currentCanvas=this._initCanvas()):(this._bufferImage=this._initImage(),this._currentImage=this._initImage()),this._update()},getPane:function(){return c.Layer?c.Layer.prototype.getPane.call(this):(this.options.pane?this._pane=this.options.pane:this._pane=this._map.getPanes().overlayPane,this._pane)},onRemove:function(a){c.version<"1.0"&&this._map.off(this.getEvents(),this),this.getPane().removeChild(this._div),this._useCanvas?(this._div.removeChild(this._bufferCanvas),this._div.removeChild(this._currentCanvas)):(this._div.removeChild(this._bufferImage),this._div.removeChild(this._currentImage))},addTo:function(a){return a.addLayer(this),this},_setZoom:function(){this._useCanvas?(this._currentCanvas._bounds&&this._resetImageScale(this._currentCanvas,!0),this._bufferCanvas._bounds&&this._resetImageScale(this._bufferCanvas)):(this._currentImage._bounds&&this._resetImageScale(this._currentImage,!0),this._bufferImage._bounds&&this._resetImageScale(this._bufferImage))},getEvents:function(){var a={moveend:this._update};return this._zoomAnimated&&(a.zoomanim=this._animateZoom),c.version>="1.0"&&(a.zoom=this._setZoom),a},getElement:function(){return this._div},setOpacity:function(a){return this.options.opacity=a,this._div&&c.DomUtil.setOpacity(this._div,this.options.opacity),this},setZIndex:function(a){return a&&(this.options.zIndex=a,this._div&&(this._div.style.zIndex=a)),this},bringToFront:function(){return this._div&&this.getPane().appendChild(this._div),this},bringToBack:function(){return this._div&&this.getPane().insertBefore(this._div,this.getPane().firstChild),this},getAttribution:function(){return this.options.attribution},_initCanvas:function(){var a=c.DomUtil.create("canvas","leaflet-image-layer");return this._div.appendChild(a),a._image=new Image,this._ctx=a.getContext("2d"),this.options.crossOrigin&&(a._image.crossOrigin=this.options.crossOrigin),this._map.options.zoomAnimation&&c.Browser.any3d?c.DomUtil.addClass(a,"leaflet-zoom-animated"):c.DomUtil.addClass(a,"leaflet-zoom-hide"),c.extend(a._image,{onload:c.bind(this._onImageLoad,this),onerror:c.bind(this._onImageError,this)}),a},_initImage:function(){var a=c.DomUtil.create("img","leaflet-image-layer");return this.options.crossOrigin&&(a.crossOrigin=this.options.crossOrigin),this._div.appendChild(a),this._map.options.zoomAnimation&&c.Browser.any3d?c.DomUtil.addClass(a,"leaflet-zoom-animated"):c.DomUtil.addClass(a,"leaflet-zoom-hide"),c.extend(a,{galleryimg:"no",onselectstart:c.Util.falseFn,onmousemove:c.Util.falseFn,onload:c.bind(this._onImageLoad,this),onerror:c.bind(this._onImageError,this)}),a},redraw:function(){return this._map&&this._update(),this},_animateZoom:function(a){this._useCanvas?(this._currentCanvas._bounds&&this._animateImage(this._currentCanvas,a),this._bufferCanvas._bounds&&this._animateImage(this._bufferCanvas,a)):(this._currentImage._bounds&&this._animateImage(this._currentImage,a),this._bufferImage._bounds&&this._animateImage(this._bufferImage,a))},_animateImage:function(a,b){if(void 0===c.DomUtil.setTransform){var d=this._map,e=a._scale*d.getZoomScale(b.zoom),f=a._bounds.getNorthWest(),g=a._bounds.getSouthEast(),h=d._latLngToNewLayerPoint(f,b.zoom,b.center),i=d._latLngToNewLayerPoint(g,b.zoom,b.center)._subtract(h),j=h._add(i._multiplyBy(.5*(1-1/e)));a.style[c.DomUtil.TRANSFORM]=c.DomUtil.getTranslateString(j)+" scale("+e+") "}else{var d=this._map,e=a._scale*a._sscale*d.getZoomScale(b.zoom),f=a._bounds.getNorthWest(),g=a._bounds.getSouthEast(),h=d._latLngToNewLayerPoint(f,b.zoom,b.center);c.DomUtil.setTransform(a,h,e)}a._lastScale=e},_resetImageScale:function(a,b){var d=new c.Bounds(this._map.latLngToLayerPoint(a._bounds.getNorthWest()),this._map.latLngToLayerPoint(a._bounds.getSouthEast())),e=a._orgBounds.getSize().y,f=d.getSize().y,g=f/e;a._sscale=g,c.DomUtil.setTransform(a,d.min,g)},_resetImage:function(a){var b=new c.Bounds(this._map.latLngToLayerPoint(a._bounds.getNorthWest()),this._map.latLngToLayerPoint(a._bounds.getSouthEast())),d=b.getSize();c.DomUtil.setPosition(a,b.min),a._orgBounds=b,a._sscale=1,this._useCanvas?(a.width=d.x,a.height=d.y):(a.style.width=d.x+"px",a.style.height=d.y+"px")},_getClippedBounds:function(){var a=this._map.getBounds(),b=a.getSouth(),d=a.getNorth(),e=a.getWest(),f=a.getEast(),g=this.options.bounds.getSouth(),h=this.options.bounds.getNorth(),i=this.options.bounds.getWest(),j=this.options.bounds.getEast();bh&&(d=h),ej&&(f=j);var k=new c.LatLng(d,e),l=new c.LatLng(b,f);return new c.LatLngBounds(k,l)},_getImageScale:function(){return this.options.detectRetina&&c.Browser.retina?2:1},_update:function(){var a,b=this._getClippedBounds(),d=this._map.latLngToContainerPoint(b.getNorthWest()),e=this._map.latLngToContainerPoint(b.getSouthEast()),f=e.x-d.x,g=e.y-d.y;if(this._useCanvas?(this._bufferCanvas._scale=this._bufferCanvas._lastScale,this._currentCanvas._scale=this._currentCanvas._lastScale=1,this._bufferCanvas._sscale=1,this._currentCanvas._bounds=b,this._resetImage(this._currentCanvas),a=this._currentCanvas._image,c.DomUtil.setOpacity(a,0)):(this._bufferImage._scale=this._bufferImage._lastScale,this._currentImage._scale=this._currentImage._lastScale=1,this._bufferImage._sscale=1,this._currentImage._bounds=b,this._resetImage(this._currentImage),a=this._currentImage,c.DomUtil.setOpacity(a,0)),this._map.getZoom()this.options.maxZoom||f<32||g<32)return this._div.style.visibility="hidden",a.src=this.emptyImageUrl,this.key=a.key="",void(a.tag=null);this.fire("loading"),f*=this._getImageScale(),g*=this._getImageScale(),this.key=b.getNorthWest()+", "+b.getSouthEast()+", "+f+", "+g,this.getImageUrl?(a.src=this.getImageUrl(b,f,g),a.key=this.key):this.getImageUrlAsync(b,f,g,this.key,function(b,c,d){a.key=b,a.src=c,a.tag=d})},_onImageError:function(a){this.fire("error",a),c.DomUtil.addClass(a.target,"invalid"),a.target.src!==this.options.errorImageUrl&&(a.target.src=this.options.errorImageUrl)},_onImageLoad:function(a){(a.target.src===this.options.errorImageUrl||(c.DomUtil.removeClass(a.target,"invalid"),a.target.key&&a.target.key===this.key))&&(this._onImageDone(a),this.fire("load",a))},_onImageDone:function(a){if(this._useCanvas)this._renderCanvas(a);else{c.DomUtil.setOpacity(this._currentImage,1),c.DomUtil.setOpacity(this._bufferImage,0),this._addInteraction&&this._currentImage.tag&&this._addInteraction(this._currentImage.tag);var b=this._bufferImage;this._bufferImage=this._currentImage,this._currentImage=b}""!==a.target.key&&(this._div.style.visibility="visible")},_renderCanvas:function(a){this._currentCanvas.getContext("2d").drawImage(this._currentCanvas._image,0,0,this._currentCanvas.width,this._currentCanvas.height),c.DomUtil.setOpacity(this._currentCanvas,1),c.DomUtil.setOpacity(this._bufferCanvas,0),this._addInteraction&&this._currentCanvas._image.tag&&this._addInteraction(this._currentCanvas._image.tag);var b=this._bufferCanvas;this._bufferCanvas=this._currentCanvas,this._currentCanvas=b}}),c.nonTiledLayer=function(){return new c.NonTiledLayer},b.exports=c.NonTiledLayer}).call(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[2,1])(2)}); //# sourceMappingURL=NonTiledLayer.js.map \ No newline at end of file diff --git a/web/codes.txt b/web/codes.txt index c804c28..4d00d6e 100644 --- a/web/codes.txt +++ b/web/codes.txt @@ -1,7 +1,7 @@ -U+0061 -U+0074 -U+0073 -U+0079 -U+002B -U+002D -U+2212 +U+0061 +U+0074 +U+0073 +U+0079 +U+002B +U+002D +U+2212 diff --git a/web/error.html b/web/error.html index 0ef49f6..919487c 100644 --- a/web/error.html +++ b/web/error.html @@ -1,38 +1,38 @@ - - - - staty | OSM Station Relationships - - - - - - - - - - - - - - - - - - -
-
-
- -
- -
-
-

staty is being updated.

-

Please come back in a few minutes.

-
-
- - + + + + staty | OSM Station Relationships + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ +
+
+

staty is being updated.

+

Please come back in a few minutes.

+
+
+ + diff --git a/web/index.html b/web/index.html index 3419b7f..69fd83f 100755 --- a/web/index.html +++ b/web/index.html @@ -1,78 +1,78 @@ - - - - QLever petrimaps - - - - - - - - - - - - -
-
-
- -
- - - -
- - - - - -
-
-
- -
- -
- - -
- - - -
-

Query

- -

Backend

- -
-
-

GeoJSON-File

- -
- - - - - -
- -
-
Loading results from QLever
-
- -
-
Parsing geometry... (1/2)
-
-
-
0.00%
-
-
-
-
- - - - - + + + + QLever petrimaps + + + + + + + + + + + + +
+
+
+ +
+ + + +
+ + + + + +
+
+
+ +
+ +
+ + +
+ + + +
+

Query

+ +

Backend

+ +
+
+

GeoJSON-File

+ +
+ + + + + +
+ +
+
Loading results from QLever
+
+ +
+
Parsing geometry... (1/2)
+
+
+
0.00%
+
+
+
+
+ + + + + diff --git a/web/leaflet-heat.js b/web/leaflet-heat.js index aa8031a..afe8887 100755 --- a/web/leaflet-heat.js +++ b/web/leaflet-heat.js @@ -1,11 +1,11 @@ -/* - (c) 2014, Vladimir Agafonkin - simpleheat, a tiny JavaScript library for drawing heatmaps with Canvas - https://github.com/mourner/simpleheat -*/ -!function(){"use strict";function t(i){return this instanceof t?(this._canvas=i="string"==typeof i?document.getElementById(i):i,this._ctx=i.getContext("2d"),this._width=i.width,this._height=i.height,this._max=1,void this.clear()):new t(i)}t.prototype={defaultRadius:25,defaultGradient:{.4:"blue",.6:"cyan",.7:"lime",.8:"yellow",1:"red"},data:function(t,i){return this._data=t,this},max:function(t){return this._max=t,this},add:function(t){return this._data.push(t),this},clear:function(){return this._data=[],this},radius:function(t,i){i=i||15;var a=this._circle=document.createElement("canvas"),s=a.getContext("2d"),e=this._r=t+i;return a.width=a.height=2*e,s.shadowOffsetX=s.shadowOffsetY=200,s.shadowBlur=i,s.shadowColor="black",s.beginPath(),s.arc(e-200,e-200,t,0,2*Math.PI,!0),s.closePath(),s.fill(),this},gradient:function(t){var i=document.createElement("canvas"),a=i.getContext("2d"),s=a.createLinearGradient(0,0,0,256);i.width=1,i.height=256;for(var e in t)s.addColorStop(e,t[e]);return a.fillStyle=s,a.fillRect(0,0,1,256),this._grad=a.getImageData(0,0,1,256).data,this},draw:function(t){this._circle||this.radius(this.defaultRadius),this._grad||this.gradient(this.defaultGradient);var i=this._ctx;i.clearRect(0,0,this._width,this._height);for(var a,s=0,e=this._data.length;e>s;s++)a=this._data[s],i.globalAlpha=Math.max(a[2]/this._max,t||.05),i.drawImage(this._circle,a[0]-this._r,a[1]-this._r);var n=i.getImageData(0,0,this._width,this._height);return this._colorize(n.data,this._grad),i.putImageData(n,0,0),this},_colorize:function(t,i){for(var a,s=3,e=t.length;e>s;s+=4)a=4*t[s],a&&(t[s-3]=i[a],t[s-2]=i[a+1],t[s-1]=i[a+2])}},window.simpleheat=t}(),/* - (c) 2014, Vladimir Agafonkin - Leaflet.heat, a tiny and fast heatmap plugin for Leaflet. - https://github.com/Leaflet/Leaflet.heat -*/ +/* + (c) 2014, Vladimir Agafonkin + simpleheat, a tiny JavaScript library for drawing heatmaps with Canvas + https://github.com/mourner/simpleheat +*/ +!function(){"use strict";function t(i){return this instanceof t?(this._canvas=i="string"==typeof i?document.getElementById(i):i,this._ctx=i.getContext("2d"),this._width=i.width,this._height=i.height,this._max=1,void this.clear()):new t(i)}t.prototype={defaultRadius:25,defaultGradient:{.4:"blue",.6:"cyan",.7:"lime",.8:"yellow",1:"red"},data:function(t,i){return this._data=t,this},max:function(t){return this._max=t,this},add:function(t){return this._data.push(t),this},clear:function(){return this._data=[],this},radius:function(t,i){i=i||15;var a=this._circle=document.createElement("canvas"),s=a.getContext("2d"),e=this._r=t+i;return a.width=a.height=2*e,s.shadowOffsetX=s.shadowOffsetY=200,s.shadowBlur=i,s.shadowColor="black",s.beginPath(),s.arc(e-200,e-200,t,0,2*Math.PI,!0),s.closePath(),s.fill(),this},gradient:function(t){var i=document.createElement("canvas"),a=i.getContext("2d"),s=a.createLinearGradient(0,0,0,256);i.width=1,i.height=256;for(var e in t)s.addColorStop(e,t[e]);return a.fillStyle=s,a.fillRect(0,0,1,256),this._grad=a.getImageData(0,0,1,256).data,this},draw:function(t){this._circle||this.radius(this.defaultRadius),this._grad||this.gradient(this.defaultGradient);var i=this._ctx;i.clearRect(0,0,this._width,this._height);for(var a,s=0,e=this._data.length;e>s;s++)a=this._data[s],i.globalAlpha=Math.max(a[2]/this._max,t||.05),i.drawImage(this._circle,a[0]-this._r,a[1]-this._r);var n=i.getImageData(0,0,this._width,this._height);return this._colorize(n.data,this._grad),i.putImageData(n,0,0),this},_colorize:function(t,i){for(var a,s=3,e=t.length;e>s;s+=4)a=4*t[s],a&&(t[s-3]=i[a],t[s-2]=i[a+1],t[s-1]=i[a+2])}},window.simpleheat=t}(),/* + (c) 2014, Vladimir Agafonkin + Leaflet.heat, a tiny and fast heatmap plugin for Leaflet. + https://github.com/Leaflet/Leaflet.heat +*/ L.HeatLayer=(L.Layer?L.Layer:L.Class).extend({initialize:function(t,i){this._latlngs=t,L.setOptions(this,i)},setLatLngs:function(t){return this._latlngs=t,this.redraw()},addLatLng:function(t){return this._latlngs.push(t),this.redraw()},setOptions:function(t){return L.setOptions(this,t),this._heat&&this._updateOptions(),this.redraw()},redraw:function(){return!this._heat||this._frame||this._map._animating||(this._frame=L.Util.requestAnimFrame(this._redraw,this)),this},onAdd:function(t){this._map=t,this._canvas||this._initCanvas(),t._panes.overlayPane.appendChild(this._canvas),t.on("moveend",this._reset,this),t.options.zoomAnimation&&L.Browser.any3d&&t.on("zoomanim",this._animateZoom,this),this._reset()},onRemove:function(t){t.getPanes().overlayPane.removeChild(this._canvas),t.off("moveend",this._reset,this),t.options.zoomAnimation&&t.off("zoomanim",this._animateZoom,this)},addTo:function(t){return t.addLayer(this),this},_initCanvas:function(){var t=this._canvas=L.DomUtil.create("canvas","leaflet-heatmap-layer leaflet-layer"),i=L.DomUtil.testProp(["transformOrigin","WebkitTransformOrigin","msTransformOrigin"]);t.style[i]="50% 50%";var a=this._map.getSize();t.width=a.x,t.height=a.y;var s=this._map.options.zoomAnimation&&L.Browser.any3d;L.DomUtil.addClass(t,"leaflet-zoom-"+(s?"animated":"hide")),this._heat=simpleheat(t),this._updateOptions()},_updateOptions:function(){this._heat.radius(this.options.radius||this._heat.defaultRadius,this.options.blur),this.options.gradient&&this._heat.gradient(this.options.gradient),this.options.max&&this._heat.max(this.options.max)},_reset:function(){var t=this._map.containerPointToLayerPoint([0,0]);L.DomUtil.setPosition(this._canvas,t);var i=this._map.getSize();this._heat._width!==i.x&&(this._canvas.width=this._heat._width=i.x),this._heat._height!==i.y&&(this._canvas.height=this._heat._height=i.y),this._redraw()},_redraw:function(){var t,i,a,s,e,n,h,o,r,d=[],_=this._heat._r,l=this._map.getSize(),m=new L.Bounds(L.point([-_,-_]),l.add([_,_])),c=void 0===this.options.max?1:this.options.max,u=void 0===this.options.maxZoom?this._map.getMaxZoom():this.options.maxZoom,f=1/Math.pow(2,Math.max(0,Math.min(u-this._map.getZoom(),12))),g=_/2,p=[],v=this._map._getMapPanePos(),w=v.x%g,y=v.y%g;for(t=0,i=this._latlngs.length;i>t;t++)if(a=this._map.latLngToContainerPoint(this._latlngs[t]),m.contains(a)){e=Math.floor((a.x-w)/g)+2,n=Math.floor((a.y-y)/g)+2;var x=void 0!==this._latlngs[t].alt?this._latlngs[t].alt:void 0!==this._latlngs[t][2]?+this._latlngs[t][2]:1;r=x*f,p[n]=p[n]||[],s=p[n][e],s?(s[0]=(s[0]*s[2]+a.x*r)/(s[2]+r),s[1]=(s[1]*s[2]+a.y*r)/(s[2]+r),s[2]+=r):p[n][e]=[a.x,a.y,r]}for(t=0,i=p.length;i>t;t++)if(p[t])for(h=0,o=p[t].length;o>h;h++)s=p[t][h],s&&d.push([Math.round(s[0]),Math.round(s[1]),Math.min(s[2],c)]);this._heat.data(d).draw(this.options.minOpacity),this._frame=null},_animateZoom:function(t){var i=this._map.getZoomScale(t.zoom),a=this._map._getCenterOffset(t.center)._multiplyBy(-i).subtract(this._map._getMapPanePos());L.DomUtil.setTransform?L.DomUtil.setTransform(this._canvas,a,i):this._canvas.style[L.DomUtil.TRANSFORM]=L.DomUtil.getTranslateString(a)+" scale("+i+")"}}),L.heatLayer=function(t,i){return new L.HeatLayer(t,i)}; \ No newline at end of file diff --git a/web/leaflet.js b/web/leaflet.js index a98a411..606a227 100644 --- a/web/leaflet.js +++ b/web/leaflet.js @@ -1,6 +1,6 @@ -/* @preserve - * Leaflet 1.8.0, a JS library for interactive maps. https://leafletjs.com - * (c) 2010-2022 Vladimir Agafonkin, (c) 2010-2011 CloudMade - */ -!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?i(exports):"function"==typeof define&&define.amd?define(["exports"],i):i((t="undefined"!=typeof globalThis?globalThis:t||self).leaflet={})}(this,function(t){"use strict";function l(t){for(var i,e,n=1,o=arguments.length;n=this.min.x&&e.x<=this.max.x&&i.y>=this.min.y&&e.y<=this.max.y},intersects:function(t){t=f(t);var i=this.min,e=this.max,n=t.min,t=t.max,o=t.x>=i.x&&n.x<=e.x,t=t.y>=i.y&&n.y<=e.y;return o&&t},overlaps:function(t){t=f(t);var i=this.min,e=this.max,n=t.min,t=t.max,o=t.x>i.x&&n.xi.y&&n.y=n.lat&&e.lat<=o.lat&&i.lng>=n.lng&&e.lng<=o.lng},intersects:function(t){t=g(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),t=t.getNorthEast(),o=t.lat>=i.lat&&n.lat<=e.lat,t=t.lng>=i.lng&&n.lng<=e.lng;return o&&t},overlaps:function(t){t=g(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),t=t.getNorthEast(),o=t.lat>i.lat&&n.lati.lng&&n.lng","http://www.w3.org/2000/svg"===(Wt.firstChild&&Wt.firstChild.namespaceURI));function y(t){return 0<=navigator.userAgent.toLowerCase().indexOf(t)}var P={ie:pt,ielt9:mt,edge:n,webkit:ft,android:gt,android23:vt,androidStock:yt,opera:xt,chrome:wt,gecko:Pt,safari:bt,phantom:Lt,opera12:o,win:Tt,ie3d:zt,webkit3d:Mt,gecko3d:_t,any3d:Ct,mobile:Zt,mobileWebkit:St,mobileWebkit3d:kt,msPointer:Et,pointer:Bt,touch:It,touchNative:At,mobileOpera:Ot,mobileGecko:Rt,retina:Nt,passiveEvents:Dt,canvas:jt,svg:Ht,vml:!Ht&&function(){try{var t=document.createElement("div"),i=(t.innerHTML='',t.firstChild);return i.style.behavior="url(#default#VML)",i&&"object"==typeof i.adj}catch(t){return!1}}(),inlineSvg:Wt},Ft=P.msPointer?"MSPointerDown":"pointerdown",Ut=P.msPointer?"MSPointerMove":"pointermove",Vt=P.msPointer?"MSPointerUp":"pointerup",qt=P.msPointer?"MSPointerCancel":"pointercancel",Gt={touchstart:Ft,touchmove:Ut,touchend:Vt,touchcancel:qt},Kt={touchstart:function(t,i){i.MSPOINTER_TYPE_TOUCH&&i.pointerType===i.MSPOINTER_TYPE_TOUCH&&B(i);ii(t,i)},touchmove:ii,touchend:ii,touchcancel:ii},Yt={},Xt=!1;function Jt(t,i,e){return"touchstart"!==i||Xt||(document.addEventListener(Ft,$t,!0),document.addEventListener(Ut,Qt,!0),document.addEventListener(Vt,ti,!0),document.addEventListener(qt,ti,!0),Xt=!0),Kt[i]?(e=Kt[i].bind(this,e),t.addEventListener(Gt[i],e,!1),e):(console.warn("wrong event specified:",i),L.Util.falseFn)}function $t(t){Yt[t.pointerId]=t}function Qt(t){Yt[t.pointerId]&&(Yt[t.pointerId]=t)}function ti(t){delete Yt[t.pointerId]}function ii(t,i){if(i.pointerType!==(i.MSPOINTER_TYPE_MOUSE||"mouse")){for(var e in i.touches=[],Yt)i.touches.push(Yt[e]);i.changedTouches=[i],t(i)}}var ei=200;function ni(t,e){t.addEventListener("dblclick",e);var n,o=0;function i(t){var i;1!==t.detail?n=t.detail:"mouse"===t.pointerType||t.sourceCapabilities&&!t.sourceCapabilities.firesTouchEvents||((i=Date.now())-o<=ei?2===++n&&e(function(t){var i,e,n={};for(e in t)i=t[e],n[e]=i&&i.bind?i.bind(t):i;return(t=n).type="dblclick",n.detail=2,n.isTrusted=!1,n._simulated=!0,n}(t)):n=1,o=i)}return t.addEventListener("click",i),{dblclick:e,simDblclick:i}}var oi,si,ri,ai,hi,li,ui=wi(["transform","webkitTransform","OTransform","MozTransform","msTransform"]),ci=wi(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),di="webkitTransition"===ci||"OTransition"===ci?ci+"End":"transitionend";function _i(t){return"string"==typeof t?document.getElementById(t):t}function pi(t,i){var e=t.style[i]||t.currentStyle&&t.currentStyle[i];return"auto"===(e=e&&"auto"!==e||!document.defaultView?e:(t=document.defaultView.getComputedStyle(t,null))?t[i]:null)?null:e}function b(t,i,e){t=document.createElement(t);return t.className=i||"",e&&e.appendChild(t),t}function T(t){var i=t.parentNode;i&&i.removeChild(t)}function mi(t){for(;t.firstChild;)t.removeChild(t.firstChild)}function fi(t){var i=t.parentNode;i&&i.lastChild!==t&&i.appendChild(t)}function gi(t){var i=t.parentNode;i&&i.firstChild!==t&&i.insertBefore(t,i.firstChild)}function vi(t,i){if(void 0!==t.classList)return t.classList.contains(i);t=xi(t);return 0this.options.maxZoom)?this.setZoom(t):this},panInsideBounds:function(t,i){this._enforcingBounds=!0;var e=this.getCenter(),t=this._limitCenter(e,this._zoom,g(t));return e.equals(t)||this.panTo(t,i),this._enforcingBounds=!1,this},panInside:function(t,i){var e=_((i=i||{}).paddingTopLeft||i.padding||[0,0]),n=_(i.paddingBottomRight||i.padding||[0,0]),o=this.project(this.getCenter()),t=this.project(t),s=this.getPixelBounds(),e=f([s.min.add(e),s.max.subtract(n)]),s=e.getSize();return e.contains(t)||(this._enforcingBounds=!0,n=t.subtract(e.getCenter()),e=e.extend(t).getSize().subtract(s),o.x+=n.x<0?-e.x:e.x,o.y+=n.y<0?-e.y:e.y,this.panTo(this.unproject(o),i),this._enforcingBounds=!1),this},invalidateSize:function(t){if(!this._loaded)return this;t=l({animate:!1,pan:!0},!0===t?{animate:!0}:t);var i=this.getSize(),e=(this._sizeChanged=!0,this._lastCenter=null,this.getSize()),n=i.divideBy(2).round(),o=e.divideBy(2).round(),n=n.subtract(o);return n.x||n.y?(t.animate&&t.pan?this.panBy(n):(t.pan&&this._rawPanBy(n),this.fire("move"),t.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(a(this.fire,this,"moveend"),200)):this.fire("moveend")),this.fire("resize",{oldSize:i,newSize:e})):this},stop:function(){return this.setZoom(this._limitZoom(this._zoom)),this.options.zoomSnap||this.fire("viewreset"),this._stop()},locate:function(t){if(t=this._locateOptions=l({timeout:1e4,watch:!1},t),!("geolocation"in navigator))return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var i=a(this._handleGeolocationResponse,this),e=a(this._handleGeolocationError,this);return t.watch?this._locationWatchId=navigator.geolocation.watchPosition(i,e,t):navigator.geolocation.getCurrentPosition(i,e,t),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var i;this._container._leaflet_id&&(i=t.code,t=t.message||(1===i?"permission denied":2===i?"position unavailable":"timeout"),this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:i,message:"Geolocation error: "+t+"."}))},_handleGeolocationResponse:function(t){if(this._container._leaflet_id){var i,e,n=new v(t.coords.latitude,t.coords.longitude),o=n.toBounds(2*t.coords.accuracy),s=this._locateOptions,r=(s.setView&&(i=this.getBoundsZoom(o),this.setView(n,s.maxZoom?Math.min(i,s.maxZoom):i)),{latlng:n,bounds:o,timestamp:t.timestamp});for(e in t.coords)"number"==typeof t.coords[e]&&(r[e]=t.coords[e]);this.fire("locationfound",r)}},addHandler:function(t,i){if(!i)return this;i=this[t]=new i(this);return this._handlers.push(i),this.options[t]&&i.enable(),this},remove:function(){if(this._initEvents(!0),this.options.maxBounds&&this.off("moveend",this._panInsideMaxBounds),this._containerId!==this._container._leaflet_id)throw new Error("Map container is being reused by another instance");try{delete this._container._leaflet_id,delete this._containerId}catch(t){this._container._leaflet_id=void 0,this._containerId=void 0}for(var t in void 0!==this._locationWatchId&&this.stopLocate(),this._stop(),T(this._mapPane),this._clearControlPos&&this._clearControlPos(),this._resizeRequest&&(r(this._resizeRequest),this._resizeRequest=null),this._clearHandlers(),this._loaded&&this.fire("unload"),this._layers)this._layers[t].remove();for(t in this._panes)T(this._panes[t]);return this._layers=[],this._panes=[],delete this._mapPane,delete this._renderer,this},createPane:function(t,i){i=b("div","leaflet-pane"+(t?" leaflet-"+t.replace("Pane","")+"-pane":""),i||this._mapPane);return t&&(this._panes[t]=i),i},getCenter:function(){return this._checkIfLoaded(),this._lastCenter&&!this._moved()?this._lastCenter:this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds();return new s(this.unproject(t.getBottomLeft()),this.unproject(t.getTopRight()))},getMinZoom:function(){return void 0===this.options.minZoom?this._layersMinZoom||0:this.options.minZoom},getMaxZoom:function(){return void 0===this.options.maxZoom?void 0===this._layersMaxZoom?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(t,i,e){t=g(t),e=_(e||[0,0]);var n=this.getZoom()||0,o=this.getMinZoom(),s=this.getMaxZoom(),r=t.getNorthWest(),t=t.getSouthEast(),e=this.getSize().subtract(e),t=f(this.project(t,n),this.project(r,n)).getSize(),r=P.any3d?this.options.zoomSnap:1,a=e.x/t.x,e=e.y/t.y,t=i?Math.max(a,e):Math.min(a,e),n=this.getScaleZoom(t,n);return r&&(n=Math.round(n/(r/100))*(r/100),n=i?Math.ceil(n/r)*r:Math.floor(n/r)*r),Math.max(o,Math.min(s,n))},getSize:function(){return this._size&&!this._sizeChanged||(this._size=new p(this._container.clientWidth||0,this._container.clientHeight||0),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(t,i){t=this._getTopLeftPoint(t,i);return new m(t,t.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._pixelOrigin},getPixelWorldBounds:function(t){return this.options.crs.getProjectedBounds(void 0===t?this.getZoom():t)},getPane:function(t){return"string"==typeof t?this._panes[t]:t},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t,i){var e=this.options.crs;return i=void 0===i?this._zoom:i,e.scale(t)/e.scale(i)},getScaleZoom:function(t,i){var e=this.options.crs,t=(i=void 0===i?this._zoom:i,e.zoom(t*e.scale(i)));return isNaN(t)?1/0:t},project:function(t,i){return i=void 0===i?this._zoom:i,this.options.crs.latLngToPoint(w(t),i)},unproject:function(t,i){return i=void 0===i?this._zoom:i,this.options.crs.pointToLatLng(_(t),i)},layerPointToLatLng:function(t){t=_(t).add(this.getPixelOrigin());return this.unproject(t)},latLngToLayerPoint:function(t){return this.project(w(t))._round()._subtract(this.getPixelOrigin())},wrapLatLng:function(t){return this.options.crs.wrapLatLng(w(t))},wrapLatLngBounds:function(t){return this.options.crs.wrapLatLngBounds(g(t))},distance:function(t,i){return this.options.crs.distance(w(t),w(i))},containerPointToLayerPoint:function(t){return _(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return _(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){t=this.containerPointToLayerPoint(_(t));return this.layerPointToLatLng(t)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(w(t)))},mouseEventToContainerPoint:function(t){return Ni(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){t=this._container=_i(t);if(!t)throw new Error("Map container not found.");if(t._leaflet_id)throw new Error("Map container is already initialized.");S(t,"scroll",this._onScroll,this),this._containerId=h(t)},_initLayout:function(){var t=this._container,i=(this._fadeAnimated=this.options.fadeAnimation&&P.any3d,z(t,"leaflet-container"+(P.touch?" leaflet-touch":"")+(P.retina?" leaflet-retina":"")+(P.ielt9?" leaflet-oldie":"")+(P.safari?" leaflet-safari":"")+(this._fadeAnimated?" leaflet-fade-anim":"")),pi(t,"position"));"absolute"!==i&&"relative"!==i&&"fixed"!==i&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._paneRenderers={},this._mapPane=this.createPane("mapPane",this._container),Z(this._mapPane,new p(0,0)),this.createPane("tilePane"),this.createPane("overlayPane"),this.createPane("shadowPane"),this.createPane("markerPane"),this.createPane("tooltipPane"),this.createPane("popupPane"),this.options.markerZoomAnimation||(z(t.markerPane,"leaflet-zoom-hide"),z(t.shadowPane,"leaflet-zoom-hide"))},_resetView:function(t,i){Z(this._mapPane,new p(0,0));var e=!this._loaded,n=(this._loaded=!0,i=this._limitZoom(i),this.fire("viewprereset"),this._zoom!==i);this._moveStart(n,!1)._move(t,i)._moveEnd(n),this.fire("viewreset"),e&&this.fire("load")},_moveStart:function(t,i){return t&&this.fire("zoomstart"),i||this.fire("movestart"),this},_move:function(t,i,e,n){void 0===i&&(i=this._zoom);var o=this._zoom!==i;return this._zoom=i,this._lastCenter=t,this._pixelOrigin=this._getNewPixelOrigin(t),n?e&&e.pinch&&this.fire("zoom",e):((o||e&&e.pinch)&&this.fire("zoom",e),this.fire("move",e)),this},_moveEnd:function(t){return t&&this.fire("zoomend"),this.fire("moveend")},_stop:function(){return r(this._flyToFrame),this._panAnim&&this._panAnim.stop(),this},_rawPanBy:function(t){Z(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_panInsideMaxBounds:function(){this._enforcingBounds||this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(t){this._targets={};var i=t?E:S;i((this._targets[h(this._container)]=this)._container,"click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress keydown keyup",this._handleDOMEvent,this),this.options.trackResize&&i(window,"resize",this._onResize,this),P.any3d&&this.options.transform3DLimit&&(t?this.off:this.on).call(this,"moveend",this._onMoveEnd)},_onResize:function(){r(this._resizeRequest),this._resizeRequest=x(function(){this.invalidateSize({debounceMoveend:!0})},this)},_onScroll:function(){this._container.scrollTop=0,this._container.scrollLeft=0},_onMoveEnd:function(){var t=this._getMapPanePos();Math.max(Math.abs(t.x),Math.abs(t.y))>=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(t,i){for(var e,n=[],o="mouseout"===i||"mouseover"===i,s=t.target||t.srcElement,r=!1;s;){if((e=this._targets[h(s)])&&("click"===i||"preclick"===i)&&this._draggableMoved(e)){r=!0;break}if(e&&e.listens(i,!0)){if(o&&!Hi(s,t))break;if(n.push(e),o)break}if(s===this._container)break;s=s.parentNode}return n=n.length||r||o||!this.listens(i,!0)?n:[this]},_isClickDisabled:function(t){for(;t!==this._container;){if(t._leaflet_disable_click)return!0;t=t.parentNode}},_handleDOMEvent:function(t){var i,e=t.target||t.srcElement;!this._loaded||e._leaflet_disable_events||"click"===t.type&&this._isClickDisabled(e)||("mousedown"===(i=t.type)&&zi(e),this._fireDOMEvent(t,i))},_mouseEvents:["click","dblclick","mouseover","mouseout","contextmenu"],_fireDOMEvent:function(t,i,e){"click"===t.type&&((a=l({},t)).type="preclick",this._fireDOMEvent(a,a.type,e));var n=this._findEventTargets(t,i);if(e){for(var o=[],s=0;sthis.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(i),n=this._getCenterOffset(t)._divideBy(1-1/n);return!(!0!==e.animate&&!this.getSize().contains(n))&&(x(function(){this._moveStart(!0,!1)._animateZoom(t,i,!0)},this),!0)},_animateZoom:function(t,i,e,n){this._mapPane&&(e&&(this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=i,z(this._mapPane,"leaflet-zoom-anim")),this.fire("zoomanim",{center:t,zoom:i,noUpdate:n}),this._tempFireZoomEvent||(this._tempFireZoomEvent=this._zoom!==this._animateToZoom),this._move(this._animateToCenter,this._animateToZoom,void 0,!0),setTimeout(a(this._onZoomTransitionEnd,this),250))},_onZoomTransitionEnd:function(){this._animatingZoom&&(this._mapPane&&M(this._mapPane,"leaflet-zoom-anim"),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom,void 0,!0),this._tempFireZoomEvent&&this.fire("zoom"),delete this._tempFireZoomEvent,this.fire("move"),this._moveEnd(!0))}});function Fi(t){return new I(t)}var Ui,I=it.extend({options:{position:"topright"},initialize:function(t){c(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var i=this._map;return i&&i.removeControl(this),this.options.position=t,i&&i.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this.remove(),this._map=t;var i=this._container=this.onAdd(t),e=this.getPosition(),t=t._controlCorners[e];return z(i,"leaflet-control"),-1!==e.indexOf("bottom")?t.insertBefore(i,t.firstChild):t.appendChild(i),this._map.on("unload",this.remove,this),this},remove:function(){return this._map&&(T(this._container),this.onRemove&&this.onRemove(this._map),this._map.off("unload",this.remove,this),this._map=null),this},_refocusOnMap:function(t){this._map&&t&&0",i=document.createElement("div");return i.innerHTML=t,i.firstChild},_addItem:function(t){var i,e=document.createElement("label"),n=this._map.hasLayer(t.layer),n=(t.overlay?((i=document.createElement("input")).type="checkbox",i.className="leaflet-control-layers-selector",i.defaultChecked=n):i=this._createRadioElement("leaflet-base-layers_"+h(this),n),this._layerControlInputs.push(i),i.layerId=h(t.layer),S(i,"click",this._onInputClick,this),document.createElement("span")),o=(n.innerHTML=" "+t.name,document.createElement("span"));return e.appendChild(o),o.appendChild(i),o.appendChild(n),(t.overlay?this._overlaysList:this._baseLayersList).appendChild(e),this._checkDisabledLayers(),e},_onInputClick:function(){var t,i,e=this._layerControlInputs,n=[],o=[];this._handlingClick=!0;for(var s=e.length-1;0<=s;s--)t=e[s],i=this._getLayer(t.layerId).layer,t.checked?n.push(i):t.checked||o.push(i);for(s=0;si.options.maxZoom},_expandIfNotCollapsed:function(){return this._map&&!this.options.collapsed&&this.expand(),this}})),qi=I.extend({options:{position:"topleft",zoomInText:'',zoomInTitle:"Zoom in",zoomOutText:'',zoomOutTitle:"Zoom out"},onAdd:function(t){var i="leaflet-control-zoom",e=b("div",i+" leaflet-bar"),n=this.options;return this._zoomInButton=this._createButton(n.zoomInText,n.zoomInTitle,i+"-in",e,this._zoomIn),this._zoomOutButton=this._createButton(n.zoomOutText,n.zoomOutTitle,i+"-out",e,this._zoomOut),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),e},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(t){!this._disabled&&this._map._zoomthis._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(t.shiftKey?3:1))},_createButton:function(t,i,e,n,o){e=b("a",e,n);return e.innerHTML=t,e.href="#",e.title=i,e.setAttribute("role","button"),e.setAttribute("aria-label",i),Oi(e),S(e,"click",Ri),S(e,"click",o,this),S(e,"click",this._refocusOnMap,this),e},_updateDisabled:function(){var t=this._map,i="leaflet-disabled";M(this._zoomInButton,i),M(this._zoomOutButton,i),this._zoomInButton.setAttribute("aria-disabled","false"),this._zoomOutButton.setAttribute("aria-disabled","false"),!this._disabled&&t._zoom!==t.getMinZoom()||(z(this._zoomOutButton,i),this._zoomOutButton.setAttribute("aria-disabled","true")),!this._disabled&&t._zoom!==t.getMaxZoom()||(z(this._zoomInButton,i),this._zoomInButton.setAttribute("aria-disabled","true"))}}),Gi=(A.mergeOptions({zoomControl:!0}),A.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new qi,this.addControl(this.zoomControl))}),I.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0},onAdd:function(t){var i="leaflet-control-scale",e=b("div",i),n=this.options;return this._addScales(n,i+"-line",e),t.on(n.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),e},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,i,e){t.metric&&(this._mScale=b("div",i,e)),t.imperial&&(this._iScale=b("div",i,e))},_update:function(){var t=this._map,i=t.getSize().y/2,t=t.distance(t.containerPointToLatLng([0,i]),t.containerPointToLatLng([this.options.maxWidth,i]));this._updateScales(t)},_updateScales:function(t){this.options.metric&&t&&this._updateMetric(t),this.options.imperial&&t&&this._updateImperial(t)},_updateMetric:function(t){var i=this._getRoundNum(t);this._updateScale(this._mScale,i<1e3?i+" m":i/1e3+" km",i/t)},_updateImperial:function(t){var i,e,t=3.2808399*t;5280'+(P.inlineSvg?' ':"")+"Leaflet"},initialize:function(t){c(this,t),this._attributions={}},onAdd:function(t){for(var i in(t.attributionControl=this)._container=b("div","leaflet-control-attribution"),Oi(this._container),t._layers)t._layers[i].getAttribution&&this.addAttribution(t._layers[i].getAttribution());return this._update(),t.on("layeradd",this._addAttribution,this),this._container},onRemove:function(t){t.off("layeradd",this._addAttribution,this)},_addAttribution:function(t){t.layer.getAttribution&&(this.addAttribution(t.layer.getAttribution()),t.layer.once("remove",function(){this.removeAttribution(t.layer.getAttribution())},this))},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t&&(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update()),this},removeAttribution:function(t){return t&&this._attributions[t]&&(this._attributions[t]--,this._update()),this},_update:function(){if(this._map){var t,i=[];for(t in this._attributions)this._attributions[t]&&i.push(t);var e=[];this.options.prefix&&e.push(this.options.prefix),i.length&&e.push(i.join(", ")),this._container.innerHTML=e.join(' ')}}}),n=(A.mergeOptions({attributionControl:!0}),A.addInitHook(function(){this.options.attributionControl&&(new Ki).addTo(this)}),I.Layers=Vi,I.Zoom=qi,I.Scale=Gi,I.Attribution=Ki,Fi.layers=function(t,i,e){return new Vi(t,i,e)},Fi.zoom=function(t){return new qi(t)},Fi.scale=function(t){return new Gi(t)},Fi.attribution=function(t){return new Ki(t)},it.extend({initialize:function(t){this._map=t},enable:function(){return this._enabled||(this._enabled=!0,this.addHooks()),this},disable:function(){return this._enabled&&(this._enabled=!1,this.removeHooks()),this},enabled:function(){return!!this._enabled}})),ft=(n.addTo=function(t,i){return t.addHandler(i,this),this},{Events:i}),Yi=P.touch?"touchstart mousedown":"mousedown",Xi=et.extend({options:{clickTolerance:3},initialize:function(t,i,e,n){c(this,n),this._element=t,this._dragStartTarget=i||t,this._preventOutline=e},enable:function(){this._enabled||(S(this._dragStartTarget,Yi,this._onDown,this),this._enabled=!0)},disable:function(){this._enabled&&(Xi._dragging===this&&this.finishDrag(!0),E(this._dragStartTarget,Yi,this._onDown,this),this._enabled=!1,this._moved=!1)},_onDown:function(t){var i,e;this._enabled&&(this._moved=!1,vi(this._element,"leaflet-zoom-anim")||(t.touches&&1!==t.touches.length?Xi._dragging===this&&this.finishDrag():Xi._dragging||t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||((Xi._dragging=this)._preventOutline&&zi(this._element),Li(),ri(),this._moving||(this.fire("down"),e=t.touches?t.touches[0]:t,i=Ci(this._element),this._startPoint=new p(e.clientX,e.clientY),this._startPos=bi(this._element),this._parentScale=Zi(i),e="mousedown"===t.type,S(document,e?"mousemove":"touchmove",this._onMove,this),S(document,e?"mouseup":"touchend touchcancel",this._onUp,this)))))},_onMove:function(t){var i;this._enabled&&(t.touches&&1i&&(e.push(t[n]),o=n);oi.max.x&&(e|=2),t.yi.max.y&&(e|=8),e}function ee(t,i,e,n){var o=i.x,i=i.y,s=e.x-o,r=e.y-i,a=s*s+r*r;return 0this._layersMaxZoom&&this.setZoom(this._layersMaxZoom),void 0===this.options.minZoom&&this._layersMinZoom&&this.getZoom()t.y!=n.y>t.y&&t.x<(n.x-e.x)*(t.y-e.y)/(n.y-e.y)+e.x&&(l=!l);return l||fe.prototype._containsPoint.call(this,t,!0)}});var ve=he.extend({initialize:function(t,i){c(this,i),this._layers={},t&&this.addData(t)},addData:function(t){var i,e,n,o=d(t)?t:t.features;if(o){for(i=0,e=o.length;ir.x&&(a=n.x+h-r.x+s.x),n.x-a-o.x<(h=0)&&(a=n.x-o.x),n.y+e+s.y>r.y&&(h=n.y+e-r.y+s.y),n.y-h-o.y<0&&(h=n.y-o.y),(a||h)&&i.fire("autopanstart").panBy([a,h],{animate:t&&"moveend"===t.type}))},_getAnchor:function(){return _(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}})),Ee=(A.mergeOptions({closePopupOnClick:!0}),A.include({openPopup:function(t,i,e){return this._initOverlay(ke,t,i,e).openOn(this),this},closePopup:function(t){return(t=arguments.length?t:this._popup)&&t.close(),this}}),o.include({bindPopup:function(t,i){return this._popup=this._initOverlay(ke,this._popup,t,i),this._popupHandlersAdded||(this.on({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t){return this._popup&&this._popup._prepareOpen(t)&&this._popup.openOn(this._map),this},closePopup:function(){return this._popup&&this._popup.close(),this},togglePopup:function(){return this._popup&&this._popup.toggle(this),this},isPopupOpen:function(){return!!this._popup&&this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var i;this._popup&&this._map&&(Ri(t),i=t.layer||t.target,this._popup._source!==i||i instanceof _e?(this._popup._source=i,this.openPopup(t.latlng)):this._map.hasLayer(this._popup)?this.closePopup():this.openPopup(t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)},_onKeyPress:function(t){13===t.originalEvent.keyCode&&this._openPopup(t)}}),O.extend({options:{pane:"tooltipPane",offset:[0,0],direction:"auto",permanent:!1,sticky:!1,opacity:.9},onAdd:function(t){O.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire("tooltipopen",{tooltip:this}),this._source&&(this.addEventParent(this._source),this._source.fire("tooltipopen",{tooltip:this},!0))},onRemove:function(t){O.prototype.onRemove.call(this,t),t.fire("tooltipclose",{tooltip:this}),this._source&&(this.removeEventParent(this._source),this._source.fire("tooltipclose",{tooltip:this},!0))},getEvents:function(){var t=O.prototype.getEvents.call(this);return this.options.permanent||(t.preclick=this.close),t},_initLayout:function(){var t="leaflet-tooltip "+(this.options.className||"")+" leaflet-zoom-"+(this._zoomAnimated?"animated":"hide");this._contentNode=this._container=b("div",t)},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var i,e=this._map,n=this._container,o=e.latLngToContainerPoint(e.getCenter()),e=e.layerPointToContainerPoint(t),s=this.options.direction,r=n.offsetWidth,a=n.offsetHeight,h=_(this.options.offset),l=this._getAnchor(),e="top"===s?(i=r/2,a):"bottom"===s?(i=r/2,0):(i="center"===s?r/2:"right"===s?0:"left"===s?r:e.xthis.options.maxZoom||nthis.options.maxZoom||void 0!==this.options.minZoom&&oe.max.x)||!i.wrapLat&&(t.ye.max.y))return!1}if(!this.options.bounds)return!0;i=this._tileCoordsToBounds(t);return g(this.options.bounds).overlaps(i)},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToNwSe:function(t){var i=this._map,e=this.getTileSize(),n=t.scaleBy(e),e=n.add(e);return[i.unproject(n,t.z),i.unproject(e,t.z)]},_tileCoordsToBounds:function(t){t=this._tileCoordsToNwSe(t),t=new s(t[0],t[1]);return t=this.options.noWrap?t:this._map.wrapLatLngBounds(t)},_tileCoordsToKey:function(t){return t.x+":"+t.y+":"+t.z},_keyToTileCoords:function(t){var t=t.split(":"),i=new p(+t[0],+t[1]);return i.z=+t[2],i},_removeTile:function(t){var i=this._tiles[t];i&&(T(i.el),delete this._tiles[t],this.fire("tileunload",{tile:i.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){z(t,"leaflet-tile");var i=this.getTileSize();t.style.width=i.x+"px",t.style.height=i.y+"px",t.onselectstart=u,t.onmousemove=u,P.ielt9&&this.options.opacity<1&&C(t,this.options.opacity)},_addTile:function(t,i){var e=this._getTilePos(t),n=this._tileCoordsToKey(t),o=this.createTile(this._wrapCoords(t),a(this._tileReady,this,t));this._initTile(o),this.createTile.length<2&&x(a(this._tileReady,this,t,null,o)),Z(o,e),this._tiles[n]={el:o,coords:t,current:!0},i.appendChild(o),this.fire("tileloadstart",{tile:o,coords:t})},_tileReady:function(t,i,e){i&&this.fire("tileerror",{error:i,tile:e,coords:t});var n=this._tileCoordsToKey(t);(e=this._tiles[n])&&(e.loaded=+new Date,this._map._fadeAnimated?(C(e.el,0),r(this._fadeFrame),this._fadeFrame=x(this._updateOpacity,this)):(e.active=!0,this._pruneTiles()),i||(z(e.el,"leaflet-tile-loaded"),this.fire("tileload",{tile:e.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire("load"),P.ielt9||!this._map._fadeAnimated?x(this._pruneTiles,this):setTimeout(a(this._pruneTiles,this),250)))},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var i=new p(this._wrapX?H(t.x,this._wrapX):t.x,this._wrapY?H(t.y,this._wrapY):t.y);return i.z=t.z,i},_pxBoundsToTileRange:function(t){var i=this.getTileSize();return new m(t.min.unscaleBy(i).floor(),t.max.unscaleBy(i).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}});var Ie=Ae.extend({options:{minZoom:0,maxZoom:18,subdomains:"abc",errorTileUrl:"",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1,referrerPolicy:!1},initialize:function(t,i){this._url=t,(i=c(this,i)).detectRetina&&P.retina&&0')}}catch(t){}return function(t){return document.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}(),Mt={_initContainer:function(){this._container=b("div","leaflet-vml-container")},_update:function(){this._map._animatingZoom||(Ne.prototype._update.call(this),this.fire("update"))},_initPath:function(t){var i=t._container=He("shape");z(i,"leaflet-vml-shape "+(this.options.className||"")),i.coordsize="1 1",t._path=He("path"),i.appendChild(t._path),this._updateStyle(t),this._layers[h(t)]=t},_addPath:function(t){var i=t._container;this._container.appendChild(i),t.options.interactive&&t.addInteractiveTarget(i)},_removePath:function(t){var i=t._container;T(i),t.removeInteractiveTarget(i),delete this._layers[h(t)]},_updateStyle:function(t){var i=t._stroke,e=t._fill,n=t.options,o=t._container;o.stroked=!!n.stroke,o.filled=!!n.fill,n.stroke?(i=i||(t._stroke=He("stroke")),o.appendChild(i),i.weight=n.weight+"px",i.color=n.color,i.opacity=n.opacity,n.dashArray?i.dashStyle=d(n.dashArray)?n.dashArray.join(" "):n.dashArray.replace(/( *, *)/g," "):i.dashStyle="",i.endcap=n.lineCap.replace("butt","flat"),i.joinstyle=n.lineJoin):i&&(o.removeChild(i),t._stroke=null),n.fill?(e=e||(t._fill=He("fill")),o.appendChild(e),e.color=n.fillColor||n.color,e.opacity=n.fillOpacity):e&&(o.removeChild(e),t._fill=null)},_updateCircle:function(t){var i=t._point.round(),e=Math.round(t._radius),n=Math.round(t._radiusY||e);this._setPath(t,t._empty()?"M0 0":"AL "+i.x+","+i.y+" "+e+","+n+" 0,23592600")},_setPath:function(t,i){t._path.v=i},_bringToFront:function(t){fi(t._container)},_bringToBack:function(t){gi(t._container)}},We=P.vml?He:ct,Fe=Ne.extend({_initContainer:function(){this._container=We("svg"),this._container.setAttribute("pointer-events","none"),this._rootGroup=We("g"),this._container.appendChild(this._rootGroup)},_destroyContainer:function(){T(this._container),E(this._container),delete this._container,delete this._rootGroup,delete this._svgSize},_update:function(){var t,i,e;this._map._animatingZoom&&this._bounds||(Ne.prototype._update.call(this),i=(t=this._bounds).getSize(),e=this._container,this._svgSize&&this._svgSize.equals(i)||(this._svgSize=i,e.setAttribute("width",i.x),e.setAttribute("height",i.y)),Z(e,t.min),e.setAttribute("viewBox",[t.min.x,t.min.y,i.x,i.y].join(" ")),this.fire("update"))},_initPath:function(t){var i=t._path=We("path");t.options.className&&z(i,t.options.className),t.options.interactive&&z(i,"leaflet-interactive"),this._updateStyle(t),this._layers[h(t)]=t},_addPath:function(t){this._rootGroup||this._initContainer(),this._rootGroup.appendChild(t._path),t.addInteractiveTarget(t._path)},_removePath:function(t){T(t._path),t.removeInteractiveTarget(t._path),delete this._layers[h(t)]},_updatePath:function(t){t._project(),t._update()},_updateStyle:function(t){var i=t._path,t=t.options;i&&(t.stroke?(i.setAttribute("stroke",t.color),i.setAttribute("stroke-opacity",t.opacity),i.setAttribute("stroke-width",t.weight),i.setAttribute("stroke-linecap",t.lineCap),i.setAttribute("stroke-linejoin",t.lineJoin),t.dashArray?i.setAttribute("stroke-dasharray",t.dashArray):i.removeAttribute("stroke-dasharray"),t.dashOffset?i.setAttribute("stroke-dashoffset",t.dashOffset):i.removeAttribute("stroke-dashoffset")):i.setAttribute("stroke","none"),t.fill?(i.setAttribute("fill",t.fillColor||t.color),i.setAttribute("fill-opacity",t.fillOpacity),i.setAttribute("fill-rule",t.fillRule||"evenodd")):i.setAttribute("fill","none"))},_updatePoly:function(t,i){this._setPath(t,dt(t._parts,i))},_updateCircle:function(t){var i=t._point,e=Math.max(Math.round(t._radius),1),n="a"+e+","+(Math.max(Math.round(t._radiusY),1)||e)+" 0 1,0 ",i=t._empty()?"M0 0":"M"+(i.x-e)+","+i.y+n+2*e+",0 "+n+2*-e+",0 ";this._setPath(t,i)},_setPath:function(t,i){t._path.setAttribute("d",i)},_bringToFront:function(t){fi(t._path)},_bringToBack:function(t){gi(t._path)}});function Ue(t){return P.svg||P.vml?new Fe(t):null}P.vml&&Fe.include(Mt),A.include({getRenderer:function(t){t=(t=t.options.renderer||this._getPaneRenderer(t.options.pane)||this.options.renderer||this._renderer)||(this._renderer=this._createRenderer());return this.hasLayer(t)||this.addLayer(t),t},_getPaneRenderer:function(t){if("overlayPane"===t||void 0===t)return!1;var i=this._paneRenderers[t];return void 0===i&&(i=this._createRenderer({pane:t}),this._paneRenderers[t]=i),i},_createRenderer:function(t){return this.options.preferCanvas&&je(t)||Ue(t)}});var Ve=ge.extend({initialize:function(t,i){ge.prototype.initialize.call(this,this._boundsToLatLngs(t),i)},setBounds:function(t){return this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return[(t=g(t)).getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}});Fe.create=We,Fe.pointsToPath=dt,ve.geometryToLayer=ye,ve.coordsToLatLng=we,ve.coordsToLatLngs=Pe,ve.latLngToCoords=be,ve.latLngsToCoords=Le,ve.getFeature=Te,ve.asFeature=ze,A.mergeOptions({boxZoom:!0});var _t=n.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._resetStateTimeout=0,t.on("unload",this._destroy,this)},addHooks:function(){S(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){E(this._container,"mousedown",this._onMouseDown,this)},moved:function(){return this._moved},_destroy:function(){T(this._pane),delete this._pane},_resetState:function(){this._resetStateTimeout=0,this._moved=!1},_clearDeferredResetState:function(){0!==this._resetStateTimeout&&(clearTimeout(this._resetStateTimeout),this._resetStateTimeout=0)},_onMouseDown:function(t){if(!t.shiftKey||1!==t.which&&1!==t.button)return!1;this._clearDeferredResetState(),this._resetState(),ri(),Li(),this._startPoint=this._map.mouseEventToContainerPoint(t),S(document,{contextmenu:Ri,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=b("div","leaflet-zoom-box",this._container),z(this._container,"leaflet-crosshair"),this._map.fire("boxzoomstart")),this._point=this._map.mouseEventToContainerPoint(t);var t=new m(this._point,this._startPoint),i=t.getSize();Z(this._box,t.min),this._box.style.width=i.x+"px",this._box.style.height=i.y+"px"},_finish:function(){this._moved&&(T(this._box),M(this._container,"leaflet-crosshair")),ai(),Ti(),E(document,{contextmenu:Ri,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){1!==t.which&&1!==t.button||(this._finish(),this._moved&&(this._clearDeferredResetState(),this._resetStateTimeout=setTimeout(a(this._resetState,this),0),t=new s(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point)),this._map.fitBounds(t).fire("boxzoomend",{boxZoomBounds:t})))},_onKeyDown:function(t){27===t.keyCode&&(this._finish(),this._clearDeferredResetState(),this._resetState())}}),Ct=(A.addInitHook("addHandler","boxZoom",_t),A.mergeOptions({doubleClickZoom:!0}),n.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var i=this._map,e=i.getZoom(),n=i.options.zoomDelta,e=t.originalEvent.shiftKey?e-n:e+n;"center"===i.options.doubleClickZoom?i.setZoom(e):i.setZoomAround(t.containerPoint,e)}})),Zt=(A.addInitHook("addHandler","doubleClickZoom",Ct),A.mergeOptions({dragging:!0,inertia:!0,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,easeLinearity:.2,worldCopyJump:!1,maxBoundsViscosity:0}),n.extend({addHooks:function(){var t;this._draggable||(t=this._map,this._draggable=new Xi(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),this._draggable.on("predrag",this._onPreDragLimit,this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDragWrap,this),t.on("zoomend",this._onZoomEnd,this),t.whenReady(this._onZoomEnd,this))),z(this._map._container,"leaflet-grab leaflet-touch-drag"),this._draggable.enable(),this._positions=[],this._times=[]},removeHooks:function(){M(this._map._container,"leaflet-grab"),M(this._map._container,"leaflet-touch-drag"),this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},moving:function(){return this._draggable&&this._draggable._moving},_onDragStart:function(){var t,i=this._map;i._stop(),this._map.options.maxBounds&&this._map.options.maxBoundsViscosity?(t=g(this._map.options.maxBounds),this._offsetLimit=f(this._map.latLngToContainerPoint(t.getNorthWest()).multiplyBy(-1),this._map.latLngToContainerPoint(t.getSouthEast()).multiplyBy(-1).add(this._map.getSize())),this._viscosity=Math.min(1,Math.max(0,this._map.options.maxBoundsViscosity))):this._offsetLimit=null,i.fire("movestart").fire("dragstart"),i.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(t){var i,e;this._map.options.inertia&&(i=this._lastTime=+new Date,e=this._lastPos=this._draggable._absPos||this._draggable._newPos,this._positions.push(e),this._times.push(i),this._prunePositions(i)),this._map.fire("move",t).fire("drag",t)},_prunePositions:function(t){for(;1i.max.x&&(t.x=this._viscousLimit(t.x,i.max.x)),t.y>i.max.y&&(t.y=this._viscousLimit(t.y,i.max.y)),this._draggable._newPos=this._draggable._startPos.add(t))},_onPreDragWrap:function(){var t=this._worldWidth,i=Math.round(t/2),e=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-i+e)%t+i-e,n=(n+i+e)%t-i-e,t=Math.abs(o+e)i.getMaxZoom()&&1=this.min.x&&e.x<=this.max.x&&i.y>=this.min.y&&e.y<=this.max.y},intersects:function(t){t=f(t);var i=this.min,e=this.max,n=t.min,t=t.max,o=t.x>=i.x&&n.x<=e.x,t=t.y>=i.y&&n.y<=e.y;return o&&t},overlaps:function(t){t=f(t);var i=this.min,e=this.max,n=t.min,t=t.max,o=t.x>i.x&&n.xi.y&&n.y=n.lat&&e.lat<=o.lat&&i.lng>=n.lng&&e.lng<=o.lng},intersects:function(t){t=g(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),t=t.getNorthEast(),o=t.lat>=i.lat&&n.lat<=e.lat,t=t.lng>=i.lng&&n.lng<=e.lng;return o&&t},overlaps:function(t){t=g(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),t=t.getNorthEast(),o=t.lat>i.lat&&n.lati.lng&&n.lng","http://www.w3.org/2000/svg"===(Wt.firstChild&&Wt.firstChild.namespaceURI));function y(t){return 0<=navigator.userAgent.toLowerCase().indexOf(t)}var P={ie:pt,ielt9:mt,edge:n,webkit:ft,android:gt,android23:vt,androidStock:yt,opera:xt,chrome:wt,gecko:Pt,safari:bt,phantom:Lt,opera12:o,win:Tt,ie3d:zt,webkit3d:Mt,gecko3d:_t,any3d:Ct,mobile:Zt,mobileWebkit:St,mobileWebkit3d:kt,msPointer:Et,pointer:Bt,touch:It,touchNative:At,mobileOpera:Ot,mobileGecko:Rt,retina:Nt,passiveEvents:Dt,canvas:jt,svg:Ht,vml:!Ht&&function(){try{var t=document.createElement("div"),i=(t.innerHTML='',t.firstChild);return i.style.behavior="url(#default#VML)",i&&"object"==typeof i.adj}catch(t){return!1}}(),inlineSvg:Wt},Ft=P.msPointer?"MSPointerDown":"pointerdown",Ut=P.msPointer?"MSPointerMove":"pointermove",Vt=P.msPointer?"MSPointerUp":"pointerup",qt=P.msPointer?"MSPointerCancel":"pointercancel",Gt={touchstart:Ft,touchmove:Ut,touchend:Vt,touchcancel:qt},Kt={touchstart:function(t,i){i.MSPOINTER_TYPE_TOUCH&&i.pointerType===i.MSPOINTER_TYPE_TOUCH&&B(i);ii(t,i)},touchmove:ii,touchend:ii,touchcancel:ii},Yt={},Xt=!1;function Jt(t,i,e){return"touchstart"!==i||Xt||(document.addEventListener(Ft,$t,!0),document.addEventListener(Ut,Qt,!0),document.addEventListener(Vt,ti,!0),document.addEventListener(qt,ti,!0),Xt=!0),Kt[i]?(e=Kt[i].bind(this,e),t.addEventListener(Gt[i],e,!1),e):(console.warn("wrong event specified:",i),L.Util.falseFn)}function $t(t){Yt[t.pointerId]=t}function Qt(t){Yt[t.pointerId]&&(Yt[t.pointerId]=t)}function ti(t){delete Yt[t.pointerId]}function ii(t,i){if(i.pointerType!==(i.MSPOINTER_TYPE_MOUSE||"mouse")){for(var e in i.touches=[],Yt)i.touches.push(Yt[e]);i.changedTouches=[i],t(i)}}var ei=200;function ni(t,e){t.addEventListener("dblclick",e);var n,o=0;function i(t){var i;1!==t.detail?n=t.detail:"mouse"===t.pointerType||t.sourceCapabilities&&!t.sourceCapabilities.firesTouchEvents||((i=Date.now())-o<=ei?2===++n&&e(function(t){var i,e,n={};for(e in t)i=t[e],n[e]=i&&i.bind?i.bind(t):i;return(t=n).type="dblclick",n.detail=2,n.isTrusted=!1,n._simulated=!0,n}(t)):n=1,o=i)}return t.addEventListener("click",i),{dblclick:e,simDblclick:i}}var oi,si,ri,ai,hi,li,ui=wi(["transform","webkitTransform","OTransform","MozTransform","msTransform"]),ci=wi(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),di="webkitTransition"===ci||"OTransition"===ci?ci+"End":"transitionend";function _i(t){return"string"==typeof t?document.getElementById(t):t}function pi(t,i){var e=t.style[i]||t.currentStyle&&t.currentStyle[i];return"auto"===(e=e&&"auto"!==e||!document.defaultView?e:(t=document.defaultView.getComputedStyle(t,null))?t[i]:null)?null:e}function b(t,i,e){t=document.createElement(t);return t.className=i||"",e&&e.appendChild(t),t}function T(t){var i=t.parentNode;i&&i.removeChild(t)}function mi(t){for(;t.firstChild;)t.removeChild(t.firstChild)}function fi(t){var i=t.parentNode;i&&i.lastChild!==t&&i.appendChild(t)}function gi(t){var i=t.parentNode;i&&i.firstChild!==t&&i.insertBefore(t,i.firstChild)}function vi(t,i){if(void 0!==t.classList)return t.classList.contains(i);t=xi(t);return 0this.options.maxZoom)?this.setZoom(t):this},panInsideBounds:function(t,i){this._enforcingBounds=!0;var e=this.getCenter(),t=this._limitCenter(e,this._zoom,g(t));return e.equals(t)||this.panTo(t,i),this._enforcingBounds=!1,this},panInside:function(t,i){var e=_((i=i||{}).paddingTopLeft||i.padding||[0,0]),n=_(i.paddingBottomRight||i.padding||[0,0]),o=this.project(this.getCenter()),t=this.project(t),s=this.getPixelBounds(),e=f([s.min.add(e),s.max.subtract(n)]),s=e.getSize();return e.contains(t)||(this._enforcingBounds=!0,n=t.subtract(e.getCenter()),e=e.extend(t).getSize().subtract(s),o.x+=n.x<0?-e.x:e.x,o.y+=n.y<0?-e.y:e.y,this.panTo(this.unproject(o),i),this._enforcingBounds=!1),this},invalidateSize:function(t){if(!this._loaded)return this;t=l({animate:!1,pan:!0},!0===t?{animate:!0}:t);var i=this.getSize(),e=(this._sizeChanged=!0,this._lastCenter=null,this.getSize()),n=i.divideBy(2).round(),o=e.divideBy(2).round(),n=n.subtract(o);return n.x||n.y?(t.animate&&t.pan?this.panBy(n):(t.pan&&this._rawPanBy(n),this.fire("move"),t.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(a(this.fire,this,"moveend"),200)):this.fire("moveend")),this.fire("resize",{oldSize:i,newSize:e})):this},stop:function(){return this.setZoom(this._limitZoom(this._zoom)),this.options.zoomSnap||this.fire("viewreset"),this._stop()},locate:function(t){if(t=this._locateOptions=l({timeout:1e4,watch:!1},t),!("geolocation"in navigator))return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var i=a(this._handleGeolocationResponse,this),e=a(this._handleGeolocationError,this);return t.watch?this._locationWatchId=navigator.geolocation.watchPosition(i,e,t):navigator.geolocation.getCurrentPosition(i,e,t),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var i;this._container._leaflet_id&&(i=t.code,t=t.message||(1===i?"permission denied":2===i?"position unavailable":"timeout"),this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:i,message:"Geolocation error: "+t+"."}))},_handleGeolocationResponse:function(t){if(this._container._leaflet_id){var i,e,n=new v(t.coords.latitude,t.coords.longitude),o=n.toBounds(2*t.coords.accuracy),s=this._locateOptions,r=(s.setView&&(i=this.getBoundsZoom(o),this.setView(n,s.maxZoom?Math.min(i,s.maxZoom):i)),{latlng:n,bounds:o,timestamp:t.timestamp});for(e in t.coords)"number"==typeof t.coords[e]&&(r[e]=t.coords[e]);this.fire("locationfound",r)}},addHandler:function(t,i){if(!i)return this;i=this[t]=new i(this);return this._handlers.push(i),this.options[t]&&i.enable(),this},remove:function(){if(this._initEvents(!0),this.options.maxBounds&&this.off("moveend",this._panInsideMaxBounds),this._containerId!==this._container._leaflet_id)throw new Error("Map container is being reused by another instance");try{delete this._container._leaflet_id,delete this._containerId}catch(t){this._container._leaflet_id=void 0,this._containerId=void 0}for(var t in void 0!==this._locationWatchId&&this.stopLocate(),this._stop(),T(this._mapPane),this._clearControlPos&&this._clearControlPos(),this._resizeRequest&&(r(this._resizeRequest),this._resizeRequest=null),this._clearHandlers(),this._loaded&&this.fire("unload"),this._layers)this._layers[t].remove();for(t in this._panes)T(this._panes[t]);return this._layers=[],this._panes=[],delete this._mapPane,delete this._renderer,this},createPane:function(t,i){i=b("div","leaflet-pane"+(t?" leaflet-"+t.replace("Pane","")+"-pane":""),i||this._mapPane);return t&&(this._panes[t]=i),i},getCenter:function(){return this._checkIfLoaded(),this._lastCenter&&!this._moved()?this._lastCenter:this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds();return new s(this.unproject(t.getBottomLeft()),this.unproject(t.getTopRight()))},getMinZoom:function(){return void 0===this.options.minZoom?this._layersMinZoom||0:this.options.minZoom},getMaxZoom:function(){return void 0===this.options.maxZoom?void 0===this._layersMaxZoom?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(t,i,e){t=g(t),e=_(e||[0,0]);var n=this.getZoom()||0,o=this.getMinZoom(),s=this.getMaxZoom(),r=t.getNorthWest(),t=t.getSouthEast(),e=this.getSize().subtract(e),t=f(this.project(t,n),this.project(r,n)).getSize(),r=P.any3d?this.options.zoomSnap:1,a=e.x/t.x,e=e.y/t.y,t=i?Math.max(a,e):Math.min(a,e),n=this.getScaleZoom(t,n);return r&&(n=Math.round(n/(r/100))*(r/100),n=i?Math.ceil(n/r)*r:Math.floor(n/r)*r),Math.max(o,Math.min(s,n))},getSize:function(){return this._size&&!this._sizeChanged||(this._size=new p(this._container.clientWidth||0,this._container.clientHeight||0),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(t,i){t=this._getTopLeftPoint(t,i);return new m(t,t.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._pixelOrigin},getPixelWorldBounds:function(t){return this.options.crs.getProjectedBounds(void 0===t?this.getZoom():t)},getPane:function(t){return"string"==typeof t?this._panes[t]:t},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t,i){var e=this.options.crs;return i=void 0===i?this._zoom:i,e.scale(t)/e.scale(i)},getScaleZoom:function(t,i){var e=this.options.crs,t=(i=void 0===i?this._zoom:i,e.zoom(t*e.scale(i)));return isNaN(t)?1/0:t},project:function(t,i){return i=void 0===i?this._zoom:i,this.options.crs.latLngToPoint(w(t),i)},unproject:function(t,i){return i=void 0===i?this._zoom:i,this.options.crs.pointToLatLng(_(t),i)},layerPointToLatLng:function(t){t=_(t).add(this.getPixelOrigin());return this.unproject(t)},latLngToLayerPoint:function(t){return this.project(w(t))._round()._subtract(this.getPixelOrigin())},wrapLatLng:function(t){return this.options.crs.wrapLatLng(w(t))},wrapLatLngBounds:function(t){return this.options.crs.wrapLatLngBounds(g(t))},distance:function(t,i){return this.options.crs.distance(w(t),w(i))},containerPointToLayerPoint:function(t){return _(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return _(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){t=this.containerPointToLayerPoint(_(t));return this.layerPointToLatLng(t)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(w(t)))},mouseEventToContainerPoint:function(t){return Ni(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){t=this._container=_i(t);if(!t)throw new Error("Map container not found.");if(t._leaflet_id)throw new Error("Map container is already initialized.");S(t,"scroll",this._onScroll,this),this._containerId=h(t)},_initLayout:function(){var t=this._container,i=(this._fadeAnimated=this.options.fadeAnimation&&P.any3d,z(t,"leaflet-container"+(P.touch?" leaflet-touch":"")+(P.retina?" leaflet-retina":"")+(P.ielt9?" leaflet-oldie":"")+(P.safari?" leaflet-safari":"")+(this._fadeAnimated?" leaflet-fade-anim":"")),pi(t,"position"));"absolute"!==i&&"relative"!==i&&"fixed"!==i&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._paneRenderers={},this._mapPane=this.createPane("mapPane",this._container),Z(this._mapPane,new p(0,0)),this.createPane("tilePane"),this.createPane("overlayPane"),this.createPane("shadowPane"),this.createPane("markerPane"),this.createPane("tooltipPane"),this.createPane("popupPane"),this.options.markerZoomAnimation||(z(t.markerPane,"leaflet-zoom-hide"),z(t.shadowPane,"leaflet-zoom-hide"))},_resetView:function(t,i){Z(this._mapPane,new p(0,0));var e=!this._loaded,n=(this._loaded=!0,i=this._limitZoom(i),this.fire("viewprereset"),this._zoom!==i);this._moveStart(n,!1)._move(t,i)._moveEnd(n),this.fire("viewreset"),e&&this.fire("load")},_moveStart:function(t,i){return t&&this.fire("zoomstart"),i||this.fire("movestart"),this},_move:function(t,i,e,n){void 0===i&&(i=this._zoom);var o=this._zoom!==i;return this._zoom=i,this._lastCenter=t,this._pixelOrigin=this._getNewPixelOrigin(t),n?e&&e.pinch&&this.fire("zoom",e):((o||e&&e.pinch)&&this.fire("zoom",e),this.fire("move",e)),this},_moveEnd:function(t){return t&&this.fire("zoomend"),this.fire("moveend")},_stop:function(){return r(this._flyToFrame),this._panAnim&&this._panAnim.stop(),this},_rawPanBy:function(t){Z(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_panInsideMaxBounds:function(){this._enforcingBounds||this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(t){this._targets={};var i=t?E:S;i((this._targets[h(this._container)]=this)._container,"click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress keydown keyup",this._handleDOMEvent,this),this.options.trackResize&&i(window,"resize",this._onResize,this),P.any3d&&this.options.transform3DLimit&&(t?this.off:this.on).call(this,"moveend",this._onMoveEnd)},_onResize:function(){r(this._resizeRequest),this._resizeRequest=x(function(){this.invalidateSize({debounceMoveend:!0})},this)},_onScroll:function(){this._container.scrollTop=0,this._container.scrollLeft=0},_onMoveEnd:function(){var t=this._getMapPanePos();Math.max(Math.abs(t.x),Math.abs(t.y))>=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(t,i){for(var e,n=[],o="mouseout"===i||"mouseover"===i,s=t.target||t.srcElement,r=!1;s;){if((e=this._targets[h(s)])&&("click"===i||"preclick"===i)&&this._draggableMoved(e)){r=!0;break}if(e&&e.listens(i,!0)){if(o&&!Hi(s,t))break;if(n.push(e),o)break}if(s===this._container)break;s=s.parentNode}return n=n.length||r||o||!this.listens(i,!0)?n:[this]},_isClickDisabled:function(t){for(;t!==this._container;){if(t._leaflet_disable_click)return!0;t=t.parentNode}},_handleDOMEvent:function(t){var i,e=t.target||t.srcElement;!this._loaded||e._leaflet_disable_events||"click"===t.type&&this._isClickDisabled(e)||("mousedown"===(i=t.type)&&zi(e),this._fireDOMEvent(t,i))},_mouseEvents:["click","dblclick","mouseover","mouseout","contextmenu"],_fireDOMEvent:function(t,i,e){"click"===t.type&&((a=l({},t)).type="preclick",this._fireDOMEvent(a,a.type,e));var n=this._findEventTargets(t,i);if(e){for(var o=[],s=0;sthis.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(i),n=this._getCenterOffset(t)._divideBy(1-1/n);return!(!0!==e.animate&&!this.getSize().contains(n))&&(x(function(){this._moveStart(!0,!1)._animateZoom(t,i,!0)},this),!0)},_animateZoom:function(t,i,e,n){this._mapPane&&(e&&(this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=i,z(this._mapPane,"leaflet-zoom-anim")),this.fire("zoomanim",{center:t,zoom:i,noUpdate:n}),this._tempFireZoomEvent||(this._tempFireZoomEvent=this._zoom!==this._animateToZoom),this._move(this._animateToCenter,this._animateToZoom,void 0,!0),setTimeout(a(this._onZoomTransitionEnd,this),250))},_onZoomTransitionEnd:function(){this._animatingZoom&&(this._mapPane&&M(this._mapPane,"leaflet-zoom-anim"),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom,void 0,!0),this._tempFireZoomEvent&&this.fire("zoom"),delete this._tempFireZoomEvent,this.fire("move"),this._moveEnd(!0))}});function Fi(t){return new I(t)}var Ui,I=it.extend({options:{position:"topright"},initialize:function(t){c(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var i=this._map;return i&&i.removeControl(this),this.options.position=t,i&&i.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this.remove(),this._map=t;var i=this._container=this.onAdd(t),e=this.getPosition(),t=t._controlCorners[e];return z(i,"leaflet-control"),-1!==e.indexOf("bottom")?t.insertBefore(i,t.firstChild):t.appendChild(i),this._map.on("unload",this.remove,this),this},remove:function(){return this._map&&(T(this._container),this.onRemove&&this.onRemove(this._map),this._map.off("unload",this.remove,this),this._map=null),this},_refocusOnMap:function(t){this._map&&t&&0",i=document.createElement("div");return i.innerHTML=t,i.firstChild},_addItem:function(t){var i,e=document.createElement("label"),n=this._map.hasLayer(t.layer),n=(t.overlay?((i=document.createElement("input")).type="checkbox",i.className="leaflet-control-layers-selector",i.defaultChecked=n):i=this._createRadioElement("leaflet-base-layers_"+h(this),n),this._layerControlInputs.push(i),i.layerId=h(t.layer),S(i,"click",this._onInputClick,this),document.createElement("span")),o=(n.innerHTML=" "+t.name,document.createElement("span"));return e.appendChild(o),o.appendChild(i),o.appendChild(n),(t.overlay?this._overlaysList:this._baseLayersList).appendChild(e),this._checkDisabledLayers(),e},_onInputClick:function(){var t,i,e=this._layerControlInputs,n=[],o=[];this._handlingClick=!0;for(var s=e.length-1;0<=s;s--)t=e[s],i=this._getLayer(t.layerId).layer,t.checked?n.push(i):t.checked||o.push(i);for(s=0;si.options.maxZoom},_expandIfNotCollapsed:function(){return this._map&&!this.options.collapsed&&this.expand(),this}})),qi=I.extend({options:{position:"topleft",zoomInText:'',zoomInTitle:"Zoom in",zoomOutText:'',zoomOutTitle:"Zoom out"},onAdd:function(t){var i="leaflet-control-zoom",e=b("div",i+" leaflet-bar"),n=this.options;return this._zoomInButton=this._createButton(n.zoomInText,n.zoomInTitle,i+"-in",e,this._zoomIn),this._zoomOutButton=this._createButton(n.zoomOutText,n.zoomOutTitle,i+"-out",e,this._zoomOut),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),e},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(t){!this._disabled&&this._map._zoomthis._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(t.shiftKey?3:1))},_createButton:function(t,i,e,n,o){e=b("a",e,n);return e.innerHTML=t,e.href="#",e.title=i,e.setAttribute("role","button"),e.setAttribute("aria-label",i),Oi(e),S(e,"click",Ri),S(e,"click",o,this),S(e,"click",this._refocusOnMap,this),e},_updateDisabled:function(){var t=this._map,i="leaflet-disabled";M(this._zoomInButton,i),M(this._zoomOutButton,i),this._zoomInButton.setAttribute("aria-disabled","false"),this._zoomOutButton.setAttribute("aria-disabled","false"),!this._disabled&&t._zoom!==t.getMinZoom()||(z(this._zoomOutButton,i),this._zoomOutButton.setAttribute("aria-disabled","true")),!this._disabled&&t._zoom!==t.getMaxZoom()||(z(this._zoomInButton,i),this._zoomInButton.setAttribute("aria-disabled","true"))}}),Gi=(A.mergeOptions({zoomControl:!0}),A.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new qi,this.addControl(this.zoomControl))}),I.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0},onAdd:function(t){var i="leaflet-control-scale",e=b("div",i),n=this.options;return this._addScales(n,i+"-line",e),t.on(n.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),e},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,i,e){t.metric&&(this._mScale=b("div",i,e)),t.imperial&&(this._iScale=b("div",i,e))},_update:function(){var t=this._map,i=t.getSize().y/2,t=t.distance(t.containerPointToLatLng([0,i]),t.containerPointToLatLng([this.options.maxWidth,i]));this._updateScales(t)},_updateScales:function(t){this.options.metric&&t&&this._updateMetric(t),this.options.imperial&&t&&this._updateImperial(t)},_updateMetric:function(t){var i=this._getRoundNum(t);this._updateScale(this._mScale,i<1e3?i+" m":i/1e3+" km",i/t)},_updateImperial:function(t){var i,e,t=3.2808399*t;5280'+(P.inlineSvg?' ':"")+"Leaflet"},initialize:function(t){c(this,t),this._attributions={}},onAdd:function(t){for(var i in(t.attributionControl=this)._container=b("div","leaflet-control-attribution"),Oi(this._container),t._layers)t._layers[i].getAttribution&&this.addAttribution(t._layers[i].getAttribution());return this._update(),t.on("layeradd",this._addAttribution,this),this._container},onRemove:function(t){t.off("layeradd",this._addAttribution,this)},_addAttribution:function(t){t.layer.getAttribution&&(this.addAttribution(t.layer.getAttribution()),t.layer.once("remove",function(){this.removeAttribution(t.layer.getAttribution())},this))},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t&&(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update()),this},removeAttribution:function(t){return t&&this._attributions[t]&&(this._attributions[t]--,this._update()),this},_update:function(){if(this._map){var t,i=[];for(t in this._attributions)this._attributions[t]&&i.push(t);var e=[];this.options.prefix&&e.push(this.options.prefix),i.length&&e.push(i.join(", ")),this._container.innerHTML=e.join(' ')}}}),n=(A.mergeOptions({attributionControl:!0}),A.addInitHook(function(){this.options.attributionControl&&(new Ki).addTo(this)}),I.Layers=Vi,I.Zoom=qi,I.Scale=Gi,I.Attribution=Ki,Fi.layers=function(t,i,e){return new Vi(t,i,e)},Fi.zoom=function(t){return new qi(t)},Fi.scale=function(t){return new Gi(t)},Fi.attribution=function(t){return new Ki(t)},it.extend({initialize:function(t){this._map=t},enable:function(){return this._enabled||(this._enabled=!0,this.addHooks()),this},disable:function(){return this._enabled&&(this._enabled=!1,this.removeHooks()),this},enabled:function(){return!!this._enabled}})),ft=(n.addTo=function(t,i){return t.addHandler(i,this),this},{Events:i}),Yi=P.touch?"touchstart mousedown":"mousedown",Xi=et.extend({options:{clickTolerance:3},initialize:function(t,i,e,n){c(this,n),this._element=t,this._dragStartTarget=i||t,this._preventOutline=e},enable:function(){this._enabled||(S(this._dragStartTarget,Yi,this._onDown,this),this._enabled=!0)},disable:function(){this._enabled&&(Xi._dragging===this&&this.finishDrag(!0),E(this._dragStartTarget,Yi,this._onDown,this),this._enabled=!1,this._moved=!1)},_onDown:function(t){var i,e;this._enabled&&(this._moved=!1,vi(this._element,"leaflet-zoom-anim")||(t.touches&&1!==t.touches.length?Xi._dragging===this&&this.finishDrag():Xi._dragging||t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||((Xi._dragging=this)._preventOutline&&zi(this._element),Li(),ri(),this._moving||(this.fire("down"),e=t.touches?t.touches[0]:t,i=Ci(this._element),this._startPoint=new p(e.clientX,e.clientY),this._startPos=bi(this._element),this._parentScale=Zi(i),e="mousedown"===t.type,S(document,e?"mousemove":"touchmove",this._onMove,this),S(document,e?"mouseup":"touchend touchcancel",this._onUp,this)))))},_onMove:function(t){var i;this._enabled&&(t.touches&&1i&&(e.push(t[n]),o=n);oi.max.x&&(e|=2),t.yi.max.y&&(e|=8),e}function ee(t,i,e,n){var o=i.x,i=i.y,s=e.x-o,r=e.y-i,a=s*s+r*r;return 0this._layersMaxZoom&&this.setZoom(this._layersMaxZoom),void 0===this.options.minZoom&&this._layersMinZoom&&this.getZoom()t.y!=n.y>t.y&&t.x<(n.x-e.x)*(t.y-e.y)/(n.y-e.y)+e.x&&(l=!l);return l||fe.prototype._containsPoint.call(this,t,!0)}});var ve=he.extend({initialize:function(t,i){c(this,i),this._layers={},t&&this.addData(t)},addData:function(t){var i,e,n,o=d(t)?t:t.features;if(o){for(i=0,e=o.length;ir.x&&(a=n.x+h-r.x+s.x),n.x-a-o.x<(h=0)&&(a=n.x-o.x),n.y+e+s.y>r.y&&(h=n.y+e-r.y+s.y),n.y-h-o.y<0&&(h=n.y-o.y),(a||h)&&i.fire("autopanstart").panBy([a,h],{animate:t&&"moveend"===t.type}))},_getAnchor:function(){return _(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}})),Ee=(A.mergeOptions({closePopupOnClick:!0}),A.include({openPopup:function(t,i,e){return this._initOverlay(ke,t,i,e).openOn(this),this},closePopup:function(t){return(t=arguments.length?t:this._popup)&&t.close(),this}}),o.include({bindPopup:function(t,i){return this._popup=this._initOverlay(ke,this._popup,t,i),this._popupHandlersAdded||(this.on({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t){return this._popup&&this._popup._prepareOpen(t)&&this._popup.openOn(this._map),this},closePopup:function(){return this._popup&&this._popup.close(),this},togglePopup:function(){return this._popup&&this._popup.toggle(this),this},isPopupOpen:function(){return!!this._popup&&this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var i;this._popup&&this._map&&(Ri(t),i=t.layer||t.target,this._popup._source!==i||i instanceof _e?(this._popup._source=i,this.openPopup(t.latlng)):this._map.hasLayer(this._popup)?this.closePopup():this.openPopup(t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)},_onKeyPress:function(t){13===t.originalEvent.keyCode&&this._openPopup(t)}}),O.extend({options:{pane:"tooltipPane",offset:[0,0],direction:"auto",permanent:!1,sticky:!1,opacity:.9},onAdd:function(t){O.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire("tooltipopen",{tooltip:this}),this._source&&(this.addEventParent(this._source),this._source.fire("tooltipopen",{tooltip:this},!0))},onRemove:function(t){O.prototype.onRemove.call(this,t),t.fire("tooltipclose",{tooltip:this}),this._source&&(this.removeEventParent(this._source),this._source.fire("tooltipclose",{tooltip:this},!0))},getEvents:function(){var t=O.prototype.getEvents.call(this);return this.options.permanent||(t.preclick=this.close),t},_initLayout:function(){var t="leaflet-tooltip "+(this.options.className||"")+" leaflet-zoom-"+(this._zoomAnimated?"animated":"hide");this._contentNode=this._container=b("div",t)},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var i,e=this._map,n=this._container,o=e.latLngToContainerPoint(e.getCenter()),e=e.layerPointToContainerPoint(t),s=this.options.direction,r=n.offsetWidth,a=n.offsetHeight,h=_(this.options.offset),l=this._getAnchor(),e="top"===s?(i=r/2,a):"bottom"===s?(i=r/2,0):(i="center"===s?r/2:"right"===s?0:"left"===s?r:e.xthis.options.maxZoom||nthis.options.maxZoom||void 0!==this.options.minZoom&&oe.max.x)||!i.wrapLat&&(t.ye.max.y))return!1}if(!this.options.bounds)return!0;i=this._tileCoordsToBounds(t);return g(this.options.bounds).overlaps(i)},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToNwSe:function(t){var i=this._map,e=this.getTileSize(),n=t.scaleBy(e),e=n.add(e);return[i.unproject(n,t.z),i.unproject(e,t.z)]},_tileCoordsToBounds:function(t){t=this._tileCoordsToNwSe(t),t=new s(t[0],t[1]);return t=this.options.noWrap?t:this._map.wrapLatLngBounds(t)},_tileCoordsToKey:function(t){return t.x+":"+t.y+":"+t.z},_keyToTileCoords:function(t){var t=t.split(":"),i=new p(+t[0],+t[1]);return i.z=+t[2],i},_removeTile:function(t){var i=this._tiles[t];i&&(T(i.el),delete this._tiles[t],this.fire("tileunload",{tile:i.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){z(t,"leaflet-tile");var i=this.getTileSize();t.style.width=i.x+"px",t.style.height=i.y+"px",t.onselectstart=u,t.onmousemove=u,P.ielt9&&this.options.opacity<1&&C(t,this.options.opacity)},_addTile:function(t,i){var e=this._getTilePos(t),n=this._tileCoordsToKey(t),o=this.createTile(this._wrapCoords(t),a(this._tileReady,this,t));this._initTile(o),this.createTile.length<2&&x(a(this._tileReady,this,t,null,o)),Z(o,e),this._tiles[n]={el:o,coords:t,current:!0},i.appendChild(o),this.fire("tileloadstart",{tile:o,coords:t})},_tileReady:function(t,i,e){i&&this.fire("tileerror",{error:i,tile:e,coords:t});var n=this._tileCoordsToKey(t);(e=this._tiles[n])&&(e.loaded=+new Date,this._map._fadeAnimated?(C(e.el,0),r(this._fadeFrame),this._fadeFrame=x(this._updateOpacity,this)):(e.active=!0,this._pruneTiles()),i||(z(e.el,"leaflet-tile-loaded"),this.fire("tileload",{tile:e.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire("load"),P.ielt9||!this._map._fadeAnimated?x(this._pruneTiles,this):setTimeout(a(this._pruneTiles,this),250)))},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var i=new p(this._wrapX?H(t.x,this._wrapX):t.x,this._wrapY?H(t.y,this._wrapY):t.y);return i.z=t.z,i},_pxBoundsToTileRange:function(t){var i=this.getTileSize();return new m(t.min.unscaleBy(i).floor(),t.max.unscaleBy(i).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}});var Ie=Ae.extend({options:{minZoom:0,maxZoom:18,subdomains:"abc",errorTileUrl:"",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1,referrerPolicy:!1},initialize:function(t,i){this._url=t,(i=c(this,i)).detectRetina&&P.retina&&0')}}catch(t){}return function(t){return document.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}(),Mt={_initContainer:function(){this._container=b("div","leaflet-vml-container")},_update:function(){this._map._animatingZoom||(Ne.prototype._update.call(this),this.fire("update"))},_initPath:function(t){var i=t._container=He("shape");z(i,"leaflet-vml-shape "+(this.options.className||"")),i.coordsize="1 1",t._path=He("path"),i.appendChild(t._path),this._updateStyle(t),this._layers[h(t)]=t},_addPath:function(t){var i=t._container;this._container.appendChild(i),t.options.interactive&&t.addInteractiveTarget(i)},_removePath:function(t){var i=t._container;T(i),t.removeInteractiveTarget(i),delete this._layers[h(t)]},_updateStyle:function(t){var i=t._stroke,e=t._fill,n=t.options,o=t._container;o.stroked=!!n.stroke,o.filled=!!n.fill,n.stroke?(i=i||(t._stroke=He("stroke")),o.appendChild(i),i.weight=n.weight+"px",i.color=n.color,i.opacity=n.opacity,n.dashArray?i.dashStyle=d(n.dashArray)?n.dashArray.join(" "):n.dashArray.replace(/( *, *)/g," "):i.dashStyle="",i.endcap=n.lineCap.replace("butt","flat"),i.joinstyle=n.lineJoin):i&&(o.removeChild(i),t._stroke=null),n.fill?(e=e||(t._fill=He("fill")),o.appendChild(e),e.color=n.fillColor||n.color,e.opacity=n.fillOpacity):e&&(o.removeChild(e),t._fill=null)},_updateCircle:function(t){var i=t._point.round(),e=Math.round(t._radius),n=Math.round(t._radiusY||e);this._setPath(t,t._empty()?"M0 0":"AL "+i.x+","+i.y+" "+e+","+n+" 0,23592600")},_setPath:function(t,i){t._path.v=i},_bringToFront:function(t){fi(t._container)},_bringToBack:function(t){gi(t._container)}},We=P.vml?He:ct,Fe=Ne.extend({_initContainer:function(){this._container=We("svg"),this._container.setAttribute("pointer-events","none"),this._rootGroup=We("g"),this._container.appendChild(this._rootGroup)},_destroyContainer:function(){T(this._container),E(this._container),delete this._container,delete this._rootGroup,delete this._svgSize},_update:function(){var t,i,e;this._map._animatingZoom&&this._bounds||(Ne.prototype._update.call(this),i=(t=this._bounds).getSize(),e=this._container,this._svgSize&&this._svgSize.equals(i)||(this._svgSize=i,e.setAttribute("width",i.x),e.setAttribute("height",i.y)),Z(e,t.min),e.setAttribute("viewBox",[t.min.x,t.min.y,i.x,i.y].join(" ")),this.fire("update"))},_initPath:function(t){var i=t._path=We("path");t.options.className&&z(i,t.options.className),t.options.interactive&&z(i,"leaflet-interactive"),this._updateStyle(t),this._layers[h(t)]=t},_addPath:function(t){this._rootGroup||this._initContainer(),this._rootGroup.appendChild(t._path),t.addInteractiveTarget(t._path)},_removePath:function(t){T(t._path),t.removeInteractiveTarget(t._path),delete this._layers[h(t)]},_updatePath:function(t){t._project(),t._update()},_updateStyle:function(t){var i=t._path,t=t.options;i&&(t.stroke?(i.setAttribute("stroke",t.color),i.setAttribute("stroke-opacity",t.opacity),i.setAttribute("stroke-width",t.weight),i.setAttribute("stroke-linecap",t.lineCap),i.setAttribute("stroke-linejoin",t.lineJoin),t.dashArray?i.setAttribute("stroke-dasharray",t.dashArray):i.removeAttribute("stroke-dasharray"),t.dashOffset?i.setAttribute("stroke-dashoffset",t.dashOffset):i.removeAttribute("stroke-dashoffset")):i.setAttribute("stroke","none"),t.fill?(i.setAttribute("fill",t.fillColor||t.color),i.setAttribute("fill-opacity",t.fillOpacity),i.setAttribute("fill-rule",t.fillRule||"evenodd")):i.setAttribute("fill","none"))},_updatePoly:function(t,i){this._setPath(t,dt(t._parts,i))},_updateCircle:function(t){var i=t._point,e=Math.max(Math.round(t._radius),1),n="a"+e+","+(Math.max(Math.round(t._radiusY),1)||e)+" 0 1,0 ",i=t._empty()?"M0 0":"M"+(i.x-e)+","+i.y+n+2*e+",0 "+n+2*-e+",0 ";this._setPath(t,i)},_setPath:function(t,i){t._path.setAttribute("d",i)},_bringToFront:function(t){fi(t._path)},_bringToBack:function(t){gi(t._path)}});function Ue(t){return P.svg||P.vml?new Fe(t):null}P.vml&&Fe.include(Mt),A.include({getRenderer:function(t){t=(t=t.options.renderer||this._getPaneRenderer(t.options.pane)||this.options.renderer||this._renderer)||(this._renderer=this._createRenderer());return this.hasLayer(t)||this.addLayer(t),t},_getPaneRenderer:function(t){if("overlayPane"===t||void 0===t)return!1;var i=this._paneRenderers[t];return void 0===i&&(i=this._createRenderer({pane:t}),this._paneRenderers[t]=i),i},_createRenderer:function(t){return this.options.preferCanvas&&je(t)||Ue(t)}});var Ve=ge.extend({initialize:function(t,i){ge.prototype.initialize.call(this,this._boundsToLatLngs(t),i)},setBounds:function(t){return this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return[(t=g(t)).getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}});Fe.create=We,Fe.pointsToPath=dt,ve.geometryToLayer=ye,ve.coordsToLatLng=we,ve.coordsToLatLngs=Pe,ve.latLngToCoords=be,ve.latLngsToCoords=Le,ve.getFeature=Te,ve.asFeature=ze,A.mergeOptions({boxZoom:!0});var _t=n.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._resetStateTimeout=0,t.on("unload",this._destroy,this)},addHooks:function(){S(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){E(this._container,"mousedown",this._onMouseDown,this)},moved:function(){return this._moved},_destroy:function(){T(this._pane),delete this._pane},_resetState:function(){this._resetStateTimeout=0,this._moved=!1},_clearDeferredResetState:function(){0!==this._resetStateTimeout&&(clearTimeout(this._resetStateTimeout),this._resetStateTimeout=0)},_onMouseDown:function(t){if(!t.shiftKey||1!==t.which&&1!==t.button)return!1;this._clearDeferredResetState(),this._resetState(),ri(),Li(),this._startPoint=this._map.mouseEventToContainerPoint(t),S(document,{contextmenu:Ri,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=b("div","leaflet-zoom-box",this._container),z(this._container,"leaflet-crosshair"),this._map.fire("boxzoomstart")),this._point=this._map.mouseEventToContainerPoint(t);var t=new m(this._point,this._startPoint),i=t.getSize();Z(this._box,t.min),this._box.style.width=i.x+"px",this._box.style.height=i.y+"px"},_finish:function(){this._moved&&(T(this._box),M(this._container,"leaflet-crosshair")),ai(),Ti(),E(document,{contextmenu:Ri,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){1!==t.which&&1!==t.button||(this._finish(),this._moved&&(this._clearDeferredResetState(),this._resetStateTimeout=setTimeout(a(this._resetState,this),0),t=new s(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point)),this._map.fitBounds(t).fire("boxzoomend",{boxZoomBounds:t})))},_onKeyDown:function(t){27===t.keyCode&&(this._finish(),this._clearDeferredResetState(),this._resetState())}}),Ct=(A.addInitHook("addHandler","boxZoom",_t),A.mergeOptions({doubleClickZoom:!0}),n.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var i=this._map,e=i.getZoom(),n=i.options.zoomDelta,e=t.originalEvent.shiftKey?e-n:e+n;"center"===i.options.doubleClickZoom?i.setZoom(e):i.setZoomAround(t.containerPoint,e)}})),Zt=(A.addInitHook("addHandler","doubleClickZoom",Ct),A.mergeOptions({dragging:!0,inertia:!0,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,easeLinearity:.2,worldCopyJump:!1,maxBoundsViscosity:0}),n.extend({addHooks:function(){var t;this._draggable||(t=this._map,this._draggable=new Xi(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),this._draggable.on("predrag",this._onPreDragLimit,this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDragWrap,this),t.on("zoomend",this._onZoomEnd,this),t.whenReady(this._onZoomEnd,this))),z(this._map._container,"leaflet-grab leaflet-touch-drag"),this._draggable.enable(),this._positions=[],this._times=[]},removeHooks:function(){M(this._map._container,"leaflet-grab"),M(this._map._container,"leaflet-touch-drag"),this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},moving:function(){return this._draggable&&this._draggable._moving},_onDragStart:function(){var t,i=this._map;i._stop(),this._map.options.maxBounds&&this._map.options.maxBoundsViscosity?(t=g(this._map.options.maxBounds),this._offsetLimit=f(this._map.latLngToContainerPoint(t.getNorthWest()).multiplyBy(-1),this._map.latLngToContainerPoint(t.getSouthEast()).multiplyBy(-1).add(this._map.getSize())),this._viscosity=Math.min(1,Math.max(0,this._map.options.maxBoundsViscosity))):this._offsetLimit=null,i.fire("movestart").fire("dragstart"),i.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(t){var i,e;this._map.options.inertia&&(i=this._lastTime=+new Date,e=this._lastPos=this._draggable._absPos||this._draggable._newPos,this._positions.push(e),this._times.push(i),this._prunePositions(i)),this._map.fire("move",t).fire("drag",t)},_prunePositions:function(t){for(;1i.max.x&&(t.x=this._viscousLimit(t.x,i.max.x)),t.y>i.max.y&&(t.y=this._viscousLimit(t.y,i.max.y)),this._draggable._newPos=this._draggable._startPos.add(t))},_onPreDragWrap:function(){var t=this._worldWidth,i=Math.round(t/2),e=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-i+e)%t+i-e,n=(n+i+e)%t-i-e,t=Math.abs(o+e)i.getMaxZoom()&&1s;s++)a=this._data[s],i.globalAlpha=Math.max(a[2]/this._max,t||.05),i.drawImage(this._circle,a[0]-this._r,a[1]-this._r);var n=i.getImageData(0,0,this._width,this._height);return this._colorize(n.data,this._grad),i.putImageData(n,0,0),this},_colorize:function(t,i){for(var a,s=3,e=t.length;e>s;s+=4)a=4*t[s],a&&(t[s-3]=i[a],t[s-2]=i[a+1],t[s-1]=i[a+2])}},window.simpleheat=t}(),/* - (c) 2014, Vladimir Agafonkin - Leaflet.heat, a tiny and fast heatmap plugin for Leaflet. - https://github.com/Leaflet/Leaflet.heat -*/ -L.HeatLayer=(L.Layer?L.Layer:L.Class).extend({initialize:function(t,i){this._latlngs=t,L.setOptions(this,i)},setLatLngs:function(t){return this._latlngs=t,this.redraw()},addLatLng:function(t){return this._latlngs.push(t),this.redraw()},setOptions:function(t){return L.setOptions(this,t),this._heat&&this._updateOptions(),this.redraw()},redraw:function(){return!this._heat||this._frame||this._map._animating||(this._frame=L.Util.requestAnimFrame(this._redraw,this)),this},onAdd:function(t){this._map=t,this._canvas||this._initCanvas(),t._panes.overlayPane.appendChild(this._canvas),t.on("moveend",this._reset,this),t.options.zoomAnimation&&L.Browser.any3d&&t.on("zoomanim",this._animateZoom,this),this._reset()},onRemove:function(t){t.getPanes().overlayPane.removeChild(this._canvas),t.off("moveend",this._reset,this),t.options.zoomAnimation&&t.off("zoomanim",this._animateZoom,this)},addTo:function(t){return t.addLayer(this),this},_initCanvas:function(){var t=this._canvas=L.DomUtil.create("canvas","leaflet-heatmap-layer leaflet-layer"),i=L.DomUtil.testProp(["transformOrigin","WebkitTransformOrigin","msTransformOrigin"]);t.style[i]="50% 50%";var a=this._map.getSize();t.width=a.x,t.height=a.y;var s=this._map.options.zoomAnimation&&L.Browser.any3d;L.DomUtil.addClass(t,"leaflet-zoom-"+(s?"animated":"hide")),this._heat=simpleheat(t),this._updateOptions()},_updateOptions:function(){this._heat.radius(this.options.radius||this._heat.defaultRadius,this.options.blur),this.options.gradient&&this._heat.gradient(this.options.gradient),this.options.max&&this._heat.max(this.options.max)},_reset:function(){var t=this._map.containerPointToLayerPoint([0,0]);L.DomUtil.setPosition(this._canvas,t);var i=this._map.getSize();this._heat._width!==i.x&&(this._canvas.width=this._heat._width=i.x),this._heat._height!==i.y&&(this._canvas.height=this._heat._height=i.y),this._redraw()},_redraw:function(){var t,i,a,s,e,n,h,o,r,d=[],_=this._heat._r,l=this._map.getSize(),m=new L.Bounds(L.point([-_,-_]),l.add([_,_])),c=void 0===this.options.max?1:this.options.max,u=void 0===this.options.maxZoom?this._map.getMaxZoom():this.options.maxZoom,f=1/Math.pow(2,Math.max(0,Math.min(u-this._map.getZoom(),12))),g=_/2,p=[],v=this._map._getMapPanePos(),w=v.x%g,y=v.y%g;for(t=0,i=this._latlngs.length;i>t;t++)if(a=this._map.latLngToContainerPoint(this._latlngs[t]),m.contains(a)){e=Math.floor((a.x-w)/g)+2,n=Math.floor((a.y-y)/g)+2;var x=void 0!==this._latlngs[t].alt?this._latlngs[t].alt:void 0!==this._latlngs[t][2]?+this._latlngs[t][2]:1;r=x*f,p[n]=p[n]||[],s=p[n][e],s?(s[0]=(s[0]*s[2]+a.x*r)/(s[2]+r),s[1]=(s[1]*s[2]+a.y*r)/(s[2]+r),s[2]+=r):p[n][e]=[a.x,a.y,r]}for(t=0,i=p.length;i>t;t++)if(p[t])for(h=0,o=p[t].length;o>h;h++)s=p[t][h],s&&d.push([Math.round(s[0]),Math.round(s[1]),Math.min(s[2],c)]);this._heat.data(d).draw(this.options.minOpacity),this._frame=null},_animateZoom:function(t){var i=this._map.getZoomScale(t.zoom),a=this._map._getCenterOffset(t.center)._multiplyBy(-i).subtract(this._map._getMapPanePos());L.DomUtil.setTransform?L.DomUtil.setTransform(this._canvas,a,i):this._canvas.style[L.DomUtil.TRANSFORM]=L.DomUtil.getTranslateString(a)+" scale("+i+")"}}),L.heatLayer=function(t,i){return new L.HeatLayer(t,i)};var widths = [12, 13, 13, 13, 13, 13, 13, 13]; -var opas = [0.8, 0.6, 0.5, 0.5, 0.4]; -var mwidths = [1, 1, 1, 1.5, 2, 3, 5, 6, 6, 4, 3, 2]; -var stCols = ['#78f378', '#0000c3', 'red']; - -var osmUrl = "//www.openstreetmap.org/"; - -var grIdx, stIdx, selectedRes, prevSearch, delayTimer; - -var reqs = {}; - -var openedGr = -1; -var openedSt = -1; - -var sgMvOrNew = "Move into a new relation public_transport=stop_area."; -var sgMvOrEx = "Move into relation ${toid}."; -var sgMvRelNew = "Move from relation ${ooid} into a new relation public_transport=stop_area."; -var sgMvRelRel = "Move from relation ${ooid} into relation ${toid}."; -var sgMvOutRel = "Move out of relation ${ooid}"; -var sgFixAttr = "Fix attribute ${attr}."; -var sgAddName = "Consider adding a name attribute."; -var sgAttrTr = "Attribute ${attr} seems to be a track number. Use ref for this and set ${attr} to the station name."; - -var suggsMsg = [sgMvOrNew, sgMvOrEx, sgMvRelNew, sgMvRelRel, sgMvOutRel, sgFixAttr, sgAddName, sgAttrTr]; - -function $(a){return a[0] == "#" ? document.getElementById(a.substr(1)) : a[0] == "." ? document.getElementsByClassName(a.substr(1)) : document.getElementsByTagName(a)} -function $$(t){return document.createElement(t) } -function ll(g){return {"lat" : g[0], "lng" : g[1]}} -function hasCl(e, c){return e.className.split(" ").indexOf(c) != -1} -function addCl(e, c){if (!hasCl(e, c)) e.className += " " + c;e.className = e.className.trim()} -function delCl(e, c){var a = e.className.split(" "); delete a[a.indexOf(c)]; e.className = a.join(" ").trim()} -function stCol(s){return s.e ? stCols[2] : s.s ? stCols[1] : stCols[0]} -function tmpl(s, r){for (p in r) s = s.replace(new RegExp("\\${" + p + "}", "g"), r[p]); return s} -function req(id, u, cb) { - if (reqs[id]) reqs[id].abort(); - reqs[id] = new XMLHttpRequest(); - reqs[id].onreadystatechange = function() { if (this.readyState == 4 && this.status == 200 && this == reqs[id]) cb(JSON.parse(this.responseText))}; - reqs[id].open("GET", u, 1); - reqs[id].send(); -} - -function marker(stat, z) { - if (stat.g.length == 1) { - if (z > 15) { - return L.circle( - stat.g[0], { - color: '#000', - fillColor: stCol(stat), - radius: mwidths[23 - z], - fillOpacity: 1, - weight: z > 17 ? 1.5 : 1, - id: stat.i - } - ); - } else { - return L.polyline( - [stat.g[0], stat.g[0]], { - color: stCol(stat), - fillColor: stCol(stat), - weight: widths[15 - z], - opacity: opas[15 - z], - id: stat.i - } - ); - } - } else { - return L.polygon( - stat.g, { - color: z > 15 ? '#000': stCol(stat), - fillColor: stCol(stat), - smoothFactor: 0, - fillOpacity: 0.75, - weight: z > 17 ? 1.5 : 1, - id: stat.i - } - ); - } -} - -function poly(group, z) { - var col = group.e ? 'red' : group.s ? '#0000c3' : '#85f385'; - var style = { - color: col, - fillColor: col, - smoothFactor: 0.4, - fillOpacity: 0.2, - id: group.i - }; - if (z < 16) { - style.weight = 11; - style.opacity = 0.5; - style.fillOpacity = 0.5; - } - return L.polygon(group.g, style) -} - -function sugArr(sug, z) { - return L.polyline(sug.a, { - id: sug.i, - color: '#0000c3', - smoothFactor: 0.1, - weight: 4, - opacity: 0.5 - }); -} - -function rndrSt(stat) { - openedSt = stat.id; - stHl(stat.id); - var attrrows = {}; - - var way = stat.osmid < 0; - var osmid = Math.abs(stat.osmid); - var ident = way ? "Way" : "Node"; - - var con = $$('div'); - con.setAttribute("id", "nav") - - var suggD = $$('div'); - suggD.setAttribute("id", "sugg") - - con.innerHTML = ident + " " + osmid + ""; - - if (stat.attrs.name) con.innerHTML += " (\"" + stat.attrs.name + "\")"; - - con.innerHTML += ""; - - var attrTbl = $$('table'); - attrTbl.setAttribute("id", "attr-tbl") - con.appendChild(attrTbl); - con.appendChild(suggD); - - var tbody = $$('tbody'); - attrTbl.appendChild(tbody); - - for (var key in stat.attrs) { - var row = $$('tr'); - var col1 = $$('td'); - var col2 = $$('td'); - addCl(col2, "err-wrap"); - tbody.appendChild(row); - row.appendChild(col1); - row.appendChild(col2); - col1.innerHTML = "" + key + ""; - for (var i = 0; i < stat.attrs[key].length; i++) col2.innerHTML += "" + stat.attrs[key][i] + "" + "
"; - attrrows[key] = row; - } - - for (var i = 0; i < stat.attrerrs.length; i++) { - var err = stat.attrerrs[i]; - var row = attrrows[err.attr[0]]; - addCl(row, "err-" + Math.round(err.conf * 10)); - - var info = $$('div'); - - if (err.other_grp) { - // the mismatch was with a group name - if (err.other_osmid > 1) info.innerHTML = "Does not match " + err.other_attr[0] + " in relation " + Math.abs(err.other_osmid) + ""; - else info.innerHTML = "Does not match " + err.other_attr[0] + " in relation " + Math.abs(err.other_osmid) + ""; - } else { - // the mismatch was with another station - if (err.other_osmid != stat.osmid) { - var ident = err.other_osmid < 0 ? "way" : "node"; - info.innerHTML = "Does not match " + err.other_attr[0] + " in " + ident + " " + Math.abs(err.other_osmid) + ""; - } else { - info.innerHTML = "Does not match " + err.other_attr[0] + " = '" + err.other_attr[1] + "'"; - } - } - addCl(info, 'attr-err-info'); - row.childNodes[1].appendChild(info); - } - - var suggList = $$('ul'); - - if (stat.su.length) { - var a = $$('span'); - addCl(a, "sugtit"); - a.innerHTML = "Suggestions"; - suggD.appendChild(a); - } - - suggD.appendChild(suggList); - - for (var i = 0; i < stat.su.length; i++) { - var sg = stat.su[i]; - var sgDiv = $$('li'); - sgDiv.innerHTML = tmpl(suggsMsg[sg.type - 1], {"attr" : sg.attr, "tid" : sg.target_gid, "ooid" : sg.orig_osm_rel_id, "toid" : sg.target_osm_rel_id, "oid" : sg.orig_gid}); - suggList.appendChild(sgDiv); - } - - L.popup({opacity: 0.8}) - .setLatLng(stat) - .setContent(con) - .openOn(map) - .on('remove', function() {openedSt = -1; stUnHl(stat.id);}); -} - -function openSt(id) {req("s", "/stat?id=" + id, function(c) {rndrSt(c)});} - -function rndrGr(grp, ll) { - openedGr = grp.id; - var attrrows = {}; - grHl(grp.id); - - var con = $$('div'); - con.setAttribute("id", "nav"); - - var newMembers = $$('div'); - newMembers.setAttribute("id", "group-stations-new") - newMembers.innerHTML = "New Members"; - - var oldMembers = $$('div'); - oldMembers.setAttribute("id", "group-stations-old") - oldMembers.innerHTML = "Existing Members"; - - if (grp.osmid == 1) { - con.innerHTML = "New relation public_transport=stop_area"; - } else { - con.innerHTML = "OSM relation " + grp.osmid + ""; - - if (grp.attrs.name) con.innerHTML += " (\"" + grp.attrs.name + "\")"; - - con.innerHTML += ""; - } - - var attrTbl = $$('table'); - attrTbl.setAttribute("id", "attr-tbl") - con.appendChild(attrTbl); - - var tbody = $$('tbody'); - attrTbl.appendChild(tbody); - - var suggD = $$('div'); - suggD.setAttribute("id", "sugg") - - for (var key in grp.attrs) { - var row = $$('tr'); - var col1 = $$('td'); - var col2 = $$('td'); - addCl(col2, "err-wrap"); - tbody.appendChild(row); - row.appendChild(col1); - row.appendChild(col2); - col1.innerHTML = "" + key + ""; - for (var i = 0; i < grp.attrs[key].length; i++) col2.innerHTML += "" + grp.attrs[key][i] + "" + "
"; - attrrows[key] = row; - } - - for (var i = 0; i < grp.attrerrs.length; i++) { - var err = grp.attrerrs[i]; - var row = attrrows[err.attr[0]]; - addCl(row, "err-" + Math.round(err.conf * 10)); - - var info = $$('div'); - - if (err.other_grp) { - // the mismatch was with a group name - if (err.other_osmid != grp.osmid) { - if (err.other_osmid > 1) info.innerHTML = "Does not match " + err.other_attr[0] + " in relation " + Math.abs(err.other_osmid) + ""; - else info.innerHTML = "Does not match " + err.other_attr[0] + " in relation " + Math.abs(err.other_osmid) + ""; - } else info.innerHTML = "Does not match " + err.other_attr[0] + " = '" + err.other_attr[1] + "'"; - } else { - // the mismatch was with another station - var ident = err.other_osmid < 0 ? "way" : "node"; - info.innerHTML = "Does not match " + err.other_attr[0] + " in " + ident + " " + Math.abs(err.other_osmid) + ""; - } - - addCl(info, 'attr-err-info'); - row.childNodes[1].appendChild(info); - } - - con.appendChild(newMembers); - if (grp.osmid != 1) con.appendChild(oldMembers); - - for (var key in grp.stations) { - var stat = grp.stations[key]; - var row = $$('div'); - var ident = stat.osmid < 0 ? "Way" : "Node"; - - row.innerHTML = ident + " " + Math.abs(stat.osmid) + ""; - - if (stat.attrs.name) row.innerHTML += " (\"" + stat.attrs.name + "\")"; - - row.style.backgroundColor = stat.e ? '#f58d8d' : stat.s ? '#b6b6e4' : '#c0f7c0'; - - if (grp.osmid == 1 || stat.orig_group != grp.id) newMembers.appendChild(row); - else { - oldMembers.appendChild(row); - if (stat.group != grp.id) addCl(row, "del-stat"); - } - } - - var suggList = $$('ul'); - - if (grp.su.length) { - var a = $$('span'); - addCl(a, "sugtit"); - a.innerHTML = "Suggestions"; - suggD.appendChild(a); - } - - suggD.appendChild(suggList); - - for (var i = 0; i < grp.su.length; i++) { - var sugg = grp.su[i]; - var suggDiv = $$('li'); - - if (sugg.type == 6) suggDiv.innerHTML = "Fix attribute " + sugg.attr + "."; - else if (sugg.type == 7) suggDiv.innerHTML = "Consider adding a name attribute."; - else if (sugg.type == 8) suggDiv.innerHTML = "Attribute " + sugg.attr + " seems to be a track number. Use ref for this and set " + sugg.attr + " to the station name."; - - suggList.appendChild(suggDiv); - } - - con.appendChild(suggD); - - L.popup({opacity: 0.8}) - .setLatLng(ll) - .setContent(con) - .openOn(map) - .on('remove', function() {openedGr = -1; grUnHl(grp.id)}); -} - -function openGr(id, ll) { - req("g", "/group?id=" + id, function(c) {rndrGr(c, ll)}); -} - -function grHl(id) { - !grIdx[id] || grIdx[id].setStyle({'weight': 6, 'color': "#eecc00"}); -} - -function grUnHl(id) { - !grIdx[id] || grIdx[id].setStyle({ - 'weight': 3, - 'color': grIdx[id].options["fillColor"] - }); -} - -function stHl(id) { - if (!stIdx[id]) return; - - if (map.getZoom() > 15) { - stIdx[id].setStyle({ - 'weight': 5, - 'color': "#eecc00" - }); - } else { - stIdx[id].setStyle({ - 'color': "#eecc00" - }); - } -} - -function stUnHl(id) { - if (!stIdx[id]) return; - - if (map.getZoom() > 15) { - stIdx[id].setStyle({ - 'weight': map.getZoom() > 17 ? 1.5 : 1, - 'color': "black" - }); - } else { - stIdx[id].setStyle({ - 'color': stIdx[id].options["fillColor"] - }); - } -} - -var map = L.map('map', {renderer: L.canvas(), attributionControl: false}).setView([47.9965, 7.8469], 13); - -map.addControl(L.control.attribution({ - position: 'bottomright', - prefix: '© University of Freiburg, Chair of Algorithms and Data Structures' -})); - -map.on('popupopen', function(e) { - var px = map.project(e.target._popup._latlng); - px.y -= e.target._popup._container.clientHeight/2; - map.panTo(map.unproject(px),{animate: true}); - search(); -}); - -L.tileLayer('http://{s}.tile.stamen.com/toner-lite/{z}/{x}/{y}.png', { - maxZoom: 20, - attribution: '© OpenStreetMap', - opacity: 0.8 -}).addTo(map); - -var l = L.featureGroup().addTo(map); - -map.on("moveend", function() {render();}); -map.on("click", function() {search()}); - -function render() { - if (map.getZoom() < 11) { - req("m", "/heatmap?z=" + map.getZoom() + "&bbox=" + [map.getBounds().getSouthWest().lat, map.getBounds().getSouthWest().lng, map.getBounds().getNorthEast().lat, map.getBounds().getNorthEast().lng].join(","), - function(re) { - l.clearLayers(); - - var blur = 22 - map.getZoom(); - var rad = 25 - map.getZoom(); - - l.addLayer(L.heatLayer(re.ok, { - max: 500, - gradient: { - 0: '#cbf7cb', - 0.5: '#78f378', - 1: '#29c329' - }, - minOpacity: 0.65, - blur: blur, - radius: rad - })); - l.addLayer(L.heatLayer(re.sugg, { - max: 500, - gradient: { - 0: '#7f7fbd', - 0.5: '#4444b3', - 1: '#0606c1' - }, - minOpacity: 0.65, - blur: blur - 3, - radius: Math.min(12, rad - 3) - })); - l.addLayer(L.heatLayer(re.err, { - max: 500, - gradient: { - 0: '#f39191', - 0.5: '#ff5656', - 1: '#ff0000' - }, - minOpacity: 0.75, - blur: blur - 3, - radius: Math.min(10, rad - 3), - maxZoom: 15 - })); - } - ) - } else { - req("m", "/map?z=" + map.getZoom() + "&bbox=" + [map.getBounds().getSouthWest().lat, map.getBounds().getSouthWest().lng, map.getBounds().getNorthEast().lat, map.getBounds().getNorthEast().lng].join(","), - function(re) { - l.clearLayers(); - grIdx = {}; - stIdx = {}; - - var stats = []; - for (var i = 0; i < re.stats.length; i++) { - stIdx[re.stats[i].i] = stats[stats.push(marker(re.stats[i], map.getZoom())) - 1]; - } - - var groups = []; - for (var i = 0; i < re.groups.length; i++) { - grIdx[re.groups[i].i] = groups[groups.push(poly(re.groups[i], map.getZoom())) - 1];; - } - - var suggs = []; - for (var i = 0; i < re.su.length; i++) { - suggs.push(sugArr(re.su[i], map.getZoom())); - } - - if (map.getZoom() > 13) { - l.addLayer(L.featureGroup(groups).on('click', function(a) { - openGr(a.layer.options.id, a.layer.getBounds().getCenter()); - })); - } - - l.addLayer(L.featureGroup(stats).on('click', function(a) { - openSt(a.layer.options.id); - })); - - if (map.getZoom() > 15) { - l.addLayer(L.featureGroup(suggs).on('click', function(a) { - openSt(a.layer.options.id); - })); - } - - grHl(openedGr); - stHl(openedSt); - } - ) - }; -} - -function rowClick(row) { - if (!isSearchOpen()) return; - if (row.stat) openSt(row.stat.i, ll(row.stat.g[0])); - else openGr(row.group.i, ll(row.group.g[0])); -} - -function select(row) { - if (!row) return; - if (!isSearchOpen()) return; - unselect(selectedRes); - selectedRes = row; - addCl(row, "selres"); - if (row.stat) stHl(row.stat.i); - if (row.group) grHl(row.group.i); -} - -function unselect(row) { - selectedRes = undefined; - if (!row) return; - delCl(row, "selres"); - if (row.stat) stUnHl(row.stat.i); - if (row.group) grUnHl(row.group.i); -} - -function isSearchOpen() { - return $("#sres").className == "res-open"; -} - -function search(q) { - var delay = 0; - if (q == prevSearch) return; - clearTimeout(delayTimer); - prevSearch = q; - unselect(selectedRes); - if (!q) { - $('#searchinput').value = ""; - $("#sres").className = ""; - return; - } - - delayTimer = setTimeout(function() { - req("sr", "/search?q=" + q, function(c) { - var res = $("#sres"); - addCl(res, "res-open"); - res.innerHTML = ""; - for (var i = 0; i < c.length; i++) { - var e = c[i]; - var row = $$('span'); - addCl(row, "sres"); - row.innerHTML = e.n; - if (e.w) addCl(row, "res-way"); - if (e.s) { - row.stat = e.s; - addCl(row, "res-stat"); - if (e.s.s) addCl(row, "res-sugg"); - if (e.s.e) addCl(row, "res-err"); - } else { - row.group = e.g; - addCl(row, "res-group"); - if (e.g.s) addCl(row, "res-sugg"); - if (e.g.e) addCl(row, "res-err"); - } - - row.onmouseover = function(){select(this)}; - row.onclick = function(){rowClick(this)}; - - if (e.v && e.v != e.name) { - var via = $$('span'); - addCl(via, "via"); - via.innerHTML = e.v; - row.appendChild(via); - } - res.appendChild(row); - } - } - )}, delay); -} - -function keypress(e) { - if (e.keyCode == 40) { - var sels = $('.selres') - if (sels.length) select(sels[0].nextSibling); - else select($('.sres')[0]); - e.preventDefault(); - } else if (e.keyCode == 38) { - var sels = $('.selres') - if (sels.length) { - if (sels[0].previousSibling) select(sels[0].previousSibling); - else unselect(sels[0]); - e.preventDefault(); - } - } - - if (e.keyCode == 13) { - var sels = $('.selres'); - if (sels.length) rowClick(sels[0]); - } -} - -$('#del').onclick = function() {search();} - +/* + (c) 2014, Vladimir Agafonkin + simpleheat, a tiny JavaScript library for drawing heatmaps with Canvas + https://github.com/mourner/simpleheat +*/ +!function(){"use strict";function t(i){return this instanceof t?(this._canvas=i="string"==typeof i?document.getElementById(i):i,this._ctx=i.getContext("2d"),this._width=i.width,this._height=i.height,this._max=1,void this.clear()):new t(i)}t.prototype={defaultRadius:25,defaultGradient:{.4:"blue",.6:"cyan",.7:"lime",.8:"yellow",1:"red"},data:function(t,i){return this._data=t,this},max:function(t){return this._max=t,this},add:function(t){return this._data.push(t),this},clear:function(){return this._data=[],this},radius:function(t,i){i=i||15;var a=this._circle=document.createElement("canvas"),s=a.getContext("2d"),e=this._r=t+i;return a.width=a.height=2*e,s.shadowOffsetX=s.shadowOffsetY=200,s.shadowBlur=i,s.shadowColor="black",s.beginPath(),s.arc(e-200,e-200,t,0,2*Math.PI,!0),s.closePath(),s.fill(),this},gradient:function(t){var i=document.createElement("canvas"),a=i.getContext("2d"),s=a.createLinearGradient(0,0,0,256);i.width=1,i.height=256;for(var e in t)s.addColorStop(e,t[e]);return a.fillStyle=s,a.fillRect(0,0,1,256),this._grad=a.getImageData(0,0,1,256).data,this},draw:function(t){this._circle||this.radius(this.defaultRadius),this._grad||this.gradient(this.defaultGradient);var i=this._ctx;i.clearRect(0,0,this._width,this._height);for(var a,s=0,e=this._data.length;e>s;s++)a=this._data[s],i.globalAlpha=Math.max(a[2]/this._max,t||.05),i.drawImage(this._circle,a[0]-this._r,a[1]-this._r);var n=i.getImageData(0,0,this._width,this._height);return this._colorize(n.data,this._grad),i.putImageData(n,0,0),this},_colorize:function(t,i){for(var a,s=3,e=t.length;e>s;s+=4)a=4*t[s],a&&(t[s-3]=i[a],t[s-2]=i[a+1],t[s-1]=i[a+2])}},window.simpleheat=t}(),/* + (c) 2014, Vladimir Agafonkin + Leaflet.heat, a tiny and fast heatmap plugin for Leaflet. + https://github.com/Leaflet/Leaflet.heat +*/ +L.HeatLayer=(L.Layer?L.Layer:L.Class).extend({initialize:function(t,i){this._latlngs=t,L.setOptions(this,i)},setLatLngs:function(t){return this._latlngs=t,this.redraw()},addLatLng:function(t){return this._latlngs.push(t),this.redraw()},setOptions:function(t){return L.setOptions(this,t),this._heat&&this._updateOptions(),this.redraw()},redraw:function(){return!this._heat||this._frame||this._map._animating||(this._frame=L.Util.requestAnimFrame(this._redraw,this)),this},onAdd:function(t){this._map=t,this._canvas||this._initCanvas(),t._panes.overlayPane.appendChild(this._canvas),t.on("moveend",this._reset,this),t.options.zoomAnimation&&L.Browser.any3d&&t.on("zoomanim",this._animateZoom,this),this._reset()},onRemove:function(t){t.getPanes().overlayPane.removeChild(this._canvas),t.off("moveend",this._reset,this),t.options.zoomAnimation&&t.off("zoomanim",this._animateZoom,this)},addTo:function(t){return t.addLayer(this),this},_initCanvas:function(){var t=this._canvas=L.DomUtil.create("canvas","leaflet-heatmap-layer leaflet-layer"),i=L.DomUtil.testProp(["transformOrigin","WebkitTransformOrigin","msTransformOrigin"]);t.style[i]="50% 50%";var a=this._map.getSize();t.width=a.x,t.height=a.y;var s=this._map.options.zoomAnimation&&L.Browser.any3d;L.DomUtil.addClass(t,"leaflet-zoom-"+(s?"animated":"hide")),this._heat=simpleheat(t),this._updateOptions()},_updateOptions:function(){this._heat.radius(this.options.radius||this._heat.defaultRadius,this.options.blur),this.options.gradient&&this._heat.gradient(this.options.gradient),this.options.max&&this._heat.max(this.options.max)},_reset:function(){var t=this._map.containerPointToLayerPoint([0,0]);L.DomUtil.setPosition(this._canvas,t);var i=this._map.getSize();this._heat._width!==i.x&&(this._canvas.width=this._heat._width=i.x),this._heat._height!==i.y&&(this._canvas.height=this._heat._height=i.y),this._redraw()},_redraw:function(){var t,i,a,s,e,n,h,o,r,d=[],_=this._heat._r,l=this._map.getSize(),m=new L.Bounds(L.point([-_,-_]),l.add([_,_])),c=void 0===this.options.max?1:this.options.max,u=void 0===this.options.maxZoom?this._map.getMaxZoom():this.options.maxZoom,f=1/Math.pow(2,Math.max(0,Math.min(u-this._map.getZoom(),12))),g=_/2,p=[],v=this._map._getMapPanePos(),w=v.x%g,y=v.y%g;for(t=0,i=this._latlngs.length;i>t;t++)if(a=this._map.latLngToContainerPoint(this._latlngs[t]),m.contains(a)){e=Math.floor((a.x-w)/g)+2,n=Math.floor((a.y-y)/g)+2;var x=void 0!==this._latlngs[t].alt?this._latlngs[t].alt:void 0!==this._latlngs[t][2]?+this._latlngs[t][2]:1;r=x*f,p[n]=p[n]||[],s=p[n][e],s?(s[0]=(s[0]*s[2]+a.x*r)/(s[2]+r),s[1]=(s[1]*s[2]+a.y*r)/(s[2]+r),s[2]+=r):p[n][e]=[a.x,a.y,r]}for(t=0,i=p.length;i>t;t++)if(p[t])for(h=0,o=p[t].length;o>h;h++)s=p[t][h],s&&d.push([Math.round(s[0]),Math.round(s[1]),Math.min(s[2],c)]);this._heat.data(d).draw(this.options.minOpacity),this._frame=null},_animateZoom:function(t){var i=this._map.getZoomScale(t.zoom),a=this._map._getCenterOffset(t.center)._multiplyBy(-i).subtract(this._map._getMapPanePos());L.DomUtil.setTransform?L.DomUtil.setTransform(this._canvas,a,i):this._canvas.style[L.DomUtil.TRANSFORM]=L.DomUtil.getTranslateString(a)+" scale("+i+")"}}),L.heatLayer=function(t,i){return new L.HeatLayer(t,i)};var widths = [12, 13, 13, 13, 13, 13, 13, 13]; +var opas = [0.8, 0.6, 0.5, 0.5, 0.4]; +var mwidths = [1, 1, 1, 1.5, 2, 3, 5, 6, 6, 4, 3, 2]; +var stCols = ['#78f378', '#0000c3', 'red']; + +var osmUrl = "//www.openstreetmap.org/"; + +var grIdx, stIdx, selectedRes, prevSearch, delayTimer; + +var reqs = {}; + +var openedGr = -1; +var openedSt = -1; + +var sgMvOrNew = "Move into a new relation public_transport=stop_area."; +var sgMvOrEx = "Move into relation ${toid}."; +var sgMvRelNew = "Move from relation ${ooid} into a new relation public_transport=stop_area."; +var sgMvRelRel = "Move from relation ${ooid} into relation ${toid}."; +var sgMvOutRel = "Move out of relation ${ooid}"; +var sgFixAttr = "Fix attribute ${attr}."; +var sgAddName = "Consider adding a name attribute."; +var sgAttrTr = "Attribute ${attr} seems to be a track number. Use ref for this and set ${attr} to the station name."; + +var suggsMsg = [sgMvOrNew, sgMvOrEx, sgMvRelNew, sgMvRelRel, sgMvOutRel, sgFixAttr, sgAddName, sgAttrTr]; + +function $(a){return a[0] == "#" ? document.getElementById(a.substr(1)) : a[0] == "." ? document.getElementsByClassName(a.substr(1)) : document.getElementsByTagName(a)} +function $$(t){return document.createElement(t) } +function ll(g){return {"lat" : g[0], "lng" : g[1]}} +function hasCl(e, c){return e.className.split(" ").indexOf(c) != -1} +function addCl(e, c){if (!hasCl(e, c)) e.className += " " + c;e.className = e.className.trim()} +function delCl(e, c){var a = e.className.split(" "); delete a[a.indexOf(c)]; e.className = a.join(" ").trim()} +function stCol(s){return s.e ? stCols[2] : s.s ? stCols[1] : stCols[0]} +function tmpl(s, r){for (p in r) s = s.replace(new RegExp("\\${" + p + "}", "g"), r[p]); return s} +function req(id, u, cb) { + if (reqs[id]) reqs[id].abort(); + reqs[id] = new XMLHttpRequest(); + reqs[id].onreadystatechange = function() { if (this.readyState == 4 && this.status == 200 && this == reqs[id]) cb(JSON.parse(this.responseText))}; + reqs[id].open("GET", u, 1); + reqs[id].send(); +} + +function marker(stat, z) { + if (stat.g.length == 1) { + if (z > 15) { + return L.circle( + stat.g[0], { + color: '#000', + fillColor: stCol(stat), + radius: mwidths[23 - z], + fillOpacity: 1, + weight: z > 17 ? 1.5 : 1, + id: stat.i + } + ); + } else { + return L.polyline( + [stat.g[0], stat.g[0]], { + color: stCol(stat), + fillColor: stCol(stat), + weight: widths[15 - z], + opacity: opas[15 - z], + id: stat.i + } + ); + } + } else { + return L.polygon( + stat.g, { + color: z > 15 ? '#000': stCol(stat), + fillColor: stCol(stat), + smoothFactor: 0, + fillOpacity: 0.75, + weight: z > 17 ? 1.5 : 1, + id: stat.i + } + ); + } +} + +function poly(group, z) { + var col = group.e ? 'red' : group.s ? '#0000c3' : '#85f385'; + var style = { + color: col, + fillColor: col, + smoothFactor: 0.4, + fillOpacity: 0.2, + id: group.i + }; + if (z < 16) { + style.weight = 11; + style.opacity = 0.5; + style.fillOpacity = 0.5; + } + return L.polygon(group.g, style) +} + +function sugArr(sug, z) { + return L.polyline(sug.a, { + id: sug.i, + color: '#0000c3', + smoothFactor: 0.1, + weight: 4, + opacity: 0.5 + }); +} + +function rndrSt(stat) { + openedSt = stat.id; + stHl(stat.id); + var attrrows = {}; + + var way = stat.osmid < 0; + var osmid = Math.abs(stat.osmid); + var ident = way ? "Way" : "Node"; + + var con = $$('div'); + con.setAttribute("id", "nav") + + var suggD = $$('div'); + suggD.setAttribute("id", "sugg") + + con.innerHTML = ident + " " + osmid + ""; + + if (stat.attrs.name) con.innerHTML += " (\"" + stat.attrs.name + "\")"; + + con.innerHTML += ""; + + var attrTbl = $$('table'); + attrTbl.setAttribute("id", "attr-tbl") + con.appendChild(attrTbl); + con.appendChild(suggD); + + var tbody = $$('tbody'); + attrTbl.appendChild(tbody); + + for (var key in stat.attrs) { + var row = $$('tr'); + var col1 = $$('td'); + var col2 = $$('td'); + addCl(col2, "err-wrap"); + tbody.appendChild(row); + row.appendChild(col1); + row.appendChild(col2); + col1.innerHTML = "" + key + ""; + for (var i = 0; i < stat.attrs[key].length; i++) col2.innerHTML += "" + stat.attrs[key][i] + "" + "
"; + attrrows[key] = row; + } + + for (var i = 0; i < stat.attrerrs.length; i++) { + var err = stat.attrerrs[i]; + var row = attrrows[err.attr[0]]; + addCl(row, "err-" + Math.round(err.conf * 10)); + + var info = $$('div'); + + if (err.other_grp) { + // the mismatch was with a group name + if (err.other_osmid > 1) info.innerHTML = "Does not match " + err.other_attr[0] + " in relation " + Math.abs(err.other_osmid) + ""; + else info.innerHTML = "Does not match " + err.other_attr[0] + " in relation " + Math.abs(err.other_osmid) + ""; + } else { + // the mismatch was with another station + if (err.other_osmid != stat.osmid) { + var ident = err.other_osmid < 0 ? "way" : "node"; + info.innerHTML = "Does not match " + err.other_attr[0] + " in " + ident + " " + Math.abs(err.other_osmid) + ""; + } else { + info.innerHTML = "Does not match " + err.other_attr[0] + " = '" + err.other_attr[1] + "'"; + } + } + addCl(info, 'attr-err-info'); + row.childNodes[1].appendChild(info); + } + + var suggList = $$('ul'); + + if (stat.su.length) { + var a = $$('span'); + addCl(a, "sugtit"); + a.innerHTML = "Suggestions"; + suggD.appendChild(a); + } + + suggD.appendChild(suggList); + + for (var i = 0; i < stat.su.length; i++) { + var sg = stat.su[i]; + var sgDiv = $$('li'); + sgDiv.innerHTML = tmpl(suggsMsg[sg.type - 1], {"attr" : sg.attr, "tid" : sg.target_gid, "ooid" : sg.orig_osm_rel_id, "toid" : sg.target_osm_rel_id, "oid" : sg.orig_gid}); + suggList.appendChild(sgDiv); + } + + L.popup({opacity: 0.8}) + .setLatLng(stat) + .setContent(con) + .openOn(map) + .on('remove', function() {openedSt = -1; stUnHl(stat.id);}); +} + +function openSt(id) {req("s", "/stat?id=" + id, function(c) {rndrSt(c)});} + +function rndrGr(grp, ll) { + openedGr = grp.id; + var attrrows = {}; + grHl(grp.id); + + var con = $$('div'); + con.setAttribute("id", "nav"); + + var newMembers = $$('div'); + newMembers.setAttribute("id", "group-stations-new") + newMembers.innerHTML = "New Members"; + + var oldMembers = $$('div'); + oldMembers.setAttribute("id", "group-stations-old") + oldMembers.innerHTML = "Existing Members"; + + if (grp.osmid == 1) { + con.innerHTML = "New relation public_transport=stop_area"; + } else { + con.innerHTML = "OSM relation " + grp.osmid + ""; + + if (grp.attrs.name) con.innerHTML += " (\"" + grp.attrs.name + "\")"; + + con.innerHTML += ""; + } + + var attrTbl = $$('table'); + attrTbl.setAttribute("id", "attr-tbl") + con.appendChild(attrTbl); + + var tbody = $$('tbody'); + attrTbl.appendChild(tbody); + + var suggD = $$('div'); + suggD.setAttribute("id", "sugg") + + for (var key in grp.attrs) { + var row = $$('tr'); + var col1 = $$('td'); + var col2 = $$('td'); + addCl(col2, "err-wrap"); + tbody.appendChild(row); + row.appendChild(col1); + row.appendChild(col2); + col1.innerHTML = "" + key + ""; + for (var i = 0; i < grp.attrs[key].length; i++) col2.innerHTML += "" + grp.attrs[key][i] + "" + "
"; + attrrows[key] = row; + } + + for (var i = 0; i < grp.attrerrs.length; i++) { + var err = grp.attrerrs[i]; + var row = attrrows[err.attr[0]]; + addCl(row, "err-" + Math.round(err.conf * 10)); + + var info = $$('div'); + + if (err.other_grp) { + // the mismatch was with a group name + if (err.other_osmid != grp.osmid) { + if (err.other_osmid > 1) info.innerHTML = "Does not match " + err.other_attr[0] + " in relation " + Math.abs(err.other_osmid) + ""; + else info.innerHTML = "Does not match " + err.other_attr[0] + " in relation " + Math.abs(err.other_osmid) + ""; + } else info.innerHTML = "Does not match " + err.other_attr[0] + " = '" + err.other_attr[1] + "'"; + } else { + // the mismatch was with another station + var ident = err.other_osmid < 0 ? "way" : "node"; + info.innerHTML = "Does not match " + err.other_attr[0] + " in " + ident + " " + Math.abs(err.other_osmid) + ""; + } + + addCl(info, 'attr-err-info'); + row.childNodes[1].appendChild(info); + } + + con.appendChild(newMembers); + if (grp.osmid != 1) con.appendChild(oldMembers); + + for (var key in grp.stations) { + var stat = grp.stations[key]; + var row = $$('div'); + var ident = stat.osmid < 0 ? "Way" : "Node"; + + row.innerHTML = ident + " " + Math.abs(stat.osmid) + ""; + + if (stat.attrs.name) row.innerHTML += " (\"" + stat.attrs.name + "\")"; + + row.style.backgroundColor = stat.e ? '#f58d8d' : stat.s ? '#b6b6e4' : '#c0f7c0'; + + if (grp.osmid == 1 || stat.orig_group != grp.id) newMembers.appendChild(row); + else { + oldMembers.appendChild(row); + if (stat.group != grp.id) addCl(row, "del-stat"); + } + } + + var suggList = $$('ul'); + + if (grp.su.length) { + var a = $$('span'); + addCl(a, "sugtit"); + a.innerHTML = "Suggestions"; + suggD.appendChild(a); + } + + suggD.appendChild(suggList); + + for (var i = 0; i < grp.su.length; i++) { + var sugg = grp.su[i]; + var suggDiv = $$('li'); + + if (sugg.type == 6) suggDiv.innerHTML = "Fix attribute " + sugg.attr + "."; + else if (sugg.type == 7) suggDiv.innerHTML = "Consider adding a name attribute."; + else if (sugg.type == 8) suggDiv.innerHTML = "Attribute " + sugg.attr + " seems to be a track number. Use ref for this and set " + sugg.attr + " to the station name."; + + suggList.appendChild(suggDiv); + } + + con.appendChild(suggD); + + L.popup({opacity: 0.8}) + .setLatLng(ll) + .setContent(con) + .openOn(map) + .on('remove', function() {openedGr = -1; grUnHl(grp.id)}); +} + +function openGr(id, ll) { + req("g", "/group?id=" + id, function(c) {rndrGr(c, ll)}); +} + +function grHl(id) { + !grIdx[id] || grIdx[id].setStyle({'weight': 6, 'color': "#eecc00"}); +} + +function grUnHl(id) { + !grIdx[id] || grIdx[id].setStyle({ + 'weight': 3, + 'color': grIdx[id].options["fillColor"] + }); +} + +function stHl(id) { + if (!stIdx[id]) return; + + if (map.getZoom() > 15) { + stIdx[id].setStyle({ + 'weight': 5, + 'color': "#eecc00" + }); + } else { + stIdx[id].setStyle({ + 'color': "#eecc00" + }); + } +} + +function stUnHl(id) { + if (!stIdx[id]) return; + + if (map.getZoom() > 15) { + stIdx[id].setStyle({ + 'weight': map.getZoom() > 17 ? 1.5 : 1, + 'color': "black" + }); + } else { + stIdx[id].setStyle({ + 'color': stIdx[id].options["fillColor"] + }); + } +} + +var map = L.map('map', {renderer: L.canvas(), attributionControl: false}).setView([47.9965, 7.8469], 13); + +map.addControl(L.control.attribution({ + position: 'bottomright', + prefix: '© University of Freiburg, Chair of Algorithms and Data Structures' +})); + +map.on('popupopen', function(e) { + var px = map.project(e.target._popup._latlng); + px.y -= e.target._popup._container.clientHeight/2; + map.panTo(map.unproject(px),{animate: true}); + search(); +}); + +L.tileLayer('http://{s}.tile.stamen.com/toner-lite/{z}/{x}/{y}.png', { + maxZoom: 20, + attribution: '© OpenStreetMap', + opacity: 0.8 +}).addTo(map); + +var l = L.featureGroup().addTo(map); + +map.on("moveend", function() {render();}); +map.on("click", function() {search()}); + +function render() { + if (map.getZoom() < 11) { + req("m", "/heatmap?z=" + map.getZoom() + "&bbox=" + [map.getBounds().getSouthWest().lat, map.getBounds().getSouthWest().lng, map.getBounds().getNorthEast().lat, map.getBounds().getNorthEast().lng].join(","), + function(re) { + l.clearLayers(); + + var blur = 22 - map.getZoom(); + var rad = 25 - map.getZoom(); + + l.addLayer(L.heatLayer(re.ok, { + max: 500, + gradient: { + 0: '#cbf7cb', + 0.5: '#78f378', + 1: '#29c329' + }, + minOpacity: 0.65, + blur: blur, + radius: rad + })); + l.addLayer(L.heatLayer(re.sugg, { + max: 500, + gradient: { + 0: '#7f7fbd', + 0.5: '#4444b3', + 1: '#0606c1' + }, + minOpacity: 0.65, + blur: blur - 3, + radius: Math.min(12, rad - 3) + })); + l.addLayer(L.heatLayer(re.err, { + max: 500, + gradient: { + 0: '#f39191', + 0.5: '#ff5656', + 1: '#ff0000' + }, + minOpacity: 0.75, + blur: blur - 3, + radius: Math.min(10, rad - 3), + maxZoom: 15 + })); + } + ) + } else { + req("m", "/map?z=" + map.getZoom() + "&bbox=" + [map.getBounds().getSouthWest().lat, map.getBounds().getSouthWest().lng, map.getBounds().getNorthEast().lat, map.getBounds().getNorthEast().lng].join(","), + function(re) { + l.clearLayers(); + grIdx = {}; + stIdx = {}; + + var stats = []; + for (var i = 0; i < re.stats.length; i++) { + stIdx[re.stats[i].i] = stats[stats.push(marker(re.stats[i], map.getZoom())) - 1]; + } + + var groups = []; + for (var i = 0; i < re.groups.length; i++) { + grIdx[re.groups[i].i] = groups[groups.push(poly(re.groups[i], map.getZoom())) - 1];; + } + + var suggs = []; + for (var i = 0; i < re.su.length; i++) { + suggs.push(sugArr(re.su[i], map.getZoom())); + } + + if (map.getZoom() > 13) { + l.addLayer(L.featureGroup(groups).on('click', function(a) { + openGr(a.layer.options.id, a.layer.getBounds().getCenter()); + })); + } + + l.addLayer(L.featureGroup(stats).on('click', function(a) { + openSt(a.layer.options.id); + })); + + if (map.getZoom() > 15) { + l.addLayer(L.featureGroup(suggs).on('click', function(a) { + openSt(a.layer.options.id); + })); + } + + grHl(openedGr); + stHl(openedSt); + } + ) + }; +} + +function rowClick(row) { + if (!isSearchOpen()) return; + if (row.stat) openSt(row.stat.i, ll(row.stat.g[0])); + else openGr(row.group.i, ll(row.group.g[0])); +} + +function select(row) { + if (!row) return; + if (!isSearchOpen()) return; + unselect(selectedRes); + selectedRes = row; + addCl(row, "selres"); + if (row.stat) stHl(row.stat.i); + if (row.group) grHl(row.group.i); +} + +function unselect(row) { + selectedRes = undefined; + if (!row) return; + delCl(row, "selres"); + if (row.stat) stUnHl(row.stat.i); + if (row.group) grUnHl(row.group.i); +} + +function isSearchOpen() { + return $("#sres").className == "res-open"; +} + +function search(q) { + var delay = 0; + if (q == prevSearch) return; + clearTimeout(delayTimer); + prevSearch = q; + unselect(selectedRes); + if (!q) { + $('#searchinput').value = ""; + $("#sres").className = ""; + return; + } + + delayTimer = setTimeout(function() { + req("sr", "/search?q=" + q, function(c) { + var res = $("#sres"); + addCl(res, "res-open"); + res.innerHTML = ""; + for (var i = 0; i < c.length; i++) { + var e = c[i]; + var row = $$('span'); + addCl(row, "sres"); + row.innerHTML = e.n; + if (e.w) addCl(row, "res-way"); + if (e.s) { + row.stat = e.s; + addCl(row, "res-stat"); + if (e.s.s) addCl(row, "res-sugg"); + if (e.s.e) addCl(row, "res-err"); + } else { + row.group = e.g; + addCl(row, "res-group"); + if (e.g.s) addCl(row, "res-sugg"); + if (e.g.e) addCl(row, "res-err"); + } + + row.onmouseover = function(){select(this)}; + row.onclick = function(){rowClick(this)}; + + if (e.v && e.v != e.name) { + var via = $$('span'); + addCl(via, "via"); + via.innerHTML = e.v; + row.appendChild(via); + } + res.appendChild(row); + } + } + )}, delay); +} + +function keypress(e) { + if (e.keyCode == 40) { + var sels = $('.selres') + if (sels.length) select(sels[0].nextSibling); + else select($('.sres')[0]); + e.preventDefault(); + } else if (e.keyCode == 38) { + var sels = $('.selres') + if (sels.length) { + if (sels[0].previousSibling) select(sels[0].previousSibling); + else unselect(sels[0]); + e.preventDefault(); + } + } + + if (e.keyCode == 13) { + var sels = $('.selres'); + if (sels.length) rowClick(sels[0]); + } +} + +$('#del').onclick = function() {search();} + render(); \ No newline at end of file diff --git a/web/merged.js b/web/merged.js index d501608..1f236a4 100644 --- a/web/merged.js +++ b/web/merged.js @@ -1,229 +1,229 @@ -/* @preserve - * Leaflet 1.8.0, a JS library for interactive maps. https://leafletjs.com - * (c) 2010-2022 Vladimir Agafonkin, (c) 2010-2011 CloudMade - */ -!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?i(exports):"function"==typeof define&&define.amd?define(["exports"],i):i((t="undefined"!=typeof globalThis?globalThis:t||self).leaflet={})}(this,function(t){"use strict";function l(t){for(var i,e,n=1,o=arguments.length;n=this.min.x&&e.x<=this.max.x&&i.y>=this.min.y&&e.y<=this.max.y},intersects:function(t){t=f(t);var i=this.min,e=this.max,n=t.min,t=t.max,o=t.x>=i.x&&n.x<=e.x,t=t.y>=i.y&&n.y<=e.y;return o&&t},overlaps:function(t){t=f(t);var i=this.min,e=this.max,n=t.min,t=t.max,o=t.x>i.x&&n.xi.y&&n.y=n.lat&&e.lat<=o.lat&&i.lng>=n.lng&&e.lng<=o.lng},intersects:function(t){t=g(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),t=t.getNorthEast(),o=t.lat>=i.lat&&n.lat<=e.lat,t=t.lng>=i.lng&&n.lng<=e.lng;return o&&t},overlaps:function(t){t=g(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),t=t.getNorthEast(),o=t.lat>i.lat&&n.lati.lng&&n.lng","http://www.w3.org/2000/svg"===(Wt.firstChild&&Wt.firstChild.namespaceURI));function y(t){return 0<=navigator.userAgent.toLowerCase().indexOf(t)}var P={ie:pt,ielt9:mt,edge:n,webkit:ft,android:gt,android23:vt,androidStock:yt,opera:xt,chrome:wt,gecko:Pt,safari:bt,phantom:Lt,opera12:o,win:Tt,ie3d:zt,webkit3d:Mt,gecko3d:_t,any3d:Ct,mobile:Zt,mobileWebkit:St,mobileWebkit3d:kt,msPointer:Et,pointer:Bt,touch:It,touchNative:At,mobileOpera:Ot,mobileGecko:Rt,retina:Nt,passiveEvents:Dt,canvas:jt,svg:Ht,vml:!Ht&&function(){try{var t=document.createElement("div"),i=(t.innerHTML='',t.firstChild);return i.style.behavior="url(#default#VML)",i&&"object"==typeof i.adj}catch(t){return!1}}(),inlineSvg:Wt},Ft=P.msPointer?"MSPointerDown":"pointerdown",Ut=P.msPointer?"MSPointerMove":"pointermove",Vt=P.msPointer?"MSPointerUp":"pointerup",qt=P.msPointer?"MSPointerCancel":"pointercancel",Gt={touchstart:Ft,touchmove:Ut,touchend:Vt,touchcancel:qt},Kt={touchstart:function(t,i){i.MSPOINTER_TYPE_TOUCH&&i.pointerType===i.MSPOINTER_TYPE_TOUCH&&B(i);ii(t,i)},touchmove:ii,touchend:ii,touchcancel:ii},Yt={},Xt=!1;function Jt(t,i,e){return"touchstart"!==i||Xt||(document.addEventListener(Ft,$t,!0),document.addEventListener(Ut,Qt,!0),document.addEventListener(Vt,ti,!0),document.addEventListener(qt,ti,!0),Xt=!0),Kt[i]?(e=Kt[i].bind(this,e),t.addEventListener(Gt[i],e,!1),e):(console.warn("wrong event specified:",i),L.Util.falseFn)}function $t(t){Yt[t.pointerId]=t}function Qt(t){Yt[t.pointerId]&&(Yt[t.pointerId]=t)}function ti(t){delete Yt[t.pointerId]}function ii(t,i){if(i.pointerType!==(i.MSPOINTER_TYPE_MOUSE||"mouse")){for(var e in i.touches=[],Yt)i.touches.push(Yt[e]);i.changedTouches=[i],t(i)}}var ei=200;function ni(t,e){t.addEventListener("dblclick",e);var n,o=0;function i(t){var i;1!==t.detail?n=t.detail:"mouse"===t.pointerType||t.sourceCapabilities&&!t.sourceCapabilities.firesTouchEvents||((i=Date.now())-o<=ei?2===++n&&e(function(t){var i,e,n={};for(e in t)i=t[e],n[e]=i&&i.bind?i.bind(t):i;return(t=n).type="dblclick",n.detail=2,n.isTrusted=!1,n._simulated=!0,n}(t)):n=1,o=i)}return t.addEventListener("click",i),{dblclick:e,simDblclick:i}}var oi,si,ri,ai,hi,li,ui=wi(["transform","webkitTransform","OTransform","MozTransform","msTransform"]),ci=wi(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),di="webkitTransition"===ci||"OTransition"===ci?ci+"End":"transitionend";function _i(t){return"string"==typeof t?document.getElementById(t):t}function pi(t,i){var e=t.style[i]||t.currentStyle&&t.currentStyle[i];return"auto"===(e=e&&"auto"!==e||!document.defaultView?e:(t=document.defaultView.getComputedStyle(t,null))?t[i]:null)?null:e}function b(t,i,e){t=document.createElement(t);return t.className=i||"",e&&e.appendChild(t),t}function T(t){var i=t.parentNode;i&&i.removeChild(t)}function mi(t){for(;t.firstChild;)t.removeChild(t.firstChild)}function fi(t){var i=t.parentNode;i&&i.lastChild!==t&&i.appendChild(t)}function gi(t){var i=t.parentNode;i&&i.firstChild!==t&&i.insertBefore(t,i.firstChild)}function vi(t,i){if(void 0!==t.classList)return t.classList.contains(i);t=xi(t);return 0this.options.maxZoom)?this.setZoom(t):this},panInsideBounds:function(t,i){this._enforcingBounds=!0;var e=this.getCenter(),t=this._limitCenter(e,this._zoom,g(t));return e.equals(t)||this.panTo(t,i),this._enforcingBounds=!1,this},panInside:function(t,i){var e=_((i=i||{}).paddingTopLeft||i.padding||[0,0]),n=_(i.paddingBottomRight||i.padding||[0,0]),o=this.project(this.getCenter()),t=this.project(t),s=this.getPixelBounds(),e=f([s.min.add(e),s.max.subtract(n)]),s=e.getSize();return e.contains(t)||(this._enforcingBounds=!0,n=t.subtract(e.getCenter()),e=e.extend(t).getSize().subtract(s),o.x+=n.x<0?-e.x:e.x,o.y+=n.y<0?-e.y:e.y,this.panTo(this.unproject(o),i),this._enforcingBounds=!1),this},invalidateSize:function(t){if(!this._loaded)return this;t=l({animate:!1,pan:!0},!0===t?{animate:!0}:t);var i=this.getSize(),e=(this._sizeChanged=!0,this._lastCenter=null,this.getSize()),n=i.divideBy(2).round(),o=e.divideBy(2).round(),n=n.subtract(o);return n.x||n.y?(t.animate&&t.pan?this.panBy(n):(t.pan&&this._rawPanBy(n),this.fire("move"),t.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(a(this.fire,this,"moveend"),200)):this.fire("moveend")),this.fire("resize",{oldSize:i,newSize:e})):this},stop:function(){return this.setZoom(this._limitZoom(this._zoom)),this.options.zoomSnap||this.fire("viewreset"),this._stop()},locate:function(t){if(t=this._locateOptions=l({timeout:1e4,watch:!1},t),!("geolocation"in navigator))return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var i=a(this._handleGeolocationResponse,this),e=a(this._handleGeolocationError,this);return t.watch?this._locationWatchId=navigator.geolocation.watchPosition(i,e,t):navigator.geolocation.getCurrentPosition(i,e,t),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var i;this._container._leaflet_id&&(i=t.code,t=t.message||(1===i?"permission denied":2===i?"position unavailable":"timeout"),this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:i,message:"Geolocation error: "+t+"."}))},_handleGeolocationResponse:function(t){if(this._container._leaflet_id){var i,e,n=new v(t.coords.latitude,t.coords.longitude),o=n.toBounds(2*t.coords.accuracy),s=this._locateOptions,r=(s.setView&&(i=this.getBoundsZoom(o),this.setView(n,s.maxZoom?Math.min(i,s.maxZoom):i)),{latlng:n,bounds:o,timestamp:t.timestamp});for(e in t.coords)"number"==typeof t.coords[e]&&(r[e]=t.coords[e]);this.fire("locationfound",r)}},addHandler:function(t,i){if(!i)return this;i=this[t]=new i(this);return this._handlers.push(i),this.options[t]&&i.enable(),this},remove:function(){if(this._initEvents(!0),this.options.maxBounds&&this.off("moveend",this._panInsideMaxBounds),this._containerId!==this._container._leaflet_id)throw new Error("Map container is being reused by another instance");try{delete this._container._leaflet_id,delete this._containerId}catch(t){this._container._leaflet_id=void 0,this._containerId=void 0}for(var t in void 0!==this._locationWatchId&&this.stopLocate(),this._stop(),T(this._mapPane),this._clearControlPos&&this._clearControlPos(),this._resizeRequest&&(r(this._resizeRequest),this._resizeRequest=null),this._clearHandlers(),this._loaded&&this.fire("unload"),this._layers)this._layers[t].remove();for(t in this._panes)T(this._panes[t]);return this._layers=[],this._panes=[],delete this._mapPane,delete this._renderer,this},createPane:function(t,i){i=b("div","leaflet-pane"+(t?" leaflet-"+t.replace("Pane","")+"-pane":""),i||this._mapPane);return t&&(this._panes[t]=i),i},getCenter:function(){return this._checkIfLoaded(),this._lastCenter&&!this._moved()?this._lastCenter:this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds();return new s(this.unproject(t.getBottomLeft()),this.unproject(t.getTopRight()))},getMinZoom:function(){return void 0===this.options.minZoom?this._layersMinZoom||0:this.options.minZoom},getMaxZoom:function(){return void 0===this.options.maxZoom?void 0===this._layersMaxZoom?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(t,i,e){t=g(t),e=_(e||[0,0]);var n=this.getZoom()||0,o=this.getMinZoom(),s=this.getMaxZoom(),r=t.getNorthWest(),t=t.getSouthEast(),e=this.getSize().subtract(e),t=f(this.project(t,n),this.project(r,n)).getSize(),r=P.any3d?this.options.zoomSnap:1,a=e.x/t.x,e=e.y/t.y,t=i?Math.max(a,e):Math.min(a,e),n=this.getScaleZoom(t,n);return r&&(n=Math.round(n/(r/100))*(r/100),n=i?Math.ceil(n/r)*r:Math.floor(n/r)*r),Math.max(o,Math.min(s,n))},getSize:function(){return this._size&&!this._sizeChanged||(this._size=new p(this._container.clientWidth||0,this._container.clientHeight||0),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(t,i){t=this._getTopLeftPoint(t,i);return new m(t,t.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._pixelOrigin},getPixelWorldBounds:function(t){return this.options.crs.getProjectedBounds(void 0===t?this.getZoom():t)},getPane:function(t){return"string"==typeof t?this._panes[t]:t},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t,i){var e=this.options.crs;return i=void 0===i?this._zoom:i,e.scale(t)/e.scale(i)},getScaleZoom:function(t,i){var e=this.options.crs,t=(i=void 0===i?this._zoom:i,e.zoom(t*e.scale(i)));return isNaN(t)?1/0:t},project:function(t,i){return i=void 0===i?this._zoom:i,this.options.crs.latLngToPoint(w(t),i)},unproject:function(t,i){return i=void 0===i?this._zoom:i,this.options.crs.pointToLatLng(_(t),i)},layerPointToLatLng:function(t){t=_(t).add(this.getPixelOrigin());return this.unproject(t)},latLngToLayerPoint:function(t){return this.project(w(t))._round()._subtract(this.getPixelOrigin())},wrapLatLng:function(t){return this.options.crs.wrapLatLng(w(t))},wrapLatLngBounds:function(t){return this.options.crs.wrapLatLngBounds(g(t))},distance:function(t,i){return this.options.crs.distance(w(t),w(i))},containerPointToLayerPoint:function(t){return _(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return _(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){t=this.containerPointToLayerPoint(_(t));return this.layerPointToLatLng(t)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(w(t)))},mouseEventToContainerPoint:function(t){return Ni(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){t=this._container=_i(t);if(!t)throw new Error("Map container not found.");if(t._leaflet_id)throw new Error("Map container is already initialized.");S(t,"scroll",this._onScroll,this),this._containerId=h(t)},_initLayout:function(){var t=this._container,i=(this._fadeAnimated=this.options.fadeAnimation&&P.any3d,z(t,"leaflet-container"+(P.touch?" leaflet-touch":"")+(P.retina?" leaflet-retina":"")+(P.ielt9?" leaflet-oldie":"")+(P.safari?" leaflet-safari":"")+(this._fadeAnimated?" leaflet-fade-anim":"")),pi(t,"position"));"absolute"!==i&&"relative"!==i&&"fixed"!==i&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._paneRenderers={},this._mapPane=this.createPane("mapPane",this._container),Z(this._mapPane,new p(0,0)),this.createPane("tilePane"),this.createPane("overlayPane"),this.createPane("shadowPane"),this.createPane("markerPane"),this.createPane("tooltipPane"),this.createPane("popupPane"),this.options.markerZoomAnimation||(z(t.markerPane,"leaflet-zoom-hide"),z(t.shadowPane,"leaflet-zoom-hide"))},_resetView:function(t,i){Z(this._mapPane,new p(0,0));var e=!this._loaded,n=(this._loaded=!0,i=this._limitZoom(i),this.fire("viewprereset"),this._zoom!==i);this._moveStart(n,!1)._move(t,i)._moveEnd(n),this.fire("viewreset"),e&&this.fire("load")},_moveStart:function(t,i){return t&&this.fire("zoomstart"),i||this.fire("movestart"),this},_move:function(t,i,e,n){void 0===i&&(i=this._zoom);var o=this._zoom!==i;return this._zoom=i,this._lastCenter=t,this._pixelOrigin=this._getNewPixelOrigin(t),n?e&&e.pinch&&this.fire("zoom",e):((o||e&&e.pinch)&&this.fire("zoom",e),this.fire("move",e)),this},_moveEnd:function(t){return t&&this.fire("zoomend"),this.fire("moveend")},_stop:function(){return r(this._flyToFrame),this._panAnim&&this._panAnim.stop(),this},_rawPanBy:function(t){Z(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_panInsideMaxBounds:function(){this._enforcingBounds||this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(t){this._targets={};var i=t?E:S;i((this._targets[h(this._container)]=this)._container,"click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress keydown keyup",this._handleDOMEvent,this),this.options.trackResize&&i(window,"resize",this._onResize,this),P.any3d&&this.options.transform3DLimit&&(t?this.off:this.on).call(this,"moveend",this._onMoveEnd)},_onResize:function(){r(this._resizeRequest),this._resizeRequest=x(function(){this.invalidateSize({debounceMoveend:!0})},this)},_onScroll:function(){this._container.scrollTop=0,this._container.scrollLeft=0},_onMoveEnd:function(){var t=this._getMapPanePos();Math.max(Math.abs(t.x),Math.abs(t.y))>=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(t,i){for(var e,n=[],o="mouseout"===i||"mouseover"===i,s=t.target||t.srcElement,r=!1;s;){if((e=this._targets[h(s)])&&("click"===i||"preclick"===i)&&this._draggableMoved(e)){r=!0;break}if(e&&e.listens(i,!0)){if(o&&!Hi(s,t))break;if(n.push(e),o)break}if(s===this._container)break;s=s.parentNode}return n=n.length||r||o||!this.listens(i,!0)?n:[this]},_isClickDisabled:function(t){for(;t!==this._container;){if(t._leaflet_disable_click)return!0;t=t.parentNode}},_handleDOMEvent:function(t){var i,e=t.target||t.srcElement;!this._loaded||e._leaflet_disable_events||"click"===t.type&&this._isClickDisabled(e)||("mousedown"===(i=t.type)&&zi(e),this._fireDOMEvent(t,i))},_mouseEvents:["click","dblclick","mouseover","mouseout","contextmenu"],_fireDOMEvent:function(t,i,e){"click"===t.type&&((a=l({},t)).type="preclick",this._fireDOMEvent(a,a.type,e));var n=this._findEventTargets(t,i);if(e){for(var o=[],s=0;sthis.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(i),n=this._getCenterOffset(t)._divideBy(1-1/n);return!(!0!==e.animate&&!this.getSize().contains(n))&&(x(function(){this._moveStart(!0,!1)._animateZoom(t,i,!0)},this),!0)},_animateZoom:function(t,i,e,n){this._mapPane&&(e&&(this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=i,z(this._mapPane,"leaflet-zoom-anim")),this.fire("zoomanim",{center:t,zoom:i,noUpdate:n}),this._tempFireZoomEvent||(this._tempFireZoomEvent=this._zoom!==this._animateToZoom),this._move(this._animateToCenter,this._animateToZoom,void 0,!0),setTimeout(a(this._onZoomTransitionEnd,this),250))},_onZoomTransitionEnd:function(){this._animatingZoom&&(this._mapPane&&M(this._mapPane,"leaflet-zoom-anim"),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom,void 0,!0),this._tempFireZoomEvent&&this.fire("zoom"),delete this._tempFireZoomEvent,this.fire("move"),this._moveEnd(!0))}});function Fi(t){return new I(t)}var Ui,I=it.extend({options:{position:"topright"},initialize:function(t){c(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var i=this._map;return i&&i.removeControl(this),this.options.position=t,i&&i.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this.remove(),this._map=t;var i=this._container=this.onAdd(t),e=this.getPosition(),t=t._controlCorners[e];return z(i,"leaflet-control"),-1!==e.indexOf("bottom")?t.insertBefore(i,t.firstChild):t.appendChild(i),this._map.on("unload",this.remove,this),this},remove:function(){return this._map&&(T(this._container),this.onRemove&&this.onRemove(this._map),this._map.off("unload",this.remove,this),this._map=null),this},_refocusOnMap:function(t){this._map&&t&&0",i=document.createElement("div");return i.innerHTML=t,i.firstChild},_addItem:function(t){var i,e=document.createElement("label"),n=this._map.hasLayer(t.layer),n=(t.overlay?((i=document.createElement("input")).type="checkbox",i.className="leaflet-control-layers-selector",i.defaultChecked=n):i=this._createRadioElement("leaflet-base-layers_"+h(this),n),this._layerControlInputs.push(i),i.layerId=h(t.layer),S(i,"click",this._onInputClick,this),document.createElement("span")),o=(n.innerHTML=" "+t.name,document.createElement("span"));return e.appendChild(o),o.appendChild(i),o.appendChild(n),(t.overlay?this._overlaysList:this._baseLayersList).appendChild(e),this._checkDisabledLayers(),e},_onInputClick:function(){var t,i,e=this._layerControlInputs,n=[],o=[];this._handlingClick=!0;for(var s=e.length-1;0<=s;s--)t=e[s],i=this._getLayer(t.layerId).layer,t.checked?n.push(i):t.checked||o.push(i);for(s=0;si.options.maxZoom},_expandIfNotCollapsed:function(){return this._map&&!this.options.collapsed&&this.expand(),this}})),qi=I.extend({options:{position:"topleft",zoomInText:'',zoomInTitle:"Zoom in",zoomOutText:'',zoomOutTitle:"Zoom out"},onAdd:function(t){var i="leaflet-control-zoom",e=b("div",i+" leaflet-bar"),n=this.options;return this._zoomInButton=this._createButton(n.zoomInText,n.zoomInTitle,i+"-in",e,this._zoomIn),this._zoomOutButton=this._createButton(n.zoomOutText,n.zoomOutTitle,i+"-out",e,this._zoomOut),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),e},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(t){!this._disabled&&this._map._zoomthis._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(t.shiftKey?3:1))},_createButton:function(t,i,e,n,o){e=b("a",e,n);return e.innerHTML=t,e.href="#",e.title=i,e.setAttribute("role","button"),e.setAttribute("aria-label",i),Oi(e),S(e,"click",Ri),S(e,"click",o,this),S(e,"click",this._refocusOnMap,this),e},_updateDisabled:function(){var t=this._map,i="leaflet-disabled";M(this._zoomInButton,i),M(this._zoomOutButton,i),this._zoomInButton.setAttribute("aria-disabled","false"),this._zoomOutButton.setAttribute("aria-disabled","false"),!this._disabled&&t._zoom!==t.getMinZoom()||(z(this._zoomOutButton,i),this._zoomOutButton.setAttribute("aria-disabled","true")),!this._disabled&&t._zoom!==t.getMaxZoom()||(z(this._zoomInButton,i),this._zoomInButton.setAttribute("aria-disabled","true"))}}),Gi=(A.mergeOptions({zoomControl:!0}),A.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new qi,this.addControl(this.zoomControl))}),I.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0},onAdd:function(t){var i="leaflet-control-scale",e=b("div",i),n=this.options;return this._addScales(n,i+"-line",e),t.on(n.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),e},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,i,e){t.metric&&(this._mScale=b("div",i,e)),t.imperial&&(this._iScale=b("div",i,e))},_update:function(){var t=this._map,i=t.getSize().y/2,t=t.distance(t.containerPointToLatLng([0,i]),t.containerPointToLatLng([this.options.maxWidth,i]));this._updateScales(t)},_updateScales:function(t){this.options.metric&&t&&this._updateMetric(t),this.options.imperial&&t&&this._updateImperial(t)},_updateMetric:function(t){var i=this._getRoundNum(t);this._updateScale(this._mScale,i<1e3?i+" m":i/1e3+" km",i/t)},_updateImperial:function(t){var i,e,t=3.2808399*t;5280'+(P.inlineSvg?' ':"")+"Leaflet"},initialize:function(t){c(this,t),this._attributions={}},onAdd:function(t){for(var i in(t.attributionControl=this)._container=b("div","leaflet-control-attribution"),Oi(this._container),t._layers)t._layers[i].getAttribution&&this.addAttribution(t._layers[i].getAttribution());return this._update(),t.on("layeradd",this._addAttribution,this),this._container},onRemove:function(t){t.off("layeradd",this._addAttribution,this)},_addAttribution:function(t){t.layer.getAttribution&&(this.addAttribution(t.layer.getAttribution()),t.layer.once("remove",function(){this.removeAttribution(t.layer.getAttribution())},this))},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t&&(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update()),this},removeAttribution:function(t){return t&&this._attributions[t]&&(this._attributions[t]--,this._update()),this},_update:function(){if(this._map){var t,i=[];for(t in this._attributions)this._attributions[t]&&i.push(t);var e=[];this.options.prefix&&e.push(this.options.prefix),i.length&&e.push(i.join(", ")),this._container.innerHTML=e.join(' ')}}}),n=(A.mergeOptions({attributionControl:!0}),A.addInitHook(function(){this.options.attributionControl&&(new Ki).addTo(this)}),I.Layers=Vi,I.Zoom=qi,I.Scale=Gi,I.Attribution=Ki,Fi.layers=function(t,i,e){return new Vi(t,i,e)},Fi.zoom=function(t){return new qi(t)},Fi.scale=function(t){return new Gi(t)},Fi.attribution=function(t){return new Ki(t)},it.extend({initialize:function(t){this._map=t},enable:function(){return this._enabled||(this._enabled=!0,this.addHooks()),this},disable:function(){return this._enabled&&(this._enabled=!1,this.removeHooks()),this},enabled:function(){return!!this._enabled}})),ft=(n.addTo=function(t,i){return t.addHandler(i,this),this},{Events:i}),Yi=P.touch?"touchstart mousedown":"mousedown",Xi=et.extend({options:{clickTolerance:3},initialize:function(t,i,e,n){c(this,n),this._element=t,this._dragStartTarget=i||t,this._preventOutline=e},enable:function(){this._enabled||(S(this._dragStartTarget,Yi,this._onDown,this),this._enabled=!0)},disable:function(){this._enabled&&(Xi._dragging===this&&this.finishDrag(!0),E(this._dragStartTarget,Yi,this._onDown,this),this._enabled=!1,this._moved=!1)},_onDown:function(t){var i,e;this._enabled&&(this._moved=!1,vi(this._element,"leaflet-zoom-anim")||(t.touches&&1!==t.touches.length?Xi._dragging===this&&this.finishDrag():Xi._dragging||t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||((Xi._dragging=this)._preventOutline&&zi(this._element),Li(),ri(),this._moving||(this.fire("down"),e=t.touches?t.touches[0]:t,i=Ci(this._element),this._startPoint=new p(e.clientX,e.clientY),this._startPos=bi(this._element),this._parentScale=Zi(i),e="mousedown"===t.type,S(document,e?"mousemove":"touchmove",this._onMove,this),S(document,e?"mouseup":"touchend touchcancel",this._onUp,this)))))},_onMove:function(t){var i;this._enabled&&(t.touches&&1i&&(e.push(t[n]),o=n);oi.max.x&&(e|=2),t.yi.max.y&&(e|=8),e}function ee(t,i,e,n){var o=i.x,i=i.y,s=e.x-o,r=e.y-i,a=s*s+r*r;return 0this._layersMaxZoom&&this.setZoom(this._layersMaxZoom),void 0===this.options.minZoom&&this._layersMinZoom&&this.getZoom()t.y!=n.y>t.y&&t.x<(n.x-e.x)*(t.y-e.y)/(n.y-e.y)+e.x&&(l=!l);return l||fe.prototype._containsPoint.call(this,t,!0)}});var ve=he.extend({initialize:function(t,i){c(this,i),this._layers={},t&&this.addData(t)},addData:function(t){var i,e,n,o=d(t)?t:t.features;if(o){for(i=0,e=o.length;ir.x&&(a=n.x+h-r.x+s.x),n.x-a-o.x<(h=0)&&(a=n.x-o.x),n.y+e+s.y>r.y&&(h=n.y+e-r.y+s.y),n.y-h-o.y<0&&(h=n.y-o.y),(a||h)&&i.fire("autopanstart").panBy([a,h],{animate:t&&"moveend"===t.type}))},_getAnchor:function(){return _(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}})),Ee=(A.mergeOptions({closePopupOnClick:!0}),A.include({openPopup:function(t,i,e){return this._initOverlay(ke,t,i,e).openOn(this),this},closePopup:function(t){return(t=arguments.length?t:this._popup)&&t.close(),this}}),o.include({bindPopup:function(t,i){return this._popup=this._initOverlay(ke,this._popup,t,i),this._popupHandlersAdded||(this.on({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t){return this._popup&&this._popup._prepareOpen(t)&&this._popup.openOn(this._map),this},closePopup:function(){return this._popup&&this._popup.close(),this},togglePopup:function(){return this._popup&&this._popup.toggle(this),this},isPopupOpen:function(){return!!this._popup&&this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var i;this._popup&&this._map&&(Ri(t),i=t.layer||t.target,this._popup._source!==i||i instanceof _e?(this._popup._source=i,this.openPopup(t.latlng)):this._map.hasLayer(this._popup)?this.closePopup():this.openPopup(t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)},_onKeyPress:function(t){13===t.originalEvent.keyCode&&this._openPopup(t)}}),O.extend({options:{pane:"tooltipPane",offset:[0,0],direction:"auto",permanent:!1,sticky:!1,opacity:.9},onAdd:function(t){O.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire("tooltipopen",{tooltip:this}),this._source&&(this.addEventParent(this._source),this._source.fire("tooltipopen",{tooltip:this},!0))},onRemove:function(t){O.prototype.onRemove.call(this,t),t.fire("tooltipclose",{tooltip:this}),this._source&&(this.removeEventParent(this._source),this._source.fire("tooltipclose",{tooltip:this},!0))},getEvents:function(){var t=O.prototype.getEvents.call(this);return this.options.permanent||(t.preclick=this.close),t},_initLayout:function(){var t="leaflet-tooltip "+(this.options.className||"")+" leaflet-zoom-"+(this._zoomAnimated?"animated":"hide");this._contentNode=this._container=b("div",t)},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var i,e=this._map,n=this._container,o=e.latLngToContainerPoint(e.getCenter()),e=e.layerPointToContainerPoint(t),s=this.options.direction,r=n.offsetWidth,a=n.offsetHeight,h=_(this.options.offset),l=this._getAnchor(),e="top"===s?(i=r/2,a):"bottom"===s?(i=r/2,0):(i="center"===s?r/2:"right"===s?0:"left"===s?r:e.xthis.options.maxZoom||nthis.options.maxZoom||void 0!==this.options.minZoom&&oe.max.x)||!i.wrapLat&&(t.ye.max.y))return!1}if(!this.options.bounds)return!0;i=this._tileCoordsToBounds(t);return g(this.options.bounds).overlaps(i)},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToNwSe:function(t){var i=this._map,e=this.getTileSize(),n=t.scaleBy(e),e=n.add(e);return[i.unproject(n,t.z),i.unproject(e,t.z)]},_tileCoordsToBounds:function(t){t=this._tileCoordsToNwSe(t),t=new s(t[0],t[1]);return t=this.options.noWrap?t:this._map.wrapLatLngBounds(t)},_tileCoordsToKey:function(t){return t.x+":"+t.y+":"+t.z},_keyToTileCoords:function(t){var t=t.split(":"),i=new p(+t[0],+t[1]);return i.z=+t[2],i},_removeTile:function(t){var i=this._tiles[t];i&&(T(i.el),delete this._tiles[t],this.fire("tileunload",{tile:i.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){z(t,"leaflet-tile");var i=this.getTileSize();t.style.width=i.x+"px",t.style.height=i.y+"px",t.onselectstart=u,t.onmousemove=u,P.ielt9&&this.options.opacity<1&&C(t,this.options.opacity)},_addTile:function(t,i){var e=this._getTilePos(t),n=this._tileCoordsToKey(t),o=this.createTile(this._wrapCoords(t),a(this._tileReady,this,t));this._initTile(o),this.createTile.length<2&&x(a(this._tileReady,this,t,null,o)),Z(o,e),this._tiles[n]={el:o,coords:t,current:!0},i.appendChild(o),this.fire("tileloadstart",{tile:o,coords:t})},_tileReady:function(t,i,e){i&&this.fire("tileerror",{error:i,tile:e,coords:t});var n=this._tileCoordsToKey(t);(e=this._tiles[n])&&(e.loaded=+new Date,this._map._fadeAnimated?(C(e.el,0),r(this._fadeFrame),this._fadeFrame=x(this._updateOpacity,this)):(e.active=!0,this._pruneTiles()),i||(z(e.el,"leaflet-tile-loaded"),this.fire("tileload",{tile:e.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire("load"),P.ielt9||!this._map._fadeAnimated?x(this._pruneTiles,this):setTimeout(a(this._pruneTiles,this),250)))},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var i=new p(this._wrapX?H(t.x,this._wrapX):t.x,this._wrapY?H(t.y,this._wrapY):t.y);return i.z=t.z,i},_pxBoundsToTileRange:function(t){var i=this.getTileSize();return new m(t.min.unscaleBy(i).floor(),t.max.unscaleBy(i).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}});var Ie=Ae.extend({options:{minZoom:0,maxZoom:18,subdomains:"abc",errorTileUrl:"",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1,referrerPolicy:!1},initialize:function(t,i){this._url=t,(i=c(this,i)).detectRetina&&P.retina&&0')}}catch(t){}return function(t){return document.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}(),Mt={_initContainer:function(){this._container=b("div","leaflet-vml-container")},_update:function(){this._map._animatingZoom||(Ne.prototype._update.call(this),this.fire("update"))},_initPath:function(t){var i=t._container=He("shape");z(i,"leaflet-vml-shape "+(this.options.className||"")),i.coordsize="1 1",t._path=He("path"),i.appendChild(t._path),this._updateStyle(t),this._layers[h(t)]=t},_addPath:function(t){var i=t._container;this._container.appendChild(i),t.options.interactive&&t.addInteractiveTarget(i)},_removePath:function(t){var i=t._container;T(i),t.removeInteractiveTarget(i),delete this._layers[h(t)]},_updateStyle:function(t){var i=t._stroke,e=t._fill,n=t.options,o=t._container;o.stroked=!!n.stroke,o.filled=!!n.fill,n.stroke?(i=i||(t._stroke=He("stroke")),o.appendChild(i),i.weight=n.weight+"px",i.color=n.color,i.opacity=n.opacity,n.dashArray?i.dashStyle=d(n.dashArray)?n.dashArray.join(" "):n.dashArray.replace(/( *, *)/g," "):i.dashStyle="",i.endcap=n.lineCap.replace("butt","flat"),i.joinstyle=n.lineJoin):i&&(o.removeChild(i),t._stroke=null),n.fill?(e=e||(t._fill=He("fill")),o.appendChild(e),e.color=n.fillColor||n.color,e.opacity=n.fillOpacity):e&&(o.removeChild(e),t._fill=null)},_updateCircle:function(t){var i=t._point.round(),e=Math.round(t._radius),n=Math.round(t._radiusY||e);this._setPath(t,t._empty()?"M0 0":"AL "+i.x+","+i.y+" "+e+","+n+" 0,23592600")},_setPath:function(t,i){t._path.v=i},_bringToFront:function(t){fi(t._container)},_bringToBack:function(t){gi(t._container)}},We=P.vml?He:ct,Fe=Ne.extend({_initContainer:function(){this._container=We("svg"),this._container.setAttribute("pointer-events","none"),this._rootGroup=We("g"),this._container.appendChild(this._rootGroup)},_destroyContainer:function(){T(this._container),E(this._container),delete this._container,delete this._rootGroup,delete this._svgSize},_update:function(){var t,i,e;this._map._animatingZoom&&this._bounds||(Ne.prototype._update.call(this),i=(t=this._bounds).getSize(),e=this._container,this._svgSize&&this._svgSize.equals(i)||(this._svgSize=i,e.setAttribute("width",i.x),e.setAttribute("height",i.y)),Z(e,t.min),e.setAttribute("viewBox",[t.min.x,t.min.y,i.x,i.y].join(" ")),this.fire("update"))},_initPath:function(t){var i=t._path=We("path");t.options.className&&z(i,t.options.className),t.options.interactive&&z(i,"leaflet-interactive"),this._updateStyle(t),this._layers[h(t)]=t},_addPath:function(t){this._rootGroup||this._initContainer(),this._rootGroup.appendChild(t._path),t.addInteractiveTarget(t._path)},_removePath:function(t){T(t._path),t.removeInteractiveTarget(t._path),delete this._layers[h(t)]},_updatePath:function(t){t._project(),t._update()},_updateStyle:function(t){var i=t._path,t=t.options;i&&(t.stroke?(i.setAttribute("stroke",t.color),i.setAttribute("stroke-opacity",t.opacity),i.setAttribute("stroke-width",t.weight),i.setAttribute("stroke-linecap",t.lineCap),i.setAttribute("stroke-linejoin",t.lineJoin),t.dashArray?i.setAttribute("stroke-dasharray",t.dashArray):i.removeAttribute("stroke-dasharray"),t.dashOffset?i.setAttribute("stroke-dashoffset",t.dashOffset):i.removeAttribute("stroke-dashoffset")):i.setAttribute("stroke","none"),t.fill?(i.setAttribute("fill",t.fillColor||t.color),i.setAttribute("fill-opacity",t.fillOpacity),i.setAttribute("fill-rule",t.fillRule||"evenodd")):i.setAttribute("fill","none"))},_updatePoly:function(t,i){this._setPath(t,dt(t._parts,i))},_updateCircle:function(t){var i=t._point,e=Math.max(Math.round(t._radius),1),n="a"+e+","+(Math.max(Math.round(t._radiusY),1)||e)+" 0 1,0 ",i=t._empty()?"M0 0":"M"+(i.x-e)+","+i.y+n+2*e+",0 "+n+2*-e+",0 ";this._setPath(t,i)},_setPath:function(t,i){t._path.setAttribute("d",i)},_bringToFront:function(t){fi(t._path)},_bringToBack:function(t){gi(t._path)}});function Ue(t){return P.svg||P.vml?new Fe(t):null}P.vml&&Fe.include(Mt),A.include({getRenderer:function(t){t=(t=t.options.renderer||this._getPaneRenderer(t.options.pane)||this.options.renderer||this._renderer)||(this._renderer=this._createRenderer());return this.hasLayer(t)||this.addLayer(t),t},_getPaneRenderer:function(t){if("overlayPane"===t||void 0===t)return!1;var i=this._paneRenderers[t];return void 0===i&&(i=this._createRenderer({pane:t}),this._paneRenderers[t]=i),i},_createRenderer:function(t){return this.options.preferCanvas&&je(t)||Ue(t)}});var Ve=ge.extend({initialize:function(t,i){ge.prototype.initialize.call(this,this._boundsToLatLngs(t),i)},setBounds:function(t){return this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return[(t=g(t)).getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}});Fe.create=We,Fe.pointsToPath=dt,ve.geometryToLayer=ye,ve.coordsToLatLng=we,ve.coordsToLatLngs=Pe,ve.latLngToCoords=be,ve.latLngsToCoords=Le,ve.getFeature=Te,ve.asFeature=ze,A.mergeOptions({boxZoom:!0});var _t=n.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._resetStateTimeout=0,t.on("unload",this._destroy,this)},addHooks:function(){S(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){E(this._container,"mousedown",this._onMouseDown,this)},moved:function(){return this._moved},_destroy:function(){T(this._pane),delete this._pane},_resetState:function(){this._resetStateTimeout=0,this._moved=!1},_clearDeferredResetState:function(){0!==this._resetStateTimeout&&(clearTimeout(this._resetStateTimeout),this._resetStateTimeout=0)},_onMouseDown:function(t){if(!t.shiftKey||1!==t.which&&1!==t.button)return!1;this._clearDeferredResetState(),this._resetState(),ri(),Li(),this._startPoint=this._map.mouseEventToContainerPoint(t),S(document,{contextmenu:Ri,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=b("div","leaflet-zoom-box",this._container),z(this._container,"leaflet-crosshair"),this._map.fire("boxzoomstart")),this._point=this._map.mouseEventToContainerPoint(t);var t=new m(this._point,this._startPoint),i=t.getSize();Z(this._box,t.min),this._box.style.width=i.x+"px",this._box.style.height=i.y+"px"},_finish:function(){this._moved&&(T(this._box),M(this._container,"leaflet-crosshair")),ai(),Ti(),E(document,{contextmenu:Ri,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){1!==t.which&&1!==t.button||(this._finish(),this._moved&&(this._clearDeferredResetState(),this._resetStateTimeout=setTimeout(a(this._resetState,this),0),t=new s(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point)),this._map.fitBounds(t).fire("boxzoomend",{boxZoomBounds:t})))},_onKeyDown:function(t){27===t.keyCode&&(this._finish(),this._clearDeferredResetState(),this._resetState())}}),Ct=(A.addInitHook("addHandler","boxZoom",_t),A.mergeOptions({doubleClickZoom:!0}),n.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var i=this._map,e=i.getZoom(),n=i.options.zoomDelta,e=t.originalEvent.shiftKey?e-n:e+n;"center"===i.options.doubleClickZoom?i.setZoom(e):i.setZoomAround(t.containerPoint,e)}})),Zt=(A.addInitHook("addHandler","doubleClickZoom",Ct),A.mergeOptions({dragging:!0,inertia:!0,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,easeLinearity:.2,worldCopyJump:!1,maxBoundsViscosity:0}),n.extend({addHooks:function(){var t;this._draggable||(t=this._map,this._draggable=new Xi(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),this._draggable.on("predrag",this._onPreDragLimit,this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDragWrap,this),t.on("zoomend",this._onZoomEnd,this),t.whenReady(this._onZoomEnd,this))),z(this._map._container,"leaflet-grab leaflet-touch-drag"),this._draggable.enable(),this._positions=[],this._times=[]},removeHooks:function(){M(this._map._container,"leaflet-grab"),M(this._map._container,"leaflet-touch-drag"),this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},moving:function(){return this._draggable&&this._draggable._moving},_onDragStart:function(){var t,i=this._map;i._stop(),this._map.options.maxBounds&&this._map.options.maxBoundsViscosity?(t=g(this._map.options.maxBounds),this._offsetLimit=f(this._map.latLngToContainerPoint(t.getNorthWest()).multiplyBy(-1),this._map.latLngToContainerPoint(t.getSouthEast()).multiplyBy(-1).add(this._map.getSize())),this._viscosity=Math.min(1,Math.max(0,this._map.options.maxBoundsViscosity))):this._offsetLimit=null,i.fire("movestart").fire("dragstart"),i.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(t){var i,e;this._map.options.inertia&&(i=this._lastTime=+new Date,e=this._lastPos=this._draggable._absPos||this._draggable._newPos,this._positions.push(e),this._times.push(i),this._prunePositions(i)),this._map.fire("move",t).fire("drag",t)},_prunePositions:function(t){for(;1i.max.x&&(t.x=this._viscousLimit(t.x,i.max.x)),t.y>i.max.y&&(t.y=this._viscousLimit(t.y,i.max.y)),this._draggable._newPos=this._draggable._startPos.add(t))},_onPreDragWrap:function(){var t=this._worldWidth,i=Math.round(t/2),e=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-i+e)%t+i-e,n=(n+i+e)%t-i-e,t=Math.abs(o+e)i.getMaxZoom()&&1=1.3?"crs":"srs";this.wmsParams[b]=this._crs.code,c.NonTiledLayer.prototype.onAdd.call(this,a)},getImageUrl:function(a,b,d){var e=this.wmsParams;e.width=b,e.height=d;var f=this._crs.project(a.getNorthWest()),g=this._crs.project(a.getSouthEast()),h=this._wmsUrl,i=i=(this._wmsVersion>=1.3&&this._crs===c.CRS.EPSG4326?[g.y,f.x,f.y,g.x]:[f.x,g.y,g.x,f.y]).join(",");return h+c.Util.getParamString(this.wmsParams,h,this.options.uppercase)+(this.options.uppercase?"&BBOX=":"&bbox=")+i},setParams:function(a,b){return c.extend(this.wmsParams,a),b||this.redraw(),this}}),c.nonTiledLayer.wms=function(a,b){return new c.NonTiledLayer.WMS(a,b)},b.exports=c.NonTiledLayer.WMS}).call(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],2:[function(a,b,c){(function(a){(function(){"use strict";var c="undefined"!=typeof window?window.L:void 0!==a?a.L:null;c.NonTiledLayer=(c.Layer||c.Class).extend({includes:c.Evented||c.Mixin.Events,emptyImageUrl:"data:image/gif;base64,R0lGODlhAQABAHAAACH5BAUAAAAALAAAAAABAAEAAAICRAEAOw==",options:{attribution:"",opacity:1,zIndex:void 0,minZoom:0,maxZoom:18,pointerEvents:null,errorImageUrl:"data:image/gif;base64,R0lGODlhAQABAHAAACH5BAUAAAAALAAAAAABAAEAAAICRAEAOw==",bounds:c.latLngBounds([-85.05,-180],[85.05,180]),useCanvas:void 0,detectRetina:!1},key:"",initialize:function(a){c.setOptions(this,a)},onAdd:function(a){this._map=a,void 0===this._zoomAnimated&&(this._zoomAnimated=c.DomUtil.TRANSITION&&c.Browser.any3d&&!c.Browser.mobileOpera&&this._map.options.zoomAnimation),c.version<"1.0"&&this._map.on(this.getEvents(),this),this._div||(this._div=c.DomUtil.create("div","leaflet-image-layer"),this.options.pointerEvents&&(this._div.style["pointer-events"]=this.options.pointerEvents),void 0!==this.options.zIndex&&(this._div.style.zIndex=this.options.zIndex),void 0!==this.options.opacity&&(this._div.style.opacity=this.options.opacity)),this.getPane().appendChild(this._div);var b=!!window.HTMLCanvasElement;void 0===this.options.useCanvas?this._useCanvas=b:this._useCanvas=this.options.useCanvas,this._useCanvas?(this._bufferCanvas=this._initCanvas(),this._currentCanvas=this._initCanvas()):(this._bufferImage=this._initImage(),this._currentImage=this._initImage()),this._update()},getPane:function(){return c.Layer?c.Layer.prototype.getPane.call(this):(this.options.pane?this._pane=this.options.pane:this._pane=this._map.getPanes().overlayPane,this._pane)},onRemove:function(a){c.version<"1.0"&&this._map.off(this.getEvents(),this),this.getPane().removeChild(this._div),this._useCanvas?(this._div.removeChild(this._bufferCanvas),this._div.removeChild(this._currentCanvas)):(this._div.removeChild(this._bufferImage),this._div.removeChild(this._currentImage))},addTo:function(a){return a.addLayer(this),this},_setZoom:function(){this._useCanvas?(this._currentCanvas._bounds&&this._resetImageScale(this._currentCanvas,!0),this._bufferCanvas._bounds&&this._resetImageScale(this._bufferCanvas)):(this._currentImage._bounds&&this._resetImageScale(this._currentImage,!0),this._bufferImage._bounds&&this._resetImageScale(this._bufferImage))},getEvents:function(){var a={moveend:this._update};return this._zoomAnimated&&(a.zoomanim=this._animateZoom),c.version>="1.0"&&(a.zoom=this._setZoom),a},getElement:function(){return this._div},setOpacity:function(a){return this.options.opacity=a,this._div&&c.DomUtil.setOpacity(this._div,this.options.opacity),this},setZIndex:function(a){return a&&(this.options.zIndex=a,this._div&&(this._div.style.zIndex=a)),this},bringToFront:function(){return this._div&&this.getPane().appendChild(this._div),this},bringToBack:function(){return this._div&&this.getPane().insertBefore(this._div,this.getPane().firstChild),this},getAttribution:function(){return this.options.attribution},_initCanvas:function(){var a=c.DomUtil.create("canvas","leaflet-image-layer");return this._div.appendChild(a),a._image=new Image,this._ctx=a.getContext("2d"),this.options.crossOrigin&&(a._image.crossOrigin=this.options.crossOrigin),this._map.options.zoomAnimation&&c.Browser.any3d?c.DomUtil.addClass(a,"leaflet-zoom-animated"):c.DomUtil.addClass(a,"leaflet-zoom-hide"),c.extend(a._image,{onload:c.bind(this._onImageLoad,this),onerror:c.bind(this._onImageError,this)}),a},_initImage:function(){var a=c.DomUtil.create("img","leaflet-image-layer");return this.options.crossOrigin&&(a.crossOrigin=this.options.crossOrigin),this._div.appendChild(a),this._map.options.zoomAnimation&&c.Browser.any3d?c.DomUtil.addClass(a,"leaflet-zoom-animated"):c.DomUtil.addClass(a,"leaflet-zoom-hide"),c.extend(a,{galleryimg:"no",onselectstart:c.Util.falseFn,onmousemove:c.Util.falseFn,onload:c.bind(this._onImageLoad,this),onerror:c.bind(this._onImageError,this)}),a},redraw:function(){return this._map&&this._update(),this},_animateZoom:function(a){this._useCanvas?(this._currentCanvas._bounds&&this._animateImage(this._currentCanvas,a),this._bufferCanvas._bounds&&this._animateImage(this._bufferCanvas,a)):(this._currentImage._bounds&&this._animateImage(this._currentImage,a),this._bufferImage._bounds&&this._animateImage(this._bufferImage,a))},_animateImage:function(a,b){if(void 0===c.DomUtil.setTransform){var d=this._map,e=a._scale*d.getZoomScale(b.zoom),f=a._bounds.getNorthWest(),g=a._bounds.getSouthEast(),h=d._latLngToNewLayerPoint(f,b.zoom,b.center),i=d._latLngToNewLayerPoint(g,b.zoom,b.center)._subtract(h),j=h._add(i._multiplyBy(.5*(1-1/e)));a.style[c.DomUtil.TRANSFORM]=c.DomUtil.getTranslateString(j)+" scale("+e+") "}else{var d=this._map,e=a._scale*a._sscale*d.getZoomScale(b.zoom),f=a._bounds.getNorthWest(),g=a._bounds.getSouthEast(),h=d._latLngToNewLayerPoint(f,b.zoom,b.center);c.DomUtil.setTransform(a,h,e)}a._lastScale=e},_resetImageScale:function(a,b){var d=new c.Bounds(this._map.latLngToLayerPoint(a._bounds.getNorthWest()),this._map.latLngToLayerPoint(a._bounds.getSouthEast())),e=a._orgBounds.getSize().y,f=d.getSize().y,g=f/e;a._sscale=g,c.DomUtil.setTransform(a,d.min,g)},_resetImage:function(a){var b=new c.Bounds(this._map.latLngToLayerPoint(a._bounds.getNorthWest()),this._map.latLngToLayerPoint(a._bounds.getSouthEast())),d=b.getSize();c.DomUtil.setPosition(a,b.min),a._orgBounds=b,a._sscale=1,this._useCanvas?(a.width=d.x,a.height=d.y):(a.style.width=d.x+"px",a.style.height=d.y+"px")},_getClippedBounds:function(){var a=this._map.getBounds(),b=a.getSouth(),d=a.getNorth(),e=a.getWest(),f=a.getEast(),g=this.options.bounds.getSouth(),h=this.options.bounds.getNorth(),i=this.options.bounds.getWest(),j=this.options.bounds.getEast();bh&&(d=h),ej&&(f=j);var k=new c.LatLng(d,e),l=new c.LatLng(b,f);return new c.LatLngBounds(k,l)},_getImageScale:function(){return this.options.detectRetina&&c.Browser.retina?2:1},_update:function(){var a,b=this._getClippedBounds(),d=this._map.latLngToContainerPoint(b.getNorthWest()),e=this._map.latLngToContainerPoint(b.getSouthEast()),f=e.x-d.x,g=e.y-d.y;if(this._useCanvas?(this._bufferCanvas._scale=this._bufferCanvas._lastScale,this._currentCanvas._scale=this._currentCanvas._lastScale=1,this._bufferCanvas._sscale=1,this._currentCanvas._bounds=b,this._resetImage(this._currentCanvas),a=this._currentCanvas._image,c.DomUtil.setOpacity(a,0)):(this._bufferImage._scale=this._bufferImage._lastScale,this._currentImage._scale=this._currentImage._lastScale=1,this._bufferImage._sscale=1,this._currentImage._bounds=b,this._resetImage(this._currentImage),a=this._currentImage,c.DomUtil.setOpacity(a,0)),this._map.getZoom()this.options.maxZoom||f<32||g<32)return this._div.style.visibility="hidden",a.src=this.emptyImageUrl,this.key=a.key="",void(a.tag=null);this.fire("loading"),f*=this._getImageScale(),g*=this._getImageScale(),this.key=b.getNorthWest()+", "+b.getSouthEast()+", "+f+", "+g,this.getImageUrl?(a.src=this.getImageUrl(b,f,g),a.key=this.key):this.getImageUrlAsync(b,f,g,this.key,function(b,c,d){a.key=b,a.src=c,a.tag=d})},_onImageError:function(a){this.fire("error",a),c.DomUtil.addClass(a.target,"invalid"),a.target.src!==this.options.errorImageUrl&&(a.target.src=this.options.errorImageUrl)},_onImageLoad:function(a){(a.target.src===this.options.errorImageUrl||(c.DomUtil.removeClass(a.target,"invalid"),a.target.key&&a.target.key===this.key))&&(this._onImageDone(a),this.fire("load",a))},_onImageDone:function(a){if(this._useCanvas)this._renderCanvas(a);else{c.DomUtil.setOpacity(this._currentImage,1),c.DomUtil.setOpacity(this._bufferImage,0),this._addInteraction&&this._currentImage.tag&&this._addInteraction(this._currentImage.tag);var b=this._bufferImage;this._bufferImage=this._currentImage,this._currentImage=b}""!==a.target.key&&(this._div.style.visibility="visible")},_renderCanvas:function(a){this._currentCanvas.getContext("2d").drawImage(this._currentCanvas._image,0,0,this._currentCanvas.width,this._currentCanvas.height),c.DomUtil.setOpacity(this._currentCanvas,1),c.DomUtil.setOpacity(this._bufferCanvas,0),this._addInteraction&&this._currentCanvas._image.tag&&this._addInteraction(this._currentCanvas._image.tag);var b=this._bufferCanvas;this._bufferCanvas=this._currentCanvas,this._currentCanvas=b}}),c.nonTiledLayer=function(){return new c.NonTiledLayer},b.exports=c.NonTiledLayer}).call(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[2,1])(2)}); -//# sourceMappingURL=NonTiledLayer.js.mapvar sessionId; -var curGeojson; -var curGeojsonId = -1; - -var urlParams = new URLSearchParams(window.location.search); -var qleverBackend = urlParams.get("backend"); -var query = urlParams.get("query"); - -var map = L.map('m', { - renderer: L.canvas(), - preferCanvas: true -}).setView([47.9965, 7.8469], 13); -map.attributionControl.setPrefix('University of Freiburg'); - -var layerControl = L.control.layers([], [], {collapsed:true, position: 'topleft'}).addTo(map); - -var osmLayer = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { - attribution: '© OpenStreetMap', - maxZoom: 19, - opacity:0.9 -}).addTo(map); - -var genError = "

Session has been removed from cache.

Resend request

"; - -function openPopup(data) { - if (data.length > 0) { - let select_variables = []; - let row = []; - - for (let i in data[0]["attrs"]) { - select_variables.push(data[0]["attrs"][i][0]); - row.push(data[0]["attrs"][i][1]); - } - - // code by hannah from old map UI - - // Build the HTML of the popup. - // - // NOTE: We assume that the last column contains the geometry information - // (WKT), which we will not put in the table. - let geometry_column = select_variables.length - 1; - // If the second to last variable exists and is called "?image" or ends in - // "_image", then show an image with that URL in the first column of the - // table. Note that we compute the cell contents here and add it during the - // loop (it has to be the first cell of a table row). - let image_cell = ""; - if (select_variables.length >= 2) { - let image_column = select_variables.length - 2; - if (select_variables[image_column] == "?image" || - select_variables[image_column] == "?flag" || - select_variables[image_column].endsWith("_image")) { - let num_table_rows = select_variables.length - 2; - let image_url = row[image_column]; - if (image_url != null) - image_cell = ""]$/, "") + "\">"]$/, "") - + "\">"; - } - } - - // Now compute the table rows in an array. - let popup_content_strings = []; - select_variables.forEach(function(variable, i) { - // Skip the last column (WKT literal) and the ?image column (if it - // exists). - if (i == geometry_column || - variable == "?image" || variable == "?flag" || variable.endsWith("_image")) return; - - // Take the variable name as one table column and the result value as - // another. Reformat a bit, so that it looks nice in an HTML table. and - // the result value as another. Reformat a bit, so that it looks nice in - // an HTML table. - let key = variable.substring(1); - if (row[i] == null) { row[i] = "---"; } - let value = row[i].replace(/\\([()])/g, "$1") - .replace(/<((.*)\/(.*))>/, - "$3") - .replace(/\^\^.*$/, "") - .replace(/\"(.*)\"(@[a-z]+)?$/, "$1"); - - popup_content_strings.push( - "" + (i == 0 ? image_cell : "") + - "" + key.replace(/_/g, " ") + "" + - "" + value + ""); - }) - let popup_html = "" + popup_content_strings.join("\n") + "
"; - popup_html += 'Export as GeoJSON'; - - if (curGeojson) curGeojson.remove(); - - L.popup({"maxWidth" : 600}) - .setLatLng(data[0]["ll"]) - .setContent(popup_html) - .openOn(map) - .on('remove', function() { - curGeojson.remove(); - curGeojsonId = -1; - }); - - curGeojson = getGeoJsonLayer(data[0].geom); - curGeojsonId = data[0].id; - curGeojson.addTo(map); - } -} - -function getGeoJsonLayer(geom) { - const color = "#e6930e"; - return L.geoJSON(geom, { - style: {color : color, fillColor: color, weight: 7, fillOpacity: 0.2}, - pointToLayer: function (feature, latlng) { - return L.circleMarker(latlng, { - radius: 8, - fillColor: color, - color: color, - weight: 4, - opacity: 1, - fillOpacity: 0.2 - });} - }) -} - -function showError(error) { - document.getElementById("msg").style.display = "block"; - document.getElementById("loader").style.display = "none"; - document.getElementById("msg-inner").style.color = "red"; - document.getElementById("msg-inner").style.fontWeight = "bold"; - document.getElementById("msg-inner").style.fontSize = "20px"; - document.getElementById("msg-inner").innerHTML = error; -} - -function loadMap(id, bounds, numObjects) { - document.getElementById("msg").style.display = "none"; - const ll = L.Projection.SphericalMercator.unproject({"x": bounds[0][0], "y":bounds[0][1]}); - const ur = L.Projection.SphericalMercator.unproject({"x": bounds[1][0], "y":bounds[1][1]}); - const boundsLatLng = [[ll.lat, ll.lng], [ur.lat, ur.lng]]; - map.fitBounds(boundsLatLng); - sessionId = id; - - document.getElementById("stats").innerHTML = "Showing " + numObjects + " objects"; - - const heatmapLayer = L.nonTiledLayer.wms('heatmap', { - minZoom: 0, - maxZoom: 19, - opacity: 0.8, - layers: id, - styles: ["heatmap"], - format: 'image/png', - transparent: true, - }); - - const objectsLayer = L.nonTiledLayer.wms('heatmap', { - minZoom: 0, - maxZoom: 19, - opacity: 0.8, - layers: id, - styles: ["objects"], - format: 'image/png', - transparent: true, - }); - - heatmapLayer.addTo(map).on('error', function() {showError(genError);}); - objectsLayer.on('error', function() {showError(genError);}); - - layerControl.addBaseLayer(heatmapLayer, "Heatmap"); - layerControl.addBaseLayer(objectsLayer, "Objects"); - - map.on('click', function(e) { - const ll= e.latlng; - const pos = L.Projection.SphericalMercator.project(ll); - - fetch('pos?x=' + pos.x + "&y=" + pos.y + "&id=" + id + "&rad=" + (100 * Math.pow(2, 14 - map.getZoom()))) - .then(response => response.json()) - .then(data => openPopup(data)) - .catch(error => showError(genError)); - }); - - map.on('zoomend', function(e) { - if (curGeojsonId > -1) { - fetch('geojson?gid=' + curGeojsonId + "&id=" + id + "&rad=" + (100 * Math.pow(2, 14 - map.getZoom()))) - .then(response => response.json()) - .then(function(data) { - curGeojson.remove(); - curGeojson = getGeoJsonLayer(data); - curGeojson.addTo(map); - }) - .catch(error => showError(genError)); - } - }); -} - -fetch('query' + window.location.search) - .then(response => { - if (!response.ok) return response.text().then(text => {throw new Error(text)}); - return response; - }) - .then(response => response.json()) - .then(data => loadMap(data["qid"], data["bounds"], data["numobjects"])) - .catch(error => {showError(error);}); - -document.getElementById("export-geojson").onclick = function() { - if (!sessionId) return; - let a = document.createElement("a"); - a.href = "export?id="+ sessionId; - a.setAttribute("download", "export.json"); - a.click(); -} - -document.getElementById("export-tsv").onclick = function() { - let a = document.createElement("a"); - a.href = qleverBackend + "?query=" + encodeURIComponent(query) + "&action=tsv_export"; - a.setAttribute("download", "export.tsv"); - a.click(); -} - -document.getElementById("export-csv").onclick = function() { - let a = document.createElement("a"); - a.href = qleverBackend + "?query=" + encodeURIComponent(query) + "&action=csv_export"; - a.setAttribute("download", "export.csv"); - a.click(); -} +/* @preserve + * Leaflet 1.8.0, a JS library for interactive maps. https://leafletjs.com + * (c) 2010-2022 Vladimir Agafonkin, (c) 2010-2011 CloudMade + */ +!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?i(exports):"function"==typeof define&&define.amd?define(["exports"],i):i((t="undefined"!=typeof globalThis?globalThis:t||self).leaflet={})}(this,function(t){"use strict";function l(t){for(var i,e,n=1,o=arguments.length;n=this.min.x&&e.x<=this.max.x&&i.y>=this.min.y&&e.y<=this.max.y},intersects:function(t){t=f(t);var i=this.min,e=this.max,n=t.min,t=t.max,o=t.x>=i.x&&n.x<=e.x,t=t.y>=i.y&&n.y<=e.y;return o&&t},overlaps:function(t){t=f(t);var i=this.min,e=this.max,n=t.min,t=t.max,o=t.x>i.x&&n.xi.y&&n.y=n.lat&&e.lat<=o.lat&&i.lng>=n.lng&&e.lng<=o.lng},intersects:function(t){t=g(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),t=t.getNorthEast(),o=t.lat>=i.lat&&n.lat<=e.lat,t=t.lng>=i.lng&&n.lng<=e.lng;return o&&t},overlaps:function(t){t=g(t);var i=this._southWest,e=this._northEast,n=t.getSouthWest(),t=t.getNorthEast(),o=t.lat>i.lat&&n.lati.lng&&n.lng","http://www.w3.org/2000/svg"===(Wt.firstChild&&Wt.firstChild.namespaceURI));function y(t){return 0<=navigator.userAgent.toLowerCase().indexOf(t)}var P={ie:pt,ielt9:mt,edge:n,webkit:ft,android:gt,android23:vt,androidStock:yt,opera:xt,chrome:wt,gecko:Pt,safari:bt,phantom:Lt,opera12:o,win:Tt,ie3d:zt,webkit3d:Mt,gecko3d:_t,any3d:Ct,mobile:Zt,mobileWebkit:St,mobileWebkit3d:kt,msPointer:Et,pointer:Bt,touch:It,touchNative:At,mobileOpera:Ot,mobileGecko:Rt,retina:Nt,passiveEvents:Dt,canvas:jt,svg:Ht,vml:!Ht&&function(){try{var t=document.createElement("div"),i=(t.innerHTML='',t.firstChild);return i.style.behavior="url(#default#VML)",i&&"object"==typeof i.adj}catch(t){return!1}}(),inlineSvg:Wt},Ft=P.msPointer?"MSPointerDown":"pointerdown",Ut=P.msPointer?"MSPointerMove":"pointermove",Vt=P.msPointer?"MSPointerUp":"pointerup",qt=P.msPointer?"MSPointerCancel":"pointercancel",Gt={touchstart:Ft,touchmove:Ut,touchend:Vt,touchcancel:qt},Kt={touchstart:function(t,i){i.MSPOINTER_TYPE_TOUCH&&i.pointerType===i.MSPOINTER_TYPE_TOUCH&&B(i);ii(t,i)},touchmove:ii,touchend:ii,touchcancel:ii},Yt={},Xt=!1;function Jt(t,i,e){return"touchstart"!==i||Xt||(document.addEventListener(Ft,$t,!0),document.addEventListener(Ut,Qt,!0),document.addEventListener(Vt,ti,!0),document.addEventListener(qt,ti,!0),Xt=!0),Kt[i]?(e=Kt[i].bind(this,e),t.addEventListener(Gt[i],e,!1),e):(console.warn("wrong event specified:",i),L.Util.falseFn)}function $t(t){Yt[t.pointerId]=t}function Qt(t){Yt[t.pointerId]&&(Yt[t.pointerId]=t)}function ti(t){delete Yt[t.pointerId]}function ii(t,i){if(i.pointerType!==(i.MSPOINTER_TYPE_MOUSE||"mouse")){for(var e in i.touches=[],Yt)i.touches.push(Yt[e]);i.changedTouches=[i],t(i)}}var ei=200;function ni(t,e){t.addEventListener("dblclick",e);var n,o=0;function i(t){var i;1!==t.detail?n=t.detail:"mouse"===t.pointerType||t.sourceCapabilities&&!t.sourceCapabilities.firesTouchEvents||((i=Date.now())-o<=ei?2===++n&&e(function(t){var i,e,n={};for(e in t)i=t[e],n[e]=i&&i.bind?i.bind(t):i;return(t=n).type="dblclick",n.detail=2,n.isTrusted=!1,n._simulated=!0,n}(t)):n=1,o=i)}return t.addEventListener("click",i),{dblclick:e,simDblclick:i}}var oi,si,ri,ai,hi,li,ui=wi(["transform","webkitTransform","OTransform","MozTransform","msTransform"]),ci=wi(["webkitTransition","transition","OTransition","MozTransition","msTransition"]),di="webkitTransition"===ci||"OTransition"===ci?ci+"End":"transitionend";function _i(t){return"string"==typeof t?document.getElementById(t):t}function pi(t,i){var e=t.style[i]||t.currentStyle&&t.currentStyle[i];return"auto"===(e=e&&"auto"!==e||!document.defaultView?e:(t=document.defaultView.getComputedStyle(t,null))?t[i]:null)?null:e}function b(t,i,e){t=document.createElement(t);return t.className=i||"",e&&e.appendChild(t),t}function T(t){var i=t.parentNode;i&&i.removeChild(t)}function mi(t){for(;t.firstChild;)t.removeChild(t.firstChild)}function fi(t){var i=t.parentNode;i&&i.lastChild!==t&&i.appendChild(t)}function gi(t){var i=t.parentNode;i&&i.firstChild!==t&&i.insertBefore(t,i.firstChild)}function vi(t,i){if(void 0!==t.classList)return t.classList.contains(i);t=xi(t);return 0this.options.maxZoom)?this.setZoom(t):this},panInsideBounds:function(t,i){this._enforcingBounds=!0;var e=this.getCenter(),t=this._limitCenter(e,this._zoom,g(t));return e.equals(t)||this.panTo(t,i),this._enforcingBounds=!1,this},panInside:function(t,i){var e=_((i=i||{}).paddingTopLeft||i.padding||[0,0]),n=_(i.paddingBottomRight||i.padding||[0,0]),o=this.project(this.getCenter()),t=this.project(t),s=this.getPixelBounds(),e=f([s.min.add(e),s.max.subtract(n)]),s=e.getSize();return e.contains(t)||(this._enforcingBounds=!0,n=t.subtract(e.getCenter()),e=e.extend(t).getSize().subtract(s),o.x+=n.x<0?-e.x:e.x,o.y+=n.y<0?-e.y:e.y,this.panTo(this.unproject(o),i),this._enforcingBounds=!1),this},invalidateSize:function(t){if(!this._loaded)return this;t=l({animate:!1,pan:!0},!0===t?{animate:!0}:t);var i=this.getSize(),e=(this._sizeChanged=!0,this._lastCenter=null,this.getSize()),n=i.divideBy(2).round(),o=e.divideBy(2).round(),n=n.subtract(o);return n.x||n.y?(t.animate&&t.pan?this.panBy(n):(t.pan&&this._rawPanBy(n),this.fire("move"),t.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(a(this.fire,this,"moveend"),200)):this.fire("moveend")),this.fire("resize",{oldSize:i,newSize:e})):this},stop:function(){return this.setZoom(this._limitZoom(this._zoom)),this.options.zoomSnap||this.fire("viewreset"),this._stop()},locate:function(t){if(t=this._locateOptions=l({timeout:1e4,watch:!1},t),!("geolocation"in navigator))return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var i=a(this._handleGeolocationResponse,this),e=a(this._handleGeolocationError,this);return t.watch?this._locationWatchId=navigator.geolocation.watchPosition(i,e,t):navigator.geolocation.getCurrentPosition(i,e,t),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var i;this._container._leaflet_id&&(i=t.code,t=t.message||(1===i?"permission denied":2===i?"position unavailable":"timeout"),this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:i,message:"Geolocation error: "+t+"."}))},_handleGeolocationResponse:function(t){if(this._container._leaflet_id){var i,e,n=new v(t.coords.latitude,t.coords.longitude),o=n.toBounds(2*t.coords.accuracy),s=this._locateOptions,r=(s.setView&&(i=this.getBoundsZoom(o),this.setView(n,s.maxZoom?Math.min(i,s.maxZoom):i)),{latlng:n,bounds:o,timestamp:t.timestamp});for(e in t.coords)"number"==typeof t.coords[e]&&(r[e]=t.coords[e]);this.fire("locationfound",r)}},addHandler:function(t,i){if(!i)return this;i=this[t]=new i(this);return this._handlers.push(i),this.options[t]&&i.enable(),this},remove:function(){if(this._initEvents(!0),this.options.maxBounds&&this.off("moveend",this._panInsideMaxBounds),this._containerId!==this._container._leaflet_id)throw new Error("Map container is being reused by another instance");try{delete this._container._leaflet_id,delete this._containerId}catch(t){this._container._leaflet_id=void 0,this._containerId=void 0}for(var t in void 0!==this._locationWatchId&&this.stopLocate(),this._stop(),T(this._mapPane),this._clearControlPos&&this._clearControlPos(),this._resizeRequest&&(r(this._resizeRequest),this._resizeRequest=null),this._clearHandlers(),this._loaded&&this.fire("unload"),this._layers)this._layers[t].remove();for(t in this._panes)T(this._panes[t]);return this._layers=[],this._panes=[],delete this._mapPane,delete this._renderer,this},createPane:function(t,i){i=b("div","leaflet-pane"+(t?" leaflet-"+t.replace("Pane","")+"-pane":""),i||this._mapPane);return t&&(this._panes[t]=i),i},getCenter:function(){return this._checkIfLoaded(),this._lastCenter&&!this._moved()?this._lastCenter:this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds();return new s(this.unproject(t.getBottomLeft()),this.unproject(t.getTopRight()))},getMinZoom:function(){return void 0===this.options.minZoom?this._layersMinZoom||0:this.options.minZoom},getMaxZoom:function(){return void 0===this.options.maxZoom?void 0===this._layersMaxZoom?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(t,i,e){t=g(t),e=_(e||[0,0]);var n=this.getZoom()||0,o=this.getMinZoom(),s=this.getMaxZoom(),r=t.getNorthWest(),t=t.getSouthEast(),e=this.getSize().subtract(e),t=f(this.project(t,n),this.project(r,n)).getSize(),r=P.any3d?this.options.zoomSnap:1,a=e.x/t.x,e=e.y/t.y,t=i?Math.max(a,e):Math.min(a,e),n=this.getScaleZoom(t,n);return r&&(n=Math.round(n/(r/100))*(r/100),n=i?Math.ceil(n/r)*r:Math.floor(n/r)*r),Math.max(o,Math.min(s,n))},getSize:function(){return this._size&&!this._sizeChanged||(this._size=new p(this._container.clientWidth||0,this._container.clientHeight||0),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(t,i){t=this._getTopLeftPoint(t,i);return new m(t,t.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._pixelOrigin},getPixelWorldBounds:function(t){return this.options.crs.getProjectedBounds(void 0===t?this.getZoom():t)},getPane:function(t){return"string"==typeof t?this._panes[t]:t},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t,i){var e=this.options.crs;return i=void 0===i?this._zoom:i,e.scale(t)/e.scale(i)},getScaleZoom:function(t,i){var e=this.options.crs,t=(i=void 0===i?this._zoom:i,e.zoom(t*e.scale(i)));return isNaN(t)?1/0:t},project:function(t,i){return i=void 0===i?this._zoom:i,this.options.crs.latLngToPoint(w(t),i)},unproject:function(t,i){return i=void 0===i?this._zoom:i,this.options.crs.pointToLatLng(_(t),i)},layerPointToLatLng:function(t){t=_(t).add(this.getPixelOrigin());return this.unproject(t)},latLngToLayerPoint:function(t){return this.project(w(t))._round()._subtract(this.getPixelOrigin())},wrapLatLng:function(t){return this.options.crs.wrapLatLng(w(t))},wrapLatLngBounds:function(t){return this.options.crs.wrapLatLngBounds(g(t))},distance:function(t,i){return this.options.crs.distance(w(t),w(i))},containerPointToLayerPoint:function(t){return _(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return _(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){t=this.containerPointToLayerPoint(_(t));return this.layerPointToLatLng(t)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(w(t)))},mouseEventToContainerPoint:function(t){return Ni(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){t=this._container=_i(t);if(!t)throw new Error("Map container not found.");if(t._leaflet_id)throw new Error("Map container is already initialized.");S(t,"scroll",this._onScroll,this),this._containerId=h(t)},_initLayout:function(){var t=this._container,i=(this._fadeAnimated=this.options.fadeAnimation&&P.any3d,z(t,"leaflet-container"+(P.touch?" leaflet-touch":"")+(P.retina?" leaflet-retina":"")+(P.ielt9?" leaflet-oldie":"")+(P.safari?" leaflet-safari":"")+(this._fadeAnimated?" leaflet-fade-anim":"")),pi(t,"position"));"absolute"!==i&&"relative"!==i&&"fixed"!==i&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._paneRenderers={},this._mapPane=this.createPane("mapPane",this._container),Z(this._mapPane,new p(0,0)),this.createPane("tilePane"),this.createPane("overlayPane"),this.createPane("shadowPane"),this.createPane("markerPane"),this.createPane("tooltipPane"),this.createPane("popupPane"),this.options.markerZoomAnimation||(z(t.markerPane,"leaflet-zoom-hide"),z(t.shadowPane,"leaflet-zoom-hide"))},_resetView:function(t,i){Z(this._mapPane,new p(0,0));var e=!this._loaded,n=(this._loaded=!0,i=this._limitZoom(i),this.fire("viewprereset"),this._zoom!==i);this._moveStart(n,!1)._move(t,i)._moveEnd(n),this.fire("viewreset"),e&&this.fire("load")},_moveStart:function(t,i){return t&&this.fire("zoomstart"),i||this.fire("movestart"),this},_move:function(t,i,e,n){void 0===i&&(i=this._zoom);var o=this._zoom!==i;return this._zoom=i,this._lastCenter=t,this._pixelOrigin=this._getNewPixelOrigin(t),n?e&&e.pinch&&this.fire("zoom",e):((o||e&&e.pinch)&&this.fire("zoom",e),this.fire("move",e)),this},_moveEnd:function(t){return t&&this.fire("zoomend"),this.fire("moveend")},_stop:function(){return r(this._flyToFrame),this._panAnim&&this._panAnim.stop(),this},_rawPanBy:function(t){Z(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_panInsideMaxBounds:function(){this._enforcingBounds||this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(t){this._targets={};var i=t?E:S;i((this._targets[h(this._container)]=this)._container,"click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress keydown keyup",this._handleDOMEvent,this),this.options.trackResize&&i(window,"resize",this._onResize,this),P.any3d&&this.options.transform3DLimit&&(t?this.off:this.on).call(this,"moveend",this._onMoveEnd)},_onResize:function(){r(this._resizeRequest),this._resizeRequest=x(function(){this.invalidateSize({debounceMoveend:!0})},this)},_onScroll:function(){this._container.scrollTop=0,this._container.scrollLeft=0},_onMoveEnd:function(){var t=this._getMapPanePos();Math.max(Math.abs(t.x),Math.abs(t.y))>=this.options.transform3DLimit&&this._resetView(this.getCenter(),this.getZoom())},_findEventTargets:function(t,i){for(var e,n=[],o="mouseout"===i||"mouseover"===i,s=t.target||t.srcElement,r=!1;s;){if((e=this._targets[h(s)])&&("click"===i||"preclick"===i)&&this._draggableMoved(e)){r=!0;break}if(e&&e.listens(i,!0)){if(o&&!Hi(s,t))break;if(n.push(e),o)break}if(s===this._container)break;s=s.parentNode}return n=n.length||r||o||!this.listens(i,!0)?n:[this]},_isClickDisabled:function(t){for(;t!==this._container;){if(t._leaflet_disable_click)return!0;t=t.parentNode}},_handleDOMEvent:function(t){var i,e=t.target||t.srcElement;!this._loaded||e._leaflet_disable_events||"click"===t.type&&this._isClickDisabled(e)||("mousedown"===(i=t.type)&&zi(e),this._fireDOMEvent(t,i))},_mouseEvents:["click","dblclick","mouseover","mouseout","contextmenu"],_fireDOMEvent:function(t,i,e){"click"===t.type&&((a=l({},t)).type="preclick",this._fireDOMEvent(a,a.type,e));var n=this._findEventTargets(t,i);if(e){for(var o=[],s=0;sthis.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(i),n=this._getCenterOffset(t)._divideBy(1-1/n);return!(!0!==e.animate&&!this.getSize().contains(n))&&(x(function(){this._moveStart(!0,!1)._animateZoom(t,i,!0)},this),!0)},_animateZoom:function(t,i,e,n){this._mapPane&&(e&&(this._animatingZoom=!0,this._animateToCenter=t,this._animateToZoom=i,z(this._mapPane,"leaflet-zoom-anim")),this.fire("zoomanim",{center:t,zoom:i,noUpdate:n}),this._tempFireZoomEvent||(this._tempFireZoomEvent=this._zoom!==this._animateToZoom),this._move(this._animateToCenter,this._animateToZoom,void 0,!0),setTimeout(a(this._onZoomTransitionEnd,this),250))},_onZoomTransitionEnd:function(){this._animatingZoom&&(this._mapPane&&M(this._mapPane,"leaflet-zoom-anim"),this._animatingZoom=!1,this._move(this._animateToCenter,this._animateToZoom,void 0,!0),this._tempFireZoomEvent&&this.fire("zoom"),delete this._tempFireZoomEvent,this.fire("move"),this._moveEnd(!0))}});function Fi(t){return new I(t)}var Ui,I=it.extend({options:{position:"topright"},initialize:function(t){c(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var i=this._map;return i&&i.removeControl(this),this.options.position=t,i&&i.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this.remove(),this._map=t;var i=this._container=this.onAdd(t),e=this.getPosition(),t=t._controlCorners[e];return z(i,"leaflet-control"),-1!==e.indexOf("bottom")?t.insertBefore(i,t.firstChild):t.appendChild(i),this._map.on("unload",this.remove,this),this},remove:function(){return this._map&&(T(this._container),this.onRemove&&this.onRemove(this._map),this._map.off("unload",this.remove,this),this._map=null),this},_refocusOnMap:function(t){this._map&&t&&0",i=document.createElement("div");return i.innerHTML=t,i.firstChild},_addItem:function(t){var i,e=document.createElement("label"),n=this._map.hasLayer(t.layer),n=(t.overlay?((i=document.createElement("input")).type="checkbox",i.className="leaflet-control-layers-selector",i.defaultChecked=n):i=this._createRadioElement("leaflet-base-layers_"+h(this),n),this._layerControlInputs.push(i),i.layerId=h(t.layer),S(i,"click",this._onInputClick,this),document.createElement("span")),o=(n.innerHTML=" "+t.name,document.createElement("span"));return e.appendChild(o),o.appendChild(i),o.appendChild(n),(t.overlay?this._overlaysList:this._baseLayersList).appendChild(e),this._checkDisabledLayers(),e},_onInputClick:function(){var t,i,e=this._layerControlInputs,n=[],o=[];this._handlingClick=!0;for(var s=e.length-1;0<=s;s--)t=e[s],i=this._getLayer(t.layerId).layer,t.checked?n.push(i):t.checked||o.push(i);for(s=0;si.options.maxZoom},_expandIfNotCollapsed:function(){return this._map&&!this.options.collapsed&&this.expand(),this}})),qi=I.extend({options:{position:"topleft",zoomInText:'',zoomInTitle:"Zoom in",zoomOutText:'',zoomOutTitle:"Zoom out"},onAdd:function(t){var i="leaflet-control-zoom",e=b("div",i+" leaflet-bar"),n=this.options;return this._zoomInButton=this._createButton(n.zoomInText,n.zoomInTitle,i+"-in",e,this._zoomIn),this._zoomOutButton=this._createButton(n.zoomOutText,n.zoomOutTitle,i+"-out",e,this._zoomOut),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),e},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},disable:function(){return this._disabled=!0,this._updateDisabled(),this},enable:function(){return this._disabled=!1,this._updateDisabled(),this},_zoomIn:function(t){!this._disabled&&this._map._zoomthis._map.getMinZoom()&&this._map.zoomOut(this._map.options.zoomDelta*(t.shiftKey?3:1))},_createButton:function(t,i,e,n,o){e=b("a",e,n);return e.innerHTML=t,e.href="#",e.title=i,e.setAttribute("role","button"),e.setAttribute("aria-label",i),Oi(e),S(e,"click",Ri),S(e,"click",o,this),S(e,"click",this._refocusOnMap,this),e},_updateDisabled:function(){var t=this._map,i="leaflet-disabled";M(this._zoomInButton,i),M(this._zoomOutButton,i),this._zoomInButton.setAttribute("aria-disabled","false"),this._zoomOutButton.setAttribute("aria-disabled","false"),!this._disabled&&t._zoom!==t.getMinZoom()||(z(this._zoomOutButton,i),this._zoomOutButton.setAttribute("aria-disabled","true")),!this._disabled&&t._zoom!==t.getMaxZoom()||(z(this._zoomInButton,i),this._zoomInButton.setAttribute("aria-disabled","true"))}}),Gi=(A.mergeOptions({zoomControl:!0}),A.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new qi,this.addControl(this.zoomControl))}),I.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0},onAdd:function(t){var i="leaflet-control-scale",e=b("div",i),n=this.options;return this._addScales(n,i+"-line",e),t.on(n.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),e},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,i,e){t.metric&&(this._mScale=b("div",i,e)),t.imperial&&(this._iScale=b("div",i,e))},_update:function(){var t=this._map,i=t.getSize().y/2,t=t.distance(t.containerPointToLatLng([0,i]),t.containerPointToLatLng([this.options.maxWidth,i]));this._updateScales(t)},_updateScales:function(t){this.options.metric&&t&&this._updateMetric(t),this.options.imperial&&t&&this._updateImperial(t)},_updateMetric:function(t){var i=this._getRoundNum(t);this._updateScale(this._mScale,i<1e3?i+" m":i/1e3+" km",i/t)},_updateImperial:function(t){var i,e,t=3.2808399*t;5280'+(P.inlineSvg?' ':"")+"Leaflet"},initialize:function(t){c(this,t),this._attributions={}},onAdd:function(t){for(var i in(t.attributionControl=this)._container=b("div","leaflet-control-attribution"),Oi(this._container),t._layers)t._layers[i].getAttribution&&this.addAttribution(t._layers[i].getAttribution());return this._update(),t.on("layeradd",this._addAttribution,this),this._container},onRemove:function(t){t.off("layeradd",this._addAttribution,this)},_addAttribution:function(t){t.layer.getAttribution&&(this.addAttribution(t.layer.getAttribution()),t.layer.once("remove",function(){this.removeAttribution(t.layer.getAttribution())},this))},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t&&(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update()),this},removeAttribution:function(t){return t&&this._attributions[t]&&(this._attributions[t]--,this._update()),this},_update:function(){if(this._map){var t,i=[];for(t in this._attributions)this._attributions[t]&&i.push(t);var e=[];this.options.prefix&&e.push(this.options.prefix),i.length&&e.push(i.join(", ")),this._container.innerHTML=e.join(' ')}}}),n=(A.mergeOptions({attributionControl:!0}),A.addInitHook(function(){this.options.attributionControl&&(new Ki).addTo(this)}),I.Layers=Vi,I.Zoom=qi,I.Scale=Gi,I.Attribution=Ki,Fi.layers=function(t,i,e){return new Vi(t,i,e)},Fi.zoom=function(t){return new qi(t)},Fi.scale=function(t){return new Gi(t)},Fi.attribution=function(t){return new Ki(t)},it.extend({initialize:function(t){this._map=t},enable:function(){return this._enabled||(this._enabled=!0,this.addHooks()),this},disable:function(){return this._enabled&&(this._enabled=!1,this.removeHooks()),this},enabled:function(){return!!this._enabled}})),ft=(n.addTo=function(t,i){return t.addHandler(i,this),this},{Events:i}),Yi=P.touch?"touchstart mousedown":"mousedown",Xi=et.extend({options:{clickTolerance:3},initialize:function(t,i,e,n){c(this,n),this._element=t,this._dragStartTarget=i||t,this._preventOutline=e},enable:function(){this._enabled||(S(this._dragStartTarget,Yi,this._onDown,this),this._enabled=!0)},disable:function(){this._enabled&&(Xi._dragging===this&&this.finishDrag(!0),E(this._dragStartTarget,Yi,this._onDown,this),this._enabled=!1,this._moved=!1)},_onDown:function(t){var i,e;this._enabled&&(this._moved=!1,vi(this._element,"leaflet-zoom-anim")||(t.touches&&1!==t.touches.length?Xi._dragging===this&&this.finishDrag():Xi._dragging||t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||((Xi._dragging=this)._preventOutline&&zi(this._element),Li(),ri(),this._moving||(this.fire("down"),e=t.touches?t.touches[0]:t,i=Ci(this._element),this._startPoint=new p(e.clientX,e.clientY),this._startPos=bi(this._element),this._parentScale=Zi(i),e="mousedown"===t.type,S(document,e?"mousemove":"touchmove",this._onMove,this),S(document,e?"mouseup":"touchend touchcancel",this._onUp,this)))))},_onMove:function(t){var i;this._enabled&&(t.touches&&1i&&(e.push(t[n]),o=n);oi.max.x&&(e|=2),t.yi.max.y&&(e|=8),e}function ee(t,i,e,n){var o=i.x,i=i.y,s=e.x-o,r=e.y-i,a=s*s+r*r;return 0this._layersMaxZoom&&this.setZoom(this._layersMaxZoom),void 0===this.options.minZoom&&this._layersMinZoom&&this.getZoom()t.y!=n.y>t.y&&t.x<(n.x-e.x)*(t.y-e.y)/(n.y-e.y)+e.x&&(l=!l);return l||fe.prototype._containsPoint.call(this,t,!0)}});var ve=he.extend({initialize:function(t,i){c(this,i),this._layers={},t&&this.addData(t)},addData:function(t){var i,e,n,o=d(t)?t:t.features;if(o){for(i=0,e=o.length;ir.x&&(a=n.x+h-r.x+s.x),n.x-a-o.x<(h=0)&&(a=n.x-o.x),n.y+e+s.y>r.y&&(h=n.y+e-r.y+s.y),n.y-h-o.y<0&&(h=n.y-o.y),(a||h)&&i.fire("autopanstart").panBy([a,h],{animate:t&&"moveend"===t.type}))},_getAnchor:function(){return _(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}})),Ee=(A.mergeOptions({closePopupOnClick:!0}),A.include({openPopup:function(t,i,e){return this._initOverlay(ke,t,i,e).openOn(this),this},closePopup:function(t){return(t=arguments.length?t:this._popup)&&t.close(),this}}),o.include({bindPopup:function(t,i){return this._popup=this._initOverlay(ke,this._popup,t,i),this._popupHandlersAdded||(this.on({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t){return this._popup&&this._popup._prepareOpen(t)&&this._popup.openOn(this._map),this},closePopup:function(){return this._popup&&this._popup.close(),this},togglePopup:function(){return this._popup&&this._popup.toggle(this),this},isPopupOpen:function(){return!!this._popup&&this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var i;this._popup&&this._map&&(Ri(t),i=t.layer||t.target,this._popup._source!==i||i instanceof _e?(this._popup._source=i,this.openPopup(t.latlng)):this._map.hasLayer(this._popup)?this.closePopup():this.openPopup(t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)},_onKeyPress:function(t){13===t.originalEvent.keyCode&&this._openPopup(t)}}),O.extend({options:{pane:"tooltipPane",offset:[0,0],direction:"auto",permanent:!1,sticky:!1,opacity:.9},onAdd:function(t){O.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire("tooltipopen",{tooltip:this}),this._source&&(this.addEventParent(this._source),this._source.fire("tooltipopen",{tooltip:this},!0))},onRemove:function(t){O.prototype.onRemove.call(this,t),t.fire("tooltipclose",{tooltip:this}),this._source&&(this.removeEventParent(this._source),this._source.fire("tooltipclose",{tooltip:this},!0))},getEvents:function(){var t=O.prototype.getEvents.call(this);return this.options.permanent||(t.preclick=this.close),t},_initLayout:function(){var t="leaflet-tooltip "+(this.options.className||"")+" leaflet-zoom-"+(this._zoomAnimated?"animated":"hide");this._contentNode=this._container=b("div",t)},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var i,e=this._map,n=this._container,o=e.latLngToContainerPoint(e.getCenter()),e=e.layerPointToContainerPoint(t),s=this.options.direction,r=n.offsetWidth,a=n.offsetHeight,h=_(this.options.offset),l=this._getAnchor(),e="top"===s?(i=r/2,a):"bottom"===s?(i=r/2,0):(i="center"===s?r/2:"right"===s?0:"left"===s?r:e.xthis.options.maxZoom||nthis.options.maxZoom||void 0!==this.options.minZoom&&oe.max.x)||!i.wrapLat&&(t.ye.max.y))return!1}if(!this.options.bounds)return!0;i=this._tileCoordsToBounds(t);return g(this.options.bounds).overlaps(i)},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToNwSe:function(t){var i=this._map,e=this.getTileSize(),n=t.scaleBy(e),e=n.add(e);return[i.unproject(n,t.z),i.unproject(e,t.z)]},_tileCoordsToBounds:function(t){t=this._tileCoordsToNwSe(t),t=new s(t[0],t[1]);return t=this.options.noWrap?t:this._map.wrapLatLngBounds(t)},_tileCoordsToKey:function(t){return t.x+":"+t.y+":"+t.z},_keyToTileCoords:function(t){var t=t.split(":"),i=new p(+t[0],+t[1]);return i.z=+t[2],i},_removeTile:function(t){var i=this._tiles[t];i&&(T(i.el),delete this._tiles[t],this.fire("tileunload",{tile:i.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){z(t,"leaflet-tile");var i=this.getTileSize();t.style.width=i.x+"px",t.style.height=i.y+"px",t.onselectstart=u,t.onmousemove=u,P.ielt9&&this.options.opacity<1&&C(t,this.options.opacity)},_addTile:function(t,i){var e=this._getTilePos(t),n=this._tileCoordsToKey(t),o=this.createTile(this._wrapCoords(t),a(this._tileReady,this,t));this._initTile(o),this.createTile.length<2&&x(a(this._tileReady,this,t,null,o)),Z(o,e),this._tiles[n]={el:o,coords:t,current:!0},i.appendChild(o),this.fire("tileloadstart",{tile:o,coords:t})},_tileReady:function(t,i,e){i&&this.fire("tileerror",{error:i,tile:e,coords:t});var n=this._tileCoordsToKey(t);(e=this._tiles[n])&&(e.loaded=+new Date,this._map._fadeAnimated?(C(e.el,0),r(this._fadeFrame),this._fadeFrame=x(this._updateOpacity,this)):(e.active=!0,this._pruneTiles()),i||(z(e.el,"leaflet-tile-loaded"),this.fire("tileload",{tile:e.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire("load"),P.ielt9||!this._map._fadeAnimated?x(this._pruneTiles,this):setTimeout(a(this._pruneTiles,this),250)))},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var i=new p(this._wrapX?H(t.x,this._wrapX):t.x,this._wrapY?H(t.y,this._wrapY):t.y);return i.z=t.z,i},_pxBoundsToTileRange:function(t){var i=this.getTileSize();return new m(t.min.unscaleBy(i).floor(),t.max.unscaleBy(i).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}});var Ie=Ae.extend({options:{minZoom:0,maxZoom:18,subdomains:"abc",errorTileUrl:"",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1,referrerPolicy:!1},initialize:function(t,i){this._url=t,(i=c(this,i)).detectRetina&&P.retina&&0')}}catch(t){}return function(t){return document.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}(),Mt={_initContainer:function(){this._container=b("div","leaflet-vml-container")},_update:function(){this._map._animatingZoom||(Ne.prototype._update.call(this),this.fire("update"))},_initPath:function(t){var i=t._container=He("shape");z(i,"leaflet-vml-shape "+(this.options.className||"")),i.coordsize="1 1",t._path=He("path"),i.appendChild(t._path),this._updateStyle(t),this._layers[h(t)]=t},_addPath:function(t){var i=t._container;this._container.appendChild(i),t.options.interactive&&t.addInteractiveTarget(i)},_removePath:function(t){var i=t._container;T(i),t.removeInteractiveTarget(i),delete this._layers[h(t)]},_updateStyle:function(t){var i=t._stroke,e=t._fill,n=t.options,o=t._container;o.stroked=!!n.stroke,o.filled=!!n.fill,n.stroke?(i=i||(t._stroke=He("stroke")),o.appendChild(i),i.weight=n.weight+"px",i.color=n.color,i.opacity=n.opacity,n.dashArray?i.dashStyle=d(n.dashArray)?n.dashArray.join(" "):n.dashArray.replace(/( *, *)/g," "):i.dashStyle="",i.endcap=n.lineCap.replace("butt","flat"),i.joinstyle=n.lineJoin):i&&(o.removeChild(i),t._stroke=null),n.fill?(e=e||(t._fill=He("fill")),o.appendChild(e),e.color=n.fillColor||n.color,e.opacity=n.fillOpacity):e&&(o.removeChild(e),t._fill=null)},_updateCircle:function(t){var i=t._point.round(),e=Math.round(t._radius),n=Math.round(t._radiusY||e);this._setPath(t,t._empty()?"M0 0":"AL "+i.x+","+i.y+" "+e+","+n+" 0,23592600")},_setPath:function(t,i){t._path.v=i},_bringToFront:function(t){fi(t._container)},_bringToBack:function(t){gi(t._container)}},We=P.vml?He:ct,Fe=Ne.extend({_initContainer:function(){this._container=We("svg"),this._container.setAttribute("pointer-events","none"),this._rootGroup=We("g"),this._container.appendChild(this._rootGroup)},_destroyContainer:function(){T(this._container),E(this._container),delete this._container,delete this._rootGroup,delete this._svgSize},_update:function(){var t,i,e;this._map._animatingZoom&&this._bounds||(Ne.prototype._update.call(this),i=(t=this._bounds).getSize(),e=this._container,this._svgSize&&this._svgSize.equals(i)||(this._svgSize=i,e.setAttribute("width",i.x),e.setAttribute("height",i.y)),Z(e,t.min),e.setAttribute("viewBox",[t.min.x,t.min.y,i.x,i.y].join(" ")),this.fire("update"))},_initPath:function(t){var i=t._path=We("path");t.options.className&&z(i,t.options.className),t.options.interactive&&z(i,"leaflet-interactive"),this._updateStyle(t),this._layers[h(t)]=t},_addPath:function(t){this._rootGroup||this._initContainer(),this._rootGroup.appendChild(t._path),t.addInteractiveTarget(t._path)},_removePath:function(t){T(t._path),t.removeInteractiveTarget(t._path),delete this._layers[h(t)]},_updatePath:function(t){t._project(),t._update()},_updateStyle:function(t){var i=t._path,t=t.options;i&&(t.stroke?(i.setAttribute("stroke",t.color),i.setAttribute("stroke-opacity",t.opacity),i.setAttribute("stroke-width",t.weight),i.setAttribute("stroke-linecap",t.lineCap),i.setAttribute("stroke-linejoin",t.lineJoin),t.dashArray?i.setAttribute("stroke-dasharray",t.dashArray):i.removeAttribute("stroke-dasharray"),t.dashOffset?i.setAttribute("stroke-dashoffset",t.dashOffset):i.removeAttribute("stroke-dashoffset")):i.setAttribute("stroke","none"),t.fill?(i.setAttribute("fill",t.fillColor||t.color),i.setAttribute("fill-opacity",t.fillOpacity),i.setAttribute("fill-rule",t.fillRule||"evenodd")):i.setAttribute("fill","none"))},_updatePoly:function(t,i){this._setPath(t,dt(t._parts,i))},_updateCircle:function(t){var i=t._point,e=Math.max(Math.round(t._radius),1),n="a"+e+","+(Math.max(Math.round(t._radiusY),1)||e)+" 0 1,0 ",i=t._empty()?"M0 0":"M"+(i.x-e)+","+i.y+n+2*e+",0 "+n+2*-e+",0 ";this._setPath(t,i)},_setPath:function(t,i){t._path.setAttribute("d",i)},_bringToFront:function(t){fi(t._path)},_bringToBack:function(t){gi(t._path)}});function Ue(t){return P.svg||P.vml?new Fe(t):null}P.vml&&Fe.include(Mt),A.include({getRenderer:function(t){t=(t=t.options.renderer||this._getPaneRenderer(t.options.pane)||this.options.renderer||this._renderer)||(this._renderer=this._createRenderer());return this.hasLayer(t)||this.addLayer(t),t},_getPaneRenderer:function(t){if("overlayPane"===t||void 0===t)return!1;var i=this._paneRenderers[t];return void 0===i&&(i=this._createRenderer({pane:t}),this._paneRenderers[t]=i),i},_createRenderer:function(t){return this.options.preferCanvas&&je(t)||Ue(t)}});var Ve=ge.extend({initialize:function(t,i){ge.prototype.initialize.call(this,this._boundsToLatLngs(t),i)},setBounds:function(t){return this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return[(t=g(t)).getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}});Fe.create=We,Fe.pointsToPath=dt,ve.geometryToLayer=ye,ve.coordsToLatLng=we,ve.coordsToLatLngs=Pe,ve.latLngToCoords=be,ve.latLngsToCoords=Le,ve.getFeature=Te,ve.asFeature=ze,A.mergeOptions({boxZoom:!0});var _t=n.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._resetStateTimeout=0,t.on("unload",this._destroy,this)},addHooks:function(){S(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){E(this._container,"mousedown",this._onMouseDown,this)},moved:function(){return this._moved},_destroy:function(){T(this._pane),delete this._pane},_resetState:function(){this._resetStateTimeout=0,this._moved=!1},_clearDeferredResetState:function(){0!==this._resetStateTimeout&&(clearTimeout(this._resetStateTimeout),this._resetStateTimeout=0)},_onMouseDown:function(t){if(!t.shiftKey||1!==t.which&&1!==t.button)return!1;this._clearDeferredResetState(),this._resetState(),ri(),Li(),this._startPoint=this._map.mouseEventToContainerPoint(t),S(document,{contextmenu:Ri,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=b("div","leaflet-zoom-box",this._container),z(this._container,"leaflet-crosshair"),this._map.fire("boxzoomstart")),this._point=this._map.mouseEventToContainerPoint(t);var t=new m(this._point,this._startPoint),i=t.getSize();Z(this._box,t.min),this._box.style.width=i.x+"px",this._box.style.height=i.y+"px"},_finish:function(){this._moved&&(T(this._box),M(this._container,"leaflet-crosshair")),ai(),Ti(),E(document,{contextmenu:Ri,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){1!==t.which&&1!==t.button||(this._finish(),this._moved&&(this._clearDeferredResetState(),this._resetStateTimeout=setTimeout(a(this._resetState,this),0),t=new s(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point)),this._map.fitBounds(t).fire("boxzoomend",{boxZoomBounds:t})))},_onKeyDown:function(t){27===t.keyCode&&(this._finish(),this._clearDeferredResetState(),this._resetState())}}),Ct=(A.addInitHook("addHandler","boxZoom",_t),A.mergeOptions({doubleClickZoom:!0}),n.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var i=this._map,e=i.getZoom(),n=i.options.zoomDelta,e=t.originalEvent.shiftKey?e-n:e+n;"center"===i.options.doubleClickZoom?i.setZoom(e):i.setZoomAround(t.containerPoint,e)}})),Zt=(A.addInitHook("addHandler","doubleClickZoom",Ct),A.mergeOptions({dragging:!0,inertia:!0,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,easeLinearity:.2,worldCopyJump:!1,maxBoundsViscosity:0}),n.extend({addHooks:function(){var t;this._draggable||(t=this._map,this._draggable=new Xi(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),this._draggable.on("predrag",this._onPreDragLimit,this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDragWrap,this),t.on("zoomend",this._onZoomEnd,this),t.whenReady(this._onZoomEnd,this))),z(this._map._container,"leaflet-grab leaflet-touch-drag"),this._draggable.enable(),this._positions=[],this._times=[]},removeHooks:function(){M(this._map._container,"leaflet-grab"),M(this._map._container,"leaflet-touch-drag"),this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},moving:function(){return this._draggable&&this._draggable._moving},_onDragStart:function(){var t,i=this._map;i._stop(),this._map.options.maxBounds&&this._map.options.maxBoundsViscosity?(t=g(this._map.options.maxBounds),this._offsetLimit=f(this._map.latLngToContainerPoint(t.getNorthWest()).multiplyBy(-1),this._map.latLngToContainerPoint(t.getSouthEast()).multiplyBy(-1).add(this._map.getSize())),this._viscosity=Math.min(1,Math.max(0,this._map.options.maxBoundsViscosity))):this._offsetLimit=null,i.fire("movestart").fire("dragstart"),i.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(t){var i,e;this._map.options.inertia&&(i=this._lastTime=+new Date,e=this._lastPos=this._draggable._absPos||this._draggable._newPos,this._positions.push(e),this._times.push(i),this._prunePositions(i)),this._map.fire("move",t).fire("drag",t)},_prunePositions:function(t){for(;1i.max.x&&(t.x=this._viscousLimit(t.x,i.max.x)),t.y>i.max.y&&(t.y=this._viscousLimit(t.y,i.max.y)),this._draggable._newPos=this._draggable._startPos.add(t))},_onPreDragWrap:function(){var t=this._worldWidth,i=Math.round(t/2),e=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-i+e)%t+i-e,n=(n+i+e)%t-i-e,t=Math.abs(o+e)i.getMaxZoom()&&1=1.3?"crs":"srs";this.wmsParams[b]=this._crs.code,c.NonTiledLayer.prototype.onAdd.call(this,a)},getImageUrl:function(a,b,d){var e=this.wmsParams;e.width=b,e.height=d;var f=this._crs.project(a.getNorthWest()),g=this._crs.project(a.getSouthEast()),h=this._wmsUrl,i=i=(this._wmsVersion>=1.3&&this._crs===c.CRS.EPSG4326?[g.y,f.x,f.y,g.x]:[f.x,g.y,g.x,f.y]).join(",");return h+c.Util.getParamString(this.wmsParams,h,this.options.uppercase)+(this.options.uppercase?"&BBOX=":"&bbox=")+i},setParams:function(a,b){return c.extend(this.wmsParams,a),b||this.redraw(),this}}),c.nonTiledLayer.wms=function(a,b){return new c.NonTiledLayer.WMS(a,b)},b.exports=c.NonTiledLayer.WMS}).call(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],2:[function(a,b,c){(function(a){(function(){"use strict";var c="undefined"!=typeof window?window.L:void 0!==a?a.L:null;c.NonTiledLayer=(c.Layer||c.Class).extend({includes:c.Evented||c.Mixin.Events,emptyImageUrl:"data:image/gif;base64,R0lGODlhAQABAHAAACH5BAUAAAAALAAAAAABAAEAAAICRAEAOw==",options:{attribution:"",opacity:1,zIndex:void 0,minZoom:0,maxZoom:18,pointerEvents:null,errorImageUrl:"data:image/gif;base64,R0lGODlhAQABAHAAACH5BAUAAAAALAAAAAABAAEAAAICRAEAOw==",bounds:c.latLngBounds([-85.05,-180],[85.05,180]),useCanvas:void 0,detectRetina:!1},key:"",initialize:function(a){c.setOptions(this,a)},onAdd:function(a){this._map=a,void 0===this._zoomAnimated&&(this._zoomAnimated=c.DomUtil.TRANSITION&&c.Browser.any3d&&!c.Browser.mobileOpera&&this._map.options.zoomAnimation),c.version<"1.0"&&this._map.on(this.getEvents(),this),this._div||(this._div=c.DomUtil.create("div","leaflet-image-layer"),this.options.pointerEvents&&(this._div.style["pointer-events"]=this.options.pointerEvents),void 0!==this.options.zIndex&&(this._div.style.zIndex=this.options.zIndex),void 0!==this.options.opacity&&(this._div.style.opacity=this.options.opacity)),this.getPane().appendChild(this._div);var b=!!window.HTMLCanvasElement;void 0===this.options.useCanvas?this._useCanvas=b:this._useCanvas=this.options.useCanvas,this._useCanvas?(this._bufferCanvas=this._initCanvas(),this._currentCanvas=this._initCanvas()):(this._bufferImage=this._initImage(),this._currentImage=this._initImage()),this._update()},getPane:function(){return c.Layer?c.Layer.prototype.getPane.call(this):(this.options.pane?this._pane=this.options.pane:this._pane=this._map.getPanes().overlayPane,this._pane)},onRemove:function(a){c.version<"1.0"&&this._map.off(this.getEvents(),this),this.getPane().removeChild(this._div),this._useCanvas?(this._div.removeChild(this._bufferCanvas),this._div.removeChild(this._currentCanvas)):(this._div.removeChild(this._bufferImage),this._div.removeChild(this._currentImage))},addTo:function(a){return a.addLayer(this),this},_setZoom:function(){this._useCanvas?(this._currentCanvas._bounds&&this._resetImageScale(this._currentCanvas,!0),this._bufferCanvas._bounds&&this._resetImageScale(this._bufferCanvas)):(this._currentImage._bounds&&this._resetImageScale(this._currentImage,!0),this._bufferImage._bounds&&this._resetImageScale(this._bufferImage))},getEvents:function(){var a={moveend:this._update};return this._zoomAnimated&&(a.zoomanim=this._animateZoom),c.version>="1.0"&&(a.zoom=this._setZoom),a},getElement:function(){return this._div},setOpacity:function(a){return this.options.opacity=a,this._div&&c.DomUtil.setOpacity(this._div,this.options.opacity),this},setZIndex:function(a){return a&&(this.options.zIndex=a,this._div&&(this._div.style.zIndex=a)),this},bringToFront:function(){return this._div&&this.getPane().appendChild(this._div),this},bringToBack:function(){return this._div&&this.getPane().insertBefore(this._div,this.getPane().firstChild),this},getAttribution:function(){return this.options.attribution},_initCanvas:function(){var a=c.DomUtil.create("canvas","leaflet-image-layer");return this._div.appendChild(a),a._image=new Image,this._ctx=a.getContext("2d"),this.options.crossOrigin&&(a._image.crossOrigin=this.options.crossOrigin),this._map.options.zoomAnimation&&c.Browser.any3d?c.DomUtil.addClass(a,"leaflet-zoom-animated"):c.DomUtil.addClass(a,"leaflet-zoom-hide"),c.extend(a._image,{onload:c.bind(this._onImageLoad,this),onerror:c.bind(this._onImageError,this)}),a},_initImage:function(){var a=c.DomUtil.create("img","leaflet-image-layer");return this.options.crossOrigin&&(a.crossOrigin=this.options.crossOrigin),this._div.appendChild(a),this._map.options.zoomAnimation&&c.Browser.any3d?c.DomUtil.addClass(a,"leaflet-zoom-animated"):c.DomUtil.addClass(a,"leaflet-zoom-hide"),c.extend(a,{galleryimg:"no",onselectstart:c.Util.falseFn,onmousemove:c.Util.falseFn,onload:c.bind(this._onImageLoad,this),onerror:c.bind(this._onImageError,this)}),a},redraw:function(){return this._map&&this._update(),this},_animateZoom:function(a){this._useCanvas?(this._currentCanvas._bounds&&this._animateImage(this._currentCanvas,a),this._bufferCanvas._bounds&&this._animateImage(this._bufferCanvas,a)):(this._currentImage._bounds&&this._animateImage(this._currentImage,a),this._bufferImage._bounds&&this._animateImage(this._bufferImage,a))},_animateImage:function(a,b){if(void 0===c.DomUtil.setTransform){var d=this._map,e=a._scale*d.getZoomScale(b.zoom),f=a._bounds.getNorthWest(),g=a._bounds.getSouthEast(),h=d._latLngToNewLayerPoint(f,b.zoom,b.center),i=d._latLngToNewLayerPoint(g,b.zoom,b.center)._subtract(h),j=h._add(i._multiplyBy(.5*(1-1/e)));a.style[c.DomUtil.TRANSFORM]=c.DomUtil.getTranslateString(j)+" scale("+e+") "}else{var d=this._map,e=a._scale*a._sscale*d.getZoomScale(b.zoom),f=a._bounds.getNorthWest(),g=a._bounds.getSouthEast(),h=d._latLngToNewLayerPoint(f,b.zoom,b.center);c.DomUtil.setTransform(a,h,e)}a._lastScale=e},_resetImageScale:function(a,b){var d=new c.Bounds(this._map.latLngToLayerPoint(a._bounds.getNorthWest()),this._map.latLngToLayerPoint(a._bounds.getSouthEast())),e=a._orgBounds.getSize().y,f=d.getSize().y,g=f/e;a._sscale=g,c.DomUtil.setTransform(a,d.min,g)},_resetImage:function(a){var b=new c.Bounds(this._map.latLngToLayerPoint(a._bounds.getNorthWest()),this._map.latLngToLayerPoint(a._bounds.getSouthEast())),d=b.getSize();c.DomUtil.setPosition(a,b.min),a._orgBounds=b,a._sscale=1,this._useCanvas?(a.width=d.x,a.height=d.y):(a.style.width=d.x+"px",a.style.height=d.y+"px")},_getClippedBounds:function(){var a=this._map.getBounds(),b=a.getSouth(),d=a.getNorth(),e=a.getWest(),f=a.getEast(),g=this.options.bounds.getSouth(),h=this.options.bounds.getNorth(),i=this.options.bounds.getWest(),j=this.options.bounds.getEast();bh&&(d=h),ej&&(f=j);var k=new c.LatLng(d,e),l=new c.LatLng(b,f);return new c.LatLngBounds(k,l)},_getImageScale:function(){return this.options.detectRetina&&c.Browser.retina?2:1},_update:function(){var a,b=this._getClippedBounds(),d=this._map.latLngToContainerPoint(b.getNorthWest()),e=this._map.latLngToContainerPoint(b.getSouthEast()),f=e.x-d.x,g=e.y-d.y;if(this._useCanvas?(this._bufferCanvas._scale=this._bufferCanvas._lastScale,this._currentCanvas._scale=this._currentCanvas._lastScale=1,this._bufferCanvas._sscale=1,this._currentCanvas._bounds=b,this._resetImage(this._currentCanvas),a=this._currentCanvas._image,c.DomUtil.setOpacity(a,0)):(this._bufferImage._scale=this._bufferImage._lastScale,this._currentImage._scale=this._currentImage._lastScale=1,this._bufferImage._sscale=1,this._currentImage._bounds=b,this._resetImage(this._currentImage),a=this._currentImage,c.DomUtil.setOpacity(a,0)),this._map.getZoom()this.options.maxZoom||f<32||g<32)return this._div.style.visibility="hidden",a.src=this.emptyImageUrl,this.key=a.key="",void(a.tag=null);this.fire("loading"),f*=this._getImageScale(),g*=this._getImageScale(),this.key=b.getNorthWest()+", "+b.getSouthEast()+", "+f+", "+g,this.getImageUrl?(a.src=this.getImageUrl(b,f,g),a.key=this.key):this.getImageUrlAsync(b,f,g,this.key,function(b,c,d){a.key=b,a.src=c,a.tag=d})},_onImageError:function(a){this.fire("error",a),c.DomUtil.addClass(a.target,"invalid"),a.target.src!==this.options.errorImageUrl&&(a.target.src=this.options.errorImageUrl)},_onImageLoad:function(a){(a.target.src===this.options.errorImageUrl||(c.DomUtil.removeClass(a.target,"invalid"),a.target.key&&a.target.key===this.key))&&(this._onImageDone(a),this.fire("load",a))},_onImageDone:function(a){if(this._useCanvas)this._renderCanvas(a);else{c.DomUtil.setOpacity(this._currentImage,1),c.DomUtil.setOpacity(this._bufferImage,0),this._addInteraction&&this._currentImage.tag&&this._addInteraction(this._currentImage.tag);var b=this._bufferImage;this._bufferImage=this._currentImage,this._currentImage=b}""!==a.target.key&&(this._div.style.visibility="visible")},_renderCanvas:function(a){this._currentCanvas.getContext("2d").drawImage(this._currentCanvas._image,0,0,this._currentCanvas.width,this._currentCanvas.height),c.DomUtil.setOpacity(this._currentCanvas,1),c.DomUtil.setOpacity(this._bufferCanvas,0),this._addInteraction&&this._currentCanvas._image.tag&&this._addInteraction(this._currentCanvas._image.tag);var b=this._bufferCanvas;this._bufferCanvas=this._currentCanvas,this._currentCanvas=b}}),c.nonTiledLayer=function(){return new c.NonTiledLayer},b.exports=c.NonTiledLayer}).call(this)}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}]},{},[2,1])(2)}); +//# sourceMappingURL=NonTiledLayer.js.mapvar sessionId; +var curGeojson; +var curGeojsonId = -1; + +var urlParams = new URLSearchParams(window.location.search); +var qleverBackend = urlParams.get("backend"); +var query = urlParams.get("query"); + +var map = L.map('m', { + renderer: L.canvas(), + preferCanvas: true +}).setView([47.9965, 7.8469], 13); +map.attributionControl.setPrefix('University of Freiburg'); + +var layerControl = L.control.layers([], [], {collapsed:true, position: 'topleft'}).addTo(map); + +var osmLayer = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap', + maxZoom: 19, + opacity:0.9 +}).addTo(map); + +var genError = "

Session has been removed from cache.

Resend request

"; + +function openPopup(data) { + if (data.length > 0) { + let select_variables = []; + let row = []; + + for (let i in data[0]["attrs"]) { + select_variables.push(data[0]["attrs"][i][0]); + row.push(data[0]["attrs"][i][1]); + } + + // code by hannah from old map UI + + // Build the HTML of the popup. + // + // NOTE: We assume that the last column contains the geometry information + // (WKT), which we will not put in the table. + let geometry_column = select_variables.length - 1; + // If the second to last variable exists and is called "?image" or ends in + // "_image", then show an image with that URL in the first column of the + // table. Note that we compute the cell contents here and add it during the + // loop (it has to be the first cell of a table row). + let image_cell = ""; + if (select_variables.length >= 2) { + let image_column = select_variables.length - 2; + if (select_variables[image_column] == "?image" || + select_variables[image_column] == "?flag" || + select_variables[image_column].endsWith("_image")) { + let num_table_rows = select_variables.length - 2; + let image_url = row[image_column]; + if (image_url != null) + image_cell = ""]$/, "") + "\">"]$/, "") + + "\">"; + } + } + + // Now compute the table rows in an array. + let popup_content_strings = []; + select_variables.forEach(function(variable, i) { + // Skip the last column (WKT literal) and the ?image column (if it + // exists). + if (i == geometry_column || + variable == "?image" || variable == "?flag" || variable.endsWith("_image")) return; + + // Take the variable name as one table column and the result value as + // another. Reformat a bit, so that it looks nice in an HTML table. and + // the result value as another. Reformat a bit, so that it looks nice in + // an HTML table. + let key = variable.substring(1); + if (row[i] == null) { row[i] = "---"; } + let value = row[i].replace(/\\([()])/g, "$1") + .replace(/<((.*)\/(.*))>/, + "$3") + .replace(/\^\^.*$/, "") + .replace(/\"(.*)\"(@[a-z]+)?$/, "$1"); + + popup_content_strings.push( + "" + (i == 0 ? image_cell : "") + + "" + key.replace(/_/g, " ") + "" + + "" + value + ""); + }) + let popup_html = "" + popup_content_strings.join("\n") + "
"; + popup_html += 'Export as GeoJSON'; + + if (curGeojson) curGeojson.remove(); + + L.popup({"maxWidth" : 600}) + .setLatLng(data[0]["ll"]) + .setContent(popup_html) + .openOn(map) + .on('remove', function() { + curGeojson.remove(); + curGeojsonId = -1; + }); + + curGeojson = getGeoJsonLayer(data[0].geom); + curGeojsonId = data[0].id; + curGeojson.addTo(map); + } +} + +function getGeoJsonLayer(geom) { + const color = "#e6930e"; + return L.geoJSON(geom, { + style: {color : color, fillColor: color, weight: 7, fillOpacity: 0.2}, + pointToLayer: function (feature, latlng) { + return L.circleMarker(latlng, { + radius: 8, + fillColor: color, + color: color, + weight: 4, + opacity: 1, + fillOpacity: 0.2 + });} + }) +} + +function showError(error) { + document.getElementById("msg").style.display = "block"; + document.getElementById("loader").style.display = "none"; + document.getElementById("msg-inner").style.color = "red"; + document.getElementById("msg-inner").style.fontWeight = "bold"; + document.getElementById("msg-inner").style.fontSize = "20px"; + document.getElementById("msg-inner").innerHTML = error; +} + +function loadMap(id, bounds, numObjects) { + document.getElementById("msg").style.display = "none"; + const ll = L.Projection.SphericalMercator.unproject({"x": bounds[0][0], "y":bounds[0][1]}); + const ur = L.Projection.SphericalMercator.unproject({"x": bounds[1][0], "y":bounds[1][1]}); + const boundsLatLng = [[ll.lat, ll.lng], [ur.lat, ur.lng]]; + map.fitBounds(boundsLatLng); + sessionId = id; + + document.getElementById("stats").innerHTML = "Showing " + numObjects + " objects"; + + const heatmapLayer = L.nonTiledLayer.wms('heatmap', { + minZoom: 0, + maxZoom: 19, + opacity: 0.8, + layers: id, + styles: ["heatmap"], + format: 'image/png', + transparent: true, + }); + + const objectsLayer = L.nonTiledLayer.wms('heatmap', { + minZoom: 0, + maxZoom: 19, + opacity: 0.8, + layers: id, + styles: ["objects"], + format: 'image/png', + transparent: true, + }); + + heatmapLayer.addTo(map).on('error', function() {showError(genError);}); + objectsLayer.on('error', function() {showError(genError);}); + + layerControl.addBaseLayer(heatmapLayer, "Heatmap"); + layerControl.addBaseLayer(objectsLayer, "Objects"); + + map.on('click', function(e) { + const ll= e.latlng; + const pos = L.Projection.SphericalMercator.project(ll); + + fetch('pos?x=' + pos.x + "&y=" + pos.y + "&id=" + id + "&rad=" + (100 * Math.pow(2, 14 - map.getZoom()))) + .then(response => response.json()) + .then(data => openPopup(data)) + .catch(error => showError(genError)); + }); + + map.on('zoomend', function(e) { + if (curGeojsonId > -1) { + fetch('geojson?gid=' + curGeojsonId + "&id=" + id + "&rad=" + (100 * Math.pow(2, 14 - map.getZoom()))) + .then(response => response.json()) + .then(function(data) { + curGeojson.remove(); + curGeojson = getGeoJsonLayer(data); + curGeojson.addTo(map); + }) + .catch(error => showError(genError)); + } + }); +} + +fetch('query' + window.location.search) + .then(response => { + if (!response.ok) return response.text().then(text => {throw new Error(text)}); + return response; + }) + .then(response => response.json()) + .then(data => loadMap(data["qid"], data["bounds"], data["numobjects"])) + .catch(error => {showError(error);}); + +document.getElementById("export-geojson").onclick = function() { + if (!sessionId) return; + let a = document.createElement("a"); + a.href = "export?id="+ sessionId; + a.setAttribute("download", "export.json"); + a.click(); +} + +document.getElementById("export-tsv").onclick = function() { + let a = document.createElement("a"); + a.href = qleverBackend + "?query=" + encodeURIComponent(query) + "&action=tsv_export"; + a.setAttribute("download", "export.tsv"); + a.click(); +} + +document.getElementById("export-csv").onclick = function() { + let a = document.createElement("a"); + a.href = qleverBackend + "?query=" + encodeURIComponent(query) + "&action=csv_export"; + a.setAttribute("download", "export.csv"); + a.click(); +} diff --git a/web/min.js b/web/min.js index 02406d1..4f44231 100644 --- a/web/min.js +++ b/web/min.js @@ -1,20 +1,20 @@ -var e=[12,13,13,13,13,13,13,13],l=[.8,.6,.5,.5,.4],t=[1,1,1,1.5,2,3,5,6,6,4,3,2],u=["#78f378","#0000c3","red"],v,w,x,y,z={},B=-1,C=-1,D="Move into a new relation public_transport=stop_area.;Move into relation ${toid}.;Move from relation ${ooid} into a new relation public_transport=stop_area.;Move from relation ${ooid} into relation ${toid}.;Move out of relation ${ooid};Fix attribute ${attr}.;Consider adding a name attribute.;Attribute ${attr} seems to be a track number. Use ref for this and set ${attr} to the station name.".split(";"); -function E(a){return"#"==a[0]?document.getElementById(a.substr(1)):"."==a[0]?document.getElementsByClassName(a.substr(1)):document.getElementsByTagName(a)}function F(a){return document.createElement(a)}function G(a,c){-1==a.className.split(" ").indexOf(c)&&(a.className+=" "+c);a.className=a.className.trim()}function H(a){return a.e?u[2]:a.O?u[1]:u[0]}function I(a,c){for(p in c)a=a.replace(new RegExp("\\${"+p+"}","g"),c[p]);return a} -function J(a,c,b){z[a]&&z[a].abort();z[a]=new XMLHttpRequest;z[a].onreadystatechange=function(){4==this.readyState&&200==this.status&&this==z[a]&&b(JSON.parse(this.responseText))};z[a].open("GET",c,1);z[a].send()} -function K(a){var c=M.b();return 1==a.u.length?15c&&(b.weight=11,b.opacity=.5,b.B=.5);return L.Z(a.u,b)} -function O(a){M.b();return L.$(a.ga,{id:a.j,color:"#0000c3",P:.1,weight:4,opacity:.5})} -function P(a){C=a.id;Q(a.id);var c={},b=Math.abs(a.c),h=0>a.c?"Way":"Node",n=F("div");n.setAttribute("id","nav");var f=F("div");f.setAttribute("id","sugg");n.innerHTML=h+" "+b+"";a.f.name&&(n.innerHTML+=' ("'+a.f.name+'")');n.innerHTML+="";b=F("table");b.setAttribute("id","attr-tbl");n.appendChild(b);n.appendChild(f); -h=F("tbody");b.appendChild(h);for(var d in a.f){var q=F("tr"),b=F("td"),m=F("td");G(m,"err-wrap");h.appendChild(q);q.appendChild(b);q.appendChild(m);b.innerHTML=''+d+"";for(b=0;b"+a.f[d][b]+"
";c[d]=q}for(b=0;b"+d.g[0]+" in relation '+Math.abs(d.a)+"":"Does not match "+d.g[0]+" in relation "+Math.abs(d.a)+"":d.a!=a.c?(h=0>d.a?"way":"node",m.innerHTML="Does not match "+d.g[0]+" in "+h+" '+Math.abs(d.a)+ -""):m.innerHTML="Does not match "+d.g[0]+" = '"+d.g[1]+"'",G(m,"attr-err-info"),q.childNodes[1].appendChild(m);c=F("ul");a.m.length&&(b=F("span"),G(b,"sugtit"),b.innerHTML="Suggestions",f.appendChild(b));f.appendChild(c);for(b=0;bNew Members";var f=F("div");f.setAttribute("id","group-stations-old");f.innerHTML="Existing Members";1==a.c?h.innerHTML="New relation public_transport=stop_area":(h.innerHTML="OSM relation "+a.c+"",a.f.name&&(h.innerHTML+=' ("'+a.f.name+'")'),h.innerHTML+="");var d=F("table");d.setAttribute("id","attr-tbl");h.appendChild(d);var q=F("tbody");d.appendChild(q);d=F("div");d.setAttribute("id","sugg");for(var m in a.f){var r=F("tr"),k=F("td"),g=F("td");G(g,"err-wrap");q.appendChild(r);r.appendChild(k);r.appendChild(g);k.innerHTML=''+m+"";for(k=0;k"+a.f[m][k]+"
";b[m]=r}for(k=0;k"+g.g[0]+" in relation '+Math.abs(g.a)+"":"Does not match "+g.g[0]+" in relation "+Math.abs(g.a)+"":"Does not match "+g.g[0]+" = '"+g.g[1]+"'":(q=0>g.a?"way":"node",A.innerHTML="Does not match "+g.g[0]+" in "+q+" '+Math.abs(g.a)+"");G(A,"attr-err-info");r.childNodes[1].appendChild(A)}h.appendChild(n);1!=a.c&&h.appendChild(f);for(m in a.da)k=a.da[m],r=F("div"),q=0>k.c?"Way":"Node", -r.innerHTML=q+" "+Math.abs(k.c)+"",k.f.name&&(r.innerHTML+=' ("'+k.f.name+'")'),r.style.backgroundColor=k.e?"#f58d8d":k.O?"#b6b6e4":"#c0f7c0",1==a.c||k.oa!=a.id?n.appendChild(r):(f.appendChild(r),k.group!=a.id&&G(r,"del-stat"));n=F("ul");a.m.length&&(f=F("span"),G(f,"sugtit"),f.innerHTML="Suggestions",d.appendChild(f));d.appendChild(n);for(k= -0;k"+f.s+".":7==f.type?m.innerHTML="Consider adding a name attribute.":8==f.type&&(m.innerHTML="Attribute "+f.s+" seems to be a track number. Use ref for this and set "+f.s+" to the station name."),n.appendChild(m);h.appendChild(d);L.aa({opacity:.8}).ca(c).ba(h).Y(M).l("remove",function(){B=-1;V(a.id)})} -function W(a,c){J("g","/group?id="+a,function(a){T(a,c)})}function U(a){!v[a]||v[a].v({weight:6,color:"#eecc00"})}function V(a){!v[a]||v[a].v({weight:3,color:v[a].options.fillColor})}function Q(a){w[a]&&(15University of Freiburg, Chair of Algorithms and Data Structures'})); -M.l("popupopen",function(a){var c=M.ra(a.target.S.fa);c.y-=a.target.S.ea.clientHeight/2;M.qa(M.ya(c),{ia:!0});X()});L.xa("http://{s}.tile.stamen.com/toner-lite/{z}/{x}/{y}.png",{X:20,U:'© OpenStreetMap',opacity:.8}).T(M);var Y=L.A().T(M);M.l("moveend",function(){Z()});M.l("click",function(){X()}); -function Z(){11>M.b()?J("m","/heatmap?z="+M.b()+"&bbox="+[M.i().D().F,M.i().D().H,M.i().C().F,M.i().C().H].join(),function(a){Y.V();var c=22-M.b(),b=25-M.b();Y.o(L.M(a.ok,{max:500,K:{0:"#cbf7cb","0.5":"#78f378",1:"#29c329"},N:.65,blur:c,J:b}));Y.o(L.M(a.ua,{max:500,K:{0:"#7f7fbd","0.5":"#4444b3",1:"#0606c1"},N:.65,blur:c-3,J:Math.min(12,b-3)}));Y.o(L.M(a.la,{max:500,K:{0:"#f39191","0.5":"#ff5656",1:"#ff0000"},N:.75,blur:c-3,J:Math.min(10,b-3),X:15}))}):J("m","/map?z="+M.b()+"&bbox="+[M.i().D().F, -M.i().D().H,M.i().C().F,M.i().C().H].join(),function(a){Y.V();v={};w={};for(var c=[],b=0;bnew relation public_transport=stop_area.;Move into relation ${toid}.;Move from relation ${ooid} into a new relation public_transport=stop_area.;Move from relation ${ooid} into relation ${toid}.;Move out of relation ${ooid};Fix attribute ${attr}.;Consider adding a name attribute.;Attribute ${attr} seems to be a track number. Use ref for this and set ${attr} to the station name.".split(";"); +function E(a){return"#"==a[0]?document.getElementById(a.substr(1)):"."==a[0]?document.getElementsByClassName(a.substr(1)):document.getElementsByTagName(a)}function F(a){return document.createElement(a)}function G(a,c){-1==a.className.split(" ").indexOf(c)&&(a.className+=" "+c);a.className=a.className.trim()}function H(a){return a.e?u[2]:a.O?u[1]:u[0]}function I(a,c){for(p in c)a=a.replace(new RegExp("\\${"+p+"}","g"),c[p]);return a} +function J(a,c,b){z[a]&&z[a].abort();z[a]=new XMLHttpRequest;z[a].onreadystatechange=function(){4==this.readyState&&200==this.status&&this==z[a]&&b(JSON.parse(this.responseText))};z[a].open("GET",c,1);z[a].send()} +function K(a){var c=M.b();return 1==a.u.length?15c&&(b.weight=11,b.opacity=.5,b.B=.5);return L.Z(a.u,b)} +function O(a){M.b();return L.$(a.ga,{id:a.j,color:"#0000c3",P:.1,weight:4,opacity:.5})} +function P(a){C=a.id;Q(a.id);var c={},b=Math.abs(a.c),h=0>a.c?"Way":"Node",n=F("div");n.setAttribute("id","nav");var f=F("div");f.setAttribute("id","sugg");n.innerHTML=h+" "+b+"";a.f.name&&(n.innerHTML+=' ("'+a.f.name+'")');n.innerHTML+="";b=F("table");b.setAttribute("id","attr-tbl");n.appendChild(b);n.appendChild(f); +h=F("tbody");b.appendChild(h);for(var d in a.f){var q=F("tr"),b=F("td"),m=F("td");G(m,"err-wrap");h.appendChild(q);q.appendChild(b);q.appendChild(m);b.innerHTML=''+d+"";for(b=0;b"+a.f[d][b]+"
";c[d]=q}for(b=0;b"+d.g[0]+" in relation '+Math.abs(d.a)+"":"Does not match "+d.g[0]+" in relation "+Math.abs(d.a)+"":d.a!=a.c?(h=0>d.a?"way":"node",m.innerHTML="Does not match "+d.g[0]+" in "+h+" '+Math.abs(d.a)+ +""):m.innerHTML="Does not match "+d.g[0]+" = '"+d.g[1]+"'",G(m,"attr-err-info"),q.childNodes[1].appendChild(m);c=F("ul");a.m.length&&(b=F("span"),G(b,"sugtit"),b.innerHTML="Suggestions",f.appendChild(b));f.appendChild(c);for(b=0;bNew Members";var f=F("div");f.setAttribute("id","group-stations-old");f.innerHTML="Existing Members";1==a.c?h.innerHTML="New relation public_transport=stop_area":(h.innerHTML="OSM relation "+a.c+"",a.f.name&&(h.innerHTML+=' ("'+a.f.name+'")'),h.innerHTML+="");var d=F("table");d.setAttribute("id","attr-tbl");h.appendChild(d);var q=F("tbody");d.appendChild(q);d=F("div");d.setAttribute("id","sugg");for(var m in a.f){var r=F("tr"),k=F("td"),g=F("td");G(g,"err-wrap");q.appendChild(r);r.appendChild(k);r.appendChild(g);k.innerHTML=''+m+"";for(k=0;k"+a.f[m][k]+"
";b[m]=r}for(k=0;k"+g.g[0]+" in relation '+Math.abs(g.a)+"":"Does not match "+g.g[0]+" in relation "+Math.abs(g.a)+"":"Does not match "+g.g[0]+" = '"+g.g[1]+"'":(q=0>g.a?"way":"node",A.innerHTML="Does not match "+g.g[0]+" in "+q+" '+Math.abs(g.a)+"");G(A,"attr-err-info");r.childNodes[1].appendChild(A)}h.appendChild(n);1!=a.c&&h.appendChild(f);for(m in a.da)k=a.da[m],r=F("div"),q=0>k.c?"Way":"Node", +r.innerHTML=q+" "+Math.abs(k.c)+"",k.f.name&&(r.innerHTML+=' ("'+k.f.name+'")'),r.style.backgroundColor=k.e?"#f58d8d":k.O?"#b6b6e4":"#c0f7c0",1==a.c||k.oa!=a.id?n.appendChild(r):(f.appendChild(r),k.group!=a.id&&G(r,"del-stat"));n=F("ul");a.m.length&&(f=F("span"),G(f,"sugtit"),f.innerHTML="Suggestions",d.appendChild(f));d.appendChild(n);for(k= +0;k"+f.s+".":7==f.type?m.innerHTML="Consider adding a name attribute.":8==f.type&&(m.innerHTML="Attribute "+f.s+" seems to be a track number. Use ref for this and set "+f.s+" to the station name."),n.appendChild(m);h.appendChild(d);L.aa({opacity:.8}).ca(c).ba(h).Y(M).l("remove",function(){B=-1;V(a.id)})} +function W(a,c){J("g","/group?id="+a,function(a){T(a,c)})}function U(a){!v[a]||v[a].v({weight:6,color:"#eecc00"})}function V(a){!v[a]||v[a].v({weight:3,color:v[a].options.fillColor})}function Q(a){w[a]&&(15University of Freiburg, Chair of Algorithms and Data Structures'})); +M.l("popupopen",function(a){var c=M.ra(a.target.S.fa);c.y-=a.target.S.ea.clientHeight/2;M.qa(M.ya(c),{ia:!0});X()});L.xa("http://{s}.tile.stamen.com/toner-lite/{z}/{x}/{y}.png",{X:20,U:'© OpenStreetMap',opacity:.8}).T(M);var Y=L.A().T(M);M.l("moveend",function(){Z()});M.l("click",function(){X()}); +function Z(){11>M.b()?J("m","/heatmap?z="+M.b()+"&bbox="+[M.i().D().F,M.i().D().H,M.i().C().F,M.i().C().H].join(),function(a){Y.V();var c=22-M.b(),b=25-M.b();Y.o(L.M(a.ok,{max:500,K:{0:"#cbf7cb","0.5":"#78f378",1:"#29c329"},N:.65,blur:c,J:b}));Y.o(L.M(a.ua,{max:500,K:{0:"#7f7fbd","0.5":"#4444b3",1:"#0606c1"},N:.65,blur:c-3,J:Math.min(12,b-3)}));Y.o(L.M(a.la,{max:500,K:{0:"#f39191","0.5":"#ff5656",1:"#ff0000"},N:.75,blur:c-3,J:Math.min(10,b-3),X:15}))}):J("m","/map?z="+M.b()+"&bbox="+[M.i().D().F, +M.i().D().H,M.i().C().F,M.i().C().H].join(),function(a){Y.V();v={};w={};for(var c=[],b=0;b - - - - - - - - - image/svg+xml - - - - - - - - - + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/web/script.js b/web/script.js index fe080c1..fc0b385 100755 --- a/web/script.js +++ b/web/script.js @@ -1,437 +1,437 @@ -let sessionId; -let curGeojson; -let curGeojsonId = -1; - -// id of SetInterval to stop loadStatus requests on error or load finish -let loadStatusIntervalId = -1; - -// tabName of current submit tab -let tabName = ""; - -let map = L.map('m', { - renderer: L.canvas(), - preferCanvas: true -}).setView([47.9965, 7.8469], 13); -map.attributionControl.setPrefix('University of Freiburg'); - -let layerControl = L.control.layers([], [], {collapsed:true, position: 'topleft'}).addTo(map); - -let osmLayer = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { - attribution: '© OpenStreetMap', - maxZoom: 19, - opacity:0.9 -}).addTo(map); - -let genError = "

Session has been removed from cache.

Resend request

"; - -function openPopup(data) { - if (data.length > 0) { - let select_variables = []; - let row = []; - - for (let i in data[0]["attrs"]) { - select_variables.push(data[0]["attrs"][i][0]); - row.push(data[0]["attrs"][i][1]); - } - - // code by hannah from old map UI - - // Build the HTML of the popup. - // - // NOTE: We assume that the last column contains the geometry information - // (WKT), which we will not put in the table. - let geometry_column = select_variables.length - 1; - // If the second to last variable exists and is called "?image" or ends in - // "_image", then show an image with that URL in the first column of the - // table. Note that we compute the cell contents here and add it during the - // loop (it has to be the first cell of a table row). - let image_cell = ""; - if (select_variables.length >= 2) { - let image_column = select_variables.length - 2; - if (select_variables[image_column] == "?image" || - select_variables[image_column] == "?flag" || - select_variables[image_column].endsWith("_image")) { - let num_table_rows = select_variables.length - 2; - let image_url = row[image_column]; - if (image_url != null) - image_cell = ""]$/, "") + "\">"]$/, "") - + "\">"; - } - } - - // Now compute the table rows in an array. - let popup_content_strings = []; - select_variables.forEach(function(variable, i) { - // Skip the last column (WKT literal) and the ?image column (if it - // exists). - if (i == geometry_column || - variable == "?image" || variable == "?flag" || variable.endsWith("_image")) return; - - // Take the variable name as one table column and the result value as - // another. Reformat a bit, so that it looks nice in an HTML table. and - // the result value as another. Reformat a bit, so that it looks nice in - // an HTML table. - let key = variable.substring(1); - if (row[i] == null) { row[i] = "---" } - let value = row[i].replace(/\\([()])/g, "$1") - .replace(/<((.*)\/(.*))>/, - "$3") - .replace(/\^\^.*$/, "") - .replace(/\"(.*)\"(@[a-z]+)?$/, "$1"); - - popup_content_strings.push( - "" + (i == 0 ? image_cell : "") + - "" + key.replace(/_/g, " ") + "" + - "" + value + ""); - }) - let popup_html = "" + popup_content_strings.join("\n") + "
"; - popup_html += 'Export as GeoJSON'; - - if (curGeojson) curGeojson.remove(); - - L.popup({"maxWidth" : 600}) - .setLatLng(data[0]["ll"]) - .setContent(popup_html) - .openOn(map) - .on('remove', function() { - curGeojson.remove(); - curGeojsonId = -1; - }); - - curGeojson = getGeoJsonLayer(data[0].geom); - curGeojsonId = data[0].id; - curGeojson.addTo(map); - } -} - -function getGeoJsonLayer(geom) { - const color = "#e6930e"; - return L.geoJSON(geom, { - style: {color : color, fillColor: color, weight: 7, fillOpacity: 0.2}, - pointToLayer: function (feature, latlng) { - return L.circleMarker(latlng, { - radius: 8, - fillColor: color, - color: color, - weight: 4, - opacity: 1, - fillOpacity: 0.2 - });} - }); -} - -function showError(error) { - error = error.toString(); - document.getElementById("msg").style.display = "block"; - document.getElementById("load").style.display = "none"; - document.getElementById("msg-inner").style.color = "red"; - document.getElementById("msg-inner").style.fontSize = "20px"; - document.getElementById("msg-inner").innerHTML = error.split("\n")[0]; - if (error.search("\n") > 0) document.getElementById("msg-inner-desc").innerHTML = "
" + error.substring(error.search("\n")) + "
"; - else document.getElementById("msg-inner-desc").innerHTML = ""; -} - -function loadMap(id, bounds, numObjects) { - document.getElementById("msg").style.display = "none"; - - const ll = L.Projection.SphericalMercator.unproject({"x": bounds[0][0], "y":bounds[0][1]}); - const ur = L.Projection.SphericalMercator.unproject({"x": bounds[1][0], "y":bounds[1][1]}); - const boundsLatLng = [[ll.lat, ll.lng], [ur.lat, ur.lng]]; - map.fitBounds(boundsLatLng); - sessionId = id; - - document.getElementById("stats").innerHTML = "Showing " + numObjects + " objects"; - - const heatmapLayer = L.nonTiledLayer.wms('heatmap', { - minZoom: 0, - maxZoom: 19, - opacity: 0.8, - layers: id, - styles: ["heatmap"], - format: 'image/png', - transparent: true, - }); - - const objectsLayer = L.nonTiledLayer.wms('heatmap', { - minZoom: 0, - maxZoom: 19, - layers: id, - styles: ["objects"], - format: 'image/png' - }); - - const autoLayer = L.layerGroup([ - L.nonTiledLayer.wms('heatmap', { - minZoom: 0, - maxZoom: 15, - opacity: 0.8, - layers: id, - styles: ["heatmap"], - format: 'image/png', - transparent: true, - }), L.nonTiledLayer.wms('heatmap', { - minZoom: 16, - maxZoom: 19, - layers: id, - styles: ["objects"], - format: 'image/png' - }) - ]); - - heatmapLayer.on('error', function() {showError(genError);}); - objectsLayer.on('error', function() {showError(genError);}); - heatmapLayer.on('load', function() {console.log("Finished loading map!");}); - objectsLayer.on('load', function() {console.log("Finished loading map!");}); - autoLayer.addTo(map).on('error', function() {showError(genError);}); - - layerControl.addBaseLayer(heatmapLayer, "Heatmap"); - layerControl.addBaseLayer(objectsLayer, "Objects"); - layerControl.addBaseLayer(autoLayer, "Auto"); - - map.on('click', function(e) { - const ll = e.latlng; - const pos = L.Projection.SphericalMercator.project(ll); - - const w = map.getPixelBounds().max.x - map.getPixelBounds().min.x; - const h = map.getPixelBounds().max.y - map.getPixelBounds().min.y; - - const sw = L.Projection.SphericalMercator.project((map.getBounds().getSouthWest())); - const ne = L.Projection.SphericalMercator.project((map.getBounds().getNorthEast())); - - const bounds = [sw.x, sw.y, ne.x, ne.y]; - - let styles = "objects"; - if (map.hasLayer(heatmapLayer)) styles = "heatmap"; - if (map.hasLayer(objectsLayer)) styles = "objects"; - if (map.hasLayer(objectsLayer)) styles = "objects"; - - fetch('pos?x=' + pos.x + "&y=" + pos.y + "&id=" + id + "&rad=" + (100 * Math.pow(2, 14 - map.getZoom())) + '&width=' + w + '&height=' + h + '&bbox=' + bounds.join(',') + '&styles=' + styles) - .then(response => { - if (!response.ok) return response.text().then(text => {throw new Error(text)}); - return response.json(); - }) - .then(data => openPopup(data)) - .catch(error => showError(error)); - }); - - map.on('zoomend', function(e) { - if (curGeojsonId > -1) { - fetch('geojson?gid=' + curGeojsonId + "&id=" + id + "&rad=" + (100 * Math.pow(2, 14 - map.getZoom()))) - .then(response => response.json()) - .then(function(data) { - curGeojson.remove(); - curGeojson = getGeoJsonLayer(data); - curGeojson.addTo(map); - }) - .catch(error => showError(genError)); - } - }); - - document.getElementById("options-ex").style.display = "inline-block"; -} - -function updateLoad(stage, percent) { - const stageElem = document.getElementById("load-stage"); - const barElem = document.getElementById("load-bar"); - const percentElem = document.getElementById("load-percent"); - switch (stage) { - case 1: - stageElem.innerHTML = "Parsing geometry... (1/2)"; - break; - case 2: - stageElem.innerHTML = "Parsing geometry Ids... (2/2)"; - break; - } - barElem.style.width = percent + "%"; - percentElem.innerHTML = percent.toString() + "%"; -} - -function fetchQuery(query, backend) { - console.log("Fetching query..."); - - const query_encoded = encodeURIComponent(query); - const backend_encoded = encodeURIComponent(backend); - - document.getElementById("options-ex-tsv").onclick = function() { - let a = document.createElement("a"); - a.href = backend + "?query=" + query_encoded + "&action=tsv_export"; - a.setAttribute("download", "export.tsv"); - a.click(); - } - - document.getElementById("options-ex-csv").onclick = function() { - let a = document.createElement("a"); - a.href = backend + "?query=" + query_encoded + "&action=csv_export"; - a.setAttribute("download", "export.csv"); - a.click(); - } - - const url = "?query=" + query_encoded + "&backend=" + backend_encoded; - fetchResults(url); - fetchLoadStatusInterval(1000, backend_encoded); -} - -function fetchGeoJsonFile(content) { - console.log("Fetching GeoJson-File"); - console.log(content) - - const url = "geoJsonFile=" + content; -} - -function fetchResults(url) { - setSubmitMenuVisible(false); - document.getElementById("submit-button").disabled = true; - - fetch('query' + url) - .then(response => { - if (!response.ok) return response.text().then(text => {throw new Error(text)}); - return response; - }) - .then(response => response.json()) - .then(data => { - clearInterval(loadStatusIntervalId); - document.getElementById("submit-button").disabled = false; - loadMap(data["qid"], data["bounds"], data["numobjects"]); - }) - .catch(error => showError(error)); - - document.getElementById("msg").style.display = "block"; -} - -function fetchLoadStatusInterval(interval, backend) { - fetchLoadStatus(); - loadStatusIntervalId = setInterval(fetchLoadStatus, interval, backend); - document.getElementById("load").style.display = "block"; -} - -async function fetchLoadStatus(backend) { - console.log("Fetching load status..."); - - fetch('loadstatus?backend=' + backend) - .then(response => { - if (!response.ok) return response.text().then(text => {throw new Error(text)}); - return response; - }) - .then(response => response.json()) - .then(data => { - const stage = data["stage"]; - const percent = parseFloat(data["percent"]).toFixed(2); - updateLoad(stage, percent); - }) - .catch(error => { - showError(error); - clearInterval(loadStatusIntervalId); - }); -} - -function fileToText(file) { - const reader = new FileReader(); - reader.readAsText(file, "UTF-8"); - reader.onprogress = fileToTextOnProgress; - reader.onload = fileToTextOnLoad; - reader.onerror = fileToTextOnError; -} - -function fileToTextOnProgress(evt) { -} - -function fileToTextOnLoad(evt) { - const content = evt.target.result; - const content_encoded = encodeURIComponent(content); - fetchGeoJsonFile(content_encoded); -} - -function fileToTextOnError(evt) { - console.log("File Loading Error:", evt); -} - -function openTab(evt, tabName) { - // Tab changing for submit tabs - // Hide tabs - tab_content_elems = document.getElementsByClassName("submit-tab_content"); - for (let i = 0; i < tab_content_elems.length; i++) { - tab_content_elems[i].style.display = "none"; - } - - // Deactivate tabs - tab_links_elems = document.getElementsByClassName("submit-tab_link"); - for (let i = 0; i < tab_links_elems.length; i++) { - tab_links_elems[i].className = tab_links_elems[i].className.replace(" active", ""); - } - - // Show current tab - document.getElementById("submit-" + tabName).style.display = "block"; - evt.currentTarget.className += " active"; - this.tabName = tabName; -} - -function setSubmitMenuVisible(visible) { - if (visible) { - document.getElementById("submit").style.display = "block"; - document.getElementById("options-submit").innerHTML = "Hide Submit Menu"; - } else { - document.getElementById("submit").style.display = "none"; - document.getElementById("options-submit").innerHTML = "Show Submit Menu"; - } -} - -// Focus default submit tab -document.getElementById("submit-tabs-default_open").click(); -const queryElem = document.getElementById("submit-query-query"); -const backendElem = document.getElementById("submit-query-backend"); -backendElem.value = "https://qlever.cs.uni-freiburg.de/api/wikidata"; - -const url = window.location.search; -const urlParams = new URLSearchParams(url); -if (urlParams.has("query")) { - const query = urlParams.get("query"); - queryElem.value = query; -} -if (urlParams.has("backend")) { - const backend = urlParams.get("backend"); - backendElem.value = backend; -} -if (urlParams.has("query") && urlParams.has("backend")) { - // User wants to send a SPARQL query - const query = urlParams.get("query"); - const backend = urlParams.get("backend"); - fetchQuery(query, backend); -} else if (urlParams.has("geoJsonFile")) { - // User wants to send content of a .geojson file - console.log("GeoJson file."); -} else { - // No useful information in url => Show submit menu - setSubmitMenuVisible(true); -} - -document.getElementById("options-ex-geojson").onclick = function() { - if (!sessionId) return; - const a = document.createElement("a"); - a.href = "export?id="+ sessionId; - a.setAttribute("download", "export.json"); - a.click(); -} - -document.getElementById("options-submit").onclick = function() { - const isVisible = document.getElementById("submit").style.display == "block"; - setSubmitMenuVisible(!isVisible); -} - -document.getElementById("submit-button").onclick = function() { - switch (tabName) { - case "query": - const query = queryElem.value; - const backend = backendElem.value; - fetchQuery(query, backend); - break; - - case "geoJsonFile": - const fileElem = document.getElementById("submit-geoJsonFile-file"); - const file = fileElem.files[0]; - if (file) { - fileToText(file); - } - break; - } +let sessionId; +let curGeojson; +let curGeojsonId = -1; + +// id of SetInterval to stop loadStatus requests on error or load finish +let loadStatusIntervalId = -1; + +// tabName of current submit tab +let tabName = ""; + +let map = L.map('m', { + renderer: L.canvas(), + preferCanvas: true +}).setView([47.9965, 7.8469], 13); +map.attributionControl.setPrefix('University of Freiburg'); + +let layerControl = L.control.layers([], [], {collapsed:true, position: 'topleft'}).addTo(map); + +let osmLayer = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: '© OpenStreetMap', + maxZoom: 19, + opacity:0.9 +}).addTo(map); + +let genError = "

Session has been removed from cache.

Resend request

"; + +function openPopup(data) { + if (data.length > 0) { + let select_variables = []; + let row = []; + + for (let i in data[0]["attrs"]) { + select_variables.push(data[0]["attrs"][i][0]); + row.push(data[0]["attrs"][i][1]); + } + + // code by hannah from old map UI + + // Build the HTML of the popup. + // + // NOTE: We assume that the last column contains the geometry information + // (WKT), which we will not put in the table. + let geometry_column = select_variables.length - 1; + // If the second to last variable exists and is called "?image" or ends in + // "_image", then show an image with that URL in the first column of the + // table. Note that we compute the cell contents here and add it during the + // loop (it has to be the first cell of a table row). + let image_cell = ""; + if (select_variables.length >= 2) { + let image_column = select_variables.length - 2; + if (select_variables[image_column] == "?image" || + select_variables[image_column] == "?flag" || + select_variables[image_column].endsWith("_image")) { + let num_table_rows = select_variables.length - 2; + let image_url = row[image_column]; + if (image_url != null) + image_cell = ""]$/, "") + "\">"]$/, "") + + "\">"; + } + } + + // Now compute the table rows in an array. + let popup_content_strings = []; + select_variables.forEach(function(variable, i) { + // Skip the last column (WKT literal) and the ?image column (if it + // exists). + if (i == geometry_column || + variable == "?image" || variable == "?flag" || variable.endsWith("_image")) return; + + // Take the variable name as one table column and the result value as + // another. Reformat a bit, so that it looks nice in an HTML table. and + // the result value as another. Reformat a bit, so that it looks nice in + // an HTML table. + let key = variable.substring(1); + if (row[i] == null) { row[i] = "---" } + let value = row[i].replace(/\\([()])/g, "$1") + .replace(/<((.*)\/(.*))>/, + "$3") + .replace(/\^\^.*$/, "") + .replace(/\"(.*)\"(@[a-z]+)?$/, "$1"); + + popup_content_strings.push( + "" + (i == 0 ? image_cell : "") + + "" + key.replace(/_/g, " ") + "" + + "" + value + ""); + }) + let popup_html = "" + popup_content_strings.join("\n") + "
"; + popup_html += 'Export as GeoJSON'; + + if (curGeojson) curGeojson.remove(); + + L.popup({"maxWidth" : 600}) + .setLatLng(data[0]["ll"]) + .setContent(popup_html) + .openOn(map) + .on('remove', function() { + curGeojson.remove(); + curGeojsonId = -1; + }); + + curGeojson = getGeoJsonLayer(data[0].geom); + curGeojsonId = data[0].id; + curGeojson.addTo(map); + } +} + +function getGeoJsonLayer(geom) { + const color = "#e6930e"; + return L.geoJSON(geom, { + style: {color : color, fillColor: color, weight: 7, fillOpacity: 0.2}, + pointToLayer: function (feature, latlng) { + return L.circleMarker(latlng, { + radius: 8, + fillColor: color, + color: color, + weight: 4, + opacity: 1, + fillOpacity: 0.2 + });} + }); +} + +function showError(error) { + error = error.toString(); + document.getElementById("msg").style.display = "block"; + document.getElementById("load").style.display = "none"; + document.getElementById("msg-inner").style.color = "red"; + document.getElementById("msg-inner").style.fontSize = "20px"; + document.getElementById("msg-inner").innerHTML = error.split("\n")[0]; + if (error.search("\n") > 0) document.getElementById("msg-inner-desc").innerHTML = "
" + error.substring(error.search("\n")) + "
"; + else document.getElementById("msg-inner-desc").innerHTML = ""; +} + +function loadMap(id, bounds, numObjects) { + document.getElementById("msg").style.display = "none"; + + const ll = L.Projection.SphericalMercator.unproject({"x": bounds[0][0], "y":bounds[0][1]}); + const ur = L.Projection.SphericalMercator.unproject({"x": bounds[1][0], "y":bounds[1][1]}); + const boundsLatLng = [[ll.lat, ll.lng], [ur.lat, ur.lng]]; + map.fitBounds(boundsLatLng); + sessionId = id; + + document.getElementById("stats").innerHTML = "Showing " + numObjects + " objects"; + + const heatmapLayer = L.nonTiledLayer.wms('heatmap', { + minZoom: 0, + maxZoom: 19, + opacity: 0.8, + layers: id, + styles: ["heatmap"], + format: 'image/png', + transparent: true, + }); + + const objectsLayer = L.nonTiledLayer.wms('heatmap', { + minZoom: 0, + maxZoom: 19, + layers: id, + styles: ["objects"], + format: 'image/png' + }); + + const autoLayer = L.layerGroup([ + L.nonTiledLayer.wms('heatmap', { + minZoom: 0, + maxZoom: 15, + opacity: 0.8, + layers: id, + styles: ["heatmap"], + format: 'image/png', + transparent: true, + }), L.nonTiledLayer.wms('heatmap', { + minZoom: 16, + maxZoom: 19, + layers: id, + styles: ["objects"], + format: 'image/png' + }) + ]); + + heatmapLayer.on('error', function() {showError(genError);}); + objectsLayer.on('error', function() {showError(genError);}); + heatmapLayer.on('load', function() {console.log("Finished loading map!");}); + objectsLayer.on('load', function() {console.log("Finished loading map!");}); + autoLayer.addTo(map).on('error', function() {showError(genError);}); + + layerControl.addBaseLayer(heatmapLayer, "Heatmap"); + layerControl.addBaseLayer(objectsLayer, "Objects"); + layerControl.addBaseLayer(autoLayer, "Auto"); + + map.on('click', function(e) { + const ll = e.latlng; + const pos = L.Projection.SphericalMercator.project(ll); + + const w = map.getPixelBounds().max.x - map.getPixelBounds().min.x; + const h = map.getPixelBounds().max.y - map.getPixelBounds().min.y; + + const sw = L.Projection.SphericalMercator.project((map.getBounds().getSouthWest())); + const ne = L.Projection.SphericalMercator.project((map.getBounds().getNorthEast())); + + const bounds = [sw.x, sw.y, ne.x, ne.y]; + + let styles = "objects"; + if (map.hasLayer(heatmapLayer)) styles = "heatmap"; + if (map.hasLayer(objectsLayer)) styles = "objects"; + if (map.hasLayer(objectsLayer)) styles = "objects"; + + fetch('pos?x=' + pos.x + "&y=" + pos.y + "&id=" + id + "&rad=" + (100 * Math.pow(2, 14 - map.getZoom())) + '&width=' + w + '&height=' + h + '&bbox=' + bounds.join(',') + '&styles=' + styles) + .then(response => { + if (!response.ok) return response.text().then(text => {throw new Error(text)}); + return response.json(); + }) + .then(data => openPopup(data)) + .catch(error => showError(error)); + }); + + map.on('zoomend', function(e) { + if (curGeojsonId > -1) { + fetch('geojson?gid=' + curGeojsonId + "&id=" + id + "&rad=" + (100 * Math.pow(2, 14 - map.getZoom()))) + .then(response => response.json()) + .then(function(data) { + curGeojson.remove(); + curGeojson = getGeoJsonLayer(data); + curGeojson.addTo(map); + }) + .catch(error => showError(genError)); + } + }); + + document.getElementById("options-ex").style.display = "inline-block"; +} + +function updateLoad(stage, percent) { + const stageElem = document.getElementById("load-stage"); + const barElem = document.getElementById("load-bar"); + const percentElem = document.getElementById("load-percent"); + switch (stage) { + case 1: + stageElem.innerHTML = "Parsing geometry... (1/2)"; + break; + case 2: + stageElem.innerHTML = "Parsing geometry Ids... (2/2)"; + break; + } + barElem.style.width = percent + "%"; + percentElem.innerHTML = percent.toString() + "%"; +} + +function fetchQuery(query, backend) { + console.log("Fetching query..."); + + const query_encoded = encodeURIComponent(query); + const backend_encoded = encodeURIComponent(backend); + + document.getElementById("options-ex-tsv").onclick = function() { + let a = document.createElement("a"); + a.href = backend + "?query=" + query_encoded + "&action=tsv_export"; + a.setAttribute("download", "export.tsv"); + a.click(); + } + + document.getElementById("options-ex-csv").onclick = function() { + let a = document.createElement("a"); + a.href = backend + "?query=" + query_encoded + "&action=csv_export"; + a.setAttribute("download", "export.csv"); + a.click(); + } + + const url = "?query=" + query_encoded + "&backend=" + backend_encoded; + fetchResults(url); + fetchLoadStatusInterval(1000, backend_encoded); +} + +function fetchGeoJsonFile(content) { + console.log("Fetching GeoJson-File"); + console.log(content) + + const url = "geoJsonFile=" + content; +} + +function fetchResults(url) { + setSubmitMenuVisible(false); + document.getElementById("submit-button").disabled = true; + + fetch('query' + url) + .then(response => { + if (!response.ok) return response.text().then(text => {throw new Error(text)}); + return response; + }) + .then(response => response.json()) + .then(data => { + clearInterval(loadStatusIntervalId); + document.getElementById("submit-button").disabled = false; + loadMap(data["qid"], data["bounds"], data["numobjects"]); + }) + .catch(error => showError(error)); + + document.getElementById("msg").style.display = "block"; +} + +function fetchLoadStatusInterval(interval, backend) { + fetchLoadStatus(); + loadStatusIntervalId = setInterval(fetchLoadStatus, interval, backend); + document.getElementById("load").style.display = "block"; +} + +async function fetchLoadStatus(backend) { + console.log("Fetching load status..."); + + fetch('loadstatus?backend=' + backend) + .then(response => { + if (!response.ok) return response.text().then(text => {throw new Error(text)}); + return response; + }) + .then(response => response.json()) + .then(data => { + const stage = data["stage"]; + const percent = parseFloat(data["percent"]).toFixed(2); + updateLoad(stage, percent); + }) + .catch(error => { + showError(error); + clearInterval(loadStatusIntervalId); + }); +} + +function fileToText(file) { + const reader = new FileReader(); + reader.readAsText(file, "UTF-8"); + reader.onprogress = fileToTextOnProgress; + reader.onload = fileToTextOnLoad; + reader.onerror = fileToTextOnError; +} + +function fileToTextOnProgress(evt) { +} + +function fileToTextOnLoad(evt) { + const content = evt.target.result; + const content_encoded = encodeURIComponent(content); + fetchGeoJsonFile(content_encoded); +} + +function fileToTextOnError(evt) { + console.log("File Loading Error:", evt); +} + +function openTab(evt, tabName) { + // Tab changing for submit tabs + // Hide tabs + tab_content_elems = document.getElementsByClassName("submit-tab_content"); + for (let i = 0; i < tab_content_elems.length; i++) { + tab_content_elems[i].style.display = "none"; + } + + // Deactivate tabs + tab_links_elems = document.getElementsByClassName("submit-tab_link"); + for (let i = 0; i < tab_links_elems.length; i++) { + tab_links_elems[i].className = tab_links_elems[i].className.replace(" active", ""); + } + + // Show current tab + document.getElementById("submit-" + tabName).style.display = "block"; + evt.currentTarget.className += " active"; + this.tabName = tabName; +} + +function setSubmitMenuVisible(visible) { + if (visible) { + document.getElementById("submit").style.display = "block"; + document.getElementById("options-submit").innerHTML = "Hide Submit Menu"; + } else { + document.getElementById("submit").style.display = "none"; + document.getElementById("options-submit").innerHTML = "Show Submit Menu"; + } +} + +// Focus default submit tab +document.getElementById("submit-tabs-default_open").click(); +const queryElem = document.getElementById("submit-query-query"); +const backendElem = document.getElementById("submit-query-backend"); +backendElem.value = "https://qlever.cs.uni-freiburg.de/api/wikidata"; + +const url = window.location.search; +const urlParams = new URLSearchParams(url); +if (urlParams.has("query")) { + const query = urlParams.get("query"); + queryElem.value = query; +} +if (urlParams.has("backend")) { + const backend = urlParams.get("backend"); + backendElem.value = backend; +} +if (urlParams.has("query") && urlParams.has("backend")) { + // User wants to send a SPARQL query + const query = urlParams.get("query"); + const backend = urlParams.get("backend"); + fetchQuery(query, backend); +} else if (urlParams.has("geoJsonFile")) { + // User wants to send content of a .geojson file + console.log("GeoJson file."); +} else { + // No useful information in url => Show submit menu + setSubmitMenuVisible(true); +} + +document.getElementById("options-ex-geojson").onclick = function() { + if (!sessionId) return; + const a = document.createElement("a"); + a.href = "export?id="+ sessionId; + a.setAttribute("download", "export.json"); + a.click(); +} + +document.getElementById("options-submit").onclick = function() { + const isVisible = document.getElementById("submit").style.display == "block"; + setSubmitMenuVisible(!isVisible); +} + +document.getElementById("submit-button").onclick = function() { + switch (tabName) { + case "query": + const query = queryElem.value; + const backend = backendElem.value; + fetchQuery(query, backend); + break; + + case "geoJsonFile": + const fileElem = document.getElementById("submit-geoJsonFile-file"); + const file = fileElem.files[0]; + if (file) { + fileToText(file); + } + break; + } } \ No newline at end of file diff --git a/web/style.css b/web/style.css index d69ee2e..93d60b6 100755 --- a/web/style.css +++ b/web/style.css @@ -1,855 +1,855 @@ -.leaflet-image-layer, .leaflet-layer, .leaflet-pane, .leaflet-pane>canvas, .leaflet-pane>svg, .leaflet-tile, .leaflet-tile-container, .leaflet-zoom-box { - position: absolute; - left: 0; - top: 0 -} - -.leaflet-container { - overflow: hidden -} - -.leaflet-tile { - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - -webkit-user-drag: none -} - -.leaflet-tile::selection { - background: 0 0 -} - -.leaflet-safari .leaflet-tile { - image-rendering: -webkit-optimize-contrast -} - -.leaflet-safari .leaflet-tile-container { - width: 1600px; - height: 1600px; - -webkit-transform-origin: 0 0 -} - -.leaflet-container .leaflet-overlay-pane svg, .leaflet-container .leaflet-shadow-pane img, .leaflet-container .leaflet-tile, .leaflet-container .leaflet-tile-pane img, .leaflet-container img.leaflet-image-layer { - max-width: none !important; - max-height: none !important -} - -.leaflet-container.leaflet-touch-zoom { - -ms-touch-action: pan-x pan-y; - touch-action: pan-x pan-y -} - -.leaflet-container.leaflet-touch-drag { - -ms-touch-action: pinch-zoom; - touch-action: none; - touch-action: pinch-zoom -} - -.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { - -ms-touch-action: none; - touch-action: none -} - -.leaflet-container { - -webkit-tap-highlight-color: transparent -} - -.leaflet-container a { - -webkit-tap-highlight-color: rgba(51, 181, 229, .4) -} - -.leaflet-tile { - filter: inherit; - visibility: hidden -} - -.leaflet-tile-loaded { - visibility: inherit -} - -.leaflet-zoom-box { - width: 0; - height: 0; - -moz-box-sizing: border-box; - box-sizing: border-box; - z-index: 800 -} - -.leaflet-overlay-pane svg { - -moz-user-select: none -} - -.leaflet-pane { - z-index: 400 -} - -.leaflet-tile-pane { - z-index: 200 -} - -.leaflet-overlay-pane { - z-index: 400 -} - -.leaflet-shadow-pane { - z-index: 500 -} - -.leaflet-marker-pane { - z-index: 600 -} - -.leaflet-tooltip-pane { - z-index: 650 -} - -.leaflet-popup-pane { - z-index: 700 -} - -.leaflet-map-pane canvas { - z-index: 100 -} - -.leaflet-map-pane svg { - z-index: 200 -} - -.leaflet-vml-shape { - width: 1px; - height: 1px -} - -.lvml { - behavior: url(#default#VML); - display: inline-block; - position: absolute -} - -.leaflet-control { - position: relative; - z-index: 800; - pointer-events: visiblePainted; - pointer-events: auto -} - -.leaflet-bottom, .leaflet-top { - position: absolute; - z-index: 1000; - pointer-events: none -} - -.leaflet-top { - top: 0 -} - -.leaflet-right { - right: 0 -} - -.leaflet-bottom { - bottom: 0 -} - -.leaflet-left { - left: 0 -} - -.leaflet-control { - float: left; - clear: both -} - -.leaflet-right .leaflet-control { - float: right -} - -.leaflet-top .leaflet-control { - margin-top: 10px -} - -.leaflet-bottom .leaflet-control { - margin-bottom: 10px -} - -.leaflet-left .leaflet-control { - margin-left: 10px -} - -.leaflet-right .leaflet-control { - margin-right: 10px -} - -.leaflet-fade-anim .leaflet-tile { - will-change: opacity -} - -.leaflet-fade-anim .leaflet-popup { - opacity: 0; - -webkit-transition: opacity .2s linear; - -moz-transition: opacity .2s linear; - transition: opacity .2s linear -} - -.leaflet-fade-anim .leaflet-map-pane .leaflet-popup { - opacity: 1 -} - -.leaflet-zoom-animated { - -webkit-transform-origin: 0 0; - -ms-transform-origin: 0 0; - transform-origin: 0 0 -} - -.leaflet-zoom-anim .leaflet-zoom-animated { - will-change: transform -} - -.leaflet-zoom-anim .leaflet-zoom-animated { - -webkit-transition: -webkit-transform .25s cubic-bezier(0, 0, .25, 1); - -moz-transition: -moz-transform .25s cubic-bezier(0, 0, .25, 1); - transition: transform .25s cubic-bezier(0, 0, .25, 1) -} - -.leaflet-pan-anim .leaflet-tile, .leaflet-zoom-anim .leaflet-tile { - -webkit-transition: none; - -moz-transition: none; - transition: none -} - -.leaflet-zoom-anim .leaflet-zoom-hide { - visibility: hidden -} - -.leaflet-interactive { - cursor: pointer -} - -.leaflet-grab { - cursor: -webkit-grab; - cursor: -moz-grab; - cursor: grab -} - -.leaflet-control, .leaflet-popup-pane { - cursor: auto -} - -.leaflet-dragging .leaflet-grab, .leaflet-dragging .leaflet-grab .leaflet-interactive { - cursor: move; - cursor: -webkit-grabbing; - cursor: -moz-grabbing; - cursor: grabbing -} - -.leaflet-image-layer, .leaflet-pane>svg path, .leaflet-tile-container { - pointer-events: none -} - -.leaflet-image-layer.leaflet-interactive, .leaflet-pane>svg path.leaflet-interactive, svg.leaflet-image-layer.leaflet-interactive path { - pointer-events: visiblePainted; - pointer-events: auto -} - -.leaflet-container { - background: #ddd; - outline: 0 -} - -.leaflet-container a { - color: #0078a8 -} - -.leaflet-container a.leaflet-active { - outline: 2px solid orange -} - -.leaflet-zoom-box { - border: 2px dotted #38f; - background: rgba(255, 255, 255, .5) -} - -.leaflet-bar { - box-shadow: 0 1px 5px rgba(0, 0, 0, .65); - border-radius: 4px -} - -.leaflet-bar a, .leaflet-bar a:hover { - background-color: #fff; - border-bottom: 1px solid #ccc; - width: 26px; - height: 26px; - line-height: 26px; - display: block; - text-align: center; - text-decoration: none; - color: #000 -} - -.leaflet-bar a, .leaflet-control-layers-toggle { - background-position: 50% 50%; - background-repeat: no-repeat; - display: block -} - -.leaflet-bar a:hover { - background-color: #f4f4f4 -} - -.leaflet-bar a:first-child { - border-top-left-radius: 4px; - border-top-right-radius: 4px -} - -.leaflet-bar a:last-child { - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; - border-bottom: none -} - -.leaflet-bar a.leaflet-disabled { - cursor: default; - background-color: #f4f4f4; - color: #bbb -} - -.leaflet-touch .leaflet-bar a { - width: 30px; - height: 30px; - line-height: 30px -} - -.leaflet-touch .leaflet-bar a:first-child { - border-top-left-radius: 2px; - border-top-right-radius: 2px -} - -.leaflet-touch .leaflet-bar a:last-child { - border-bottom-left-radius: 2px; - border-bottom-right-radius: 2px -} - -.leaflet-control-zoom-in, .leaflet-control-zoom-out { - text-indent: 1px -} - -.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { - font-size: 22px -} - -.leaflet-container .leaflet-control-attribution { - background: #fff; - background: rgba(255, 255, 255, .7); - margin: 0 -} - -.leaflet-control-attribution { - padding: 0 5px; - color: #333 -} - -.leaflet-control-attribution a { - text-decoration: none -} - -.leaflet-control-attribution a:hover { - text-decoration: underline -} - -.leaflet-container .leaflet-control-attribution { - font-size: 11px -} - -.leaflet-touch .leaflet-bar, .leaflet-touch .leaflet-control-attribution, .leaflet-touch .leaflet-control-layers { - box-shadow: none -} - -.leaflet-touch .leaflet-bar, .leaflet-touch .leaflet-control-layers { - border: 2px solid rgba(0, 0, 0, .2); - background-clip: padding-box -} - -.leaflet-popup { - position: absolute; - text-align: center; - margin-bottom: 20px -} - -.leaflet-popup-content-wrapper { - padding: 1px; - text-align: left; - border-radius: 12px -} - -.leaflet-popup-content { - margin: 13px 19px; - line-height: 1.4 -} - -.leaflet-popup-content p { - margin: 18px 0 -} - -.leaflet-popup-tip-container { - width: 40px; - height: 20px; - position: absolute; - left: 50%; - margin-left: -20px; - overflow: hidden; - pointer-events: none -} - -.leaflet-popup-tip { - width: 17px; - height: 17px; - padding: 1px; - margin: -10px auto 0; - -webkit-transform: rotate(45deg); - -moz-transform: rotate(45deg); - -ms-transform: rotate(45deg); - transform: rotate(45deg) -} - -.leaflet-popup-content-wrapper, .leaflet-popup-tip { - background: #fff; - color: #333; - box-shadow: 0 3px 14px rgba(0, 0, 0, .4) -} - -.leaflet-container a.leaflet-popup-close-button { - position: absolute; - top: 0; - right: 0; - padding: 5px 5px 0 0; - border: none; - text-align: center; - width: 22px; - height: 18px; - font: 24px/22px Tahoma, Verdana, sans-serif; - color: #c3c3c3; - text-decoration: none; - font-weight: 700; - background: 0 0 -} - -.leaflet-container a.leaflet-popup-close-button:hover { - color: #999 -} - -.leaflet-popup-scrolled { - overflow: auto; - border-bottom: 1px solid #ddd; - border-top: 1px solid #ddd -} - -body { - padding: 0; - margin: 0; - font-family: sans-serif -} - -body, html { - height: 100%; - width: 100% -} - -tt { - letter-spacing: -.5px -} - -#attr-tbl tr { - background-color: #c0f7c0 -} - -.stat-label { - white-space: nowrap; - background: 0 0 -} - -#attr-tbl .err-10 { - background-color: red -} - -#attr-tbl .err-9 { - background-color: #ff1010 -} - -#attr-tbl .err-8 { - background-color: #f92424 -} - -#attr-tbl .err-7 { - background-color: #f93838 -} - -#attr-tbl .err-6 { - background-color: #f56262 -} - -#attr-tbl .err-5 { - background-color: #f38484 -} - -.attr-err-info { - margin-top: 5px; - font-size: 13px; - font-style: italic -} - -.leaflet-popup-content-wrapper { - border-radius: 5px -} - -#attr-tbl .err-wrap { - max-height: 100px; - display: block; - overflow-y: auto -} - -#attr-tbl { - margin-top: 10px; - width: 100% -} - -.nmt, .omt, .sugtit { - font-style: normal; - font-weight: 700 -} - -#gsn, #gso { - margin-top: 8px; - padding-top: 4px; - border-top: solid 1px #aaa -} - -#gsn div { - background-color: #b6b6e4; - border-left: solid 4px #00f; - padding: 2px; - margin: 2px; - padding-left: 5px -} - -#gso div { - border-left: solid 4px #78f378; - padding: 2px; - margin: 2px; - padding-left: 5px -} - -#sugg { - margin-top: 8px; - padding-top: 4px; - border-top: solid 1px #aaa; - color: #0000c3 -} - -#sugg ul { - padding: 5px; - margin: 5px; - padding-top: 0 -} - -#m, main { - width: 100%; - height: 100% -} - -#m { - cursor: pointer; -} - -.leaflet-control-layers { - box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4); - background: #fff; - border-radius: 5px; -} - -.leaflet-control-layers-toggle { - text-decoration: none; - text-align: center; - width: 36px; - height: 36px; - color: black; -} - -.leaflet-control-layers-toggle:after { - content: "🗺"; - line-height: 45px; - font-size: 25px; - color: black; -} - -.leaflet-retina .leaflet-control-layers-toggle { - background-image: url(images/layers-2x.png); - background-size: 26px 26px; -} - -.leaflet-touch .leaflet-control-layers-toggle { - width: 44px; - height: 44px; -} - -.leaflet-control-layers .leaflet-control-layers-list, .leaflet-control-layers-expanded .leaflet-control-layers-toggle { - display: none; -} - -.leaflet-control-layers-expanded .leaflet-control-layers-list { - display: block; - position: relative; -} - -.leaflet-control-layers-expanded { - padding: 6px 10px 6px 6px; - color: #333; - background: #fff; -} - -.leaflet-control-layers-scrollbar { - overflow-y: scroll; - overflow-x: hidden; - padding-right: 5px; -} - -.leaflet-control-layers-selector { - margin-top: 2px; - position: relative; - top: 1px; -} - -.leaflet-control-layers label { - display: block; - font-size: 13px; - font-size: 1.08333em; -} - -.leaflet-control-layers-separator { - height: 0; - border-top: 1px solid #ddd; - margin: 5px -10px 5px -6px; -} - -.leaflet-popup-content img { - width: 200px; -} - -.leaflet-popup-content a { - border: none; -} - -h1, #msg-inner { - font: 1.5em sans-serif; - margin-bottom: 40px; - opacity: 0.8; -} - -#msg { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 25%; - border-radius: 4px; - background-color: white; - border: solid 1px black; - text-align: center; - z-index: 99998; - padding: 50px; -} - -#msg-inner-desc { - font: 0.9em mono; - text-align: left; - background-color: #fefefe; - width: 100%; - max-height: 200px; - overflow-y: auto; - overflow-x: auto; -} - -@keyframes spin { - to { - -webkit-transform: rotate(360deg); - } -} - -@-webkit-keyframes spin { - to { - -webkit-transform: rotate(360deg); - } -} - -#options { - position: absolute; - top: 10px; - right: 10px; - z-index: 9998; -} - -#options-submit { - display: inline-block; -} - -#options-ex { - display: none; -} - -button { - padding: 6px 12px; - margin-bottom: 0; - font-size: 15px; - -ms-touch-action: manipulation; - touch-action: manipulation; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - border: 1px solid #9d9d9d; - border-radius: 4px; - margin-left: 10px; - cursor: pointer; - vertical-align: middle; - font-weight: 400; - line-height: 1.42857143; - text-align: center; - white-space: nowrap; - background-color: white; -} - -#button:hover { - background-color: #f4f4f4; -} - -#stats { - position: absolute; - bottom: 10px; - left: 10px; - z-index: 9999; - font-size: 80%; - border-radius: 5px; - opacity: 0.8; - background: white; -} - -#msg { - display: none; -} - -#submit { - display: none; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 40%; - height: 40%; - max-width: 40%; - border-radius: 4px; - background-color: white; - border: solid 1px black; - z-index: 99999; -} - -.submit-tabs { - overflow: hidden; - border: 1px solid #ccc; - background-color: #f1f1f1; -} - -.submit-tabs button { - background-color: inherit; - float: left; - border: none; - border-radius: 0px; - margin-left: 0px; - outline: none; - cursor: pointer; - padding: 14px 16px; - transition: 0.3s; - width: 50%; -} - -.submit-tabs button.active { - background-color: #ccc; -} - -.submit-tab_content { - display: none; - box-sizing: border-box; - padding-left: 12px; - padding-right: 12px; - text-align: left; -} - -.submit-text_input { - display: block; - box-sizing: border-box; - width: 100%; - height: 30px; -} - -#submit-button { - position: absolute; - bottom: 12px; - left: 0; - right: 0; - width: 72px; - margin: auto; -} - -#load { - display: none; -} - -#load-status { - display: grid; - width: 100%; - background-color: white; - border-radius: 4px; - border: solid 1px black; -} - -#load-bar { - grid-column: 1; - grid-row: 1; - - width: 0%; - height: 30px; - background-color: black; - transition: width 400ms; - transition-timing-function: linear; -} - -#load-percent { - grid-column: 1; - grid-row: 1; - - text-align: center; - font-size: 15px; - line-height: 30px; - color: white; - mix-blend-mode: difference; -} - -#load-spinner { - position: absolute; - right: 8px; - top: 8px; - display: inline-block; - width: 16px; - height: 16px; - border: 3px solid rgba(0, 0, 0, .3); - border-radius: 50%; - border-top-color: #fff; - animation: spin 1s ease-in-out infinite; - -webkit-animation: spin 1s ease-in-out infinite; -} - -#stats span { - padding: 5px; -} - -.export-link { - font-size: 80%; -} +.leaflet-image-layer, .leaflet-layer, .leaflet-pane, .leaflet-pane>canvas, .leaflet-pane>svg, .leaflet-tile, .leaflet-tile-container, .leaflet-zoom-box { + position: absolute; + left: 0; + top: 0 +} + +.leaflet-container { + overflow: hidden +} + +.leaflet-tile { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-user-drag: none +} + +.leaflet-tile::selection { + background: 0 0 +} + +.leaflet-safari .leaflet-tile { + image-rendering: -webkit-optimize-contrast +} + +.leaflet-safari .leaflet-tile-container { + width: 1600px; + height: 1600px; + -webkit-transform-origin: 0 0 +} + +.leaflet-container .leaflet-overlay-pane svg, .leaflet-container .leaflet-shadow-pane img, .leaflet-container .leaflet-tile, .leaflet-container .leaflet-tile-pane img, .leaflet-container img.leaflet-image-layer { + max-width: none !important; + max-height: none !important +} + +.leaflet-container.leaflet-touch-zoom { + -ms-touch-action: pan-x pan-y; + touch-action: pan-x pan-y +} + +.leaflet-container.leaflet-touch-drag { + -ms-touch-action: pinch-zoom; + touch-action: none; + touch-action: pinch-zoom +} + +.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { + -ms-touch-action: none; + touch-action: none +} + +.leaflet-container { + -webkit-tap-highlight-color: transparent +} + +.leaflet-container a { + -webkit-tap-highlight-color: rgba(51, 181, 229, .4) +} + +.leaflet-tile { + filter: inherit; + visibility: hidden +} + +.leaflet-tile-loaded { + visibility: inherit +} + +.leaflet-zoom-box { + width: 0; + height: 0; + -moz-box-sizing: border-box; + box-sizing: border-box; + z-index: 800 +} + +.leaflet-overlay-pane svg { + -moz-user-select: none +} + +.leaflet-pane { + z-index: 400 +} + +.leaflet-tile-pane { + z-index: 200 +} + +.leaflet-overlay-pane { + z-index: 400 +} + +.leaflet-shadow-pane { + z-index: 500 +} + +.leaflet-marker-pane { + z-index: 600 +} + +.leaflet-tooltip-pane { + z-index: 650 +} + +.leaflet-popup-pane { + z-index: 700 +} + +.leaflet-map-pane canvas { + z-index: 100 +} + +.leaflet-map-pane svg { + z-index: 200 +} + +.leaflet-vml-shape { + width: 1px; + height: 1px +} + +.lvml { + behavior: url(#default#VML); + display: inline-block; + position: absolute +} + +.leaflet-control { + position: relative; + z-index: 800; + pointer-events: visiblePainted; + pointer-events: auto +} + +.leaflet-bottom, .leaflet-top { + position: absolute; + z-index: 1000; + pointer-events: none +} + +.leaflet-top { + top: 0 +} + +.leaflet-right { + right: 0 +} + +.leaflet-bottom { + bottom: 0 +} + +.leaflet-left { + left: 0 +} + +.leaflet-control { + float: left; + clear: both +} + +.leaflet-right .leaflet-control { + float: right +} + +.leaflet-top .leaflet-control { + margin-top: 10px +} + +.leaflet-bottom .leaflet-control { + margin-bottom: 10px +} + +.leaflet-left .leaflet-control { + margin-left: 10px +} + +.leaflet-right .leaflet-control { + margin-right: 10px +} + +.leaflet-fade-anim .leaflet-tile { + will-change: opacity +} + +.leaflet-fade-anim .leaflet-popup { + opacity: 0; + -webkit-transition: opacity .2s linear; + -moz-transition: opacity .2s linear; + transition: opacity .2s linear +} + +.leaflet-fade-anim .leaflet-map-pane .leaflet-popup { + opacity: 1 +} + +.leaflet-zoom-animated { + -webkit-transform-origin: 0 0; + -ms-transform-origin: 0 0; + transform-origin: 0 0 +} + +.leaflet-zoom-anim .leaflet-zoom-animated { + will-change: transform +} + +.leaflet-zoom-anim .leaflet-zoom-animated { + -webkit-transition: -webkit-transform .25s cubic-bezier(0, 0, .25, 1); + -moz-transition: -moz-transform .25s cubic-bezier(0, 0, .25, 1); + transition: transform .25s cubic-bezier(0, 0, .25, 1) +} + +.leaflet-pan-anim .leaflet-tile, .leaflet-zoom-anim .leaflet-tile { + -webkit-transition: none; + -moz-transition: none; + transition: none +} + +.leaflet-zoom-anim .leaflet-zoom-hide { + visibility: hidden +} + +.leaflet-interactive { + cursor: pointer +} + +.leaflet-grab { + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab +} + +.leaflet-control, .leaflet-popup-pane { + cursor: auto +} + +.leaflet-dragging .leaflet-grab, .leaflet-dragging .leaflet-grab .leaflet-interactive { + cursor: move; + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + cursor: grabbing +} + +.leaflet-image-layer, .leaflet-pane>svg path, .leaflet-tile-container { + pointer-events: none +} + +.leaflet-image-layer.leaflet-interactive, .leaflet-pane>svg path.leaflet-interactive, svg.leaflet-image-layer.leaflet-interactive path { + pointer-events: visiblePainted; + pointer-events: auto +} + +.leaflet-container { + background: #ddd; + outline: 0 +} + +.leaflet-container a { + color: #0078a8 +} + +.leaflet-container a.leaflet-active { + outline: 2px solid orange +} + +.leaflet-zoom-box { + border: 2px dotted #38f; + background: rgba(255, 255, 255, .5) +} + +.leaflet-bar { + box-shadow: 0 1px 5px rgba(0, 0, 0, .65); + border-radius: 4px +} + +.leaflet-bar a, .leaflet-bar a:hover { + background-color: #fff; + border-bottom: 1px solid #ccc; + width: 26px; + height: 26px; + line-height: 26px; + display: block; + text-align: center; + text-decoration: none; + color: #000 +} + +.leaflet-bar a, .leaflet-control-layers-toggle { + background-position: 50% 50%; + background-repeat: no-repeat; + display: block +} + +.leaflet-bar a:hover { + background-color: #f4f4f4 +} + +.leaflet-bar a:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px +} + +.leaflet-bar a:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom: none +} + +.leaflet-bar a.leaflet-disabled { + cursor: default; + background-color: #f4f4f4; + color: #bbb +} + +.leaflet-touch .leaflet-bar a { + width: 30px; + height: 30px; + line-height: 30px +} + +.leaflet-touch .leaflet-bar a:first-child { + border-top-left-radius: 2px; + border-top-right-radius: 2px +} + +.leaflet-touch .leaflet-bar a:last-child { + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px +} + +.leaflet-control-zoom-in, .leaflet-control-zoom-out { + text-indent: 1px +} + +.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { + font-size: 22px +} + +.leaflet-container .leaflet-control-attribution { + background: #fff; + background: rgba(255, 255, 255, .7); + margin: 0 +} + +.leaflet-control-attribution { + padding: 0 5px; + color: #333 +} + +.leaflet-control-attribution a { + text-decoration: none +} + +.leaflet-control-attribution a:hover { + text-decoration: underline +} + +.leaflet-container .leaflet-control-attribution { + font-size: 11px +} + +.leaflet-touch .leaflet-bar, .leaflet-touch .leaflet-control-attribution, .leaflet-touch .leaflet-control-layers { + box-shadow: none +} + +.leaflet-touch .leaflet-bar, .leaflet-touch .leaflet-control-layers { + border: 2px solid rgba(0, 0, 0, .2); + background-clip: padding-box +} + +.leaflet-popup { + position: absolute; + text-align: center; + margin-bottom: 20px +} + +.leaflet-popup-content-wrapper { + padding: 1px; + text-align: left; + border-radius: 12px +} + +.leaflet-popup-content { + margin: 13px 19px; + line-height: 1.4 +} + +.leaflet-popup-content p { + margin: 18px 0 +} + +.leaflet-popup-tip-container { + width: 40px; + height: 20px; + position: absolute; + left: 50%; + margin-left: -20px; + overflow: hidden; + pointer-events: none +} + +.leaflet-popup-tip { + width: 17px; + height: 17px; + padding: 1px; + margin: -10px auto 0; + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg) +} + +.leaflet-popup-content-wrapper, .leaflet-popup-tip { + background: #fff; + color: #333; + box-shadow: 0 3px 14px rgba(0, 0, 0, .4) +} + +.leaflet-container a.leaflet-popup-close-button { + position: absolute; + top: 0; + right: 0; + padding: 5px 5px 0 0; + border: none; + text-align: center; + width: 22px; + height: 18px; + font: 24px/22px Tahoma, Verdana, sans-serif; + color: #c3c3c3; + text-decoration: none; + font-weight: 700; + background: 0 0 +} + +.leaflet-container a.leaflet-popup-close-button:hover { + color: #999 +} + +.leaflet-popup-scrolled { + overflow: auto; + border-bottom: 1px solid #ddd; + border-top: 1px solid #ddd +} + +body { + padding: 0; + margin: 0; + font-family: sans-serif +} + +body, html { + height: 100%; + width: 100% +} + +tt { + letter-spacing: -.5px +} + +#attr-tbl tr { + background-color: #c0f7c0 +} + +.stat-label { + white-space: nowrap; + background: 0 0 +} + +#attr-tbl .err-10 { + background-color: red +} + +#attr-tbl .err-9 { + background-color: #ff1010 +} + +#attr-tbl .err-8 { + background-color: #f92424 +} + +#attr-tbl .err-7 { + background-color: #f93838 +} + +#attr-tbl .err-6 { + background-color: #f56262 +} + +#attr-tbl .err-5 { + background-color: #f38484 +} + +.attr-err-info { + margin-top: 5px; + font-size: 13px; + font-style: italic +} + +.leaflet-popup-content-wrapper { + border-radius: 5px +} + +#attr-tbl .err-wrap { + max-height: 100px; + display: block; + overflow-y: auto +} + +#attr-tbl { + margin-top: 10px; + width: 100% +} + +.nmt, .omt, .sugtit { + font-style: normal; + font-weight: 700 +} + +#gsn, #gso { + margin-top: 8px; + padding-top: 4px; + border-top: solid 1px #aaa +} + +#gsn div { + background-color: #b6b6e4; + border-left: solid 4px #00f; + padding: 2px; + margin: 2px; + padding-left: 5px +} + +#gso div { + border-left: solid 4px #78f378; + padding: 2px; + margin: 2px; + padding-left: 5px +} + +#sugg { + margin-top: 8px; + padding-top: 4px; + border-top: solid 1px #aaa; + color: #0000c3 +} + +#sugg ul { + padding: 5px; + margin: 5px; + padding-top: 0 +} + +#m, main { + width: 100%; + height: 100% +} + +#m { + cursor: pointer; +} + +.leaflet-control-layers { + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.4); + background: #fff; + border-radius: 5px; +} + +.leaflet-control-layers-toggle { + text-decoration: none; + text-align: center; + width: 36px; + height: 36px; + color: black; +} + +.leaflet-control-layers-toggle:after { + content: "🗺"; + line-height: 45px; + font-size: 25px; + color: black; +} + +.leaflet-retina .leaflet-control-layers-toggle { + background-image: url(images/layers-2x.png); + background-size: 26px 26px; +} + +.leaflet-touch .leaflet-control-layers-toggle { + width: 44px; + height: 44px; +} + +.leaflet-control-layers .leaflet-control-layers-list, .leaflet-control-layers-expanded .leaflet-control-layers-toggle { + display: none; +} + +.leaflet-control-layers-expanded .leaflet-control-layers-list { + display: block; + position: relative; +} + +.leaflet-control-layers-expanded { + padding: 6px 10px 6px 6px; + color: #333; + background: #fff; +} + +.leaflet-control-layers-scrollbar { + overflow-y: scroll; + overflow-x: hidden; + padding-right: 5px; +} + +.leaflet-control-layers-selector { + margin-top: 2px; + position: relative; + top: 1px; +} + +.leaflet-control-layers label { + display: block; + font-size: 13px; + font-size: 1.08333em; +} + +.leaflet-control-layers-separator { + height: 0; + border-top: 1px solid #ddd; + margin: 5px -10px 5px -6px; +} + +.leaflet-popup-content img { + width: 200px; +} + +.leaflet-popup-content a { + border: none; +} + +h1, #msg-inner { + font: 1.5em sans-serif; + margin-bottom: 40px; + opacity: 0.8; +} + +#msg { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 25%; + border-radius: 4px; + background-color: white; + border: solid 1px black; + text-align: center; + z-index: 99998; + padding: 50px; +} + +#msg-inner-desc { + font: 0.9em mono; + text-align: left; + background-color: #fefefe; + width: 100%; + max-height: 200px; + overflow-y: auto; + overflow-x: auto; +} + +@keyframes spin { + to { + -webkit-transform: rotate(360deg); + } +} + +@-webkit-keyframes spin { + to { + -webkit-transform: rotate(360deg); + } +} + +#options { + position: absolute; + top: 10px; + right: 10px; + z-index: 9998; +} + +#options-submit { + display: inline-block; +} + +#options-ex { + display: none; +} + +button { + padding: 6px 12px; + margin-bottom: 0; + font-size: 15px; + -ms-touch-action: manipulation; + touch-action: manipulation; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + border: 1px solid #9d9d9d; + border-radius: 4px; + margin-left: 10px; + cursor: pointer; + vertical-align: middle; + font-weight: 400; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + background-color: white; +} + +#button:hover { + background-color: #f4f4f4; +} + +#stats { + position: absolute; + bottom: 10px; + left: 10px; + z-index: 9999; + font-size: 80%; + border-radius: 5px; + opacity: 0.8; + background: white; +} + +#msg { + display: none; +} + +#submit { + display: none; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40%; + height: 40%; + max-width: 40%; + border-radius: 4px; + background-color: white; + border: solid 1px black; + z-index: 99999; +} + +.submit-tabs { + overflow: hidden; + border: 1px solid #ccc; + background-color: #f1f1f1; +} + +.submit-tabs button { + background-color: inherit; + float: left; + border: none; + border-radius: 0px; + margin-left: 0px; + outline: none; + cursor: pointer; + padding: 14px 16px; + transition: 0.3s; + width: 50%; +} + +.submit-tabs button.active { + background-color: #ccc; +} + +.submit-tab_content { + display: none; + box-sizing: border-box; + padding-left: 12px; + padding-right: 12px; + text-align: left; +} + +.submit-text_input { + display: block; + box-sizing: border-box; + width: 100%; + height: 30px; +} + +#submit-button { + position: absolute; + bottom: 12px; + left: 0; + right: 0; + width: 72px; + margin: auto; +} + +#load { + display: none; +} + +#load-status { + display: grid; + width: 100%; + background-color: white; + border-radius: 4px; + border: solid 1px black; +} + +#load-bar { + grid-column: 1; + grid-row: 1; + + width: 0%; + height: 30px; + background-color: black; + transition: width 400ms; + transition-timing-function: linear; +} + +#load-percent { + grid-column: 1; + grid-row: 1; + + text-align: center; + font-size: 15px; + line-height: 30px; + color: white; + mix-blend-mode: difference; +} + +#load-spinner { + position: absolute; + right: 8px; + top: 8px; + display: inline-block; + width: 16px; + height: 16px; + border: 3px solid rgba(0, 0, 0, .3); + border-radius: 50%; + border-top-color: #fff; + animation: spin 1s ease-in-out infinite; + -webkit-animation: spin 1s ease-in-out infinite; +} + +#stats span { + padding: 5px; +} + +.export-link { + font-size: 80%; +}