soiz1 commited on
Commit
37e11aa
·
verified ·
1 Parent(s): 275c93a

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1497 -112
index.html CHANGED
@@ -391,6 +391,18 @@
391
  color: var(--hacker-primary);
392
  font-weight: bold;
393
  margin-bottom: 0.5rem;
 
 
 
 
 
 
 
 
 
 
 
 
394
  }
395
 
396
  .gallery-map-preview {
@@ -449,6 +461,218 @@
449
  pointer-events: none;
450
  cursor: not-allowed;
451
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
  </style>
453
  </head>
454
 
@@ -485,6 +709,15 @@
485
  <button onclick="if(confirm('現在のマップのすべてのデータが消去されます。いいですか?')){clearCurrentMap()}" class="hacker-btn danger">
486
  <span class="glow-text">現在のマップをリセット</span>
487
  </button>
 
 
 
 
 
 
 
 
 
488
  </div>
489
 
490
  <div class="hacker-container">
@@ -492,6 +725,7 @@
492
  <div id="map"></div>
493
  </div>
494
 
 
495
  <div id="marker-editor">
496
  <h3>MARKER EDITOR</h3>
497
  <div class="terminal-line">緯度:</div>
@@ -537,6 +771,124 @@
537
  </button>
538
  </div>
539
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
540
  <div class="hacker-container">
541
  <button id="generate-html" class="hacker-btn">
542
  <span class="glow-text">HTMLを生成</span>
@@ -575,6 +927,30 @@
575
  </div>
576
  </div>
577
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
578
  <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>
579
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/mono-blue.min.css">
580
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js"></script>
@@ -586,6 +962,11 @@
586
  let nextMarkerEdit = false;
587
  let markers = [];
588
  let currentMapName = '';
 
 
 
 
 
589
 
590
  // 初期化処理
591
  window.onload = function() {
@@ -595,70 +976,113 @@
595
  loading.style.display = 'none';
596
  initMap();
597
  updateEditNextMarkerButton();
 
598
  }, 1000);
599
 
600
  // イベントリスナーの設定
601
  setupEventListeners();
602
  };
603
- // マップ初期化
604
- function initMap() {
605
- map = L.map("map").setView([33.321797711641395, 130.52061378343208], 16);
606
- L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
607
- attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors'
608
- }).addTo(map);
609
 
610
- // マーカーイベントの設定
611
- map.on("click", function(e) {
612
- if (nextMarkerEdit) {
613
- return;
614
- }
 
615
 
616
- if (editingMarker) {
617
- const latlng = e.latlng;
618
- editingMarker.setLatLng([latlng.lat, latlng.lng]);
619
- document.getElementById("marker-lat").value = latlng.lat;
620
- document.getElementById("marker-lng").value = latlng.lng;
621
- updatePreviewSize();
622
- saveCurrentMapToStorage();
623
- } else {
624
- const latlng = e.latlng;
625
- const marker = L.marker(latlng).addTo(map);
626
- marker.bindPopup("新しいマーカーのポップアップ");
627
- marker.bindTooltip("新しいマーカーのツールチップ");
628
-
629
- marker.on("mouseover", function() {
630
- hoveredMarker = marker;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
631
  });
632
-
633
- marker.on("mouseout", function() {
634
- if (hoveredMarker === marker) {
635
- hoveredMarker = null;
 
 
 
 
 
 
 
 
 
 
 
 
636
  }
637
  });
638
-
639
- document.getElementById("marker-icon-url").value = "https://unpkg.com/leaflet@1.9.3/dist/images/marker-icon-2x.png";
640
- openEditor(marker);
641
- saveCurrentMapToStorage();
642
  }
643
- });
644
 
