Map: add cluster feature
This commit is contained in:
parent
6c0df1994b
commit
ed07e95c9e
|
|
@ -98,7 +98,7 @@
|
||||||
|
|
||||||
<script>(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})
|
<script>(g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})
|
||||||
({key: "AIzaSyDg9u03mGrBhyOisp7VGc27CTPI9QXp8sY", v: "weekly"});</script>
|
({key: "AIzaSyDg9u03mGrBhyOisp7VGc27CTPI9QXp8sY", v: "weekly"});</script>
|
||||||
|
<script src="https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js"></script>
|
||||||
|
|
||||||
<!-- New -->
|
<!-- New -->
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
|
||||||
|
|
@ -1248,13 +1248,14 @@ function popup(){
|
||||||
}
|
}
|
||||||
|
|
||||||
var rstSaveInput = function(json) {
|
var rstSaveInput = function(json) {
|
||||||
$('#map-modal-input .close').trigger('click');
|
$('#map-modal-input').modal('hide'); // Bootstrap 정상 닫기 시도
|
||||||
jQuery(".modal").removeClass("show");;
|
$('#map-modal-input').removeClass('show in').css('display', 'none').attr('aria-hidden', 'true'); // 남아 있을 수 있는 클래스 제거
|
||||||
jQuery(".modal-backdrop").remove();
|
$('.modal-backdrop').remove(); // backdrop 제거
|
||||||
|
|
||||||
showPopupMessage(json.msg);
|
showPopupMessage(json.msg);
|
||||||
//drawPoint();
|
//drawPoint();
|
||||||
updatePickupQty();
|
updatePickupQty();
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceAll(str, searchStr, replaceStr) {
|
function replaceAll(str, searchStr, replaceStr) {
|
||||||
|
|
||||||
|
|
@ -1593,7 +1594,17 @@ function popup(){
|
||||||
var marker = [];
|
var marker = [];
|
||||||
var markerwindow = [];
|
var markerwindow = [];
|
||||||
var overlay = [];
|
var overlay = [];
|
||||||
|
|
||||||
|
var clusterer = null;
|
||||||
|
var clusterInfoGlobal = null;
|
||||||
|
var typeOrder = { "#FF0000": 1, "#800080": 2, "#FF80FF": 3, "#7B7A7A": 4 }; // Request > Scheduled > Normal > Finished
|
||||||
|
|
||||||
var rstInqPoint = function(json) {
|
var rstInqPoint = function(json) {
|
||||||
|
if (clusterInfoGlobal) {
|
||||||
|
clusterInfoGlobal.close();
|
||||||
|
clusterInfoGlobal = null;
|
||||||
|
}
|
||||||
|
|
||||||
for(let j=0; j < marker.length; j++) {
|
for(let j=0; j < marker.length; j++) {
|
||||||
marker[j].setMap(null);
|
marker[j].setMap(null);
|
||||||
marker[j] = null;
|
marker[j] = null;
|
||||||
|
|
@ -1602,6 +1613,12 @@ function popup(){
|
||||||
overlay[j] = null;
|
overlay[j] = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (clusterer) {
|
||||||
|
clusterer.setMap(null);
|
||||||
|
clusterer = null;
|
||||||
|
}
|
||||||
|
|
||||||
marker = [];
|
marker = [];
|
||||||
overlay = [];
|
overlay = [];
|
||||||
let pointGeocoder = new google.maps.Geocoder();
|
let pointGeocoder = new google.maps.Geocoder();
|
||||||
|
|
@ -1678,10 +1695,11 @@ function popup(){
|
||||||
position: location,
|
position: location,
|
||||||
map: map,
|
map: map,
|
||||||
icon: icon,
|
icon: icon,
|
||||||
animation: (property.rnote && /\S/.test(property.rnote))? google.maps.Animation.BOUNCE : null // google.maps.Animation.DROP
|
animation: (property.rnote && /\S/.test(property.rnote) && property.color !== "#7B7A7A") ? google.maps.Animation.BOUNCE : null
|
||||||
});
|
});
|
||||||
|
|
||||||
marker[i].customType = property.type; //'flag' 또는 'map-marker'
|
marker[i].customType = property.type; //'flag' 또는 'map-marker'
|
||||||
|
marker[i].property = property;
|
||||||
|
|
||||||
// Add an info window with company information
|
// Add an info window with company information
|
||||||
markerwindow[i] = new google.maps.InfoWindow({
|
markerwindow[i] = new google.maps.InfoWindow({
|
||||||
|
|
@ -1693,7 +1711,10 @@ function popup(){
|
||||||
});
|
});
|
||||||
|
|
||||||
google.maps.event.addListener(markerwindow[i], 'domready', function() {
|
google.maps.event.addListener(markerwindow[i], 'domready', function() {
|
||||||
jQuery(".gm-ui-hover-effect").css("display","none");
|
// InfoWindow의 닫기 버튼만 숨기기
|
||||||
|
const iwOuter = jQuery(".gm-style-iw").last();
|
||||||
|
const closeBtn = iwOuter.parent().find(".gm-ui-hover-effect");
|
||||||
|
closeBtn.css("display","none");
|
||||||
});
|
});
|
||||||
|
|
||||||
marker[i].addListener('mouseout', () => {
|
marker[i].addListener('mouseout', () => {
|
||||||
|
|
@ -1712,6 +1733,190 @@ function popup(){
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 클러스터 생성
|
||||||
|
if (marker.length > 0) {
|
||||||
|
// flag 타입만 필터링
|
||||||
|
const flagMarkers = marker.filter(m => m.customType === 'flag');
|
||||||
|
|
||||||
|
if (flagMarkers.length > 0) {
|
||||||
|
// 사이즈 줄이기
|
||||||
|
const algorithm = new markerClusterer.GridAlgorithm({
|
||||||
|
gridSize: 5,
|
||||||
|
maxDistance: 50,
|
||||||
|
});
|
||||||
|
|
||||||
|
clusterer = new markerClusterer.MarkerClusterer({
|
||||||
|
map,
|
||||||
|
markers: flagMarkers,
|
||||||
|
algorithm,
|
||||||
|
renderer: {
|
||||||
|
render: ({ count, position, markers }) => {
|
||||||
|
// 클러스터 안에 rnote 있는 flag 마커가 한 개라도 포함됐는지 확인
|
||||||
|
const hasRnoteMarker = markers.some(m =>
|
||||||
|
m.property && m.property.rnote && /\S/.test(m.property.rnote)
|
||||||
|
);
|
||||||
|
|
||||||
|
markers.sort((a, b) => {
|
||||||
|
const t1 = a.property.color || "#FF80FF";
|
||||||
|
const t2 = b.property.color || "#FF80FF";
|
||||||
|
return typeOrder[t1] - typeOrder[t2];
|
||||||
|
});
|
||||||
|
|
||||||
|
const topColor = markers[0]?.property?.color || "#4285f4";
|
||||||
|
const size = Math.min(50, 30 + Math.log(count) * 10);
|
||||||
|
|
||||||
|
return new google.maps.Marker({
|
||||||
|
position,
|
||||||
|
icon: {
|
||||||
|
url:
|
||||||
|
"data:image/svg+xml;charset=UTF-8," +
|
||||||
|
encodeURIComponent(`
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40">
|
||||||
|
<circle cx="20" cy="20" r="18"
|
||||||
|
fill="${topColor}" />
|
||||||
|
<text x="20" y="25" text-anchor="middle"
|
||||||
|
font-size="14" fill="#fff">${count}</text>
|
||||||
|
</svg>
|
||||||
|
`),
|
||||||
|
scaledSize: new google.maps.Size(40, 40),
|
||||||
|
anchor: new google.maps.Point(10, 10),
|
||||||
|
},
|
||||||
|
zIndex: Number(google.maps.Marker.MAX_ZINDEX) + count,
|
||||||
|
|
||||||
|
animation: (
|
||||||
|
// 클러스터 안에 rnote가 있으면서 finished가 아닌 마커가 1개라도 있는 경우만 Bounce
|
||||||
|
markers.some(m =>
|
||||||
|
m.property &&
|
||||||
|
m.property.rnote && /\S/.test(m.property.rnote) &&
|
||||||
|
m.property.color !== "#7B7A7A"
|
||||||
|
)
|
||||||
|
) ? google.maps.Animation.BOUNCE : null,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
onClusterClick: (...args) => {
|
||||||
|
let clusterObj = null;
|
||||||
|
|
||||||
|
if (args.length === 1 && args[0]) {
|
||||||
|
const a = args[0];
|
||||||
|
clusterObj = a.cluster || a;
|
||||||
|
} else if (args.length >= 2) {
|
||||||
|
clusterObj = args[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!clusterObj) return false;
|
||||||
|
|
||||||
|
const markersInCluster =
|
||||||
|
clusterObj.markers ||
|
||||||
|
(typeof clusterObj.getMarkers === "function"
|
||||||
|
? clusterObj.getMarkers()
|
||||||
|
: []) ||
|
||||||
|
[];
|
||||||
|
|
||||||
|
const clusterCenter =
|
||||||
|
clusterObj.position ||
|
||||||
|
(typeof clusterObj.getCenter === "function"
|
||||||
|
? clusterObj.getCenter()
|
||||||
|
: null);
|
||||||
|
|
||||||
|
if (!markersInCluster.length || !clusterCenter) return false;
|
||||||
|
|
||||||
|
if (clusterInfoGlobal) {
|
||||||
|
clusterInfoGlobal.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
markersInCluster.sort((a, b) => {
|
||||||
|
const t1 = a.property.color || "#FF80FF";
|
||||||
|
const t2 = b.property.color || "#FF80FF";
|
||||||
|
return typeOrder[t1] - typeOrder[t2];
|
||||||
|
});
|
||||||
|
|
||||||
|
let html =
|
||||||
|
"<div style='min-width:220px;max-height:240px;overflow:auto'>";
|
||||||
|
markersInCluster.forEach((m) => {
|
||||||
|
const p = m.property || {};
|
||||||
|
const hasRnote = p.rnote && /\S/.test(p.rnote);
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<div class="cluster-item" data-marker-index="${p.index}" style="display:flex; align-items:center; margin-bottom:4px; cursor:pointer;">
|
||||||
|
<span style="width:15px; height:15px; margin-right:6px; background-color:${p.color || "#ccc"}; display:inline-block; border:1px solid #999;"></span>
|
||||||
|
<strong>${p.store ?? "—"}</strong>
|
||||||
|
${
|
||||||
|
hasRnote
|
||||||
|
? `<span style="margin-left:6px; padding:2px 4px; background:#FF9800; color:white; border-radius:3px; font-size:10px;">
|
||||||
|
notice
|
||||||
|
</span>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div style="margin-left:22px; font-size:12px; color:#333;">
|
||||||
|
Est. Quantity : ${p.estqty ?? "-"}
|
||||||
|
</div>
|
||||||
|
<hr style="margin:7px 0;">
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
html += "</div>";
|
||||||
|
|
||||||
|
const clusterInfo = new google.maps.InfoWindow({
|
||||||
|
content: html,
|
||||||
|
position: clusterCenter,
|
||||||
|
pixelOffset: new google.maps.Size(0, -25),
|
||||||
|
});
|
||||||
|
|
||||||
|
clusterInfoGlobal = clusterInfo;
|
||||||
|
clusterInfo.open(map);
|
||||||
|
|
||||||
|
google.maps.event.addListenerOnce(
|
||||||
|
clusterInfo,
|
||||||
|
"domready",
|
||||||
|
function () {
|
||||||
|
document.querySelectorAll(".cluster-item").forEach((el) => {
|
||||||
|
el.addEventListener("click", () => {
|
||||||
|
const idx = el.getAttribute("data-marker-index");
|
||||||
|
if (idx !== null && marker[idx]) {
|
||||||
|
google.maps.event.trigger(marker[idx], "click");
|
||||||
|
}
|
||||||
|
clusterInfo.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 클러스터링이 끝났을 때 실행되는 이벤트
|
||||||
|
clusterer.addListener('clusteringend', () => {
|
||||||
|
marker.forEach(m => {
|
||||||
|
if (!m.getMap()) return;
|
||||||
|
if (m.property?.rnote && /\S/.test(m.property.rnote) && m.property.color !== "#7B7A7A") {
|
||||||
|
m.setAnimation(google.maps.Animation.BOUNCE);
|
||||||
|
} else {
|
||||||
|
m.setAnimation(null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showInfoWindow(position, title, quantity, infoType) {
|
||||||
|
const showClose = infoType === 'cluster'; // 클러스터일 때만 X 표시
|
||||||
|
|
||||||
|
const infoContent = `
|
||||||
|
<div class="info-window">
|
||||||
|
${showClose ? '<button class="close" onclick="closeInfoWindow()">×</button>' : ''}
|
||||||
|
<div class="info-body">
|
||||||
|
<strong>${title}</strong><br>
|
||||||
|
Est. Quantity: ${quantity}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
infoWindow.setContent(infoContent);
|
||||||
|
infoWindow.setPosition(position);
|
||||||
|
infoWindow.open(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue