Compare commits

..

No commits in common. "2b2cd328471881b96994728598bd112ada5ac105" and "10a5db9d0501a2a138083e6107d05339decc74b0" have entirely different histories.

13 changed files with 394 additions and 2423 deletions

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
Lession_material
*.DS_Store
.aider*
.aider*

View File

@ -1,15 +1,36 @@
# Journey Mapper Backend
# Travel Journal (Draft README)
## Projectdescription
On this website you can document your travelstories.
Where you where, what you did and what videos and pictures you took.
Keep your journal and share it with friends or just log it for your memory.
A Flask server for the Journey Mapper web application.
## Pages
### Map Page
Here you can create a journey through setting marker on a map.
At each marker you can write notes or implement images.
### Blog Page
Here you can look at all notes from one journey.
The markers have a date so they are in a time order.
### Journey Explorer
Here you can search for all journeys. Like overview page from a blogwebsite.
## Features
- Blog entry
- Log places
- Add pictures to specific places
- RESTful API for managing journeys
- Persistent storage using JSON file
- CORS enabled for frontend integration
- Health check endpoint
- Easy to set up and run
## Installation
TBD
## Getting Started
## Tech-Stack
- HTML
- CSS
- JavaScript
- WebGL - MapLibre
1. Install dependencies:
## Members
- Flepp Stiafen
- Kohler Joshua
- Rüegger André

View File

@ -1,140 +0,0 @@
from flask import Flask, request, jsonify
from flask_cors import CORS
# Initialize CORS
app = Flask(__name__)
CORS(app)
import json
import os
from datetime import datetime
app = Flask(__name__)
# Define the data directory
DATA_DIR = 'data'
os.makedirs(DATA_DIR, exist_ok=True)
# Define the data file path
DATA_FILE = os.path.join(DATA_DIR, 'journeys.json')
# Initialize journeys list
journeys = []
def load_journeys():
"""Load journeys from the data file."""
global journeys
try:
if os.path.exists(DATA_FILE):
with open(DATA_FILE, 'r') as f:
journeys = json.load(f)
else:
journeys = []
except Exception as e:
print(f"Error loading journeys: {e}")
journeys = []
def save_journeys():
"""Save journeys to the data file."""
try:
with open(DATA_FILE, 'w') as f:
json.dump(journeys, f, indent=2)
except Exception as e:
print(f"Error saving journeys: {e}")
# Load journeys when the app starts
load_journeys()
@app.route('/api/journeys', methods=['POST'])
def create_journey():
"""Create a new journey."""
data = request.get_json()
if not data or 'name' not in data:
return jsonify({'error': 'Journey name is required'}), 400
# Create new journey
new_journey = {
'id': len(journeys) + 1,
'name': data['name'],
'description': data.get('description', ''),
'markers': data.get('markers', []),
'createdAt': datetime.now().isoformat()
}
journeys.append(new_journey)
save_journeys()
return jsonify(new_journey), 201
@app.route('/api/journeys', methods=['GET'])
def get_journeys():
"""Get all journeys."""
return jsonify(journeys)
@app.route('/api/journeys/<string:journey_id>', methods=['GET'])
def get_journey(journey_id):
"""Get a specific journey by ID."""
journey = next((j for j in journeys if str(j['id']) == journey_id), None)
if journey:
return jsonify(journey)
return jsonify({'error': 'Journey not found'}), 404
@app.route('/api/journeys/<string:journey_id>', methods=['PUT'])
def update_journey(journey_id):
"""Update an existing journey."""
journey = next((j for j in journeys if str(j['id']) == journey_id), None)
if not journey:
return jsonify({'error': 'Journey not found'}), 404
data = request.get_json()
# Update journey fields
if 'name' in data:
journey['name'] = data['name']
if 'description' in data:
journey['description'] = data['description']
if 'markers' in data:
journey['markers'] = data['markers']
save_journeys()
return jsonify(journey)
@app.route('/api/journeys/<string:journey_id>', methods=['DELETE'])
def delete_journey(journey_id):
"""Delete a journey."""
global journeys
journey = next((j for j in journeys if str(j['id']) == journey_id), None)
if not journey:
return jsonify({'error': 'Journey not found'}), 404
journeys = [j for j in journeys if str(j['id']) != int(journey_id)]
save_journeys()
return jsonify({'message': 'Journey deleted successfully', 'journey': journey})
@app.route('/api/journeys/health', methods=['GET'])
def health_check():
"""Health check endpoint."""
return jsonify({'status': 'healthy', 'timestamp': datetime.now().isoformat()})
@app.route('/')
def index():
"""Serve the main HTML page."""
return '''
<!DOCTYPE html>
<html>
<head>
<title>Journey Mapper</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>Journey Mapper</h1>
<p>Backend is running. Access API at <a href="/api/journeys">/api/journeys</a></p>
</body>
</html>
'''
if __name__ == '__main__':
app.run(debug=True, port=5000)

View File

@ -1,138 +0,0 @@
from flask import Flask, request, jsonify
from flask_cors import CORS
import json
import os
from datetime import datetime
app = Flask(__name__)
CORS(app) # Enable CORS for all routes
# Data directory and file
DATA_DIR = 'data'
DATA_FILE = os.path.join(DATA_DIR, 'journeys.json')
os.makedirs(DATA_DIR, exist_ok=True)
# In-memory store (loaded from file on startup)
journeys = []
def load_journeys():
"""Load journeys from the JSON file."""
global journeys
try:
if os.path.exists(DATA_FILE):
with open(DATA_FILE, 'r') as f:
journeys = json.load(f)
else:
journeys = []
except Exception as e:
print(f"Error loading journeys: {e}")
journeys = []
def save_journeys():
"""Save journeys to the JSON file."""
try:
with open(DATA_FILE, 'w') as f:
json.dump(journeys, f, indent=2)
except Exception as e:
print(f"Error saving journeys: {e}")
# Load existing journeys on startup
load_journeys()
def get_next_id():
"""Return the next available ID (simple integer increment)."""
if not journeys:
return 1
return max(j['id'] for j in journeys) + 1
@app.route('/api/journeys', methods=['POST'])
def create_journey():
"""Create a new journey."""
data = request.get_json()
if not data:
return jsonify({'error': 'No data provided'}), 400
title = data.get('title')
if not title:
return jsonify({'error': 'Journey title is required'}), 400
new_journey = {
'id': get_next_id(),
'title': title,
'description': data.get('description', ''),
'markers': data.get('markers', []), # list of marker objects
'created_at': datetime.now().isoformat()
}
journeys.append(new_journey)
save_journeys()
return jsonify(new_journey), 201
@app.route('/api/journeys', methods=['GET'])
def get_journeys():
"""Return all journeys."""
return jsonify(journeys)
@app.route('/api/journeys/<int:journey_id>', methods=['GET'])
def get_journey(journey_id):
"""Return a specific journey by ID."""
journey = next((j for j in journeys if j['id'] == journey_id), None)
if journey is None:
return jsonify({'error': 'Journey not found'}), 404
return jsonify(journey)
@app.route('/api/journeys/<int:journey_id>', methods=['PUT'])
def update_journey(journey_id):
"""Update an existing journey."""
journey = next((j for j in journeys if j['id'] == journey_id), None)
if journey is None:
return jsonify({'error': 'Journey not found'}), 404
data = request.get_json()
if not data:
return jsonify({'error': 'No data provided'}), 400
# Update allowed fields
if 'title' in data:
journey['title'] = data['title']
if 'description' in data:
journey['description'] = data['description']
if 'markers' in data:
journey['markers'] = data['markers']
save_journeys()
return jsonify(journey)
@app.route('/api/journeys/<int:journey_id>', methods=['DELETE'])
def delete_journey(journey_id):
"""Delete a journey."""
global journeys
journey = next((j for j in journeys if j['id'] == journey_id), None)
if journey is None:
return jsonify({'error': 'Journey not found'}), 404
journeys = [j for j in journeys if j['id'] != journey_id]
save_journeys()
return jsonify({'message': 'Journey deleted successfully', 'journey': journey})
@app.route('/api/journeys/health', methods=['GET'])
def health_check():
"""Simple health check endpoint."""
return jsonify({'status': 'healthy', 'timestamp': datetime.now().isoformat()})
@app.route('/')
def index():
"""Root endpoint just a welcome message."""
return '''
<!DOCTYPE html>
<html>
<head><title>Journey Mapper Backend</title></head>
<body>
<h1>Journey Mapper API</h1>
<p>Backend is running. Use <code>/api/journeys</code> endpoints.</p>
</body>
</html>
'''
if __name__ == '__main__':
app.run(debug=True, port=5000)

View File

@ -1,34 +0,0 @@
[
{
"id": 1,
"title": "test",
"description": "sdfsf\n",
"markers": [
{
"lat": 48.22467264956519,
"lng": 9.536132812500002,
"title": "New Marker",
"date": "",
"description": "",
"videoUrl": ""
},
{
"lat": 49.937079756975294,
"lng": 8.789062500000002,
"title": "New Marker",
"date": "",
"description": "",
"videoUrl": ""
},
{
"lat": 50.583236614805905,
"lng": 9.689941406250002,
"title": "New Marker",
"date": "",
"description": "",
"videoUrl": ""
}
],
"created_at": "2026-03-01T19:02:15.679031"
}
]

View File

@ -1,8 +0,0 @@
[project]
name = "backend"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"flask>=3.1.3",
"flask-cors>=6.0.2",
]

174
backend/uv.lock generated
View File

