map-editor / index.html
soiz1's picture
Update index.html
bf4347f verified
raw
history blame
26.1 kB
<!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>
<!-- HTML入力エリアの追加 -->
<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: '&copy; <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: '&copy; <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>