Map: add cluster feature
This commit is contained in:
parent
6c0df1994b
commit
ed07e95c9e
|
|
@ -98,8 +98,8 @@
|
|||
|
||||
<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>
|
||||
<script src="https://unpkg.com/@googlemaps/markerclusterer/dist/index.min.js"></script>
|
||||
|
||||
|
||||
<!-- 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/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
||||
|
|
@ -1246,15 +1246,16 @@ function popup(){
|
|||
var rstInqPickupQty = function(json) {
|
||||
jQuery("#pickupquantity").text(json.curPickupQty);
|
||||
}
|
||||
|
||||
|
||||
var rstSaveInput = function(json) {
|
||||
$('#map-modal-input .close').trigger('click');
|
||||
jQuery(".modal").removeClass("show");;
|
||||
jQuery(".modal-backdrop").remove();
|
||||
$('#map-modal-input').modal('hide'); // Bootstrap 정상 닫기 시도
|
||||
$('#map-modal-input').removeClass('show in').css('display', 'none').attr('aria-hidden', 'true'); // 남아 있을 수 있는 클래스 제거
|
||||
$('.modal-backdrop').remove(); // backdrop 제거
|
||||
|
||||
showPopupMessage(json.msg);
|
||||
//drawPoint();
|
||||
updatePickupQty();
|
||||
}
|
||||
}
|
||||
|
||||
function replaceAll(str, searchStr, replaceStr) {
|
||||
|
||||
|
|
@ -1593,7 +1594,17 @@ function popup(){
|
|||
var marker = [];
|
||||
var markerwindow = [];
|
||||
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) {
|
||||
if (clusterInfoGlobal) {
|
||||
clusterInfoGlobal.close();
|
||||
clusterInfoGlobal = null;
|
||||
}
|
||||
|
||||
for(let j=0; j < marker.length; j++) {
|
||||
marker[j].setMap(null);
|
||||
marker[j] = null;
|
||||
|
|
@ -1602,6 +1613,12 @@ function popup(){
|
|||
overlay[j] = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (clusterer) {
|
||||
clusterer.setMap(null);
|
||||
clusterer = null;
|
||||
}
|
||||
|
||||
marker = [];
|
||||
overlay = [];
|
||||
let pointGeocoder = new google.maps.Geocoder();
|
||||
|
|
@ -1678,10 +1695,11 @@ function popup(){
|
|||
position: location,
|
||||
map: map,
|
||||
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].property = property;
|
||||
|
||||
// Add an info window with company information
|
||||
markerwindow[i] = new google.maps.InfoWindow({
|
||||
|
|
@ -1692,10 +1710,13 @@ function popup(){
|
|||
markerwindow[i].open(map, marker[i]);
|
||||
});
|
||||
|
||||
google.maps.event.addListener(markerwindow[i], 'domready', function() {
|
||||
jQuery(".gm-ui-hover-effect").css("display","none");
|
||||
google.maps.event.addListener(markerwindow[i], 'domready', function() {
|
||||
// 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', () => {
|
||||
markerwindow[i].close();
|
||||
});
|
||||
|
|
@ -1711,9 +1732,193 @@ 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);
|
||||
}
|
||||
|
||||
|
||||
function addDragEvent(customOverlay) {
|
||||
let div = customOverlay.div;
|
||||
|
|
|
|||
Loading…
Reference in New Issue