@ -1,174 +0,0 @@
version = 1
revision = 3
requires-python = ">=3.12"
[[package]]
name = "backend"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "flask" },
{ name = "flask-cors" },
]
[package.metadata]
requires-dist = [
{ name = "flask", specifier = ">=3.1.3" },
{ name = "flask-cors", specifier = ">=6.0.2" },
]
[[package]]
name = "blinker"
version = "1.9.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" },
]
[[package]]
name = "click"
version = "8.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "flask"
version = "3.1.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "blinker" },
{ name = "click" },
{ name = "itsdangerous" },
{ name = "jinja2" },
{ name = "markupsafe" },
{ name = "werkzeug" },
]
sdist = { url = "https://files.pythonhosted.org/packages/26/00/35d85dcce6c57fdc871f3867d465d780f302a175ea360f62533f12b27e2b/flask-3.1.3.tar.gz", hash = "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", size = 759004, upload-time = "2026-02-19T05:00:57.678Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7f/9c/34f6962f9b9e9c71f6e5ed806e0d0ff03c9d1b0b2340088a0cf4bce09b18/flask-3.1.3-py3-none-any.whl", hash = "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c", size = 103424, upload-time = "2026-02-19T05:00:56.027Z" },
]
[[package]]
name = "flask-cors"
version = "6.0.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "flask" },
{ name = "werkzeug" },
]
sdist = { url = "https://files.pythonhosted.org/packages/70/74/0fc0fa68d62f21daef41017dafab19ef4b36551521260987eb3a5394c7ba/flask_cors-6.0.2.tar.gz", hash = "sha256:6e118f3698249ae33e429760db98ce032a8bf9913638d085ca0f4c5534ad2423", size = 13472, upload-time = "2025-12-12T20:31:42.861Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4f/af/72ad54402e599152de6d067324c46fe6a4f531c7c65baf7e96c63db55eaf/flask_cors-6.0.2-py3-none-any.whl", hash = "sha256:e57544d415dfd7da89a9564e1e3a9e515042df76e12130641ca6f3f2f03b699a", size = 13257, upload-time = "2025-12-12T20:31:41.3Z" },
]
[[package]]
name = "itsdangerous"
version = "2.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" },
]
[[package]]
name = "jinja2"
version = "3.1.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markupsafe" },
]
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
]
[[package]]
name = "markupsafe"
version = "3.0.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" },
{ url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" },
{ url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" },
{ url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" },
{ url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" },
{ url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" },
{ url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" },
{ url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" },
{ url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" },
{ url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" },
{ url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" },
{ url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" },
{ url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" },
{ url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" },
{ url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" },
{ url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" },
{ url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" },
{ url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" },
{ url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" },
{ url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" },
{ url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" },
{ url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" },
{ url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" },
{ url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" },
{ url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" },
{ url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" },
{ url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" },
{ url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" },
{ url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" },
{ url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" },
{ url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" },
{ url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" },
{ url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" },
{ url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
{ url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
{ url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
{ url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
{ url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
{ url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
{ url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
{ url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
{ url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
{ url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
{ url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
{ url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
{ url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
{ url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
{ url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
{ url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
{ url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
{ url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
{ url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
{ url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
{ url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
]
[[package]]
name = "werkzeug"
version = "3.1.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markupsafe" },
]
sdist = { url = "https://files.pythonhosted.org/packages/61/f1/ee81806690a87dab5f5653c1f146c92bc066d7f4cebc603ef88eb9e13957/werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25", size = 864736, upload-time = "2026-02-19T15:17:18.884Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4d/ec/d58832f89ede95652fd01f4f24236af7d32b70cab2196dfcc2d2fd13c5c2/werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131", size = 225166, upload-time = "2026-02-19T15:17:17.475Z" },
]

View File

@ -5,28 +5,19 @@
display: flex;
height: 100vh;
overflow: hidden;
font-family: 'Poppins', sans-serif;
}
/* Sidebar Styles */
.sidebar {
width: 300px;
min-width: 300px;
background-color: #253342;
color: white;
padding: 20px;
}
.map-area {
flex: 1;
width: calc(100% - 300px);
}
#map {
width: 100%;
height: 100vh;
}
.map-area {
flex: 1;
width: 350px;
background-color: #2c3e50;
color: #ecf0f1;
display: flex;
flex-direction: column;
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease;
z-index: 10;
}
.sidebar-header {
@ -266,61 +257,6 @@
padding: 20px;
}
/* Journey Panel Styles */
.journey-panel {
padding: 20px;
}
.journey-form .form-group {
margin-bottom: 15px;
}
.journey-form label {
display: block;
margin-bottom: 5px;
}
.marker-item {
padding: 8px;
margin: 3px 0;
background-color: #f8f9fa;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.marker-item:hover {
background-color: #e9ecef;
}
.button-group {
display: flex;
gap: 10px;
margin-top: 15px;
}
.btn {
padding: 8px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-primary {
background-color: #3498db;
color: white;
}
.btn-success {
background-color: #2ecc71;
color: white;
}
.btn-danger {
background-color: #e74c3c;
color: white;
}
/* Footer */
.sidebar-footer {
padding: 15px 20px;
@ -354,6 +290,12 @@
margin: 0;
}
/* Map Area */
.map-area {
flex: 1;
position: relative;
}
#map {
width: 100%;
height: 100%;
@ -562,25 +504,25 @@
height: 100%;
transform: translateX(-100%);
}
.sidebar.active {
transform: translateX(0);
}
.app-container {
flex-direction: column;
}
.map-controls {
top: auto;
bottom: 20px;
right: 20px;
flex-direction: row;
}
.mode-indicator {
top: 10px;
left: 10px;
font-size: 0.9rem;
}
}
}

View File

@ -1,191 +1,4 @@
// /Volumes/Data/Code/FHGR/Frontend/js/main.js
document.addEventListener('DOMContentLoaded', function() {
// Journey Management
let currentJourney = {
name: "",
description: "",
markers: []
};
function saveJourney() {
const journeyData = {
name: document.getElementById('journey-name').value,
description: document.getElementById('journey-desc').value,
markers: currentJourney.markers.map(marker => ({
lat: marker.getLatLng().lat,
lng: marker.getLatLng().lng
}))
};
// Save to backend
fetch('/api/journeys', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(journeyData)
})
.then(response => response.json())
.then(data => {
console.log('Journey saved:', data);
alert('Journey saved successfully!');
currentJourney = {
name: "",
description: "",
markers: []
};
document.getElementById('journey-name').value = '';
document.getElementById('journey-desc').value = '';
})
.catch(error => console.error('Error saving journey:', error));
}
function updateMarkersList() {
const container = document.getElementById('markers-list');
container.innerHTML = '';
currentJourney.markers.forEach((marker, index) => {
const markerElement = document.createElement('div');
markerElement.className = 'marker-item';
markerElement.innerHTML = `
<strong>${index + 1}</strong>
${marker.getLatLng().lat.toFixed(4)}, ${marker.getLngLat().lng.toFixed(4)}
`;
// Add click handler to focus on marker
markerElement.addEventListener('click', () => {
map.flyTo(marker.getLatLng(), 10);
});
container.appendChild(markerElement);
});
}
// Event Listeners
document.getElementById('save-journey').addEventListener('click', saveJourney);
// Initialize current journey when page loads
window.currentJourney = {
id: Date.now(),
name: "",
description: "",
markers: [],
path: null
};
// Function to prepare and save the journey
function prepareAndSaveJourney() {
const journeyData = {
name: document.getElementById('journey-title').value,
description: document.getElementById('journey-description').value,
markers: window.currentJourney.markers.map(marker => ({
id: marker.id,
lngLat: [marker.getLatLng().lat, marker.getLatLng().lng],
content: marker.content
}))
};
// Save to backend
fetch('http://localhost:5000/api/journeys', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(journeyData)
})
.then(response => response.json())
.then(data => {
alert('Journey saved successfully!');
window.currentJourney = {
id: Date.now(),
name: "",
description: "",
markers: [],
path: null
};
document.getElementById('journey-title').value = '';
document.getElementById('journey-description').value = '';
})
.catch(error => {
console.error('Error:', error);
alert('Failed to save journey. Please try again.');
});
}
// Event listeners for the buttons
document.getElementById('add-marker-btn').addEventListener('click', function() {
map.on('click', function(e) {
const marker = L.marker(e.latlng, {draggable: true}).addTo(map);
// Add popup with input field
marker.bindPopup('<input type="text" id="marker-title" placeholder="Enter title">');
window.currentJourney.markers.push(marker);
updateMarkersList();
});
});
document.getElementById('save-journey-btn').addEventListener('click', prepareAndSaveJourney);
document.getElementById('clear-markers-btn').addEventListener('click', function() {
map.eachLayer(function(layer) {
if (layer instanceof L.Marker) {
map.removeLayer(layer);
}
});
window.currentJourney.markers = [];
updateMarkersList();
});
function updateMarkersList() {
const markersContainer = document.getElementById('markers-container');
markersContainer.innerHTML = '';
if (window.currentJourney.markers.length === 0) {
markersContainer.innerHTML = '<p class="empty-message">No markers yet. Click on the map to add markers.</p>';
return;
}
window.currentJourney.markers.forEach((marker, index) => {
const markerElement = document.createElement('div');
markerElement.className = 'marker-item';
markerElement.innerHTML = `
<strong>${index + 1}</strong>
${marker.getLatLng().lat.toFixed(4)}, ${marker.getLngLat().lng.toFixed(4)}
`;
// Add click event to edit marker
markerElement.addEventListener('click', () => {
marker.openPopup();
});
markersContainer.appendChild(markerElement);
});
}
window.journeys = [];
window.isCreatingJourney = true;
// Initialize the map
const map = L.map('map').setView([8.5, 47.3], 10);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
// Add navigation controls
L.control.scale().addTo(map);
// Add geolocate control
map.addControl(new maplibregl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true
},
trackUserLocation: true
}));
// Add fullscreen control
map.addControl(L.control.fullscreen());
// Mode switching
const modeCreateBtn = document.getElementById('mode-create');
const modeViewBtn = document.getElementById('mode-view');
@ -196,11 +9,11 @@ document.addEventListener('DOMContentLoaded', function() {
function switchMode(mode) {
if (mode === 'create') {
modeCreateBtn.classList.add('active');
-> modeCreateBtn.classList.add('active');
modeViewBtn.classList.remove('active');
createPanel.classList.add('active-panel');
viewPanel.classList.remove('active-panel');
// Enable marker adding
//ҧ Enable marker adding
window.isCreatingJourney = true;
} else { // view mode
modeCreateBtn.classList.remove('active');
@ -215,211 +28,39 @@ document.addEventListener('DOMContentLoaded', function() {
modeCreateBtn.addEventListener('click', () => switchMode('create'));
modeViewBtn.addEventListener('click', () => switchMode('view'));
async function loadJourneysFromBackend() {
try {
const response = await fetch('http://localhost:5000/api/journeys');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const journeysData = await response.json();
// Ensure we always have an array
if (!Array.isArray(journeysData)) {
console.warn('Expected array of journeys, got:', journeysData);
window.journeys = [];
return [];
}
window.journeys = journeysData;
populateJourneySelect(journeysData);
return journeysData;
} catch (err) {
console.error('Error loading journeys from backend:', err);
// Fallback to local storage
const savedJourneys = localStorage.getItem('journeys');
if (savedJourneys) {
try {
const parsedJourneys = JSON.parse(savedJourneys);
if (Array.isArray(parsedJourneys)) {
window.journeys = parsedJourneys;
populateJourneySelect(window.journeys);
return window.journeys;
} else {
console.warn('Saved journeys are not an array:', parsedJourneys);
window.journeys = [];
populateJourneySelect([]);
return [];
}
} catch (parseError) {
console.error('Error parsing saved journeys:', parseError);
window.journeys = [];
populateJourneySelect([]);
return [];
}
}
// If no data available, initialize empty array
window.journeys = [];
populateJourneySelect([]);
return [];
}
}
async function saveJourneyToBackend(journey) {
try {
const response = await fetch('http://localhost:5000/api/journeys', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: journey.id,
name: journey.name,
description: journey.description,
markers: journey.markers.map(marker => ({
lngLat: marker.getLngLat().toArray(),
content: marker.content
}))
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const journeyData = await response.json();
console.log('Journey saved:', journeyData);
return journeyData;
} catch (err) {
console.error('Error saving journey to backend:', err);
// Fallback to local storage
const savedJourneys = localStorage.getItem('journeys') || '[]';
const journeys = JSON.parse(savedJourneys);
// Update or add the journey
const existingIndex = journeys.findIndex(j => j.id === journey.id);
if (existingIndex !== -1) {
journeys[existingIndex] = {
id: journey.id,
name: journey.name,
description: journey.description,
markers: journey.markers.map(marker => ({
id: marker.id,
lngLat: marker.getLngLat(),
content: marker.content
}))
};
} else {
journeys.push({
id: journey.id,
name: journey.name,
description: journey.description,
markers: journey.markers.map(marker => ({
id: marker.id,
lngLat: marker.getLngLat(),
content: marker.content
}))
});
}
localStorage.setItem('journeys', JSON.stringify(journeys));
console.log('Journey saved to local storage');
return journey;
}
}
async function loadCurrentJourneyFromBackend(journeyId) {
try {
const response = await fetch(`http://localhost:5000/api/journeys/${journeyId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const journeyData = await response.json();
// Update the current journey object
window.currentJourney = {
id: journeyData.id,
name: journeyData.name,
description: journeyData.description,
markers: [],
path: null
};
// Create markers from the journey data
if (Array.isArray(journeyData.markers)) {
journeyData.markers.forEach(markerData => {
const lngLat = maplibregl.LngLat.fromArray(markerData.lngLat);
const marker = window.createMarker(lngLat, markerData.content);
window.currentJourney.markers.push(marker);
});
}
// Update the journey path
updateJourneyPath();
return journeyData;
} catch (err) {
console.error('Error loading journey from backend:', err);
// Fallback to local storage
const savedJourneys = localStorage.getItem('journeys');
if (savedJourneys) {
try {
const journeys = JSON.parse(savedJourneys);
if (Array.isArray(journeys)) {
const journey = journeys.find(j => j.id === journeyId);
if (journey) {
// Update the current journey object
window.currentJourney = {
id: journey.id,
name: journey.name,
description: journey.description,
markers: [],
path: null
};
// Create markers from the journey data
if (Array.isArray(journey.markers)) {
journey.markers.forEach(markerData => {
const lngLat = maplibregl.LngLat.fromArray(markerData.lngLat);
const marker = window.createMarker(lngLat, markerData.content);
window.currentJourney.markers.push(marker);
});
}
// Update the journey path
updateJourneyPath();
console.log('Journey loaded from local storage');
return journey;
}
} else {
console.warn('Saved journeys are not an array:', journeys);
}
} catch (parseError) {
console.error('Error parsing saved journeys:', parseError);
}
}
// If no data available, return null
return null;
}
}
// Update the journey select dropdown
function populateJourneySelect(journeys) {
const select = document.getElementById('journey-select');
select.innerHTML = '<option value="">-- Choose a journey --</option>';
select.innerHTML += '<option value="all">Show All Journeys</option>';
// Journey save handler
document.getElementById('save-journey').addEventListener('click', function() {
const title = document.getElementById('journey-title').value;
const description = document.getElementById('journey-description').value;
// Sort journeys by name for consistent ordering
const sortedJourneys = [...journeys].sort((a, b) => a.name.localeCompare(b.name));
if (!title.trim()) {
alert('Journey title cannot be empty');
return;
}
sortedJourneys.forEach(journey => {
const option = document.createElement('option');
option.value = journey.id;
option.textContent = journey.name;
select.appendChild(option);
});
}
window.currentJourney.name = title;
window.currentJourney.description = description;
window.saveJourneyToLocalStorage();
// Show notification
document.getElementById('toast-message').textContent = 'Journey saved successfully!';
document.getElementById('toast').classList.add('show');
setTimeout(() => {
document.getElementById('toast').classList.remove('show');
}, 3000);
});
// Clear markers
document.getElementById('clear-markers').addEventListener('click', function() {
if (window.currentJourney.markers.length > 0 && confirm('Are you sure you want to clear all markers?')) {
window.currentJourney.markers.forEach(marker => marker.remove());
window.currentJourney.markers = [];
markersContainer.innerHTML = '';
emptyMarkers.style.display = 'block';
window.updateJourneyPath();
}
});
// Toggle sidebar
document.getElementById('toggle-sidebar').addEventListener('click', function() {
document.querySelector('.sidebar').classList.toggle('collapsed');
@ -427,127 +68,4 @@ document.addEventListener('DOMContentLoaded', function() {
// Initialize in create mode
switchMode('create');
// Load journeys from backend when the page loads
loadJourneysFromBackend().then(() => {
// If there are journeys, set the first one as the current journey
if (window.journeys.length > 0) {
// Set the first journey as current
const firstJourney = window.journeys[0];
loadCurrentJourneyFromBackend(firstJourney.id).then(() => {
// Update the journey title and description
document.getElementById('journey-title').value = currentJourney.name;
document.getElementById('journey-description').value = currentJourney.description;
});
}
});
// Add journey selection functionality
document.getElementById('journey-select').addEventListener('change', function() {
const selectedId = this.value;
if (selectedId === 'all') {
// Show all journeys
// Implementation depends on how you want to display multiple journeys
return;
}
if (selectedId) {
// Load the selected journey
loadCurrentJourneyFromBackend(selectedId).then(() => {
// Update the journey title and description
document.getElementById('journey-title').value = currentJourney.name;
document.getElementById('journey-description').value = currentJourney.description;
// Update the journey info panel
document.getElementById('info-title').textContent = currentJourney.name;
document.getElementById('info-description').textContent = currentJourney.description;
document.getElementById('info-marker-count').textContent = currentJourney.markers.length;
document.getElementById('info-date').textContent = new Date().toLocaleDateString();
});
}
});
// View blog post button
document.getElementById('view-blog').addEventListener('click', function() {
// Implementation depends on your blog system
alert('Viewing blog post for this journey...');
});
// Edit journey button
document.getElementById('edit-journey').addEventListener('click', function() {
// Switch to create mode
switchMode('create');
// Update the journey title and description
document.getElementById('journey-title').value = currentJourney.name;
document.getElementById('journey-description').value = currentJourney.description;
});
// Delete journey button
document.getElementById('delete-journey').addEventListener('click', async function() {
if (confirm('Are you sure you want to delete this journey?')) {
try {
const response = await fetch(`http://localhost:5000/api/journeys/${currentJourney.id}`, {
method: 'DELETE'
});
if (response.ok) {
// Remove from the journeys array
const index = window.journeys.findIndex(j => j.id === currentJourney.id);
if (index !== -1) {
window.journeys.splice(index, 1);
}
// Update the journey select dropdown
populateJourneySelect(window.journeys);
// Clear the current journey
currentJourney = {
id: Date.now(),
name: "Untitled Journey",
description: "",
markers: [],
path: null
};
// Clear the map
map.getSource('journey-path').setData({
type: 'Feature',
properties: {},
geometry: {
type: 'LineString',
coordinates: []
}
});
// Clear the form
document.getElementById('journey-title').value = '';
document.getElementById('journey-description').value = '';
alert('Journey deleted successfully!');
} else {
alert('Failed to delete journey.');
}
} catch (err) {
console.error('Error deleting journey:', err);
// Fallback to local storage
const savedJourneys = localStorage.getItem('journeys');
if (savedJourneys) {
try {
const journeys = JSON.parse(savedJourneys);
if (Array.isArray(journeys)) {
const index = journeys.findIndex(j => j.id === currentJourney.id);
if (index !== -1) {
journeys.splice(index, 1);
localStorage.setItem('journeys', JSON.stringify(journeys));
}
}
} catch (parseError) {
console.error('Error parsing saved journeys:', parseError);
}
}
alert('Journey deleted successfully (local storage)');
}
}
});
});

273
js/map.js
View File

@ -1,101 +1,188 @@
// /Volumes/Data/Code/FHGR/Frontend/js/map.js
// Add the createMarker function to the window object
window.createMarker = function(lngLat, content = {}) {
const marker = L.marker(lngLat, {
draggable: true,
title: content.title || 'Untitled'
}).addTo(map);
// Create popup with marker content
marker.bindPopup(`<strong>${content.title || 'Untitled'}</strong>${content.description ? `<br>${content.description}` : ''}`);
// When the marker is clicked, open the editor
marker.on('click', () => {
openMarkerEditor(marker);
});
// Add marker to current journey
const markerData = {
id: Date.now(),
lngLat: [lngLat.lat, lngLat.lng], // Leaflet uses [lat, lng]
content: content,
coordinates: [lngLat.lat, lngLat.lng]
};
if (!currentJourney.markers) currentJourney.markers = [];
currentJourney.markers.push(marker);
updateJourneyPath();
// Add marker to the markers list
const markersContainer = document.getElementById('markers-container');
const markerElement = document.createElement('div');
markerElement.className = 'marker-item';
markerElement.innerHTML = `
<div class="marker-title">${content.title || 'Untitled'}</div>
<div class="marker-coords">${lngLat.lat.toFixed(4)}, ${lngLat.lng.toFixed(4)}</div>
`;
// Add click event to marker item
markerElement.addEventListener('click', function() {
map.flyTo({
center: [lngLat.lat, lngLat.lng],
zoom: 10
});
openMarkerEditor(marker);
});
if (markersContainer.children.length === 1 &&
markersContainer.firstElementChild.id === 'empty-markers') {
markersContainer.removeChild(markersContainer.firstElementChild);
}
markersContainer.appendChild(markerElement);
updateMarkersList(); // Call the new function to update the list
return marker;
let map;
let journeys = [];
let currentJourney = {
id: Date.now(),
name: "Untitled Journey",
description: "",
markers: [],
path: null
};
let currentMarkerBeingEdited = null;
let isCreatingJourney = true;
// Update the updateJourneyPath function to handle cases where markers array is empty
function updateMarkersList() {
const markersContainer = document.getElementById('markers-container');
markersContainer.innerHTML = '';
if (currentJourney.markers.length === 0) {
markersContainer.innerHTML = '<p class="empty-message">No markers yet. Click on the map to add markers.</p>';
return;
}
function saveJourneyToLocalStorage() {
journeys.push({
id: currentJourney.id,
name: currentJourney.name,
description: currentJourney.description,
markers: currentJourney.markers.map(marker => ({
id: marker.id,
lngLat: marker.getLngLat(),
content: marker.content
}))
});
localStorage.setItem('journeys', JSON.stringify(journeys));
}
currentJourney.markers.forEach((marker, index) => {
const markerElement = document.createElement('div');
markerElement.className = 'marker-item';
markerElement.innerHTML = `
<strong>${index + 1}</strong>
${marker.getLatLng().lat.toFixed(4)}, ${marker.getLngLat().lng.toFixed(4)}
`;
// Add click handler to zoom to marker
markerElement.addEventListener('click', () => {
map.flyTo({
center: [marker.getLatLng().lat, marker.getLatLng().lng],
zoom: 10
});
});
markersContainer.appendChild(markerElement);
function loadJourneysFromLocalStorage() {
const stored = localStorage.getItem('journeyMapper_journeys');
if (stored) {
journeys = JSON.parse(stored);
}
}
// Function to create a marker at a lngLat and add to the map
function createMarker(lngLat) {
const markerElement = document.createElement('div');
markerElement.className = 'marker';
markerElement.innerHTML = '<i class="fas fa-map-marker"></i>';
const marker = new maplibregl.Marker(markerElement)
.setLngLat(lngLat)
.addTo(map);
marker.id = Date.now();
marker.content = {
title: '',
date: '',
text: '',
images: [],
videoUrl: ''
};
// Add a popup
const popup = new maplibregl.Popup({ offset: 25 })
.setHTML('<strong>New Marker</strong>');
marker.setPopup(popup);
// When the marker is clicked, open the editor
markerElement.addEventListener('click', () => {
openMarkerEditor(marker);
});
return marker;
}
function openMarkerEditor(marker) {
currentMarkerBeingEdited = marker;
document.getElementById('marker-title').value = marker.content.title || '';
document.getElementById('marker-date').value = marker.content.date || '';
document.getElementById('marker-text').value = marker.content.text || '';
document.getElementById('video-url').value = marker.content.videoUrl || '';
document.getElementById('marker-coords').textContent =
`${marker.getLngLat().lng.toFixed(4)}, ${marker.getLngLat().lat.toFixed(4)}`;
// Update imagine review
const imagePreview = document.getElementById('image-preview');
imagePreview.innerHTML = '';
学生学习 if (marker.content.images && marker.content.images.length > 0) {
marker.content.images.forEach(img => {
const imgEl = document.createElement('img');
imgEl.src = img;
imagePreview.appendChild(imgEl);
});
}
document.getElementById('marker-modal').style.display = 'block';
}
function updateJourneyPath() {
if (!map.hasLayer(journeyPath)) {
journeyPath = L.polyline([], {color: '#3887be', weight: 4});
map.addLayer(journeyPath);
function closeMarkerEditor() {
document.getElementById('marker-modal').style.display = 'none';
currentMarkerBeingEdited = null;
}
document.addEventListener('DOMContentLoaded', function() {
map = new maplibregl.Map({
container: 'map',
style: 'https://demotiles.maplibre.org/style.json',
center: [0, 20],
zoom: 2
});
// Add journey path layer
map.on('load', function() {
map.addSource('journey-path', {
type: 'geojson',
data: {
type: 'Feature',
properties: {},
geometry: {
type: 'LineString',
coordinates: []
}
}
});
map.addLayer({
id: 'journey-path',
type: 'line',
source: 'journey-path',
layout: {
'line-join': 'round',
'line-cap': 'round'
},
paint: {
'line-color': '#3887be',
'line-width': 5
}
});
});
// Close editor events
document.getElementById('close-modal').addEventListener('click', closeMarkerEditor);
document.getElementById('cancel-marker').addEventListener('click', closeMarkerEditor);
// Save marker event
document.getElementById('save-marker').addEventListener('click', function() {
if (!currentMarkerBeingEdited) return;
// Update marker content
currentMarkerBeingEdited.content.title = document.getElementById('marker-title').value;
currentMarkerBeingEdited.content.date = document.getElementById('marker-date').value;
currentMarkerBeingEdited.content.text = document.getElementById('marker-text').value;
currentMarkerBeingEdited.content.videoUrl = document.getElementById('video-url').value;
// Update the popup
currentMarkerBeingEdited.getPopup().setHTML(
`<strong>${currentMarkerBeingEdited.content.title || 'Untitled'}</strong>`
);
closeMarkerEditor();
});
// Delete marker event
document.getElementById('delete-marker').addEventListener('click', function() {
if (!currentMarkerBeingEdited) return;
// Remove from map
currentMarkerBeingEdited.remove();
// Remove from currentJourney.markers
const index = currentJourney.markers.findIndex(m => m.id === currentMarkerBeingEdited.id);
if (index !== -1) {
currentJourney.markers.splice(index, 1);
}
const coordinates = currentJourney.markers.map(marker => [
marker.getLatLng().lat,
marker.getLatLng().lng
]);
journeyPath.setLatLngs(coordinates);
}
closeMarkerEditor();
});
// Add marker on map click
map.on('click', (e) => {
if (isCreatingJourney) {
const marker = createMarker(e.lngLat);
currentJourney.markers.push(marker);
updateJourneyPath();
}
});
// Save journey button
document.getElementById('save-journey-btn').addEventListener('click', function() {
currentJourney.name = document.getElementById('journey-name').value;
currentJourney.description = document.getElementById('journey-description').value;
saveJourneyToLocalStorage();
alert('Journey saved!');
});
// Load journeys on start
loadJourneysFromLocalStorage();
});

File diff suppressed because it is too large Load Diff

View File

@ -1,80 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Map Project</title>
<!-- Leaflet CSS -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<!-- Custom CSS -->
<link rel="stylesheet" href="css/map.css">
<style>
/* Your existing inline styles... */
</style>
</head>
```
map.html
```html
<<<<<<< SEARCH
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Journey Mapper</title>
<!-- Leaflet CSS -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<!-- Custom CSS -->
<link rel="stylesheet" href="css/map.css">
</head>
<body>
<div class="app-container">
<div class="sidebar">
<div class="journey-panel">
<h3><i class="fas fa-route"></i> Journey Manager</h3>
<!-- Journey Form -->
<form id="journey-form" class="journey-form">
<div class="form-group">
<label for="journey-name"><i class="fas fa-heading"></i> Journey Name:</label>
<input type="text" id="journey-name" required>
</div>
<div class="form-group">
<label for="journey-desc"><i class="fas fa-align-left"></i> Description:</label>
<textarea id="journey-desc"></textarea>
</div>
</form>
<!-- Markers List -->
<h4><i class="fas fa-map-marker-alt"></i> Journey Markers</h4>
<div id="markers-container" class="markers-list">
<!-- Markers will be added here -->
</div>
<!-- Buttons -->
<div class="button-group">
<button id="add-marker" class="btn btn-primary"><i class="fas fa-plus"></i> Add Marker</button>
<button id="save-journey" class="btn btn-success"><i class="fas fa-save"></i> Save Journey</button>
<button id="clear-markers" class="btn btn-danger"><i class="fas fa-trash"></i> Clear Markers</button>
</div>
</div>
</div>
<!-- Map container -->
<div id="map" class="map-area"></div>
</div>
<!-- Leaflet JS -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<!-- Custom JavaScript -->
<script src="js/main.js"></script>
</body>
</html>

View File

@ -1,189 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Simple Map Project</title>
<!-- Leaflet CSS -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
height: 100vh;
display: flex;
}
#map {
flex: 1;
height: 100%;
}
.sidebar {
width: 300px;
background: #f8f9fa;
padding: 20px;
box-shadow: 2px 0 5px rgba(0,0,0,0.1);
overflow-y: auto
}
.map-controls {
position: absolute;
top: 20px;
right: 20px;
display: flex;
gap: 10px;
z-index: 1000;
}
.btn {
padding: 8px 12px;
border: none;
border-radius: 4px;
background: #3887be;
color: white;
cursor: pointer;
font-family: Arial, sans-serif;
}
.btn:hover {
background: #2c6d95;
}
</style>
</head>
<body>
<div class="sidebar">
<div class="journey-panel">
<h3><i class="fas fa-route"></i> Journey Manager</h3>
<!-- Journey Form -->
<div class="journey-form">
<div class="form-group">
<label for="journey-name"><i class="fas fa-heading"></i> Journey Name:</label>
<input type="text" id="journey-name" required>
</div>
<div class="form-group">
<label for="journey-desc"><i class="fas fa-align-left"></i> Description:</label>
<textarea id="journey-desc"></textarea>
</div>
</div>
<!-- Markers List -->
<h4><i class="fas fa-map-marker-alt"></i> Journey Markers</h4>
<div id="markers-list" class="markers-container">
<!-- Markers will be added here -->
</div>
<!-- Buttons -->
<div class="button-group">
<button id="add-marker" class="btn btn-primary"><i class="fas fa-plus"></i> Add Marker</button>
<button id="save-journey" class="btn btn-success"><i class="fas fa-save"></i> Save Journey</button>
<button id="clear-markers" class="btn btn-danger"><i class="fas fa-trash"></i> Clear Markers</button>
</div>
</div>
</div>
<div id="map"></div>
<!-- Leaflet JS -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
// Initialize the map
const map = L.map('map').setView([8.5, 47.3], 10);
// Add tile layer
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
// Add controls
const controls = document.createElement('div');
controls.className = 'map-controls';
// Zoom controls
const zoomInBtn = document.createElement('button');
zoomInBtn.className = 'btn';
zoomInBtn.innerHTML = '+';
zoomInBtn.addEventListener('click', () => map.zoomIn());
const zoomOutBtn = document.createElement('button');
zoomOutBtn.className = 'btn';
zoomOutBtn.innerHTML = '-';
zoomOutBtn.addEventListener('click', () => map.zoomOut());
controls.appendChild(zoomInBtn);
controls.appendChild(zoomOutBtn);
document.body.appendChild(controls);
// Add geolocation control
const locateBtn = document.createElement('button');
locateBtn.className = 'btn';
locateBtn.innerHTML = '📍';
locateBtn.addEventListener('click', () => {
map.locate({setView: true});
});
controls.appendChild(locateBtn);
// Marker functionality
let markers = [];
document.getElementById('add-marker').addEventListener('click', () => {
map.on('click', function(e) {
const marker = L.marker(e.latlng, {draggable: true}).addTo(map);
marker.bindPopup('<input type="text" placeholder="Enter title">');
markers.push(marker);
updateMarkerList();
});
});
document.getElementById('clear-markers').addEventListener('click', () => {
map.eachLayer(function(layer) {
if (layer instanceof L.Marker) {
map.removeLayer(layer);
}
});
markers = [];
updateMarkerList();
});
function updateMarkerList() {
const list = document.getElementById('marker-list');
list.innerHTML = '';
markers.forEach((marker, index) => {
const li = document.createElement('div');
li.textContent = `Marker ${index + 1}: ${marker.getLatLng().lat.toFixed(4)}, ${marker.getLngLat().lng.toFixed(4)}`;
list.appendChild(li);
});
}
// Initialize with some basic markers
map.on('ready', () => {
const initialMarkers = [
[8.5, 47.3],
[48.8566, 2.3522]
];
initialMarkers.forEach(lngLat => {
const marker = L.marker(lngLat).addTo(map);
markers.push(marker);
});
updateMarkerList();
});
</script>
</body>
</html>