@premiate/strapi-plugin-maplibre-field 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @premiate/strapi-plugin-maplibre-field might be problematic. Click here for more details.
- package/CHANGELOG.md +77 -0
- package/LICENSE +23 -0
- package/README.md +781 -0
- package/dist/_chunks/de-CGU2cyif.mjs +16 -0
- package/dist/_chunks/de-Dq_t3Z6M.js +16 -0
- package/dist/_chunks/en-BxxNWf9i.mjs +16 -0
- package/dist/_chunks/en-CgSPA-1L.js +16 -0
- package/dist/_chunks/es-B_cPv3G5.mjs +16 -0
- package/dist/_chunks/es-Sgja1XAa.js +16 -0
- package/dist/_chunks/fr-B3JIzyzo.js +16 -0
- package/dist/_chunks/fr-Dw5wEoDC.mjs +16 -0
- package/dist/_chunks/index-BF5T-kqa.mjs +171 -0
- package/dist/_chunks/index-BNnkn7JG.mjs +1778 -0
- package/dist/_chunks/index-CGJogtZr.js +1799 -0
- package/dist/_chunks/index-nbk0hg-O.js +170 -0
- package/dist/_chunks/it-BgWDIXzn.js +16 -0
- package/dist/_chunks/it-CoUEVPt6.mjs +16 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/index.mjs +4 -0
- package/dist/admin/src/components/Initializer.d.ts +6 -0
- package/dist/admin/src/components/MapInput/basemap-control.d.ts +8 -0
- package/dist/admin/src/components/MapInput/credits-control.d.ts +10 -0
- package/dist/admin/src/components/MapInput/geocoder-control.d.ts +20 -0
- package/dist/admin/src/components/MapInput/index.d.ts +19 -0
- package/dist/admin/src/components/MapInput/layer-control.d.ts +18 -0
- package/dist/admin/src/components/PluginIcon.d.ts +2 -0
- package/dist/admin/src/hooks/usePluginConfig.d.ts +2 -0
- package/dist/admin/src/index.d.ts +16 -0
- package/dist/admin/src/mutations/mutateEditViewHook.d.ts +30 -0
- package/dist/admin/src/services/poi-service.d.ts +160 -0
- package/dist/admin/src/utils/getTrad.d.ts +2 -0
- package/dist/admin/src/utils/pluginId.d.ts +2 -0
- package/dist/admin/src/utils/prefixPluginTranslations.d.ts +3 -0
- package/dist/server/index.js +107 -0
- package/dist/server/index.mjs +108 -0
- package/dist/server/src/bootstrap.d.ts +2 -0
- package/dist/server/src/config/index.d.ts +61 -0
- package/dist/server/src/config/schema.d.ts +58 -0
- package/dist/server/src/controllers/config.d.ts +7 -0
- package/dist/server/src/controllers/index.d.ts +8 -0
- package/dist/server/src/destroy.d.ts +5 -0
- package/dist/server/src/index.d.ts +85 -0
- package/dist/server/src/register.d.ts +5 -0
- package/dist/server/src/routes/index.d.ts +9 -0
- package/dist/server/src/types/config.d.ts +26 -0
- package/logo.png +0 -0
- package/package.json +99 -0
package/README.md
ADDED
|
@@ -0,0 +1,781 @@
|
|
|
1
|
+
# MapLibre Field - Strapi v5 Plugin
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@premiate/strapi-plugin-maplibre-field)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://strapi.io)
|
|
6
|
+
|
|
7
|
+
A [Strapi](https://strapi.io/) plugin that provides a [MapLibre](https://www.maplibre.org/) map custom field for your content-types, allowing for mutiple base maps and multiple POI layers configuration, storing GeoJSON Features behind the scene.
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
You can use the search box to pinpoint the location you are looking for. Alternatively, you can double-click anywhere on the map, which will put a marker at the exact point and set longitude and latitude. Any Point Of Interest on the base map or while setting the address at the closest geolocated point on OpenStreetMap.
|
|
12
|
+
|
|
13
|
+
The longitude and latitude of the geolocated point are displayed in the readonly fields underneath the map. The address matches the closest geolocated point.
|
|
14
|
+
|
|
15
|
+
The field is stored as a standard [GeoJSON Feature](https://geojson.org/) (RFC 7946) in a JSON field:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"type": "Feature",
|
|
20
|
+
"geometry": {
|
|
21
|
+
"type": "Point",
|
|
22
|
+
"coordinates": [9.195433, 45.464181]
|
|
23
|
+
},
|
|
24
|
+
"properties": {
|
|
25
|
+
"name": "Comando Polizia Locale",
|
|
26
|
+
"address": "Piazza Cesare Beccaria, Duomo, Municipio 1, Milano, 20122, Italia",
|
|
27
|
+
"source": "nominatim",
|
|
28
|
+
"sourceId": "nominatim-12345678",
|
|
29
|
+
"category": "police",
|
|
30
|
+
"inputMethod": "search"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
See [Data Model](#data-model) for details on all properties.
|
|
36
|
+
|
|
37
|
+
## Table of Contents
|
|
38
|
+
|
|
39
|
+
- [Installation](#installation)
|
|
40
|
+
- [Configuration](#configuration)
|
|
41
|
+
- [Add a map field](#add-a-map-field-to-your-content-type)
|
|
42
|
+
- [Features](#features)
|
|
43
|
+
- [POI Selection](#poi-point-of-interest-selection)
|
|
44
|
+
- [Data Model](#data-model)
|
|
45
|
+
- [Contributing](#contributing)
|
|
46
|
+
- [Credits](#credits)
|
|
47
|
+
- [License](#license)
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
### Requirements
|
|
52
|
+
|
|
53
|
+
- Strapi v5.0.0 or higher
|
|
54
|
+
- Node.js 20.0.0 or higher
|
|
55
|
+
|
|
56
|
+
### Install
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
# Using Yarn
|
|
60
|
+
yarn add strapi-plugin-maplibre-field
|
|
61
|
+
|
|
62
|
+
# Or using NPM
|
|
63
|
+
npm install strapi-plugin-maplibre-field
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Configuration
|
|
67
|
+
|
|
68
|
+
### Enable the plugin
|
|
69
|
+
|
|
70
|
+
Create or update `config/plugins.js` (or `config/plugins.ts` for TypeScript):
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// config/plugins.ts
|
|
74
|
+
export default {
|
|
75
|
+
"maplibre-field": {
|
|
76
|
+
enabled: true,
|
|
77
|
+
config: {
|
|
78
|
+
// Optional: Customize map settings
|
|
79
|
+
mapStyles: [
|
|
80
|
+
{
|
|
81
|
+
id: "satellite",
|
|
82
|
+
name: "Satellite",
|
|
83
|
+
url: "https://api.maptiler.com/maps/satellite-v4/style.json?key=YOUR_API_KEY",
|
|
84
|
+
isDefault: true,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "osm",
|
|
88
|
+
name: "OpenStreetMap",
|
|
89
|
+
url: "https://api.maptiler.com/maps/openstreetmap/style.json?key=YOUR_API_KEY",
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
defaultZoom: 4.5,
|
|
93
|
+
defaultCenter: [9.19, 45.46], // [longitude, latitude] - Milano, Italy
|
|
94
|
+
geocodingProvider: "nominatim",
|
|
95
|
+
nominatimUrl: "https://nominatim.openstreetmap.org",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Configuration Options
|
|
102
|
+
|
|
103
|
+
| Option | Type | Default | Description |
|
|
104
|
+
| ------------------- | ------------------ | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
105
|
+
| `mapStyles` | `MapStyle[]` | See default config | Array of map style configurations. The map will open with the first style or the one marked as `isDefault: true` |
|
|
106
|
+
| `defaultZoom` | `number` | `4.5` | Initial zoom level (0-20) |
|
|
107
|
+
| `defaultCenter` | `[number, number]` | `[0, 0]` | Initial map center [longitude, latitude] |
|
|
108
|
+
| `geocodingProvider` | `string` | `'nominatim'` | Geocoding service provider |
|
|
109
|
+
| `nominatimUrl` | `string` | `'https://nominatim.openstreetmap.org'` | Nominatim API endpoint |
|
|
110
|
+
|
|
111
|
+
#### MapStyle Interface
|
|
112
|
+
|
|
113
|
+
Each map style in the `mapStyles` array has the following structure:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
interface MapStyle {
|
|
117
|
+
id: string; // Unique identifier for the style
|
|
118
|
+
name: string; // Display name shown in the basemap switcher
|
|
119
|
+
url: string; // URL to MapLibre style JSON
|
|
120
|
+
isDefault?: boolean; // Set to true for the default style (optional)
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Map Style Options
|
|
125
|
+
|
|
126
|
+
The plugin uses **MapLibre GL JS** for rendering, which supports any style that follows the [MapLibre Style Specification](https://maplibre.org/maplibre-style-spec/).
|
|
127
|
+
|
|
128
|
+
#### Default Configuration
|
|
129
|
+
|
|
130
|
+
By default, the plugin includes multiple map styles:
|
|
131
|
+
|
|
132
|
+
- **Satellite** view using MapTiler satellite imagery
|
|
133
|
+
- **OpenStreetMap** view with vector tiles
|
|
134
|
+
- Users can switch between styles using the basemap control in the map interface
|
|
135
|
+
|
|
136
|
+
#### Configuring Map Styles
|
|
137
|
+
|
|
138
|
+
You can configure multiple map styles that users can switch between. The map will initially open with the first style in the array, or the one marked with `isDefault: true`.
|
|
139
|
+
|
|
140
|
+
**1. MapLibre Demo Tiles** (Free, public):
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// In config/plugins.ts
|
|
144
|
+
export default {
|
|
145
|
+
"maplibre-field": {
|
|
146
|
+
enabled: true,
|
|
147
|
+
config: {
|
|
148
|
+
mapStyles: [
|
|
149
|
+
{
|
|
150
|
+
id: "demo",
|
|
151
|
+
name: "Demo",
|
|
152
|
+
url: "https://demotiles.maplibre.org/style.json",
|
|
153
|
+
isDefault: true,
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**2. MapTiler** (Requires API key):
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// In Strapi's config/plugins.ts
|
|
165
|
+
module.exports = ({ env }) => ({
|
|
166
|
+
"maplibre-field": {
|
|
167
|
+
enabled: true,
|
|
168
|
+
config: {
|
|
169
|
+
mapStyles: [
|
|
170
|
+
{
|
|
171
|
+
id: "streets",
|
|
172
|
+
name: "Streets",
|
|
173
|
+
url: `https://api.maptiler.com/maps/streets-v2/style.json?key=${env(
|
|
174
|
+
"MAPTILER_API_KEY"
|
|
175
|
+
)}`,
|
|
176
|
+
isDefault: true,
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
id: "outdoor",
|
|
180
|
+
name: "Outdoor",
|
|
181
|
+
url: `https://api.maptiler.com/maps/outdoor-v2/style.json?key=${env(
|
|
182
|
+
"MAPTILER_API_KEY"
|
|
183
|
+
)}`,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: "satellite",
|
|
187
|
+
name: "Satellite",
|
|
188
|
+
url: `https://api.maptiler.com/maps/satellite-v4/style.json?key=${env(
|
|
189
|
+
"MAPTILER_API_KEY"
|
|
190
|
+
)}`,
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
# In Strapi's .env file
|
|
200
|
+
MAPTILER_API_KEY=your_actual_api_key_here
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**3. Stadia Maps** (Requires API key):
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// In Strapi's config/plugins.ts
|
|
207
|
+
module.exports = ({ env }) => ({
|
|
208
|
+
"maplibre-field": {
|
|
209
|
+
enabled: true,
|
|
210
|
+
config: {
|
|
211
|
+
mapStyles: [
|
|
212
|
+
{
|
|
213
|
+
id: "alidade",
|
|
214
|
+
name: "Alidade Smooth",
|
|
215
|
+
url: `https://tiles.stadiamaps.com/styles/alidade_smooth.json?api_key=${env(
|
|
216
|
+
"STADIA_API_KEY"
|
|
217
|
+
)}`,
|
|
218
|
+
isDefault: true,
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: "osm",
|
|
222
|
+
name: "OSM Bright",
|
|
223
|
+
url: `https://tiles.stadiamaps.com/styles/osm_bright.json?api_key=${env(
|
|
224
|
+
"STADIA_API_KEY"
|
|
225
|
+
)}`,
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
# In .env file
|
|
235
|
+
STADIA_API_KEY=your_actual_api_key_here
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**4. Custom Style**:
|
|
239
|
+
|
|
240
|
+
- Create your own style using [Maputnik](https://maputnik.github.io/) (visual style editor)
|
|
241
|
+
- Host the style JSON file on your server or object storage
|
|
242
|
+
- Add it to the `mapStyles` array with a unique `id` and descriptive `name`
|
|
243
|
+
|
|
244
|
+
#### Environment Variables for API Keys
|
|
245
|
+
|
|
246
|
+
To keep API keys secure and out of version control, use Strapi's built-in `env()` function:
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// config/plugins.ts
|
|
250
|
+
module.exports = ({ env }) => ({
|
|
251
|
+
"maplibre-field": {
|
|
252
|
+
enabled: true,
|
|
253
|
+
config: {
|
|
254
|
+
mapStyles: [
|
|
255
|
+
{
|
|
256
|
+
id: "streets",
|
|
257
|
+
name: "Streets",
|
|
258
|
+
url: `https://api.maptiler.com/maps/streets-v4/style.json?key=${env(
|
|
259
|
+
"MAPTILER_API_KEY"
|
|
260
|
+
)}`,
|
|
261
|
+
isDefault: true,
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
# .env
|
|
271
|
+
MAPTILER_API_KEY=your_secret_key_here
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Important Notes**:
|
|
275
|
+
|
|
276
|
+
- Always use template literals (backticks) when interpolating environment variables
|
|
277
|
+
- The `env()` function is provided by Strapi and evaluated at server startup
|
|
278
|
+
- API keys for map tiles (Maptiler, Stadia, etc.) are safe to expose as they're meant for client-side use with domain restrictions
|
|
279
|
+
- For production, ensure all required environment variables are set in your deployment environment
|
|
280
|
+
|
|
281
|
+
#### Dependencies
|
|
282
|
+
|
|
283
|
+
The plugin includes these mapping libraries:
|
|
284
|
+
|
|
285
|
+
- **maplibre-gl** (v5.16.0): Core WebGL-based map rendering engine
|
|
286
|
+
- **react-map-gl** (v8.1.0): React wrapper for MapLibre GL with components
|
|
287
|
+
- **pmtiles** (v4.3.2): Support for cloud-native PMTiles format (efficient tile hosting without a server)
|
|
288
|
+
- **@maplibre/maplibre-gl-geocoder** (v1.9.4): Geocoding control for the map
|
|
289
|
+
|
|
290
|
+
### Geocoding Configuration
|
|
291
|
+
|
|
292
|
+
The plugin uses **Nominatim** by default for geocoding (converting addresses to coordinates and vice versa).
|
|
293
|
+
|
|
294
|
+
#### Using Public Nominatim (Default)
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
geocodingProvider: 'nominatim',
|
|
298
|
+
nominatimUrl: 'https://nominatim.openstreetmap.org',
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Important**: Public Nominatim has usage limits. Please review their [Usage Policy](https://operations.osmfoundation.org/policies/nominatim/).
|
|
302
|
+
|
|
303
|
+
#### Using Self-Hosted Nominatim
|
|
304
|
+
|
|
305
|
+
For production use with high traffic, consider hosting your own Nominatim instance:
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
nominatimUrl: 'https://your-nominatim-server.org',
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
#### Alternative Geocoding Providers
|
|
312
|
+
|
|
313
|
+
Currently, the plugin is optimized for Nominatim. Support for other providers (MapTiler, Google, etc.) can be added by extending the geocoder component.
|
|
314
|
+
|
|
315
|
+
### Update security middleware
|
|
316
|
+
|
|
317
|
+
For the map to display properly, update the `strapi::security` middleware configuration.
|
|
318
|
+
|
|
319
|
+
Open `config/middlewares.ts` (or `.js`) and add `'worker-src': ['blob:']` to the CSP directives:
|
|
320
|
+
|
|
321
|
+
```typescript
|
|
322
|
+
// config/middlewares.ts
|
|
323
|
+
export default [
|
|
324
|
+
"strapi::errors",
|
|
325
|
+
{
|
|
326
|
+
name: "strapi::security",
|
|
327
|
+
config: {
|
|
328
|
+
contentSecurityPolicy: {
|
|
329
|
+
useDefaults: true,
|
|
330
|
+
directives: {
|
|
331
|
+
"connect-src": ["'self'", "https:"],
|
|
332
|
+
"script-src": ["'self'", "'unsafe-inline'"],
|
|
333
|
+
"img-src": ["'self'", "data:", "blob:"],
|
|
334
|
+
"media-src": ["'self'", "data:", "blob:"],
|
|
335
|
+
"worker-src": ["blob:"], // Required for MapLibre workers
|
|
336
|
+
upgradeInsecureRequests: null,
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
"strapi::cors",
|
|
342
|
+
"strapi::poweredBy",
|
|
343
|
+
"strapi::logger",
|
|
344
|
+
"strapi::query",
|
|
345
|
+
"strapi::body",
|
|
346
|
+
"strapi::session",
|
|
347
|
+
"strapi::favicon",
|
|
348
|
+
"strapi::public",
|
|
349
|
+
];
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## Add a map field to your content type
|
|
353
|
+
|
|
354
|
+
In the Strapi content type builder:
|
|
355
|
+
|
|
356
|
+
- Click on `Add another field`
|
|
357
|
+
- Select the `Custom` tab
|
|
358
|
+
- Select the `Map` field
|
|
359
|
+
- Type a name for the field
|
|
360
|
+
- Click `Finish`
|
|
361
|
+
|
|
362
|
+

|
|
363
|
+
|
|
364
|
+
### Localization (i18n)
|
|
365
|
+
|
|
366
|
+
By default, **localization is disabled** for map fields because geographic coordinates are universal and typically don't vary by language.
|
|
367
|
+
|
|
368
|
+
However, if you need different locations per language (e.g., different office addresses in different countries), you can enable localization in the **Advanced Settings** tab when adding the field:
|
|
369
|
+
|
|
370
|
+
1. In the Content-Type Builder, click on the **Advanced Settings** tab
|
|
371
|
+
2. Check "Enable localization for this field"
|
|
372
|
+
|
|
373
|
+
> **Note**: This setting is disabled by default as coordinates are typically the same across all languages.
|
|
374
|
+
|
|
375
|
+
## Features
|
|
376
|
+
|
|
377
|
+
### Interactive Map
|
|
378
|
+
|
|
379
|
+
- **MapLibre GL** powered interactive map with smooth zoom and pan
|
|
380
|
+
- **OpenStreetMap** tiles via Protomaps/PMTiles
|
|
381
|
+
- Fully customizable map style via configuration
|
|
382
|
+
|
|
383
|
+
### Geocoding
|
|
384
|
+
|
|
385
|
+
- **Forward geocoding**: Search for places using the search box
|
|
386
|
+
- **Reverse geocoding**: Double-click anywhere on the map to find the nearest address
|
|
387
|
+
- Powered by **Nominatim** (OpenStreetMap's geocoding service)
|
|
388
|
+
- Real-time notifications for geocoding success, errors, and warnings
|
|
389
|
+
|
|
390
|
+
### Multi-language Support
|
|
391
|
+
|
|
392
|
+
Built-in translations for:
|
|
393
|
+
- English
|
|
394
|
+
- German (Deutsch)
|
|
395
|
+
- French (Français)
|
|
396
|
+
- Italian (Italiano)
|
|
397
|
+
- Spanish (Español)
|
|
398
|
+
|
|
399
|
+
### User Experience
|
|
400
|
+
|
|
401
|
+
- **Visual feedback**: Toast notifications for all user actions
|
|
402
|
+
- Success messages when addresses are found
|
|
403
|
+
- Warnings when no results are available
|
|
404
|
+
- Error messages with actionable guidance
|
|
405
|
+
|
|
406
|
+
### Developer Experience
|
|
407
|
+
|
|
408
|
+
- **TypeScript** support
|
|
409
|
+
- **Configurable**: All map settings can be customized
|
|
410
|
+
- **Type-safe**: Proper interfaces for all API responses
|
|
411
|
+
- **Well-tested**: Comprehensive test coverage
|
|
412
|
+
|
|
413
|
+
## POI (Point of Interest) Selection
|
|
414
|
+
|
|
415
|
+
The plugin supports direct POI selection with visual markers on the map, allowing users to select pre-defined locations or save coordinates.
|
|
416
|
+
|
|
417
|
+
### POI Features
|
|
418
|
+
|
|
419
|
+
- **POI Markers Always Visible** - Shows available POIs on the map when zoomed in (customizable zoom level)
|
|
420
|
+
- **Direct Click Selection** - Click on any POI marker to select and save complete POI data
|
|
421
|
+
- **Coordinates-Only Mode** - Double-click anywhere on empty map area to save only coordinates (no name/address)
|
|
422
|
+
- **Custom API Support** - Integrate your own GeoJSON API alongside Nominatim
|
|
423
|
+
- **Enhanced Search** - Search field queries both Nominatim AND custom API, merging results with custom API priority
|
|
424
|
+
- **Visual Distinction** - Different marker colors configurable for custom POIs
|
|
425
|
+
- **Performance Optimized** - Zoom-based visibility and display limits prevent overcrowding
|
|
426
|
+
|
|
427
|
+
### POI Configuration
|
|
428
|
+
|
|
429
|
+
Add POI configuration to your plugin settings:
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
// config/plugins.ts
|
|
433
|
+
export default {
|
|
434
|
+
"maplibre-field": {
|
|
435
|
+
enabled: true,
|
|
436
|
+
config: {
|
|
437
|
+
// Basic configuration
|
|
438
|
+
mapStyles: [
|
|
439
|
+
{
|
|
440
|
+
id: "satellite",
|
|
441
|
+
name: "Satellite",
|
|
442
|
+
url: "https://api.maptiler.com/maps/satellite-v4/style.json?key=YOUR_API_KEY",
|
|
443
|
+
isDefault: true,
|
|
444
|
+
},
|
|
445
|
+
],
|
|
446
|
+
nominatimUrl: "https://nominatim.openstreetmap.org",
|
|
447
|
+
defaultCenter: [9.19, 45.46],
|
|
448
|
+
defaultZoom: 13,
|
|
449
|
+
|
|
450
|
+
// POI Configuration
|
|
451
|
+
poiDisplayEnabled: true, // Enable POI markers display
|
|
452
|
+
poiMinZoom: 10, // Show POIs only when zoomed in >= this level
|
|
453
|
+
poiMaxDisplay: 100, // Maximum number of POIs to display
|
|
454
|
+
poiSearchEnabled: true, // Include custom API results in search
|
|
455
|
+
poiSnapRadius: 5, // Snap radius in meters for double-click POI detection (default: 5m)
|
|
456
|
+
poiSources: [
|
|
457
|
+
// Example using static GeoJSON files from the public repository
|
|
458
|
+
{
|
|
459
|
+
id: "skatespots",
|
|
460
|
+
name: "My Skatespots",
|
|
461
|
+
apiUrl: "https://codeberg.org/premiate-edizioni/strapi-plugin-maplibre-field/raw/branch/main/samples/skatespots.geojson",
|
|
462
|
+
enabled: true,
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
id: "skateshops",
|
|
466
|
+
name: "My Skateshops",
|
|
467
|
+
apiUrl: "https://codeberg.org/premiate-edizioni/strapi-plugin-maplibre-field/raw/branch/main/samples/skateshops.geojson",
|
|
468
|
+
enabled: false,
|
|
469
|
+
},
|
|
470
|
+
],
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### POI Configuration Options
|
|
477
|
+
|
|
478
|
+
| Option | Type | Default | Description |
|
|
479
|
+
| ------------------- | ------------- | ------- | ----------------------------------------------------------------------- |
|
|
480
|
+
| `poiDisplayEnabled` | `boolean` | `true` | Display POI markers on map |
|
|
481
|
+
| `poiMinZoom` | `number` | `10` | Minimum zoom level to show POI markers (prevents overcrowding) |
|
|
482
|
+
| `poiMaxDisplay` | `number` | `100` | Maximum number of POIs displayed (closest to map center) |
|
|
483
|
+
| `poiSearchEnabled` | `boolean` | `true` | Include custom API results in search field |
|
|
484
|
+
| `poiSnapRadius` | `number` | `5` | Snap radius in meters for double-click POI detection |
|
|
485
|
+
| `poiSources` | `POISource[]` | `[]` | Array of POI sources with layer control (see POISource interface below) |
|
|
486
|
+
|
|
487
|
+
#### POISource Interface
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
interface POISource {
|
|
491
|
+
id: string; // Unique identifier for the layer
|
|
492
|
+
name: string; // Display name in layer control
|
|
493
|
+
apiUrl: string; // GeoJSON API endpoint URL
|
|
494
|
+
enabled?: boolean; // Initial layer visibility (default: true)
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
**Notes:**
|
|
499
|
+
|
|
500
|
+
- `id`: Must be unique across all POI sources. Used internally to track layer state.
|
|
501
|
+
- `name`: Displayed in the layer control panel. Should be descriptive and concise.
|
|
502
|
+
- `apiUrl`: Must return a valid GeoJSON FeatureCollection (see Custom POI API section below).
|
|
503
|
+
- `enabled`: Controls initial visibility. Users can toggle layers on/off using the layer control panel regardless of this setting.
|
|
504
|
+
|
|
505
|
+
### Layer Control
|
|
506
|
+
|
|
507
|
+
When multiple POI sources are configured, a **layer control panel** appears on the map allowing you to:
|
|
508
|
+
|
|
509
|
+
- **Toggle individual layers on/off**: Click the eye icon to show/hide POI markers from each source
|
|
510
|
+
- **Real-time updates**: POIs are automatically loaded/removed when toggling layers
|
|
511
|
+
- **Dynamic map movement**: When you move the map to a new area, POIs from enabled layers are automatically fetched for the new viewport
|
|
512
|
+
- **Independent sources**: Each POI source can be controlled separately
|
|
513
|
+
|
|
514
|
+
The layer control is automatically displayed when you configure multiple `poiSources` in your plugin configuration.
|
|
515
|
+
|
|
516
|
+
### User Interaction
|
|
517
|
+
|
|
518
|
+
**Click on POI marker** → Selects that POI and saves complete data:
|
|
519
|
+
|
|
520
|
+
- POI name
|
|
521
|
+
- POI type
|
|
522
|
+
- Full address
|
|
523
|
+
- Coordinates
|
|
524
|
+
- Custom metadata
|
|
525
|
+
- Source indicator (Nominatim or custom)
|
|
526
|
+
|
|
527
|
+
**Double-click anywhere** → Intelligent POI detection with snap radius:
|
|
528
|
+
|
|
529
|
+
- **If POI found within snap radius** (default: 5m):
|
|
530
|
+
- Automatically selects the nearest POI
|
|
531
|
+
- Saves complete POI data (name, type, address, coordinates, metadata)
|
|
532
|
+
- Shows distance in notification (e.g., "Skatepark Milano (3m)")
|
|
533
|
+
- **If no POI found within snap radius**:
|
|
534
|
+
- Saves only coordinates (no name or address)
|
|
535
|
+
- Useful for marking exact locations without POI data
|
|
536
|
+
- **Configurable**: Adjust `poiSnapRadius` to change detection sensitivity (in meters)
|
|
537
|
+
|
|
538
|
+
**Search field** → Queries both sources:
|
|
539
|
+
|
|
540
|
+
- Nominatim results: Address Name
|
|
541
|
+
- Custom API results: POI Name
|
|
542
|
+
- Both displayed in dropdown
|
|
543
|
+
|
|
544
|
+
### Custom POI API
|
|
545
|
+
|
|
546
|
+
The plugin supports custom GeoJSON FeatureCollection APIs for POI data.
|
|
547
|
+
|
|
548
|
+
#### Example: Fotta Skateparks API
|
|
549
|
+
|
|
550
|
+
```
|
|
551
|
+
GET https://your-api-server.org/endpoint
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
#### Response Format (GeoJSON FeatureCollection)
|
|
555
|
+
|
|
556
|
+
```json
|
|
557
|
+
{
|
|
558
|
+
"type": "FeatureCollection",
|
|
559
|
+
"features": [
|
|
560
|
+
{
|
|
561
|
+
"id": "019ac1b6-5823-7808-9be6-62733b3d0a0a",
|
|
562
|
+
"type": "Feature",
|
|
563
|
+
"geometry": {
|
|
564
|
+
"type": "Point",
|
|
565
|
+
"coordinates": [11.1448496, 46.052144]
|
|
566
|
+
},
|
|
567
|
+
"properties": {
|
|
568
|
+
"name": "Skatepark Villa",
|
|
569
|
+
"sport": "skateboard",
|
|
570
|
+
"leisure": "pitch",
|
|
571
|
+
"osm_id": "node/12345",
|
|
572
|
+
"surface": "concrete"
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
]
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
#### Requirements
|
|
580
|
+
|
|
581
|
+
- **Format**: Must return GeoJSON FeatureCollection with Point features
|
|
582
|
+
- **Authentication**: Must be publicly accessible (no authentication)
|
|
583
|
+
- **Coordinates**: Must use `geometry.coordinates` as `[longitude, latitude]`
|
|
584
|
+
- **Name**: Must include `properties.name` (can be `null`, will use fallback)
|
|
585
|
+
- **Structure**: Compatible with standard GeoJSON specification
|
|
586
|
+
|
|
587
|
+
#### How It Works
|
|
588
|
+
|
|
589
|
+
1. **Search Field**:
|
|
590
|
+
|
|
591
|
+
- Plugin queries full dataset from API
|
|
592
|
+
- Filters results client-side by name matching search query
|
|
593
|
+
- Merges with Nominatim results (both shown in dropdown)
|
|
594
|
+
|
|
595
|
+
2. **POI Display**:
|
|
596
|
+
|
|
597
|
+
- Query POIs based on map viewport and zoom level
|
|
598
|
+
- Display up to `poiMaxDisplay` closest POIs to map center
|
|
599
|
+
- Only show when `zoom >= poiMinZoom`
|
|
600
|
+
- Update markers when map moves/zooms
|
|
601
|
+
|
|
602
|
+
3. **POI Selection** (click on marker):
|
|
603
|
+
|
|
604
|
+
- Save complete POI data: name, type, address, coordinates, metadata, source
|
|
605
|
+
- Visual feedback with orange highlight on selected marker
|
|
606
|
+
- Success notification shows POI name and source
|
|
607
|
+
|
|
608
|
+
4. **Performance**:
|
|
609
|
+
- API response is cached client-side (15 minutes)
|
|
610
|
+
- Filtering by name/viewport happens in browser
|
|
611
|
+
- Works well with datasets up to ~5000 features
|
|
612
|
+
|
|
613
|
+
## Data Model
|
|
614
|
+
|
|
615
|
+
The field stores location data as a **standard GeoJSON Feature** ([RFC 7946](https://datatracker.ietf.org/doc/html/rfc7946)). Properties are only included when available (no null or empty values).
|
|
616
|
+
|
|
617
|
+
### GeoJSON Feature Structure
|
|
618
|
+
|
|
619
|
+
```typescript
|
|
620
|
+
interface LocationFeature {
|
|
621
|
+
type: "Feature";
|
|
622
|
+
geometry: {
|
|
623
|
+
type: "Point";
|
|
624
|
+
coordinates: [number, number]; // [longitude, latitude]
|
|
625
|
+
};
|
|
626
|
+
properties: {
|
|
627
|
+
name?: string; // POI name or short location name
|
|
628
|
+
address?: string; // Full formatted address
|
|
629
|
+
source?: string; // "nominatim" or custom source ID (e.g., "fotta-skatespots")
|
|
630
|
+
sourceId?: string; // Original ID from the source
|
|
631
|
+
sourceLayer?: string; // Display name of the source (e.g., "Fotta Skatespots")
|
|
632
|
+
category?: string; // POI type/category (e.g., "skating_spot", "bus_stop")
|
|
633
|
+
inputMethod?: string; // How it was created: "search" | "poi_click" | "map_click"
|
|
634
|
+
metadata?: object; // Original metadata from the source
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### Example: Nominatim Search Result
|
|
640
|
+
|
|
641
|
+
```json
|
|
642
|
+
{
|
|
643
|
+
"type": "Feature",
|
|
644
|
+
"geometry": {
|
|
645
|
+
"type": "Point",
|
|
646
|
+
"coordinates": [9.1901019, 45.460068]
|
|
647
|
+
},
|
|
648
|
+
"properties": {
|
|
649
|
+
"name": "Piazza Velasca",
|
|
650
|
+
"address": "Piazza Velasca, Cerchia dei Navigli, Municipio 1, Milano, Lombardia, 20122, Italia",
|
|
651
|
+
"source": "nominatim",
|
|
652
|
+
"sourceId": "nominatim-68428992",
|
|
653
|
+
"category": "bus_stop",
|
|
654
|
+
"inputMethod": "search",
|
|
655
|
+
"metadata": {
|
|
656
|
+
"osm_id": 4843517235,
|
|
657
|
+
"osm_type": "node",
|
|
658
|
+
"place_id": 68428992,
|
|
659
|
+
"addresstype": "highway"
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
### Example: POI Click (Custom Source)
|
|
666
|
+
|
|
667
|
+
```json
|
|
668
|
+
{
|
|
669
|
+
"type": "Feature",
|
|
670
|
+
"geometry": {
|
|
671
|
+
"type": "Point",
|
|
672
|
+
"coordinates": [9.196492, 45.480958]
|
|
673
|
+
},
|
|
674
|
+
"properties": {
|
|
675
|
+
"name": "Ledge Milano District",
|
|
676
|
+
"address": "Via Roma 1, 20121 Milano MI, Italy",
|
|
677
|
+
"source": "fotta-skatespots",
|
|
678
|
+
"sourceId": "019ac1b6-5823-7808-9be6-62733b3d0a0a",
|
|
679
|
+
"sourceLayer": "Fotta Skatespots",
|
|
680
|
+
"category": "skating_spot",
|
|
681
|
+
"inputMethod": "poi_click",
|
|
682
|
+
"metadata": {
|
|
683
|
+
"sport": "skateboard",
|
|
684
|
+
"surface": "concrete"
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
### Example: Double-Click on Empty Area
|
|
691
|
+
|
|
692
|
+
When double-clicking on an empty area (no POI within snap radius), only coordinates and input method are saved:
|
|
693
|
+
|
|
694
|
+
```json
|
|
695
|
+
{
|
|
696
|
+
"type": "Feature",
|
|
697
|
+
"geometry": {
|
|
698
|
+
"type": "Point",
|
|
699
|
+
"coordinates": [9.190252, 45.459891]
|
|
700
|
+
},
|
|
701
|
+
"properties": {
|
|
702
|
+
"inputMethod": "map_click"
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
### Properties Reference
|
|
708
|
+
|
|
709
|
+
| Property | Type | Description |
|
|
710
|
+
| ------------- | -------- | ----------------------------------------------------------------- |
|
|
711
|
+
| `name` | `string` | POI name or short location name |
|
|
712
|
+
| `address` | `string` | Full formatted address (from Nominatim or reverse geocoding) |
|
|
713
|
+
| `source` | `string` | Data source: `"nominatim"` or custom source ID |
|
|
714
|
+
| `sourceId` | `string` | Original identifier from the source |
|
|
715
|
+
| `sourceLayer` | `string` | Human-readable name of the source layer |
|
|
716
|
+
| `category` | `string` | POI type/category (e.g., `"skating_spot"`, `"bus_stop"`) |
|
|
717
|
+
| `inputMethod` | `string` | How the location was selected: `"search"`, `"poi_click"`, `"map_click"` |
|
|
718
|
+
| `metadata` | `object` | Additional metadata from the source (varies by source) |
|
|
719
|
+
|
|
720
|
+
**Note**: All properties are optional and only included when available. Empty strings and null values are automatically omitted.
|
|
721
|
+
|
|
722
|
+
### Performance & Visual Design
|
|
723
|
+
|
|
724
|
+
**Zoom-based Visibility**:
|
|
725
|
+
|
|
726
|
+
- POIs hidden when zoomed out (< `poiMinZoom`)
|
|
727
|
+
- Prevents map clutter at country/continent view
|
|
728
|
+
- Markers appear when zooming into city/neighborhood level
|
|
729
|
+
|
|
730
|
+
**Display Limit**:
|
|
731
|
+
|
|
732
|
+
- Maximum `poiMaxDisplay` POIs shown (default: 100)
|
|
733
|
+
- Sorted by distance from map center
|
|
734
|
+
- Only closest/most relevant POIs displayed
|
|
735
|
+
|
|
736
|
+
**Visual Distinction**:
|
|
737
|
+
|
|
738
|
+
- Custom POIs: Configurable colors
|
|
739
|
+
- Selected POI: Orange marker (#ff5200 - Strapi orange)
|
|
740
|
+
- Labels show POI names on hover
|
|
741
|
+
|
|
742
|
+
**Viewport-based Loading**:
|
|
743
|
+
|
|
744
|
+
- POIs query based on current map bounds
|
|
745
|
+
- Updates automatically when panning or zooming
|
|
746
|
+
- Debounced for smooth performance
|
|
747
|
+
|
|
748
|
+
### Development
|
|
749
|
+
|
|
750
|
+
The plugin uses:
|
|
751
|
+
|
|
752
|
+
- **Build system**: `@strapi/sdk-plugin` for TypeScript compilation
|
|
753
|
+
- **Package manager**: npm
|
|
754
|
+
- **Testing**: Jest for unit tests
|
|
755
|
+
- **Type checking**: TypeScript strict mode
|
|
756
|
+
|
|
757
|
+
Build the plugin:
|
|
758
|
+
|
|
759
|
+
```bash
|
|
760
|
+
npm run build
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
Watch mode for development:
|
|
764
|
+
|
|
765
|
+
```bash
|
|
766
|
+
npm run watch
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
## Contributing
|
|
770
|
+
|
|
771
|
+
Bug reports and pull requests are welcome on [Codeberg](https://codeberg.org/premiate-edizioni/strapi-plugin-maplibre-field).
|
|
772
|
+
|
|
773
|
+
## Credits
|
|
774
|
+
|
|
775
|
+
This plugin was forked from the [Strapi plugin map-field](https://github.com/play14team/strapi-plugin-map-field) by Cédric Pontet and moves from Mapbox to MapLibre with foundations on OpenStreetMap, Nominatim geocoding and Protomaps.
|
|
776
|
+
|
|
777
|
+
Thanks [Enzo Brunii](https://github.com/enzobrunii/strapi-plugin-map-field/commits?author=enzobrunii) for initial hints.
|
|
778
|
+
|
|
779
|
+
## License
|
|
780
|
+
|
|
781
|
+
[MIT](LICENSE) © Claudio Bernardini / Dipartimento di Cartografia Esistenzialista in Fotta, Premiate Edizioni
|