645
- // マーカーが変更されたらボタンの状態を更新
646
- map.on('layeradd layerremove', function() {
647
- updateEditNextMarkerButton();
648
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
649
 
650
- // ポップアップが開いた時の処理をここに移動
651
- map.on('popupopen', function(e) {
652
- const marker = e.popup._source;
653
- if (nextMarkerEdit) {
654
- openEditor(marker);
655
- nextMarkerEdit = false;
656
- document.getElementById("edit-next-marker").textContent = "次のマーカーを編集";
657
- document.getElementById("edit-next-marker").classList.remove("danger");
658
- document.getElementById("edit-next-marker").classList.add("secondary");
 
 
659
  }
660
- });
661
- }
662
 
663
  // イベントリスナーの設定
664
  function setupEventListeners() {
@@ -695,6 +1119,625 @@ function initMap() {
695
  // HTML生成関連
696
  document.getElementById("generate-html").addEventListener("click", generateMapHTML);
697
  document.getElementById("copyButton").onclick = copyHTMLToClipboard;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
698
  }
699
 
700
  // マーカー編集モードのトグル
@@ -948,9 +1991,12 @@ function initMap() {
948
  const mapData = {
949
  center: map.getCenter(),
950
  zoom: map.getZoom(),
951
- markers: []
 
 
952
  };
953
 
 
954
  map.eachLayer((layer) => {
955
  if (layer instanceof L.Marker) {
956
  const marker = layer;
@@ -967,6 +2013,32 @@ function initMap() {
967
  }
968
  });
969
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
970
  if (currentMapName) {
971
  // 既存のマップを更新
972
  const savedMaps = JSON.parse(localStorage.getItem('savedMaps')) || {};
@@ -996,9 +2068,12 @@ function initMap() {
996
  const mapData = {
997
  center: map.getCenter(),
998
  zoom: map.getZoom(),
999
- markers: []
 
 
1000
  };
1001
 
 
1002
  map.eachLayer((layer) => {
1003
  if (layer instanceof L.Marker) {
1004
  const marker = layer;
@@ -1015,6 +2090,32 @@ function initMap() {
1015
  }
1016
  });
1017
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1018
  const savedMaps = JSON.parse(localStorage.getItem('savedMaps')) || {};
1019
  savedMaps[mapName] = mapData;
1020
  localStorage.setItem('savedMaps', JSON.stringify(savedMaps));
@@ -1040,19 +2141,50 @@ function initMap() {
1040
  const mapItem = document.createElement("div");
1041
  mapItem.className = "gallery-map-item";
1042
 
1043
- mapItem.innerHTML = `
1044
- <div class="gallery-map-title">${name}</div>
1045
- <div class="gallery-map-preview">
1046
- マーカー数: ${data.markers.length}<br>
1047
- 中心座標: ${data.center.lat.toFixed(4)}, ${data.center.lng.toFixed(4)}<br>
1048
- ズームレベル: ${data.zoom}
1049
- </div>
1050
- <div class="gallery-map-actions">
1051
- <button class="hacker-btn gallery-btn load-map-btn" data-name="${name}">読み込み</button>
1052
- <button class="hacker-btn gallery-btn danger delete-map-btn" data-name="${name}">削除</button>
1053
- </div>
1054
- `;
 
 
 
 
 
 
1055
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1056
  mapList.appendChild(mapItem);
1057
  }
1058
 
@@ -1075,62 +2207,220 @@ function initMap() {
1075
  gallery.style.display = "block";
1076
  }
1077
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1078
  // マップギャラリーを非表示
1079
  function hideGallery() {
1080
  document.getElementById("gallery-container").style.display = "none";
1081
  }
1082
 
1083
  // ギャラリーからマップを読み込み
1084
- function loadMapFromGallery(mapName) {
1085
- const savedMaps = JSON.parse(localStorage.getItem('savedMaps')) || {};
1086
- const mapData = savedMaps[mapName];
1087
-
1088
- if (!mapData) {
1089
- alert("マップデータが見つかりません");
1090
- return;
1091
- }
1092
-
1093
- // 現在のマップをクリア
1094
- clearCurrentMap();
1095
-
1096
- // 新しいマップを読み込み
1097
- map.setView(mapData.center, mapData.zoom);
1098
- currentMapName = mapName; // 現在のマップ名を更新
1099
-
1100
- // マーカーを追加
1101
- mapData.markers.forEach((markerData) => {
1102
- const icon = L.icon({
1103
- iconUrl: markerData.iconUrl,
1104
- iconSize: markerData.iconSize,
1105
- iconAnchor: [markerData.iconSize[0] / 2, markerData.iconSize[1]],
1106
- popupAnchor: [0, -markerData.iconSize[1]],
1107
- tooltipAnchor: [markerData.iconSize[0] / 2, -markerData.iconSize[1] / 2],
1108
- });
1109
 
1110
- const marker = L.marker([markerData.lat, markerData.lng], { icon: icon }).addTo(map);
1111
- if (markerData.popupContent) marker.bindPopup(markerData.popupContent);
1112
- if (markerData.tooltipContent) marker.bindTooltip(markerData.tooltipContent);
 
1113
 
1114
- marker.on("mouseover", function() {
1115
- hoveredMarker = marker;
1116
- });
1117
 
1118
- marker.on("mouseout", function() {
1119
- if (hoveredMarker === marker) {
1120
- hoveredMarker = null;
1121
- }
1122
- });
1123
- });
1124
 
1125
- // 保存フォームにマップ名をセット
1126
- document.getElementById("save-map-name").value = currentMapName;
1127
-
1128
- // ユーザーに通知
1129
- alert(`マップ「${mapName}」を読み込みました`);
1130
-
1131
- hideGallery();
1132
- updateEditNextMarkerButton();
1133
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1134
 
1135
  // ギャラリーからマップを削除
1136
  function deleteMapFromGallery(mapName) {
@@ -1148,12 +2438,21 @@ function loadMapFromGallery(mapName) {
1148
  // 現在のマップをクリア
1149
  function clearCurrentMap() {
1150
  map.eachLayer(layer => {
1151
- if (layer instanceof L.Marker) {
1152
  map.removeLayer(layer);
1153
  }
1154
  });
 
 
 
 
 
 
 
 
1155
  currentMapName = '';
1156
  updateEditNextMarkerButton();
 
1157
  }
1158
 
1159
  // HTMLを生成
@@ -1182,8 +2481,90 @@ function loadMapFromGallery(mapName) {
1182
 
1183
  const center = map.getCenter();
1184
  const zoom = map.getZoom();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1185
  let html = `<div id="map" style="height: 600px; width: 100%;">
1186
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" />
 
1187
  <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"><\/script>
1188
  <script>
1189
  var map = L.map('map').setView([${center.lat}, ${center.lng}], ${zoom});
@@ -1191,6 +2572,10 @@ function loadMapFromGallery(mapName) {
1191
  attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors'
1192
  }).addTo(map);
1193
 
 
 
 
 
1194
  ${markers.map(marker => `
1195
  var icon = L.icon({
1196
  iconUrl: '${marker.iconUrl}',
@@ -1230,4 +2615,4 @@ function loadMapFromGallery(mapName) {
1230
  }
1231
  </script>
1232
  </body>
1233
- </html>
 
391
  color: var(--hacker-primary);
392
  font-weight: bold;
393
  margin-bottom: 0.5rem;
394
+ cursor: pointer;
395
+ padding: 0.25rem;
396
+ border-radius: 3px;
397
+ }
398
+
399
+ .gallery-map-title:hover {
400
+ background: rgba(0, 100, 200, 0.3);
401
+ }
402
+
403
+ .gallery-map-title.editing {
404
+ background: rgba(0, 100, 200, 0.5);
405
+ outline: 1px solid var(--hacker-primary);
406
  }
407
 
408
  .gallery-map-preview {
 
461
  pointer-events: none;
462
  cursor: not-allowed;
463
  }
464
+
465
+ /* レイヤーエディタ用スタイル */
466
+ #layer-editor {
467
+ display: none;
468
+ position: absolute;
469
+ top: 10px;
470
+ right: 10px;
471
+ background: rgba(0, 20, 40, 0.9);
472
+ padding: 1.5rem;
473
+ border-radius: 0;
474
+ border: 1px solid var(--hacker-border);
475
+ box-shadow: 0 0 20px rgba(0, 200, 255, 0.4);
476
+ z-index: 1000;
477
+ cursor: move;
478
+ font-family: 'Source Code Pro', monospace;
479
+ color: var(--hacker-text);
480
+ width: 350px;
481
+ max-height: 80vh;
482
+ overflow-y: auto;
483
+ }
484
+
485
+ #layer-editor h3 {
486
+ color: var(--hacker-primary);
487
+ text-shadow: 0 0 5px var(--hacker-primary);
488
+ border-bottom: 1px solid var(--hacker-border);
489
+ padding-bottom: 0.5rem;
490
+ margin-bottom: 1rem;
491
+ font-weight: 700;
492
+ }
493
+
494
+ .layer-tabs {
495
+ display: flex;
496
+ margin-bottom: 1rem;
497
+ border-bottom: 1px solid var(--hacker-border);
498
+ }
499
+
500
+ .layer-tab {
501
+ padding: 0.5rem 1rem;
502
+ cursor: pointer;
503
+ border: 1px solid transparent;
504
+ margin-right: 0.5rem;
505
+ }
506
+
507
+ .layer-tab.active {
508
+ border: 1px solid var(--hacker-border);
509
+ border-bottom: 1px solid rgba(0, 20, 40, 0.9);
510
+ margin-bottom: -1px;
511
+ background: rgba(0, 100, 200, 0.3);
512
+ }
513
+
514
+ .layer-tab-content {
515
+ display: none;
516
+ }
517
+
518
+ .layer-tab-content.active {
519
+ display: block;
520
+ }
521
+
522
+ .layer-form-group {
523
+ margin-bottom: 1rem;
524
+ }
525
+
526
+ .layer-form-group label {
527
+ display: block;
528
+ margin-bottom: 0.5rem;
529
+ color: var(--hacker-secondary);
530
+ }
531
+
532
+ .layer-form-group input,
533
+ .layer-form-group textarea,
534
+ .layer-form-group select {
535
+ width: 100%;
536
+ padding: 0.5rem;
537
+ background: rgba(0, 10, 20, 0.8);
538
+ border: 1px solid var(--hacker-border);
539
+ color: var(--hacker-text);
540
+ font-family: 'Source Code Pro', monospace;
541
+ }
542
+
543
+ .layer-form-group input[type="color"] {
544
+ height: 40px;
545
+ padding: 0.2rem;
546
+ }
547
+
548
+ .layer-form-actions {
549
+ margin-top: 1rem;
550
+ display: flex;
551
+ gap: 0.5rem;
552
+ }
553
+
554
+ .layer-form-actions button {
555
+ flex: 1;
556
+ }
557
+
558
+ #plugin-manager {
559
+ display: none;
560
+ position: absolute;
561
+ top: 50%;
562
+ left: 50%;
563
+ transform: translate(-50%, -50%);
564
+ background: rgba(0, 20, 40, 0.95);
565
+ border: 1px solid var(--hacker-border);
566
+ padding: 2rem;
567
+ z-index: 2002;
568
+ width: 80%;
569
+ max-width: 600px;
570
+ }
571
+
572
+ #plugin-manager h3 {
573
+ color: var(--hacker-primary);
574
+ margin-bottom: 1rem;
575
+ text-align: center;
576
+ }
577
+
578
+ #plugin-url {
579
+ width: 100%;
580
+ margin-bottom: 1rem;
581
+ background: rgba(0, 10, 20, 0.8);
582
+ border: 1px solid var(--hacker-border);
583
+ color: var(--hacker-text);
584
+ padding: 0.5rem;
585
+ }
586
+
587
+ #plugin-list {
588
+ max-height: 300px;
589
+ overflow-y: auto;
590
+ margin: 1rem 0;
591
+ padding: 0.5rem;
592
+ background: rgba(0, 10, 20, 0.5);
593
+ border: 1px solid var(--hacker-border);
594
+ }
595
+
596
+ .plugin-item {
597
+ padding: 0.5rem;
598
+ margin-bottom: 0.5rem;
599
+ background: rgba(0, 30, 60, 0.5);
600
+ border-left: 3px solid var(--hacker-primary);
601
+ }
602
+
603
+ .plugin-item-actions {
604
+ display: flex;
605
+ gap: 0.5rem;
606
+ margin-top: 0.5rem;
607
+ }
608
+
609
+ .plugin-item-actions button {
610
+ flex: 1;
611
+ padding: 0.25rem;
612
+ font-size: 0.8rem;
613
+ }
614
+
615
+ /* レイヤーツリー用スタイル */
616
+ #layer-tree {
617
+ position: absolute;
618
+ top: 10px;
619
+ left: 10px;
620
+ background: rgba(0, 20, 40, 0.9);
621
+ padding: 1rem;
622
+ border: 1px solid var(--hacker-border);
623
+ box-shadow: 0 0 20px rgba(0, 200, 255, 0.4);
624
+ z-index: 1000;
625
+ max-height: 80vh;
626
+ overflow-y: auto;
627
+ font-size: 0.9rem;
628
+ }
629
+
630
+ #layer-tree h3 {
631
+ color: var(--hacker-primary);
632
+ margin-bottom: 0.5rem;
633
+ font-size: 1rem;
634
+ }
635
+
636
+ .layer-tree-item {
637
+ padding: 0.25rem 0;
638
+ cursor: pointer;
639
+ margin-left: 1rem;
640
+ }
641
+
642
+ .layer-tree-item:hover {
643
+ color: var(--hacker-primary);
644
+ }
645
+
646
+ .layer-tree-item.selected {
647
+ color: var(--hacker-primary);
648
+ font-weight: bold;
649
+ }
650
+
651
+ .layer-tree-item::before {
652
+ content: "▸";
653
+ margin-right: 0.5rem;
654
+ }
655
+
656
+ .layer-tree-item.expanded::before {
657
+ content: "▾";
658
+ }
659
+
660
+ .layer-tree-item-group {
661
+ margin-left: 1rem;
662
+ display: none;
663
+ }
664
+
665
+ .layer-tree-item-group.expanded {
666
+ display: block;
667
+ }
668
+
669
+ .layer-tree-item-icon {
670
+ width: 16px;
671
+ height: 16px;
672
+ display: inline-block;
673
+ margin-right: 0.5rem;
674
+ vertical-align: middle;
675
+ }
676
  </style>
677
  </head>
678
 
 
709
  <button onclick="if(confirm('現在のマップのすべてのデータが消去されます。いいですか?')){clearCurrentMap()}" class="hacker-btn danger">
710
  <span class="glow-text">現在のマップをリセット</span>
711
  </button>
712
+ <button id="add-layer-btn" class="hacker-btn">
713
+ <span class="glow-text">レイヤーを追加</span>
714
+ </button>
715
+ <button id="manage-plugins-btn" class="hacker-btn">
716
+ <span class="glow-text">プラグイン管理</span>
717
+ </button>
718
+ <button id="toggle-layer-tree-btn" class="hacker-btn secondary">
719
+ <span class="glow-text">レイヤーツリー</span>
720
+ </button>
721
  </div>
722
 
723
  <div class="hacker-container">
 
725
  <div id="map"></div>
726
  </div>
727
 
728
+ <!-- マーカーエディタ -->
729
  <div id="marker-editor">
730
  <h3>MARKER EDITOR</h3>
731
  <div class="terminal-line">緯度:</div>
 
771
  </button>
772
  </div>
773
 
774
+ <!-- レイヤーエディタ -->
775
+ <div id="layer-editor">
776
+ <h3>LAYER EDITOR</h3>
777
+ <div class="layer-tabs">
778
+ <div class="layer-tab active" data-tab="base">ベースレイヤー</div>
779
+ <div class="layer-tab" data-tab="overlay">オーバーレイ</div>
780
+ <div class="layer-tab" data-tab="other">その他</div>
781
+ </div>
782
+
783
+ <!-- ベースレイヤータブ -->
784
+ <div class="layer-tab-content active" id="base-tab">
785
+ <div class="layer-form-group">
786
+ <label for="base-layer-type">レイヤータイプ:</label>
787
+ <select id="base-layer-type">
788
+ <option value="tile">タイルレイヤー (TileLayer)</option>
789
+ <option value="canvas">Canvasレイヤー (L.Canvas)</option>
790
+ <option value="svg">SVGレイヤー (L.SVG)</option>
791
+ <option value="grid">グリッドレイヤー (L.GridLayer)</option>
792
+ </select>
793
+ </div>
794
+
795
+ <div class="layer-form-group" id="tile-url-group">
796
+ <label for="tile-layer-url">タイルURL:</label>
797
+ <input type="text" id="tile-layer-url" value="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png">
798
+ </div>
799
+
800
+ <div class="layer-form-group" id="tile-attribution-group">
801
+ <label for="tile-layer-attribution">クレジット表示:</label>
802
+ <input type="text" id="tile-layer-attribution" value="© OpenStreetMap contributors">
803
+ </div>
804
+
805
+ <div class="layer-form-group" id="tile-options-group">
806
+ <label for="tile-layer-options">オプション (JSON):</label>
807
+ <textarea id="tile-layer-options" placeholder='{"minZoom": 0, "maxZoom": 19, "subdomains": "abc"}'></textarea>
808
+ </div>
809
+
810
+ <div class="layer-form-actions">
811
+ <button id="add-base-layer" class="hacker-btn">
812
+ <span class="glow-text">レイヤーを追加</span>
813
+ </button>
814
+ <button id="cancel-layer" class="hacker-btn danger">
815
+ <span class="glow-text">キャンセル</span>
816
+ </button>
817
+ </div>
818
+ </div>
819
+
820
+ <!-- オーバーレイタブ -->
821
+ <div class="layer-tab-content" id="overlay-tab">
822
+ <div class="layer-form-group">
823
+ <label for="overlay-layer-type">レイ��ータイプ:</label>
824
+ <select id="overlay-layer-type">
825
+ <option value="marker">マーカー (Marker)</option>
826
+ <option value="polyline">ポリライン (Polyline)</option>
827
+ <option value="polygon">ポリゴン (Polygon)</option>
828
+ <option value="circle">サークル (Circle)</option>
829
+ <option value="circlemarker">サークルマーカー (CircleMarker)</option>
830
+ <option value="geojson">GeoJSONレイヤー (GeoJSON)</option>
831
+ <option value="image">画像オーバーレイ (ImageOverlay)</option>
832
+ <option value="video">ビデオオーバーレイ (VideoOverlay)</option>
833
+ </select>
834
+ </div>
835
+
836
+ <div class="layer-form-group" id="overlay-options-group">
837
+ <label for="overlayer-options">オプション (JSON):</label>
838
+ <textarea id="overlayer-options" placeholder='{"color": "#ff0000", "weight": 5}'></textarea>
839
+ </div>
840
+
841
+ <div class="layer-form-group" id="overlay-coords-group">
842
+ <label>座標 (クリックで追加):</label>
843
+ <div id="overlay-coords-list"></div>
844
+ </div>
845
+
846
+ <div class="layer-form-actions">
847
+ <button id="add-overlay-layer" class="hacker-btn">
848
+ <span class="glow-text">レイヤーを追加</span>
849
+ </button>
850
+ <button id="cancel-overlay-layer" class="hacker-btn danger">
851
+ <span class="glow-text">キャンセル</span>
852
+ </button>
853
+ </div>
854
+ </div>
855
+
856
+ <!-- その他タブ -->
857
+ <div class="layer-tab-content" id="other-tab">
858
+ <div class="layer-form-group">
859
+ <label for="other-layer-type">レイヤータイプ:</label>
860
+ <select id="other-layer-type">
861
+ <option value="layergroup">レイヤーグループ (LayerGroup)</option>
862
+ <option value="featuregroup">フィーチャーグループ (FeatureGroup)</option>
863
+ <option value="control">レイヤー切替UI (Control.Layers)</option>
864
+ <option value="heatmap">ヒートマップレイヤー (Heatmap)</option>
865
+ <option value="cluster">クラスターレイヤー (MarkerCluster)</option>
866
+ <option value="vectorgrid">ベクターグリッド (VectorGrid)</option>
867
+ <option value="custom">カスタムレイヤー</option>
868
+ </select>
869
+ </div>
870
+
871
+ <div class="layer-form-group" id="other-options-group">
872
+ <label for="other-layer-options">オプション (JSON):</label>
873
+ <textarea id="other-layer-options" placeholder='{"radius": 25, "maxZoom": 18}'></textarea>
874
+ </div>
875
+
876
+ <div class="layer-form-group" id="other-custom-code-group">
877
+ <label for="other-layer-custom-code">カスタムコード:</label>
878
+ <textarea id="other-layer-custom-code" placeholder="function(layer) { /* カスタム処理 */ }"></textarea>
879
+ </div>
880
+
881
+ <div class="layer-form-actions">
882
+ <button id="add-other-layer" class="hacker-btn">
883
+ <span class="glow-text">レイヤーを追加</span>
884
+ </button>
885
+ <button id="cancel-other-layer" class="hacker-btn danger">
886
+ <span class="glow-text">キャンセル</span>
887
+ </button>
888
+ </div>
889
+ </div>
890
+ </div>
891
+
892
  <div class="hacker-container">
893
  <button id="generate-html" class="hacker-btn">
894
  <span class="glow-text">HTMLを生成</span>
 
927
  </div>
928
  </div>
929
 
930
+ <!-- プラグインマネージャー -->
931
+ <div id="plugin-manager">
932
+ <h3>プラグイン管理</h3>
933
+ <div class="layer-form-group">
934
+ <label for="plugin-url">プラグインURL:</label>
935
+ <input type="text" id="plugin-url" placeholder="https://unpkg.com/leaflet.markercluster@1.4.1/dist/leaflet.markercluster.js">
936
+ </div>
937
+ <div class="layer-form-actions">
938
+ <button id="add-plugin" class="hacker-btn">
939
+ <span class="glow-text">プラグインを追加</span>
940
+ </button>
941
+ <button id="close-plugin-manager" class="hacker-btn danger">
942
+ <span class="glow-text">閉じる</span>
943
+ </button>
944
+ </div>
945
+ <div id="plugin-list"></div>
946
+ </div>
947
+
948
+ <!-- レイヤーツリー -->
949
+ <div id="layer-tree" style="display: none;">
950
+ <h3>レイヤーツリー</h3>
951
+ <div id="layer-tree-content"></div>
952
+ </div>
953
+
954
  <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>
955
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/mono-blue.min.css">
956
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js"></script>
 
962
  let nextMarkerEdit = false;
963
  let markers = [];
964
  let currentMapName = '';
965
+ let layers = [];
966
+ let plugins = [];
967
+ let layerControls = {};
968
+ let currentEditingLayer = null;
969
+ let overlayCoords = [];
970
 
971
  // 初期化処理
972
  window.onload = function() {
 
976
  loading.style.display = 'none';
977
  initMap();
978
  updateEditNextMarkerButton();
979
+ loadPluginsFromStorage();
980
  }, 1000);
981
 
982
  // イベントリスナーの設定
983
  setupEventListeners();
984
  };
 
 
 
 
 
 
985
 
986
+ // マップ初期化
987
+ function initMap() {
988
+ map = L.map("map").setView([33.321797711641395, 130.52061378343208], 16);
989
+ L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
990
+ attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors'
991
+ }).addTo(map);
992
 
993
+ // マーカーイベントの設定
994
+ map.on("click", function(e) {
995
+ if (nextMarkerEdit) {
996
+ return;
997
+ }
998
+
999
+ if (currentEditingLayer) {
1000
+ handleLayerEditingClick(e);
1001
+ return;
1002
+ }
1003
+
1004
+ if (editingMarker) {
1005
+ const latlng = e.latlng;
1006
+ editingMarker.setLatLng([latlng.lat, latlng.lng]);
1007
+ document.getElementById("marker-lat").value = latlng.lat;
1008
+ document.getElementById("marker-lng").value = latlng.lng;
1009
+ updatePreviewSize();
1010
+ saveCurrentMapToStorage();
1011
+ } else {
1012
+ const latlng = e.latlng;
1013
+ const marker = L.marker(latlng).addTo(map);
1014
+ marker.bindPopup("新しいマーカーのポップアップ");
1015
+ marker.bindTooltip("新しいマーカーのツールチップ");
1016
+
1017
+ marker.on("mouseover", function() {
1018
+ hoveredMarker = marker;
1019
+ });
1020
+
1021
+ marker.on("mouseout", function() {
1022
+ if (hoveredMarker === marker) {
1023
+ hoveredMarker = null;
1024
+ }
1025
+ });
1026
+
1027
+ document.getElementById("marker-icon-url").value = "https://unpkg.com/leaflet@1.9.3/dist/images/marker-icon-2x.png";
1028
+ openEditor(marker);
1029
+ saveCurrentMapToStorage();
1030
+ }
1031
  });
1032
+
1033
+ // マーカーが変更されたらボタンの状態を更新
1034
+ map.on('layeradd layerremove', function() {
1035
+ updateEditNextMarkerButton();
1036
+ updateLayerTree();
1037
+ });
1038
+
1039
+ // ポップアップが開いた時の処理をここに移動
1040
+ map.on('popupopen', function(e) {
1041
+ const marker = e.popup._source;
1042
+ if (nextMarkerEdit) {
1043
+ openEditor(marker);
1044
+ nextMarkerEdit = false;
1045
+ document.getElementById("edit-next-marker").textContent = "次のマーカーを編集";
1046
+ document.getElementById("edit-next-marker").classList.remove("danger");
1047
+ document.getElementById("edit-next-marker").classList.add("secondary");
1048
  }
1049
  });
 
 
 
 
1050
  }
 
1051
 
1052
+ // レイヤー編集時のクリック処理
1053
+ function handleLayerEditingClick(e) {
1054
+ const latlng = e.latlng;
1055
+ overlayCoords.push([latlng.lat, latlng.lng]);
1056
+ updateOverlayCoordsList();
1057
+
1058
+ // 現在編集中のレイヤーを更新
1059
+ if (currentEditingLayer) {
1060
+ const layerType = document.getElementById("overlay-layer-type").value;
1061
+
1062
+ if (layerType === 'polyline' || layerType === 'polygon') {
1063
+ if (currentEditingLayer.setLatLngs) {
1064
+ currentEditingLayer.setLatLngs(overlayCoords);
1065
+ }
1066
+ } else if (layerType === 'circle' || layerType === 'circlemarker') {
1067
+ if (currentEditingLayer.setLatLng) {
1068
+ currentEditingLayer.setLatLng(latlng);
1069
+ }
1070
+ }
1071
+ }
1072
+ }
1073
 
1074
+ // オーバーレイ座標リストを更新
1075
+ function updateOverlayCoordsList() {
1076
+ const coordsList = document.getElementById("overlay-coords-list");
1077
+ coordsList.innerHTML = '';
1078
+
1079
+ overlayCoords.forEach((coord, index) => {
1080
+ const coordItem = document.createElement('div');
1081
+ coordItem.className = 'terminal-line';
1082
+ coordItem.textContent = `${index + 1}. ${coord[0].toFixed(6)}, ${coord[1].toFixed(6)}`;
1083
+ coordsList.appendChild(coordItem);
1084
+ });
1085
  }
 
 
1086
 
1087
  // イベントリスナーの設定
1088
  function setupEventListeners() {
 
1119
  // HTML生成関連
1120
  document.getElementById("generate-html").addEventListener("click", generateMapHTML);
1121
  document.getElementById("copyButton").onclick = copyHTMLToClipboard;
1122
+
1123
+ // レイヤー関連
1124
+ document.getElementById("add-layer-btn").addEventListener("click", showLayerEditor);
1125
+ document.getElementById("cancel-layer").addEventListener("click", hideLayerEditor);
1126
+ document.getElementById("add-base-layer").addEventListener("click", addBaseLayer);
1127
+ document.getElementById("add-overlay-layer").addEventListener("click", addOverlayLayer);
1128
+ document.getElementById("cancel-overlay-layer").addEventListener("click", cancelOverlayLayer);
1129
+ document.getElementById("add-other-layer").addEventListener("click", addOtherLayer);
1130
+ document.getElementById("cancel-other-layer").addEventListener("click", cancelOtherLayer);
1131
+ document.getElementById("toggle-layer-tree-btn").addEventListener("click", toggleLayerTree);
1132
+
1133
+ // タブ切り替え
1134
+ document.querySelectorAll('.layer-tab').forEach(tab => {
1135
+ tab.addEventListener('click', function() {
1136
+ const tabId = this.dataset.tab;
1137
+ switchLayerTab(tabId);
1138
+ });
1139
+ });
1140
+
1141
+ // レイヤータイプ変更
1142
+ document.getElementById("base-layer-type").addEventListener("change", updateBaseLayerForm);
1143
+ document.getElementById("overlay-layer-type").addEventListener("change", updateOverlayLayerForm);
1144
+ document.getElementById("other-layer-type").addEventListener("change", updateOtherLayerForm);
1145
+
1146
+ // プラグイン管理
1147
+ document.getElementById("manage-plugins-btn").addEventListener("click", showPluginManager);
1148
+ document.getElementById("close-plugin-manager").addEventListener("click", hidePluginManager);
1149
+ document.getElementById("add-plugin").addEventListener("click", addPlugin);
1150
+ }
1151
+
1152
+ // レイヤーエディタを表示
1153
+ function showLayerEditor() {
1154
+ document.getElementById("layer-editor").style.display = "block";
1155
+ switchLayerTab('base');
1156
+ updateBaseLayerForm();
1157
+ updateOverlayLayerForm();
1158
+ updateOtherLayerForm();
1159
+ }
1160
+
1161
+ // レイヤーエディタを非表示
1162
+ function hideLayerEditor() {
1163
+ document.getElementById("layer-editor").style.display = "none";
1164
+ if (currentEditingLayer) {
1165
+ map.removeLayer(currentEditingLayer);
1166
+ currentEditingLayer = null;
1167
+ overlayCoords = [];
1168
+ }
1169
+ }
1170
+
1171
+ // レイヤータブを切り替え
1172
+ function switchLayerTab(tabId) {
1173
+ // タブを非アクティブ化
1174
+ document.querySelectorAll('.layer-tab').forEach(tab => {
1175
+ tab.classList.remove('active');
1176
+ });
1177
+
1178
+ // タブコンテンツを非表示
1179
+ document.querySelectorAll('.layer-tab-content').forEach(content => {
1180
+ content.classList.remove('active');
1181
+ });
1182
+
1183
+ // 選択されたタブをアクティブ化
1184
+ document.querySelector(`.layer-tab[data-tab="${tabId}"]`).classList.add('active');
1185
+ document.getElementById(`${tabId}-tab`).classList.add('active');
1186
+ }
1187
+
1188
+ // ベースレイヤーフォームを更新
1189
+ function updateBaseLayerForm() {
1190
+ const layerType = document.getElementById("base-layer-type").value;
1191
+
1192
+ // すべてのグループを非表示
1193
+ document.getElementById("tile-url-group").style.display = 'none';
1194
+ document.getElementById("tile-attribution-group").style.display = 'none';
1195
+ document.getElementById("tile-options-group").style.display = 'none';
1196
+
1197
+ // 選択されたタイプに応じて表示
1198
+ if (layerType === 'tile') {
1199
+ document.getElementById("tile-url-group").style.display = 'block';
1200
+ document.getElementById("tile-attribution-group").style.display = 'block';
1201
+ document.getElementById("tile-options-group").style.display = 'block';
1202
+ }
1203
+ }
1204
+
1205
+ // オーバーレイレイヤーフォームを更新
1206
+ function updateOverlayLayerForm() {
1207
+ const layerType = document.getElementById("overlay-layer-type").value;
1208
+
1209
+ // 現在編集中のレイヤーをクリア
1210
+ if (currentEditingLayer) {
1211
+ map.removeLayer(currentEditingLayer);
1212
+ currentEditingLayer = null;
1213
+ }
1214
+
1215
+ overlayCoords = [];
1216
+ updateOverlayCoordsList();
1217
+
1218
+ // 新しいレイヤーを作成
1219
+ if (layerType === 'polyline') {
1220
+ currentEditingLayer = L.polyline([], JSON.parse(document.getElementById("overlayer-options").value || '{}')).addTo(map);
1221
+ } else if (layerType === 'polygon') {
1222
+ currentEditingLayer = L.polygon([], JSON.parse(document.getElementById("overlayer-options").value || '{}')).addTo(map);
1223
+ } else if (layerType === 'circle') {
1224
+ currentEditingLayer = L.circle([0, 0], JSON.parse(document.getElementById("overlayer-options").value || '{}')).addTo(map);
1225
+ } else if (layerType === 'circlemarker') {
1226
+ currentEditingLayer = L.circleMarker([0, 0], JSON.parse(document.getElementById("overlayer-options").value || '{}')).addTo(map);
1227
+ } else if (layerType === 'marker') {
1228
+ currentEditingLayer = L.marker([0, 0], JSON.parse(document.getElementById("overlayer-options").value || '{}')).addTo(map);
1229
+ }
1230
+ }
1231
+
1232
+ // その他レイヤーフォームを更新
1233
+ function updateOtherLayerForm() {
1234
+ const layerType = document.getElementById("other-layer-type").value;
1235
+
1236
+ document.getElementById("other-custom-code-group").style.display = 'none';
1237
+
1238
+ if (layerType === 'custom') {
1239
+ document.getElementById("other-custom-code-group").style.display = 'block';
1240
+ }
1241
+ }
1242
+
1243
+ // ベースレイヤーを追加
1244
+ function addBaseLayer() {
1245
+ const layerType = document.getElementById("base-layer-type").value;
1246
+ const layerName = prompt("レイヤー名を入力してください", "新しいレイヤー");
1247
+
1248
+ if (!layerName) return;
1249
+
1250
+ let layer;
1251
+ let options = {};
1252
+
1253
+ try {
1254
+ const optionsText = document.getElementById("tile-layer-options").value;
1255
+ if (optionsText) {
1256
+ options = JSON.parse(optionsText);
1257
+ }
1258
+ } catch (e) {
1259
+ alert("オプションのJSONが不正です");
1260
+ return;
1261
+ }
1262
+
1263
+ if (layerType === 'tile') {
1264
+ const url = document.getElementById("tile-layer-url").value;
1265
+ const attribution = document.getElementById("tile-layer-attribution").value;
1266
+
1267
+ if (!url) {
1268
+ alert("タイルURLを入力してください");
1269
+ return;
1270
+ }
1271
+
1272
+ layer = L.tileLayer(url, {
1273
+ attribution: attribution,
1274
+ ...options
1275
+ }).addTo(map);
1276
+ } else if (layerType === 'canvas') {
1277
+ layer = L.canvas(options).addTo(map);
1278
+ } else if (layerType === 'svg') {
1279
+ layer = L.svg(options).addTo(map);
1280
+ } else if (layerType === 'grid') {
1281
+ layer = L.gridLayer(options).addTo(map);
1282
+ }
1283
+
1284
+ if (layer) {
1285
+ layers.push({
1286
+ id: 'layer-' + Date.now(),
1287
+ name: layerName,
1288
+ type: layerType,
1289
+ layer: layer,
1290
+ options: options
1291
+ });
1292
+
1293
+ saveCurrentMapToStorage();
1294
+ updateLayerTree();
1295
+ hideLayerEditor();
1296
+ }
1297
+ }
1298
+
1299
+ // オーバーレイレイヤーを追加
1300
+ function addOverlayLayer() {
1301
+ const layerType = document.getElementById("overlay-layer-type").value;
1302
+ const layerName = prompt("レイヤー名を入力してください", "新しいオーバーレイ");
1303
+
1304
+ if (!layerName) return;
1305
+
1306
+ let layer;
1307
+ let options = {};
1308
+
1309
+ try {
1310
+ const optionsText = document.getElementById("overlayer-options").value;
1311
+ if (optionsText) {
1312
+ options = JSON.parse(optionsText);
1313
+ }
1314
+ } catch (e) {
1315
+ alert("オプションのJSONが不正です");
1316
+ return;
1317
+ }
1318
+
1319
+ if (layerType === 'polyline') {
1320
+ if (overlayCoords.length < 2) {
1321
+ alert("ポリラインには少なくとも2点の座標が必要です");
1322
+ return;
1323
+ }
1324
+ layer = L.polyline(overlayCoords, options).addTo(map);
1325
+ } else if (layerType === 'polygon') {
1326
+ if (overlayCoords.length < 3) {
1327
+ alert("ポリゴンには少なくとも3点の座標が必要です");
1328
+ return;
1329
+ }
1330
+ layer = L.polygon(overlayCoords, options).addTo(map);
1331
+ } else if (layerType === 'circle') {
1332
+ if (overlayCoords.length === 0) {
1333
+ alert("サークルには中心点が必要です");
1334
+ return;
1335
+ }
1336
+ layer = L.circle(overlayCoords[0], options).addTo(map);
1337
+ } else if (layerType === 'circlemarker') {
1338
+ if (overlayCoords.length === 0) {
1339
+ alert("サークルマーカーには中心点が必要です");
1340
+ return;
1341
+ }
1342
+ layer = L.circleMarker(overlayCoords[0], options).addTo(map);
1343
+ } else if (layerType === 'marker') {
1344
+ if (overlayCoords.length === 0) {
1345
+ alert("マーカーには位置が必要です");
1346
+ return;
1347
+ }
1348
+ layer = L.marker(overlayCoords[0], options).addTo(map);
1349
+ }
1350
+
1351
+ if (layer) {
1352
+ layers.push({
1353
+ id: 'layer-' + Date.now(),
1354
+ name: layerName,
1355
+ type: layerType,
1356
+ layer: layer,
1357
+ options: options,
1358
+ coords: [...overlayCoords]
1359
+ });
1360
+
1361
+ saveCurrentMapToStorage();
1362
+ updateLayerTree();
1363
+ hideLayerEditor();
1364
+ }
1365
+ }
1366
+
1367
+ // オーバーレイレイヤー追加をキャンセル
1368
+ function cancelOverlayLayer() {
1369
+ if (currentEditingLayer) {
1370
+ map.removeLayer(currentEditingLayer);
1371
+ currentEditingLayer = null;
1372
+ }
1373
+ overlayCoords = [];
1374
+ hideLayerEditor();
1375
+ }
1376
+
1377
+ // その他レイヤーを追加
1378
+ function addOtherLayer() {
1379
+ const layerType = document.getElementById("other-layer-type").value;
1380
+ const layerName = prompt("レイヤー名を入力してください", "新しいレイヤー");
1381
+
1382
+ if (!layerName) return;
1383
+
1384
+ let layer;
1385
+ let options = {};
1386
+
1387
+ try {
1388
+ const optionsText = document.getElementById("other-layer-options").value;
1389
+ if (optionsText) {
1390
+ options = JSON.parse(optionsText);
1391
+ }
1392
+ } catch (e) {
1393
+ alert("オプションのJSONが不正です");
1394
+ return;
1395
+ }
1396
+
1397
+ if (layerType === 'layergroup') {
1398
+ layer = L.layerGroup().addTo(map);
1399
+ } else if (layerType === 'featuregroup') {
1400
+ layer = L.featureGroup().addTo(map);
1401
+ } else if (layerType === 'control') {
1402
+ // ベースレイヤーとオーバーレイレイヤーを収集
1403
+ const baseLayers = {};
1404
+ const overlays = {};
1405
+
1406
+ layers.forEach(l => {
1407
+ if (l.type === 'tile' || l.type === 'canvas' || l.type === 'svg' || l.type === 'grid') {
1408
+ baseLayers[l.name] = l.layer;
1409
+ } else {
1410
+ overlays[l.name] = l.layer;
1411
+ }
1412
+ });
1413
+
1414
+ layer = L.control.layers(baseLayers, overlays, options).addTo(map);
1415
+ layerControls[layer._leaflet_id] = layer;
1416
+ } else if (layerType === 'heatmap') {
1417
+ // ヒートマッププラグインが読み込まれているか確認
1418
+ if (typeof L.HeatLayer === 'undefined') {
1419
+ alert("ヒートマッププラグインが読み込まれていません");
1420
+ return;
1421
+ }
1422
+ layer = L.heatLayer([], options).addTo(map);
1423
+ } else if (layerType === 'cluster') {
1424
+ // クラスタープラグインが読み込まれているか確認
1425
+ if (typeof L.markerClusterGroup === 'undefined') {
1426
+ alert("クラスタープラグインが読み込まれていません");
1427
+ return;
1428
+ }
1429
+ layer = L.markerClusterGroup(options).addTo(map);
1430
+ } else if (layerType === 'custom') {
1431
+ const customCode = document.getElementById("other-layer-custom-code").value;
1432
+ try {
1433
+ // カスタムコードを実行
1434
+ const customFunc = new Function('layer', 'map', customCode);
1435
+ layer = customFunc(L, map);
1436
+ if (!layer) {
1437
+ alert("カスタムコードはレイヤーオブジェクトを返す必要があります");
1438
+ return;
1439
+ }
1440
+ layer.addTo(map);
1441
+ } catch (e) {
1442
+ alert("カスタムコードの実行中にエラーが発生しました: " + e.message);
1443
+ return;
1444
+ }
1445
+ }
1446
+
1447
+ if (layer) {
1448
+ layers.push({
1449
+ id: 'layer-' + Date.now(),
1450
+ name: layerName,
1451
+ type: layerType,
1452
+ layer: layer,
1453
+ options: options
1454
+ });
1455
+
1456
+ saveCurrentMapToStorage();
1457
+ updateLayerTree();
1458
+ hideLayerEditor();
1459
+ }
1460
+ }
1461
+
1462
+ // その他レイヤー追加をキャンセル
1463
+ function cancelOtherLayer() {
1464
+ hideLayerEditor();
1465
+ }
1466
+
1467
+ // レイヤーツリーを表示/非表示
1468
+ function toggleLayerTree() {
1469
+ const layerTree = document.getElementById("layer-tree");
1470
+ if (layerTree.style.display === 'none') {
1471
+ layerTree.style.display = 'block';
1472
+ updateLayerTree();
1473
+ } else {
1474
+ layerTree.style.display = 'none';
1475
+ }
1476
+ }
1477
+
1478
+ // レイヤーツリーを更新
1479
+ function updateLayerTree() {
1480
+ const layerTreeContent = document.getElementById("layer-tree-content");
1481
+ layerTreeContent.innerHTML = '';
1482
+
1483
+ // ベースレイヤー
1484
+ const baseLayers = layers.filter(l =>
1485
+ l.type === 'tile' || l.type === 'canvas' || l.type === 'svg' || l.type === 'grid'
1486
+ );
1487
+
1488
+ if (baseLayers.length > 0) {
1489
+ const baseHeader = document.createElement('div');
1490
+ baseHeader.className = 'layer-tree-item';
1491
+ baseHeader.textContent = 'ベースレイヤー';
1492
+ baseHeader.addEventListener('click', function() {
1493
+ this.classList.toggle('expanded');
1494
+ document.getElementById('base-layers-group').classList.toggle('expanded');
1495
+ });
1496
+ layerTreeContent.appendChild(baseHeader);
1497
+
1498
+ const baseGroup = document.createElement('div');
1499
+ baseGroup.id = 'base-layers-group';
1500
+ baseGroup.className = 'layer-tree-item-group';
1501
+
1502
+ baseLayers.forEach(layer => {
1503
+ const layerItem = document.createElement('div');
1504
+ layerItem.className = 'layer-tree-item';
1505
+ layerItem.textContent = layer.name;
1506
+ layerItem.addEventListener('click', function(e) {
1507
+ e.stopPropagation();
1508
+ // レイヤーを選択状態にする
1509
+ document.querySelectorAll('.layer-tree-item').forEach(item => {
1510
+ item.classList.remove('selected');
1511
+ });
1512
+ this.classList.add('selected');
1513
+ // レイヤーを中央に表示
1514
+ if (layer.layer.getBounds) {
1515
+ map.fitBounds(layer.layer.getBounds());
1516
+ } else if (layer.layer.getLatLng) {
1517
+ map.setView(layer.layer.getLatLng(), map.getZoom());
1518
+ }
1519
+ });
1520
+ baseGroup.appendChild(layerItem);
1521
+ });
1522
+
1523
+ layerTreeContent.appendChild(baseGroup);
1524
+ }
1525
+
1526
+ // オーバーレイレイヤー
1527
+ const overlayLayers = layers.filter(l =>
1528
+ l.type === 'marker' || l.type === 'polyline' || l.type === 'polygon' ||
1529
+ l.type === 'circle' || l.type === 'circlemarker' || l.type === 'geojson' ||
1530
+ l.type === 'image' || l.type === 'video'
1531
+ );
1532
+
1533
+ if (overlayLayers.length > 0) {
1534
+ const overlayHeader = document.createElement('div');
1535
+ overlayHeader.className = 'layer-tree-item';
1536
+ overlayHeader.textContent = 'オーバーレイレイヤー';
1537
+ overlayHeader.addEventListener('click', function() {
1538
+ this.classList.toggle('expanded');
1539
+ document.getElementById('overlay-layers-group').classList.toggle('expanded');
1540
+ });
1541
+ layerTreeContent.appendChild(overlayHeader);
1542
+
1543
+ const overlayGroup = document.createElement('div');
1544
+ overlayGroup.id = 'overlay-layers-group';
1545
+ overlayGroup.className = 'layer-tree-item-group';
1546
+
1547
+ overlayLayers.forEach(layer => {
1548
+ const layerItem = document.createElement('div');
1549
+ layerItem.className = 'layer-tree-item';
1550
+ layerItem.textContent = layer.name;
1551
+ layerItem.addEventListener('click', function(e) {
1552
+ e.stopPropagation();
1553
+ // レイヤーを選択状態にする
1554
+ document.querySelectorAll('.layer-tree-item').forEach(item => {
1555
+ item.classList.remove('selected');
1556
+ });
1557
+ this.classList.add('selected');
1558
+ // レイヤーを中央に表示
1559
+ if (layer.layer.getBounds) {
1560
+ map.fitBounds(layer.layer.getBounds());
1561
+ } else if (layer.layer.getLatLng) {
1562
+ map.setView(layer.layer.getLatLng(), map.getZoom());
1563
+ }
1564
+ });
1565
+ overlayGroup.appendChild(layerItem);
1566
+ });
1567
+
1568
+ layerTreeContent.appendChild(overlayGroup);
1569
+ }
1570
+
1571
+ // その他レイヤー
1572
+ const otherLayers = layers.filter(l =>
1573
+ l.type === 'layergroup' || l.type === 'featuregroup' || l.type === 'control' ||
1574
+ l.type === 'heatmap' || l.type === 'cluster' || l.type === 'vectorgrid' ||
1575
+ l.type === 'custom'
1576
+ );
1577
+
1578
+ if (otherLayers.length > 0) {
1579
+ const otherHeader = document.createElement('div');
1580
+ otherHeader.className = 'layer-tree-item';
1581
+ otherHeader.textContent = 'その他レイヤー';
1582
+ otherHeader.addEventListener('click', function() {
1583
+ this.classList.toggle('expanded');
1584
+ document.getElementById('other-layers-group').classList.toggle('expanded');
1585
+ });
1586
+ layerTreeContent.appendChild(otherHeader);
1587
+
1588
+ const otherGroup = document.createElement('div');
1589
+ otherGroup.id = 'other-layers-group';
1590
+ otherGroup.className = 'layer-tree-item-group';
1591
+
1592
+ otherLayers.forEach(layer => {
1593
+ const layerItem = document.createElement('div');
1594
+ layerItem.className = 'layer-tree-item';
1595
+ layerItem.textContent = layer.name;
1596
+ layerItem.addEventListener('click', function(e) {
1597
+ e.stopPropagation();
1598
+ // レイヤーを選択状態にする
1599
+ document.querySelectorAll('.layer-tree-item').forEach(item => {
1600
+ item.classList.remove('selected');
1601
+ });
1602
+ this.classList.add('selected');
1603
+ // レイヤーを中央に表示
1604
+ if (layer.layer.getBounds) {
1605
+ map.fitBounds(layer.layer.getBounds());
1606
+ } else if (layer.layer.getLatLng) {
1607
+ map.setView(layer.layer.getLatLng(), map.getZoom());
1608
+ }
1609
+ });
1610
+ otherGroup.appendChild(layerItem);
1611
+ });
1612
+
1613
+ layerTreeContent.appendChild(otherGroup);
1614
+ }
1615
+ }
1616
+
1617
+ // プラグインマネージャーを表示
1618
+ function showPluginManager() {
1619
+ document.getElementById("plugin-manager").style.display = "block";
1620
+ updatePluginList();
1621
+ }
1622
+
1623
+ // プラグインマネージャーを非表示
1624
+ function hidePluginManager() {
1625
+ document.getElementById("plugin-manager").style.display = "none";
1626
+ }
1627
+
1628
+ // プラグインを追加
1629
+ function addPlugin() {
1630
+ const pluginUrl = document.getElementById("plugin-url").value.trim();
1631
+
1632
+ if (!pluginUrl) {
1633
+ alert("プラグインURLを入力してください");
1634
+ return;
1635
+ }
1636
+
1637
+ // 既に追加されているかチェック
1638
+ if (plugins.some(p => p.url === pluginUrl)) {
1639
+ alert("このプラグインは既に追加されています");
1640
+ return;
1641
+ }
1642
+
1643
+ // プラグインを追加
1644
+ plugins.push({
1645
+ id: 'plugin-' + Date.now(),
1646
+ url: pluginUrl,
1647
+ loaded: false
1648
+ });
1649
+
1650
+ // プラグインを読み込み
1651
+ loadPlugin(pluginUrl);
1652
+
1653
+ // プラグインリストを更新
1654
+ updatePluginList();
1655
+
1656
+ // ストレージに保存
1657
+ savePluginsToStorage();
1658
+
1659
+ document.getElementById("plugin-url").value = "";
1660
+ }
1661
+
1662
+ // プラグインを読み込み
1663
+ function loadPlugin(url) {
1664
+ const script = document.createElement('script');
1665
+ script.src = url;
1666
+ script.onload = function() {
1667
+ // プラグインの読み込み状態を更新
1668
+ const plugin = plugins.find(p => p.url === url);
1669
+ if (plugin) {
1670
+ plugin.loaded = true;
1671
+ updatePluginList();
1672
+ savePluginsToStorage();
1673
+ }
1674
+ };
1675
+ script.onerror = function() {
1676
+ alert("プラグインの読み込みに失敗しました: " + url);
1677
+ };
1678
+ document.head.appendChild(script);
1679
+ }
1680
+
1681
+ // プラグインリストを更新
1682
+ function updatePluginList() {
1683
+ const pluginList = document.getElementById("plugin-list");
1684
+ pluginList.innerHTML = '';
1685
+
1686
+ if (plugins.length === 0) {
1687
+ pluginList.innerHTML = '<div class="terminal-line">プラグインがありません</div>';
1688
+ return;
1689
+ }
1690
+
1691
+ plugins.forEach(plugin => {
1692
+ const pluginItem = document.createElement('div');
1693
+ pluginItem.className = 'plugin-item';
1694
+
1695
+ const pluginStatus = plugin.loaded ? '✅ 読み込み済み' : '⏳ 読み込み中...';
1696
+ pluginItem.innerHTML = `
1697
+ <div class="terminal-line">${plugin.url}</div>
1698
+ <div class="terminal-line">${pluginStatus}</div>
1699
+ <div class="plugin-item-actions">
1700
+ <button class="hacker-btn secondary remove-plugin-btn" data-id="${plugin.id}">削除</button>
1701
+ </div>
1702
+ `;
1703
+
1704
+ pluginList.appendChild(pluginItem);
1705
+ });
1706
+
1707
+ // 削除ボタンのイベントリスナーを追加
1708
+ document.querySelectorAll('.remove-plugin-btn').forEach(btn => {
1709
+ btn.addEventListener('click', function() {
1710
+ const pluginId = this.dataset.id;
1711
+ removePlugin(pluginId);
1712
+ });
1713
+ });
1714
+ }
1715
+
1716
+ // プラグインを削除
1717
+ function removePlugin(pluginId) {
1718
+ if (confirm("このプラグインを削除しますか?ページをリロードするとプラグインの機能は利用できなくなります。")) {
1719
+ plugins = plugins.filter(p => p.id !== pluginId);
1720
+ updatePluginList();
1721
+ savePluginsToStorage();
1722
+ }
1723
+ }
1724
+
1725
+ // プラグインをストレージから読み込み
1726
+ function loadPluginsFromStorage() {
1727
+ const savedPlugins = localStorage.getItem('mapEditorPlugins');
1728
+ if (savedPlugins) {
1729
+ plugins = JSON.parse(savedPlugins);
1730
+ plugins.forEach(plugin => {
1731
+ if (plugin.loaded) {
1732
+ loadPlugin(plugin.url);
1733
+ }
1734
+ });
1735
+ }
1736
+ }
1737
+
1738
+ // プラグインをストレージに保存
1739
+ function savePluginsToStorage() {
1740
+ localStorage.setItem('mapEditorPlugins', JSON.stringify(plugins));
1741
  }
1742
 
1743
  // マーカー編集モードのトグル
 
1991
  const mapData = {
1992
  center: map.getCenter(),
1993
  zoom: map.getZoom(),
1994
+ markers: [],
1995
+ layers: [],
1996
+ plugins: plugins
1997
  };
1998
 
1999
+ // マーカーを収集
2000
  map.eachLayer((layer) => {
2001
  if (layer instanceof L.Marker) {
2002
  const marker = layer;
 
2013
  }
2014
  });
2015
 
2016
+ // レイヤーを収集
2017
+ layers.forEach(layer => {
2018
+ const layerData = {
2019
+ id: layer.id,
2020
+ name: layer.name,
2021
+ type: layer.type,
2022
+ options: layer.options
2023
+ };
2024
+
2025
+ // タイプに応じて追加データを保存
2026
+ if (layer.type === 'polyline' || layer.type === 'polygon' ||
2027
+ layer.type === 'circle' || layer.type === 'circlemarker' ||
2028
+ layer.type === 'marker') {
2029
+ if (layer.layer.getLatLngs) {
2030
+ layerData.coords = layer.layer.getLatLngs();
2031
+ } else if (layer.layer.getLatLng) {
2032
+ layerData.coords = [layer.layer.getLatLng()];
2033
+ }
2034
+ } else if (layer.type === 'tile') {
2035
+ layerData.url = layer.layer._url;
2036
+ layerData.attribution = layer.layer.options.attribution;
2037
+ }
2038
+
2039
+ mapData.layers.push(layerData);
2040
+ });
2041
+
2042
  if (currentMapName) {
2043
  // 既存のマップを更新
2044
  const savedMaps = JSON.parse(localStorage.getItem('savedMaps')) || {};
 
2068
  const mapData = {
2069
  center: map.getCenter(),
2070
  zoom: map.getZoom(),
2071
+ markers: [],
2072
+ layers: [],
2073
+ plugins: plugins
2074
  };
2075
 
2076
+ // マーカーを収集
2077
  map.eachLayer((layer) => {
2078
  if (layer instanceof L.Marker) {
2079
  const marker = layer;
 
2090
  }
2091
  });
2092
 
2093
+ // レイヤーを収集
2094
+ layers.forEach(layer => {
2095
+ const layerData = {
2096
+ id: layer.id,
2097
+ name: layer.name,
2098
+ type: layer.type,
2099
+ options: layer.options
2100
+ };
2101
+
2102
+ // タイプに応じて追加データを保存
2103
+ if (layer.type === 'polyline' || layer.type === 'polygon' ||
2104
+ layer.type === 'circle' || layer.type === 'circlemarker' ||
2105
+ layer.type === 'marker') {
2106
+ if (layer.layer.getLatLngs) {
2107
+ layerData.coords = layer.layer.getLatLngs();
2108
+ } else if (layer.layer.getLatLng) {
2109
+ layerData.coords = [layer.layer.getLatLng()];
2110
+ }
2111
+ } else if (layer.type === 'tile') {
2112
+ layerData.url = layer.layer._url;
2113
+ layerData.attribution = layer.layer.options.attribution;
2114
+ }
2115
+
2116
+ mapData.layers.push(layerData);
2117
+ });
2118
+
2119
  const savedMaps = JSON.parse(localStorage.getItem('savedMaps')) || {};
2120
  savedMaps[mapName] = mapData;
2121
  localStorage.setItem('savedMaps', JSON.stringify(savedMaps));
 
2141
  const mapItem = document.createElement("div");
2142
  mapItem.className = "gallery-map-item";
2143
 
2144
+ // マップ名を編集可能にする
2145
+ const mapNameElement = document.createElement("div");
2146
+ mapNameElement.className = "gallery-map-title";
2147
+ mapNameElement.textContent = name;
2148
+ mapNameElement.dataset.name = name;
2149
+
2150
+ // マップ名の編集イベント
2151
+ mapNameElement.addEventListener('click', function(e) {
2152
+ if (e.target === this) {
2153
+ editMapName(this);
2154
+ }
2155
+ });
2156
+
2157
+ mapItem.appendChild(mapNameElement);
2158
+
2159
+ // プレビュー情報
2160
+ const previewDiv = document.createElement("div");
2161
+ previewDiv.className = "gallery-map-preview";
2162
 
2163
+ let previewText = `マーカー数: ${data.markers.length}\n`;
2164
+ previewText += `レイヤー数: ${data.layers.length}\n`;
2165
+ previewText += `中心座標: ${data.center.lat.toFixed(4)}, ${data.center.lng.toFixed(4)}\n`;
2166
+ previewText += `ズームレベル: ${data.zoom}`;
2167
+
2168
+ previewDiv.textContent = previewText;
2169
+ mapItem.appendChild(previewDiv);
2170
+
2171
+ // アクションボタン
2172
+ const actionsDiv = document.createElement("div");
2173
+ actionsDiv.className = "gallery-map-actions";
2174
+
2175
+ const loadBtn = document.createElement("button");
2176
+ loadBtn.className = "hacker-btn gallery-btn load-map-btn";
2177
+ loadBtn.dataset.name = name;
2178
+ loadBtn.textContent = "読み込み";
2179
+ actionsDiv.appendChild(loadBtn);
2180
+
2181
+ const deleteBtn = document.createElement("button");
2182
+ deleteBtn.className = "hacker-btn gallery-btn danger delete-map-btn";
2183
+ deleteBtn.dataset.name = name;
2184
+ deleteBtn.textContent = "削除";
2185
+ actionsDiv.appendChild(deleteBtn);
2186
+
2187
+ mapItem.appendChild(actionsDiv);
2188
  mapList.appendChild(mapItem);
2189
  }
