From 0fc3c74bb600e3ae4cb2706a3b9fe2565a372258 Mon Sep 17 00:00:00 2001 From: gimonaa <125867318+gimonaa@users.noreply.github.com> Date: Thu, 27 Jun 2024 13:33:13 +0200 Subject: [PATCH] docs: added an example for creating a dynamic Leaflet map (#6599) --------- Co-authored-by: gioboa --- packages/docs/package.json | 2 + .../demo/cookbook/leaflet-map/index.tsx | 174 ++++++++++++++ .../docs/integrations/leaflet-map/index.mdx | 222 ++++++++++++++++++ pnpm-lock.yaml | 23 ++ 4 files changed, 421 insertions(+) create mode 100644 packages/docs/src/routes/demo/cookbook/leaflet-map/index.tsx diff --git a/packages/docs/package.json b/packages/docs/package.json index abd093a3d4b..f5e9e420d24 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -21,6 +21,7 @@ "@mui/system": "^5.15.14", "@mui/x-data-grid": "^6.19.6", "@supabase/supabase-js": "^2.39.8", + "@types/leaflet": "^1.9.12", "@types/prismjs": "^1.26.3", "@types/react": "^18.2.79", "@types/react-dom": "^18.2.25", @@ -30,6 +31,7 @@ "autoprefixer": "^10.4.18", "fflate": "^0.8.2", "gray-matter": "4.0.3", + "leaflet": "^1.9.4", "openai": "^3.3.0", "postcss": "^8.4.37", "prettier": "^3.2.5", diff --git a/packages/docs/src/routes/demo/cookbook/leaflet-map/index.tsx b/packages/docs/src/routes/demo/cookbook/leaflet-map/index.tsx new file mode 100644 index 00000000000..f65ea1308bb --- /dev/null +++ b/packages/docs/src/routes/demo/cookbook/leaflet-map/index.tsx @@ -0,0 +1,174 @@ +import { + component$, + noSerialize, + useSignal, + useStyles$, + useVisibleTask$, + type Signal, +} from '@builder.io/qwik'; +import * as L from 'leaflet'; +import leafletStyles from 'leaflet/dist/leaflet.css?inline'; + +export default component$(() => { + useStyles$( + leafletStyles + + ` + .marker-label { + color: red; + font-weight: 700; + } + ` + ); + + const markers: Record = { + FDA: [ + { + name: "Terzo d'Aquileia", + label: 'TRZ', + lat: '45.770946', + lon: '13.31338', + }, + { + name: 'Musi', + label: 'MUS', + lat: '46.312663', + lon: '13.274682', + }, + ], + FVG: [ + { + name: 'Borgo Grotta Gigante', + label: 'BGG', + lat: '45.709385', + lon: '13.764681', + }, + { + name: 'Muggia', + label: 'MGG', + lat: '45.610495', + lon: '13.752682', + }, + ], + }; + + const groupSig = useSignal('FDA'); + const currentLocation = useSignal({ + name: 'Udine', + point: [46.06600881056668, 13.237724558490601], + zoom: 10, + marker: true, + }); + + return ( + <> + Change markers:{' '} + + + + ); +}); + +// The properties (props) used in the `LeafletMap` component and other related components are defined as follows: + +export interface MapProps { + location: Signal; + markers?: MarkersProps[]; + group?: Signal; +} + +export interface LocationsProps { + name: string; + point: [number, number]; + zoom: number; + marker: boolean; +} + +export interface MarkersProps { + name: string; + label: string; + lat: string; + lon: string; +} + +/* +The `LeafletMap` component leverages the Leaflet library to render an interactive map. +This component can be configured with various properties (props) to set the central location, add markers, and draw boundaries. +In the `LeafletMap` component, both the location and the group signal are tracked. +This ensures that when the signal changes, the server function is called, and the map is updated with the new data. +*/ + +export const LeafletMap = component$( + ({ location, markers, group }) => { + const mapContainerSig = useSignal(); + + useVisibleTask$(async ({ track }) => { + track(location); + group && track(group); + + if (mapContainerSig.value) { + mapContainerSig.value.remove(); + } + + // center location + const { value: locationData } = location; + const centerPosition = locationData.point; + + // layers + const markersLayer = new L.LayerGroup(); + const bordersLayer = new L.LayerGroup(); + + // map + const map = L.map('map', { + layers: [markersLayer, bordersLayer], + }).setView(centerPosition, locationData.zoom || 14); + L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + attribution: + '© OpenStreetMap', + }).addTo(map); + + // center position marker + + const qwikMarker = L.divIcon({ + html: ` + + + + + + `, + className: '', + iconSize: [24, 40], + }); + + locationData.marker && + L.marker(centerPosition, { icon: qwikMarker }) + .bindPopup(`Udine`) + .addTo(map); + + // add markers to map + const markersList = await markers; + markersList && + markersList.map((m) => { + const myIcon = L.divIcon({ + className: 'marker-point', + html: `
${m.label}
`, + }); + L.marker([+m.lat, +m.lon], { icon: myIcon }).addTo(markersLayer); + }); + + mapContainerSig.value = noSerialize(map); + }); + + return
; + } +); diff --git a/packages/docs/src/routes/docs/integrations/leaflet-map/index.mdx b/packages/docs/src/routes/docs/integrations/leaflet-map/index.mdx index 760551dc452..d2ba4614f83 100644 --- a/packages/docs/src/routes/docs/integrations/leaflet-map/index.mdx +++ b/packages/docs/src/routes/docs/integrations/leaflet-map/index.mdx @@ -5,10 +5,14 @@ contributors: - mugan86 - igorbabko - anartzdev + - gimonaa + - gioboa updated_at: '2023-10-03T18:53:23Z' created_at: '2023-09-13T18:55:37Z' --- +import CodeSandbox from '../../../../components/code-sandbox/index.tsx'; + # LeafletJS Map Leaflet is the leading open-source JavaScript library for mobile-friendly interactive maps. @@ -38,6 +42,224 @@ It also adds new files to your project folder: - `src/components/leaflet-map/index.tsx`: Leaflet map simple map features component. - `src/routes/basic-map/index.tsx`: Example to consume Leaflet Map component with demo data +## Example + +The main component configures the map, including the initial position and the group of markers to load. +This setup allows you to create a dynamic and interactive map that can be easily configured and extended with different locations and markers. + +### Here is an example: + + +```tsx +import { + component$, + noSerialize, + useSignal, + useStyles$, + useVisibleTask$, + type Signal, +} from '@builder.io/qwik'; +import * as L from 'leaflet'; +import leafletStyles from 'leaflet/dist/leaflet.css?inline'; + +// Sample data json and geojson + +export const fvg: any = { + type: 'FeatureCollection', + name: 'FVG_line_0_001', + crs: { type: 'name', properties: { name: 'urn:ogc:def:crs:OGC:1.3:CRS84' } }, + features: [ + { + type: 'Feature', + properties: { ID_OGG: '08020060000', NAME: 'GEOJSON NAME' }, + geometry: { + type: 'MultiLineString', + coordinates: [ + [ + [12.4188, 46.3528], + [12.4178, 46.3547], + [12.4284, 46.3517], + [12.4425, 46.3599], + [12.4488, 46.3605], + [12.4554, 46.3652], + [12.4552, 46.3672], + [12.4513, 46.3706], + ], + ], + }, + }, + ], +}; + +const markers: Record = { + FDA: [ + { + name: "Terzo d'Aquileia", + label: 'TRZ', + lat: '45.770946', + lon: '13.31338', + }, + { + name: 'Musi', + label: 'MUS', + lat: '46.312663', + lon: '13.274682', + }, + ], + FVG: [ + { + name: 'Borgo Grotta Gigante', + label: 'BGG', + lat: '45.709385', + lon: '13.764681', + }, + { + name: 'Muggia', + label: 'MGG', + lat: '45.610495', + lon: '13.752682', + }, + ], +}; + +export default component$(() => { + useStyles$( + leafletStyles + + ` + .marker-label { + color: red; + font-weight: 700; + } + ` + ); + + const groupSig = useSignal('FDA'); + const currentLocation = useSignal({ + name: 'Udine', + point: [46.06600881056668, 13.237724558490601], + zoom: 10, + marker: true, + }); + + return ( + <> + Change markers:{' '} + + + + ); +}); + +// The properties (props) used in the `LeafletMap` component and other related components are defined as follows: + +export interface MapProps { + location: Signal; + markers?: MarkersProps[]; + group?: Signal; +} + +export interface LocationsProps { + name: string; + point: [number, number]; + zoom: number; + marker: boolean; +} + +export interface MarkersProps { + name: string; + label: string; + lat: string; + lon: string; +} + +/* +The `LeafletMap` component leverages the Leaflet library to render an interactive map. +This component can be configured with various properties (props) to set the central location, add markers, and draw boundaries. +In the `LeafletMap` component, both the location and the group signal are tracked. +This ensures that when the signal changes, the server function is called, and the map is updated with the new data. +*/ + +export const LeafletMap = component$( + ({ location, markers, group }) => { + const mapContainerSig = useSignal(); + + useVisibleTask$(async ({ track }) => { + track(location); + group && track(group); + + if (mapContainerSig.value) { + mapContainerSig.value.remove(); + } + + // center location + const { value: locationData } = location; + const centerPosition = locationData.point; + + // layers + const markersLayer = new L.LayerGroup(); + const bordersLayer = new L.LayerGroup(); + + // map + const map = L.map('map', { + layers: [markersLayer, bordersLayer], + }).setView(centerPosition, locationData.zoom || 14); + L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { + maxZoom: 19, + attribution: + '© OpenStreetMap', + }).addTo(map); + + // center position marker + + const qwikMarker = L.divIcon({ + html: ` + + + + + + `, + className: '', + iconSize: [24, 40], + }); + + locationData.marker && + L.marker(centerPosition, { icon: qwikMarker }) + .bindPopup(`Udine`) + .addTo(map); + + // add boundaries to map + L.geoJSON(fvg, { style: { color: '#005DA4' } }).addTo(bordersLayer); + + // add markers to map + const markersList = await markers; + markersList && + markersList.map((m) => { + const myIcon = L.divIcon({ + className: 'marker-point', + html: `
${m.label}
`, + }); + L.marker([+m.lat, +m.lon], { icon: myIcon }).addTo(markersLayer); + }); + + mapContainerSig.value = noSerialize(map); + }); + + return
; + } +); +``` +
+ ## Interesting info about LeafletJS Map: ### Official diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 07cc647deee..35368446b19 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -296,6 +296,9 @@ importers: '@supabase/supabase-js': specifier: ^2.39.8 version: 2.43.1 + '@types/leaflet': + specifier: ^1.9.12 + version: 1.9.12 '@types/prismjs': specifier: ^1.26.3 version: 1.26.3 @@ -323,6 +326,9 @@ importers: gray-matter: specifier: 4.0.3 version: 4.0.3 + leaflet: + specifier: ^1.9.4 + version: 1.9.4 openai: specifier: ^3.3.0 version: 3.3.0 @@ -2997,6 +3003,9 @@ packages: '@types/express@4.17.21': resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + '@types/geojson@7946.0.14': + resolution: {integrity: sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==} + '@types/hast@2.3.10': resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} @@ -3024,6 +3033,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/leaflet@1.9.12': + resolution: {integrity: sha512-BK7XS+NyRI291HIo0HCfE18Lp8oA30H1gpi1tf0mF3TgiCEzanQjOqNZ4x126SXzzi2oNSZhZ5axJp1k0iM6jg==} + '@types/mdast@4.0.3': resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==} @@ -6468,6 +6480,9 @@ packages: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} engines: {node: '>= 0.6.3'} + leaflet@1.9.4: + resolution: {integrity: sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==} + leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -12013,6 +12028,8 @@ snapshots: '@types/qs': 6.9.11 '@types/serve-static': 1.15.5 + '@types/geojson@7946.0.14': {} + '@types/hast@2.3.10': dependencies: '@types/unist': 2.0.10 @@ -12041,6 +12058,10 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/leaflet@1.9.12': + dependencies: + '@types/geojson': 7946.0.14 + '@types/mdast@4.0.3': dependencies: '@types/unist': 3.0.2 @@ -16015,6 +16036,8 @@ snapshots: dependencies: readable-stream: 2.3.8 + leaflet@1.9.4: {} + leven@3.1.0: {} levn@0.4.1: