diff --git a/map/README.md b/map/README.md new file mode 100644 index 0000000..0edc7cd --- /dev/null +++ b/map/README.md @@ -0,0 +1,26 @@ +# 404 Camp Not Found Map + +Interactive map for navigating the 404 Camp Not Found event. Find facilities, activities, camping spots, and amenities across the campgrounds. + +## Features + +- **Interactive facilities map** with OpenStreetMap tiles +- **Location markers** for key areas (kitchen, workshops, camping, beach) +- **Activity schedules** with meal times and events +- **Facility information** including amenities and descriptions +- **Marker clustering** for better map navigation + +## Stack + +- **Leaflet** - Interactive map library +- **OpenStreetMap** - Map tiles and geographic data +- **GeoJSON** - Location data format +- **Marker Clustering** - Enhanced map navigation + +## Usage + +```sh +python -m http.server 8000 +``` + +Open your browser and navigate to `http://localhost:8000` to view the map. \ No newline at end of file diff --git a/map/index.html b/map/index.html new file mode 100644 index 0000000..e310001 --- /dev/null +++ b/map/index.html @@ -0,0 +1,18 @@ + + + + + + 404 Camp Not Found Map + + + + + + +
+ + + + + diff --git a/map/locations.geojson b/map/locations.geojson new file mode 100644 index 0000000..b46e67f --- /dev/null +++ b/map/locations.geojson @@ -0,0 +1,269 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "id": "main-entrance", + "geometry": { + "type": "Point", + "coordinates": [24.98482, 54.657344] + }, + "properties": { + "name": "Main Entrance", + "icons": ["đŸšĒ"], + "description": "Primary entry point with information desk and lost & found", + "activities": ["đŸšĒ Entry Gate", "â„šī¸ Information", "đŸ“Ļ Lost & Found"] + } + }, + { + "type": "Feature", + "id": "parking-main", + "geometry": { + "type": "Point", + "coordinates": [24.984841, 54.661329] + }, + "properties": { + "name": "Parking Area", + "icons": ["đŸ…ŋī¸"], + "description": "Central parking zone for camp visitors", + "activities": ["đŸ…ŋī¸ Car Parking", "📍 Central Parking Zone"] + } + }, + { + "type": "Feature", + "id": "base-camp", + "geometry": { + "type": "Point", + "coordinates": [24.985192, 54.661103] + }, + "properties": { + "name": "Base Camp", + "icons": ["đŸšŊ", "â™ģī¸", "💧", "🔌"], + "description": "Central facility hub with essential amenities", + "activities": ["đŸšŊ Toilets", "â™ģī¸ Recycling", "💧 Hot Water", "🔌 Charging Station"] + } + }, + { + "type": "Feature", + "id": "kitchen-area", + "geometry": { + "type": "Point", + "coordinates": [24.9861, 54.6613] + }, + "properties": { + "name": "Kitchen", + "icons": ["đŸŊī¸", "đŸŗ", "☕", "đŸŋ"], + "description": "Main food service area with scheduled meal times", + "activities": [ + "đŸŗ Breakfast 07:-09:30", + "☕ Coffee 07:-11:", + "đŸĨĒ Lunch 12:-14:30", + "đŸŋ Popcorn 15:-17:", + "đŸŊī¸ Dinner 18:-20:30" + ], + + "marker-color": "#ea580c", + "marker-size": "large" + } + }, + { + "type": "Feature", + "id": "talks", + "geometry": { + "type": "Point", + "coordinates": [24.9858, 54.6611] + }, + "properties": { + "name": "Talks", + "icons": ["đŸ“ĸ"], + "description": "Learning area for hands-on workshops and presentations", + "activities": ["đŸ“ĸ Talks"] + } + }, + { + "type": "Feature", + "id": "workshop-zone", + "geometry": { + "type": "Point", + "coordinates": [24.9857, 54.6614] + }, + "properties": { + "name": "Workshop", + "icons": ["🔨"], + "description": "Learning area for hands-on workshops and presentations", + "activities": ["🔨 Workshops"] + } + }, + { + "type": "Feature", + "id": "campfire-circle", + "geometry": { + "type": "Point", + "coordinates": [24.986815, 54.660984] + }, + "properties": { + "name": "Campfire", + "icons": ["đŸ”Ĩ", "đŸē"], + "description": "Social area for evening fires and refreshments", + "activities": ["đŸ”Ĩ Evening Fires", "đŸē Beer Area 20:-23:"] + } + }, + { + "type": "Feature", + "id": "lieptelis-grove", + "geometry": { + "type": "Point", + "coordinates": [24.9869683, 54.6613] + }, + "properties": { + "name": "Lieptelis Grove", + "icons": ["đŸŒŗ", "🧘"], + "description": "Peaceful area around the historic Lieptelis tree", + "activities": ["đŸŒŗ Historic Tree", "🧘 Quiet Space"] + } + }, + { + "type": "Feature", + "id": "beach-main", + "geometry": { + "type": "Point", + "coordinates": [24.98656, 54.6613] + }, + "properties": { + "name": "Beach", + "icons": ["đŸ–ī¸", "🏊"], + "description": "Primary beach access point for swimming and water activities", + "activities": ["🏊 Swimming", "đŸ–ī¸ Beach Access"] + } + }, + { + "type": "Feature", + "id": "sunset-peninsula", + "geometry": { + "type": "Point", + "coordinates": [24.983742, 54.661853] + }, + "properties": { + "name": "Sunset Peninsula", + "icons": ["🌅", "📸"], + "description": "Scenic peninsula perfect for sunset photography", + "activities": ["🌅 Sunset Views", "📸 Photo Spot"] + } + }, + { + "type": "Feature", + "id": "camping-site-2", + "geometry": { + "type": "Point", + "coordinates": [24.983811, 54.661425] + }, + "properties": { + "name": "Camp 2", + "icons": ["â›ē", "🌙"], + "description": "Designated camping area for overnight stays", + "activities": ["â›ē Camping Spot", "🌙 Night Area"] + } + }, + { + "type": "Feature", + "id": "camping-site-3", + "geometry": { + "type": "Point", + "coordinates": [24.983731, 54.661232] + }, + "properties": { + "name": "Camp 3", + "icons": ["â›ē", "đŸ•ī¸"], + "description": "Peaceful camping area away from main activities", + "activities": ["â›ē Camping Spot", "đŸ•ī¸ Quiet Zone"] + } + }, + { + "type": "Feature", + "id": "south-beach", + "geometry": { + "type": "Point", + "coordinates": [24.987159, 54.660553] + }, + "properties": { + "name": "South Beach", + "icons": ["đŸ–ī¸", "🌊"], + "description": "Secondary beach area for water activities", + "activities": ["đŸ–ī¸ Beach Access", "🌊 Water Activities"], + + "marker-color": "#0ea5e9", + "marker-size": "medium" + } + }, + { + "type": "Feature", + "id": "high-mountain", + "geometry": { + "type": "Point", + "coordinates": [24.985989, 54.660596] + }, + "properties": { + "name": "High Mountain", + "icons": ["â›°ī¸", "📸"], + "description": "Elevated viewpoint offering panoramic views of the area", + "activities": ["â›°ī¸ Viewpoint", "📸 Scenic Spot"] + } + }, + + + { + "type": "Feature", + "id": "trash-bin-3", + "geometry": { + "type": "Point", + "coordinates": [24.987, 54.6608] + }, + "properties": { + "name": "Trash Bin", + "icons": ["đŸ—‘ī¸"], + "activities": [] + } + }, + { + "type": "Feature", + "id": "trash-bin-4", + "geometry": { + "type": "Point", + "coordinates": [24.9842, 54.661] + }, + "properties": { + "name": "Trash Bin", + "icons": ["đŸ—‘ī¸"], + "activities": [] + } + }, + { + "type": "Feature", + "id": "restroom", + "geometry": { + "type": "Point", + "coordinates": [24.9856, 54.6612] + }, + "properties": { + "name": "Restroom", + "icons": ["đŸšģ"], + "description": "", + "activities": [] + } + }, + { + "type": "Feature", + "id": "restroom", + "geometry": { + "type": "Point", + "coordinates": [24.986, 54.6609] + }, + "properties": { + "name": "Restroom", + "icons": ["đŸšģ"], + "description": "", + "activities": [] + } + } + ] +} diff --git a/map/script.js b/map/script.js new file mode 100644 index 0000000..f421836 --- /dev/null +++ b/map/script.js @@ -0,0 +1,108 @@ +const MAP_CONFIG = { + center: [54.661103, 24.986192], + zoom: 18, + maxZoom: 18, + minZoom: 16, +}; + +const ICON_CONFIG = { + iconOnly: { + size: [25, 25], + anchor: [12, 12], + fontSize: 16, + }, +}; + +const map = L.map("map", MAP_CONFIG); + +const createIcon = (properties) => { + const { name, icons } = properties; + + // For trashbins, display icon only + if ((name && name.toLowerCase().includes("trash")) || name.toLowerCase().includes("restroom")) { + const iconString = icons && icons.length > 0 ? icons[0] : "đŸ—‘ī¸"; + return L.divIcon({ + html: `
${iconString}
`, + iconSize: ICON_CONFIG.iconOnly.size, + iconAnchor: ICON_CONFIG.iconOnly.anchor, + className: "icon-only-marker", + }); + } + + // Shorten long names (Base Camp fits, but longer names get truncated) + const iconString = icons && icons.length > 0 ? icons.join("") : ""; + const displayText = iconString ? `${name}\n${iconString}` : name; + + return L.divIcon({ + className: "custom-div-icon", + html: `
${displayText}
`, + iconSize: [null, null], + iconAnchor: [0, 0], + }); +}; + +const createPopupContent = (properties) => { + if (!properties?.name) { + return null; + } + + const { name, icons, activities } = properties; + let content = `

${name}

`; + + // Add icons section if available + if (icons && icons.length > 0) { + const iconsHtml = icons.map((icon) => `${icon}`).join(""); + content += ``; + } + + // Add activities if available + if (activities && activities.length > 0) { + const activitiesHtml = activities.map((activity) => `${activity}
`).join(""); + content += ``; + } + + return content; +}; + +const onEachFeature = (feature, layer) => { + const popupContent = createPopupContent(feature.properties); + if (popupContent) { + layer.bindPopup(popupContent); + } +}; + +const pointToLayer = (feature, latlng) => { + const icon = createIcon(feature.properties); + return L.marker(latlng, { icon }); +}; + +document.addEventListener("DOMContentLoaded", () => { + L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { + attribution: 'Š OpenStreetMap contributors', + }).addTo(map); + + const markers = L.markerClusterGroup({ + maxClusterRadius: 50, + disableClusteringAtZoom: 18, + }); + + fetch("locations.geojson") + .then((response) => response.json()) + .then((data) => { + const geoJsonLayer = L.geoJSON(data, { + onEachFeature: onEachFeature, + pointToLayer: pointToLayer, + }); + + markers.addLayer(geoJsonLayer); + map.addLayer(markers); + }) + .catch((error) => { + console.error("Error loading location data:", error); + alert( + "Could not load location data. Please serve this page from a web server (e.g., python3 -m http.server 8000)" + ); + }); + + L.control.scale().addTo(map); +}); diff --git a/map/styles.css b/map/styles.css new file mode 100644 index 0000000..8cfc766 --- /dev/null +++ b/map/styles.css @@ -0,0 +1,46 @@ +body { + margin: 0; + padding: 0; +} + +#map { + width: 100%; + height: 100vh; +} + +/* Custom marker styles */ +.custom-marker { + background: rgba(255, 255, 255, 0.95); + border: 2px solid #2e86ab; + border-radius: 15px; + padding: 2px 4px; + font-size: 1em; + color: #2e86ab; + text-align: center; + white-space: pre-line; + min-width: max-content; +} + +/* Popup styles */ +.popup-icons { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin: 10px 0; + justify-content: flex-start; +} + +.popup-icon { + font-size: 24px; + display: inline-block; + padding: 4px; + background: rgba(46, 134, 171, 0.1); + border-radius: 8px; + border: 1px solid rgba(46, 134, 171, 0.2); +} + +.popup-activities { + margin-top: 10px; + font-size: 14px; + line-height: 1.4; +} \ No newline at end of file