2190
 
 
2207
  gallery.style.display = "block";
2208
  }
2209
 
2210
+ // マップ名を編集
2211
+ function editMapName(element) {
2212
+ const oldName = element.dataset.name;
2213
+ const savedMaps = JSON.parse(localStorage.getItem('savedMaps')) || {};
2214
+
2215
+ // 編集モードに入る
2216
+ element.contentEditable = true;
2217
+ element.classList.add('editing');
2218
+ element.focus();
2219
+
2220
+ // 選択範囲を最後に移動
2221
+ const range = document.createRange();
2222
+ range.selectNodeContents(element);
2223
+ range.collapse(false);
2224
+ const selection = window.getSelection();
2225
+ selection.removeAllRanges();
2226
+ selection.addRange(range);
2227
+
2228
+ // 編集終了時の処理
2229
+ const handleBlur = function() {
2230
+ element.contentEditable = false;
2231
+ element.classList.remove('editing');
2232
+
2233
+ const newName = element.textContent.trim();
2234
+
2235
+ if (newName && newName !== oldName) {
2236
+ if (savedMaps[newName]) {
2237
+ alert("この名前のマップは既に存在します");
2238
+ element.textContent = oldName;
2239
+ return;
2240
+ }
2241
+
2242
+ // マップ名を変更
2243
+ savedMaps[newName] = savedMaps[oldName];
2244
+ delete savedMaps[oldName];
2245
+ localStorage.setItem('savedMaps', JSON.stringify(savedMaps));
2246
+
2247
+ // 現在のマップ名を更新
2248
+ if (currentMapName === oldName) {
2249
+ currentMapName = newName;
2250
+ }
2251
+
2252
+ alert(`マップ名を「${oldName}」から「${newName}」に変更しました`);
2253
+ } else {
2254
+ element.textContent = oldName;
2255
+ }
2256
+
2257
+ element.removeEventListener('blur', handleBlur);
2258
+ element.removeEventListener('keydown', handleKeyDown);
2259
+ };
2260
+
2261
+ // Enterキーで編集終了
2262
+ const handleKeyDown = function(e) {
2263
+ if (e.key === 'Enter') {
2264
+ e.preventDefault();
2265
+ element.blur();
2266
+ }
2267
+ };
2268
+
2269
+ element.addEventListener('blur', handleBlur);
2270
+ element.addEventListener('keydown', handleKeyDown);
2271
+ }
2272
+
2273
  // マップギャラリーを非表示
