-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcog_map_step_10.html
More file actions
276 lines (239 loc) · 13.2 KB
/
Copy pathcog_map_step_10.html
File metadata and controls
276 lines (239 loc) · 13.2 KB
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
265
266
267
268
269
270
271
272
273
274
275
276
<!DOCTYPE html>
<html lang="en">
<head>
<base target="_top">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Stanford Campus Map - Bonus Step: Filter to Wikidata Features</title>
<!-- Leaflet CSS for mapping library -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin=""/>
<!-- Leaflet JS for mapping functionality -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<!-- Projection library for geospatial coordinate transformations -->
<script src="https://unpkg.com/proj4"></script>
<!-- GeoRaster library for reading raster data -->
<script src="https://unpkg.com/georaster"></script>
<!-- GeoRaster Layer plugin for Leaflet to display rasters -->
<script src="https://unpkg.com/georaster-layer-for-leaflet"></script>
<!-- External CSS file with all styling for the split-screen layout -->
<link rel="stylesheet" href="styles_wikidata.css">
</head>
<body>
<!-- Page heading -->
<h1>Stanford Public Art Map</h1>
<!-- Step description -->
<p>Bonus Step: Filter the map to only show features that have Wikidata IDs</p>
<!-- Main container divided into two sections: map and info panel -->
<div id="container">
<!-- Left side: map wrapper contains the Leaflet map -->
<div id="mapWrapper">
<div id="map"></div>
</div>
<!-- Right side: Wikidata information panel with dynamic content -->
<div id="wikidata-panel">
<div id="no-selection">Click on an artwork to see Wikidata information</div>
</div>
</div>
<script>
// Initialize the Leaflet map object centered on Stanford campus (lat: 37.427, lon: -122.169) at zoom level 15
const map = L.map('map').setView([37.427, -122.169], 15);
// Add OpenStreetMap basemap tiles to the map
const tiles = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
// Helper function to fetch human-readable label for a Wikidata entity by its ID
// Used to convert property values (which are Wikidata IDs) into readable names
async function getWikidataLabel(wikidataId) {
try {
// Fetch the Wikidata entity in JSON format from the official Wikidata API
const response = await fetch(`https://www.wikidata.org/wiki/Special:EntityData/${wikidataId}.json`);
const data = await response.json();
const entity = data.entities[wikidataId];
// Return the English label if available, otherwise return the ID itself
return entity.labels.en ? entity.labels.en.value : wikidataId;
} catch (error) {
// If there's an error fetching the label, just return the ID
return wikidataId;
}
}
// Helper function to fetch Wikidata image from Wikidata entity
// Returns the URL to a Wikimedia Commons image if the entity has one, otherwise null
async function getWikidataImage(wikidataId) {
try {
// Fetch the Wikidata entity in JSON format from the official Wikidata API
const response = await fetch(`https://www.wikidata.org/wiki/Special:EntityData/${wikidataId}.json`);
const data = await response.json();
const entity = data.entities[wikidataId];
// Check if the entity has claims (properties) and specifically P18 (image)
// P18 is the Wikidata property ID for "image"
if (entity.claims && entity.claims.P18) {
// Extract the filename from the first image claim
const filename = entity.claims.P18[0].mainsnak.datavalue.value;
// URL-encode the filename, replacing spaces with underscores as per Wikimedia convention
const encodedFilename = encodeURIComponent(filename.replace(/ /g, '_'));
// Return the full URL to the image hosted on Wikimedia Commons, with width constraint
return `https://commons.wikimedia.org/wiki/Special:FilePath/${encodedFilename}?width=300`;
}
} catch (error) {
// Log any errors to the browser console for debugging
console.error('Error fetching image:', error);
}
// Return null if no image was found
return null;
}
// Function to fetch and display complete Wikidata content including images and properties
async function displayWikidataInfo(wikidataId) {
const panel = document.getElementById('wikidata-panel');
try {
// Fetch the Wikidata entity in JSON format from the official Wikidata API
const response = await fetch(`https://www.wikidata.org/wiki/Special:EntityData/${wikidataId}.json`);
const data = await response.json();
const entity = data.entities[wikidataId];
// Check if entity was found in the API response
if (!entity) {
panel.innerHTML = '<div id="no-selection">Wikidata not found</div>';
return;
}
// Begin building the HTML content for the panel
let html = '<div class="wikidata-content">';
// Extract and display the artwork label (English version, or fallback to ID)
const label = entity.labels.en ? entity.labels.en.value : wikidataId;
html += `<div class="wikidata-label">${label}</div>`;
// Extract and display the description (English version) if available
if (entity.descriptions.en) {
html += `<div class="wikidata-description">${entity.descriptions.en.value}</div>`;
}
// Fetch and display the image associated with the artwork if it exists
const imageUrl = await getWikidataImage(wikidataId);
if (imageUrl) {
html += `<img src="${imageUrl}" alt="${label}" class="wikidata-image">`;
}
// Display additional Wikidata properties/claims if they exist
if (entity.claims) {
// P170 is the Wikidata property ID for "artist"
if (entity.claims.P170) {
const artistId = entity.claims.P170[0].mainsnak.datavalue.value.id;
// Fetch the human-readable name of the artist
const artistName = await getWikidataLabel(artistId);
html += `<div class="wikidata-property"><span class="wikidata-property-label">Artist:</span> ${artistName}</div>`;
}
// P61 is the Wikidata property ID for "creator" (alternative to artist)
if (entity.claims.P61) {
const creatorId = entity.claims.P61[0].mainsnak.datavalue.value.id;
// Fetch the human-readable name of the creator
const creatorName = await getWikidataLabel(creatorId);
html += `<div class="wikidata-property"><span class="wikidata-property-label">Creator:</span> ${creatorName}</div>`;
}
// P571 is the Wikidata property ID for "inception" (date of creation)
if (entity.claims.P571) {
// Extract the date value (formatted as ISO 8601 timestamp)
const date = entity.claims.P571[0].mainsnak.datavalue.value.time;
html += `<div class="wikidata-property"><span class="wikidata-property-label">Date:</span> ${date}</div>`;
}
// P186 is the Wikidata property ID for "material"
if (entity.claims.P186) {
const materialId = entity.claims.P186[0].mainsnak.datavalue.value.id;
// Fetch the human-readable name of the material
const materialName = await getWikidataLabel(materialId);
html += `<div class="wikidata-property"><span class="wikidata-property-label">Material:</span> ${materialName}</div>`;
}
}
// Link to view the full Wikidata page in a new tab
html += `<div class="wikidata-property"><a href="https://www.wikidata.org/wiki/${wikidataId}" target="_blank" class="wikidata-link">View on Wikidata</a></div>`;
// Close the content div and update the panel
html += '</div>';
panel.innerHTML = html;
} catch (error) {
// Log any errors to the browser console for debugging
console.error('Error fetching Wikidata:', error);
// Display an error message in the panel
panel.innerHTML = '<div id="no-selection">Error loading Wikidata information</div>';
}
}
// Load and display the Cloud Optimized GeoTIFF (COG) raster layer
var url_to_geotiff_file = new URL("./stanford_campus_irg.tif", window.location.href).href;
// Parse the GeoTIFF file to extract georaster information
parseGeoraster(url_to_geotiff_file).then(georaster => {
console.log("georaster:", georaster);
// Fetch the mask GeoJSON to clip the raster to Stanford campus boundary
fetch("./stanford_campus.geojson")
.then(r => r.json())
.then(maskGeojson => {
// Create a new GeoRaster layer with the parsed data and mask
var layer = new GeoRasterLayer({
attribution: "Planet",
georaster: georaster,
resolution: 128,
mask: maskGeojson
});
// Add the raster layer to the map and adjust view to fit layer bounds
layer.addTo(map);
map.fitBounds(layer.getBounds());
});
}).catch(console.error);
// Load the public art GeoJSON file
fetch("./stanford_public_art.geojson")
.then(response => response.json())
.then(data => {
// Filter to only features that include a wikidata ID
const filteredFeatures = (data.features || []).filter(feature => {
const props = feature.properties || {};
return Boolean(props.wikidata);
});
// If nothing remains after filtering, inform the user
if (filteredFeatures.length === 0) {
document.getElementById('wikidata-panel').innerHTML = '<div id="no-selection">No artworks with Wikidata IDs were found in the dataset.</div>';
return;
}
// Build a new GeoJSON object with only the filtered features
const filteredGeojson = {
type: 'FeatureCollection',
features: filteredFeatures
};
// Create a GeoJSON layer with custom styling and interactivity
const artworkLayer = L.geoJSON(filteredGeojson, {
// Custom function to create circle markers instead of default icons
pointToLayer: function(feature, latlng) {
return L.circleMarker(latlng, {
radius: 6,
color: 'white',
weight: 2,
fillColor: 'blue',
fillOpacity: 0.7
});
},
// Add popup content and click handlers to each feature
onEachFeature: function(feature, layer) {
const props = feature.properties || {};
const title = props.name || 'Artwork';
const artist = props.artist_name;
const type = props.artwork_type;
// Build popup HTML content with artwork information from the GeoJSON
let popupContent = '<div style="min-width:200px;">';
popupContent += '<b>' + title + '</b><br>';
if (artist) popupContent += '<b>Artist:</b> ' + artist + '<br>';
if (type) popupContent += '<b>Type:</b> ' + type;
popupContent += '</div>';
// Bind the popup to the marker so it appears on click
layer.bindPopup(popupContent);
// Add click handler to display Wikidata info when marker is clicked
layer.on('click', function() {
// props.wikidata is guaranteed by the filter, but keep a guard
if (props.wikidata) {
displayWikidataInfo(props.wikidata);
} else {
document.getElementById('wikidata-panel').innerHTML = '<div id="no-selection">No Wikidata ID available for this artwork</div>';
}
});
}
}).addTo(map);
// Adjust map zoom and position to display all filtered artwork markers
if (filteredFeatures.length > 0) {
map.fitBounds(artworkLayer.getBounds());
}
})
.catch(error => console.error('Error loading GeoJSON:', error));
</script>
</body>
</html>