|
|
| <!DOCTYPE html> |
| <html lang="ja"> |
| <head> |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| <link href="https://fonts.googleapis.com/css2?family=Yomogi&display=swap" rel="stylesheet"> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1"> |
| <title>マーカー編集機能付きのLeafletマップ</title> |
| <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" /> |
| <style> |
| body{ |
| font-family: "Yomogi", cursive; |
| font-weight: 400; |
| font-style: normal; |
| background-repeat: repeat; background-image:url("https://lh3.googleusercontent.com/d/1MpGi0bdyjKZNYgKQyMWpIZM0R0q1i4rD"); |
| background-attachment: fixed; |
| |
| } |
| #map { height: 600px; width: 100%; } |
| #marker-editor { display: none; position: absolute; top: 10px; left: 10px; background: #fff; padding: 15px; border-radius: 5px; box-shadow: 0 0 15px rgba(0, 0, 0, .3); z-index: 1000; cursor: move; } |
| #marker-editor input, #marker-editor textarea { display: block; width: 100%; margin-bottom: 10px; } |
| #marker-editor button { width: 100%; padding: 10px; border: none; background-color: #007bff; color: #fff; font-size: 16px; border-radius: 4px; cursor: pointer; } |
| #marker-editor button:hover { background-color: #0056b3; } |
| input[type=file] { color: #1f2937; cursor: pointer; border: 1px solid #bfc2c7; border-radius: .375rem; padding-right: .5rem; width: 24rem; } |
| ::-webkit-file-upload-button, ::file-selector-button { background-color: #d1d5db; color: #1f2937; border: none; cursor: pointer; border-right: 1px solid #bfc2c7; padding: .25rem 1rem; margin-right: 1rem; } |
| #icon-preview { display: none; margin-top: 10px; max-width: 100%; } |
| #icon-settings { display: none; margin-top: 10px; } |
| #icon-settings label { display: block; margin-bottom: 5px; } |
| |
| body::-webkit-scrollbar { |
| width: 5px; |
| background-color: #c9d8f5; |
| } |
| body::-webkit-scrollbar-thumb { |
| background: #67b9e6; |
| width: 5px; |
| border-radius: 5px; |
| } |
| #loading { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background-color: rgba(255, 255, 255, 0.8); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 24px; |
| z-index: 9999; |
| opacity: 1; |
| transition: opacity 0.5s ease; |
| } |
| </style> |
| </head> |
| <body> |
| <div id="loading">Loading...</div> |
| <img src="https://lh3.googleusercontent.com/d/1Wo9oLj4D6JEsT_MWUZ4VyaIy10uLN3q0" /> |
| <button id="edit-next-marker" style="font-family: inherit; font-weight: 700; font-style: normal;">次のマーカーを編集</button> |
| <div id="map" style="width:90%; margin:5%;"></div> |
| <div id="marker-editor"> |
| <h3>マーカーを編集</h3> |
| <label for="marker-lat">緯度:</label> |
| <input type="text" id="marker-lat"> |
| <hr> |
| <label for="marker-lng">経度:</label> |
| <input type="text" id="marker-lng"> |
| <hr> |
| <a href="javascript:window.open('/marker.html');">アイコン</a> |
| <input type="radio" name="icon-source" value="upload" checked>アップロードから |
| <input type="radio" name="icon-source" value="url">URLから読み込む |
| <div id="icon-upload-input" style="display: block; margin-bottom: 20px;"> |
| <label for="marker-icon-upload">マーカーのアイコンをアップロード:</label> |
| <input type="file" id="marker-icon-upload" accept="image/*"> |
| </div> |
| <div id="icon-url-input" style="display: none; margin-bottom: 20px;"> |
| <label for="marker-icon-url">マーカーのアイコンURL:</label> |
| <input type="text" id="marker-icon-url" value="https://unpkg.com/leaflet@1.9.3/dist/images/marker-icon-2x.png"> |
| <button id="load-icon-url">画像を編集</button> |
| </div> |
| <img id="icon-preview" src="" alt="アイコンプレビュー"> |
| <div id="icon-settings"> |
| <label for="icon-width">アイコンの幅:</label> |
| <input type="range" id="icon-width" min="10" max="100" value="25"> |
| <span id="icon-width-value">25</span>px |
| <label for="icon-height">アイコンの高さ:</label> |
| <input type="range" id="icon-height" min="10" max="100" value="41"> |
| <span id="icon-height-value">41</span>px |
| </div> |
| <label for="marker-popup">ポップアップHTML:</label> |
| <textarea id="marker-popup" rows="4"></textarea> |
| <label for="marker-tooltip">ツールチップHTML:</label> |
| <textarea id="marker-tooltip" rows="4"></textarea> |
| <button id="save-marker" style="font-family: inherit; font-weight: 800; font-style: normal;">OK</button> |
| </div> |
| <textarea id="output-html" rows="20" cols="100" readonly hidden></textarea><div id="output-code" style="word-break: break-all;">ここに生成されたHTMLが表示されます。</div><button id="copyButton" style="font-family: inherit; font-weight: 700; font-style: normal;">コードをコピー</button><br> |
| <button id="generate-html" style="width: 50%; padding: 10px; border: none; background-color: #007bff; color: #fff; font-size: 16px; border-radius: 4px; cursor: pointer; margin: 20px; font-family: inherit; font-weight: 800; font-style: normal;">HTMLを生成</button> |
| |
|
|
|
|
| <script> |
| const output = document.getElementById('output-code'); |
| output.innerHTML = hljs.highlight('html', "ここに生成されたHTMLが表示されます。").value; |
| output.classList.add('hljs');</script> |
| <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/mono-blue.min.css"> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js"></script> |
| |
| <script> |
| window.onload = function() { |
| const loading = document.getElementById('loading'); |
| loading.style.opacity = '0'; |
| setTimeout(() => { |
| loading.style.display = 'none'; |
| loadMapFromStorage(); |
| }, 500); |
| }; |
| </script> |
|
|
|
|
| <script> |
| |
| function saveMapToStorage() { |
| const markers = []; |
| map.eachLayer((layer) => { |
| if (layer instanceof L.Marker) { |
| const marker = layer; |
| const icon = marker.options.icon; |
| const { lat, lng } = marker.getLatLng(); |
| markers.push({ |
| lat: lat, |
| lng: lng, |
| iconUrl: icon.options.iconUrl, |
| iconSize: icon.options.iconSize, |
| popupContent: marker.getPopup() ? marker.getPopup().getContent() : '', |
| tooltipContent: marker.getTooltip() ? marker.getTooltip().getContent() : '', |
| }); |
| } |
| }); |
| localStorage.setItem('leafletMap', JSON.stringify(markers)); |
| } |
| |
| function loadMapFromStorage() { |
| const storedMarkers = JSON.parse(localStorage.getItem('leafletMap')); |
| if (!storedMarkers) { |
| console.warn('No markers found in local storage.'); |
| return; |
| } |
| |
| if (!map) { |
| console.error('Map is not initialized.'); |
| return; |
| } |
| |
| storedMarkers.forEach((data) => { |
| if (!data.iconUrl || !data.iconSize) { |
| console.error('Icon data is incomplete.'); |
| return; |
| } |
| |
| const icon = L.icon({ |
| iconUrl: data.iconUrl, |
| iconSize: data.iconSize, |
| iconAnchor: [data.iconSize[0] / 2, data.iconSize[1]], |
| popupAnchor: [0, -data.iconSize[1]], |
| tooltipAnchor: [data.iconSize[0] / 2, -data.iconSize[1] / 2], |
| }); |
| const marker = L.marker([data.lat, data.lng], { icon: icon }).addTo(map); |
| if (data.popupContent) marker.bindPopup(data.popupContent); |
| if (data.tooltipContent) marker.bindTooltip(data.tooltipContent); |
| |
| // ⭐ここから追加⭐ |
| marker.on("mouseover", function() { |
| hoveredMarker = marker; |
| console.log("Marker hovered:", marker); |
| }); |
| marker.on("mouseout", function() { |
| if (hoveredMarker === marker) { |
| hoveredMarker = null; |
| console.log("Marker no longer hovered"); |
| } |
| }); |
| // ⭐ここまで追加⭐ |
| |
| }); |
| } |
| |
| function updatePreviewSize() { |
| var e = document.getElementById("icon-width").value; |
| var t = document.getElementById("icon-height").value; |
| var n = document.getElementById("icon-preview"); |
| n.style.width = e + "px"; |
| n.style.height = t + "px"; |
| document.getElementById("icon-width-value").textContent = e; |
| document.getElementById("icon-height-value").textContent = t; |
| if (editingMarker) { |
| var o = document.querySelector('input[name="icon-source"]:checked').value === "url" ? document.getElementById("marker-icon-url").value : n.src; |
| var i = L.icon({ |
| iconUrl: o, |
| iconSize: [e, t], |
| iconAnchor: [e / 2, t], |
| popupAnchor: [0, -t], |
| tooltipAnchor: [e / 2, -t / 2] |
| }); |
| editingMarker.setIcon(i); |
| saveMapToStorage(); |
| } |
| } |
| |
| function openEditor(e) { |
| const t = e.getLatLng(); |
| document.getElementById("marker-lat").value = t.lat; |
| document.getElementById("marker-lng").value = t.lng; |
| document.getElementById("marker-popup").value = e.getPopup() ? e.getPopup().getContent() : ""; |
| document.getElementById("marker-tooltip").value = e.getTooltip() ? e.getTooltip().getContent() : ""; |
| document.getElementById("marker-editor").style.display = "block"; |
| editingMarker = e; |
| |
| // ここから追加 |
| const icon = e.options.icon; |
| if (icon && icon.options) { |
| if (icon.options.iconUrl) { |
| // URLから読み込むラジオボタンを選択 |
| document.querySelector('input[name="icon-source"][value="url"]').checked = true; |
| document.getElementById("icon-url-input").style.display = "block"; |
| document.getElementById("icon-upload-input").style.display = "none"; |
| document.getElementById("marker-icon-url").value = icon.options.iconUrl; |
| document.getElementById("icon-preview").src = icon.options.iconUrl; |
| document.getElementById("icon-preview").style.display = 'block'; |
| // 高さと幅の設定を表示 |
| document.getElementById("icon-settings").style.display = "block"; |
| } else { |
| // URLから読み込むラジオボタンを選択 |
| document.querySelector('input[name="icon-source"][value="upload"]').checked = true; |
| document.getElementById("icon-url-input").style.display = "none"; |
| document.getElementById("icon-upload-input").style.display = "block"; |
| document.getElementById("marker-icon-url").value = icon.options.iconUrl; |
| document.getElementById("icon-preview").src = icon.options.iconUrl; |
| document.getElementById("icon-preview").style.display = 'block'; |
| // 高さと幅の設定を表示 |
| document.getElementById("icon-settings").style.display = "block"; |
| } |
| document.getElementById("icon-width").value = icon.options.iconSize[0]; |
| document.getElementById("icon-height").value = icon.options.iconSize[1]; |
| document.getElementById("icon-width-value").textContent = icon.options.iconSize[0]; |
| document.getElementById("icon-height-value").textContent = icon.options.iconSize[1]; |
| } |
| // ここまで追加 |
| |
| updatePreviewSize(); |
| } |
| |
| function generateMapHTML() { |
| const e = []; |
| map.eachLayer((function(t) { |
| if (t instanceof L.Marker) { |
| const n = t; |
| const o = n.options.icon; |
| const i = o.options.iconUrl; |
| const r = o.options.iconSize; |
| const c = n.getLatLng(); |
| const l = n.getPopup() ? n.getPopup().getContent() : ""; |
| const a = n.getTooltip() ? n.getTooltip().getContent() : ""; |
| e.push({ |
| lat: c.lat, |
| lng: c.lng, |
| iconUrl: i, |
| iconWidth: r[0], |
| iconHeight: r[1], |
| popupContent: l, |
| tooltipContent: a |
| }); |
| } |
| })); |
| const t = map.getCenter(); |
| const n = map.getZoom(); |
| let o = ` |
| <div id="map" style="height: 600px; width: 100%;"></div> |
| <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" /> |
| <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"><\/script> |
| <script> |
| var map = L.map('map').setView([${t.lat}, ${t.lng}], ${n}); |
| L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { |
| attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' |
| }).addTo(map); |
| |
| ${e.map(e => ` |
| var icon = L.icon({ |
| iconUrl: '${e.iconUrl}', |
| iconSize: [${e.iconWidth}, ${e.iconHeight}], |
| iconAnchor: [${e.iconWidth} / 2, ${e.iconHeight}], |
| popupAnchor: [0, -${e.iconHeight}], |
| tooltipAnchor: [${e.iconWidth} / 2, -${e.iconHeight} / 2] |
| }); |
| |
| var marker = L.marker([${e.lat}, ${e.lng}], { |
| icon: icon, |
| zIndexOffset: 1000 |
| }).addTo(map); |
| |
| ${e.popupContent ? `marker.bindPopup('${e.popupContent.replace(/'/g, "\\'").replace(/<\/script>/g, "<\\/script>")}');` : ""} |
| ${e.tooltipContent ? `marker.bindTooltip('${e.tooltipContent.replace(/'/g, "\\'").replace(/<\/script>/g, "<\\/script>")}');` : ""} |
| `).join("\n")} |
| <\/script> |
| `; |
| o = o.replace(/iconUrl: 'marker-icon\.png'/g, `iconUrl: '${location.origin}/marker-icon.png'`); |
| document.getElementById("output-html").value = o; |
| const input = document.getElementById('output-html').value; |
| const output = document.getElementById('output-code'); |
| output.innerHTML = hljs.highlight('html', input).value; |
| output.classList.add('hljs'); |
| } |
| |
| function resizeImage(e, t, n, o) { |
| const i = new FileReader(); |
| i.onload = function(e) { |
| const i = new Image(); |
| i.onload = function() { |
| const e = document.createElement("canvas"); |
| e.width = t; |
| e.height = n; |
| e.getContext("2d").drawImage(i, 0, 0, t, n); |
| o(e.toDataURL()); |
| }; |
| i.src = e.target.result; |
| }; |
| i.readAsDataURL(e); |
| } |
| |
| document.getElementById("marker-icon-upload").addEventListener("change", function() { |
| const e = this.files[0]; |
| const t = document.getElementById("icon-preview"); |
| if (e) { |
| resizeImage(e, parseInt(document.getElementById("icon-width").value), parseInt(document.getElementById("icon-height").value), function(imageDataUrl) { |
| t.src = imageDataUrl; |
| t.style.display = "block"; |
| document.getElementById("icon-settings").style.display = "block"; // 高さと幅の設定を表示 |
| updatePreviewSize(); |
| }); |
| } else { |
| t.style.display = "none"; |
| document.getElementById("icon-settings").style.display = "none"; // アップロードされていない場合は非表示 |
| } |
| }); |
| |
| |
| document.getElementById("load-icon-url").addEventListener("click", function() { |
| const e = document.getElementById("marker-icon-url").value; |
| const t = document.getElementById("icon-preview"); |
| if (e) { |
| t.src = e; |
| t.onload = function() { |
| t.style.display = "block"; |
| document.getElementById("icon-settings").style.display = "block"; |
| updatePreviewSize(); |
| }; |
| t.onerror = function() { |
| alert("画像の読み込みに失敗しました。URLを確認してください。"); |
| t.style.display = "none"; |
| document.getElementById("icon-settings").style.display = "none"; |
| }; |
| } else { |
| t.style.display = "none"; |
| document.getElementById("icon-settings").style.display = "none"; |
| } |
| }); |
| |
| document.getElementById("icon-width").addEventListener("input", updatePreviewSize); |
| document.getElementById("icon-height").addEventListener("input", updatePreviewSize); |
| |
| const map = L.map("map").setView([33.321797711641395, 130.52061378343208], 16); |
| L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", { attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' }).addTo(map); |
| |
| let editingMarker = null; |
| let hoveredMarker = null; |
| |
| map.on("click", function(e) { |
| if (editingMarker) { |
| const t = e.latlng; |
| editingMarker.setLatLng([t.lat, t.lng]); |
| document.getElementById("marker-lat").value = t.lat; |
| document.getElementById("marker-lng").value = t.lng; |
| updatePreviewSize(); |
| saveMapToStorage(); |
| } else { |
| const t = e.latlng; |
| const n = L.marker(t).addTo(map); |
| n.bindPopup("新しいマーカー"); |
| n.bindTooltip("新しいマーカーのツールチップ"); |
| n.on("mouseover", function() { |
| hoveredMarker = n; |
| console.log("Marker hovered:", n); |
| }); |
| n.on("mouseout", function() { |
| hoveredMarker === n && (hoveredMarker = null, console.log("Marker no longer hovered")); |
| }); |
| openEditor(n); |
| } |
| }); |
| |
| document.querySelectorAll('input[name="icon-source"]').forEach(function(e) { |
| e.addEventListener("change", function() { |
| const value = this.value; |
| if (value === "url") { |
| document.getElementById("icon-url-input").style.display = "block"; |
| document.getElementById("icon-upload-input").style.display = "none"; |
| } else { |
| document.getElementById("icon-url-input").style.display = "none"; |
| document.getElementById("icon-upload-input").style.display = "block"; |
| } |
| updatePreviewSize(); |
| }); |
| }); |
| |
| document.getElementById("save-marker").addEventListener("click", function() { |
| if (editingMarker) { |
| const lat = parseFloat(document.getElementById("marker-lat").value); |
| const lng = parseFloat(document.getElementById("marker-lng").value); |
| const popupContent = document.getElementById("marker-popup").value; |
| const tooltipContent = document.getElementById("marker-tooltip").value; |
| |
| const iconSource = document.querySelector('input[name="icon-source"]:checked').value; |
| let iconUrl = ""; |
| |
| if (iconSource === "url") { |
| iconUrl = document.getElementById("marker-icon-url").value; |
| } else if (iconSource === "upload") { |
| const file = document.getElementById("marker-icon-upload").files[0]; |
| if (file) { |
| // Blob URLからData URLに変換する処理 |
| resizeImage(file, parseInt(document.getElementById("icon-width").value), parseInt(document.getElementById("icon-height").value), function(dataUrl) { |
| iconUrl = dataUrl; |
| applyIconAndSaveMarker(lat, lng, popupContent, tooltipContent, iconUrl); |
| }); |
| saveMapToStorage(); |
| return; // 非同期処理が完了するまで待つためここでリターン |
| } |
| } |
| |
| // URLの場合や、アップロードファイルがない場合の処理 |
| applyIconAndSaveMarker(lat, lng, popupContent, tooltipContent, iconUrl); |
| } |
| }); |
| |
| function applyIconAndSaveMarker(lat, lng, popupContent, tooltipContent, iconUrl) { |
| const iconWidth = parseInt(document.getElementById("icon-width").value); |
| const iconHeight = parseInt(document.getElementById("icon-height").value); |
| |
| editingMarker.setLatLng([lat, lng]); |
| if (iconUrl) { |
| const icon = L.icon({ |
| iconUrl: iconUrl, |
| iconSize: [iconWidth, iconHeight], |
| iconAnchor: [iconWidth / 2, iconHeight], |
| popupAnchor: [0, -iconHeight], |
| tooltipAnchor: [iconWidth / 2, -iconHeight / 2] |
| }); |
| editingMarker.setIcon(icon); |
| } |
| |
| editingMarker.bindPopup(popupContent); |
| editingMarker.bindTooltip(tooltipContent); |
| |
| document.getElementById("marker-editor").style.display = "none"; |
| editingMarker = null; |
| saveMapToStorage(); |
| } |
| |
| |
| document.addEventListener("keydown", function(e) { |
| if (e.key === "e" && hoveredMarker) { |
| openEditor(hoveredMarker); |
| } |
| }); |
| |
| (function() { |
| const e = document.getElementById("marker-editor"); |
| let t, n, o = false; |
| e.addEventListener("mousedown", function(i) { |
| if (!i.target.closest("input, textarea, button")) { |
| t = i.clientX - e.getBoundingClientRect().left; |
| n = i.clientY - e.getBoundingClientRect().top; |
| o = true; |
| } |
| }); |
| document.addEventListener("mousemove", function(i) { |
| if (o) { |
| e.style.left = i.clientX - t + "px"; |
| e.style.top = i.clientY - n + "px"; |
| } |
| }); |
| document.addEventListener("mouseup", function() { |
| o = false; |
| }); |
| })(); |
| document.getElementById("copyButton").onclick = function() { |
| const textToCopy = document.getElementById("output-code").innerText; |
| navigator.clipboard.writeText(textToCopy).then(() => { |
| alert("テキストがコピーされました!"); |
| }).catch(err => { |
| console.error('コピーに失敗しました:', err); |
| }); |
| }; |
| |
| document.getElementById("generate-html").addEventListener("click", generateMapHTML); |
| |
| let nextMarkerEdit = null; |
| |
| document.getElementById("edit-next-marker").addEventListener("click", function() { |
| nextMarkerEdit = true; |
| alert("次にクリックするマーカーを編集します。"); |
| }); |
| |
| map.on("click", function(e) { |
| if (nextMarkerEdit) { |
| const clickedMarkers = []; |
| map.eachLayer(function (layer) { |
| if (layer instanceof L.Marker) { |
| const distance = map.distance(e.latlng, layer.getLatLng()); |
| if (distance < 20) { |
| clickedMarkers.push(layer); |
| } |
| } |
| }); |
| |
| if (clickedMarkers.length > 0) { |
| openEditor(clickedMarkers[0]); |
| } |
| nextMarkerEdit = false; |
| } else { |
| // 既存のクリックイベント処理 |
| if (editingMarker) { |
| const t = e.latlng; |
| editingMarker.setLatLng([t.lat, t.lng]); |
| document.getElementById("marker-lat").value = t.lat; |
| document.getElementById("marker-lng").value = t.lng; |
| updatePreviewSize(); |
| saveMapToStorage(); |
| } else { |
| const t = e.latlng; |
| const n = L.marker(t).addTo(map); |
| n.bindPopup("新しいマーカー"); |
| n.bindTooltip("新しいマーカーのツールチップ"); |
| n.on("mouseover", function() { |
| hoveredMarker = n; |
| }); |
| n.on("mouseout", function() { |
| hoveredMarker === n && (hoveredMarker = null); |
| }); |
| document.GetelementById("marker-icon-url").value = "https://unpkg.com/leaflet@1.9.3/dist/images/marker-icon-2x.png"; |
| openEditor(n); |
| saveMapToStorage(); |
| document.GetelementById("marker-icon-url").value = "https://unpkg.com/leaflet@1.9.3/dist/images/marker-icon-2x.png"; |
| } |
| } |
| }); |
| |
| // マーカー移動時にも保存 |
| map.on("markerdragend", function(e) { |
| saveMapToStorage(); // マーカー移動後に保存 |
| }); |
| |
| map.on("contextmenu", function (e) { |
| const clickedMarkers = []; |
| |
| // クリックされた位置と既存のマーカーの距離を計算 |
| map.eachLayer(function (layer) { |
| if (layer instanceof L.Marker) { |
| const distance = map.distance(e.latlng, layer.getLatLng()); |
| if (distance < 20) { // 近い距離(ピクセル相当の距離)内のマーカーを検出 |
| clickedMarkers.push(layer); |
| } |
| } |
| }); |
| |
| // クリックされたマーカーがあれば、最初の1つに対して編集を開く |
| if (clickedMarkers.length > 0) { |
| openEditor(clickedMarkers[0]); |
| } |
| }); |
| |
| // マップが動いたときに保存 |
| </script> |
| </body> |
| </html> |