-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
264 lines (249 loc) · 14.5 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Booking Map Example Demo</title>
<meta property="og:title" content="Booking Map Example Demo" />
<meta name="author" content="Jawg Maps" />
<meta property="og:locale" content="en_US" />
<meta
name="description"
content="Full booking map example using Jawg Maps Dynamic Maps and MapLibre in vanilla JS."
/>
<meta
property="og:description"
content="Full booking map example using Jawg Maps Dynamic Maps and MapLibre in vanilla JS."
/>
<link rel="canonical" href="https://jawg.github.io/booking-map-example/" />
<meta property="og:url" content="https://jawg.github.io/booking-map-example/" />
<meta property="og:site_name" content="Booking Map Example Demo" />
<meta property="og:type" content="website" />
<meta name="twitter:card" content="summary" />
<meta property="twitter:title" content="Booking Map Example Demo" />
<meta name="twitter:site" content="@Jawgio" />
<meta name="twitter:creator" content="@Jawgio" />
<link href="https://unpkg.com/[email protected]/dist/maplibre-gl.css" rel="stylesheet" />
<link href="style.css" rel="stylesheet" />
<script src="https://unpkg.com/[email protected]/dist/maplibre-gl.js"></script>
<!-- Don't forget to replace YOUR_ACCESS_TOKEN by your real access token ! -->
<script src="https://api.jawg.io/libraries/jawg-places@latest/jawg-places.js?access-token=YOUR_ACCESS_TOKEN"></script>
</head>
<body>
<main>
<section>
<div id="side-panel"></div>
<!-- You can remove this div -->
<div class="attributions">
<div>
Made with ♥ by <a target="_blank" href="https://jawg.io/?utm_medium=booking-map-example&utm_source=github"><b>Jawg</b>Maps</a>
</div>
<div><a target="_blank" href="https://github.com/jawg/booking-map-example">GitHub Project</a></div>
</div>
</section>
<div id="map">
<!-- You can remove this link -->
<a class="github-project" target="_blank" href="https://github.com/jawg/booking-map-example"><img src="./images/GitHub-Mark.png"></a>
</div>
</main>
<script>
// Don't forget to replace YOUR_ACCESS_TOKEN by your real access token !
const accessToken = 'YOUR_ACCESS_TOKEN';
// With this example, you can connect a backend that returns GeoJSON or use a file that will have all your points. Turn this const to true and see where it's used.
const DYNAMIC_DATA_SOURCE = false;
// The Map object represents the map on your page. It exposes methods and properties that enable you to programmatically change the map, and fires events as users interact with it.
const map = new maplibregl.Map({
// The HTML element in which MapLibre GL JS will render the map, or the element's string id . The specified element must have no children.
container: 'map',
// The map's MapLibre style. This must be an a JSON object conforming to the schema described in the MapLibre Style Specification , or a URL to such JSON.
style: `https://api.jawg.io/styles/jawg-sunny.json?access-token=${accessToken}`,
// The initial zoom level of the map.
zoom: 10,
// The initial geographical centerpoint of the map.
center: [2.3488, 48.8534],
// Add the map's position (zoom, center latitude, center longitude, bearing, and pitch) will be synced with the hash fragment of the page's URL. For example, http://127.0.0.1/index.html#2.59/39.26/53.07/-24.1/60.
hash: true,
// Disable map's pitch (tilt) control with "drag to rotate" interaction.
pitchWithRotate: false,
// Disable the "drag to rotate" interaction.
dragRotate: false,
// You must not set this value to false, this is a Copyright AND Terms of use infringements !
attributionControl: true,
})
// Add navigation control that contains zoom buttons without compass.
.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'top-right')
// Add autocomplete control that uses autocomplete
.addControl(new JawgPlaces.MapLibre({ searchOnTyping: true }));
// This plugin is used for right to left languages
maplibregl.setRTLTextPlugin('https://unpkg.com/@mapbox/[email protected]/mapbox-gl-rtl-text.min.js');
function onHover(e) {
const id = e.target.getAttribute('feature-id');
if (e.type === 'mouseenter' && !isNaN(id)) {
// We increase the hovered feature text size.
map.setLayoutProperty('booking-map-layer', 'text-size', ['match', ['id'], parseInt(id), 20, 14]);
} else {
// reset text layer size
map.setLayoutProperty('booking-map-layer', 'text-size', 14);
}
}
// Build popup and side panel card. This is pure HTML but you can use your own frontend library like react.
function buildDetaitHtml(feature, classNames, addListener) {
const { properties } = feature;
return `
<div class="${classNames || ''}" feature-id="${properties.id}" ${
addListener && `onmouseenter="onHover(event)" onmouseleave="onHover(event)"`
}>
<div class="popup-image" style="">
<a href="#feature-${properties.id}">
<img src="${properties.image}">
</a>
</div>
<div class="popup-content">
<div class="popup-header">
<span class="popup-title">
${properties.title}
</span>
<span class="popup-rating">
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="presentation" focusable="false" style="display: block; fill: currentcolor;"><path d="M15.094 1.579l-4.124 8.885-9.86 1.27a1 1 0 0 0-.542 1.736l7.293 6.565-1.965 9.852a1 1 0 0 0 1.483 1.061L16 25.951l8.625 4.997a1 1 0 0 0 1.482-1.06l-1.965-9.853 7.293-6.565a1 1 0 0 0-.541-1.735l-9.86-1.271-4.127-8.885a1 1 0 0 0-1.814 0z" fill-rule="evenodd"></path></svg>
${properties.rating ? `${properties.rating} (${properties.reviewsCount})` : `New`}
</span>
</div>
<div class="popup-grey">${properties.name}</div>
<div>
<span class="popup-price">${properties.price}</span>
<span>${properties.priceQualifer}</span>
<span class="popup-grey"> · ${properties.dates}</span>
</div>
</div>
</div>`;
}
// Fired immediately after all necessary resources have been downloaded and the first visually complete rendering of the map has occurred.
map.on('load', async () => {
let featureId;
let popup;
// Update the side panel with some features.
const updateSidePanel = (features) => {
const cards = features.map((feature) => buildDetaitHtml(feature, 'side-panel-card', true));
// Edit the content of the side panel and add cards or error message
document.getElementById('side-panel').innerHTML =
cards.length > 0
? `<div class="side-panel-cards">${cards.join('')}</div>`
: '<div class="empty-list">We did not find any results in this area.</div>';
};
// Only used when DYNAMIC_DATA_SOURCE == true. You should update the endpoint part and set your own.
const dynamicSourceUpdateCallback = async () => {
const bbox = map.getBounds();
const endpoint = `./booking-map-data.geojson?north=${bbox.getNorth()}&east=${bbox.getEast()}&south=${bbox.getSouth()}&west=${bbox.getWest()}`;
const featureCollection = await fetch(endpoint).then((resp) => resp.json());
// Returns the source with the specified ID in the map's style and set new data.
map.getSource('booking-map-source').setData(featureCollection);
updateSidePanel(featureCollection.features);
};
// Only used when DYNAMIC_DATA_SOURCE == false.
const staticSourceCallback = () => {
// Returns an array of GeoJSONFeature objects representing visible features in this view from the layer booking-map-layer.
const features = map.queryRenderedFeatures({ layers: ['booking-map-layer'] });
updateSidePanel(features);
};
// Fired just after the map completes a transition from one view to another, as the result of either user interaction or methods such as Map#jumpTo.
map.on('moveend', DYNAMIC_DATA_SOURCE ? dynamicSourceUpdateCallback : staticSourceCallback);
// Fired only once after the last frame rendered before the map enters an "idle" state:
map.once('idle', DYNAMIC_DATA_SOURCE ? dynamicSourceUpdateCallback : staticSourceCallback);
// Adds a source to the map's style. This contains your points.
map.addSource('booking-map-source', {
type: 'geojson',
// When we load the source dynamicaly, the default data should be an empty feature collection.
data: DYNAMIC_DATA_SOURCE ? { type: 'FeatureCollection', features: [] } : 'booking-map-data.geojson',
});
// Load an image from an external URL to be used with Map#addImage. External domains must support CORS.
map.loadImage('booking-map-selected.png', (error, image) => {
if (error) throw error;
// Add an image to the style. This image can be displayed on the map like any other icon in the style's sprite using the image's ID with icon-image, background-pattern, fill-pattern, or line-pattern. A Map.event:error event will be fired if there is not enough space in the sprite to add this image.
map.addImage('booking-map-selected', image);
});
// Load an image from an external URL to be used with Map#addImage. External domains must support CORS.
map.loadImage('booking-map-default.png', function (error, image) {
if (error) throw error;
// Add an image to the style. This image can be displayed on the map like any other icon in the style's sprite using the image's ID with icon-image, background-pattern, fill-pattern, or line-pattern. A Map.event:error event will be fired if there is not enough space in the sprite to add this image.
map.addImage('booking-map-default', image);
// Add layer to map, using your geojson as source
map.addLayer({
id: 'booking-map-layer',
type: 'symbol',
source: 'booking-map-source',
paint: {
'text-color': '#000',
},
layout: {
'icon-image': 'booking-map-default',
'icon-anchor': 'center',
'icon-text-fit': 'both',
'icon-text-fit-padding': [5, 10, 5, 10],
'text-field': ['get', 'price'],
'text-font': ['Open Sans Bold'],
'text-size': 14,
},
});
});
// Fired when a pointing device (usually a mouse) is pressed and released at the same point on the map.
map.on('click', 'booking-map-layer', (e) => {
// Returns the first GeoJSONFeature objects representing visible features from the layer booking-map-layer.
const feature = map.queryRenderedFeatures(e.point, { layers: ['booking-map-layer'] }).shift();
if (!feature || feature.id === featureId) return;
featureId = feature.id;
popup = new maplibregl.Popup({
// A string that sets the CSS property of the popup's maximum width, eg '300px' . To ensure the popup resizes to fit its content, set this property to 'none'
maxWidth: '324px',
// A pixel offset applied to the popup's location specifying a distance from the popup's location
offset: 15,
// A string indicating the part of the Popup that should be positioned closest to the coordinate set via Popup#setLngLat
anchor: 'bottom',
})
// Sets the geographical location of the popup's anchor, and moves the popup to it.
.setLngLat(feature.geometry.coordinates)
// Sets the popup's content to the HTML provided as a string. This method does not perform HTML filtering or sanitization, and must be used only with trusted content.
.setHTML(buildDetaitHtml(feature))
// Fired when the popup is opened manually or programatically.
.on('open', () => {
// Sets the value of a layout property in the specified style layer. We change the selected feature icon.
map.setLayoutProperty('booking-map-layer', 'icon-image', [
'match',
['id'],
feature.id,
'booking-map-selected',
'booking-map-default',
]);
// Sets the value of a paint property in the specified style layer. We change the selected feature color.
map.setPaintProperty('booking-map-layer', 'text-color', ['match', ['id'], feature.id, '#ffffff', '#222']);
})
//Fired when the popup is closed manually or programatically.
.on('close', () => {
// Reset properties only when the popup is closed
if (!popup.isOpen()) {
map.setLayoutProperty('booking-map-layer', 'icon-image', 'booking-map-default');
map.setPaintProperty('booking-map-layer', 'text-color', '#222');
featureId = 0;
}
})
.addTo(map);
});
// Fired when a pointing device (usually a mouse) enters a visible portion of a specified layer from outside that layer or outside the map canvas.
map.on('mouseenter', 'booking-map-layer', (e) => {
// Returns the first GeoJSONFeature objects representing visible features from the layer booking-map-layer.
const feature = map.queryRenderedFeatures(e.point, { layers: ['booking-map-layer'] }).shift();
// Returns the map's <canvas> element.
map.getCanvas().style.cursor = 'pointer';
// We increase the hovered feature text size.
feature && map.setLayoutProperty('booking-map-layer', 'text-size', ['match', ['id'], feature.id, 20, 14]);
});
// Fired when a pointing device (usually a mouse) leaves a visible portion of a specified layer, or leaves the map canvas.
map.on('mouseleave', 'booking-map-layer', () => {
// Returns the map's <canvas> element.
map.getCanvas().style.cursor = '';
// We reset the feature text size.
map.setLayoutProperty('booking-map-layer', 'text-size', 14);
});
});
</script>
</body>
</html>