2274
  function hideGallery() {
2275
  document.getElementById("gallery-container").style.display = "none";
2276
  }
2277
 
2278
  // ギャラリーからマップを読み込み
2279
+ function loadMapFromGallery(mapName) {
2280
+ const savedMaps = JSON.parse(localStorage.getItem('savedMaps')) || {};
2281
+ const mapData = savedMaps[mapName];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2282
 
2283
+ if (!mapData) {
2284
+ alert("マップデータが見つかりません");
2285
+ return;
2286
+ }
2287
 
2288
+ // 現在のマップをクリア
2289
+ clearCurrentMap();
 
2290
 
2291
+ // 新しいマップを読み込み
2292
+ map.setView(mapData.center, mapData.zoom);
2293
+ currentMapName = mapName; // 現在のマップ名を更新
 
 
 
2294
 
2295
+ // マーカーを追加
2296
+ mapData.markers.forEach((markerData) => {
2297
+ const icon = L.icon({
2298
+ iconUrl: markerData.iconUrl,
2299
+ iconSize: markerData.iconSize,
2300
+ iconAnchor: [markerData.iconSize[0] / 2, markerData.iconSize[1]],
2301
+ popupAnchor: [0, -markerData.iconSize[1]],
2302
+ tooltipAnchor: [markerData.iconSize[0] / 2, -markerData.iconSize[1] / 2],
2303
+ });
2304
+
2305
+ const marker = L.marker([markerData.lat, markerData.lng], { icon: icon }).addTo(map);
2306
+ if (markerData.popupContent) marker.bindPopup(markerData.popupContent);
2307
+ if (markerData.tooltipContent) marker.bindTooltip(markerData.tooltipContent);
2308
+
2309
+ marker.on("mouseover", function() {
2310
+ hoveredMarker = marker;
2311
+ });
2312
+
2313
+ marker.on("mouseout", function() {
2314
+ if (hoveredMarker === marker) {
2315
+ hoveredMarker = null;
2316
+ }
2317
+ });
2318
+ });
2319
+
2320
+ // レイヤーを追加
2321
+ layers = []; // レイヤーリストをリセット
2322
+
2323
+ mapData.layers.forEach(layerData => {
2324
+ let layer;
2325
+
2326
+ switch (layerData.type) {
2327
+ case 'tile':
2328
+ layer = L.tileLayer(layerData.url, {
2329
+ attribution: layerData.attribution,
2330
+ ...layerData.options
2331
+ }).addTo(map);
2332
+ break;
2333
+
2334
+ case 'canvas':
2335
+ layer = L.canvas(layerData.options).addTo(map);
2336
+ break;
2337
+
2338
+ case 'svg':
2339
+ layer = L.svg(layerData.options).addTo(map);
2340
+ break;
2341
+
2342
+ case 'grid':
2343
+ layer = L.gridLayer(layerData.options).addTo(map);
2344
+ break;
2345
+
2346
+ case 'polyline':
2347
+ layer = L.polyline(layerData.coords, layerData.options).addTo(map);
2348
+ break;
2349
+
2350
+ case 'polygon':
2351
+ layer = L.polygon(layerData.coords, layerData.options).addTo(map);
2352
+ break;
2353
+
2354
+ case 'circle':
2355
+ layer = L.circle(layerData.coords[0], layerData.options).addTo(map);
2356
+ break;
2357
+
2358
+ case 'circlemarker':
2359
+ layer = L.circleMarker(layerData.coords[0], layerData.options).addTo(map);
2360
+ break;
2361
+
2362
+ case 'marker':
2363
+ layer = L.marker(layerData.coords[0], layerData.options).addTo(map);
2364
+ break;
2365
+
2366
+ case 'layergroup':
2367
+ layer = L.layerGroup().addTo(map);
2368
+ break;
2369
+
2370
+ case 'featuregroup':
2371
+ layer = L.featureGroup().addTo(map);
2372
+ break;
2373
+
2374
+ case 'control':
2375
+ // ベースレイヤーとオーバーレイレイヤーを収集
2376
+ const baseLayers = {};
2377
+ const overlays = {};
2378
+
2379
+ layers.forEach(l => {
2380
+ if (l.type === 'tile' || l.type === 'canvas' || l.type === 'svg' || l.type === 'grid') {
2381
+ baseLayers[l.name] = l.layer;
2382
+ } else {
2383
+ overlays[l.name] = l.layer;
2384
+ }
2385
+ });
2386
+
2387
+ layer = L.control.layers(baseLayers, overlays, layerData.options).addTo(map);
2388
+ layerControls[layer._leaflet_id] = layer;
2389
+ break;
2390
+ }
2391
+
2392
+ if (layer) {
2393
+ layers.push({
2394
+ id: layerData.id,
2395
+ name: layerData.name,
2396
+ type: layerData.type,
2397
+ layer: layer,
2398
+ options: layerData.options
2399
+ });
2400
+ }
2401
+ });
2402
+
2403
+ // プラグインを読み込み
2404
+ plugins = mapData.plugins || [];
2405
+ savePluginsToStorage();
2406
+
2407
+ // 未読み込みのプラグインを読み込む
2408
+ plugins.forEach(plugin => {
2409
+ if (!plugin.loaded) {
2410
+ loadPlugin(plugin.url);
2411
+ }
2412
+ });
2413
+
2414
+ // 保存フォームにマップ名をセット
2415
+ document.getElementById("save-map-name").value = currentMapName;
2416
+
2417
+ // ユーザーに通知
2418
+ alert(`マップ「${mapName}」を読み込みました`);
2419
+
2420
+ hideGallery();
2421
+ updateEditNextMarkerButton();
2422
+ updateLayerTree();
2423
+ }
2424
 
