From f7de2bbc9d30cef63339a2b8ccb9f6ae1a957667 Mon Sep 17 00:00:00 2001 From: Antoine Lambert Date: Wed, 11 Dec 2024 23:50:00 +0100 Subject: [PATCH] GeographicView: Simplify implementation for rendering online maps (WIP) Previous implementation was depending on QtWebKit or QtWebEngine to render online maps using javascript and the open source Leaflet library. While it was working fine, it was quite cumbersome to deploy as QtWebKit is now deprecated and QtWebEngine is a beast in terms of size of binaries. Prefer to use the lightweight QGeoView library instead which only depends on Qt base libraries and works pretty fine to smoothly render an online map and interact with it. Inspired from the changes in the recent Tulip 6.0 release. --- .github/workflows/archlinux-build.yml | 1 - .github/workflows/code-coverage.yml | 1 - .github/workflows/macos-macports-build.yml | 1 - .github/workflows/ubuntu-build.yml | 1 - .github/workflows/ubuntu-qt6-build.yml | 2 - .github/workflows/windows-mingw64-build.yml | 1 - .github/workflows/windows-msvc-build.yml | 1 - .../linux/talipot_appimage_centos_build.sh | 21 +- cmake/FindQtX.cmake | 96 - plugins/view/CMakeLists.txt | 9 +- plugins/view/GeographicView/CMakeLists.txt | 28 +- .../view/GeographicView/GeographicView.cpp | 22 +- plugins/view/GeographicView/GeographicView.h | 3 - .../GeographicViewGraphicsView.cpp | 219 +- .../GeographicViewGraphicsView.h | 23 +- .../GeographicViewInteractors.cpp | 20 +- .../GeographicView/GeographicViewResource.qrc | 3 - .../GeographicViewShowElementInfo.cpp | 3 +- plugins/view/GeographicView/LeafletMaps.cpp | 432 - plugins/view/GeographicView/LeafletMaps.h | 182 - plugins/view/GeographicView/leaflet/LICENSE | 26 - .../GeographicView/leaflet/leaflet-src.js | 14512 ---------------- .../view/GeographicView/leaflet/leaflet.css | 661 - .../view/GeographicView/leaflet/leaflet.js | 6 - .../view/GeographicView/leaflet/no-tile.png | Bin 1516 -> 0 bytes software/talipot/CMakeLists.txt | 1 - software/talipot/src/TalipotMainWindow.cpp | 3 +- software/talipot/src/main.cpp | 4 - tests/CMakeLists.txt | 1 + 29 files changed, 134 insertions(+), 16149 deletions(-) delete mode 100644 plugins/view/GeographicView/LeafletMaps.cpp delete mode 100644 plugins/view/GeographicView/LeafletMaps.h delete mode 100644 plugins/view/GeographicView/leaflet/LICENSE delete mode 100644 plugins/view/GeographicView/leaflet/leaflet-src.js delete mode 100644 plugins/view/GeographicView/leaflet/leaflet.css delete mode 100644 plugins/view/GeographicView/leaflet/leaflet.js delete mode 100644 plugins/view/GeographicView/leaflet/no-tile.png diff --git a/.github/workflows/archlinux-build.yml b/.github/workflows/archlinux-build.yml index f61f855207..e928f87125 100644 --- a/.github/workflows/archlinux-build.yml +++ b/.github/workflows/archlinux-build.yml @@ -56,7 +56,6 @@ jobs: zstd \ libgit2 \ qt6-base \ - qt6-webengine \ quazip-qt6 \ glew \ glu \ diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml index 324ccbb67a..92a36ca69c 100644 --- a/.github/workflows/code-coverage.yml +++ b/.github/workflows/code-coverage.yml @@ -26,7 +26,6 @@ jobs: libzstd-dev libgit2-dev qtbase5-dev - libqt5webkit5-dev libquazip5-dev libglew-dev libfreetype6-dev diff --git a/.github/workflows/macos-macports-build.yml b/.github/workflows/macos-macports-build.yml index 49a943b931..e0c40dbafe 100644 --- a/.github/workflows/macos-macports-build.yml +++ b/.github/workflows/macos-macports-build.yml @@ -66,7 +66,6 @@ jobs: glew qt5-qtbase qt5-qttools - qt5-qtwebkit quazip py${{ env.PYTHON_VERSION_NO_DOT }}-pip - name: Install Sphinx diff --git a/.github/workflows/ubuntu-build.yml b/.github/workflows/ubuntu-build.yml index 1b546ab83b..89c8d52e5f 100644 --- a/.github/workflows/ubuntu-build.yml +++ b/.github/workflows/ubuntu-build.yml @@ -34,7 +34,6 @@ jobs: libzstd-dev libgit2-dev qtbase5-dev - libqt5webkit5-dev libquazip5-dev libglew-dev libfreetype6-dev diff --git a/.github/workflows/ubuntu-qt6-build.yml b/.github/workflows/ubuntu-qt6-build.yml index 6853aa3c28..4dd89b6782 100644 --- a/.github/workflows/ubuntu-qt6-build.yml +++ b/.github/workflows/ubuntu-qt6-build.yml @@ -50,8 +50,6 @@ jobs: graphviz qt6-base-dev libqt6core5compat6-dev - qt6-webengine-dev - qt6-webengine-dev-tools xvfb - name: Install sphinx and sip run: sudo pip install sphinx sip diff --git a/.github/workflows/windows-mingw64-build.yml b/.github/workflows/windows-mingw64-build.yml index ddb5389940..21323e1c1b 100644 --- a/.github/workflows/windows-mingw64-build.yml +++ b/.github/workflows/windows-mingw64-build.yml @@ -48,7 +48,6 @@ jobs: mingw-w64-${{ matrix.config.arch }}-glew mingw-w64-${{ matrix.config.arch }}-qt5 mingw-w64-${{ matrix.config.arch }}-quazip - mingw-w64-${{ matrix.config.arch }}-qtwebkit mingw-w64-${{ matrix.config.arch }}-python-sphinx mingw-w64-${{ matrix.config.arch }}-sip - name: Prepare ccache timestamp diff --git a/.github/workflows/windows-msvc-build.yml b/.github/workflows/windows-msvc-build.yml index 7e40792728..cbed2bc883 100644 --- a/.github/workflows/windows-msvc-build.yml +++ b/.github/workflows/windows-msvc-build.yml @@ -62,7 +62,6 @@ jobs: arch: win64_msvc2019_64 modules: qtimageformats qtpositioning - qtwebengine qtwebchannel qtwebsockets qt5compat diff --git a/bundlers/linux/talipot_appimage_centos_build.sh b/bundlers/linux/talipot_appimage_centos_build.sh index 428321dff5..602e54dfcd 100644 --- a/bundlers/linux/talipot_appimage_centos_build.sh +++ b/bundlers/linux/talipot_appimage_centos_build.sh @@ -50,10 +50,10 @@ yum -y install freetype-devel fontconfig-devel glew-devel fribidi-devel if [ "$centos8" = true ] then yum -y install qt5-qtbase-devel qt5-qtimageformats qt5-qtsvg \ - quazip-qt5-devel qt5-qtwebkit-devel --enablerepo=epel-testing --nobest + quazip-qt5-devel --enablerepo=epel-testing --nobest else yum -y install qt6-qtbase-devel qt6-qtimageformats qt6-qtsvg \ - qt6-qt5compat-devel qt6-qtwebengine-devel + qt6-qt5compat-devel fi # install Python 3, Sphinx and SIP @@ -101,23 +101,6 @@ bash bundlers/linux/make_appimage_bundle.sh --appdir $PWD APP_DIR=Talipot.AppDir -# ensure QtWebEngine is functional when bundled in AppImage -if [ "$centos9" = true ] -then - yum -y install patchelf cpio - # for some reasons, qt6-qtwebengine translations files are not installed - # by yum but those are still available in the rpm archive so we hack a bit - # to extract and copy them in the AppImage AppDir - yum -y remove --noautoremove qt6-qtwebengine - yum -y install --downloadonly --downloaddir=$PWD qt6-qtwebengine - rpm2cpio qt6-qtwebengine*.rpm | cpio -idmv --directory=/opt/qtwebengine - cp -r /opt/qtwebengine/usr/share/qt6/translations/qtwebengine_locales/ $APP_DIR/usr/translations/ - # this file is also required to be bundled in AppImage or V8 crashes on startup - cp /opt/qtwebengine/usr/share/qt6/resources/v8_context_snapshot.bin $APP_DIR/usr/resources/ - # rpath of QtWebEngineProcess also needs to be patched to work in AppImage - patchelf --set-rpath '$ORIGIN/../lib' $APP_DIR/usr/libexec/QtWebEngineProcess -fi - # get appimagetool wget "https://github.com/probonopd/AppImageKit/releases/download/\ continuous/appimagetool-$(uname -p).AppImage" diff --git a/cmake/FindQtX.cmake b/cmake/FindQtX.cmake index 4d505d4f4e..f1c5d83c4f 100644 --- a/cmake/FindQtX.cmake +++ b/cmake/FindQtX.cmake @@ -21,8 +21,6 @@ UNSET(Qt${QT_MAJOR_VERSION}Widgets_FOUND CACHE) UNSET(Qt${QT_MAJOR_VERSION}Xml_FOUND CACHE) UNSET(Qt${QT_MAJOR_VERSION}XmlPatterns_FOUND CACHE) UNSET(Qt${QT_MAJOR_VERSION}Network_FOUND CACHE) -UNSET(Qt${QT_MAJOR_VERSION}WebKit_FOUND CACHE) -UNSET(Qt${QT_MAJOR_VERSION}WebKitWidgets_FOUND CACHE) UNSET(Qt${QT_MAJOR_VERSION}Multimedia_FOUND CACHE) UNSET(Qt${QT_MAJOR_VERSION}MultimediaWidgets_FOUND CACHE) UNSET(Qt${QT_MAJOR_VERSION}Positioning_FOUND CACHE) @@ -31,7 +29,6 @@ UNSET(Qt${QT_MAJOR_VERSION}Qml_FOUND CACHE) UNSET(Qt${QT_MAJOR_VERSION}Quick_FOUND CACHE) UNSET(Qt${QT_MAJOR_VERSION}Sensors_FOUND CACHE) UNSET(Qt${QT_MAJOR_VERSION}DBus_FOUND CACHE) -UNSET(Qt${QT_MAJOR_VERSION}WebEngineWidgets_FOUND CACHE) UNSET(Qt${QT_MAJOR_VERSION}WebChannel_FOUND CACHE) IF(QT_MAJOR_VERSION EQUAL 6) @@ -44,8 +41,6 @@ UNSET(Qt${QT_MAJOR_VERSION}Widgets_DIR CACHE) UNSET(Qt${QT_MAJOR_VERSION}Xml_DIR CACHE) UNSET(Qt${QT_MAJOR_VERSION}XmlPatterns_DIR CACHE) UNSET(Qt${QT_MAJOR_VERSION}Network_DIR CACHE) -UNSET(Qt${QT_MAJOR_VERSION}WebKit_DIR CACHE) -UNSET(Qt${QT_MAJOR_VERSION}WebKitWidgets_DIR CACHE) UNSET(Qt${QT_MAJOR_VERSION}Multimedia_DIR CACHE) UNSET(Qt${QT_MAJOR_VERSION}MultimediaWidgets_DIR CACHE) UNSET(Qt${QT_MAJOR_VERSION}Positioning_DIR CACHE) @@ -54,7 +49,6 @@ UNSET(Qt${QT_MAJOR_VERSION}Qml_DIR CACHE) UNSET(Qt${QT_MAJOR_VERSION}Quick_DIR CACHE) UNSET(Qt${QT_MAJOR_VERSION}Sensors_DIR CACHE) UNSET(Qt${QT_MAJOR_VERSION}DBus_DIR CACHE) -UNSET(Qt${QT_MAJOR_VERSION}WebEngineWidgets_DIR CACHE) UNSET(Qt${QT_MAJOR_VERSION}WebChannel_DIR CACHE) IF(QT_MAJOR_VERSION EQUAL 6) @@ -62,32 +56,6 @@ IF(QT_MAJOR_VERSION EQUAL 6) UNSET(Qt6Core5Compat_DIR CACHE) ENDIF(QT_MAJOR_VERSION EQUAL 6) -SET(QT_HAS_WEBKIT FALSE) -SET(QT_HAS_WEBENGINE FALSE) - -# Macro used to workaround a small issue with QtWebkit components on MSYS2: when -# compiling in RelWithDebInfo mode, Qt debug libraries are selected instead of -# the release one (this should only happen when compiling in Debug mode) -MACRO(SETUP_QT_LIBRARIES QtModule LIBRARIES) - IF(MINGW) - GET_TARGET_PROPERTY( - Qt${QT_MAJOR_VERSION}${QtModule}_INCLUDE_DIRS - Qt${QT_MAJOR_VERSION}::${QtModule} INTERFACE_INCLUDE_DIRECTORIES) - - IF(CMAKE_DEBUG_MODE) - GET_TARGET_PROPERTY(Qt${QT_MAJOR_VERSION}${QtModule}_LIBRARIES - Qt${QT_MAJOR_VERSION}::${QtModule} LOCATION_DEBUG) - ELSE(CMAKE_DEBUG_MODE) - GET_TARGET_PROPERTY(Qt${QT_MAJOR_VERSION}${QtModule}_LIBRARIES - Qt${QT_MAJOR_VERSION}::${QtModule} LOCATION_RELEASE) - ENDIF(CMAKE_DEBUG_MODE) - ENDIF(MINGW) - - SET(${LIBRARIES} - ${${LIBRARIES}} ${Qt${QT_MAJOR_VERSION}${QtModule}_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}${QtModule}_LIBRARIES}) -ENDMACRO(SETUP_QT_LIBRARIES) - FIND_PACKAGE(Qt${QT_MAJOR_VERSION}Widgets ${QT_MIN_VERSION} REQUIRED) FIND_PACKAGE(Qt${QT_MAJOR_VERSION}Network ${QT_MIN_VERSION} REQUIRED) @@ -201,52 +169,6 @@ IF(APPLE) ENDIF(EXISTS ${QT_DBUS_CMAKE_DIR} AND EXISTS ${QT_PRINTSUPPORT_CMAKE_DIR}) ENDIF(APPLE) -# Check if the QtX installation is bundled with WebKit (deprecated since Qt 5.5) -# and setup its use if it is the case. -SET(QT_WEBKIT_WIDGETS_CMAKE_DIR - "${QT_CMAKE_DIR}/Qt${QT_MAJOR_VERSION}WebKitWidgets") - -IF(EXISTS ${QT_WEBKIT_WIDGETS_CMAKE_DIR}) - FIND_PACKAGE(Qt${QT_MAJOR_VERSION}WebKit) - FIND_PACKAGE(Qt${QT_MAJOR_VERSION}WebKitWidgets) - - IF(${Qt${QT_MAJOR_VERSION}WebKit_FOUND} - AND ${Qt${QT_MAJOR_VERSION}WebKitWidgets_FOUND}) - SET(QT_HAS_WEBKIT TRUE) - SET(QT_WEB_COMPONENT "QtWebKit") - SETUP_QT_LIBRARIES(WebKit QT_WEB_LIBRARIES) - SETUP_QT_LIBRARIES(WebKitWidgets QT_WEB_LIBRARIES) - ENDIF(${Qt${QT_MAJOR_VERSION}WebKit_FOUND} - AND ${Qt${QT_MAJOR_VERSION}WebKitWidgets_FOUND}) -ENDIF(EXISTS ${QT_WEBKIT_WIDGETS_CMAKE_DIR}) - -# If QtX is not bundled with WebKit then check if its installation provides -# WebEngine (new web module since Qt 5.4) and setup its use. -SET(QT_WEBENGINE_WIDGETS_CMAKE_DIR - "${QT_CMAKE_DIR}/Qt${QT_MAJOR_VERSION}WebEngineWidgets") - -IF(NOT QT_HAS_WEBKIT AND EXISTS ${QT_WEBENGINE_WIDGETS_CMAKE_DIR}) - FIND_PACKAGE(Qt${QT_MAJOR_VERSION}WebEngineWidgets ${QT_MIN_VERSION}) - FIND_PACKAGE(Qt${QT_MAJOR_VERSION}WebChannel ${QT_MIN_VERSION}) - - IF(${Qt${QT_MAJOR_VERSION}WebEngineWidgets_FOUND} - AND ${Qt${QT_MAJOR_VERSION}WebChannel_FOUND}) - SET(QT_HAS_WEBENGINE TRUE) - SET(QT_WEB_COMPONENT "QtWebEngine") - SET(QT_WEB_LIBRARIES ${Qt${QT_MAJOR_VERSION}WebEngineWidgets_LIBRARIES} - ${Qt${QT_MAJOR_VERSION}WebChannel_LIBRARIES}) - ENDIF(${Qt${QT_MAJOR_VERSION}WebEngineWidgets_FOUND} - AND ${Qt${QT_MAJOR_VERSION}WebChannel_FOUND}) -ENDIF(NOT QT_HAS_WEBKIT AND EXISTS ${QT_WEBENGINE_WIDGETS_CMAKE_DIR}) - -IF(NOT "${QT_WEB_COMPONENT}" STREQUAL "${LAST_FOUND_QT_WEB_COMPONENT}") - MESSAGE(STATUS "Found ${QT_WEB_COMPONENT}") -ENDIF(NOT "${QT_WEB_COMPONENT}" STREQUAL "${LAST_FOUND_QT_WEB_COMPONENT}") - -SET(LAST_FOUND_QT_WEB_COMPONENT - "${QT_WEB_COMPONENT}" - CACHE INTERNAL "") - MACRO(QTX_SET_INCLUDES_AND_DEFINITIONS) INCLUDE_DIRECTORIES(${Qt${QT_MAJOR_VERSION}Widgets_INCLUDE_DIRS}) INCLUDE_DIRECTORIES(${Qt${QT_MAJOR_VERSION}Network_INCLUDE_DIRS}) @@ -261,24 +183,6 @@ MACRO(QTX_SET_INCLUDES_AND_DEFINITIONS) ENDIF(QT_MAJOR_VERSION EQUAL 6) ENDMACRO() -MACRO(QTXWEB_SET_INCLUDES_AND_DEFINITIONS) - IF(QT_HAS_WEBKIT) - INCLUDE_DIRECTORIES(${Qt${QT_MAJOR_VERSION}WebKit_INCLUDE_DIRS}) - INCLUDE_DIRECTORIES(${Qt${QT_MAJOR_VERSION}WebKitWidgets_INCLUDE_DIRS}) - ADD_DEFINITIONS(${Qt${QT_MAJOR_VERSION}WebKit_DEFINITIONS}) - ADD_DEFINITIONS(${Qt${QT_MAJOR_VERSION}WebKitWidgets_DEFINITIONS}) - ADD_COMPILE_DEFINITIONS(QT_HAS_WEBKIT) - ENDIF() - - IF(QT_HAS_WEBENGINE) - INCLUDE_DIRECTORIES(${Qt${QT_MAJOR_VERSION}WebEngineWidgets_INCLUDE_DIRS}) - INCLUDE_DIRECTORIES(${Qt${QT_MAJOR_VERSION}WebChannel_INCLUDE_DIRS}) - ADD_DEFINITIONS(${Qt${QT_MAJOR_VERSION}WebEngineWidgets_DEFINITIONS}) - ADD_DEFINITIONS(${Qt${QT_MAJOR_VERSION}WebChannel_DEFINITIONS}) - ADD_COMPILE_DEFINITIONS(QT_HAS_WEBENGINE) - ENDIF() -ENDMACRO(QTXWEB_SET_INCLUDES_AND_DEFINITIONS) - # Define aliases for Qt macros in order to build the project IF(QT_MAJOR_VERSION EQUAL 5) MACRO(QT_WRAP_CPP outfiles) diff --git a/plugins/view/CMakeLists.txt b/plugins/view/CMakeLists.txt index 9f882803db..aa3da4b3e7 100644 --- a/plugins/view/CMakeLists.txt +++ b/plugins/view/CMakeLists.txt @@ -1,14 +1,7 @@ QTX_SET_INCLUDES_AND_DEFINITIONS() ADD_SUBDIRECTORY(HistogramView) - -IF(QT_HAS_WEBKIT OR QT_HAS_WEBENGINE) - ADD_SUBDIRECTORY(GeographicView) -ELSE() - MESSAGE( - "Unable to build 'GeographicView': QtWebKit and QtWebEngine are missing") -ENDIF(QT_HAS_WEBKIT OR QT_HAS_WEBENGINE) - +ADD_SUBDIRECTORY(GeographicView) ADD_SUBDIRECTORY(MatrixView) ADD_SUBDIRECTORY(ParallelCoordinatesView) ADD_SUBDIRECTORY(PixelOrientedView) diff --git a/plugins/view/GeographicView/CMakeLists.txt b/plugins/view/GeographicView/CMakeLists.txt index ebda7fdcf4..dd629c882b 100644 --- a/plugins/view/GeographicView/CMakeLists.txt +++ b/plugins/view/GeographicView/CMakeLists.txt @@ -6,10 +6,10 @@ INCLUDE_DIRECTORIES( ${TalipotUIGUIInclude} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} - ${GLEW_INCLUDE_DIR}) + ${GLEW_INCLUDE_DIR} + ${QGeoViewInclude}) QTX_SET_INCLUDES_AND_DEFINITIONS() -QTXWEB_SET_INCLUDES_AND_DEFINITIONS() SET(LIB_SRCS GeographicView.cpp @@ -18,7 +18,6 @@ SET(LIB_SRCS ProgressWidgetGraphicsProxy.cpp GeographicViewInteractors.cpp AddressSelectionDialog.cpp - LeafletMaps.cpp GeographicViewGraphicsView.cpp GeographicViewShowElementInfo.cpp NominatimGeocoder.cpp) @@ -36,26 +35,7 @@ QT_WRAP_CPP( ProgressWidgetGraphicsProxy.h GeographicViewConfigWidget.h GeographicViewShowElementInfo.h - GeolocationConfigWidget.h - LeafletMaps.h) - -IF(MSVC) - # When compiling with Visual Studio, install QtWebEngine process, resources - # and locales required for the Geographic view to work when installing Talipot - # from a Windows installer - IF(EXISTS "${QT_BINARY_DIR}/QtWebEngineProcess.exe") - INSTALL(FILES "${QT_BINARY_DIR}/QtWebEngineProcess.exe" - DESTINATION ${TalipotBinInstallDir}) - ENDIF(EXISTS "${QT_BINARY_DIR}/QtWebEngineProcess.exe") - IF(EXISTS "${QT_BINARY_DIR}/../resources") - INSTALL(DIRECTORY "${QT_BINARY_DIR}/../resources" - DESTINATION ${TalipotBinInstallDir}) - ENDIF(EXISTS "${QT_BINARY_DIR}/../resources") - IF(EXISTS "${QT_BINARY_DIR}/../translations/qtwebengine_locales") - INSTALL(DIRECTORY "${QT_BINARY_DIR}/../translations/qtwebengine_locales" - DESTINATION ${TalipotBinInstallDir}/translations/) - ENDIF(EXISTS "${QT_BINARY_DIR}/../translations/qtwebengine_locales") -ENDIF(MSVC) + GeolocationConfigWidget.h) TALIPOT_ADD_PLUGIN( NAME @@ -71,7 +51,7 @@ TALIPOT_ADD_PLUGIN( ${LibTalipotGUIName} ${OPENGL_gl_LIBRARY} ${QT_LIBRARIES} - ${QT_WEB_LIBRARIES} + ${QGeoViewLibrary} INSTALL_DIR ${TalipotViewPluginsInstallDir} FIXUP_INSTALL) diff --git a/plugins/view/GeographicView/GeographicView.cpp b/plugins/view/GeographicView/GeographicView.cpp index a6f6653db5..a430ac6efe 100644 --- a/plugins/view/GeographicView/GeographicView.cpp +++ b/plugins/view/GeographicView/GeographicView.cpp @@ -18,7 +18,10 @@ #include #include +#include +#include #include +#include #include "GeographicView.h" #include "GeographicViewGraphicsView.h" @@ -183,9 +186,9 @@ void GeographicView::setState(const DataSet &dataSet) { } void GeographicView::initMap() { - geoViewGraphicsView->getLeafletMapsPage()->setMapCenter(mapCenterLatitudeInit, - mapCenterLongitudeInit); - geoViewGraphicsView->getLeafletMapsPage()->setCurrentZoom(mapZoomInit); + // geoViewGraphicsView->getLeafletMapsPage()->setMapCenter(mapCenterLatitudeInit, + // mapCenterLongitudeInit); + // geoViewGraphicsView->getLeafletMapsPage()->setCurrentZoom(mapZoomInit); } DataSet GeographicView::state() const { @@ -193,10 +196,11 @@ DataSet GeographicView::state() const { DataSet configurationWidget = geoViewConfigWidget->state(); dataSet.set("configurationWidget", configurationWidget); dataSet.set("viewType", int(_viewType)); - pair mapCenter = geoViewGraphicsView->getLeafletMapsPage()->getCurrentMapCenter(); - dataSet.set("mapCenterLatitude", mapCenter.first); - dataSet.set("mapCenterLongitude", mapCenter.second); - dataSet.set("mapZoom", geoViewGraphicsView->getLeafletMapsPage()->getCurrentMapZoom()); + // pair mapCenter = + // geoViewGraphicsView->getLeafletMapsPage()->getCurrentMapCenter(); + // dataSet.set("mapCenterLatitude", mapCenter.first); + // dataSet.set("mapCenterLongitude", mapCenter.second); + // dataSet.set("mapZoom", geoViewGraphicsView->getLeafletMapsPage()->getCurrentMapZoom()); dataSet.set("renderingParameters", geoViewGraphicsView->glWidget()->renderingParameters().getParameters()); @@ -461,10 +465,6 @@ void GeographicView::graphChanged(Graph *graph) { } } -LeafletMaps *GeographicView::getLeafletMap() { - return geoViewGraphicsView->getLeafletMapsPage(); -} - bool GeographicView::getNodeOrEdgeAtViewportPos(int x, int y, node &n, edge &e) const { return GlView::getNodeOrEdgeAtViewportPos(geoViewGraphicsView->glWidget(), x, y, n, e); } diff --git a/plugins/view/GeographicView/GeographicView.h b/plugins/view/GeographicView/GeographicView.h index b0f76c671d..3aff4960c1 100644 --- a/plugins/view/GeographicView/GeographicView.h +++ b/plugins/view/GeographicView/GeographicView.h @@ -36,7 +36,6 @@ namespace tlp { class GeographicViewGraphicsView; class GeographicViewConfigWidget; class GeolocationConfigWidget; -class LeafletMaps; /** \file * \brief Geographic View @@ -98,8 +97,6 @@ class GeographicView : public View { QGraphicsItem *centralItem() const override; - LeafletMaps *getLeafletMap(); - void registerTriggers(); ViewType viewType() { diff --git a/plugins/view/GeographicView/GeographicViewGraphicsView.cpp b/plugins/view/GeographicView/GeographicViewGraphicsView.cpp index 0d11690f2d..f3d6ba13c8 100644 --- a/plugins/view/GeographicView/GeographicViewGraphicsView.cpp +++ b/plugins/view/GeographicView/GeographicViewGraphicsView.cpp @@ -27,12 +27,23 @@ #include #include +#include #include #include -#include -#include +#include #include #include +#include +#include +#include + +#include +#include +#include +#include + +#include "AddressSelectionDialog.h" +#include "ProgressWidgetGraphicsProxy.h" using namespace std; @@ -40,6 +51,15 @@ namespace tlp { const string planisphereTextureId = ":/talipot/view/geographic/planisphere.jpg"; +void setupCachedNetworkAccessManager(QObject *parent) { + QDir("cacheDir").removeRecursively(); + auto cache = new QNetworkDiskCache(parent); + cache->setCacheDirectory("cacheDir"); + auto manager = new QNetworkAccessManager(parent); + manager->setCache(cache); + QGV::setNetworkManager(manager); +} + class CustomGlWidget : public GlWidget { public: CustomGlWidget(QWidget *parent = nullptr, View *view = nullptr) : GlWidget(parent, view) {} @@ -262,11 +282,11 @@ QGraphicsProxyWidget *proxyGM = nullptr; GeographicViewGraphicsView::GeographicViewGraphicsView(GeographicView *geoView, QGraphicsScene *graphicsScene, QWidget *parent) - : QGraphicsView(graphicsScene, parent), _geoView(geoView), graph(nullptr), leafletMaps(nullptr), + : QGraphicsView(graphicsScene, parent), _geoView(geoView), graph(nullptr), globeCameraBackup(nullptr, true), mapCameraBackup(nullptr, true), geoLayout(nullptr), geoViewSize(nullptr), geoViewShape(nullptr), geoLayoutBackup(nullptr), geocodingActive(false), cancelGeocoding(false), polygonEntity(nullptr), planisphereEntity(nullptr), - noLayoutMsgBox(nullptr), firstGlobeSwitch(true), geoLayoutComputed(false), renderFbo(nullptr), + noLayoutMsgBox(nullptr), firstGlobeSwitch(true), geoLayoutComputed(false), latitudeProperty(nullptr), longitudeProperty(nullptr) { mapTextureId = "leafletMap" + to_string(reinterpret_cast(this)); setRenderHints(QPainter::SmoothPixmapTransform | QPainter::Antialiasing | @@ -275,59 +295,41 @@ GeographicViewGraphicsView::GeographicViewGraphicsView(GeographicView *geoView, setFrameStyle(QFrame::NoFrame); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - leafletMaps = new LeafletMaps(); - leafletMaps->setMouseTracking(false); - leafletMaps->resize(512, 512); - leafletMaps->installEventFilter(this); + setMouseTracking(false); + + qgvMap = new QGVMap(); + qgvMap->installEventFilter(this); + setupCachedNetworkAccessManager(this); + auto osmLayer = new QGVLayerOSM(); + qgvMap->addItem(osmLayer); + progressWidget = new ProgressWidgetGraphicsProxy(); progressWidget->hide(); progressWidget->setZValue(2); - addressSelectionDialog = new AddressSelectionDialog(leafletMaps); + addressSelectionDialog = new AddressSelectionDialog(qgvMap); scene()->addItem(progressWidget); addressSelectionProxy = scene()->addWidget(addressSelectionDialog, Qt::Dialog); addressSelectionProxy->hide(); addressSelectionProxy->setZValue(3); - leafletMaps->setProgressWidget(progressWidget); - leafletMaps->setAdresseSelectionDialog(addressSelectionDialog, addressSelectionProxy); - - connect(leafletMaps, &LeafletMaps::currentZoomChanged, _geoView, - &GeographicView::currentZoomChanged); -#ifdef QT_HAS_WEBENGINE - tId = 0; - connect(leafletMaps, &LeafletMaps::refreshMap, this, - &GeographicViewGraphicsView::queueMapRefresh); -#else - connect(leafletMaps, &LeafletMaps::refreshMap, this, &GeographicViewGraphicsView::refreshMap); -#endif _placeholderItem = new QGraphicsRectItem(0, 0, 1, 1); _placeholderItem->setBrush(Qt::transparent); _placeholderItem->setPen(QPen(Qt::transparent)); scene()->addItem(_placeholderItem); - QGraphicsProxyWidget *proxyGM = scene()->addWidget(leafletMaps); + qgvMap->geoView()->setParent(nullptr); + QGraphicsProxyWidget *proxyGM = scene()->addWidget(qgvMap->geoView()); proxyGM->setPos(0, 0); proxyGM->setParentItem(_placeholderItem); _glWidget = new CustomGlWidget(nullptr, geoView); delete _glWidget->scene()->getCalculator(); _glWidget->scene()->setCalculator(new GlCPULODCalculator()); - _glWidget->scene()->setBackgroundColor(Color::White); + _glWidget->scene()->setBackgroundColor(Color(0, 0, 0, 0)); glWidgetItem = new GlWidgetGraphicsItem(_glWidget, 512, 512); glWidgetItem->setPos(0, 0); - // disable user input - // before allowing some display feedback - tlp::disableQtUserInput(); - - while (!leafletMaps->pageInit()) { - QApplication::processEvents(); - } - - // re-enable user input - tlp::enableQtUserInput(); - scene()->addItem(glWidgetItem); glWidgetItem->setParentItem(_placeholderItem); @@ -378,18 +380,19 @@ GeographicViewGraphicsView::GeographicViewGraphicsView(GeographicView *geoView, noLayoutMsgBox->setParentItem(_placeholderItem); setAcceptDrops(false); + + connect(qgvMap, &QGVMap::scaleChanged, this, &GeographicViewGraphicsView::refreshMap); + connect(qgvMap, &QGVMap::azimuthChanged, this, &GeographicViewGraphicsView::refreshMap); + connect(qgvMap, &QGVMap::areaChanged, this, &GeographicViewGraphicsView::refreshMap); + connect(qgvMap, &QGVMap::stateChanged, this, &GeographicViewGraphicsView::refreshMap); + + QTimer::singleShot(100, this, [this]() { + auto target = qgvMap->getProjection()->boundaryGeoRect(); + qgvMap->cameraTo(QGVCameraActions(qgvMap).scaleTo(target)); + }); } GeographicViewGraphicsView::~GeographicViewGraphicsView() { -#ifdef QT_HAS_WEBENGINE - // first kill refreshMap timer if any - // and reset tId to try to ensure refreshMap - // will not be called later - if (tId) { - killTimer(tId); - tId = 0; - } -#endif if (geocodingActive) { if (addressSelectionDialog->isVisible()) { addressSelectionDialog->accept(); @@ -462,12 +465,6 @@ void GeographicViewGraphicsView::setGraph(Graph *graph) { layer->addGlEntity(glGraph, "graph"); - backgroundLayer = new GlLayer("Background"); - backgroundLayer->set2DMode(); - auto *backgroundRect = new Gl2DRect(0, 1, 0, 1, mapTextureId, true); - backgroundLayer->addGlEntity(backgroundRect, "geoview_background"); - scene->addExistingLayerBefore(backgroundLayer, "Main"); - if (geoLayout) { geoLayout->removeListener(this); } @@ -607,17 +604,23 @@ void GeographicViewGraphicsView::mapToPolygon() { } } +static const double zoomExponentDown = std::pow(2, 1.0 / 5.0); +static const double zoomExponentUp = 1.0 / std::pow(2, 1.0 / 5.0); + void GeographicViewGraphicsView::zoomIn() { - leafletMaps->setCurrentZoom(leafletMaps->getCurrentMapZoom() + 1); + qgvMap->cameraTo(QGVCameraActions(qgvMap).scaleBy(zoomExponentDown)); + currentZoomChanged(); } void GeographicViewGraphicsView::zoomOut() { - leafletMaps->setCurrentZoom(leafletMaps->getCurrentMapZoom() - 1); + qgvMap->cameraTo(QGVCameraActions(qgvMap).scaleBy(zoomExponentUp)); + currentZoomChanged(); } void GeographicViewGraphicsView::currentZoomChanged() { - zoomInButton->setEnabled(leafletMaps->getCurrentMapZoom() != 20); - zoomOutButton->setEnabled(leafletMaps->getCurrentMapZoom() != 0); + QGVCameraState camera = qgvMap->getCamera(); + zoomInButton->setEnabled(camera.scale() <= qgvMap->geoView()->getMaxScale()); + zoomOutButton->setEnabled(camera.scale() >= qgvMap->geoView()->getMinScale()); } GlGraph *GeographicViewGraphicsView::glGraph() const { @@ -794,7 +797,7 @@ void GeographicViewGraphicsView::createLayoutWithLatLngs(const std::string &lati void GeographicViewGraphicsView::resizeEvent(QResizeEvent *event) { QGraphicsView::resizeEvent(event); scene()->setSceneRect(QRect(QPoint(0, 0), size())); - leafletMaps->resize(width(), height()); + qgvMap->geoView()->resize(width(), height()); glWidgetItem->resize(width(), height()); if (progressWidget->isVisible()) { @@ -818,60 +821,56 @@ void GeographicViewGraphicsView::resizeEvent(QResizeEvent *event) { } } -#ifdef QT_HAS_WEBENGINE -void GeographicViewGraphicsView::queueMapRefresh() { - tId = startTimer(10); -} - -void GeographicViewGraphicsView::timerEvent(QTimerEvent *event) { - killTimer(event->timerId()); - // call refreshMap if needed - // accessing this->tId may result in a Free Memory Read - // because surprisingly this method may be called - // after this has been deleted - if (tId == event->timerId()) { - tId = 0; - refreshMap(); - } -} -#endif - void GeographicViewGraphicsView::refreshMap() { - if (!leafletMaps->isVisible() || !leafletMaps->mapLoaded()) { + if (!qgvMap->geoView()->isVisible()) { return; } - GlOffscreenRenderer::instance().makeOpenGLContextCurrent(); - - BoundingBox bb; - Coord rightCoord = leafletMaps->getPixelPosOnScreenForLatLng(180, 180); - Coord leftCoord = leafletMaps->getPixelPosOnScreenForLatLng(0, 0); - - if (rightCoord[0] - leftCoord[0]) { - float mapWidth = (width() / (rightCoord - leftCoord)[0]) * 180.; - float middleLng = - leafletMaps->getLatLngForPixelPosOnScreen(width() / 2., height() / 2.).second * 2.; - bb.expand(Coord(middleLng - mapWidth / 2., - latitudeToMercator(leafletMaps->getLatLngForPixelPosOnScreen(0, 0).first), 0)); - bb.expand(Coord( - middleLng + mapWidth / 2., - latitudeToMercator(leafletMaps->getLatLngForPixelPosOnScreen(width(), height()).first), 0)); + // GlOffscreenRenderer::instance().makeOpenGLContextCurrent(); + + QGVProjection *projection = qgvMap->getProjection(); + + QGV::GeoRect geoRect = projection->projToGeo(qgvMap->getCamera().projRect()); + + QGV::GeoPos sw = geoRect.bottomRight(); + QGV::GeoPos ne = geoRect.topLeft(); + + if (sw.longitude() != ne.longitude()) { + BoundingBox bb; + bb.expand(Coord(ne.longitude() * 2, latitudeToMercator(ne.latitude()))); + bb.expand(Coord(sw.longitude() * 2, latitudeToMercator(sw.latitude()))); + // Camera &camera = _glWidget->scene()->graphCamera(); + // if (camera.getCenter() == Coord()) { + // camera.setCenter(bb.center()); + // } GlSceneZoomAndPan sceneZoomAndPan(_glWidget->scene(), bb, "Main", 1); sceneZoomAndPan.zoomAndPanAnimationStep(1); } - updateMapTexture(); + qgvMap->setVisible(false); glWidgetItem->setRedrawNeeded(true); scene()->update(); } void GeographicViewGraphicsView::centerView() { - if (leafletMaps->isVisible()) { - leafletMaps->setMapBounds(graph, nodeLatLng); - } else { - _glWidget->centerScene(); + _glWidget->centerScene(); + if (qgvMap->geoView()->isVisible()) { + if (!nodeLatLng.empty()) { + auto minLatLng = make_pair(90.0, 180.0); + auto maxLatLng = make_pair(-90.0, -180.0); + for (auto [n, latLng] : nodeLatLng) { + if (graph->isElement(n)) { + minLatLng.first = std::min(minLatLng.first, latLng.first); + minLatLng.second = std::min(minLatLng.second, latLng.second); + maxLatLng.first = std::max(maxLatLng.first, latLng.first); + maxLatLng.second = std::max(maxLatLng.second, latLng.second); + } + } + QGV::GeoRect bounds(minLatLng.first, minLatLng.second, maxLatLng.first, maxLatLng.second); + qgvMap->cameraTo(QGVCameraActions(qgvMap).scaleTo(bounds)); + } } } @@ -926,7 +925,7 @@ void GeographicViewGraphicsView::switchViewType() { case GeographicView::LeafletCustomTileLayer: { enableLeafletMap = true; QString customTileLayerUrl = _geoView->getConfigWidget()->getCustomTileLayerUrl(); - leafletMaps->switchToCustomTileLayer(customTileLayerUrl); + // leafletMaps->switchToCustomTileLayer(customTileLayerUrl); break; } @@ -943,7 +942,7 @@ void GeographicViewGraphicsView::switchViewType() { default: { enableLeafletMap = true; - leafletMaps->switchToTileLayer(GeographicView::getViewNameFromType(viewType)); + // leafletMaps->switchToTileLayer(GeographicView::getViewNameFromType(viewType)); break; } } @@ -968,8 +967,7 @@ void GeographicViewGraphicsView::switchViewType() { Observable::holdObservers(); - leafletMaps->setVisible(enableLeafletMap); - backgroundLayer->setVisible(enableLeafletMap); + qgvMap->geoView()->setVisible(enableLeafletMap); if (polygonEntity) { polygonEntity->setVisible(enablePolygon); @@ -1108,32 +1106,6 @@ void GeographicViewGraphicsView::setGeoLayoutComputed() { _glWidget->scene()->glGraph()->setVisible(true); } -void GeographicViewGraphicsView::updateMapTexture() { - - int width = leafletMaps->geometry().width(); - int height = leafletMaps->geometry().height(); - - QImage image(width, height, QImage::Format_RGB32); - QPainter painter(&image); - leafletMaps->render(&painter); - painter.end(); - - GlOffscreenRenderer::instance().makeOpenGLContextCurrent(); - - if (renderFbo == nullptr || renderFbo->width() != width || renderFbo->height() != height) { - delete renderFbo; - renderFbo = new QOpenGLFramebufferObject(width, height); - GlTextureManager::registerExternalTexture(mapTextureId, renderFbo->texture()); - } - - renderFbo->bind(); - QOpenGLPaintDevice device(width, height); - QPainter fboPainter(&device); - fboPainter.drawImage(QRect(0, 0, width, height), image); - fboPainter.end(); - renderFbo->release(); -} - bool GeographicViewGraphicsView::eventFilter(QObject *, QEvent *e) { if (e->type() == QEvent::ContextMenu) { _geoView->showContextMenu(QCursor::pos(), static_cast(e)->pos()); @@ -1141,4 +1113,5 @@ bool GeographicViewGraphicsView::eventFilter(QObject *, QEvent *e) { } return false; } + } diff --git a/plugins/view/GeographicView/GeographicViewGraphicsView.h b/plugins/view/GeographicView/GeographicViewGraphicsView.h index 160dab4cda..a557d61fbd 100644 --- a/plugins/view/GeographicView/GeographicViewGraphicsView.h +++ b/plugins/view/GeographicView/GeographicViewGraphicsView.h @@ -16,8 +16,6 @@ #include -#include "LeafletMaps.h" - #include #include #include @@ -25,12 +23,15 @@ #include #include +#include -class QOpenGLFramebufferObject; +class QGVMap; namespace tlp { +class AddressSelectionDialog; class GeographicView; +class ProgressWidgetGraphicsProxy; class GeographicViewGraphicsView : public QGraphicsView, public Observable { @@ -61,10 +62,6 @@ class GeographicViewGraphicsView : public QGraphicsView, public Observable { return _glWidget; } - LeafletMaps *getLeafletMapsPage() const { - return leafletMaps; - } - LayoutProperty *getGeoLayout() const { return geoLayout; } @@ -109,24 +106,15 @@ public slots: void zoomIn(); void zoomOut(); void currentZoomChanged(); -#ifdef QT_HAS_WEBENGINE - void queueMapRefresh(); -#endif void refreshMap(); protected: void cleanup(); void resizeEvent(QResizeEvent *event) override; -#ifdef QT_HAS_WEBENGINE - int tId; - void timerEvent(QTimerEvent *event) override; -#endif - void updateMapTexture(); private: GeographicView *_geoView; Graph *graph; - LeafletMaps *leafletMaps; tlp_hash_map> nodeLatLng; tlp_hash_map>> edgeBendsLatLng; @@ -161,12 +149,13 @@ public slots: bool geoLayoutComputed; - QOpenGLFramebufferObject *renderFbo; GlLayer *backgroundLayer; std::string mapTextureId; DoubleProperty *latitudeProperty; DoubleProperty *longitudeProperty; + + QGVMap *qgvMap; }; } diff --git a/plugins/view/GeographicView/GeographicViewInteractors.cpp b/plugins/view/GeographicView/GeographicViewInteractors.cpp index b0b18d52ae..9de27ac158 100644 --- a/plugins/view/GeographicView/GeographicViewInteractors.cpp +++ b/plugins/view/GeographicView/GeographicViewInteractors.cpp @@ -1,6 +1,6 @@ /** * - * Copyright (C) 2019-2023 The Talipot developers + * Copyright (C) 2019-2024 The Talipot developers * * Talipot is a fork of Tulip, created by David Auber * and the Tulip development Team from LaBRI, University of Bordeaux @@ -292,16 +292,16 @@ class GeographicViewMouseBoxZoomer : public MouseBoxZoomer { if (started) { bool ok = MouseBoxZoomer::eventFilter(widget, event); - auto *leafletMaps = geoView->getGeographicViewGraphicsView()->getLeafletMapsPage(); - auto *qMouseEv = static_cast(event); + // auto *leafletMaps = geoView->getGeographicViewGraphicsView()->getLeafletMapsPage(); + // auto *qMouseEv = static_cast(event); - if (ok && !started && (event->type() == QEvent::MouseButtonRelease) && graph && - (qMouseEv->button() & mButton)) { - auto *glWidget = static_cast(widget); - auto minBound = leafletMaps->getLatLngForPixelPosOnScreen(x, glWidget->height() - y + h); - auto maxBound = leafletMaps->getLatLngForPixelPosOnScreen(x + w, glWidget->height() - y); - leafletMaps->zoomOnBounds(minBound, maxBound); - } + // if (ok && !started && (event->type() == QEvent::MouseButtonRelease) && graph && + // (qMouseEv->button() & mButton)) { + // auto *glWidget = static_cast(widget); + // auto minBound = leafletMaps->getLatLngForPixelPosOnScreen(x, glWidget->height() - y + h); + // auto maxBound = leafletMaps->getLatLngForPixelPosOnScreen(x + w, glWidget->height() - y); + // leafletMaps->zoomOnBounds(minBound, maxBound); + // } return ok; } return MouseBoxZoomer::eventFilter(widget, event); diff --git a/plugins/view/GeographicView/GeographicViewResource.qrc b/plugins/view/GeographicView/GeographicViewResource.qrc index d50284e81f..85cacd1103 100644 --- a/plugins/view/GeographicView/GeographicViewResource.qrc +++ b/plugins/view/GeographicView/GeographicViewResource.qrc @@ -6,8 +6,5 @@ geographic_view.png zoom+.png zoom-.png - leaflet/leaflet.js - leaflet/leaflet.css - leaflet/no-tile.png diff --git a/plugins/view/GeographicView/GeographicViewShowElementInfo.cpp b/plugins/view/GeographicView/GeographicViewShowElementInfo.cpp index 1d2cedf28c..0ea4ef3009 100644 --- a/plugins/view/GeographicView/GeographicViewShowElementInfo.cpp +++ b/plugins/view/GeographicView/GeographicViewShowElementInfo.cpp @@ -1,6 +1,6 @@ /** * - * Copyright (C) 2019-2023 The Talipot developers + * Copyright (C) 2019-2024 The Talipot developers * * Talipot is a fork of Tulip, created by David Auber * and the Tulip development Team from LaBRI, University of Bordeaux @@ -25,6 +25,7 @@ #include #include +#include using namespace std; using namespace tlp; diff --git a/plugins/view/GeographicView/LeafletMaps.cpp b/plugins/view/GeographicView/LeafletMaps.cpp deleted file mode 100644 index 74bcc96317..0000000000 --- a/plugins/view/GeographicView/LeafletMaps.cpp +++ /dev/null @@ -1,432 +0,0 @@ -/** - * - * Copyright (C) 2019-2024 The Talipot developers - * - * Talipot is a fork of Tulip, created by David Auber - * and the Tulip development Team from LaBRI, University of Bordeaux - * - * See the AUTHORS file at the top-level directory of this distribution - * License: GNU General Public License version 3, or any later version - * See top-level LICENSE file for more information - * - */ - -#include - -#include "GeographicView.h" -#include "LeafletMaps.h" - -#include -#include - -#ifdef QT_HAS_WEBENGINE -#include -#include -#include -#endif - -using namespace std; - -namespace tlp { - -static QString htmlMap() { - return QString(R"( - - -)") + -#ifdef QT_HAS_WEBENGINE - R"()" + -#endif - QString(R"( - - - - - -
- - -)"; -} - -#ifdef QT_HAS_WEBENGINE -// https://stackoverflow.com/questions/66925445/qt-webengine-not-loading-openstreetmap-tiles -class OpenStreetMapSetAcceptLanguageHeader : public QWebEngineUrlRequestInterceptor { -public: - OpenStreetMapSetAcceptLanguageHeader(LeafletMaps *leafletMaps) : _leafletMaps(leafletMaps) {} - - void interceptRequest(QWebEngineUrlRequestInfo &info) override { - if (_leafletMaps->getCurrentLayerName() == - GeographicView::getViewNameFromType(GeographicView::OpenStreetMap)) { - info.setHttpHeader("Accept-Language", "en-US,en;q=0.9,fr;q=0.8,de;q=0.7"); - } - } - -private: - LeafletMaps *_leafletMaps; -}; -#endif - -class WebPage : -#ifdef QT_HAS_WEBENGINE - public QWebEnginePage { -protected: - bool acceptNavigationRequest(const QUrl &url, QWebEnginePage::NavigationType type, - bool isMainFrame) override { - if (type == QWebEnginePage::NavigationTypeLinkClicked) { - QDesktopServices::openUrl(url); - return false; - } - return QWebEnginePage::acceptNavigationRequest(url, type, isMainFrame); - } - - void javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel, - const QString &message, int, const QString &) override { - tlp::warning() << "[JavaScript output] " << QStringToTlpString(message) << std::endl; - } -#else - public QWebPage { -protected: - bool acceptNavigationRequest(QWebFrame *frame, const QNetworkRequest &request, - NavigationType type) override { - if (type == QWebPage::NavigationTypeLinkClicked) { - QDesktopServices::openUrl(request.url()); - return false; - } - return QWebPage::acceptNavigationRequest(frame, request, type); - } - - void javaScriptConsoleMessage(const QString &message, int, const QString &) override { - tlp::warning() << "[JavaScript output] " << QStringToTlpString(message) << std::endl; - } -#endif -}; - -#ifdef QT_HAS_WEBENGINE -void MapRefresher::refreshMap() { - emit refreshMapSignal(); -} - -JsCallback *JsCallback::_lastCreatedInstance = nullptr; -#endif - -#ifdef QT_HAS_WEBKIT -LeafletMaps::LeafletMaps(QWidget *parent) : QWebView(parent), init(false) { -#else -LeafletMaps::LeafletMaps(QWidget *parent) : QWebEngineView(parent), init(false) { -#endif - setPage(new WebPage); -#ifdef QT_HAS_WEBKIT - frame = page()->mainFrame(); - frame->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff); - frame->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff); -#else - frame = page(); - osmSetAccessLanguageHeader = new OpenStreetMapSetAcceptLanguageHeader(this); -#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) - frame->profile()->setUrlRequestInterceptor(osmSetAccessLanguageHeader); -#else - frame->profile()->setRequestInterceptor(osmSetAccessLanguageHeader); -#endif - mapRefresher = new MapRefresher; - connect(mapRefresher, &MapRefresher::refreshMapSignal, this, &LeafletMaps::refreshMap); - QWebChannel *channel = new QWebChannel(frame); - frame->setWebChannel(channel); - channel->registerObject(QStringLiteral("leafletMapsQObject"), mapRefresher); -#endif - frame->setHtml(htmlMap()); - QTimer::singleShot(500, this, &LeafletMaps::triggerLoading); -} - -LeafletMaps::~LeafletMaps() { -#ifdef QT_HAS_WEBENGINE - delete mapRefresher; - delete osmSetAccessLanguageHeader; -#endif -} - -QVariant LeafletMaps::executeJavascript(const QString &jsCode) { -#ifdef QT_HAS_WEBKIT - return frame->evaluateJavaScript(jsCode); -#else - QVariant ret; - JsCallback jscb(&ret); - frame->runJavaScript(jsCode, jscb); - JsCallback::waitForCallback(); - return ret; -#endif -} - -bool LeafletMaps::pageLoaded() { - QString code = "typeof init !== \"undefined\""; - QVariant ret = executeJavascript(code); - return ret.toBool(); -} - -bool LeafletMaps::mapLoaded() { - QString code = "typeof map !== \"undefined\""; - QVariant ret = executeJavascript(code); - return ret.toBool(); -} - -void LeafletMaps::triggerLoading() { - if (!pageLoaded()) { - QTimer::singleShot(500, this, &LeafletMaps::triggerLoading); - return; - } -#ifdef QT_HAS_WEBKIT - frame->addToJavaScriptWindowObject("leafletMapsQObject", this); -#endif - // map is first centered in the Atlantic Ocean - // in order to emphasize the need to configure geolocation - static const QString code = "init(44.8084, -40, 3)"; - executeJavascript(code); - init = true; -} - -void LeafletMaps::switchToTileLayer(const QString &layerName) { - static const QString code = "switchToTileLayerName('%1')"; - currentLayerName = layerName; - executeJavascript(code.arg(layerName)); -} - -void LeafletMaps::switchToCustomTileLayer(const QString &customTileLayerUrl) { - static const QString code = "switchToCustomTileLayer('%1')"; - executeJavascript(code.arg(customTileLayerUrl)); -} - -void LeafletMaps::setMapCenter(double latitude, double longitude) { - static const QString code = "map.setView(L.latLng(%1, %2), map.getZoom());"; - executeJavascript(code.arg(latitude).arg(longitude)); -} - -Coord LeafletMaps::getPixelPosOnScreenForLatLng(double lat, double lng) { - static const QString code = "map.latLngToContainerPoint(L.latLng(%1, %2)).toString();"; - QVariant ret = executeJavascript(code.arg(lat).arg(lng)); - - QString pointStr = ret.toString(); - int pos = pointStr.indexOf('(') + 1; - QString xStr = pointStr.mid(pos, pointStr.lastIndexOf(',') - pos); - pos = pointStr.lastIndexOf(',') + 1; - QString yStr = pointStr.mid(pos, pointStr.lastIndexOf(')') - pos); - - bool ok; - return Coord(xStr.toDouble(&ok), yStr.toDouble(&ok), 0); -} - -std::pair LeafletMaps::getLatLngForPixelPosOnScreen(int x, int y) { - static const QString code = "map.containerPointToLatLng(L.point(%1, %2)).toString();"; - QVariant ret = executeJavascript(code.arg(x).arg(y)); - - QString latLngStr = ret.toString(); - int pos = latLngStr.indexOf('(') + 1; - QString latStr = latLngStr.mid(pos, latLngStr.lastIndexOf(',') - pos); - pos = latLngStr.lastIndexOf(',') + 1; - QString lngStr = latLngStr.mid(pos, latLngStr.lastIndexOf(')') - pos); - return make_pair(latStr.toDouble(), lngStr.toDouble()); -} - -int LeafletMaps::getCurrentMapZoom() { - static const QString code = "map.getZoom();"; - QVariant ret = executeJavascript(code); - return ret.toInt(); -} - -static int clamp(int i, int minVal, int maxVal) { - return min(max(i, minVal), maxVal); -} - -void LeafletMaps::setCurrentZoom(int zoom) { - static const QString code = "map.setZoom(%1);"; - executeJavascript(code.arg(clamp(zoom, 0, 20))); - emit currentZoomChanged(); -} - -pair LeafletMaps::getCurrentMapCenter() { - static const QString code = "map.getCenter().toString();"; - QVariant ret = executeJavascript(code); - - pair latLng; - - if (!ret.isNull()) { - QString pointStr = ret.toString(); - int pos = pointStr.indexOf('(') + 1; - QString xStr = pointStr.mid(pos, pointStr.lastIndexOf(',') - pos); - pos = pointStr.lastIndexOf(',') + 1; - QString yStr = pointStr.mid(pos, pointStr.lastIndexOf(')') - pos); - latLng = make_pair(xStr.toDouble(), yStr.toDouble()); - } - - return latLng; -} - -void LeafletMaps::setMapBounds(Graph *graph, - const tlp_hash_map> &nodesLatLngs) { - - if (!nodesLatLngs.empty()) { - - auto minLatLng = make_pair(90.0, 180.0); - auto maxLatLng = make_pair(-90.0, -180.0); - - for (const auto &it : nodesLatLngs) { - if (graph->isElement(it.first)) { - minLatLng.first = std::min(minLatLng.first, it.second.first); - minLatLng.second = std::min(minLatLng.second, it.second.second); - maxLatLng.first = std::max(maxLatLng.first, it.second.first); - maxLatLng.second = std::max(maxLatLng.second, it.second.second); - } - } - - zoomOnBounds(minLatLng, maxLatLng); - } -} - -void LeafletMaps::zoomOnBounds(const std::pair &minLatLng, - const std::pair &maxLatLng) { - auto code = QString("setMapBounds([L.latLng(%1, %2), L.latLng(%3, %4)])") - .arg(minLatLng.first) - .arg(minLatLng.second) - .arg(maxLatLng.first) - .arg(maxLatLng.second); - executeJavascript(code); -} -} diff --git a/plugins/view/GeographicView/LeafletMaps.h b/plugins/view/GeographicView/LeafletMaps.h deleted file mode 100644 index 48c0fe0977..0000000000 --- a/plugins/view/GeographicView/LeafletMaps.h +++ /dev/null @@ -1,182 +0,0 @@ -/** - * - * Copyright (C) 2019-2023 The Talipot developers - * - * Talipot is a fork of Tulip, created by David Auber - * and the Tulip development Team from LaBRI, University of Bordeaux - * - * See the AUTHORS file at the top-level directory of this distribution - * License: GNU General Public License version 3, or any later version - * See top-level LICENSE file for more information - * - */ - -#ifndef LEAFLET_MAPS_H -#define LEAFLET_MAPS_H - -#ifdef QT_HAS_WEBKIT -#include -#include -#else -#include -#include -#endif -#include -#include - -#include "ProgressWidgetGraphicsProxy.h" -#include "AddressSelectionDialog.h" - -namespace tlp { - -#ifdef QT_HAS_WEBENGINE - -class MapRefresher : public QObject { - - Q_OBJECT - -public slots: - - void refreshMap(); - -signals: - - void refreshMapSignal(); -}; - -class OpenStreetMapSetAcceptLanguageHeader; - -#endif - -#ifdef QT_HAS_WEBKIT -class LeafletMaps : public QWebView { -#else -class LeafletMaps : public QWebEngineView { -#endif - - Q_OBJECT - -public: - LeafletMaps(QWidget *parent = nullptr); - - ~LeafletMaps(); - - bool pageLoaded(); - - bool mapLoaded(); - - void setMapCenter(double latitude, double longitude); - - int getCurrentMapZoom(); - - void setCurrentZoom(int zoom); - - std::pair getCurrentMapCenter(); - - Coord getPixelPosOnScreenForLatLng(double lat, double lng); - - std::pair getLatLngForPixelPosOnScreen(int x, int y); - - bool pageInit() const { - return init; - } - - void setMapBounds(Graph *graph, - const tlp_hash_map> &nodesLatLngs); - - void zoomOnBounds(const std::pair &minLatLng, - const std::pair &maxLatLng); - - void switchToTileLayer(const QString &layerName); - - const QString &getCurrentLayerName() { - return currentLayerName; - } - - void switchToCustomTileLayer(const QString &customTileLayerUrl); - - void setProgressWidget(ProgressWidgetGraphicsProxy *progressWidget) { - this->progressWidget = progressWidget; - } - - void setAdresseSelectionDialog(AddressSelectionDialog *addressSelectionDialog, - QGraphicsProxyWidget *addresseSelectionProxy) { - this->addressSelectionDialog = addressSelectionDialog; - this->addressSelectionProxy = addresseSelectionProxy; - } - -signals: - - void currentZoomChanged(); - void refreshMap(); - -private slots: - - void triggerLoading(); - -private: - QVariant executeJavascript(const QString &jsCode); - - bool init; -#ifdef QT_HAS_WEBKIT - QWebFrame *frame; -#else - QWebEnginePage *frame; -#endif - int x, y; - - AddressSelectionDialog *addressSelectionDialog; - QGraphicsProxyWidget *addressSelectionProxy; - ProgressWidgetGraphicsProxy *progressWidget; - -#ifdef QT_HAS_WEBENGINE - MapRefresher *mapRefresher; - OpenStreetMapSetAcceptLanguageHeader *osmSetAccessLanguageHeader; -#endif - - QString currentLayerName; -}; - -#ifdef QT_HAS_WEBENGINE - -// Use this class as a hack to simulate synchronous javascript callbacks -// as QtWebEngine is asynchronous while QtWebKit was not -class JsCallback : public QObject { - - Q_OBJECT - -public: - JsCallback(QVariant *ret) : _ret(ret) { - _lastCreatedInstance = this; - } - - JsCallback(const JsCallback &jscb) : QObject() { - _ret = jscb._ret; - _lastCreatedInstance = this; - } - - void operator()(const QVariant &result) { - *_ret = result; - emit called(); - } - - static void waitForCallback() { - QEventLoop loop; - connect(_lastCreatedInstance, &JsCallback::called, &loop, &QEventLoop::quit); - loop.exec(); - } - -signals: - - void called(); - -private: - QVariant *_ret; - - static JsCallback *_lastCreatedInstance; -}; - -#endif -} - -#endif // LEAFLET_MAPS_H diff --git a/plugins/view/GeographicView/leaflet/LICENSE b/plugins/view/GeographicView/leaflet/LICENSE deleted file mode 100644 index 4cd1f5b588..0000000000 --- a/plugins/view/GeographicView/leaflet/LICENSE +++ /dev/null @@ -1,26 +0,0 @@ -BSD 2-Clause License - -Copyright (c) 2010-2022, Volodymyr Agafonkin -Copyright (c) 2010-2011, CloudMade -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/plugins/view/GeographicView/leaflet/leaflet-src.js b/plugins/view/GeographicView/leaflet/leaflet-src.js deleted file mode 100644 index 90f6db3041..0000000000 --- a/plugins/view/GeographicView/leaflet/leaflet-src.js +++ /dev/null @@ -1,14512 +0,0 @@ -/* @preserve - * Leaflet 1.9.4, a JS library for interactive maps. https://leafletjs.com - * (c) 2010-2023 Vladimir Agafonkin, (c) 2010-2011 CloudMade - */ - -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define(['exports'], factory) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.leaflet = {})); -})(this, (function (exports) { 'use strict'; - - var version = "1.9.4"; - - /* - * @namespace Util - * - * Various utility functions, used by Leaflet internally. - */ - - // @function extend(dest: Object, src?: Object): Object - // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut. - function extend(dest) { - var i, j, len, src; - - for (j = 1, len = arguments.length; j < len; j++) { - src = arguments[j]; - for (i in src) { - dest[i] = src[i]; - } - } - return dest; - } - - // @function create(proto: Object, properties?: Object): Object - // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create) - var create$2 = Object.create || (function () { - function F() {} - return function (proto) { - F.prototype = proto; - return new F(); - }; - })(); - - // @function bind(fn: Function, …): Function - // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind). - // Has a `L.bind()` shortcut. - function bind(fn, obj) { - var slice = Array.prototype.slice; - - if (fn.bind) { - return fn.bind.apply(fn, slice.call(arguments, 1)); - } - - var args = slice.call(arguments, 2); - - return function () { - return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments); - }; - } - - // @property lastId: Number - // Last unique ID used by [`stamp()`](#util-stamp) - var lastId = 0; - - // @function stamp(obj: Object): Number - // Returns the unique ID of an object, assigning it one if it doesn't have it. - function stamp(obj) { - if (!('_leaflet_id' in obj)) { - obj['_leaflet_id'] = ++lastId; - } - return obj._leaflet_id; - } - - // @function throttle(fn: Function, time: Number, context: Object): Function - // Returns a function which executes function `fn` with the given scope `context` - // (so that the `this` keyword refers to `context` inside `fn`'s code). The function - // `fn` will be called no more than one time per given amount of `time`. The arguments - // received by the bound function will be any arguments passed when binding the - // function, followed by any arguments passed when invoking the bound function. - // Has an `L.throttle` shortcut. - function throttle(fn, time, context) { - var lock, args, wrapperFn, later; - - later = function () { - // reset lock and call if queued - lock = false; - if (args) { - wrapperFn.apply(context, args); - args = false; - } - }; - - wrapperFn = function () { - if (lock) { - // called too soon, queue to call later - args = arguments; - - } else { - // call and lock until later - fn.apply(context, arguments); - setTimeout(later, time); - lock = true; - } - }; - - return wrapperFn; - } - - // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number - // Returns the number `num` modulo `range` in such a way so it lies within - // `range[0]` and `range[1]`. The returned value will be always smaller than - // `range[1]` unless `includeMax` is set to `true`. - function wrapNum(x, range, includeMax) { - var max = range[1], - min = range[0], - d = max - min; - return x === max && includeMax ? x : ((x - min) % d + d) % d + min; - } - - // @function falseFn(): Function - // Returns a function which always returns `false`. - function falseFn() { return false; } - - // @function formatNum(num: Number, precision?: Number|false): Number - // Returns the number `num` rounded with specified `precision`. - // The default `precision` value is 6 decimal places. - // `false` can be passed to skip any processing (can be useful to avoid round-off errors). - function formatNum(num, precision) { - if (precision === false) { return num; } - var pow = Math.pow(10, precision === undefined ? 6 : precision); - return Math.round(num * pow) / pow; - } - - // @function trim(str: String): String - // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim) - function trim(str) { - return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ''); - } - - // @function splitWords(str: String): String[] - // Trims and splits the string on whitespace and returns the array of parts. - function splitWords(str) { - return trim(str).split(/\s+/); - } - - // @function setOptions(obj: Object, options: Object): Object - // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut. - function setOptions(obj, options) { - if (!Object.prototype.hasOwnProperty.call(obj, 'options')) { - obj.options = obj.options ? create$2(obj.options) : {}; - } - for (var i in options) { - obj.options[i] = options[i]; - } - return obj.options; - } - - // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String - // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}` - // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will - // be appended at the end. If `uppercase` is `true`, the parameter names will - // be uppercased (e.g. `'?A=foo&B=bar'`) - function getParamString(obj, existingUrl, uppercase) { - var params = []; - for (var i in obj) { - params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i])); - } - return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&'); - } - - var templateRe = /\{ *([\w_ -]+) *\}/g; - - // @function template(str: String, data: Object): String - // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'` - // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string - // `('Hello foo, bar')`. You can also specify functions instead of strings for - // data values — they will be evaluated passing `data` as an argument. - function template(str, data) { - return str.replace(templateRe, function (str, key) { - var value = data[key]; - - if (value === undefined) { - throw new Error('No value provided for variable ' + str); - - } else if (typeof value === 'function') { - value = value(data); - } - return value; - }); - } - - // @function isArray(obj): Boolean - // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray) - var isArray = Array.isArray || function (obj) { - return (Object.prototype.toString.call(obj) === '[object Array]'); - }; - - // @function indexOf(array: Array, el: Object): Number - // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf) - function indexOf(array, el) { - for (var i = 0; i < array.length; i++) { - if (array[i] === el) { return i; } - } - return -1; - } - - // @property emptyImageUrl: String - // Data URI string containing a base64-encoded empty GIF image. - // Used as a hack to free memory from unused images on WebKit-powered - // mobile devices (by setting image `src` to this string). - var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; - - // inspired by https://paulirish.com/2011/requestanimationframe-for-smart-animating/ - - function getPrefixed(name) { - return window['webkit' + name] || window['moz' + name] || window['ms' + name]; - } - - var lastTime = 0; - - // fallback for IE 7-8 - function timeoutDefer(fn) { - var time = +new Date(), - timeToCall = Math.max(0, 16 - (time - lastTime)); - - lastTime = time + timeToCall; - return window.setTimeout(fn, timeToCall); - } - - var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer; - var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') || - getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); }; - - // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number - // Schedules `fn` to be executed when the browser repaints. `fn` is bound to - // `context` if given. When `immediate` is set, `fn` is called immediately if - // the browser doesn't have native support for - // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame), - // otherwise it's delayed. Returns a request ID that can be used to cancel the request. - function requestAnimFrame(fn, context, immediate) { - if (immediate && requestFn === timeoutDefer) { - fn.call(context); - } else { - return requestFn.call(window, bind(fn, context)); - } - } - - // @function cancelAnimFrame(id: Number): undefined - // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame). - function cancelAnimFrame(id) { - if (id) { - cancelFn.call(window, id); - } - } - - var Util = { - __proto__: null, - extend: extend, - create: create$2, - bind: bind, - get lastId () { return lastId; }, - stamp: stamp, - throttle: throttle, - wrapNum: wrapNum, - falseFn: falseFn, - formatNum: formatNum, - trim: trim, - splitWords: splitWords, - setOptions: setOptions, - getParamString: getParamString, - template: template, - isArray: isArray, - indexOf: indexOf, - emptyImageUrl: emptyImageUrl, - requestFn: requestFn, - cancelFn: cancelFn, - requestAnimFrame: requestAnimFrame, - cancelAnimFrame: cancelAnimFrame - }; - - // @class Class - // @aka L.Class - - // @section - // @uninheritable - - // Thanks to John Resig and Dean Edwards for inspiration! - - function Class() {} - - Class.extend = function (props) { - - // @function extend(props: Object): Function - // [Extends the current class](#class-inheritance) given the properties to be included. - // Returns a Javascript function that is a class constructor (to be called with `new`). - var NewClass = function () { - - setOptions(this); - - // call the constructor - if (this.initialize) { - this.initialize.apply(this, arguments); - } - - // call all constructor hooks - this.callInitHooks(); - }; - - var parentProto = NewClass.__super__ = this.prototype; - - var proto = create$2(parentProto); - proto.constructor = NewClass; - - NewClass.prototype = proto; - - // inherit parent's statics - for (var i in this) { - if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') { - NewClass[i] = this[i]; - } - } - - // mix static properties into the class - if (props.statics) { - extend(NewClass, props.statics); - } - - // mix includes into the prototype - if (props.includes) { - checkDeprecatedMixinEvents(props.includes); - extend.apply(null, [proto].concat(props.includes)); - } - - // mix given properties into the prototype - extend(proto, props); - delete proto.statics; - delete proto.includes; - - // merge options - if (proto.options) { - proto.options = parentProto.options ? create$2(parentProto.options) : {}; - extend(proto.options, props.options); - } - - proto._initHooks = []; - - // add method for calling all hooks - proto.callInitHooks = function () { - - if (this._initHooksCalled) { return; } - - if (parentProto.callInitHooks) { - parentProto.callInitHooks.call(this); - } - - this._initHooksCalled = true; - - for (var i = 0, len = proto._initHooks.length; i < len; i++) { - proto._initHooks[i].call(this); - } - }; - - return NewClass; - }; - - - // @function include(properties: Object): this - // [Includes a mixin](#class-includes) into the current class. - Class.include = function (props) { - var parentOptions = this.prototype.options; - extend(this.prototype, props); - if (props.options) { - this.prototype.options = parentOptions; - this.mergeOptions(props.options); - } - return this; - }; - - // @function mergeOptions(options: Object): this - // [Merges `options`](#class-options) into the defaults of the class. - Class.mergeOptions = function (options) { - extend(this.prototype.options, options); - return this; - }; - - // @function addInitHook(fn: Function): this - // Adds a [constructor hook](#class-constructor-hooks) to the class. - Class.addInitHook = function (fn) { // (Function) || (String, args...) - var args = Array.prototype.slice.call(arguments, 1); - - var init = typeof fn === 'function' ? fn : function () { - this[fn].apply(this, args); - }; - - this.prototype._initHooks = this.prototype._initHooks || []; - this.prototype._initHooks.push(init); - return this; - }; - - function checkDeprecatedMixinEvents(includes) { - /* global L: true */ - if (typeof L === 'undefined' || !L || !L.Mixin) { return; } - - includes = isArray(includes) ? includes : [includes]; - - for (var i = 0; i < includes.length; i++) { - if (includes[i] === L.Mixin.Events) { - console.warn('Deprecated include of L.Mixin.Events: ' + - 'this property will be removed in future releases, ' + - 'please inherit from L.Evented instead.', new Error().stack); - } - } - } - - /* - * @class Evented - * @aka L.Evented - * @inherits Class - * - * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event). - * - * @example - * - * ```js - * map.on('click', function(e) { - * alert(e.latlng); - * } ); - * ``` - * - * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function: - * - * ```js - * function onClick(e) { ... } - * - * map.on('click', onClick); - * map.off('click', onClick); - * ``` - */ - - var Events = { - /* @method on(type: String, fn: Function, context?: Object): this - * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`). - * - * @alternative - * @method on(eventMap: Object): this - * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` - */ - on: function (types, fn, context) { - - // types can be a map of types/handlers - if (typeof types === 'object') { - for (var type in types) { - // we don't process space-separated events here for performance; - // it's a hot path since Layer uses the on(obj) syntax - this._on(type, types[type], fn); - } - - } else { - // types can be a string of space-separated words - types = splitWords(types); - - for (var i = 0, len = types.length; i < len; i++) { - this._on(types[i], fn, context); - } - } - - return this; - }, - - /* @method off(type: String, fn?: Function, context?: Object): this - * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener. - * - * @alternative - * @method off(eventMap: Object): this - * Removes a set of type/listener pairs. - * - * @alternative - * @method off: this - * Removes all listeners to all events on the object. This includes implicitly attached events. - */ - off: function (types, fn, context) { - - if (!arguments.length) { - // clear all listeners if called without arguments - delete this._events; - - } else if (typeof types === 'object') { - for (var type in types) { - this._off(type, types[type], fn); - } - - } else { - types = splitWords(types); - - var removeAll = arguments.length === 1; - for (var i = 0, len = types.length; i < len; i++) { - if (removeAll) { - this._off(types[i]); - } else { - this._off(types[i], fn, context); - } - } - } - - return this; - }, - - // attach listener (without syntactic sugar now) - _on: function (type, fn, context, _once) { - if (typeof fn !== 'function') { - console.warn('wrong listener type: ' + typeof fn); - return; - } - - // check if fn already there - if (this._listens(type, fn, context) !== false) { - return; - } - - if (context === this) { - // Less memory footprint. - context = undefined; - } - - var newListener = {fn: fn, ctx: context}; - if (_once) { - newListener.once = true; - } - - this._events = this._events || {}; - this._events[type] = this._events[type] || []; - this._events[type].push(newListener); - }, - - _off: function (type, fn, context) { - var listeners, - i, - len; - - if (!this._events) { - return; - } - - listeners = this._events[type]; - if (!listeners) { - return; - } - - if (arguments.length === 1) { // remove all - if (this._firingCount) { - // Set all removed listeners to noop - // so they are not called if remove happens in fire - for (i = 0, len = listeners.length; i < len; i++) { - listeners[i].fn = falseFn; - } - } - // clear all listeners for a type if function isn't specified - delete this._events[type]; - return; - } - - if (typeof fn !== 'function') { - console.warn('wrong listener type: ' + typeof fn); - return; - } - - // find fn and remove it - var index = this._listens(type, fn, context); - if (index !== false) { - var listener = listeners[index]; - if (this._firingCount) { - // set the removed listener to noop so that's not called if remove happens in fire - listener.fn = falseFn; - - /* copy array in case events are being fired */ - this._events[type] = listeners = listeners.slice(); - } - listeners.splice(index, 1); - } - }, - - // @method fire(type: String, data?: Object, propagate?: Boolean): this - // Fires an event of the specified type. You can optionally provide a data - // object — the first argument of the listener function will contain its - // properties. The event can optionally be propagated to event parents. - fire: function (type, data, propagate) { - if (!this.listens(type, propagate)) { return this; } - - var event = extend({}, data, { - type: type, - target: this, - sourceTarget: data && data.sourceTarget || this - }); - - if (this._events) { - var listeners = this._events[type]; - if (listeners) { - this._firingCount = (this._firingCount + 1) || 1; - for (var i = 0, len = listeners.length; i < len; i++) { - var l = listeners[i]; - // off overwrites l.fn, so we need to copy fn to a var - var fn = l.fn; - if (l.once) { - this.off(type, fn, l.ctx); - } - fn.call(l.ctx || this, event); - } - - this._firingCount--; - } - } - - if (propagate) { - // propagate the event to parents (set with addEventParent) - this._propagateEvent(event); - } - - return this; - }, - - // @method listens(type: String, propagate?: Boolean): Boolean - // @method listens(type: String, fn: Function, context?: Object, propagate?: Boolean): Boolean - // Returns `true` if a particular event type has any listeners attached to it. - // The verification can optionally be propagated, it will return `true` if parents have the listener attached to it. - listens: function (type, fn, context, propagate) { - if (typeof type !== 'string') { - console.warn('"string" type argument expected'); - } - - // we don't overwrite the input `fn` value, because we need to use it for propagation - var _fn = fn; - if (typeof fn !== 'function') { - propagate = !!fn; - _fn = undefined; - context = undefined; - } - - var listeners = this._events && this._events[type]; - if (listeners && listeners.length) { - if (this._listens(type, _fn, context) !== false) { - return true; - } - } - - if (propagate) { - // also check parents for listeners if event propagates - for (var id in this._eventParents) { - if (this._eventParents[id].listens(type, fn, context, propagate)) { return true; } - } - } - return false; - }, - - // returns the index (number) or false - _listens: function (type, fn, context) { - if (!this._events) { - return false; - } - - var listeners = this._events[type] || []; - if (!fn) { - return !!listeners.length; - } - - if (context === this) { - // Less memory footprint. - context = undefined; - } - - for (var i = 0, len = listeners.length; i < len; i++) { - if (listeners[i].fn === fn && listeners[i].ctx === context) { - return i; - } - } - return false; - - }, - - // @method once(…): this - // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed. - once: function (types, fn, context) { - - // types can be a map of types/handlers - if (typeof types === 'object') { - for (var type in types) { - // we don't process space-separated events here for performance; - // it's a hot path since Layer uses the on(obj) syntax - this._on(type, types[type], fn, true); - } - - } else { - // types can be a string of space-separated words - types = splitWords(types); - - for (var i = 0, len = types.length; i < len; i++) { - this._on(types[i], fn, context, true); - } - } - - return this; - }, - - // @method addEventParent(obj: Evented): this - // Adds an event parent - an `Evented` that will receive propagated events - addEventParent: function (obj) { - this._eventParents = this._eventParents || {}; - this._eventParents[stamp(obj)] = obj; - return this; - }, - - // @method removeEventParent(obj: Evented): this - // Removes an event parent, so it will stop receiving propagated events - removeEventParent: function (obj) { - if (this._eventParents) { - delete this._eventParents[stamp(obj)]; - } - return this; - }, - - _propagateEvent: function (e) { - for (var id in this._eventParents) { - this._eventParents[id].fire(e.type, extend({ - layer: e.target, - propagatedFrom: e.target - }, e), true); - } - } - }; - - // aliases; we should ditch those eventually - - // @method addEventListener(…): this - // Alias to [`on(…)`](#evented-on) - Events.addEventListener = Events.on; - - // @method removeEventListener(…): this - // Alias to [`off(…)`](#evented-off) - - // @method clearAllEventListeners(…): this - // Alias to [`off()`](#evented-off) - Events.removeEventListener = Events.clearAllEventListeners = Events.off; - - // @method addOneTimeEventListener(…): this - // Alias to [`once(…)`](#evented-once) - Events.addOneTimeEventListener = Events.once; - - // @method fireEvent(…): this - // Alias to [`fire(…)`](#evented-fire) - Events.fireEvent = Events.fire; - - // @method hasEventListeners(…): Boolean - // Alias to [`listens(…)`](#evented-listens) - Events.hasEventListeners = Events.listens; - - var Evented = Class.extend(Events); - - /* - * @class Point - * @aka L.Point - * - * Represents a point with `x` and `y` coordinates in pixels. - * - * @example - * - * ```js - * var point = L.point(200, 300); - * ``` - * - * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent: - * - * ```js - * map.panBy([200, 300]); - * map.panBy(L.point(200, 300)); - * ``` - * - * Note that `Point` does not inherit from Leaflet's `Class` object, - * which means new classes can't inherit from it, and new methods - * can't be added to it with the `include` function. - */ - - function Point(x, y, round) { - // @property x: Number; The `x` coordinate of the point - this.x = (round ? Math.round(x) : x); - // @property y: Number; The `y` coordinate of the point - this.y = (round ? Math.round(y) : y); - } - - var trunc = Math.trunc || function (v) { - return v > 0 ? Math.floor(v) : Math.ceil(v); - }; - - Point.prototype = { - - // @method clone(): Point - // Returns a copy of the current point. - clone: function () { - return new Point(this.x, this.y); - }, - - // @method add(otherPoint: Point): Point - // Returns the result of addition of the current and the given points. - add: function (point) { - // non-destructive, returns a new point - return this.clone()._add(toPoint(point)); - }, - - _add: function (point) { - // destructive, used directly for performance in situations where it's safe to modify existing point - this.x += point.x; - this.y += point.y; - return this; - }, - - // @method subtract(otherPoint: Point): Point - // Returns the result of subtraction of the given point from the current. - subtract: function (point) { - return this.clone()._subtract(toPoint(point)); - }, - - _subtract: function (point) { - this.x -= point.x; - this.y -= point.y; - return this; - }, - - // @method divideBy(num: Number): Point - // Returns the result of division of the current point by the given number. - divideBy: function (num) { - return this.clone()._divideBy(num); - }, - - _divideBy: function (num) { - this.x /= num; - this.y /= num; - return this; - }, - - // @method multiplyBy(num: Number): Point - // Returns the result of multiplication of the current point by the given number. - multiplyBy: function (num) { - return this.clone()._multiplyBy(num); - }, - - _multiplyBy: function (num) { - this.x *= num; - this.y *= num; - return this; - }, - - // @method scaleBy(scale: Point): Point - // Multiply each coordinate of the current point by each coordinate of - // `scale`. In linear algebra terms, multiply the point by the - // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation) - // defined by `scale`. - scaleBy: function (point) { - return new Point(this.x * point.x, this.y * point.y); - }, - - // @method unscaleBy(scale: Point): Point - // Inverse of `scaleBy`. Divide each coordinate of the current point by - // each coordinate of `scale`. - unscaleBy: function (point) { - return new Point(this.x / point.x, this.y / point.y); - }, - - // @method round(): Point - // Returns a copy of the current point with rounded coordinates. - round: function () { - return this.clone()._round(); - }, - - _round: function () { - this.x = Math.round(this.x); - this.y = Math.round(this.y); - return this; - }, - - // @method floor(): Point - // Returns a copy of the current point with floored coordinates (rounded down). - floor: function () { - return this.clone()._floor(); - }, - - _floor: function () { - this.x = Math.floor(this.x); - this.y = Math.floor(this.y); - return this; - }, - - // @method ceil(): Point - // Returns a copy of the current point with ceiled coordinates (rounded up). - ceil: function () { - return this.clone()._ceil(); - }, - - _ceil: function () { - this.x = Math.ceil(this.x); - this.y = Math.ceil(this.y); - return this; - }, - - // @method trunc(): Point - // Returns a copy of the current point with truncated coordinates (rounded towards zero). - trunc: function () { - return this.clone()._trunc(); - }, - - _trunc: function () { - this.x = trunc(this.x); - this.y = trunc(this.y); - return this; - }, - - // @method distanceTo(otherPoint: Point): Number - // Returns the cartesian distance between the current and the given points. - distanceTo: function (point) { - point = toPoint(point); - - var x = point.x - this.x, - y = point.y - this.y; - - return Math.sqrt(x * x + y * y); - }, - - // @method equals(otherPoint: Point): Boolean - // Returns `true` if the given point has the same coordinates. - equals: function (point) { - point = toPoint(point); - - return point.x === this.x && - point.y === this.y; - }, - - // @method contains(otherPoint: Point): Boolean - // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values). - contains: function (point) { - point = toPoint(point); - - return Math.abs(point.x) <= Math.abs(this.x) && - Math.abs(point.y) <= Math.abs(this.y); - }, - - // @method toString(): String - // Returns a string representation of the point for debugging purposes. - toString: function () { - return 'Point(' + - formatNum(this.x) + ', ' + - formatNum(this.y) + ')'; - } - }; - - // @factory L.point(x: Number, y: Number, round?: Boolean) - // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values. - - // @alternative - // @factory L.point(coords: Number[]) - // Expects an array of the form `[x, y]` instead. - - // @alternative - // @factory L.point(coords: Object) - // Expects a plain object of the form `{x: Number, y: Number}` instead. - function toPoint(x, y, round) { - if (x instanceof Point) { - return x; - } - if (isArray(x)) { - return new Point(x[0], x[1]); - } - if (x === undefined || x === null) { - return x; - } - if (typeof x === 'object' && 'x' in x && 'y' in x) { - return new Point(x.x, x.y); - } - return new Point(x, y, round); - } - - /* - * @class Bounds - * @aka L.Bounds - * - * Represents a rectangular area in pixel coordinates. - * - * @example - * - * ```js - * var p1 = L.point(10, 10), - * p2 = L.point(40, 60), - * bounds = L.bounds(p1, p2); - * ``` - * - * All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this: - * - * ```js - * otherBounds.intersects([[10, 10], [40, 60]]); - * ``` - * - * Note that `Bounds` does not inherit from Leaflet's `Class` object, - * which means new classes can't inherit from it, and new methods - * can't be added to it with the `include` function. - */ - - function Bounds(a, b) { - if (!a) { return; } - - var points = b ? [a, b] : a; - - for (var i = 0, len = points.length; i < len; i++) { - this.extend(points[i]); - } - } - - Bounds.prototype = { - // @method extend(point: Point): this - // Extends the bounds to contain the given point. - - // @alternative - // @method extend(otherBounds: Bounds): this - // Extend the bounds to contain the given bounds - extend: function (obj) { - var min2, max2; - if (!obj) { return this; } - - if (obj instanceof Point || typeof obj[0] === 'number' || 'x' in obj) { - min2 = max2 = toPoint(obj); - } else { - obj = toBounds(obj); - min2 = obj.min; - max2 = obj.max; - - if (!min2 || !max2) { return this; } - } - - // @property min: Point - // The top left corner of the rectangle. - // @property max: Point - // The bottom right corner of the rectangle. - if (!this.min && !this.max) { - this.min = min2.clone(); - this.max = max2.clone(); - } else { - this.min.x = Math.min(min2.x, this.min.x); - this.max.x = Math.max(max2.x, this.max.x); - this.min.y = Math.min(min2.y, this.min.y); - this.max.y = Math.max(max2.y, this.max.y); - } - return this; - }, - - // @method getCenter(round?: Boolean): Point - // Returns the center point of the bounds. - getCenter: function (round) { - return toPoint( - (this.min.x + this.max.x) / 2, - (this.min.y + this.max.y) / 2, round); - }, - - // @method getBottomLeft(): Point - // Returns the bottom-left point of the bounds. - getBottomLeft: function () { - return toPoint(this.min.x, this.max.y); - }, - - // @method getTopRight(): Point - // Returns the top-right point of the bounds. - getTopRight: function () { // -> Point - return toPoint(this.max.x, this.min.y); - }, - - // @method getTopLeft(): Point - // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)). - getTopLeft: function () { - return this.min; // left, top - }, - - // @method getBottomRight(): Point - // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)). - getBottomRight: function () { - return this.max; // right, bottom - }, - - // @method getSize(): Point - // Returns the size of the given bounds - getSize: function () { - return this.max.subtract(this.min); - }, - - // @method contains(otherBounds: Bounds): Boolean - // Returns `true` if the rectangle contains the given one. - // @alternative - // @method contains(point: Point): Boolean - // Returns `true` if the rectangle contains the given point. - contains: function (obj) { - var min, max; - - if (typeof obj[0] === 'number' || obj instanceof Point) { - obj = toPoint(obj); - } else { - obj = toBounds(obj); - } - - if (obj instanceof Bounds) { - min = obj.min; - max = obj.max; - } else { - min = max = obj; - } - - return (min.x >= this.min.x) && - (max.x <= this.max.x) && - (min.y >= this.min.y) && - (max.y <= this.max.y); - }, - - // @method intersects(otherBounds: Bounds): Boolean - // Returns `true` if the rectangle intersects the given bounds. Two bounds - // intersect if they have at least one point in common. - intersects: function (bounds) { // (Bounds) -> Boolean - bounds = toBounds(bounds); - - var min = this.min, - max = this.max, - min2 = bounds.min, - max2 = bounds.max, - xIntersects = (max2.x >= min.x) && (min2.x <= max.x), - yIntersects = (max2.y >= min.y) && (min2.y <= max.y); - - return xIntersects && yIntersects; - }, - - // @method overlaps(otherBounds: Bounds): Boolean - // Returns `true` if the rectangle overlaps the given bounds. Two bounds - // overlap if their intersection is an area. - overlaps: function (bounds) { // (Bounds) -> Boolean - bounds = toBounds(bounds); - - var min = this.min, - max = this.max, - min2 = bounds.min, - max2 = bounds.max, - xOverlaps = (max2.x > min.x) && (min2.x < max.x), - yOverlaps = (max2.y > min.y) && (min2.y < max.y); - - return xOverlaps && yOverlaps; - }, - - // @method isValid(): Boolean - // Returns `true` if the bounds are properly initialized. - isValid: function () { - return !!(this.min && this.max); - }, - - - // @method pad(bufferRatio: Number): Bounds - // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction. - // For example, a ratio of 0.5 extends the bounds by 50% in each direction. - // Negative values will retract the bounds. - pad: function (bufferRatio) { - var min = this.min, - max = this.max, - heightBuffer = Math.abs(min.x - max.x) * bufferRatio, - widthBuffer = Math.abs(min.y - max.y) * bufferRatio; - - - return toBounds( - toPoint(min.x - heightBuffer, min.y - widthBuffer), - toPoint(max.x + heightBuffer, max.y + widthBuffer)); - }, - - - // @method equals(otherBounds: Bounds): Boolean - // Returns `true` if the rectangle is equivalent to the given bounds. - equals: function (bounds) { - if (!bounds) { return false; } - - bounds = toBounds(bounds); - - return this.min.equals(bounds.getTopLeft()) && - this.max.equals(bounds.getBottomRight()); - }, - }; - - - // @factory L.bounds(corner1: Point, corner2: Point) - // Creates a Bounds object from two corners coordinate pairs. - // @alternative - // @factory L.bounds(points: Point[]) - // Creates a Bounds object from the given array of points. - function toBounds(a, b) { - if (!a || a instanceof Bounds) { - return a; - } - return new Bounds(a, b); - } - - /* - * @class LatLngBounds - * @aka L.LatLngBounds - * - * Represents a rectangular geographical area on a map. - * - * @example - * - * ```js - * var corner1 = L.latLng(40.712, -74.227), - * corner2 = L.latLng(40.774, -74.125), - * bounds = L.latLngBounds(corner1, corner2); - * ``` - * - * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this: - * - * ```js - * map.fitBounds([ - * [40.712, -74.227], - * [40.774, -74.125] - * ]); - * ``` - * - * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range. - * - * Note that `LatLngBounds` does not inherit from Leaflet's `Class` object, - * which means new classes can't inherit from it, and new methods - * can't be added to it with the `include` function. - */ - - function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[]) - if (!corner1) { return; } - - var latlngs = corner2 ? [corner1, corner2] : corner1; - - for (var i = 0, len = latlngs.length; i < len; i++) { - this.extend(latlngs[i]); - } - } - - LatLngBounds.prototype = { - - // @method extend(latlng: LatLng): this - // Extend the bounds to contain the given point - - // @alternative - // @method extend(otherBounds: LatLngBounds): this - // Extend the bounds to contain the given bounds - extend: function (obj) { - var sw = this._southWest, - ne = this._northEast, - sw2, ne2; - - if (obj instanceof LatLng) { - sw2 = obj; - ne2 = obj; - - } else if (obj instanceof LatLngBounds) { - sw2 = obj._southWest; - ne2 = obj._northEast; - - if (!sw2 || !ne2) { return this; } - - } else { - return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this; - } - - if (!sw && !ne) { - this._southWest = new LatLng(sw2.lat, sw2.lng); - this._northEast = new LatLng(ne2.lat, ne2.lng); - } else { - sw.lat = Math.min(sw2.lat, sw.lat); - sw.lng = Math.min(sw2.lng, sw.lng); - ne.lat = Math.max(ne2.lat, ne.lat); - ne.lng = Math.max(ne2.lng, ne.lng); - } - - return this; - }, - - // @method pad(bufferRatio: Number): LatLngBounds - // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction. - // For example, a ratio of 0.5 extends the bounds by 50% in each direction. - // Negative values will retract the bounds. - pad: function (bufferRatio) { - var sw = this._southWest, - ne = this._northEast, - heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, - widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; - - return new LatLngBounds( - new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), - new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer)); - }, - - // @method getCenter(): LatLng - // Returns the center point of the bounds. - getCenter: function () { - return new LatLng( - (this._southWest.lat + this._northEast.lat) / 2, - (this._southWest.lng + this._northEast.lng) / 2); - }, - - // @method getSouthWest(): LatLng - // Returns the south-west point of the bounds. - getSouthWest: function () { - return this._southWest; - }, - - // @method getNorthEast(): LatLng - // Returns the north-east point of the bounds. - getNorthEast: function () { - return this._northEast; - }, - - // @method getNorthWest(): LatLng - // Returns the north-west point of the bounds. - getNorthWest: function () { - return new LatLng(this.getNorth(), this.getWest()); - }, - - // @method getSouthEast(): LatLng - // Returns the south-east point of the bounds. - getSouthEast: function () { - return new LatLng(this.getSouth(), this.getEast()); - }, - - // @method getWest(): Number - // Returns the west longitude of the bounds - getWest: function () { - return this._southWest.lng; - }, - - // @method getSouth(): Number - // Returns the south latitude of the bounds - getSouth: function () { - return this._southWest.lat; - }, - - // @method getEast(): Number - // Returns the east longitude of the bounds - getEast: function () { - return this._northEast.lng; - }, - - // @method getNorth(): Number - // Returns the north latitude of the bounds - getNorth: function () { - return this._northEast.lat; - }, - - // @method contains(otherBounds: LatLngBounds): Boolean - // Returns `true` if the rectangle contains the given one. - - // @alternative - // @method contains (latlng: LatLng): Boolean - // Returns `true` if the rectangle contains the given point. - contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean - if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) { - obj = toLatLng(obj); - } else { - obj = toLatLngBounds(obj); - } - - var sw = this._southWest, - ne = this._northEast, - sw2, ne2; - - if (obj instanceof LatLngBounds) { - sw2 = obj.getSouthWest(); - ne2 = obj.getNorthEast(); - } else { - sw2 = ne2 = obj; - } - - return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) && - (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng); - }, - - // @method intersects(otherBounds: LatLngBounds): Boolean - // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common. - intersects: function (bounds) { - bounds = toLatLngBounds(bounds); - - var sw = this._southWest, - ne = this._northEast, - sw2 = bounds.getSouthWest(), - ne2 = bounds.getNorthEast(), - - latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat), - lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng); - - return latIntersects && lngIntersects; - }, - - // @method overlaps(otherBounds: LatLngBounds): Boolean - // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area. - overlaps: function (bounds) { - bounds = toLatLngBounds(bounds); - - var sw = this._southWest, - ne = this._northEast, - sw2 = bounds.getSouthWest(), - ne2 = bounds.getNorthEast(), - - latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat), - lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng); - - return latOverlaps && lngOverlaps; - }, - - // @method toBBoxString(): String - // Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data. - toBBoxString: function () { - return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(','); - }, - - // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean - // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number. - equals: function (bounds, maxMargin) { - if (!bounds) { return false; } - - bounds = toLatLngBounds(bounds); - - return this._southWest.equals(bounds.getSouthWest(), maxMargin) && - this._northEast.equals(bounds.getNorthEast(), maxMargin); - }, - - // @method isValid(): Boolean - // Returns `true` if the bounds are properly initialized. - isValid: function () { - return !!(this._southWest && this._northEast); - } - }; - - // TODO International date line? - - // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng) - // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle. - - // @alternative - // @factory L.latLngBounds(latlngs: LatLng[]) - // Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds). - function toLatLngBounds(a, b) { - if (a instanceof LatLngBounds) { - return a; - } - return new LatLngBounds(a, b); - } - - /* @class LatLng - * @aka L.LatLng - * - * Represents a geographical point with a certain latitude and longitude. - * - * @example - * - * ``` - * var latlng = L.latLng(50.5, 30.5); - * ``` - * - * All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent: - * - * ``` - * map.panTo([50, 30]); - * map.panTo({lon: 30, lat: 50}); - * map.panTo({lat: 50, lng: 30}); - * map.panTo(L.latLng(50, 30)); - * ``` - * - * Note that `LatLng` does not inherit from Leaflet's `Class` object, - * which means new classes can't inherit from it, and new methods - * can't be added to it with the `include` function. - */ - - function LatLng(lat, lng, alt) { - if (isNaN(lat) || isNaN(lng)) { - throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); - } - - // @property lat: Number - // Latitude in degrees - this.lat = +lat; - - // @property lng: Number - // Longitude in degrees - this.lng = +lng; - - // @property alt: Number - // Altitude in meters (optional) - if (alt !== undefined) { - this.alt = +alt; - } - } - - LatLng.prototype = { - // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean - // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number. - equals: function (obj, maxMargin) { - if (!obj) { return false; } - - obj = toLatLng(obj); - - var margin = Math.max( - Math.abs(this.lat - obj.lat), - Math.abs(this.lng - obj.lng)); - - return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin); - }, - - // @method toString(): String - // Returns a string representation of the point (for debugging purposes). - toString: function (precision) { - return 'LatLng(' + - formatNum(this.lat, precision) + ', ' + - formatNum(this.lng, precision) + ')'; - }, - - // @method distanceTo(otherLatLng: LatLng): Number - // Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines). - distanceTo: function (other) { - return Earth.distance(this, toLatLng(other)); - }, - - // @method wrap(): LatLng - // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees. - wrap: function () { - return Earth.wrapLatLng(this); - }, - - // @method toBounds(sizeInMeters: Number): LatLngBounds - // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`. - toBounds: function (sizeInMeters) { - var latAccuracy = 180 * sizeInMeters / 40075017, - lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat); - - return toLatLngBounds( - [this.lat - latAccuracy, this.lng - lngAccuracy], - [this.lat + latAccuracy, this.lng + lngAccuracy]); - }, - - clone: function () { - return new LatLng(this.lat, this.lng, this.alt); - } - }; - - - - // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng - // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude). - - // @alternative - // @factory L.latLng(coords: Array): LatLng - // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead. - - // @alternative - // @factory L.latLng(coords: Object): LatLng - // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead. - - function toLatLng(a, b, c) { - if (a instanceof LatLng) { - return a; - } - if (isArray(a) && typeof a[0] !== 'object') { - if (a.length === 3) { - return new LatLng(a[0], a[1], a[2]); - } - if (a.length === 2) { - return new LatLng(a[0], a[1]); - } - return null; - } - if (a === undefined || a === null) { - return a; - } - if (typeof a === 'object' && 'lat' in a) { - return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt); - } - if (b === undefined) { - return null; - } - return new LatLng(a, b, c); - } - - /* - * @namespace CRS - * @crs L.CRS.Base - * Object that defines coordinate reference systems for projecting - * geographical points into pixel (screen) coordinates and back (and to - * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See - * [spatial reference system](https://en.wikipedia.org/wiki/Spatial_reference_system). - * - * Leaflet defines the most usual CRSs by default. If you want to use a - * CRS not defined by default, take a look at the - * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin. - * - * Note that the CRS instances do not inherit from Leaflet's `Class` object, - * and can't be instantiated. Also, new classes can't inherit from them, - * and methods can't be added to them with the `include` function. - */ - - var CRS = { - // @method latLngToPoint(latlng: LatLng, zoom: Number): Point - // Projects geographical coordinates into pixel coordinates for a given zoom. - latLngToPoint: function (latlng, zoom) { - var projectedPoint = this.projection.project(latlng), - scale = this.scale(zoom); - - return this.transformation._transform(projectedPoint, scale); - }, - - // @method pointToLatLng(point: Point, zoom: Number): LatLng - // The inverse of `latLngToPoint`. Projects pixel coordinates on a given - // zoom into geographical coordinates. - pointToLatLng: function (point, zoom) { - var scale = this.scale(zoom), - untransformedPoint = this.transformation.untransform(point, scale); - - return this.projection.unproject(untransformedPoint); - }, - - // @method project(latlng: LatLng): Point - // Projects geographical coordinates into coordinates in units accepted for - // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services). - project: function (latlng) { - return this.projection.project(latlng); - }, - - // @method unproject(point: Point): LatLng - // Given a projected coordinate returns the corresponding LatLng. - // The inverse of `project`. - unproject: function (point) { - return this.projection.unproject(point); - }, - - // @method scale(zoom: Number): Number - // Returns the scale used when transforming projected coordinates into - // pixel coordinates for a particular zoom. For example, it returns - // `256 * 2^zoom` for Mercator-based CRS. - scale: function (zoom) { - return 256 * Math.pow(2, zoom); - }, - - // @method zoom(scale: Number): Number - // Inverse of `scale()`, returns the zoom level corresponding to a scale - // factor of `scale`. - zoom: function (scale) { - return Math.log(scale / 256) / Math.LN2; - }, - - // @method getProjectedBounds(zoom: Number): Bounds - // Returns the projection's bounds scaled and transformed for the provided `zoom`. - getProjectedBounds: function (zoom) { - if (this.infinite) { return null; } - - var b = this.projection.bounds, - s = this.scale(zoom), - min = this.transformation.transform(b.min, s), - max = this.transformation.transform(b.max, s); - - return new Bounds(min, max); - }, - - // @method distance(latlng1: LatLng, latlng2: LatLng): Number - // Returns the distance between two geographical coordinates. - - // @property code: String - // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`) - // - // @property wrapLng: Number[] - // An array of two numbers defining whether the longitude (horizontal) coordinate - // axis wraps around a given range and how. Defaults to `[-180, 180]` in most - // geographical CRSs. If `undefined`, the longitude axis does not wrap around. - // - // @property wrapLat: Number[] - // Like `wrapLng`, but for the latitude (vertical) axis. - - // wrapLng: [min, max], - // wrapLat: [min, max], - - // @property infinite: Boolean - // If true, the coordinate space will be unbounded (infinite in both axes) - infinite: false, - - // @method wrapLatLng(latlng: LatLng): LatLng - // Returns a `LatLng` where lat and lng has been wrapped according to the - // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds. - wrapLatLng: function (latlng) { - var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, - lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat, - alt = latlng.alt; - - return new LatLng(lat, lng, alt); - }, - - // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds - // Returns a `LatLngBounds` with the same size as the given one, ensuring - // that its center is within the CRS's bounds. - // Only accepts actual `L.LatLngBounds` instances, not arrays. - wrapLatLngBounds: function (bounds) { - var center = bounds.getCenter(), - newCenter = this.wrapLatLng(center), - latShift = center.lat - newCenter.lat, - lngShift = center.lng - newCenter.lng; - - if (latShift === 0 && lngShift === 0) { - return bounds; - } - - var sw = bounds.getSouthWest(), - ne = bounds.getNorthEast(), - newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift), - newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift); - - return new LatLngBounds(newSw, newNe); - } - }; - - /* - * @namespace CRS - * @crs L.CRS.Earth - * - * Serves as the base for CRS that are global such that they cover the earth. - * Can only be used as the base for other CRS and cannot be used directly, - * since it does not have a `code`, `projection` or `transformation`. `distance()` returns - * meters. - */ - - var Earth = extend({}, CRS, { - wrapLng: [-180, 180], - - // Mean Earth Radius, as recommended for use by - // the International Union of Geodesy and Geophysics, - // see https://rosettacode.org/wiki/Haversine_formula - R: 6371000, - - // distance between two geographical points using spherical law of cosines approximation - distance: function (latlng1, latlng2) { - var rad = Math.PI / 180, - lat1 = latlng1.lat * rad, - lat2 = latlng2.lat * rad, - sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2), - sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2), - a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon, - c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - return this.R * c; - } - }); - - /* - * @namespace Projection - * @projection L.Projection.SphericalMercator - * - * Spherical Mercator projection — the most common projection for online maps, - * used by almost all free and commercial tile providers. Assumes that Earth is - * a sphere. Used by the `EPSG:3857` CRS. - */ - - var earthRadius = 6378137; - - var SphericalMercator = { - - R: earthRadius, - MAX_LATITUDE: 85.0511287798, - - project: function (latlng) { - var d = Math.PI / 180, - max = this.MAX_LATITUDE, - lat = Math.max(Math.min(max, latlng.lat), -max), - sin = Math.sin(lat * d); - - return new Point( - this.R * latlng.lng * d, - this.R * Math.log((1 + sin) / (1 - sin)) / 2); - }, - - unproject: function (point) { - var d = 180 / Math.PI; - - return new LatLng( - (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d, - point.x * d / this.R); - }, - - bounds: (function () { - var d = earthRadius * Math.PI; - return new Bounds([-d, -d], [d, d]); - })() - }; - - /* - * @class Transformation - * @aka L.Transformation - * - * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d` - * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing - * the reverse. Used by Leaflet in its projections code. - * - * @example - * - * ```js - * var transformation = L.transformation(2, 5, -1, 10), - * p = L.point(1, 2), - * p2 = transformation.transform(p), // L.point(7, 8) - * p3 = transformation.untransform(p2); // L.point(1, 2) - * ``` - */ - - - // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number) - // Creates a `Transformation` object with the given coefficients. - function Transformation(a, b, c, d) { - if (isArray(a)) { - // use array properties - this._a = a[0]; - this._b = a[1]; - this._c = a[2]; - this._d = a[3]; - return; - } - this._a = a; - this._b = b; - this._c = c; - this._d = d; - } - - Transformation.prototype = { - // @method transform(point: Point, scale?: Number): Point - // Returns a transformed point, optionally multiplied by the given scale. - // Only accepts actual `L.Point` instances, not arrays. - transform: function (point, scale) { // (Point, Number) -> Point - return this._transform(point.clone(), scale); - }, - - // destructive transform (faster) - _transform: function (point, scale) { - scale = scale || 1; - point.x = scale * (this._a * point.x + this._b); - point.y = scale * (this._c * point.y + this._d); - return point; - }, - - // @method untransform(point: Point, scale?: Number): Point - // Returns the reverse transformation of the given point, optionally divided - // by the given scale. Only accepts actual `L.Point` instances, not arrays. - untransform: function (point, scale) { - scale = scale || 1; - return new Point( - (point.x / scale - this._b) / this._a, - (point.y / scale - this._d) / this._c); - } - }; - - // factory L.transformation(a: Number, b: Number, c: Number, d: Number) - - // @factory L.transformation(a: Number, b: Number, c: Number, d: Number) - // Instantiates a Transformation object with the given coefficients. - - // @alternative - // @factory L.transformation(coefficients: Array): Transformation - // Expects an coefficients array of the form - // `[a: Number, b: Number, c: Number, d: Number]`. - - function toTransformation(a, b, c, d) { - return new Transformation(a, b, c, d); - } - - /* - * @namespace CRS - * @crs L.CRS.EPSG3857 - * - * The most common CRS for online maps, used by almost all free and commercial - * tile providers. Uses Spherical Mercator projection. Set in by default in - * Map's `crs` option. - */ - - var EPSG3857 = extend({}, Earth, { - code: 'EPSG:3857', - projection: SphericalMercator, - - transformation: (function () { - var scale = 0.5 / (Math.PI * SphericalMercator.R); - return toTransformation(scale, 0.5, -scale, 0.5); - }()) - }); - - var EPSG900913 = extend({}, EPSG3857, { - code: 'EPSG:900913' - }); - - // @namespace SVG; @section - // There are several static functions which can be called without instantiating L.SVG: - - // @function create(name: String): SVGElement - // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement), - // corresponding to the class name passed. For example, using 'line' will return - // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement). - function svgCreate(name) { - return document.createElementNS('http://www.w3.org/2000/svg', name); - } - - // @function pointsToPath(rings: Point[], closed: Boolean): String - // Generates a SVG path string for multiple rings, with each ring turning - // into "M..L..L.." instructions - function pointsToPath(rings, closed) { - var str = '', - i, j, len, len2, points, p; - - for (i = 0, len = rings.length; i < len; i++) { - points = rings[i]; - - for (j = 0, len2 = points.length; j < len2; j++) { - p = points[j]; - str += (j ? 'L' : 'M') + p.x + ' ' + p.y; - } - - // closes the ring for polygons; "x" is VML syntax - str += closed ? (Browser.svg ? 'z' : 'x') : ''; - } - - // SVG complains about empty path strings - return str || 'M0 0'; - } - - /* - * @namespace Browser - * @aka L.Browser - * - * A namespace with static properties for browser/feature detection used by Leaflet internally. - * - * @example - * - * ```js - * if (L.Browser.ielt9) { - * alert('Upgrade your browser, dude!'); - * } - * ``` - */ - - var style = document.documentElement.style; - - // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge). - var ie = 'ActiveXObject' in window; - - // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9. - var ielt9 = ie && !document.addEventListener; - - // @property edge: Boolean; `true` for the Edge web browser. - var edge = 'msLaunchUri' in navigator && !('documentMode' in document); - - // @property webkit: Boolean; - // `true` for webkit-based browsers like Chrome and Safari (including mobile versions). - var webkit = userAgentContains('webkit'); - - // @property android: Boolean - // **Deprecated.** `true` for any browser running on an Android platform. - var android = userAgentContains('android'); - - // @property android23: Boolean; **Deprecated.** `true` for browsers running on Android 2 or Android 3. - var android23 = userAgentContains('android 2') || userAgentContains('android 3'); - - /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */ - var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit - // @property androidStock: Boolean; **Deprecated.** `true` for the Android stock browser (i.e. not Chrome) - var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window); - - // @property opera: Boolean; `true` for the Opera browser - var opera = !!window.opera; - - // @property chrome: Boolean; `true` for the Chrome browser. - var chrome = !edge && userAgentContains('chrome'); - - // @property gecko: Boolean; `true` for gecko-based browsers like Firefox. - var gecko = userAgentContains('gecko') && !webkit && !opera && !ie; - - // @property safari: Boolean; `true` for the Safari browser. - var safari = !chrome && userAgentContains('safari'); - - var phantom = userAgentContains('phantom'); - - // @property opera12: Boolean - // `true` for the Opera browser supporting CSS transforms (version 12 or later). - var opera12 = 'OTransition' in style; - - // @property win: Boolean; `true` when the browser is running in a Windows platform - var win = navigator.platform.indexOf('Win') === 0; - - // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms. - var ie3d = ie && ('transition' in style); - - // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms. - var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23; - - // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms. - var gecko3d = 'MozPerspective' in style; - - // @property any3d: Boolean - // `true` for all browsers supporting CSS transforms. - var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom; - - // @property mobile: Boolean; `true` for all browsers running in a mobile device. - var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile'); - - // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device. - var mobileWebkit = mobile && webkit; - - // @property mobileWebkit3d: Boolean - // `true` for all webkit-based browsers in a mobile device supporting CSS transforms. - var mobileWebkit3d = mobile && webkit3d; - - // @property msPointer: Boolean - // `true` for browsers implementing the Microsoft touch events model (notably IE10). - var msPointer = !window.PointerEvent && window.MSPointerEvent; - - // @property pointer: Boolean - // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx). - var pointer = !!(window.PointerEvent || msPointer); - - // @property touchNative: Boolean - // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events). - // **This does not necessarily mean** that the browser is running in a computer with - // a touchscreen, it only means that the browser is capable of understanding - // touch events. - var touchNative = 'ontouchstart' in window || !!window.TouchEvent; - - // @property touch: Boolean - // `true` for all browsers supporting either [touch](#browser-touch) or [pointer](#browser-pointer) events. - // Note: pointer events will be preferred (if available), and processed for all `touch*` listeners. - var touch = !window.L_NO_TOUCH && (touchNative || pointer); - - // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device. - var mobileOpera = mobile && opera; - - // @property mobileGecko: Boolean - // `true` for gecko-based browsers running in a mobile device. - var mobileGecko = mobile && gecko; - - // @property retina: Boolean - // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%. - var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1; - - // @property passiveEvents: Boolean - // `true` for browsers that support passive events. - var passiveEvents = (function () { - var supportsPassiveOption = false; - try { - var opts = Object.defineProperty({}, 'passive', { - get: function () { // eslint-disable-line getter-return - supportsPassiveOption = true; - } - }); - window.addEventListener('testPassiveEventSupport', falseFn, opts); - window.removeEventListener('testPassiveEventSupport', falseFn, opts); - } catch (e) { - // Errors can safely be ignored since this is only a browser support test. - } - return supportsPassiveOption; - }()); - - // @property canvas: Boolean - // `true` when the browser supports [``](https://developer.mozilla.org/docs/Web/API/Canvas_API). - var canvas$1 = (function () { - return !!document.createElement('canvas').getContext; - }()); - - // @property svg: Boolean - // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG). - var svg$1 = !!(document.createElementNS && svgCreate('svg').createSVGRect); - - var inlineSvg = !!svg$1 && (function () { - var div = document.createElement('div'); - div.innerHTML = ''; - return (div.firstChild && div.firstChild.namespaceURI) === 'http://www.w3.org/2000/svg'; - })(); - - // @property vml: Boolean - // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language). - var vml = !svg$1 && (function () { - try { - var div = document.createElement('div'); - div.innerHTML = ''; - - var shape = div.firstChild; - shape.style.behavior = 'url(#default#VML)'; - - return shape && (typeof shape.adj === 'object'); - - } catch (e) { - return false; - } - }()); - - - // @property mac: Boolean; `true` when the browser is running in a Mac platform - var mac = navigator.platform.indexOf('Mac') === 0; - - // @property mac: Boolean; `true` when the browser is running in a Linux platform - var linux = navigator.platform.indexOf('Linux') === 0; - - function userAgentContains(str) { - return navigator.userAgent.toLowerCase().indexOf(str) >= 0; - } - - - var Browser = { - ie: ie, - ielt9: ielt9, - edge: edge, - webkit: webkit, - android: android, - android23: android23, - androidStock: androidStock, - opera: opera, - chrome: chrome, - gecko: gecko, - safari: safari, - phantom: phantom, - opera12: opera12, - win: win, - ie3d: ie3d, - webkit3d: webkit3d, - gecko3d: gecko3d, - any3d: any3d, - mobile: mobile, - mobileWebkit: mobileWebkit, - mobileWebkit3d: mobileWebkit3d, - msPointer: msPointer, - pointer: pointer, - touch: touch, - touchNative: touchNative, - mobileOpera: mobileOpera, - mobileGecko: mobileGecko, - retina: retina, - passiveEvents: passiveEvents, - canvas: canvas$1, - svg: svg$1, - vml: vml, - inlineSvg: inlineSvg, - mac: mac, - linux: linux - }; - - /* - * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. - */ - - var POINTER_DOWN = Browser.msPointer ? 'MSPointerDown' : 'pointerdown'; - var POINTER_MOVE = Browser.msPointer ? 'MSPointerMove' : 'pointermove'; - var POINTER_UP = Browser.msPointer ? 'MSPointerUp' : 'pointerup'; - var POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel'; - var pEvent = { - touchstart : POINTER_DOWN, - touchmove : POINTER_MOVE, - touchend : POINTER_UP, - touchcancel : POINTER_CANCEL - }; - var handle = { - touchstart : _onPointerStart, - touchmove : _handlePointer, - touchend : _handlePointer, - touchcancel : _handlePointer - }; - var _pointers = {}; - var _pointerDocListener = false; - - // Provides a touch events wrapper for (ms)pointer events. - // ref https://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 - - function addPointerListener(obj, type, handler) { - if (type === 'touchstart') { - _addPointerDocListener(); - } - if (!handle[type]) { - console.warn('wrong event specified:', type); - return falseFn; - } - handler = handle[type].bind(this, handler); - obj.addEventListener(pEvent[type], handler, false); - return handler; - } - - function removePointerListener(obj, type, handler) { - if (!pEvent[type]) { - console.warn('wrong event specified:', type); - return; - } - obj.removeEventListener(pEvent[type], handler, false); - } - - function _globalPointerDown(e) { - _pointers[e.pointerId] = e; - } - - function _globalPointerMove(e) { - if (_pointers[e.pointerId]) { - _pointers[e.pointerId] = e; - } - } - - function _globalPointerUp(e) { - delete _pointers[e.pointerId]; - } - - function _addPointerDocListener() { - // need to keep track of what pointers and how many are active to provide e.touches emulation - if (!_pointerDocListener) { - // we listen document as any drags that end by moving the touch off the screen get fired there - document.addEventListener(POINTER_DOWN, _globalPointerDown, true); - document.addEventListener(POINTER_MOVE, _globalPointerMove, true); - document.addEventListener(POINTER_UP, _globalPointerUp, true); - document.addEventListener(POINTER_CANCEL, _globalPointerUp, true); - - _pointerDocListener = true; - } - } - - function _handlePointer(handler, e) { - if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || 'mouse')) { return; } - - e.touches = []; - for (var i in _pointers) { - e.touches.push(_pointers[i]); - } - e.changedTouches = [e]; - - handler(e); - } - - function _onPointerStart(handler, e) { - // IE10 specific: MsTouch needs preventDefault. See #2000 - if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) { - preventDefault(e); - } - _handlePointer(handler, e); - } - - /* - * Extends the event handling code with double tap support for mobile browsers. - * - * Note: currently most browsers fire native dblclick, with only a few exceptions - * (see https://github.com/Leaflet/Leaflet/issues/7012#issuecomment-595087386) - */ - - function makeDblclick(event) { - // in modern browsers `type` cannot be just overridden: - // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only - var newEvent = {}, - prop, i; - for (i in event) { - prop = event[i]; - newEvent[i] = prop && prop.bind ? prop.bind(event) : prop; - } - event = newEvent; - newEvent.type = 'dblclick'; - newEvent.detail = 2; - newEvent.isTrusted = false; - newEvent._simulated = true; // for debug purposes - return newEvent; - } - - var delay = 200; - function addDoubleTapListener(obj, handler) { - // Most browsers handle double tap natively - obj.addEventListener('dblclick', handler); - - // On some platforms the browser doesn't fire native dblclicks for touch events. - // It seems that in all such cases `detail` property of `click` event is always `1`. - // So here we rely on that fact to avoid excessive 'dblclick' simulation when not needed. - var last = 0, - detail; - function simDblclick(e) { - if (e.detail !== 1) { - detail = e.detail; // keep in sync to avoid false dblclick in some cases - return; - } - - if (e.pointerType === 'mouse' || - (e.sourceCapabilities && !e.sourceCapabilities.firesTouchEvents)) { - - return; - } - - // When clicking on an , the browser generates a click on its - //