├── .browserslistrc ├── .github └── workflows │ ├── add-testlink-to-pr.yml │ ├── pr-auto-semver.yml │ └── semver.yml ├── .gitignore ├── .npmrc ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── adr ├── 2020_04_06_javascript_framework_choice.md ├── 2020_05_28_test_framework.md ├── 2021_03_16_url_param_structure.md ├── 2021_04_14_drawing_library.md ├── 2022_04_11_vuex_store_consolidation.md ├── 2022_05_09_mobile_click_behavior.md ├── 2023_11_08_vue_composition_api.md ├── 2024_01_31_drawing_icon_size.md └── 2025-01-27_modularization.md ├── env.d.ts ├── eslint.config.mjs ├── jsconfig.json ├── package.json ├── packages ├── geoadmin-coordinates │ ├── package.json │ ├── setup-vitest.ts │ ├── src │ │ ├── __test__ │ │ │ └── utils.spec.js │ │ ├── index.ts │ │ ├── proj │ │ │ ├── CoordinateSystem.ts │ │ │ ├── CoordinateSystemBounds.ts │ │ │ ├── CoordinatesChunk.ts │ │ │ ├── CustomCoordinateSystem.ts │ │ │ ├── LV03CoordinateSystem.ts │ │ │ ├── LV95CoordinateSystem.ts │ │ │ ├── StandardCoordinateSystem.ts │ │ │ ├── SwissCoordinateSystem.ts │ │ │ ├── WGS84CoordinateSystem.ts │ │ │ ├── WebMercatorCoordinateSystem.ts │ │ │ ├── __test__ │ │ │ │ ├── CoordinateSystem.class.spec.js │ │ │ │ ├── CoordinateSystemBounds.class.spec.js │ │ │ │ └── SwissCoordinateSystem.class.spec.js │ │ │ └── index.ts │ │ ├── registerProj4.ts │ │ └── utils.ts │ ├── tsconfig.json │ └── vite.config.js ├── geoadmin-elevation-profile │ ├── env.d.ts │ ├── index.html │ ├── package.json │ ├── public │ │ └── favicon.ico │ ├── src │ │ ├── DevApp.vue │ │ ├── GeoadminElevationProfile.vue │ │ ├── GeoadminElevationProfileCesiumBridge.vue │ │ ├── GeoadminElevationProfileInformation.vue │ │ ├── GeoadminElevationProfileOpenLayersBridge.vue │ │ ├── GeoadminElevationProfilePlot.vue │ │ ├── __test__ │ │ │ ├── profile.api.spec.js │ │ │ └── utils.spec.ts │ │ ├── chartjs-plugins │ │ │ ├── datamodel.plugin.ts │ │ │ ├── nodata.plugin.ts │ │ │ └── plugins.d.ts │ │ ├── config.ts │ │ ├── dev.ts │ │ ├── index.ts │ │ ├── profile.api.ts │ │ ├── style.css │ │ ├── utils.ts │ │ └── vue-i18n.d.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ └── vite.config.js ├── geoadmin-log │ ├── package.json │ ├── src │ │ ├── Message.ts │ │ └── index.ts │ ├── tsconfig.json │ └── vite.config.js ├── geoadmin-numbers │ ├── package.json │ ├── src │ │ ├── __test__ │ │ │ └── numbers.spec.js │ │ └── index.ts │ ├── tsconfig.json │ └── vite.config.js ├── geoadmin-tooltip │ ├── env.d.ts │ ├── index.html │ ├── package.json │ ├── src │ │ ├── DevApp.vue │ │ ├── GeoadminTooltip.vue │ │ ├── dev.ts │ │ ├── index.ts │ │ └── style.css │ ├── tailwind.config.js │ ├── tsconfig.json │ └── vite.config.js └── mapviewer │ ├── .env.development │ ├── .env.integration │ ├── .env.production │ ├── .env.test │ ├── cypress.config.mjs │ ├── index.html │ ├── package.json │ ├── public │ ├── apple-touch-icon.png │ ├── favicon.ico │ ├── icon-192.png │ ├── icon-512.png │ ├── icon.svg │ ├── manifest.webmanifest │ └── robots.txt │ ├── scripts │ ├── check-external-layers-providers.js │ └── generate-i18n-files.js │ ├── secrets.yml │ ├── src │ ├── App.vue │ ├── api │ │ ├── __tests__ │ │ │ ├── file-proxy.api.spec.js │ │ │ ├── print.api.spec.js │ │ │ └── search.api.spec.js │ │ ├── errorQueues.api.js │ │ ├── features │ │ │ ├── EditableFeature.class.js │ │ │ ├── LayerFeature.class.js │ │ │ ├── SelectableFeature.class.js │ │ │ └── features.api.js │ │ ├── feedback.api.js │ │ ├── file-proxy.api.js │ │ ├── files.api.js │ │ ├── height.api.js │ │ ├── icon.api.js │ │ ├── iframePostMessageEvent.api.js │ │ ├── layers │ │ │ ├── AbstractLayer.class.js │ │ │ ├── CloudOptimizedGeoTIFFLayer.class.js │ │ │ ├── ExternalLayer.class.js │ │ │ ├── ExternalWMSLayer.class.js │ │ │ ├── ExternalWMTSLayer.class.js │ │ │ ├── GPXLayer.class.js │ │ │ ├── GeoAdmin3DLayer.class.js │ │ │ ├── GeoAdminAggregateLayer.class.js │ │ │ ├── GeoAdminGeoJsonLayer.class.js │ │ │ ├── GeoAdminGroupOfLayers.class.js │ │ │ ├── GeoAdminLayer.class.js │ │ │ ├── GeoAdminVectorLayer.class.js │ │ │ ├── GeoAdminWMSLayer.class.js │ │ │ ├── GeoAdminWMTSLayer.class.js │ │ │ ├── InvalidLayerData.error.js │ │ │ ├── KMLLayer.class.js │ │ │ ├── KmlStyles.enum.js │ │ │ ├── LayerTimeConfig.class.js │ │ │ ├── LayerTimeConfigEntry.class.js │ │ │ ├── LayerTypes.enum.js │ │ │ ├── WMSCapabilitiesParser.class.js │ │ │ ├── WMTSCapabilitiesParser.class.js │ │ │ ├── __tests__ │ │ │ │ ├── KMLLayer.class.spec.js │ │ │ │ ├── WMSCapabitliesParser.class.spec.js │ │ │ │ ├── WMTSCapabitliesParser.class.spec.js │ │ │ │ ├── wms-geoadmin-sample.xml │ │ │ │ └── wmts-ogc-sample.xml │ │ │ ├── layers-external.api.js │ │ │ └── layers.api.js │ │ ├── lv03Reframe.api.js │ │ ├── print.api.js │ │ ├── qrcode.api.js │ │ ├── search.api.js │ │ ├── shortlink.api.js │ │ ├── topics.api.js │ │ └── what3words.api.js │ ├── assets │ │ ├── grid.png │ │ ├── logo.png │ │ └── svg │ │ │ └── swiss-flag.svg │ ├── config │ │ ├── baseUrl.config.js │ │ ├── cesium.config.js │ │ ├── feedback.config.js │ │ ├── map.config.js │ │ ├── print.config.js │ │ ├── responsive.config.js │ │ ├── security.config.js │ │ ├── staging.config.js │ │ ├── time.config.js │ │ └── vectortiles.config.js │ ├── css │ │ └── tailwind.css │ ├── main.js │ ├── modules │ │ ├── drawing │ │ │ ├── DrawingModule.vue │ │ │ ├── README.md │ │ │ ├── components │ │ │ │ ├── AddVertexButton.vue │ │ │ │ ├── AddVertexButtonOverlay.vue │ │ │ │ ├── DrawingExporter.vue │ │ │ │ ├── DrawingHeader.vue │ │ │ │ ├── DrawingInteractions.vue │ │ │ │ ├── DrawingLineInteraction.vue │ │ │ │ ├── DrawingMarkerInteraction.vue │ │ │ │ ├── DrawingMeasureInteraction.vue │ │ │ │ ├── DrawingSelectInteraction.vue │ │ │ │ ├── DrawingTextInteraction.vue │ │ │ │ ├── DrawingToolbox.vue │ │ │ │ ├── DrawingToolboxButton.vue │ │ │ │ ├── DrawingTooltip.vue │ │ │ │ ├── ExtendLineInteraction.vue │ │ │ │ ├── ExtendMeasureInteraction.vue │ │ │ │ ├── SharePopup.vue │ │ │ │ ├── ShareWarningPopup.vue │ │ │ │ ├── useDrawingLineInteraction.composable.js │ │ │ │ ├── useDrawingModeInteraction.composable.js │ │ │ │ ├── useExtendLineInteraction.composable.js │ │ │ │ └── useModifyInteraction.composable.js │ │ │ ├── lib │ │ │ │ ├── drawingUtils.js │ │ │ │ ├── export-utils.js │ │ │ │ ├── modifyInteraction.js │ │ │ │ └── style.js │ │ │ └── useKmlDataManagement.composable.js │ │ ├── i18n │ │ │ ├── README.md │ │ │ ├── index.js │ │ │ └── locales │ │ │ │ ├── de.json │ │ │ │ ├── en.json │ │ │ │ ├── fr.json │ │ │ │ ├── it.json │ │ │ │ └── rm.json │ │ ├── infobox │ │ │ ├── DrawingStyleMediaTypes.enum.js │ │ │ ├── InfoboxModule.vue │ │ │ ├── README.md │ │ │ ├── components │ │ │ │ ├── FeatureAreaInfo.vue │ │ │ │ ├── FeatureDetail.vue │ │ │ │ ├── FeatureDetailDisclaimer.vue │ │ │ │ ├── FeatureList.vue │ │ │ │ ├── FeatureListCategory.vue │ │ │ │ ├── FeatureListCategoryItem.vue │ │ │ │ ├── InfoboxContent.vue │ │ │ │ ├── ShowGeometryProfileButton.vue │ │ │ │ └── styling │ │ │ │ │ ├── DrawingStyleColorSelector.vue │ │ │ │ │ ├── DrawingStyleIcon.vue │ │ │ │ │ ├── DrawingStyleIconSelector.vue │ │ │ │ │ ├── DrawingStyleMediaLink.vue │ │ │ │ │ ├── DrawingStylePlacementSelector.vue │ │ │ │ │ ├── DrawingStylePopoverButton.vue │ │ │ │ │ ├── DrawingStyleSizeSelector.vue │ │ │ │ │ ├── DrawingStyleTextColorSelector.vue │ │ │ │ │ └── FeatureStyleEdit.vue │ │ │ └── index.js │ │ ├── map │ │ │ ├── MapModule.vue │ │ │ ├── README.md │ │ │ ├── assets │ │ │ │ ├── bowl.png │ │ │ │ ├── ch.swisstopo.leichte-basiskarte_world-vt.png │ │ │ │ ├── ch.swisstopo.pixelkarte-farbe.png │ │ │ │ ├── ch.swisstopo.pixelkarte-grau.png │ │ │ │ ├── ch.swisstopo.swissimage.png │ │ │ │ ├── ch.swisstopo.swissimage_3d.png │ │ │ │ ├── ch.swisstopo.swisstlm3d-karte-farbe_3d.png │ │ │ │ ├── ch.swisstopo.swisstlm3d-karte-grau_3d.png │ │ │ │ ├── circle.png │ │ │ │ ├── cross.png │ │ │ │ ├── marker.png │ │ │ │ ├── north_arrow.png │ │ │ │ ├── point.png │ │ │ │ └── void.png │ │ │ ├── components │ │ │ │ ├── CompareSlider.vue │ │ │ │ ├── LocationPopup.vue │ │ │ │ ├── LocationPopupPosition.vue │ │ │ │ ├── LocationPopupShare.vue │ │ │ │ ├── MapPopover.vue │ │ │ │ ├── WarningRibbon.vue │ │ │ │ ├── cesium │ │ │ │ │ ├── CesiumBackgroundLayer.vue │ │ │ │ │ ├── CesiumCamera.vue │ │ │ │ │ ├── CesiumGPXLayer.vue │ │ │ │ │ ├── CesiumGeoJSONLayer.vue │ │ │ │ │ ├── CesiumGeolocationFeedback.vue │ │ │ │ │ ├── CesiumHighlightedFeatures.vue │ │ │ │ │ ├── CesiumInteractions.vue │ │ │ │ │ ├── CesiumInternalLayer.vue │ │ │ │ │ ├── CesiumKMLLayer.vue │ │ │ │ │ ├── CesiumMap.vue │ │ │ │ │ ├── CesiumMouseTracker.vue │ │ │ │ │ ├── CesiumPopover.vue │ │ │ │ │ ├── CesiumVectorLayer.vue │ │ │ │ │ ├── CesiumVisibleLayers.vue │ │ │ │ │ ├── CesiumWMSLayer.vue │ │ │ │ │ ├── CesiumWMTSLayer.vue │ │ │ │ │ ├── locales │ │ │ │ │ │ ├── de.json │ │ │ │ │ │ ├── en.json │ │ │ │ │ │ ├── fr.json │ │ │ │ │ │ ├── it.json │ │ │ │ │ │ └── rm.json │ │ │ │ │ └── utils │ │ │ │ │ │ ├── addImageryLayer-mixins.js │ │ │ │ │ │ ├── addLayerToViewer-mixins.js │ │ │ │ │ │ ├── cameraUtils.js │ │ │ │ │ │ ├── highlightUtils.js │ │ │ │ │ │ ├── styleConverter.js │ │ │ │ │ │ ├── swissnamesStyle.js │ │ │ │ │ │ ├── useAddDataSourceLayer.composable.js │ │ │ │ │ │ ├── useAddImageryLayer.composable.js │ │ │ │ │ │ └── useAddPrimitiveLayer.composable.js │ │ │ │ ├── common │ │ │ │ │ ├── mouseTrackerUtils.js │ │ │ │ │ ├── useDragFileOverlay.composable.js │ │ │ │ │ └── z-index.composable.js │ │ │ │ ├── footer │ │ │ │ │ ├── MapFooter.vue │ │ │ │ │ ├── MapFooterAppCopyright.vue │ │ │ │ │ ├── MapFooterAttributionItem.vue │ │ │ │ │ ├── MapFooterAttributionList.vue │ │ │ │ │ └── backgroundSelector │ │ │ │ │ │ ├── BackgroundSelector.vue │ │ │ │ │ │ ├── BackgroundSelectorSquared.vue │ │ │ │ │ │ ├── BackgroundSelectorWheelRounded.vue │ │ │ │ │ │ ├── bg-selector.scss │ │ │ │ │ │ ├── useBackgroundSelector.js │ │ │ │ │ │ └── useBackgroundSelectorProps.js │ │ │ │ ├── openlayers │ │ │ │ │ ├── OpenLayersAccuracyCircle.vue │ │ │ │ │ ├── OpenLayersBackgroundLayer.vue │ │ │ │ │ ├── OpenLayersCOGTiffLayer.vue │ │ │ │ │ ├── OpenLayersCompassButton.vue │ │ │ │ │ ├── OpenLayersCrossHair.vue │ │ │ │ │ ├── OpenLayersExternalWMTSLayer.vue │ │ │ │ │ ├── OpenLayersGPXLayer.vue │ │ │ │ │ ├── OpenLayersGeoJSONLayer.vue │ │ │ │ │ ├── OpenLayersGeolocationFeedback.vue │ │ │ │ │ ├── OpenLayersHighlightedFeatures.vue │ │ │ │ │ ├── OpenLayersInternalLayer.vue │ │ │ │ │ ├── OpenLayersKMLLayer.vue │ │ │ │ │ ├── OpenLayersMap.vue │ │ │ │ │ ├── OpenLayersMarker.vue │ │ │ │ │ ├── OpenLayersMouseTracker.vue │ │ │ │ │ ├── OpenLayersPinnedLocation.vue │ │ │ │ │ ├── OpenLayersPopover.vue │ │ │ │ │ ├── OpenLayersPrintResolutionEnforcer.vue │ │ │ │ │ ├── OpenLayersRectangleSelectionFeedback.vue │ │ │ │ │ ├── OpenLayersScale.vue │ │ │ │ │ ├── OpenLayersVectorLayer.vue │ │ │ │ │ ├── OpenLayersVisibleLayers.vue │ │ │ │ │ ├── OpenLayersVisionCone.vue │ │ │ │ │ ├── OpenLayersWMSLayer.vue │ │ │ │ │ ├── OpenLayersWMTSLayer.vue │ │ │ │ │ ├── debug │ │ │ │ │ │ ├── OpenLayersDeviceOrientationDebugInfo.vue │ │ │ │ │ │ ├── OpenLayersLayerExtents.vue │ │ │ │ │ │ └── OpenLayersTileDebugInfo.vue │ │ │ │ │ └── utils │ │ │ │ │ │ ├── markerStyle.js │ │ │ │ │ │ ├── styleFromLiterals.js │ │ │ │ │ │ ├── useAddLayerToMap.composable.js │ │ │ │ │ │ ├── useDeviceOrientation.composable.js │ │ │ │ │ │ ├── useDragBoxSelect.composable.js │ │ │ │ │ │ ├── useMapInteractions.composable.js │ │ │ │ │ │ ├── usePrint.composable.js │ │ │ │ │ │ ├── usePrintAreaRenderer.composable.js │ │ │ │ │ │ ├── useVectorLayer.composable.js │ │ │ │ │ │ └── useViewBasedOnProjection.composable.js │ │ │ │ └── toolbox │ │ │ │ │ ├── FullScreenButton.vue │ │ │ │ │ ├── GeolocButton.vue │ │ │ │ │ ├── MapToolbox.vue │ │ │ │ │ ├── TimeSlider.vue │ │ │ │ │ ├── TimeSliderButton.vue │ │ │ │ │ ├── TimeSliderDropdown.vue │ │ │ │ │ ├── TimeSliderDropdownSearchList.vue │ │ │ │ │ ├── Toggle3dButton.vue │ │ │ │ │ └── ZoomButtons.vue │ │ │ ├── index.js │ │ │ └── scss │ │ │ │ └── toolbox-buttons.scss │ │ └── menu │ │ │ ├── MenuModule.vue │ │ │ ├── README.md │ │ │ ├── assets │ │ │ ├── text-dev.png │ │ │ ├── text.png │ │ │ └── topics.png │ │ │ ├── components │ │ │ ├── 3d │ │ │ │ └── MenuThreeD.vue │ │ │ ├── LayerCatalogue.vue │ │ │ ├── LayerCatalogueItem.vue │ │ │ ├── LayerDescriptionPopup.vue │ │ │ ├── activeLayers │ │ │ │ ├── MenuActiveLayersList.vue │ │ │ │ ├── MenuActiveLayersListItem.vue │ │ │ │ ├── MenuActiveLayersListItemTimeSelector.vue │ │ │ │ └── TransparencySlider.vue │ │ │ ├── advancedTools │ │ │ │ ├── ImportCatalogue │ │ │ │ │ ├── ImportCatalogue.vue │ │ │ │ │ ├── ProviderList.vue │ │ │ │ │ ├── ProviderUrl.vue │ │ │ │ │ ├── external-providers.json │ │ │ │ │ ├── useCapabilities.js │ │ │ │ │ ├── useProviders.js │ │ │ │ │ └── utils.js │ │ │ │ ├── ImportFile │ │ │ │ │ ├── ImportFile.vue │ │ │ │ │ ├── ImportFileButtons.vue │ │ │ │ │ ├── ImportFileLocalTab.vue │ │ │ │ │ ├── ImportFileOnlineTab.vue │ │ │ │ │ ├── parser │ │ │ │ │ │ ├── CloudOptimizedGeoTIFFParser.class.js │ │ │ │ │ │ ├── FileParser.class.js │ │ │ │ │ │ ├── GPXParser.class.js │ │ │ │ │ │ ├── KMLParser.class.js │ │ │ │ │ │ ├── KMZParser.class.js │ │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ │ └── KMLParser.class.spec.js │ │ │ │ │ │ ├── errors │ │ │ │ │ │ │ ├── EmptyFileContentError.error.js │ │ │ │ │ │ │ ├── InvalidFileContentError.error.js │ │ │ │ │ │ │ ├── OutOfBoundsError.error.js │ │ │ │ │ │ │ ├── UnknownProjectionError.error.js │ │ │ │ │ │ │ └── generateErrorMessageFromErrorType.utils.js │ │ │ │ │ │ └── index.js │ │ │ │ │ └── useImportFile.composable.js │ │ │ │ ├── MenuAdvancedToolsList.vue │ │ │ │ └── MenuAdvancedToolsListItem.vue │ │ │ ├── common │ │ │ │ └── MenuItemCheckBox.vue │ │ │ ├── debug │ │ │ │ ├── BaseUrlOverrideModal.vue │ │ │ │ ├── DebugLayerFinder.vue │ │ │ │ ├── DebugLayerFinderFilter.vue │ │ │ │ ├── DebugPrint.vue │ │ │ │ ├── DebugToolbar.vue │ │ │ │ └── DebugViewSelector.vue │ │ │ ├── header │ │ │ │ ├── AdditionalInfoCollapsable.vue │ │ │ │ ├── ConfederationFullLogo.vue │ │ │ │ ├── HeaderLangSelector.vue │ │ │ │ ├── HeaderLink.vue │ │ │ │ ├── HeaderMenuButton.vue │ │ │ │ ├── HeaderWithSearch.vue │ │ │ │ └── SwissFlag.vue │ │ │ ├── help │ │ │ │ ├── HelpLink.vue │ │ │ │ ├── MenuHelpSection.vue │ │ │ │ ├── MoreInfo.vue │ │ │ │ ├── ReportProblemButton.vue │ │ │ │ ├── UpdateInfo.vue │ │ │ │ ├── common │ │ │ │ │ └── SendActionButtons.vue │ │ │ │ └── feedback │ │ │ │ │ ├── FeedbackButton.vue │ │ │ │ │ └── FeedbackRating.vue │ │ │ ├── menu │ │ │ │ ├── MenuSection.vue │ │ │ │ └── MenuTray.vue │ │ │ ├── print │ │ │ │ └── MenuPrintSection.vue │ │ │ ├── search │ │ │ │ ├── SearchBar.vue │ │ │ │ ├── SearchResultCategory.vue │ │ │ │ ├── SearchResultList.vue │ │ │ │ └── SearchResultListEntry.vue │ │ │ ├── share │ │ │ │ ├── MenuShareEmbed.vue │ │ │ │ ├── MenuShareInputCopyButton.vue │ │ │ │ ├── MenuShareSection.vue │ │ │ │ └── MenuShareSocialNetworks.vue │ │ │ └── topics │ │ │ │ ├── MenuTopicSection.vue │ │ │ │ ├── MenuTopicSelectionPopup.vue │ │ │ │ └── TopicIcon.vue │ │ │ ├── index.js │ │ │ └── scss │ │ │ ├── menu-items.scss │ │ │ └── topics-image.scss │ ├── router │ │ ├── __tests__ │ │ │ └── legacyPermalinkManagement.routerPlugin.spec.js │ │ ├── appLoadingManagement.routerPlugin.js │ │ ├── index.js │ │ ├── legacyPermalinkManagement.routerPlugin.js │ │ ├── storeSync │ │ │ ├── BaseUrlOverrideParamConfig.class.js │ │ │ ├── CameraParamConfig.class.js │ │ │ ├── CompareSliderParamConfig.class.js │ │ │ ├── CrossHairParamConfig.class.js │ │ │ ├── LayerParamConfig.class.js │ │ │ ├── NoSimpleZoomParamConfig.class.js │ │ │ ├── PositionParamConfig.class.js │ │ │ ├── PrintConfig.class.js │ │ │ ├── SearchAutoSelectConfig.class.js │ │ │ ├── SearchParamConfig.class.js │ │ │ ├── SimpleUrlParamConfig.class.js │ │ │ ├── TimeSliderParamConfig.class.js │ │ │ ├── ZoomParamConfig.class.js │ │ │ ├── __tests__ │ │ │ │ ├── CameraParamConfig.class.spec.js │ │ │ │ ├── LayerParamConfig.class.spec.js │ │ │ │ ├── SimpleUrlParamConfig.class.spec.js │ │ │ │ ├── abstractParamConfig.class.spec.js │ │ │ │ └── layersParamParser.spec.js │ │ │ ├── abstractParamConfig.class.js │ │ │ ├── index.js │ │ │ ├── layersParamParser.js │ │ │ ├── storeSync.config.js │ │ │ └── storeSync.routerPlugin.js │ │ └── viewNames.js │ ├── scss │ │ ├── diemo.scss │ │ ├── exports.js │ │ ├── fonts │ │ │ └── FrutigerLight │ │ │ │ ├── FrutigerLTStd-Light.eot │ │ │ │ ├── FrutigerLTStd-Light.ttf │ │ │ │ ├── FrutigerLTStd-Light.woff │ │ │ │ └── FrutigerLTStd-Light.woff2 │ │ ├── main.scss │ │ ├── media-query.mixin.scss │ │ ├── variables-admin.module.scss │ │ ├── variables.module.scss │ │ ├── vue-transitions.mixin.scss │ │ └── webmapviewer-bootstrap-theme.scss │ ├── setup-fontawesome.js │ ├── store │ │ ├── README.md │ │ ├── debug.store.js │ │ ├── index.js │ │ ├── modules │ │ │ ├── __tests__ │ │ │ │ ├── layers.store.spec.js │ │ │ │ ├── profile.store.spec.js │ │ │ │ ├── rotation.store.spec.js │ │ │ │ └── zoom.store.spec.js │ │ │ ├── app.store.js │ │ │ ├── cesium.store.js │ │ │ ├── drawing.store.js │ │ │ ├── features.store.js │ │ │ ├── geolocation.store.js │ │ │ ├── i18n.store.js │ │ │ ├── layers.store.js │ │ │ ├── map.store.js │ │ │ ├── position.store.js │ │ │ ├── print.store.js │ │ │ ├── profile.store.js │ │ │ ├── search.store.js │ │ │ ├── share.store.js │ │ │ ├── topics.store.js │ │ │ └── ui.store.js │ │ └── plugins │ │ │ ├── 2d-to-3d-management.plugin.js │ │ │ ├── app-readiness.plugin.js │ │ │ ├── click-on-map-management.plugin.js │ │ │ ├── external-layers.plugin.js │ │ │ ├── geolocation-management.plugin.js │ │ │ ├── load-cog-metadata.plugin.js │ │ │ ├── load-geojson-style-and-data.plugin.js │ │ │ ├── load-gpx-data.plugin.js │ │ │ ├── load-kml-kmz-data.plugin.js │ │ │ ├── load-layersconfig-on-lang-change.js │ │ │ ├── redo-search-when-needed.plugin.js │ │ │ ├── reproject-layers-on-projection-change.plugin.js │ │ │ ├── screen-size-management.plugin.js │ │ │ ├── sync-camera-lonlatzoom.js │ │ │ ├── topic-change-management.plugin.js │ │ │ └── update-selected-features.plugin.js │ ├── utils │ │ ├── EventEmitter.class.js │ │ ├── __tests__ │ │ │ ├── extentUtils.spec.js │ │ │ ├── geodesicManager.spec.js │ │ │ ├── gpxUtils.spec.js │ │ │ ├── kmlUtils.spec.js │ │ │ ├── kml_feature_error.kml │ │ │ ├── legacyKmlUtils.spec.js │ │ │ ├── legacyLayerParamUtils.spec.js │ │ │ ├── mfgeoadmin3TestKml.kml │ │ │ ├── urlQuery.spec.js │ │ │ ├── utils.spec.js │ │ │ └── webmapviewerOffsetTestKml.kml │ │ ├── click-outside.js │ │ ├── components │ │ │ ├── AppVersion.vue │ │ │ ├── BlackBackdrop.vue │ │ │ ├── CoordinateCopySlot.vue │ │ │ ├── DragDropOverlay.vue │ │ │ ├── DropdownButton.vue │ │ │ ├── DropdownButtonItem.vue │ │ │ ├── EmailInput.vue │ │ │ ├── ErrorWindow.vue │ │ │ ├── ExtLayerInfoButton.vue │ │ │ ├── FeedbackPopup.vue │ │ │ ├── FileInput.vue │ │ │ ├── LoadingBar.vue │ │ │ ├── ModalPrintWithBackdrop.vue │ │ │ ├── ModalWithBackdrop.vue │ │ │ ├── OpenFullAppLink.vue │ │ │ ├── PrintButton.vue │ │ │ ├── ProgressBar.vue │ │ │ ├── SimpleWindow.vue │ │ │ ├── TextAreaInput.vue │ │ │ ├── TextInput.vue │ │ │ ├── TextSearchMarker.vue │ │ │ ├── TextTruncate.vue │ │ │ ├── ThirdPartyDisclaimer.vue │ │ │ ├── WarningWindow.vue │ │ │ └── ZoomToExtentButton.vue │ │ ├── composables │ │ │ ├── useComponentUniqueId.js │ │ │ ├── useFieldValidation.js │ │ │ └── useMovableElement.composable.js │ │ ├── coordinates │ │ │ ├── __test__ │ │ │ │ └── coordinateExtractors.spec.js │ │ │ ├── coordinateExtractors.js │ │ │ └── coordinateFormat.js │ │ ├── debounce.ts │ │ ├── extentUtils.js │ │ ├── featureStyleUtils.js │ │ ├── geoJsonUtils.js │ │ ├── geodesicManager.js │ │ ├── gpxUtils.js │ │ ├── identifyOnVectorLayer.js │ │ ├── kmlUtils.js │ │ ├── layerUtils.js │ │ ├── legacyLayerParamUtils.js │ │ ├── militaryGridProjection.js │ │ ├── ol │ │ │ └── format │ │ │ │ └── KML.js │ │ ├── searchParamUtils.js │ │ ├── styleUtils.js │ │ ├── url-router.js │ │ └── utils.js │ └── views │ │ ├── EmbedView.vue │ │ ├── LegacyParamsView.vue │ │ ├── MapView.vue │ │ └── PrintView.vue │ ├── tailwind.config.js │ ├── tests │ ├── README.md │ ├── cypress │ │ ├── fixtures │ │ │ ├── 256.jpeg │ │ │ ├── 256.png │ │ │ ├── 3d │ │ │ │ ├── terrain-3d-layer.json │ │ │ │ ├── tile.terrain │ │ │ │ ├── tile.vctr │ │ │ │ └── tileset.json │ │ │ ├── catalogs.fixture.json │ │ │ ├── external-wms-getcap-1.fixture.xml │ │ │ ├── external-wms-getcap-2.fixture.xml │ │ │ ├── external-wmts-getcap-1.fixture.xml │ │ │ ├── external-wmts-getcap-2.fixture.xml │ │ │ ├── features │ │ │ │ ├── featureDetail.fixture.json │ │ │ │ └── features.fixture.json │ │ │ ├── geojson-style.fixture.json │ │ │ ├── geojson.fixture.json │ │ │ ├── html-popup-german.fixture.html │ │ │ ├── html-popup.fixture.html │ │ │ ├── import-tool │ │ │ │ ├── big-external-kml-file.kml │ │ │ │ ├── empty.kml │ │ │ │ ├── external-gpx-file-multi-segment.gpx │ │ │ │ ├── external-gpx-file-multi-separated-segment.gpx │ │ │ │ ├── external-gpx-file-out-of-bounds.gpx │ │ │ │ ├── external-gpx-file.gpx │ │ │ │ ├── external-kml-file.kml │ │ │ │ ├── iframe-test.kml │ │ │ │ ├── kml-multi-polygon.kml │ │ │ │ ├── kml_feature_error.kml │ │ │ │ ├── legend.png │ │ │ │ ├── line-accross-eu.kml │ │ │ │ ├── paris.kml │ │ │ │ ├── second-external-kml-file.kml │ │ │ │ ├── wms-geo-admin-get-capabilities.xml │ │ │ │ └── wmts-geo-admin-get-capabilities.xml │ │ │ ├── infobox.fixture.html │ │ │ ├── layers-german.fixture.json │ │ │ ├── layers.fixture.json │ │ │ ├── legend.fixture.html │ │ │ ├── post-kml.fixture.json │ │ │ ├── print │ │ │ │ ├── capabilities.json │ │ │ │ ├── label.kml │ │ │ │ ├── line-and-marker.gpx │ │ │ │ ├── mapfish-print-report.pdf │ │ │ │ └── old-geoadmin-label.kml │ │ │ ├── service-alti │ │ │ │ ├── height.fixture.json │ │ │ │ ├── profile.fixture.csv │ │ │ │ ├── profile.fixture.json │ │ │ │ └── profile.too.big.error.fixture.json │ │ │ ├── service-icons │ │ │ │ ├── placeholder.png │ │ │ │ ├── set-babs.fixture.json │ │ │ │ ├── set-default.fixture.json │ │ │ │ └── sets.fixture.json │ │ │ ├── service-kml │ │ │ │ ├── legacy-mf-geoadmin3.kml │ │ │ │ └── lonelyMarker.kml │ │ │ ├── service-qrcode │ │ │ │ └── position-popup.png │ │ │ ├── topics.fixture.json │ │ │ ├── what3word.fixture.json │ │ │ └── wms-geo-admin.png │ │ ├── support │ │ │ ├── commands.js │ │ │ ├── component-index.html │ │ │ ├── component.js │ │ │ ├── drawing.js │ │ │ ├── e2e.js │ │ │ ├── intercepts.js │ │ │ └── utils.js │ │ ├── tests-component │ │ │ ├── Inputs.cy.js │ │ │ ├── TextSearchMarker.cy.js │ │ │ └── TextTruncate.cy.js │ │ └── tests-e2e │ │ │ ├── 3d │ │ │ ├── click.cy.js │ │ │ ├── featureSelection.cy.js │ │ │ ├── geolocation.cy.js │ │ │ ├── layers.cy.js │ │ │ ├── navigation.cy.js │ │ │ └── transitionTo3d.cy.js │ │ │ ├── changeLanguage.cy.js │ │ │ ├── compareSlider.cy.js │ │ │ ├── crosshair.cy.js │ │ │ ├── drawing.cy.js │ │ │ ├── embed.cy.js │ │ │ ├── featureSelection.cy.js │ │ │ ├── feedback.cy.js │ │ │ ├── feedbackTestUtils.js │ │ │ ├── footer.cy.js │ │ │ ├── geodesicDrawing.cy.js │ │ │ ├── geolocation.cy.js │ │ │ ├── header.cy.js │ │ │ ├── importToolFile.cy.js │ │ │ ├── importToolMaps.cy.js │ │ │ ├── infobox.cy.js │ │ │ ├── layers.cy.js │ │ │ ├── legacyParamImport.cy.js │ │ │ ├── menuTray.cy.js │ │ │ ├── mouseposition.cy.js │ │ │ ├── print.cy.js │ │ │ ├── reportProblem.cy.js │ │ │ ├── ribbon.cy.js │ │ │ ├── search │ │ │ ├── coordinates-search.cy.js │ │ │ └── search-results.cy.js │ │ │ ├── shareShortLink.cy.js │ │ │ ├── timeCompareSlider.cy.js │ │ │ ├── timeSlider.cy.js │ │ │ ├── toolboxRight.cy.js │ │ │ ├── topics.cy.js │ │ │ └── utils.js │ ├── samples │ │ └── kml │ │ │ ├── alpen.kml │ │ │ ├── atomGeneratorGlacier.kml │ │ │ ├── externalContent.kml │ │ │ ├── legacy-chsdi3-icon-urls.kml │ │ │ └── print.kml │ └── setup-vitest.ts │ ├── tsconfig.json │ ├── vite-plugins │ ├── vite-plugin-generate-build-info.js │ └── vite-plugin-watch-node-modules.ts │ └── vite.config.mts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── stylelint.config.js ├── tsconfig.shared.json ├── vite.config.shared.js └── vitest.workspace.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | not IE 11 5 | Firefox ESR 6 | -------------------------------------------------------------------------------- /.github/workflows/add-testlink-to-pr.yml: -------------------------------------------------------------------------------- 1 | name: Create test link on PR creation 2 | on: 3 | pull_request: 4 | types: 5 | - opened 6 | - reopened 7 | - synchronize 8 | - edited 9 | jobs: 10 | update_pr: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Create/update test link on DEV 14 | if: ${{ github.base_ref != 'master' }} 15 | uses: tzkhan/pr-update-action@v2 16 | with: 17 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 18 | head-branch-regex: '.+' 19 | body-template: | 20 | 21 | [Test link](https://sys-map.dev.bgdi.ch/preview/%headbranch%/index.html) 22 | body-update-action: 'suffix' 23 | body-uppercase-head-match: false 24 | 25 | - name: Create/update deployment link on INT 26 | if: ${{ github.base_ref == 'master' }} 27 | uses: tzkhan/pr-update-action@v2 28 | with: 29 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 30 | head-branch-regex: '.+' 31 | body-template: | 32 | 33 | [Test link](https://sys-map.int.bgdi.ch/preview/%headbranch%/index.html) 34 | body-update-action: 'suffix' 35 | body-uppercase-head-match: false 36 | -------------------------------------------------------------------------------- /.github/workflows/pr-auto-semver.yml: -------------------------------------------------------------------------------- 1 | name: on-pr 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - reopened 8 | - synchronize 9 | - edited 10 | 11 | jobs: 12 | pr-edit: 13 | uses: geoadmin/.github/.github/workflows/pr-auto-semver.yml@master -------------------------------------------------------------------------------- /.github/workflows/semver.yml: -------------------------------------------------------------------------------- 1 | name: on-push 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - develop 8 | 9 | jobs: 10 | release: 11 | uses: geoadmin/.github/.github/workflows/semver-release.yml@master -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist/ 4 | devServer/ 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | *.sublime-* 24 | *.code-* 25 | 26 | # vite 27 | vite.config.js.timestamp-*.mjs 28 | 29 | # Cypress and friends 30 | **/tests/cypress/downloads 31 | **/tests/cypress/videos 32 | **/tests/cypress/screenshots 33 | **/tests/results/* 34 | 35 | # Git 36 | *.orig 37 | 38 | # package-lock.json since we use pnpm 39 | package-lock.json 40 | 41 | 42 | **/scripts/check-layer-providers-results/*invalid_providers_cors.yaml 43 | **/scripts/check-layer-providers-results/*invalid_providers.yaml 44 | **/scripts/check-layer-providers-results/*invalid_providers_wms.yaml 45 | **/scripts/check-layer-providers-results/*invalid_providers_wmts.yaml 46 | **/scripts/check-layer-providers-results/*invalid_providers_content.yaml 47 | **/scripts/check-layer-providers-results/*valid_providers.json 48 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | link-workspace-packages=true 3 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v22 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | pnpm-lock.yaml 2 | .github/ 3 | 4 | node_modules/ 5 | **/node_modules 6 | packages/**/node_modules/ 7 | 8 | packages/mapviewer/index.html 9 | packages/mapviewer/public/icon.svg 10 | packages/mapviewer/src/assets/svg/swiss-flag.svg 11 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "semi": false, 5 | "trailingComma": "es5", 6 | "tabWidth": 4, 7 | "jsxSingleQuote": false, 8 | "plugins": [ 9 | "prettier-plugin-jsdoc", 10 | "@prettier/plugin-xml", 11 | "prettier-plugin-packagejson", 12 | "prettier-plugin-tailwindcss" 13 | ], 14 | "overrides": [ 15 | { 16 | "files": "*.md", 17 | "options": { 18 | "tabWidth": 2 19 | } 20 | } 21 | ], 22 | "singleAttributePerLine": true 23 | } 24 | -------------------------------------------------------------------------------- /adr/2020_04_06_javascript_framework_choice.md: -------------------------------------------------------------------------------- 1 | # Javascript Framework Choice 2 | 3 | Please refer to 4 | -------------------------------------------------------------------------------- /adr/2021_04_14_drawing_library.md: -------------------------------------------------------------------------------- 1 | # Drawing library 2 | 3 | > Status: accepted 4 | 5 | > Date: 14.04.2021 6 | 7 | ## Context 8 | 9 | The application will support drawing, as its predecessor. The question is, can we achieve an approach that doesn't rely on (or isn't intertwined with) the mapping framework. 10 | 11 | The goal is also to pave the way to enable users to draw with some snapping help (on roads, or other geometries) 12 | 13 | ### Potential paths prospected 14 | 15 | - No framework, implement a minimalist drawing library with only Javascript 16 | - Use a library, such as PaperJS, to handle the drawing. Translate the output into geographic geometries. 17 | - Use OpenLayers as a drawing tool (as in the viewer `mf-geoadmin3`) 18 | 19 | ## Decision 20 | 21 | After looking into all paths above, decision has been made to go with the OpenLayers approach. Here's reasons why : 22 | 23 | Pure Javascript approach could fit neatly in the current technology stack, but will require a lot of investment to achieve any kind of snapping 24 | 25 | PaperJS has a nice toolbox for drawing, but will also require some work in order to support snapping. So the balance benefice (good drawing tools) vs. work needed isn't positive. 26 | 27 | This leaves us with the OpenLayers approach, that has already proven itself on the viewer `mf-geoadmin3`. We know that snapping is a possibility, as there's some snapping (only on the current drawing though). 28 | 29 | ## Consequences 30 | 31 | Development of the drawing module can now start with OpenLayers as its backbone. 32 | 33 | In order to make it clear it is a different module as the map module, we will inject the curren/component-provide-inject.html) 34 | -------------------------------------------------------------------------------- /adr/2022_04_11_vuex_store_consolidation.md: -------------------------------------------------------------------------------- 1 | # Vuex store consolidation 2 | 3 | > Status: accepted 4 | 5 | > Date: 11.04.2022 6 | 7 | ## Context 8 | 9 | The project started with a fragemented store. The main store was itself treated like the modules that contain the components. Some of those in turn contained store modules for their state. 10 | 11 | The idea was to encapsulate each part of the application as much as possible to allow for a later externalization. But, with an intertwined application like a map viewer where much of the state is shared anyway this form of modularization doesn't bring much benefit while making it harder for a developer to find something in the store. 12 | 13 | Consequently, most store modules are already shared and only a few remain distributed among the components. 14 | 15 | ## Decision 16 | 17 | To make it easier to work with the store we decided to consolidate the store on the top-level of the `src` directory. 18 | 19 | ## Consequences 20 | 21 | With this change all the state is in one place which makes it much easier to navigate the store. 22 | 23 | Having all store modules in one directory encourages to split the store according to the content and not the consuming components. But, it allows for both. 24 | 25 | A consolidated store makes it harder to externalize modules later but this is mostly perception as the code was already interdependant before the structural change. 26 | -------------------------------------------------------------------------------- /adr/2022_05_09_mobile_click_behavior.md: -------------------------------------------------------------------------------- 1 | # Mobile click behavior 2 | 3 | > Status: accepted 4 | 5 | > Date: 06.07.2022 6 | 7 | ## Context 8 | 9 | Currently, a short click will switch the app in fullscreen mode. A long click (with a release of the click) will run an identify (like a click on desktop) 10 | 11 | The issue is that there is no location popup possibilities with that (or at least it's unwanted if it pops up after a long click). We need to rework that. 12 | 13 | ## Decision 14 | 15 | - Mobile layout (menu must be opened through header button) 16 | - Single touch (click) : identify, if no feature found, fullscreen toggle 17 | - Long touch : location popup 18 | - Desktop layout (menu always open) 19 | - Single click : identify (no fullscreen toggle) 20 | - Long click : nothing 21 | - Right click : location popup 22 | 23 | ## Consequences 24 | 25 | This would allow mobile users to use all the features we provide (identify, location) while keeping the fullscreen possibility. 26 | 27 | ## Links 28 | 29 | - [JIRA ticket this ADR is based on](https://jira.swisstopo.ch/browse/BGDIINF_SB-2235) 30 | - [JIRA ticket where this became a problem](https://jira.swisstopo.ch/browse/BGDIINF_SB-2323) 31 | -------------------------------------------------------------------------------- /adr/2023_11_08_vue_composition_api.md: -------------------------------------------------------------------------------- 1 | # Vue Composition API 2 | 3 | > Status: accepted 4 | 5 | > Date: 08.11.2023 6 | 7 | ## Context 8 | 9 | Vue3 is already our Vue version for a while, but we haven't yet utilized all its potential. One of them is to use the Composition API, which is a more succinct way of describing components (less boilerplate code). 10 | Composition API should help us, in many cases, to build more reusable portions of code (replacing the mixins for instance, which is a quite confusing concept) 11 | 12 | This, in return, gives new challenges as it doesn't force us to structure our component in the same way (props, computed, etc...) through the linter. 13 | 14 | ## Decision 15 | 16 | We will now be using the Composition API for new components, and transform Option API components into Composition API when extensive work is required on them 17 | 18 | ## Consequences 19 | 20 | - New components should be written with the Composition API 21 | - ` 29 | 30 | 31 | -------------------------------------------------------------------------------- /packages/geoadmin-elevation-profile/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/geoadmin-elevation-profile/public/favicon.ico -------------------------------------------------------------------------------- /packages/geoadmin-elevation-profile/src/DevApp.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | -------------------------------------------------------------------------------- /packages/geoadmin-elevation-profile/src/__test__/profile.api.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { describe, it } from 'vitest' 3 | 4 | import { splitIfTooManyPoints } from '@/profile.api' 5 | 6 | describe('splitIfTooManyPoints', () => { 7 | /** 8 | * @param {Number} pointsCount 9 | * @returns {CoordinatesChunk} 10 | */ 11 | function generateChunkWith(pointsCount) { 12 | const coordinates = [] 13 | for (let i = 0; i < pointsCount; i++) { 14 | coordinates.push([0, i]) 15 | } 16 | return { 17 | coordinates, 18 | isWithinBounds: true, 19 | } 20 | } 21 | 22 | it('does not split a segment that does not contain more point than the limit', () => { 23 | const result = splitIfTooManyPoints(generateChunkWith(3000)) 24 | expect(result).to.be.an('Array').lengthOf(1) 25 | expect(result[0].coordinates).to.be.an('Array').lengthOf(3000) 26 | }) 27 | it('splits if one coordinates above the limit', () => { 28 | const result = splitIfTooManyPoints(generateChunkWith(3001)) 29 | expect(result).to.be.an('Array').lengthOf(2) 30 | expect(result[0].coordinates).to.be.an('Array').lengthOf(3000) 31 | expect(result[1].coordinates).to.be.an('Array').lengthOf(1) 32 | }) 33 | it('creates as many sub-chunks as necessary', () => { 34 | const result = splitIfTooManyPoints(generateChunkWith(3000 * 4 + 123)) 35 | expect(result).to.be.an('Array').lengthOf(5) 36 | for (let i = 0; i < 4; i++) { 37 | expect(result[i].coordinates).to.be.an('Array').lengthOf(3000) 38 | } 39 | expect(result[4].coordinates).to.be.an('Array').lengthOf(123) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /packages/geoadmin-elevation-profile/src/chartjs-plugins/datamodel.plugin.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from 'chart.js' 2 | 3 | export interface DataModelPluginOptions { 4 | /** 5 | * Name of the data model that will be written in the top right corner of the chart. Defaults to 6 | * 'swissALTI3D/DHM25' 7 | */ 8 | dataModelName?: string 9 | } 10 | 11 | /** 12 | * Small ChartJS plugin that will add the data model used by the chart in the top right corner of 13 | * that chart if the chart is wide enough. 14 | * 15 | * It will not add it if the text for the data model exceeds 33% of the available space 16 | */ 17 | const dataModelPlugin: Plugin = { 18 | id: 'dataModel', 19 | afterDraw(chart, _, pluginOptions: DataModelPluginOptions) { 20 | const dataModelName: string = pluginOptions.dataModelName ?? 'swissALTI3D/DHM25' 21 | const { ctx, chartArea } = chart 22 | const { top = 0, width = 0, right = 0 } = chartArea ?? {} 23 | 24 | ctx.save() 25 | ctx.font = 'normal 700 12px Unknown, sans-serif' 26 | // Checking if there is enough space to write the text. 27 | // We do not show it if it takes more than a third of the width of the chart (i.e., on mobile) 28 | if (ctx.measureText(dataModelName).width <= width / 3.0) { 29 | ctx.textAlign = 'right' 30 | ctx.fillStyle = '#000' 31 | ctx.strokeStyle = '#fff' 32 | ctx.lineWidth = 1 33 | ctx.strokeText(dataModelName, right - 5, top + 15) 34 | ctx.fillText(dataModelName, right - 5, top + 15) 35 | } 36 | ctx.restore() 37 | }, 38 | } 39 | 40 | export default dataModelPlugin 41 | -------------------------------------------------------------------------------- /packages/geoadmin-elevation-profile/src/chartjs-plugins/plugins.d.ts: -------------------------------------------------------------------------------- 1 | import { ChartType } from 'chart.js' 2 | 3 | import type { DataModelPluginOptions } from '@/chartjs-plugins/datamodel.plugin' 4 | import type { NoDataPluginOptions } from '@/chartjs-plugins/nodata.plugin' 5 | 6 | declare module 'chart.js' { 7 | interface PluginOptionsByType { 8 | noData?: NoDataPluginOptions 9 | dataModel?: DataModelPluginOptions 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/geoadmin-elevation-profile/src/config.ts: -------------------------------------------------------------------------------- 1 | export const BASE_URL_PROD = 'https://api3.geo.admin.ch/' 2 | export const BASE_URL_INT = 'https://sys-api3.int.bgdi.ch/' 3 | export const BASE_URL_DEV = 'https://sys-api3.dev.bgdi.ch/' 4 | 5 | export type SupportedLocales = 'en' | 'de' | 'fr' | 'it' | 'rm' 6 | // mimicing values from https://github.com/geoadmin/web-mapviewer/blob/36043456b820b03f380804a63e2cac1a8a1850bc/packages/mapviewer/src/config/staging.config.js#L1-L7 7 | export type Staging = 'development' | 'integration' | 'production' 8 | 9 | export const BORDER_COLOR = 'rgb(255, 99, 132)' 10 | export const FILL_COLOR = 'rgba(255, 99, 132, 0.7)' 11 | 12 | /** 13 | * 12.5 meters is what was used in the old viewer, see 14 | * https://github.com/geoadmin/mf-geoadmin3/blob/ce24a27b0ca8192a0f78f7b8cc07f4e231031304/src/components/GeomUtilsService.js#L207 15 | * 16 | * I tried lowering the value, but with the test GPX that were attached to the ticket PB-800, I get 17 | * the worst hiking time estimation when using something like 5m than if I use this 12.5 meters. 18 | */ 19 | export const GEOMETRY_SIMPLIFICATION_TOLERANCE = 12.5 // meters 20 | -------------------------------------------------------------------------------- /packages/geoadmin-elevation-profile/src/dev.ts: -------------------------------------------------------------------------------- 1 | import '@/index.ts' 2 | 3 | import { createApp } from 'vue' 4 | import { createI18n } from 'vue-i18n' 5 | 6 | import DevApp from '@/DevApp.vue' 7 | 8 | const i18n = createI18n({ 9 | legacy: false, 10 | locale: 'en', 11 | messages: {}, 12 | missingWarn: false, 13 | }) 14 | 15 | const app = createApp(DevApp) 16 | app.use(i18n) 17 | app.mount('#app') 18 | -------------------------------------------------------------------------------- /packages/geoadmin-elevation-profile/src/style.css: -------------------------------------------------------------------------------- 1 | /* using important to ease cohabitation with Bootstrap, can be removed it once we've migrated away from Bootstrap */ 2 | @import 'tailwindcss' prefix(tw) important; 3 | 4 | @import '@geoadmin/tooltip/tailwindcss'; 5 | -------------------------------------------------------------------------------- /packages/geoadmin-elevation-profile/src/vue-i18n.d.ts: -------------------------------------------------------------------------------- 1 | import type { RemoveIndexSignature } from '@intlify/core-base' 2 | import type { ComposerTranslation } from 'vue-i18n' 3 | 4 | import type { SupportedLocales } from '@/config' 5 | 6 | export declare type VueI18nTranslateFunction = ComposerTranslation< 7 | { [x: string]: Message }, 8 | SupportedLocales, 9 | RemoveIndexSignature<{ 10 | [x: string]: Message 11 | }> 12 | > 13 | -------------------------------------------------------------------------------- /packages/geoadmin-elevation-profile/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./src/**/*.{vue,js,ts,jsx,tsx}", 5 | ], 6 | theme: { 7 | extend: {}, 8 | }, 9 | plugins: [], 10 | } 11 | -------------------------------------------------------------------------------- /packages/geoadmin-elevation-profile/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.shared.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/geoadmin-elevation-profile/vite.config.js: -------------------------------------------------------------------------------- 1 | import tailwindcss from '@tailwindcss/vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import { resolve } from 'path' 4 | import { fileURLToPath, URL } from 'url' 5 | import dts from 'vite-plugin-dts' 6 | import vueDevTools from 'vite-plugin-vue-devtools' 7 | 8 | 9 | export default { 10 | build: { 11 | lib: { 12 | entry: [resolve(__dirname, 'src/index.ts')], 13 | name: '@geoadmin/elevation-profile', 14 | }, 15 | rollupOptions: { 16 | external: ['vue', 'tailwindcss'], 17 | output: { 18 | exports: 'named', 19 | globals: { 20 | vue: 'Vue', 21 | } 22 | }, 23 | }, 24 | }, 25 | resolve: { 26 | alias: { 27 | '@': fileURLToPath(new URL('./src', import.meta.url)), 28 | }, 29 | }, 30 | plugins: [ 31 | tailwindcss(), 32 | vue(), 33 | vueDevTools(), 34 | dts(), 35 | ], 36 | } 37 | -------------------------------------------------------------------------------- /packages/geoadmin-log/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@geoadmin/log", 3 | "version": "0.0.1", 4 | "description": "Logging utils for geoadmin", 5 | "type": "module", 6 | "exports": { 7 | ".": { 8 | "types": "./dist/index.d.ts", 9 | "import": "./dist/index.js", 10 | "require": "./dist/index.umd.cjs" 11 | }, 12 | "./Message": { 13 | "types": "./dist/Message.d.ts", 14 | "import": "./dist/Message.js", 15 | "require": "./dist/Message.umd.js" 16 | } 17 | }, 18 | "files": [ 19 | "dist" 20 | ], 21 | "scripts": { 22 | "build": "pnpm run type-check && pnpm run generate-types && vite build", 23 | "build:dev": "pnpm run build -- --mode development", 24 | "build:dev:watch": "pnpm run build --watch -- --mode development", 25 | "build:int": "pnpm run build -- --mode integration", 26 | "build:prod": "pnpm run build -- --mode production", 27 | "dev": "vite", 28 | "generate-types": "vue-tsc --declaration", 29 | "type-check": "vue-tsc -p tsconfig.json" 30 | }, 31 | "peerDependencies": { 32 | "proj4": "catalog:" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/geoadmin-log/src/Message.ts: -------------------------------------------------------------------------------- 1 | /** @module geoadmin/log */ 2 | 3 | export class Message { 4 | msg: string 5 | params: Record 6 | 7 | /** 8 | * @param {string} msg Translation key message 9 | * @param {any} params Translation params to pass to i18n (used for message formatting) 10 | */ 11 | constructor(msg: string, params: Record | null = null) { 12 | this.msg = msg 13 | this.params = params ?? {} 14 | } 15 | 16 | isEquals(object: Message) { 17 | return ( 18 | object instanceof Message && 19 | object.msg === this.msg && 20 | Object.keys(this.params).length === Object.keys(object.params).length && 21 | Object.keys(this.params).every((key) => this.params[key] === object.params[key]) 22 | ) 23 | } 24 | } 25 | 26 | export class ErrorMessage extends Message {} 27 | export class WarningMessage extends Message {} 28 | -------------------------------------------------------------------------------- /packages/geoadmin-log/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.shared.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/geoadmin-log/vite.config.js: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import dts from 'vite-plugin-dts' 3 | 4 | export default { 5 | build: { 6 | lib: { 7 | entry: { 8 | index: resolve(__dirname, 'src/index.ts'), 9 | Message: resolve(__dirname, 'src/Message.ts'), 10 | }, 11 | name: '@geoadmin/log', 12 | }, 13 | rollupOptions: { 14 | output: { 15 | exports: 'named', 16 | }, 17 | }, 18 | }, 19 | plugins: [ 20 | dts({ 21 | outDir: 'dist', 22 | }), 23 | ], 24 | } 25 | -------------------------------------------------------------------------------- /packages/geoadmin-numbers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@geoadmin/numbers", 3 | "version": "0.0.1", 4 | "description": "Numbers utils for geoadmin", 5 | "type": "module", 6 | "exports": { 7 | ".": { 8 | "types": "./dist/index.d.ts", 9 | "import": "./dist/index.js", 10 | "require": "./dist/index.umd.cjs" 11 | } 12 | }, 13 | "files": [ 14 | "dist" 15 | ], 16 | "scripts": { 17 | "build": "pnpm run type-check && pnpm run generate-types && vite build", 18 | "build:dev": "pnpm run build -- --mode development", 19 | "build:dev:watch": "pnpm run build --watch -- --mode development", 20 | "build:int": "pnpm run build -- --mode integration", 21 | "build:prod": "pnpm run build -- --mode production", 22 | "dev": "vite", 23 | "generate-types": "vue-tsc --declaration", 24 | "test:unit": "vitest --run --mode development --environment jsdom", 25 | "test:unit:watch": "vitest --mode development --environment jsdom", 26 | "type-check": "vue-tsc -p tsconfig.json" 27 | }, 28 | "dependencies": { 29 | "@geoadmin/log": "workspace:*" 30 | }, 31 | "devDependencies": { 32 | "chai": "catalog:", 33 | "vite": "catalog:", 34 | "vite-plugin-dts": "catalog:", 35 | "vitest": "catalog:", 36 | "vue-tsc": "catalog:" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/geoadmin-numbers/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.shared.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/geoadmin-numbers/vite.config.js: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import { fileURLToPath, URL } from 'url' 3 | import dts from 'vite-plugin-dts' 4 | 5 | export default { 6 | build: { 7 | lib: { 8 | entry: [resolve(__dirname, 'src/index.ts')], 9 | name: '@geoadmin/utils', 10 | }, 11 | rollupOptions: { 12 | output: { 13 | exports: 'named', 14 | }, 15 | }, 16 | }, 17 | resolve: { 18 | alias: { 19 | '@': fileURLToPath(new URL('./src', import.meta.url)), 20 | }, 21 | }, 22 | plugins: [ 23 | dts({ 24 | outDir: 'dist', 25 | }), 26 | ], 27 | } 28 | -------------------------------------------------------------------------------- /packages/geoadmin-tooltip/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/geoadmin-tooltip/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 13 | Test page for geoadmin's tooltip component 14 | 22 | 23 | 24 |
25 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /packages/geoadmin-tooltip/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@geoadmin/tooltip", 3 | "version": "0.0.1", 4 | "description": "Tooltip for geoadmin", 5 | "type": "module", 6 | "exports": { 7 | ".": { 8 | "types": "./dist/index.d.ts", 9 | "import": "./dist/index.js", 10 | "require": "./dist/index.umd.cjs" 11 | }, 12 | "./tailwindcss": [ 13 | "./dist/tooltip.css" 14 | ] 15 | }, 16 | "main": "./dist/index.umd.cjs", 17 | "module": "./dist/index.js", 18 | "files": [ 19 | "dist" 20 | ], 21 | "scripts": { 22 | "build": "pnpm run type-check && pnpm run generate-types && vite build", 23 | "build:dev": "pnpm run build -- --mode development", 24 | "build:dev:watch": "pnpm run build --watch -- --mode development", 25 | "build:int": "pnpm run build -- --mode integration", 26 | "build:prod": "pnpm run build -- --mode production", 27 | "dev": "vite --host", 28 | "generate-types": "vue-tsc --declaration", 29 | "preview": "vite preview", 30 | "type-check": "vue-tsc --build" 31 | }, 32 | "dependencies": { 33 | "@floating-ui/vue": "catalog:" 34 | }, 35 | "devDependencies": { 36 | "@tailwindcss/vite": "catalog:", 37 | "@tsconfig/node22": "catalog:", 38 | "@types/jsdom": "catalog:", 39 | "@vitejs/plugin-vue": "catalog:", 40 | "@vue/tsconfig": "catalog:", 41 | "tailwindcss": "catalog:", 42 | "vite": "catalog:", 43 | "vite-plugin-vue-devtools": "catalog:", 44 | "vitest": "catalog:", 45 | "vue-tsc": "catalog:" 46 | }, 47 | "peerDependencies": { 48 | "vue": "catalog:" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/geoadmin-tooltip/src/dev.ts: -------------------------------------------------------------------------------- 1 | import '@/index.ts' 2 | 3 | import { createApp } from 'vue' 4 | 5 | import DevApp from '@/DevApp.vue' 6 | 7 | const app = createApp(DevApp) 8 | app.mount('#app') 9 | -------------------------------------------------------------------------------- /packages/geoadmin-tooltip/src/index.ts: -------------------------------------------------------------------------------- 1 | import GeoadminTooltip from '@/GeoadminTooltip.vue' 2 | import '@/style.css' 3 | 4 | export default GeoadminTooltip 5 | -------------------------------------------------------------------------------- /packages/geoadmin-tooltip/src/style.css: -------------------------------------------------------------------------------- 1 | /* using important to ease cohabitation with Bootstrap, can be removed it once we've migrated away from Bootstrap */ 2 | @import 'tailwindcss' prefix(tw) important; 3 | -------------------------------------------------------------------------------- /packages/geoadmin-tooltip/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./src/**/*.{vue,js,ts,jsx,tsx}", 5 | ], 6 | theme: { 7 | extend: {}, 8 | }, 9 | plugins: [], 10 | // to ease cohabitation with Bootstrap, can be removed once we've migrated away from Bootstrap 11 | important: true, 12 | } 13 | -------------------------------------------------------------------------------- /packages/geoadmin-tooltip/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.shared.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/geoadmin-tooltip/vite.config.js: -------------------------------------------------------------------------------- 1 | import tailwindcss from '@tailwindcss/vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import { resolve } from 'path' 4 | import { fileURLToPath, URL } from 'url' 5 | import dts from 'vite-plugin-dts' 6 | import vueDevTools from 'vite-plugin-vue-devtools' 7 | 8 | 9 | export default { 10 | build: { 11 | lib: { 12 | entry: [resolve(__dirname, 'src/index.ts')], 13 | name: '@geoadmin/tooltip', 14 | }, 15 | rollupOptions: { 16 | external: ['vue', 'tailwindcss'], 17 | output: { 18 | exports: 'named', 19 | globals: { 20 | vue: 'Vue', 21 | } 22 | }, 23 | }, 24 | }, 25 | resolve: { 26 | alias: { 27 | '@': fileURLToPath(new URL('./src', import.meta.url)), 28 | }, 29 | }, 30 | plugins: [ 31 | tailwindcss(), 32 | vue(), 33 | vueDevTools(), 34 | dts(), 35 | ], 36 | } 37 | -------------------------------------------------------------------------------- /packages/mapviewer/.env.development: -------------------------------------------------------------------------------- 1 | # local and development env 2 | VITE_API_BASE_URL=https://sys-api3.dev.bgdi.ch/ 3 | VITE_API_SERVICE_ALTI_BASE_URL=https://sys-api3.dev.bgdi.ch/ 4 | VITE_API_SERVICE_SEARCH_BASE_URL=https://sys-api3.dev.bgdi.ch/ 5 | VITE_DATA_BASE_URL=https://data.geo.admin.ch/ 6 | VITE_WMTS_BASE_URL=https://sys-wmts.dev.bgdi.ch/ 7 | VITE_WMS_BASE_URL=https://sys-wms.dev.bgdi.ch/ 8 | VITE_API_SERVICES_BASE_URL=https://sys-map.dev.bgdi.ch/api/ 9 | VITE_API_SERVICE_KML_BASE_URL=https://sys-public.dev.bgdi.ch/ 10 | VITE_APP_API_SERVICE_SHORTLINK_BASE_URL=https://sys-s.dev.bgdi.ch/ 11 | VITE_APP_3D_TILES_BASE_URL=https://sys-3d.dev.bgdi.ch/ 12 | VITE_APP_VECTORTILES_BASE_URL=https://sys-verctortiles.dev.bgdi.ch/ 13 | VITE_APP_SERVICE_PROXY_BASE_URL=https://sys-proxy.dev.bgdi.ch/ 14 | -------------------------------------------------------------------------------- /packages/mapviewer/.env.integration: -------------------------------------------------------------------------------- 1 | VITE_API_BASE_URL=https://sys-api3.int.bgdi.ch/ 2 | VITE_API_SERVICE_ALTI_BASE_URL=https://sys-api3.int.bgdi.ch/ 3 | VITE_API_SERVICE_SEARCH_BASE_URL=https://sys-api3.int.bgdi.ch/ 4 | VITE_DATA_BASE_URL=https://data.geo.admin.ch/ 5 | VITE_WMTS_BASE_URL=https://sys-wmts.int.bgdi.ch/ 6 | VITE_WMS_BASE_URL=https://sys-wms.int.bgdi.ch/ 7 | VITE_API_SERVICES_BASE_URL=https://sys-map.int.bgdi.ch/api/ 8 | VITE_API_SERVICE_KML_BASE_URL=https://sys-public.int.bgdi.ch/ 9 | VITE_APP_API_SERVICE_SHORTLINK_BASE_URL=https://sys-s.int.bgdi.ch/ 10 | VITE_APP_3D_TILES_BASE_URL=https://sys-3d.int.bgdi.ch/ 11 | VITE_APP_VECTORTILES_BASE_URL=https://sys-verctortiles.int.bgdi.ch/ 12 | VITE_APP_SERVICE_PROXY_BASE_URL=https://sys-proxy.int.bgdi.ch/ 13 | -------------------------------------------------------------------------------- /packages/mapviewer/.env.production: -------------------------------------------------------------------------------- 1 | VITE_API_BASE_URL=https://api3.geo.admin.ch/ 2 | VITE_API_SERVICE_ALTI_BASE_URL=https://api3.geo.admin.ch/ 3 | VITE_API_SERVICE_SEARCH_BASE_URL=https://api3.geo.admin.ch/ 4 | VITE_DATA_BASE_URL=https://data.geo.admin.ch/ 5 | VITE_WMTS_BASE_URL=https://wmts.geo.admin.ch/ 6 | VITE_WMS_BASE_URL=https://wms.geo.admin.ch/ 7 | VITE_API_SERVICES_BASE_URL=https://map.geo.admin.ch/api/ 8 | VITE_API_SERVICE_KML_BASE_URL=https://public.geo.admin.ch/ 9 | VITE_APP_API_SERVICE_SHORTLINK_BASE_URL=https://s.geo.admin.ch/ 10 | VITE_APP_3D_TILES_BASE_URL=https://3d.geo.admin.ch/ 11 | VITE_APP_VECTORTILES_BASE_URL=https://vectortiles.geo.admin.ch/ 12 | VITE_APP_SERVICE_PROXY_BASE_URL=https://proxy.geo.admin.ch/ 13 | -------------------------------------------------------------------------------- /packages/mapviewer/.env.test: -------------------------------------------------------------------------------- 1 | VITE_API_BASE_URL=https://sys-api3.dev.bgdi.ch/ 2 | VITE_API_SERVICE_ALTI_BASE_URL=https://sys-api3.dev.bgdi.ch/ 3 | VITE_API_SERVICE_SEARCH_BASE_URL=https://sys-api3.dev.bgdi.ch/ 4 | VITE_DATA_BASE_URL=https://data.geo.admin.ch/ 5 | VITE_WMTS_BASE_URL=https://sys-wmts.dev.bgdi.ch/ 6 | VITE_WMS_BASE_URL=https://sys-wms.dev.bgdi.ch/ 7 | VITE_API_SERVICES_BASE_URL=https://sys-map.dev.bgdi.ch/api/ 8 | VITE_API_SERVICE_KML_BASE_URL=https://sys-public.dev.bgdi.ch/ 9 | VITE_APP_API_SERVICE_SHORTLINK_BASE_URL=https://sys-s.dev.bgdi.ch/ 10 | VITE_APP_3D_TILES_BASE_URL=https://sys-3d.dev.bgdi.ch/ 11 | VITE_APP_VECTORTILES_BASE_URL=https://sys-verctortiles.dev.bgdi.ch/ 12 | VITE_APP_SERVICE_PROXY_BASE_URL=https://sys-proxy.dev.bgdi.ch/ 13 | -------------------------------------------------------------------------------- /packages/mapviewer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Maps of Switzerland - Swiss Confederation - map.geo.admin.ch 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/mapviewer/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/public/apple-touch-icon.png -------------------------------------------------------------------------------- /packages/mapviewer/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/public/favicon.ico -------------------------------------------------------------------------------- /packages/mapviewer/public/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/public/icon-192.png -------------------------------------------------------------------------------- /packages/mapviewer/public/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/public/icon-512.png -------------------------------------------------------------------------------- /packages/mapviewer/public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | Coat of Arms of Switzerland 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/mapviewer/public/manifest.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/web-manifest-combined.json", 3 | "name": "GeoAdmin", 4 | "short_name": "GeoAdmin", 5 | "start_url": ".", 6 | "display": "standalone", 7 | "background_color": "#fff", 8 | "description": "Maps of Switzerland - Swiss Confederation - map.geo.admin.ch", 9 | "icons": [ 10 | { "src": "./icon-192.png", "type": "image/png", "sizes": "192x192" }, 11 | { "src": "./icon-512.png", "type": "image/png", "sizes": "512x512" } 12 | ], 13 | "related_applications": [ 14 | { 15 | "platform": "play", 16 | "url": "https://play.google.com/store/apps/details?id=ch.admin.swisstopo" 17 | }, 18 | { "platform": "itunes", "url": "https://apps.apple.com/us/app/swisstopo/id1505986543" } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /packages/mapviewer/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /packages/mapviewer/secrets.yml: -------------------------------------------------------------------------------- 1 | # required in order to access the translation spreadsheet (while running `npm run update:translations`) 2 | GOOGLE_API_KEY: !var /google.com/kogis.iwi/tokens/web-mapviewer --profile swisstopo-bgdi-builder 3 | CYPRESS_RECORD_KEY: !var /cypress.io/ppbgdi/web-mapviewer/record-key --profile swisstopo-bgdi-builder 4 | -------------------------------------------------------------------------------- /packages/mapviewer/src/api/__tests__/file-proxy.api.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { describe, it } from 'vitest' 3 | 4 | import { transformFileUrl } from '@/api/file-proxy.api.js' 5 | 6 | describe('Serice-proxy tests', () => { 7 | describe('transformFileUrl', () => { 8 | it('returns null when the input is invalid', () => { 9 | expect(transformFileUrl(null)).to.be.null 10 | expect(transformFileUrl(undefined)).to.be.null 11 | expect(transformFileUrl(123)).to.be.null 12 | expect(transformFileUrl({})).to.be.null 13 | expect(transformFileUrl([])).to.be.null 14 | }) 15 | it('returns the URL transformed', () => { 16 | expect(transformFileUrl('http://some-file.kml?one=1&foo=bar')).to.eq( 17 | `http/some-file.kml${encodeURIComponent('?one=1&foo=bar')}` 18 | ) 19 | }) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /packages/mapviewer/src/api/__tests__/search.api.spec.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai' 2 | import { describe, it } from 'vitest' 3 | 4 | import { sanitizeTitle } from '@/api/search.api' 5 | 6 | describe('Builds object by extracting all relevant attributes from the backend', () => { 7 | describe('FeatureSearchResult.getSimpleTitle', () => { 8 | it('Returns title removing HTML', () => { 9 | const expectedResult = 'Some irrelevant stuff 123 Test' 10 | const expectedResultWrappedInHtml = 'Some irrelevant stuff 123 Test' 11 | expect(sanitizeTitle(expectedResultWrappedInHtml)).to.eq(expectedResult) 12 | }) 13 | 14 | it('Returns title as is if no HTML is present', () => { 15 | const expectedResult = 'Test 123 Test' 16 | expect(sanitizeTitle(expectedResult)).to.eq(expectedResult) 17 | }) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /packages/mapviewer/src/api/errorQueues.api.js: -------------------------------------------------------------------------------- 1 | import { ErrorMessage } from '@geoadmin/log/Message' 2 | 3 | export function getStandardErrorMessage(query, urlParamName) { 4 | return new ErrorMessage('url_parameter_error', { 5 | param: urlParamName, 6 | value: query, 7 | }) 8 | } 9 | 10 | /** 11 | * Return the standard feedback for most parameters given in the URL: if the query is validated, it 12 | * can proceed and be set in the store. 13 | * 14 | * @param {any} query The value of the URL parameter given 15 | * @param {Boolean} isValid Is the value valid or not 16 | * @returns 17 | */ 18 | export function getStandardValidationResponse(query, isValid, urlParamName) { 19 | return { 20 | valid: isValid, 21 | errors: isValid ? null : [getStandardErrorMessage(query, urlParamName)], 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/mapviewer/src/api/features/LayerFeature.class.js: -------------------------------------------------------------------------------- 1 | import SelectableFeature from '@/api/features/SelectableFeature.class' 2 | import LayerTypes from '@/api/layers/LayerTypes.enum.js' 3 | 4 | /** Describe a feature from the backend, so a feature linked to a backend layer. */ 5 | export default class LayerFeature extends SelectableFeature { 6 | /** 7 | * @param {AbstractLayer} featureData.layer The layer in which this feature belongs 8 | * @param {Number | String} featureData.id The unique feature ID in the layer it is part of 9 | * @param {String} featureData.title The title (localized) of this feature 10 | * @param {Object | String} featureData.data Data for this feature's popup (or tooltip). 11 | * @param {[[Number, Number]]} featureData.coordinates Coordinate in the current projection 12 | * ([[x,y],[x2,y2],...]) 13 | * @param {[Number, Number, Number, Number]} featureData.extent Extent of the feature expressed 14 | * with two point, bottom left and top right 15 | * @param {Object | null} [featureData.geometry=null] GeoJSON geometry (if exists). Default is 16 | * `null` 17 | */ 18 | constructor(featureData) { 19 | const { layer, id, title, data, coordinates, extent, geometry = null } = featureData 20 | super({ 21 | id, 22 | coordinates, 23 | title, 24 | extent, 25 | geometry, 26 | isEditable: false, 27 | }) 28 | this.layer = layer 29 | this.data = data 30 | // We can't trust the content of the popup data for external layers, and for KML layers. 31 | // For KML, the issue is that user can create text-rich (HTML) description with links, and such. 32 | // It would then be possible to do some XSS through this, so we need to sanitize this before showing it. 33 | this.popupDataCanBeTrusted = !this.layer.isExternal && this.layer.type !== LayerTypes.KML 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/mapviewer/src/api/layers/GeoAdminVectorLayer.class.js: -------------------------------------------------------------------------------- 1 | import { LayerAttribution } from '@/api/layers/AbstractLayer.class' 2 | import GeoAdminLayer from '@/api/layers/GeoAdminLayer.class' 3 | import LayerTypes from '@/api/layers/LayerTypes.enum' 4 | import { getVectorTilesBaseUrl } from '@/config/baseUrl.config' 5 | 6 | /** 7 | * Metadata for a vector tile layer (MapLibre layer) served by our backend 8 | * 9 | * @WARNING DON'T USE GETTER AND SETTER ! Instances of this class will be used a Vue 3 reactive 10 | * object which SHOULD BE plain javascript object ! For convenience we use class instances but this 11 | * has some limitations and javascript class getter and setter are not correctly supported which 12 | * introduced subtle bugs. As rule of thumb we should avoid any public methods with side effects on 13 | * properties, properties should change be changed either by the constructor or directly by setting 14 | * them, not through a functions that updates other properties as it can lead to subtle bugs due 15 | * to Vue reactivity engine. 16 | */ 17 | export default class GeoAdminVectorLayer extends GeoAdminLayer { 18 | /** 19 | * @param {string} layerId The ID of this layer 20 | * @param {LayerAttribution[]} extraAttributions Extra attribution in case this vector layer is 21 | * a mix of many sources 22 | */ 23 | constructor(layerId, extraAttributions = []) { 24 | super({ 25 | name: layerId, 26 | type: LayerTypes.VECTOR, 27 | baseUrl: getVectorTilesBaseUrl(), 28 | id: layerId, 29 | technicalName: layerId, 30 | attributions: [ 31 | ...extraAttributions, 32 | new LayerAttribution('swisstopo', 'https://www.swisstopo.admin.ch/en/home.html'), 33 | ], 34 | isBackground: true, 35 | hasLegend: false, 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/mapviewer/src/api/layers/InvalidLayerData.error.js: -------------------------------------------------------------------------------- 1 | export class InvalidLayerDataError extends Error { 2 | constructor(message, data) { 3 | super(message) 4 | this.data = data 5 | this.name = 'InvalidLayerDataError' 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/mapviewer/src/api/layers/KmlStyles.enum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * List of available styles to be applied to KMLs features. 3 | * 4 | * For the time being, this can be either the default style (meaning what Google Earth does) or our 5 | * custom-made Geoadmin style (everything red) 6 | * 7 | * @enum 8 | */ 9 | const KmlStyles = { 10 | DEFAULT: 'DEFAULT', 11 | GEOADMIN: 'GEOADMIN', 12 | } 13 | 14 | export const allKmlStyles = [KmlStyles.DEFAULT, KmlStyles.GEOADMIN] 15 | 16 | export default KmlStyles 17 | -------------------------------------------------------------------------------- /packages/mapviewer/src/api/layers/LayerTypes.enum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @readonly 3 | * @enum {String} 4 | */ 5 | const LayerTypes = { 6 | WMTS: 'WMTS', 7 | WMS: 'WMS', 8 | GEOJSON: 'GEOJSON', 9 | AGGREGATE: 'AGGREGATE', 10 | KML: 'KML', 11 | GPX: 'GPX', 12 | VECTOR: 'VECTOR', 13 | GROUP: 'GROUP', 14 | COG: 'COG', 15 | } 16 | export default LayerTypes 17 | -------------------------------------------------------------------------------- /packages/mapviewer/src/api/qrcode.api.js: -------------------------------------------------------------------------------- 1 | import log from '@geoadmin/log' 2 | import axios from 'axios' 3 | 4 | import { getViewerDedicatedServicesBaseUrl } from '@/config/baseUrl.config' 5 | 6 | /** 7 | * Generates a URL to generate a QR Code for a URL 8 | * 9 | * @param {String} url The URL we want to QR-Codify 10 | * @returns {String} The URL to generate the QR Code 11 | */ 12 | export function getGenerateQRCodeUrl(url) { 13 | const encodedUrl = encodeURIComponent(url) 14 | return `${getViewerDedicatedServicesBaseUrl()}qrcode/generate?url=${encodedUrl}` 15 | } 16 | 17 | /** 18 | * Generates a QR Code that, when scanned by mobile devices, open the URL given in parameters 19 | * 20 | * @param {String} url The URL we want to QR-Codify 21 | * @returns {Promise} A promise that will resolve with the image (QR-Code) in PNG format 22 | * (byte) 23 | */ 24 | export const generateQrCode = (url) => { 25 | return new Promise((resolve, reject) => { 26 | try { 27 | new URL(url) 28 | } catch (error) { 29 | const errorMessage = 'Invalid URL, no QR code generated' 30 | log.error(errorMessage, url, error) 31 | reject(errorMessage) 32 | } 33 | axios 34 | .get(getGenerateQRCodeUrl(url), { 35 | responseType: 'arraybuffer', 36 | }) 37 | .then((image) => { 38 | resolve( 39 | 'data:image/png;base64,'.concat( 40 | btoa(String.fromCharCode(...new Uint8Array(image.data))) 41 | ) 42 | ) 43 | }) 44 | .catch((error) => { 45 | log.error('Error while retrieving qrCode for', url, error) 46 | reject(error) 47 | }) 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /packages/mapviewer/src/api/shortlink.api.js: -------------------------------------------------------------------------------- 1 | import log from '@geoadmin/log' 2 | import axios from 'axios' 3 | 4 | import { getServiceShortLinkBaseUrl } from '@/config/baseUrl.config' 5 | 6 | let cancelToken = null 7 | 8 | /** 9 | * Generates a short link from the given URL 10 | * 11 | * @param {String} url The URL we want to shorten 12 | * @param {Boolean} withCrosshair If a cross-hair should be placed at the center of the map in the 13 | * shortlink 14 | * @returns {Promise} A promise that will resolve with the short link 15 | */ 16 | export function createShortLink(url, withCrosshair = false) { 17 | return new Promise((resolve, reject) => { 18 | // we do not want the geolocation of the user clicking the link to kick in, so we force the flag out of the URL 19 | let sanitizedUrl = url.replace('&geolocation', '') 20 | if (withCrosshair) { 21 | sanitizedUrl += '&crosshair=marker' 22 | } 23 | try { 24 | new URL(sanitizedUrl) 25 | } catch (error) { 26 | const errorMessage = 'Invalid URL, no short link generated' 27 | log.error(errorMessage, sanitizedUrl, error) 28 | reject(errorMessage) 29 | } 30 | 31 | // if a request is currently pending, we cancel it to start the new one 32 | if (cancelToken) { 33 | cancelToken.cancel('new shortLink request') 34 | } 35 | cancelToken = axios.CancelToken.source() 36 | 37 | axios 38 | .post(getServiceShortLinkBaseUrl(), { 39 | url: sanitizedUrl, 40 | }) 41 | .then((response) => { 42 | resolve(response.data.shorturl) 43 | }) 44 | .catch((error) => { 45 | log.error('Error while retrieving short link for', sanitizedUrl, error) 46 | reject(error) 47 | }) 48 | }) 49 | } 50 | -------------------------------------------------------------------------------- /packages/mapviewer/src/assets/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/assets/grid.png -------------------------------------------------------------------------------- /packages/mapviewer/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/assets/logo.png -------------------------------------------------------------------------------- /packages/mapviewer/src/assets/svg/swiss-flag.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Coat of Arms of Switzerland 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/mapviewer/src/config/feedback.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Email subject for feedback emails. 3 | * 4 | * @type {String} 5 | */ 6 | export const FEEDBACK_EMAIL_SUBJECT = '[Problem Report]' 7 | -------------------------------------------------------------------------------- /packages/mapviewer/src/config/print.config.js: -------------------------------------------------------------------------------- 1 | // In the old mapviewer, a magic number (90) was set to make some compensation between the print and the viewer, 2 | // to keep the scale between the two services. In the current implementation, 144 seems to be giving the best results. 3 | export const PRINT_DPI_COMPENSATION = 144 4 | 5 | // when the scale is too low, the print backend can't read the exponent. 6 | //So when there is a non 0 scale, we set its minimum to 0.0001 7 | export const MIN_PRINT_SCALE_SIZE = 0.0001 8 | 9 | /** Dimensions in mm of the viewport for each print format */ 10 | export const PRINT_DIMENSIONS = { 11 | A0: [1189, 841], 12 | A1: [841, 594], 13 | A2: [594, 420], 14 | A3: [420, 297], 15 | A4: [297, 210], 16 | A5: [210, 148], 17 | A6: [148, 105], 18 | } 19 | 20 | export const PRINT_DEFAULT_DPI = 96 21 | 22 | export const PRINT_MARGIN_IN_MILLIMETERS = 4 23 | -------------------------------------------------------------------------------- /packages/mapviewer/src/config/responsive.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Horizontal threshold for the phone view. (min-width for tablet) This will change the menu and 3 | * also some interactions. 4 | * 5 | * The value is taken from the "sm" breakpoint from Bootstrap. If this value is modified, the 6 | * variable with the same name defined in 'src/scss/media-query.mixin' must also be modified. 7 | * 8 | * @type {Number} 9 | */ 10 | export const BREAKPOINT_PHONE_WIDTH = 576 11 | 12 | /** 13 | * Horizontal threshold for the phone view. (min-height for tablet) The height is needed to catch 14 | * landscape view on mobile. 15 | * 16 | * If this value is modified, the variable with the same name defined in 17 | * 'src/scss/media-query.mixin' must also be modified. 18 | * 19 | * @type {Number} 20 | */ 21 | export const BREAKPOINT_PHONE_HEIGHT = 500 22 | 23 | /** 24 | * Horizontal threshold for the tablet view. (min-width for desktop) 25 | * 26 | * If this value is modified, the variable with the same name defined in 27 | * 'src/scss/media-query.mixin' must also be modified. 28 | * 29 | * @type {Number} 30 | */ 31 | export const BREAKPOINT_TABLET = 768 32 | 33 | /** 34 | * The width under which we no longer use floating tooltips and enforce infoboxes. 35 | * 36 | * Found empirically, taking the tooltip width of 350px into account 37 | * 38 | * @type {Number} 39 | */ 40 | export const MAX_WIDTH_SHOW_FLOATING_TOOLTIP = 400 41 | -------------------------------------------------------------------------------- /packages/mapviewer/src/config/security.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Don't show third party disclaimer for iframe with one of these hosts as src 3 | * 4 | * @type {String[]} 5 | */ 6 | export const WHITELISTED_HOSTNAMES = ['test.map.geo.admin.ch', 'map.geo.admin.ch'] 7 | 8 | /** 9 | * List of blocked file extensions 10 | * 11 | * @type {String[]} 12 | */ 13 | export const BLOCKED_EXTENSIONS = ['exe', 'bat', 'cmd', 'sh', 'msi', 'scr', 'vbs', 'dll'] 14 | -------------------------------------------------------------------------------- /packages/mapviewer/src/config/staging.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Enum that tells for which (deployment) environment the app has been built. 3 | * 4 | * @type {'development' | 'integration' | 'production'} 5 | * @see https://en.wikipedia.org/wiki/Deployment_environment 6 | */ 7 | export const ENVIRONMENT = VITE_ENVIRONMENT 8 | 9 | /** 10 | * Flag that tells if the app is currently running in a Cypress environment for E2E testing 11 | * 12 | * NOTE: this file might be imported by nodejs for external scripts therefore make sure that 13 | * `window` exists 14 | * 15 | * @type Boolean 16 | */ 17 | export const IS_TESTING_WITH_CYPRESS = typeof window !== 'undefined' ? !!window.Cypress : false 18 | 19 | /** 20 | * Current app version (from package.json) 21 | * 22 | * @type {String} 23 | */ 24 | export const APP_VERSION = __APP_VERSION__ 25 | 26 | /** 27 | * The Github Repository. 28 | * 29 | * @type {String} 30 | */ 31 | 32 | export const GITHUB_REPOSITORY = 'https://github.com/geoadmin/web-mapviewer' 33 | /** 34 | * Display a big development banner on all but these hosts. 35 | * 36 | * @type {String[]} 37 | */ 38 | export const NO_WARNING_BANNER_HOSTNAMES = ['test.map.geo.admin.ch', 'map.geo.admin.ch'] 39 | 40 | /** 41 | * Display a warning ribbon ('TEST') on the top-left (mobile) or bottom-left (desktop) corner on all 42 | * these hosts. 43 | * 44 | * @type {String[]} 45 | */ 46 | export const WARNING_RIBBON_HOSTNAMES = ['test.map.geo.admin.ch'] 47 | 48 | /** 49 | * Display Give Feedback on all these hosts 50 | * 51 | * @type {String[]} 52 | */ 53 | export const GIVE_FEEDBACK_HOSTNAMES = ['localhost', 'sys-map.dev.bgdi.ch', 'test.map.geo.admin.ch'] 54 | 55 | /** 56 | * Display Report Problem on all these hosts 57 | * 58 | * @type {String[]} 59 | */ 60 | export const REPORT_PROBLEM_HOSTNAMES = [ 61 | 'localhost', 62 | 'sys-map.dev.bgdi.ch', 63 | 'sys-map.int.bgdi.ch', 64 | 'sys-map.prod.bgdi.ch', 65 | 'map.geo.admin.ch', 66 | ] 67 | -------------------------------------------------------------------------------- /packages/mapviewer/src/config/time.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The default oldest year in our system is from the layer Journey Through Time 3 | * (ch.swisstopo.zeitreihen) which has data from the year 1844 4 | * 5 | * @type {Number} 6 | */ 7 | export const DEFAULT_OLDEST_YEAR = 1844 8 | 9 | /** 10 | * The default youngest (closest to now) year in our system, it will always be the previous year as 11 | * of now 12 | * 13 | * @type {Number} 14 | */ 15 | export const DEFAULT_YOUNGEST_YEAR = new Date().getFullYear() 16 | -------------------------------------------------------------------------------- /packages/mapviewer/src/config/vectortiles.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Light base map style ID 3 | * 4 | * From https://www.swisstopo.admin.ch/de/geodata/maps/smw/smw_lightbase.html 5 | * 6 | * @type {string} 7 | */ 8 | export const VECTOR_LIGHT_BASE_MAP_STYLE_ID = 'ch.swisstopo.leichte-basiskarte_world.vt' 9 | 10 | /** 11 | * Imagery base map style ID 12 | * 13 | * From https://www.swisstopo.admin.ch/de/geodata/maps/smw/smw_imagerybase.html 14 | * 15 | * @type {string} 16 | */ 17 | export const VECTOR_TILES_IMAGERY_STYLE_ID = 'ch.swisstopo.leichte-basiskarte-imagery_world.vt' 18 | -------------------------------------------------------------------------------- /packages/mapviewer/src/css/tailwind.css: -------------------------------------------------------------------------------- 1 | /* using important to ease cohabitation with Bootstrap, can be removed it once we've migrated away from Bootstrap */ 2 | @import 'tailwindcss' prefix(tw) important; 3 | 4 | @import '@geoadmin/elevation-profile/tailwindcss'; 5 | @import '@geoadmin/tooltip/tailwindcss'; 6 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/drawing/components/AddVertexButton.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 60 | 61 | 64 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/drawing/components/DrawingLineInteraction.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 30 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/drawing/components/DrawingMarkerInteraction.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 35 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/drawing/components/DrawingMeasureInteraction.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 30 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/drawing/components/DrawingTextInteraction.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 31 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/drawing/components/ExtendLineInteraction.vue: -------------------------------------------------------------------------------- 1 | 27 | 30 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/drawing/components/ExtendMeasureInteraction.vue: -------------------------------------------------------------------------------- 1 | 31 | 34 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/drawing/components/useDrawingLineInteraction.composable.js: -------------------------------------------------------------------------------- 1 | import { EditableFeatureTypes } from '@/api/features/EditableFeature.class' 2 | import useDrawingModeInteraction from '@/modules/drawing/components/useDrawingModeInteraction.composable' 3 | import { drawLineStyle } from '@/modules/drawing/lib/style' 4 | 5 | export default function useDrawingLineInteraction({ 6 | style = drawLineStyle, 7 | featureType = EditableFeatureTypes.LINEPOLYGON, 8 | drawEndCallback = null, 9 | }) { 10 | const { removeLastPoint } = useDrawingModeInteraction({ 11 | geometryType: 'Polygon', 12 | editingStyle: style, 13 | editableFeatureArgs: { 14 | featureType, 15 | }, 16 | useGeodesicDrawing: true, 17 | snapping: true, 18 | drawEndCallback, 19 | }) 20 | 21 | return { 22 | removeLastPoint, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/drawing/components/useExtendLineInteraction.composable.js: -------------------------------------------------------------------------------- 1 | import { toValue } from 'vue' 2 | 3 | import { EditableFeatureTypes } from '@/api/features/EditableFeature.class' 4 | import useDrawingModeInteraction from '@/modules/drawing/components/useDrawingModeInteraction.composable' 5 | import { drawLineStyle } from '@/modules/drawing/lib/style' 6 | 7 | /** 8 | * Custom hook to extend line interaction with drawing mode. 9 | * 10 | * @param {Object} options - Options for the extend line interaction. 11 | * @param {Object} [options.style=drawLineStyle] - The style to be applied to the line. Default is 12 | * `drawLineStyle` 13 | * @param {string} [options.featureType=EditableFeatureTypes.LINEPOLYGON] - The type of feature to 14 | * be edited. Default is `EditableFeatureTypes.LINEPOLYGON` 15 | * @param {Function} [options.drawEndCallback=null] - Callback function to be called when drawing 16 | * ends. Default is `null` 17 | * @param {Object} [options.startingFeature=null] - The starting feature for the drawing. Default is 18 | * `null` 19 | * @returns {Object} - An object containing the removeLastPoint function. 20 | */ 21 | export default function useExtendLineInteraction({ 22 | style = drawLineStyle, 23 | featureType = EditableFeatureTypes.LINEPOLYGON, 24 | drawEndCallback = null, 25 | startingFeature = null, 26 | }) { 27 | const { removeLastPoint } = useDrawingModeInteraction({ 28 | geometryType: 'Polygon', 29 | editingStyle: style, 30 | editableFeatureArgs: { 31 | featureType, 32 | }, 33 | useGeodesicDrawing: true, 34 | snapping: true, 35 | drawEndCallback, 36 | startingFeature: toValue(startingFeature), 37 | }) 38 | return { 39 | removeLastPoint, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/infobox/DrawingStyleMediaTypes.enum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @readonly 3 | * @enum {String} 4 | */ 5 | const MediaTypes = { 6 | link: 'link', 7 | image: 'image', 8 | video: 'video', 9 | } 10 | export default MediaTypes 11 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/infobox/README.md: -------------------------------------------------------------------------------- 1 | # Infobox module 2 | 3 | Adds a container that holds any dynamic information about the map / feature that the user could need (drawing style edition, feature identification, etc...) 4 | 5 | Depending on the viewport size (or user's preference/config) this container will be (for desktop mode) a side tray that can be collapsed at will, 6 | and for mobile mode a swipeable element that takes place at the bottom of the screen. 7 | 8 | ## Swipable element 9 | 10 | This element will by default show a small version of the content, with only its title. 11 | At the top of this swipe element is a grabbing part that can be either touched, or swiped, and can open up the whole element (and then show the feature/element whole information) 12 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/infobox/components/ShowGeometryProfileButton.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 50 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/infobox/components/styling/DrawingStyleSizeSelector.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 57 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/infobox/index.js: -------------------------------------------------------------------------------- 1 | import TooltipModule from './InfoboxModule.vue' 2 | 3 | export default TooltipModule 4 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/README.md: -------------------------------------------------------------------------------- 1 | # Map module 2 | 3 | Responsible for rendering the map according to the state, handles click on the map and acts accordingly (making API call to the backend for identify, or drawing) 4 | 5 | ## State properties 6 | 7 | | Name | Content | 8 | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 9 | | `map.isBeingDragged` | `true` when the underlying map experiences a drag event from the mouse, goes back to `false` as soon as the interaction ends (doesn't trigger `true` on a click, only on drag events) | 10 | | `map.clickInfo` | Information about the last click that has occurred on the map, including how long the click was | 11 | | `map.pinnedLocation` | Coordinate for a dropped pin on the map (only one at a time) | 12 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/assets/bowl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/modules/map/assets/bowl.png -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/assets/ch.swisstopo.leichte-basiskarte_world-vt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/modules/map/assets/ch.swisstopo.leichte-basiskarte_world-vt.png -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/assets/ch.swisstopo.pixelkarte-farbe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/modules/map/assets/ch.swisstopo.pixelkarte-farbe.png -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/assets/ch.swisstopo.pixelkarte-grau.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/modules/map/assets/ch.swisstopo.pixelkarte-grau.png -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/assets/ch.swisstopo.swissimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/modules/map/assets/ch.swisstopo.swissimage.png -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/assets/ch.swisstopo.swissimage_3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/modules/map/assets/ch.swisstopo.swissimage_3d.png -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/assets/ch.swisstopo.swisstlm3d-karte-farbe_3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/modules/map/assets/ch.swisstopo.swisstlm3d-karte-farbe_3d.png -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/assets/ch.swisstopo.swisstlm3d-karte-grau_3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/modules/map/assets/ch.swisstopo.swisstlm3d-karte-grau_3d.png -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/assets/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/modules/map/assets/circle.png -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/assets/cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/modules/map/assets/cross.png -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/assets/marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/modules/map/assets/marker.png -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/assets/north_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/modules/map/assets/north_arrow.png -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/assets/point.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/modules/map/assets/point.png -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/assets/void.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/modules/map/assets/void.png -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/components/LocationPopupShare.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 44 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/components/WarningRibbon.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 19 | 20 | 59 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/components/cesium/CesiumBackgroundLayer.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 27 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/components/cesium/CesiumVectorLayer.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 41 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/components/cesium/utils/addLayerToViewer-mixins.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Vue mixin that will handle the addition or removal of a Cesium layer. 3 | * 4 | * Each component that uses this mixin must create a layer (`this.layer`) and next methods: 5 | * 6 | * - `addLayer` that gets `layer` and `zIndex` (optional) as properties 7 | * - `removeLayer` that gets `layer` as property 8 | */ 9 | const addLayerToViewer = { 10 | inject: ['getViewer'], 11 | data() { 12 | return { 13 | isPresentOnMap: false, 14 | } 15 | }, 16 | mounted() { 17 | if (this.layer && !this.isPresentOnMap) { 18 | this.addLayer(this.layer, this.zIndex) 19 | } 20 | }, 21 | unmounted() { 22 | if (this.layer && this.isPresentOnMap) { 23 | this.removeLayer(this.layer) 24 | } 25 | 26 | delete this.layer 27 | }, 28 | } 29 | 30 | export default addLayerToViewer 31 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/components/footer/MapFooterAppCopyright.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 36 | 37 | 48 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/components/footer/MapFooterAttributionItem.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 39 | 40 | 56 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/components/footer/backgroundSelector/useBackgroundSelector.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | /** 4 | * Centralisation of the logic behind the background selector. This helps us define two flavors of 5 | * background selector with the same Vue code basis. 6 | * 7 | * @param {(AbstractLayer | null)[]} backgroundLayers All backgrounds defined in the store (void 8 | * included, meaning a null value inside the array) 9 | * @param {AbstractLayer | null} currentBackgroundLayer The background currently displayed on the 10 | * map (if none, or void, then a null value can be passed here) 11 | * @param {EmitFn} emit The emit function built by defineEmit (Vue helper function). It is sadely 12 | * not possible to define emit events directly in the composable portion, each component built 13 | * with this composable needs to define events itself. 14 | */ 15 | export default function (backgroundLayers, currentBackgroundLayer, emit) { 16 | const show = ref(false) 17 | const animate = ref(false) 18 | function getImageForBackgroundLayer(backgroundLayer) { 19 | let backgroundId = backgroundLayer?.id 20 | if (!backgroundId) { 21 | backgroundId = 'void' 22 | } 23 | return new URL(`../../../assets/${backgroundId}.png`, import.meta.url).href 24 | } 25 | function onSelectBackground(backgroundLayer) { 26 | emit('selectBackground', backgroundLayer) 27 | toggleShowSelector() 28 | } 29 | function toggleShowSelector() { 30 | show.value = !show.value 31 | 32 | animate.value = true 33 | // waiting a short time, so that the animation can kick in, them remove the flag 34 | setTimeout(() => { 35 | animate.value = false 36 | }, 100) 37 | } 38 | 39 | return { 40 | show, 41 | animate, 42 | getImageForBackgroundLayer, 43 | onSelectBackground, 44 | toggleShowSelector, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/components/footer/backgroundSelector/useBackgroundSelectorProps.js: -------------------------------------------------------------------------------- 1 | import AbstractLayer from '@/api/layers/AbstractLayer.class' 2 | 3 | /** 4 | * @returns {Object} Props definition to use in concert with useBackgroundSelector to build a 5 | * BackgroundSelector component. 6 | */ 7 | export default function () { 8 | return { 9 | backgroundLayers: { 10 | type: Array, 11 | default: () => [], 12 | }, 13 | currentBackgroundLayer: { 14 | type: AbstractLayer, 15 | default: null, 16 | }, 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/components/openlayers/OpenLayersAccuracyCircle.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 55 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/components/openlayers/OpenLayersBackgroundLayer.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 21 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/components/openlayers/OpenLayersCrossHair.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 39 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/components/openlayers/OpenLayersPinnedLocation.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 41 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/components/openlayers/OpenLayersPrintResolutionEnforcer.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 32 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/components/openlayers/OpenLayersVisibleLayers.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 43 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/components/openlayers/debug/OpenLayersTileDebugInfo.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 50 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/index.js: -------------------------------------------------------------------------------- 1 | import MapModule from './MapModule.vue' 2 | 3 | export default MapModule 4 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/map/scss/toolbox-buttons.scss: -------------------------------------------------------------------------------- 1 | @import '@/scss/webmapviewer-bootstrap-theme'; 2 | 3 | %toolbox-button { 4 | border: none; 5 | padding: 0; 6 | display: block; 7 | height: $map-button-diameter; 8 | width: $map-button-diameter; 9 | border-radius: $map-button-diameter * 0.5; 10 | overflow: hidden; 11 | cursor: pointer; 12 | background-color: $map-button-border-color; 13 | text-align: center; 14 | font-size: 0; // Needed to fully center the icon 15 | margin-bottom: 0.25rem; 16 | &:hover { 17 | background-color: $map-button-hover-border-color; 18 | border-color: $map-button-hover-border-color; 19 | } 20 | &-icon { 21 | height: $map-button-diameter - 5px; 22 | } 23 | &-inner-circle, 24 | .svg-inline--fa { 25 | height: $map-button-inner-icon-diameter; 26 | color: $white; 27 | } 28 | &.active { 29 | background-color: $primary; 30 | border-color: $primary; 31 | .toolbox-button-inner-circle { 32 | fill: $primary; 33 | } 34 | } 35 | &.disabled { 36 | background-color: $light-grey; 37 | border: $light-grey; 38 | cursor: not-allowed; 39 | } 40 | } 41 | 42 | .toolbox-button { 43 | @extend %toolbox-button; 44 | } 45 | 46 | .toolbox-button-label { 47 | font-size: smaller; 48 | text-align: center; 49 | } 50 | 51 | .overlay-button { 52 | @extend %toolbox-button; 53 | 54 | height: $map-button-diameter * 0.75; 55 | width: $map-button-diameter * 0.75; 56 | border-radius: $map-button-diameter * 0.5; 57 | 58 | &-icon { 59 | height: ($map-button-diameter - 25px); 60 | } 61 | 62 | &-inner-circle, 63 | .svg-inline--fa { 64 | height: $map-button-inner-icon-diameter * 0.75; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/menu/README.md: -------------------------------------------------------------------------------- 1 | # Menu module 2 | 3 | Adds a header bar on top of the screen that contains the search bar, and the language switch 4 | 5 | Adds a menu tray that can be opened on the side of the app, containing much of the controls the user has (layer list, drawing, print, etc...) 6 | 7 | Add a toolbox to the app that make possible to change the background, enables zooming, panning, and all other camera, position or zoom related values. 8 | 9 | Adds a search bar to the UI with all related UI elements (search results managements, etc...) 10 | 11 | ## Dependencies 12 | 13 | - Will import the lang switch button from the `i18n` module. 14 | 15 | ## State properties 16 | 17 | | Name | Content | 18 | | ---------------- | ------------------------------------------------------------------------ | 19 | | `search.query` | will trigger a search to the backend if it contains 3 or more characters | 20 | | `search.results` | results from the backend for the current search query | 21 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/menu/assets/text-dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/modules/menu/assets/text-dev.png -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/menu/assets/text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/modules/menu/assets/text.png -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/menu/assets/topics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/modules/menu/assets/topics.png -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/menu/components/advancedTools/ImportCatalogue/utils.js: -------------------------------------------------------------------------------- 1 | import { setWmsGetCapParams, setWmtsGetCapParams } from '@/api/layers/layers-external.api' 2 | 3 | /** 4 | * Checks if file has WMS Capabilities XML content 5 | * 6 | * @param {string} fileContent 7 | * @returns {boolean} 8 | */ 9 | export function isWmsGetCap(fileContent) { 10 | return /<(WMT_MS_Capabilities|WMS_Capabilities)/.test(fileContent) 11 | } 12 | 13 | /** 14 | * Checks if file has WMTS Capabilities XML content 15 | * 16 | * @param {string} fileContent 17 | * @returns {boolean} 18 | */ 19 | export function isWmtsGetCap(fileContent) { 20 | return / 2 | import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' 3 | import { ref, watch } from 'vue' 4 | 5 | const emits = defineEmits(['change']) 6 | 7 | /** @type {Ref} */ 8 | const currentValue = ref(null) 9 | 10 | watch(currentValue, (value) => { 11 | emits('change', value) 12 | }) 13 | 14 | 15 | 51 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/menu/components/debug/DebugViewSelector.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 42 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/menu/components/header/AdditionalInfoCollapsable.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 34 | 35 | 52 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/menu/components/header/HeaderLangSelector.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 35 | 36 | 46 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/menu/components/header/HeaderLink.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 49 | 50 | 60 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/menu/components/header/HeaderMenuButton.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 30 | 31 | 41 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/menu/components/header/SwissFlag.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 29 | 30 | 53 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/menu/components/help/HelpLink.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 46 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/menu/components/help/MoreInfo.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 31 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/menu/components/help/UpdateInfo.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 31 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/menu/components/help/common/SendActionButtons.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 57 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/menu/components/topics/TopicIcon.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 21 | 22 | 25 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/menu/index.js: -------------------------------------------------------------------------------- 1 | import MenuModule from './MenuModule.vue' 2 | 3 | export default MenuModule 4 | -------------------------------------------------------------------------------- /packages/mapviewer/src/modules/menu/scss/menu-items.scss: -------------------------------------------------------------------------------- 1 | @import '@/scss/webmapviewer-bootstrap-theme'; 2 | 3 | %menu-list { 4 | list-style: none; 5 | padding: 0; 6 | margin: 0; 7 | } 8 | 9 | %menu-item { 10 | &:not(.drag-in-progress):hover, 11 | &[draggable='true'] { 12 | background-color: $list-item-hover-bg-color !important; 13 | } 14 | } 15 | 16 | %menu-title { 17 | display: flex; 18 | align-items: center; 19 | padding-left: 8px; 20 | padding-right: 8px; 21 | height: 2.5rem; 22 | 23 | .compact & { 24 | height: 2.2rem; 25 | } 26 | 27 | & > button { 28 | padding-left: 6px; 29 | padding-right: 6px; 30 | } 31 | } 32 | 33 | %menu-name { 34 | flex-grow: 1; 35 | text-align: left; 36 | } 37 | 38 | .menu-list { 39 | @extend %menu-list; 40 | } 41 | 42 | .menu-item { 43 | @extend %menu-item; 44 | } 45 | 46 | .menu-title { 47 | @extend %menu-title; 48 | } 49 | 50 | .menu-name { 51 | @extend %menu-name; 52 | } 53 | -------------------------------------------------------------------------------- /packages/mapviewer/src/router/storeSync/BaseUrlOverrideParamConfig.class.js: -------------------------------------------------------------------------------- 1 | import { 2 | getBaseUrlOverride, 3 | hasBaseUrlOverrides, 4 | setBaseUrlOverrides, 5 | } from '@/config/baseUrl.config' 6 | import AbstractParamConfig from '@/router/storeSync/abstractParamConfig.class' 7 | import { isValidUrl } from '@/utils/utils' 8 | 9 | export default function createBaseUrlOverrideParamConfig({ urlParamName, baseUrlPropertyName }) { 10 | function dispatchBaseUrlOverride(to, store, urlParamValue) { 11 | if (isValidUrl(urlParamValue)) { 12 | setBaseUrlOverrides(baseUrlPropertyName, urlParamValue) 13 | } else { 14 | setBaseUrlOverrides(baseUrlPropertyName, null) 15 | } 16 | const hasNowOverrides = hasBaseUrlOverrides() 17 | if (store.state.debug.hasBaseUrlOverride !== hasNowOverrides) { 18 | store.dispatch('setHasBaseUrlOverrides', { 19 | hasOverrides: hasNowOverrides, 20 | dispatcher: `BaseUrlOverrideParamConfig.${urlParamName}`, 21 | }) 22 | } 23 | } 24 | 25 | function extractValue() { 26 | return getBaseUrlOverride(baseUrlPropertyName) 27 | } 28 | 29 | return new (class BaseUrlOverrideParamConfig extends AbstractParamConfig { 30 | constructor() { 31 | super({ 32 | urlParamName, 33 | mutationsToWatch: ['setHasBaseUrlOverrides'], 34 | setValuesInStore: dispatchBaseUrlOverride, 35 | extractValueFromStore: extractValue, 36 | keepInUrlWhenDefault: false, 37 | valueType: String, 38 | defaultValue: null, 39 | }) 40 | } 41 | })() 42 | } 43 | -------------------------------------------------------------------------------- /packages/mapviewer/src/router/storeSync/NoSimpleZoomParamConfig.class.js: -------------------------------------------------------------------------------- 1 | import AbstractParamConfig, { 2 | STORE_DISPATCHER_ROUTER_PLUGIN, 3 | } from '@/router/storeSync/abstractParamConfig.class' 4 | 5 | const URL_PARAM_NAME = 'noSimpleZoom' 6 | 7 | export default class NoSimpleZoomParamConfig extends AbstractParamConfig { 8 | constructor() { 9 | super({ 10 | urlParamName: URL_PARAM_NAME, 11 | mutationsToWatch: ['setNoSimpleZoomEmbed'], 12 | setValuesInStore: dispatchNoSimpleZoomFromUrlIntoStore, 13 | extractValueFromStore: (store) => store.state.ui.noSimpleZoomEmbed, 14 | keepInUrlWhenDefault: false, 15 | valueType: Boolean, 16 | }) 17 | } 18 | } 19 | 20 | function dispatchNoSimpleZoomFromUrlIntoStore(to, store, urlParamValue) { 21 | if (to.path.toLowerCase().includes('embed')) { 22 | store.dispatch('setNoSimpleZoomEmbed', { 23 | noSimpleZoomEmbed: urlParamValue, 24 | dispatcher: STORE_DISPATCHER_ROUTER_PLUGIN, 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/mapviewer/src/router/storeSync/SearchAutoSelectConfig.class.js: -------------------------------------------------------------------------------- 1 | import AbstractParamConfig, { 2 | STORE_DISPATCHER_ROUTER_PLUGIN, 3 | } from '@/router/storeSync/abstractParamConfig.class' 4 | import { URL_PARAM_NAME_SWISSSEARCH } from '@/router/storeSync/SearchParamConfig.class' 5 | import { removeQueryParamFromHref } from '@/utils/searchParamUtils' 6 | 7 | const URL_PARAM_NAME = 'swisssearch_autoselect' 8 | /** 9 | * Dispatches the 'setAutoSelect' action to the store if the URL parameter for swisssearch is 10 | * present. 11 | * 12 | * @param {Object} to - The target route object. 13 | * @param {Object} store - The Vuex store instance. 14 | * @param {string} urlParamValue - The value of the URL parameter to be dispatched. 15 | */ 16 | function dispatchAutoSelect(to, store, urlParamValue) { 17 | // avoiding setting the swisssearch autoselect to the store when there is nothing to autoselect because there is no swisssearch query 18 | if (urlParamValue && to.query[URL_PARAM_NAME_SWISSSEARCH]) { 19 | store.dispatch('setAutoSelect', { 20 | value: urlParamValue, 21 | dispatcher: STORE_DISPATCHER_ROUTER_PLUGIN, 22 | }) 23 | } 24 | } 25 | 26 | export default class SearchAutoSelectConfig extends AbstractParamConfig { 27 | constructor() { 28 | super({ 29 | urlParamName: URL_PARAM_NAME, 30 | mutationsToWatch: ['setAutoSelect'], 31 | setValuesInStore: dispatchAutoSelect, 32 | afterSetValuesInStore: () => removeQueryParamFromHref(URL_PARAM_NAME), 33 | extractValueFromStore: (store) => store.state.search.autoSelect, 34 | keepInUrlWhenDefault: false, 35 | valueType: Boolean, 36 | defaultValue: false, 37 | validateUrlInput: null, 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/mapviewer/src/router/storeSync/SearchParamConfig.class.js: -------------------------------------------------------------------------------- 1 | import AbstractParamConfig, { 2 | STORE_DISPATCHER_ROUTER_PLUGIN, 3 | } from '@/router/storeSync/abstractParamConfig.class' 4 | import { removeQueryParamFromHref } from '@/utils/searchParamUtils' 5 | 6 | export const URL_PARAM_NAME_SWISSSEARCH = 'swisssearch' 7 | /** 8 | * The goal is to stop centering on the search when sharing a position. When we share a position, 9 | * both the center and the crosshair are sets. 10 | * 11 | * @param {Object} to The route object containing the query 12 | * @param {Object} store The store 13 | * @param {String} urlParamValue The search param 14 | */ 15 | function dispatchSearchFromUrl(to, store, urlParamValue) { 16 | // avoiding dispatching the search query to the store when there is nothing to set. Not avoiding this makes the CI test very flaky 17 | if (urlParamValue) { 18 | store.dispatch('setSearchQuery', { 19 | query: urlParamValue, 20 | dispatcher: STORE_DISPATCHER_ROUTER_PLUGIN, 21 | originUrlParam: true, 22 | }) 23 | } 24 | } 25 | 26 | export default class SearchParamConfig extends AbstractParamConfig { 27 | constructor() { 28 | super({ 29 | urlParamName: URL_PARAM_NAME_SWISSSEARCH, 30 | mutationsToWatch: [], 31 | setValuesInStore: dispatchSearchFromUrl, 32 | afterSetValuesInStore: () => removeQueryParamFromHref(URL_PARAM_NAME_SWISSSEARCH), 33 | keepInUrlWhenDefault: false, 34 | valueType: String, 35 | defaultValue: '', 36 | validateUrlInput: null, 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/mapviewer/src/router/storeSync/ZoomParamConfig.class.js: -------------------------------------------------------------------------------- 1 | import { getStandardValidationResponse } from '@/api/errorQueues.api' 2 | import AbstractParamConfig, { 3 | STORE_DISPATCHER_ROUTER_PLUGIN, 4 | } from '@/router/storeSync/abstractParamConfig.class' 5 | 6 | export function readZoomFromUrlParam(urlParamValue) { 7 | if (urlParamValue) { 8 | return parseFloat(urlParamValue) 9 | } 10 | return null 11 | } 12 | 13 | function dispatchZoomFromUrlIntoStore(to, store, urlParamValue) { 14 | const promisesForAllDispatch = [] 15 | const zoom = readZoomFromUrlParam(urlParamValue) 16 | if (zoom) { 17 | promisesForAllDispatch.push( 18 | store.dispatch('setZoom', { 19 | zoom, 20 | dispatcher: STORE_DISPATCHER_ROUTER_PLUGIN, 21 | }) 22 | ) 23 | } 24 | return Promise.all(promisesForAllDispatch) 25 | } 26 | 27 | function generateZoomUrlParamFromStoreValues(store) { 28 | if (store.state.cesium.active) { 29 | return null 30 | } 31 | return store.state.position.zoom 32 | } 33 | 34 | /** Describe the zoom level of the map in the URL. */ 35 | export default class ZoomParamConfig extends AbstractParamConfig { 36 | constructor() { 37 | super({ 38 | urlParamName: 'z', 39 | mutationsToWatch: ['setZoom'], 40 | setValuesInStore: dispatchZoomFromUrlIntoStore, 41 | extractValueFromStore: generateZoomUrlParamFromStoreValues, 42 | keepInUrlWhenDefault: true, 43 | valueType: Number, 44 | validateUrlInput: (store, query) => 45 | getStandardValidationResponse( 46 | query, 47 | query && !isNaN(query) && Number(query) >= 0, 48 | this.urlParamName 49 | ), 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/mapviewer/src/router/storeSync/index.js: -------------------------------------------------------------------------------- 1 | import storeSyncRouterPlugin from '@/router/storeSync/storeSync.routerPlugin' 2 | 3 | export default storeSyncRouterPlugin 4 | -------------------------------------------------------------------------------- /packages/mapviewer/src/router/viewNames.js: -------------------------------------------------------------------------------- 1 | /** List of all views that display the map and that should contain the minimal map functionality */ 2 | export const MAP_VIEW = 'MapView' 3 | export const EMBED_VIEW = 'EmbedView' 4 | export const PRINT_VIEW = 'PrintView' 5 | export const MAP_VIEWS = [MAP_VIEW, EMBED_VIEW, PRINT_VIEW] 6 | 7 | /** Legacy URL views used for startup */ 8 | export const LEGACY_PARAM_VIEW = 'LegacyParamsView' 9 | export const LEGACY_EMBED_PARAM_VIEW = 'LegacyEmbedParamsView' 10 | export const LEGACY_VIEWS = [LEGACY_PARAM_VIEW, LEGACY_EMBED_PARAM_VIEW] 11 | -------------------------------------------------------------------------------- /packages/mapviewer/src/scss/exports.js: -------------------------------------------------------------------------------- 1 | import variables from '@/scss/variables.module.scss' 2 | 3 | export const cssHeaderHeight = parseInt(variables.headerHeight) 4 | export const cssDevDisclaimerHeight = parseInt(variables.devDisclaimerHeight) 5 | export const cssTimeSliderBarHeight = parseInt(variables.timeSliderBarHeight) 6 | export const cssTimeSliderDropdownHeight = parseInt(variables.timeSliderDropdownHeight) 7 | export const cssFooterHeight = parseInt(variables.footerHeight) 8 | export const cssOverlayWidth = parseInt(variables.overlayWidth) 9 | export const cssDrawingMobileToolbarHeight = parseInt(variables.drawingMobileToolbarHeight) 10 | -------------------------------------------------------------------------------- /packages/mapviewer/src/scss/fonts/FrutigerLight/FrutigerLTStd-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/scss/fonts/FrutigerLight/FrutigerLTStd-Light.eot -------------------------------------------------------------------------------- /packages/mapviewer/src/scss/fonts/FrutigerLight/FrutigerLTStd-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/scss/fonts/FrutigerLight/FrutigerLTStd-Light.ttf -------------------------------------------------------------------------------- /packages/mapviewer/src/scss/fonts/FrutigerLight/FrutigerLTStd-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/scss/fonts/FrutigerLight/FrutigerLTStd-Light.woff -------------------------------------------------------------------------------- /packages/mapviewer/src/scss/fonts/FrutigerLight/FrutigerLTStd-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/src/scss/fonts/FrutigerLight/FrutigerLTStd-Light.woff2 -------------------------------------------------------------------------------- /packages/mapviewer/src/scss/vue-transitions.mixin.scss: -------------------------------------------------------------------------------- 1 | @mixin fade-in-out($animation-time) { 2 | .fade-in-out-enter-active, 3 | .fade-in-out-leave-active { 4 | transition: opacity $animation-time; 5 | } 6 | .fade-in-out-enter-from, 7 | .fade-in-out-leave-to { 8 | opacity: 0; 9 | } 10 | } 11 | 12 | @mixin slide-up($animation-time) { 13 | .slide-up-leave-active, 14 | .slide-up-enter-active { 15 | transition: $animation-time; 16 | } 17 | .slide-up-enter-from, 18 | .slide-up-leave-to { 19 | transform: translate(0, -100%); 20 | } 21 | } 22 | 23 | @mixin slide-left($animation-time) { 24 | .slide-left-leave-active, 25 | .slide-left-enter-active { 26 | transition: $animation-time; 27 | } 28 | .slide-left-enter-from, 29 | .slide-left-leave-to { 30 | transform: translate(-100%, 0); 31 | } 32 | } 33 | 34 | @mixin slide-right($animation-time) { 35 | .slide-right-leave-active, 36 | .slide-right-enter-active { 37 | transition: $animation-time; 38 | } 39 | .slide-right-enter-from, 40 | .slide-right-leave-to { 41 | transform: translate(100%, 0); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/mapviewer/src/store/debug.store.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The name of the mutation for base URL override changes 3 | * 4 | * @type {String} 5 | */ 6 | export const SET_HAS_URL_OVERRIDES_MUTATION_KEY = 'setHasBaseUrlOverrides' 7 | 8 | const mutations = { 9 | setShowTileDebugInfo: (state, { showTileDebugInfo }) => 10 | (state.showTileDebugInfo = showTileDebugInfo), 11 | setShowLayerExtents: (state, { showLayerExtents }) => 12 | (state.showLayerExtents = showLayerExtents), 13 | } 14 | mutations[SET_HAS_URL_OVERRIDES_MUTATION_KEY] = (state, { hasOverrides }) => 15 | (state.hasBaseUrlOverride = hasOverrides) 16 | 17 | /** Vuex module that contains debug tools things */ 18 | export default { 19 | state: { 20 | showTileDebugInfo: false, 21 | showLayerExtents: false, 22 | hasBaseUrlOverride: false, 23 | }, 24 | getters: {}, 25 | actions: { 26 | toggleShowTileDebugInfo({ commit, state }, { dispatcher }) { 27 | commit('setShowTileDebugInfo', { 28 | showTileDebugInfo: !state.showTileDebugInfo, 29 | dispatcher, 30 | }) 31 | }, 32 | toggleShowLayerExtents({ commit, state }, { dispatcher }) { 33 | commit('setShowLayerExtents', { showLayerExtents: !state.showLayerExtents, dispatcher }) 34 | }, 35 | setHasBaseUrlOverrides({ commit }, { hasOverrides, dispatcher }) { 36 | commit(SET_HAS_URL_OVERRIDES_MUTATION_KEY, { 37 | hasOverrides: !!hasOverrides, 38 | dispatcher, 39 | }) 40 | }, 41 | }, 42 | mutations, 43 | } 44 | -------------------------------------------------------------------------------- /packages/mapviewer/src/store/modules/app.store.js: -------------------------------------------------------------------------------- 1 | /** Vuex module that tells if the app has finished loading (is ready to show stuff) */ 2 | export default { 3 | state: { 4 | /** 5 | * Flag that tells if the app is ready to show data and the map 6 | * 7 | * @type Boolean 8 | */ 9 | isReady: false, 10 | 11 | /** 12 | * Flag telling that the Map Module is ready. This is useful for E2E testing which should 13 | * not start before the Map Module is ready. 14 | */ 15 | isMapReady: false, 16 | }, 17 | getters: {}, 18 | actions: { 19 | setAppIsReady: ({ commit }, { dispatcher }) => { 20 | commit('setAppIsReady', { dispatcher }) 21 | }, 22 | mapModuleReady: ({ commit }, { dispatcher }) => { 23 | commit('mapModuleReady', { dispatcher }) 24 | }, 25 | }, 26 | mutations: { 27 | setAppIsReady: (state) => (state.isReady = true), 28 | mapModuleReady: (state) => (state.isMapReady = true), 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /packages/mapviewer/src/store/modules/i18n.store.js: -------------------------------------------------------------------------------- 1 | import i18n, { langToLocal } from '@/modules/i18n' 2 | 3 | /** 4 | * The name of the mutation for lang changes 5 | * 6 | * @type {String} 7 | */ 8 | export const SET_LANG_MUTATION_KEY = 'setLang' 9 | 10 | const state = { 11 | /** 12 | * The current language used by this application, expressed as an country ISO code 13 | * (`en`,`de`,`fr,etc...) 14 | * 15 | * @type String 16 | */ 17 | lang: i18n.global.locale, 18 | } 19 | 20 | const getters = {} 21 | 22 | const actions = { 23 | setLang({ commit }, args) { 24 | commit(SET_LANG_MUTATION_KEY, args) 25 | }, 26 | } 27 | 28 | const mutations = {} 29 | 30 | mutations[SET_LANG_MUTATION_KEY] = function (state, { lang }) { 31 | state.lang = lang.toLowerCase() 32 | i18n.global.locale = langToLocal(lang.toLowerCase()) 33 | } 34 | 35 | export default { 36 | state, 37 | getters, 38 | actions, 39 | mutations, 40 | } 41 | -------------------------------------------------------------------------------- /packages/mapviewer/src/store/plugins/load-cog-metadata.plugin.js: -------------------------------------------------------------------------------- 1 | import { toValue } from 'vue' 2 | 3 | import CloudOptimizedGeoTIFFLayer from '@/api/layers/CloudOptimizedGeoTIFFLayer.class' 4 | import { CloudOptimizedGeoTIFFParser } from '@/modules/menu/components/advancedTools/ImportFile/parser/CloudOptimizedGeoTIFFParser.class' 5 | 6 | const cogParser = new CloudOptimizedGeoTIFFParser() 7 | 8 | async function loadCOGMetadataAndUpdateLayer(store, layer) { 9 | const layerWithExtent = await cogParser.parse({ 10 | fileSource: layer.fileSource, 11 | currentProjection: toValue(store.state.position.projection), 12 | }) 13 | store.dispatch('updateLayer', { 14 | layerId: layer.id, 15 | values: { 16 | extent: layerWithExtent.extent, 17 | noDataValue: layerWithExtent.noDataValue, 18 | }, 19 | }) 20 | } 21 | 22 | /** 23 | * COG loaded through a URL param at startup didn't go through the file parser that loads the 24 | * extent, no data value, and other metadata of the COG from the file. 25 | * 26 | * This plugin aims to do just that, check if any added COG is missing its metadata, and if so run 27 | * the file parser on it to extract this extent. 28 | * 29 | * @param {Vuex.Store} store 30 | */ 31 | export default function loadCOGMetadata(store) { 32 | store.subscribe((mutation) => { 33 | const addLayerSubscriber = (layer) => { 34 | if (layer instanceof CloudOptimizedGeoTIFFLayer && !layer.extent) { 35 | loadCOGMetadataAndUpdateLayer(store, layer) 36 | } 37 | } 38 | if (mutation.type === 'addLayer') { 39 | addLayerSubscriber(mutation.payload.layer) 40 | } 41 | if (mutation.type === 'setLayers') { 42 | mutation.payload.layers?.forEach((layer) => { 43 | addLayerSubscriber(layer) 44 | }) 45 | } 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /packages/mapviewer/src/store/plugins/redo-search-when-needed.plugin.js: -------------------------------------------------------------------------------- 1 | import { SET_LANG_MUTATION_KEY } from '@/store/modules/i18n.store' 2 | 3 | /** 4 | * Redo the search results on lang change if the search query is defined 5 | * 6 | * @param {Vuex.Store} store 7 | */ 8 | const redoSearchWhenNeeded = (store) => { 9 | function redoSearch() { 10 | if (store.state.search.query.length > 2) { 11 | store.dispatch('setSearchQuery', { 12 | query: store.state.search.query, 13 | originUrlParam: true, // necessary to select the first result if there is only one else it will not be because this redo search is done every time the page loaded 14 | dispatcher: 'redoSearchWhenNeeded', 15 | }) 16 | } 17 | } 18 | 19 | store.subscribe((mutation) => { 20 | if (mutation.type === SET_LANG_MUTATION_KEY) { 21 | // we redispatch the same query to the search store (the lang will be picked by the search store) 22 | redoSearch() 23 | } else if ( 24 | mutation.type === 'setLayers' && 25 | mutation.payload.layers?.some((layer) => layer.searchable) 26 | ) { 27 | // rerunning search if layer added at startup are searchable, as the search has already been run 28 | // if swissearch URL param is set (and layer features for searchable layers won't be available) 29 | redoSearch() 30 | } 31 | }) 32 | } 33 | 34 | export default redoSearchWhenNeeded 35 | -------------------------------------------------------------------------------- /packages/mapviewer/src/store/plugins/screen-size-management.plugin.js: -------------------------------------------------------------------------------- 1 | import { BREAKPOINT_PHONE_HEIGHT, BREAKPOINT_PHONE_WIDTH } from '@/config/responsive.config' 2 | import { UIModes } from '@/store/modules/ui.store' 3 | 4 | const dispatcher = { dispatcher: 'screen-size-management.plugin' } 5 | 6 | /** @param store */ 7 | const screenSizeManagementPlugin = (store) => { 8 | store.subscribe((mutation, state) => { 9 | if (mutation.type === 'setSize') { 10 | // listening to screen size change to decide if we should switch UI mode too 11 | let wantedUiMode 12 | if ( 13 | state.ui.width < BREAKPOINT_PHONE_WIDTH || 14 | state.ui.height < BREAKPOINT_PHONE_HEIGHT 15 | ) { 16 | wantedUiMode = UIModes.PHONE 17 | } else { 18 | // so the UI mode DESKTOP also includes the tablet mode. 19 | wantedUiMode = UIModes.DESKTOP 20 | } 21 | if (wantedUiMode !== state.ui.mode) { 22 | store.dispatch('setUiMode', { mode: wantedUiMode, ...dispatcher }) 23 | } 24 | } 25 | }) 26 | } 27 | 28 | export default screenSizeManagementPlugin 29 | -------------------------------------------------------------------------------- /packages/mapviewer/src/utils/EventEmitter.class.js: -------------------------------------------------------------------------------- 1 | // from https://gist.github.com/mudge/5830382#gistcomment-2691957 2 | /** Enables an instance of a class to emit its own events (and be listened to) */ 3 | export default class EventEmitter { 4 | constructor() { 5 | this.events = {} 6 | } 7 | 8 | _getEventListByName(eventName) { 9 | if (typeof this.events[eventName] === 'undefined') { 10 | this.events[eventName] = new Set() 11 | } 12 | return this.events[eventName] 13 | } 14 | 15 | on(eventName, fn) { 16 | this._getEventListByName(eventName).add(fn) 17 | } 18 | 19 | once(eventName, fn) { 20 | const onceFn = (...args) => { 21 | this.removeListener(eventName, onceFn) 22 | fn?.apply(this, args) 23 | } 24 | this.on(eventName, onceFn) 25 | } 26 | 27 | emit(eventName, ...args) { 28 | this._getEventListByName(eventName).forEach( 29 | function (fn) { 30 | fn?.apply(this, args) 31 | }.bind(this) 32 | ) 33 | } 34 | 35 | removeListener(eventName, fn) { 36 | this._getEventListByName(eventName).delete(fn) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/mapviewer/src/utils/__tests__/gpxUtils.spec.js: -------------------------------------------------------------------------------- 1 | import { LV95 } from '@geoadmin/coordinates' 2 | import { expect } from 'chai' 3 | import { describe, it } from 'vitest' 4 | 5 | import { parseGpx } from '@/utils/gpxUtils.js' 6 | 7 | describe('Test GPX utils', () => { 8 | describe('parseGpx', () => { 9 | it('handles correctly invalid inputs', () => { 10 | expect(parseGpx()).to.be.null 11 | expect(parseGpx(null, null)).to.be.null 12 | expect(parseGpx(0, LV95)).to.be.null 13 | expect(parseGpx([], LV95)).to.be.null 14 | expect(parseGpx({}, LV95)).to.be.null 15 | expect(parseGpx('', LV95)).to.be.null 16 | }) 17 | // further testing isn't really necessary as it's using out-of-the-box OL functions 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /packages/mapviewer/src/utils/__tests__/kml_feature_error.kml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 1 6 | 1 7 | GPS Visualizer]]> 8 | 9 | Tracks 10 | 1 11 | 0 12 | 13 | 14 | 15 | 16 | 22 | 23 | 1 24 | clampToGround 25 | 8.511875,47.35233 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /packages/mapviewer/src/utils/click-outside.js: -------------------------------------------------------------------------------- 1 | import log from '@geoadmin/log' 2 | 3 | /* 4 | * V-click-outside directive 5 | * 6 | * This directive require a function 7 | * 8 | * @example Options API 9 | * 12 | * 13 | * 22 | * 23 | * @example Composition API 24 | * 29 | * 30 | * 33 | */ 34 | const clickOutside = { 35 | beforeMount: (el, binding) => { 36 | el.clickOutsideEvent = (event) => { 37 | // here I check that click was outside the el and his children 38 | if (!(el === event.target || el.contains(event.target))) { 39 | // and if it did, call method provided in attribute value 40 | if (typeof binding.value === 'function') { 41 | binding.value(event) 42 | } else { 43 | log.error(`Binding to v-click-outside is not a function`, binding.value) 44 | } 45 | } 46 | } 47 | document.addEventListener('click', el.clickOutsideEvent) 48 | }, 49 | unmounted: (el) => { 50 | document.removeEventListener('click', el.clickOutsideEvent) 51 | }, 52 | } 53 | export default clickOutside 54 | -------------------------------------------------------------------------------- /packages/mapviewer/src/utils/components/AppVersion.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 47 | 48 | 69 | -------------------------------------------------------------------------------- /packages/mapviewer/src/utils/components/BlackBackdrop.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 34 | 35 | 45 | -------------------------------------------------------------------------------- /packages/mapviewer/src/utils/components/DragDropOverlay.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | 18 | 54 | -------------------------------------------------------------------------------- /packages/mapviewer/src/utils/components/OpenFullAppLink.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 39 | 40 | 54 | -------------------------------------------------------------------------------- /packages/mapviewer/src/utils/components/PrintButton.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 55 | -------------------------------------------------------------------------------- /packages/mapviewer/src/utils/components/ZoomToExtentButton.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 50 | -------------------------------------------------------------------------------- /packages/mapviewer/src/utils/composables/useComponentUniqueId.js: -------------------------------------------------------------------------------- 1 | export function useComponentUniqueId(name) { 2 | return `${name}-${Date.now() + Math.random()}` 3 | } 4 | -------------------------------------------------------------------------------- /packages/mapviewer/src/utils/debounce.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Debounce function to delay execution if called repeatedly. 3 | * 4 | * @param {Function} target The actual function to execute. 5 | * @param {Number} delay The time to wait for another call. 6 | * @returns The function that can be called repeatedly. 7 | */ 8 | 9 | export default function debounce any>( 10 | target: T, 11 | delay: number 12 | ): (...args: any[]) => void { 13 | let timeout: number 14 | 15 | return function (this: T, ...args: any[]) { 16 | clearTimeout(timeout) 17 | timeout = window.setTimeout(() => { 18 | // Call the target function the way the debounced function was called. 19 | // Passing `this` isn't necessary with arrow-functions but it doesn't hurt. 20 | return target.apply(this, args) 21 | }, delay) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/mapviewer/src/utils/searchParamUtils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This will remove the query param from the URL It is necessary to do this in vanilla JS because 3 | * the router does not provide a way to remove a query without reloading the page which then removes 4 | * the value from the store. 5 | * 6 | * @param {Object} key The key to remove from the URL 7 | */ 8 | export function removeQueryParamFromHref(key) { 9 | const [baseUrl, queryString] = window.location.href.split('?') 10 | if (!queryString) { 11 | return 12 | } 13 | 14 | const params = new URLSearchParams(queryString) 15 | if (!params.has(key)) { 16 | return 17 | } 18 | params.delete(key) 19 | 20 | const newQueryString = params.toString() 21 | const newUrl = newQueryString ? `${baseUrl}?${newQueryString}` : baseUrl 22 | window.history.replaceState({}, document.title, newUrl) 23 | } 24 | -------------------------------------------------------------------------------- /packages/mapviewer/src/views/LegacyParamsView.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 26 | 27 | 40 | -------------------------------------------------------------------------------- /packages/mapviewer/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./src/**/*.{vue,js,ts,jsx,tsx}", 5 | ], 6 | theme: { 7 | extend: {}, 8 | }, 9 | plugins: [], 10 | // to ease cohabitation with Bootstrap, can be removed once we've migrated away from Bootstrap 11 | important: true, 12 | } 13 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/256.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/tests/cypress/fixtures/256.jpeg -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/tests/cypress/fixtures/256.png -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/3d/terrain-3d-layer.json: -------------------------------------------------------------------------------- 1 | { 2 | "attribution": "Put something there", 3 | "available": [ 4 | [ 5 | { 6 | "endX": 1, 7 | "endY": 0, 8 | "startX": 0, 9 | "startY": 0 10 | } 11 | ], 12 | [ 13 | { 14 | "endX": 3, 15 | "endY": 1, 16 | "startX": 0, 17 | "startY": 0 18 | } 19 | ] 20 | ], 21 | "bounds": [-180, -90, 180, 90], 22 | "description": "Nice terrains", 23 | "format": "quantized-mesh-1.0", 24 | "minzoom": 0, 25 | "projection": "EPSG:4326", 26 | "scheme": "tms", 27 | "tilejson": "2.1.0", 28 | "tiles": ["20201203/{z}/{x}/{y}.terrain?v={version}"], 29 | "version": "3924.0.0" 30 | } 31 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/3d/tile.terrain: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/tests/cypress/fixtures/3d/tile.terrain -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/3d/tile.vctr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/tests/cypress/fixtures/3d/tile.vctr -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/3d/tileset.json: -------------------------------------------------------------------------------- 1 | { 2 | "asset": { 3 | "version": "1.0", 4 | "tilesetVersion": "1.0" 5 | }, 6 | "geometricError": 4800, 7 | "root": { 8 | "boundingVolume": { 9 | "region": [ 10 | 0.10421183582826843, 0.7998092902782942, 0.18261035364587425, 0.8341285334461268, 11 | 193, 4632.359141026985 12 | ] 13 | }, 14 | "geometricError": 4800, 15 | "refine": "ADD", 16 | "children": [ 17 | { 18 | "boundingVolume": { 19 | "region": [ 20 | 0.10421183582826843, 0.7998092902782942, 0.18261035364587425, 21 | 0.8341285334461268, 193, 4632.359141026985 22 | ] 23 | }, 24 | "content": { 25 | "uri": "123/456/tile.vctr" 26 | }, 27 | "geometricError": 4800 28 | } 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/features/featureDetail.fixture.json: -------------------------------------------------------------------------------- 1 | { 2 | "feature": { 3 | "type": "Feature", 4 | "featureId": 1234, 5 | "bbox": [2600000.0, 1200000.0, 2600000.0, 1200000.0], 6 | "layerBodId": "test.wms.layer", 7 | "layerName": "WMS test layer", 8 | "id": 1234, 9 | "geometry": { 10 | "type": "MultiPoint", 11 | "coordinates": [[2600000.0, 1200000.0]] 12 | }, 13 | "properties": { 14 | "name": "Feature 1234 title", 15 | "label": "Feature 1234 label" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/features/features.fixture.json: -------------------------------------------------------------------------------- 1 | { 2 | "results": [ 3 | { 4 | "geometry": { 5 | "type": "MultiPoint", 6 | "coordinates": [[2600000.0, 1200000.0]] 7 | }, 8 | "layerBodId": "test.wms.layer", 9 | "bbox": [2600000.0, 1200000.0, 2600000.0, 1200000.0], 10 | "featureId": 1234, 11 | "layerName": "WMS test layer", 12 | "type": "Feature", 13 | "id": 1234, 14 | "properties": { 15 | "label": "Feature 1234", 16 | "link_title": "Feature 1234 link title", 17 | "link_uri": "https://fake.link.feature_1234.ch", 18 | "link_2_title": null, 19 | "link_2_uri": null, 20 | "link_3_title": null, 21 | "link_3_uri": null, 22 | "x": 2600000.0, 23 | "y": 1200000.0, 24 | "lon": 7.43863, 25 | "lat": 46.95108 26 | } 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/geojson-style.fixture.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "unique", 3 | "property": "vorhersage-class", 4 | "values": [ 5 | { 6 | "geomType": "point", 7 | "value": 0, 8 | "vectorOptions": { 9 | "type": "circle", 10 | "radius": 8, 11 | "fill": { 12 | "color": "#808080" 13 | }, 14 | "stroke": { 15 | "color": "#FFFFFF", 16 | "width": 1 17 | } 18 | } 19 | }, 20 | { 21 | "geomType": "point", 22 | "value": 1, 23 | "vectorOptions": { 24 | "type": "triangle", 25 | "radius": 12, 26 | "rotation": 1.0471975511965976, 27 | "fill": { 28 | "color": "#808080" 29 | }, 30 | "stroke": { 31 | "color": "#FFFFFF", 32 | "width": 1 33 | } 34 | } 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/import-tool/empty.kml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | Empty KML 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/import-tool/external-gpx-file.gpx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | geoadmin cypress test 5 | 6 | Geoadmin 7 | 8 | 9 | 10 | 11 | Test track 12 | Testing 13 | 14 | 15 | 1000.0 16 | 17 | 18 | 1200.0 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/import-tool/external-kml-file.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sample KML File 5 | 6 | Sample Placemark 7 | This is a sample KML Placemark. 8 | 9 | 9.749210,46.707841 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/import-tool/kml_feature_error.kml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 1 6 | 1 7 | GPS Visualizer]]> 8 | 9 | Tracks 10 | 1 11 | 0 12 | 13 | 14 | 15 | 16 | 22 | 23 | 1 24 | clampToGround 25 | 8.511875,47.35233 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/import-tool/legend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/tests/cypress/fixtures/import-tool/legend.png -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/import-tool/second-external-kml-file.kml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Another KML 5 | 6 | Another Sample Placemark 7 | This is a sample KML Placemark. 8 | 9 | 8.117189,46.852375 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/post-kml.fixture.json: -------------------------------------------------------------------------------- 1 | { 2 | "admin_id": "jNE8NfIrQ7qQO0wnWCagQQ", 3 | "author": "web-mapviewer", 4 | "author_version": "1.0.0", 5 | "created": "2025-01-27T08:23:07.158+00:00", 6 | "empty": false, 7 | "id": "ARY66ohMQqSXwG4FA2pqeA", 8 | "links": { 9 | "kml": "https://sys-public.dev.bgdi.ch/api/kml/files/ARY66ohMQqSXwG4FA2pqeA", 10 | "self": "https://sys-public.dev.bgdi.ch/api/kml/admin/ARY66ohMQqSXwG4FA2pqeA" 11 | }, 12 | "success": true, 13 | "updated": "2025-01-27T08:23:07.158+00:00" 14 | } 15 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/print/label.kml: -------------------------------------------------------------------------------- 1 | DrawingSample Label0,-44.75marker7.629431833399272,47.0431253777804 19 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/print/line-and-marker.gpx: -------------------------------------------------------------------------------- 1 | Marker 1markerlinepolygon -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/print/mapfish-print-report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/tests/cypress/fixtures/print/mapfish-print-report.pdf -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/print/old-geoadmin-label.kml: -------------------------------------------------------------------------------- 1 | DrawingmarkerOld Label1clampToGround8.161491779847474,46.97804233732786 19 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/service-alti/height.fixture.json: -------------------------------------------------------------------------------- 1 | { "height": "fakeheight" } 2 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/service-alti/profile.fixture.csv: -------------------------------------------------------------------------------- 1 | Distance;Altitude;Easting;Northing;Longitude;Latitude 2 | 0;1341.7;2704280.99;1170235.99;8.801551;46.675203 3 | 2.5;1341.8;2704283.24;1170234.98;8.80158;46.675193 4 | 3;1341.8;2704283.77;1170234.74;8.801587;46.675191 5 | 4.5;1341.7;2704285.14;1170234.13;8.801605;46.675185 6 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/service-alti/profile.fixture.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "dist": 0.0, 4 | "alts": { "DTM2": 1341.7, "COMB": 1341.7, "DTM25": 1341.7 }, 5 | "easting": 2704280.989, 6 | "northing": 1170235.988 7 | }, 8 | { 9 | "dist": 2.5, 10 | "alts": { "DTM2": 1341.8, "COMB": 1341.8, "DTM25": 1341.8 }, 11 | "easting": 2704283.24, 12 | "northing": 1170234.979 13 | }, 14 | { 15 | "dist": 3.0, 16 | "alts": { "DTM2": 1341.8, "COMB": 1341.8, "DTM25": 1341.8 }, 17 | "easting": 2704283.769, 18 | "northing": 1170234.741 19 | }, 20 | { 21 | "dist": 4.5, 22 | "alts": { "DTM2": 1341.7, "COMB": 1341.7, "DTM25": 1341.7 }, 23 | "easting": 2704285.137, 24 | "northing": 1170234.128 25 | } 26 | ] 27 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/service-alti/profile.too.big.error.fixture.json: -------------------------------------------------------------------------------- 1 | { 2 | "error": { 3 | "code": 413, 4 | "message": "Request Geometry contains too many points. Maximum number of points allowed: 123, found 456" 5 | }, 6 | "success": false 7 | } 8 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/service-icons/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/tests/cypress/fixtures/service-icons/placeholder.png -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/service-icons/sets.fixture.json: -------------------------------------------------------------------------------- 1 | { 2 | "items": [ 3 | { 4 | "colorable": false, 5 | "icons_url": "http://localhost:8080/api/icons/sets/babs/icons", 6 | "description_url": "http://localhost:8080/api/icons/sets/babs/description", 7 | "name": "babs" 8 | }, 9 | { 10 | "colorable": true, 11 | "icons_url": "http://localhost:8080/api/icons/sets/default/icons", 12 | "description_url": null, 13 | "name": "default" 14 | } 15 | ], 16 | "success": true 17 | } 18 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/service-kml/lonelyMarker.kml: -------------------------------------------------------------------------------- 1 | 7 | 8 | Dessin 9 | 10 | 11 | 32 | 33 | 34 | marker 35 | 36 | 37 | 38 | 7.656108679791837,46.883715999352546 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/service-qrcode/position-popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/tests/cypress/fixtures/service-qrcode/position-popup.png -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/what3word.fixture.json: -------------------------------------------------------------------------------- 1 | { 2 | "country": "CH", 3 | "coordinates": { 4 | "lat": 1234, 5 | "lng": 5678 6 | }, 7 | "words": "fake.what3words", 8 | "language": "en", 9 | "map": "https://w3w.co/what3words" 10 | } 11 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/fixtures/wms-geo-admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoadmin/web-mapviewer/8fa2cf2ad273779265d2dfad91c8c4b96f47b90f/packages/mapviewer/tests/cypress/fixtures/wms-geo-admin.png -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/support/component-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 13 | Components App 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/support/component.js: -------------------------------------------------------------------------------- 1 | import '../../../src/scss/main.scss' 2 | 3 | import 'cypress-real-events' 4 | import { mount } from 'cypress/vue' 5 | 6 | import i18n from '@/modules/i18n' 7 | 8 | Cypress.Commands.add('mount', (component, options = {}) => { 9 | // Setup options object 10 | options.global = options.global || {} 11 | options.global.stubs = options.global.stubs || {} 12 | options.global.stubs['transition'] = false 13 | options.global.components = options.global.components || {} 14 | options.global.plugins = options.global.plugins || [] 15 | 16 | /* Add any global plugins */ 17 | options.global.plugins.push({ 18 | install(app) { 19 | app.use(i18n) 20 | }, 21 | }) 22 | 23 | /* Add any global components */ 24 | 25 | return mount(component, options) 26 | }) 27 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/command.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | import './drawing' 19 | 20 | // Alternatively you can use CommonJS syntax: 21 | // require('./commands') 22 | 23 | Cypress.on('uncaught:exception', (err, runnable) => { 24 | Cypress.log({ 25 | name: 'Uncaught error!', 26 | message: err.message, 27 | consoleProps() { 28 | return { 29 | err, 30 | runnable, 31 | } 32 | }, 33 | }) 34 | // returning false here prevents Cypress from failing the test 35 | return false 36 | }) 37 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/support/utils.js: -------------------------------------------------------------------------------- 1 | import { BREAKPOINT_TABLET } from '@/config/responsive.config' 2 | 3 | export function isMobile() { 4 | return Cypress.config('viewportWidth') < BREAKPOINT_TABLET 5 | } 6 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/tests-e2e/ribbon.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | const ribbonSelector = '[data-cy="warning-ribbon"]' 4 | const BREAKPOINT_PHONE_WIDTH = 576 5 | 6 | describe('Testing the warning ribbon', () => { 7 | beforeEach(() => { 8 | cy.goToMapView() 9 | }) 10 | context('Checking the warning ribbon', () => { 11 | /* 12 | * Normally, 'localhost' in not in the `WARNING_RIBBON_HOSTNAMES`constant, 13 | * so the warning banner is not present on Cypress. 14 | */ 15 | 16 | it("Ribbon shouldn't be be visible on localhost", () => { 17 | cy.get(ribbonSelector).should('not.exist') 18 | }) 19 | 20 | it('If ribbon do exist, it should be at top on a phone, bottom on desktop', () => { 21 | // Conditional tests are bad on Cypress, but the banner we or not be present, 22 | // depending on the hostname 23 | cy.get(ribbonSelector) 24 | // Bypassing the built-in existence assertion 25 | .should(Cypress._.noop) 26 | .then(($el) => { 27 | if (!$el.length) { 28 | return 29 | } 30 | if (Cypress.config().viewportWidth < BREAKPOINT_PHONE_WIDTH) { 31 | cy.get(ribbonSelector).invoke('css', 'top').should('equal', '78px') 32 | } else { 33 | cy.get(ribbonSelector).invoke('css', 'bottom').should('equal', '50px') 34 | } 35 | }) 36 | }) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/cypress/tests-e2e/utils.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | export function moveTimeSlider(x) { 4 | cy.get('[data-cy="time-slider-bar-cursor-grab"]').trigger('mousedown', { button: 0 }) 5 | cy.get('[data-cy="time-slider-bar-cursor-grab"]').trigger('mousemove', { 6 | screenX: Math.abs(x), 7 | screenY: 0, 8 | }) 9 | cy.get('[data-cy="time-slider-bar-cursor-grab"]').trigger('mouseup', { force: true }) 10 | } 11 | 12 | export function getGeolocationButtonAndClickIt() { 13 | const geolocationButtonSelector = '[data-cy="geolocation-button"]' 14 | cy.get(geolocationButtonSelector).should('be.visible').click() 15 | } 16 | 17 | export function testErrorMessage(message) { 18 | const geolocationButtonSelector = '[data-cy="geolocation-button"]' 19 | // move the mouse away from the button because the tooltip covers the 20 | // error message 21 | cy.get(geolocationButtonSelector).trigger('mousemove', { clientX: 0, clientY: 0, force: true }) // Check error in store 22 | 23 | // Check error in store 24 | cy.readStoreValue('state.ui.errors').then((errors) => { 25 | expect(errors).to.be.an('Set') 26 | // Make sure this is the only error (we don't want to test other errors) 27 | expect(errors.size).to.eq(1) 28 | 29 | const error = errors.values().next().value 30 | expect(error.msg).to.eq(message) 31 | }) 32 | // Check error in UI 33 | cy.get('[data-cy="error-window"]').should('be.visible') 34 | cy.get('[data-cy="error-window-close"]').should('be.visible').click() // close the error window 35 | } 36 | 37 | export function checkStorePosition(storeString, x, y) { 38 | cy.readStoreValue(storeString).then((center) => { 39 | expect(center).to.be.an('Array') 40 | expect(center.length).to.eq(2) 41 | expect(center[0]).to.approximately(x, 0.1) 42 | expect(center[1]).to.approximately(y, 0.1) 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /packages/mapviewer/tests/setup-vitest.ts: -------------------------------------------------------------------------------- 1 | import { registerProj4 } from '@geoadmin/coordinates' 2 | import proj4 from 'proj4' 3 | import { beforeAll } from 'vitest' 4 | 5 | beforeAll(() => { 6 | registerProj4(proj4) 7 | }) 8 | -------------------------------------------------------------------------------- /packages/mapviewer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.shared.json", 3 | "compilerOptions": { 4 | // see https://www.typescriptlang.org/tsconfig#allowJs 5 | // see https://github.com/vuejs/eslint-config-typescript?tab=readme-ov-file#advanced-setup 6 | "allowJs": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/mapviewer/vite-plugins/vite-plugin-watch-node-modules.ts: -------------------------------------------------------------------------------- 1 | import { ViteDevServer } from 'vite' 2 | 3 | /** @see https://github.com/vitejs/vite/issues/8619#issuecomment-1579552753 */ 4 | export function pluginWatchNodeModules(modules) { 5 | // Merge module into pipe separated string for RegExp() below. 6 | const pattern = `/node_modules\\/(?!${modules.join('|')}).*/` 7 | return { 8 | name: 'watch-node-modules', 9 | configureServer: (server: ViteDevServer): void => { 10 | server.watcher.options = { 11 | ...server.watcher.options, 12 | ignored: [new RegExp(pattern), '**/.git/**'], 13 | } 14 | }, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | "stylelint-config-recommended-vue", 4 | "stylelint-config-recommended-scss" 5 | ], 6 | plugins: ["stylelint-scss", "stylelint-order"], 7 | overrides: [ 8 | { 9 | files: ["**/*.vue"], 10 | customSyntax: "postcss-html" 11 | } 12 | ], 13 | rules: { 14 | "no-duplicate-selectors": null, // Deactivating rule to allow duplicate selectors to follow the new sass expected bahaviour https://sass-lang.com/documentation/at-rules/mixin/ 15 | "property-no-unknown": [true, { // Deactivating rule to allow unknown properties in :export selectors 16 | "ignoreSelectors": [":export"] 17 | }], 18 | "declaration-property-value-no-unknown": [true, { 19 | "ignoreProperties": { 20 | "/.+/": "/$/", // ignore variables with a $ (i have not found a way to make it work only if the $ is at the beginning since "/^\\$/" does not work at all) 21 | } 22 | }], 23 | "selector-pseudo-class-no-unknown": [ // Deactivating rule to allow pseudo classes like :global 24 | true, 25 | { 26 | "ignorePseudoClasses": [ 27 | "global", 28 | "export", 29 | "deep", 30 | ] 31 | } 32 | ], 33 | } 34 | }; -------------------------------------------------------------------------------- /tsconfig.shared.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": [ 4 | "${configDir}/env.d.ts", 5 | "${configDir}/src/**/*.ts", 6 | "${configDir}/src/**/*.json", 7 | "${configDir}/src/**/*.vue" 8 | ], 9 | "exclude": ["${configDir}/src/**/__tests__/*"], 10 | "compilerOptions": { 11 | // see https://www.typescriptlang.org/docs/handbook/modules/guides/choosing-compiler-options.html#im-using-a-bundler 12 | "module": "ESNext", 13 | "moduleResolution": "bundler", 14 | "esModuleInterop": true, 15 | "lib": [ 16 | "DOM", 17 | "ESNext.Array", 18 | "ESNext" 19 | ], 20 | 21 | // see https://vite.dev/guide/features.html#typescript-compiler-options 22 | "isolatedModules": true, 23 | "useDefineForClassFields": true, 24 | 25 | "baseUrl": "${configDir}", 26 | "outDir": "${configDir}/dist", 27 | "paths": { 28 | "@/*": ["${configDir}/src/*"] 29 | }, 30 | "typeRoots": ["node_modules/@types"] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /vite.config.shared.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'url' 2 | import dts from 'vite-plugin-dts' 3 | 4 | export default { 5 | resolve: { 6 | alias: { 7 | '@': fileURLToPath(new URL('./src', import.meta.url)), 8 | }, 9 | }, 10 | plugins: [dts()], 11 | } 12 | -------------------------------------------------------------------------------- /vitest.workspace.js: -------------------------------------------------------------------------------- 1 | import { defineWorkspace } from 'vitest/config' 2 | 3 | export default defineWorkspace([ 4 | './vite.config.shared.js', 5 | './packages/geoadmin-numbers/vite.config.js', 6 | './packages/geoadmin-coordinates/vite.config.js', 7 | './packages/mapviewer/vite.config.mts', 8 | './packages/geoadmin-log/vite.config.js', 9 | ]) 10 | --------------------------------------------------------------------------------