2425
  // ギャラリーからマップを削除
2426
  function deleteMapFromGallery(mapName) {
 
2438
  // 現在のマップをクリア
2439
  function clearCurrentMap() {
2440
  map.eachLayer(layer => {
2441
+ if (!(layer instanceof L.TileLayer)) {
2442
  map.removeLayer(layer);
2443
  }
2444
  });
2445
+
2446
+ // レイヤーコントロールを削除
2447
+ Object.values(layerControls).forEach(control => {
2448
+ map.removeControl(control);
2449
+ });
2450
+
2451
+ layerControls = {};
2452
+ layers = [];
2453
  currentMapName = '';
2454
  updateEditNextMarkerButton();
2455
+ updateLayerTree();
2456
  }
2457
 
2458
  // HTMLを生成
 
2481
 
2482
  const center = map.getCenter();
2483
  const zoom = map.getZoom();
2484
+
2485
+ // プラグインスクリプトを収集
2486
+ let pluginScripts = '';
2487
+ plugins.forEach(plugin => {
2488
+ pluginScripts += `<script src="${plugin.url}"><\/script>\n`;
2489
+ });
2490
+
2491
+ // レイヤーを収集
2492
+ let layerScripts = '';
2493
+ let layerControls = '';
2494
+ let baseLayers = {};
2495
+ let overlayLayers = {};
2496
+
2497
+ layers.forEach(layer => {
2498
+ let layerScript = '';
2499
+ let layerVarName = `layer_${layer.id.replace(/-/g, '_')}`;
2500
+
2501
+ switch (layer.type) {
2502
+ case 'tile':
2503
+ layerScript = `var ${layerVarName} = L.tileLayer('${layer.layer._url}', ${JSON.stringify(layer.layer.options)});\n`;
2504
+ baseLayers[`"${layer.name}"`] = layerVarName;
2505
+ break;
2506
+
2507
+ case 'polyline':
2508
+ layerScript = `var ${layerVarName} = L.polyline(${JSON.stringify(layer.layer.getLatLngs())}, ${JSON.stringify(layer.layer.options)});\n`;
2509
+ overlayLayers[`"${layer.name}"`] = layerVarName;
2510
+ break;
2511
+
2512
+ case 'polygon':
2513
+ layerScript = `var ${layerVarName} = L.polygon(${JSON.stringify(layer.layer.getLatLngs())}, ${JSON.stringify(layer.layer.options)});\n`;
2514
+ overlayLayers[`"${layer.name}"`] = layerVarName;
2515
+ break;
2516
+
2517
+ case 'circle':
2518
+ layerScript = `var ${layerVarName} = L.circle([${layer.layer.getLatLng().lat}, ${layer.layer.getLatLng().lng}], ${JSON.stringify(layer.layer.options)});\n`;
2519
+ overlayLayers[`"${layer.name}"`] = layerVarName;
2520
+ break;
2521
+
2522
+ case 'circlemarker':
2523
+ layerScript = `var ${layerVarName} = L.circleMarker([${layer.layer.getLatLng().lat}, ${layer.layer.getLatLng().lng}], ${JSON.stringify(layer.layer.options)});\n`;
2524
+ overlayLayers[`"${layer.name}"`] = layerVarName;
2525
+ break;
2526
+
2527
+ case 'marker':
2528
+ layerScript = `var ${layerVarName} = L.marker([${layer.layer.getLatLng().lat}, ${layer.layer.getLatLng().lng}], ${JSON.stringify(layer.layer.options)});\n`;
2529
+ overlayLayers[`"${layer.name}"`] = layerVarName;
2530
+ break;
2531
+
2532
+ case 'layergroup':
2533
+ layerScript = `var ${layerVarName} = L.layerGroup();\n`;
2534
+ overlayLayers[`"${layer.name}"`] = layerVarName;
2535
+ break;
2536
+
2537
+ case 'featuregroup':
2538
+ layerScript = `var ${layerVarName} = L.featureGroup();\n`;
2539
+ overlayLayers[`"${layer.name}"`] = layerVarName;
2540
+ break;
2541
+
2542
+ case 'control':
2543
+ // コントロールは別途処理
2544
+ break;
2545
+ }
2546
+
2547
+ layerScripts += layerScript;
2548
+ });
2549
+
2550
+ // レイヤーコントロールを生成
2551
+ if (Object.keys(baseLayers).length > 0 || Object.keys(overlayLayers).length > 0) {
2552
+ layerControls = `L.control.layers({\n ${Object.keys(baseLayers).join(',\n ')}\n}, {\n ${Object.keys(overlayLayers).join(',\n ')}\n}).addTo(map);\n`;
2553
+ }
2554
+
2555
+ // レイヤーをマップに追加
2556
+ let addLayersScript = '';
2557
+ Object.values(baseLayers).forEach(layerVar => {
2558
+ addLayersScript += `${layerVar}.addTo(map);\n`;
2559
+ });
2560
+
2561
+ Object.values(overlayLayers).forEach(layerVar => {
2562
+ addLayersScript += `${layerVar}.addTo(map);\n`;
2563
+ });
2564
+
2565
  let html = `<div id="map" style="height: 600px; width: 100%;">
2566
  <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" />
2567
+ ${pluginScripts}
2568
  <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"><\/script>
2569
  <script>
2570
  var map = L.map('map').setView([${center.lat}, ${center.lng}], ${zoom});
 
2572
  attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors'
2573
  }).addTo(map);
2574
 
2575
+ ${layerScripts}
2576
+ ${addLayersScript}
2577
+ ${layerControls}
2578
+
2579
  ${markers.map(marker => `
2580
  var icon = L.icon({
2581
  iconUrl: '${marker.iconUrl}',
 
2615
  }
2616
  </script>
2617
  </body>
2618
+ </html>