Google Apps Script 및 Leaflet.js를 사용하여 대화형 XY 이미지 플롯 구축
Google Maps has a ton of features for plotting points on a map, but what if you want to plot points on an image? These XY Image Plot maps are commonly used for floor maps, job site inspections, and even games.
In this guide, I'll show you how to create an interactive map with draggable points using Leaflet.js and Google Apps Script. We'll cover everything from setting up the map to integrating data from Google Sheets, and deploying it as a web app.
This guide will cover:
Setting up Leaflet.js in a Google Apps Script HTML Service
Displaying Markers using data from Google Sheets
Updating Sheets row when a Marker is moved
Creating new Markers from the map and saving to Sheets
Deleting a marker from the web app
Setting up Leaflet.js in a Google Apps Script HTML Service
Leaflet.js is one of the most popular open-source mapping libraries. It's light-weight, easy to use, and had great documentation. They support a ton of different map types, including "CRS.Simple", or Coordinate Reference System, which allows you to supply a background image.
Google Sheets Set Up
Start out by creating a sheet named map_pin with the following structure:
id | title | x | y |
---|---|---|---|
1 | test1 | 10 | 30 |
2 | test2 | 50 | 80 |
그런 다음 확장 메뉴에서 Apps Script를 엽니다.
HTML 파일 생성
먼저 라이브러리를 작동시키기 위해 Leaflet 문서의 기본 예제부터 시작하겠습니다. 여기의 빠른 시작 가이드에서 전체 예를 볼 수 있습니다.
Index라는 새 HTML 파일을 추가하고 콘텐츠를 다음으로 설정합니다.
<!DOCTYPE html> <html> <head> <title>Quick Start - Leaflet</title> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" /> <style> #map { height: 400px; } </style> </head> <body> <div id="map"></div> <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script> <script> var map = L.map('map').setView([40.73, -73.99], 13); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, attribution: '© OpenStreetMap' }).addTo(map); var marker = L.marker([40.73, -73.99]).addTo(map) .bindPopup('Test Popup Message') .openPopup(); </script> </body> </html>
그런 다음 Code.gs 파일을 다음으로 업데이트하세요.
function doGet() { const html = HtmlService.createHtmlOutputFromFile('Index') .setTitle('Map with Draggable Points') .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL); return html; }
저장한 다음 배포를 클릭하고 웹 앱으로 게시하세요. 그런 다음 새 배포에 대한 링크를 열면 Leaflet.js가 뉴욕 지도를 표시하는 것을 볼 수 있습니다.
그럼 Leaflet을 사용한 일반 지도 예시입니다. 이제 배경 이미지를 제공할 수 있는 CRS.Simple 지도 유형을 살펴보겠습니다.
리플렛 튜토리얼의 예시로 HTML을 업데이트하세요.
<!DOCTYPE html> <html> <head> <title>CRS Simple Example - Leaflet</title> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" /> <style> #map { height: 400px; width: 600px; } body { margin: 0; padding: 0; } </style> </head> <body> <div id="map"></div> <script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script> <script> // Set up the map with a simple CRS (no geographic projection) var map = L.map('map', { crs: L.CRS.Simple, minZoom: -1, maxZoom: 4 }); // Define the dimensions of the image var bounds = [[0, 0], [1000, 1000]]; var image = L.imageOverlay('https://leafletjs.com/examples/crs-simple/uqm_map_full.png', bounds).addTo(map); // Set the initial view of the map to show the whole image map.fitBounds(bounds); // Optional: Add a marker or other elements to the map var marker = L.marker([500, 500]).addTo(map) .bindPopup('Center of the image') .openPopup(); </script> </body> </html>
여기에서는 1000 x 1000픽셀의 이미지를 제공하고 중앙 마커를 500, 500으로 설정합니다.
저장을 클릭한 다음 배포>배포 테스트를 클릭하여 새 지도 유형을 확인하세요. 이제 배경 이미지가 있는 지도와 중앙에 마커가 표시됩니다.
Google 스프레드시트의 데이터로 지도 초기화
다음으로 시트의 데이터를 사용하여 지도에 일련의 마커를 채웁니다.
먼저 Code.gs 파일에 함수를 추가하여 마커 위치를 가져옵니다.
function getPinData(){ const ss = SpreadsheetApp.getActiveSpreadsheet(); const sh = ss.getSheetByName('map_pin'); const data = sh.getDataRange().getValues(); const json = arrayToJSON(data); //Logger.log(json); return json } function arrayToJSON(data=getPinData()){ const headers = data[0]; const rows = data.slice(1); let jsonData = []; for(row of rows){ const obj = {}; headers.forEach((h,i)=>obj[h] = row[i]); jsonData.push(obj) } //Logger.log(jsonData) return jsonData }
다음 섹션의 HTML에서 더 쉽게 작업할 수 있도록 핀을 JSON으로 반환합니다.
이제 이 JSON을 반복하고 지도가 로드된 후 지도 핀을 생성하는 함수를 HTML에 추가하세요.
// Add map pins from sheet data google.script.run.withSuccessHandler(addMarkers).getPinData(); function addMarkers(mapPinData) { mapPinData.forEach(pin => { const marker = L.marker([pin.x, pin.y], { draggable: true }).addTo(map); marker.bindPopup(`<b>${pin.title}`).openPopup(); marker.on('dragend', function(e) { const latLng = e.target.getLatLng(); console.log(`Marker ${pin.title} moved to: ${latLng.lat}, ${latLng.lng}`); }); }); }
저장한 다음 테스트 배포를 엽니다. 이제 시트 데이터에서 마커가 생성되었습니다!
각 핀에는 해당 행의 제목이 포함된 팝업이 있습니다. 이 시점에서 핀을 드래그할 수 있지만 새 위치를 저장하는 기능이 여전히 필요합니다.
드래그 시 마커 위치 저장
새 위치를 저장하려면 클라이언트 측에서 이벤트를 캡처하는 HTML 함수와 서버 측 Code.gs 파일에 새 위치를 저장하는 함수 두 가지가 필요합니다.
다음을 사용하여 HTML을 업데이트하세요.
function addMarkers(mapPinData) { mapPinData.forEach(pin => { const { id, title, x, y } = pin; const marker = L.marker([x, y], { draggable: true }).addTo(map); marker.bindPopup(`<b>${title}</b>`).openPopup(); marker.on('dragend', function(e) { const latLng = e.target.getLatLng(); console.log(`Marker ${title} moved to: ${latLng.lat}, ${latLng.lng}`); saveMarkerPosition({ id, title, lat: latLng.lat, lng: latLng.lng }); }); }); } function saveMarkerPosition({ id, title, lat, lng }) { google.script.run.saveMarkerPosition({ id, title, lat, lng }); }
그런 다음 Code.gs 파일에 함수를 추가하여 위치를 저장합니다.
function saveMarkerPosition({ id, lat, lng }) { const ss = SpreadsheetApp.getActiveSpreadsheet(); const sh = ss.getSheetByName('map_pin'); const data = sh.getDataRange().getValues(); for (let i = 1; i < data.length; i++) { if (data[i][0] === id) { // ID column (index 0) sh.getRange(i + 1, 3).setValue(lat); // latitude column sh.getRange(i + 1, 4).setValue(lng); // longitude column break; } } }
테스트 배포를 저장하고 새로 고칩니다. 이제 마커를 드래그하면 시트 업데이트가 표시됩니다!
새 포인트 추가
이제 기존 포인트를 이동할 수 있지만 새 포인트를 추가하는 것은 어떨까요? 이번에도 HTML과 Code.gs 파일에 각각 하나씩 두 개의 함수가 필요합니다.
먼저 사용자가 지도의 빈 곳을 클릭하면 프롬프트가 열리는 함수를 HTML에 추가하고 해당 값을 서버 함수에 전달합니다.
// Function to add a new pin map.on('click', function(e) { const latLng = e.latlng; const title = prompt('Enter a title for the new pin:'); if (title) { google.script.run.withSuccessHandler(function(id) { addNewMarker({ id, title, lat: latLng.lat, lng: latLng.lng }); }).addNewPin({ title, lat: latLng.lat, lng: latLng.lng }); } }); function addNewMarker({ id, title, lat, lng }) { const marker = L.marker([lat, lng], { draggable: true }).addTo(map); marker.bindPopup(`<b>${title}</b>`).openPopup(); marker.on('dragend', function(e) { const latLng = e.target.getLatLng(); saveMarkerPosition({ id, title, lat: latLng.lat, lng: latLng.lng }); }); }
그런 다음 Code.gs에 함수를 추가하여 새 행을 저장하세요.
function addNewPin({ title, lat, lng }) { const ss = SpreadsheetApp.getActiveSpreadsheet(); const sh = ss.getSheetByName('map_pin'); // Check if there are any rows present, if not initialize ID const lastRow = sh.getLastRow(); let newId = 1; if (lastRow > 0) { const lastId = sh.getRange(lastRow, 1).getValue(); newId = lastId + 1; } sh.appendRow([newId, title, lat, lng]); return newId; }
한 번 더 저장하고 테스트 배포를 새로 고칩니다. 이제 빈 곳을 클릭하면 제목을 입력하고 새 마커를 저장할 수 있습니다!
마커 삭제
마지막으로 마커를 삭제하는 방법을 추가하여 지도 보기에서 완전한 CRUD 앱을 제공해야 합니다.
팝업에 삭제 버튼을 제공하도록 마커 추가 기능을 업데이트하세요.
const popupContent = `<b>${title}</b><br><button onclick="deleteMarker(${id})">Delete Marker</button>`; marker.bindPopup(popupContent).openPopup();
그런 다음 클라이언트 측에서 삭제하는 기능을 추가합니다.
// Function to delete a marker function deleteMarker(id) { const confirmed = confirm('Are you sure you want to delete this marker?'); if (confirmed) { google.script.run.withSuccessHandler(() => { // Refresh the markers after deletion google.script.run.withSuccessHandler(addMarkers).getPinData(); }).deleteMarker(id); } }
그런 다음 Code.gs 파일에 일치 함수를 추가합니다.
function deleteMarker(id) { const ss = SpreadsheetApp.getActiveSpreadsheet(); const sh = ss.getSheetByName('map_pin'); const data = sh.getDataRange().getValues(); for (let i = 1; i < data.length; i++) { if (data[i][0] === id) { // ID column (index 0) sh.deleteRow(i + 1); // Delete the row break; } } }
다음은 무엇입니까?
각 마커에 다른 데이터 포인트 추가, 동적 배경 이미지, 기타 클릭 및 드래그 상호 작용 등 여기에서 더 많은 작업을 수행할 수 있습니다. 게임을 만들 수도 있어요! 사용 사례에 대한 아이디어가 있나요? 아래에 댓글을 남겨주세요!
위 내용은 Google Apps Script 및 Leaflet.js를 사용하여 대화형 XY 이미지 플롯 구축의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undress AI Tool
무료로 이미지를 벗다

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

Node.js에서 HTTP 요청을 시작하는 세 가지 일반적인 방법이 있습니다. 1. 기본 시나리오에 적합하지만 데이터 스티칭 및 오류 모니터링의 수동 처리 및 https.get () 사용과 같은 데이터 스티치 및 오류 모니터링의 수동 처리가 필요합니다. 2.axios는 약속을 기반으로 한 타사 도서관입니다. 간결한 구문과 강력한 기능을 가지고 있으며 비동기/기다림, 자동 JSON 변환, 인터셉터 등을 지원합니다. 비동기 요청 작업을 단순화하는 것이 좋습니다. 3. 노드 페치는 약속과 간단한 구문을 기반으로 브라우저 페치와 유사한 스타일을 제공합니다.

JavaScript 데이터 유형은 원시 유형 및 기준 유형으로 나뉩니다. 원시 유형에는 문자열, 숫자, 부울, 널, 정의되지 않은 및 기호가 포함됩니다. 값은 불변이없고 값을 할당 할 때 사본이 복사되므로 서로 영향을 미치지 않습니다. 객체, 배열 및 함수와 같은 참조 유형은 메모리 주소를 저장하고 동일한 개체를 가리키는 변수는 서로 영향을 미칩니다. 타입 및 인스턴스는 유형을 결정하는 데 사용될 수 있지만 TypeofNull의 역사적 문제에주의를 기울일 수 있습니다. 이 두 가지 유형의 차이점을 이해하면보다 안정적이고 안정적인 코드를 작성하는 데 도움이 될 수 있습니다.

JavaScript의 필터 () 메소드는 모든 통과 테스트 요소를 포함하는 새로운 배열을 만드는 데 사용됩니다. 1.Filter ()는 원래 배열을 수정하지 않고 조건부 요소를 충족하는 새 배열을 반환합니다. 2. 기본 구문은 array.filter ((element) => {returnCondition;}); 3. 객체 배열은 30 세 이상의 필터링 사용자와 같은 속성 값으로 필터링 할 수 있습니다. 4. 연령 및 이름 길이 조건을 동시에 충족하는 것과 같은 다중 조건 필터링 지원; 5. 동적 조건을 처리하고 필터 매개 변수를 기능으로 전달하여 유연한 필터링을 달성 할 수 있습니다. 6. 그것을 사용할 때는 빈 배열을 반환하지 않기 위해 부울 값을 반환하고 다른 방법을 결합하여 문자열 일치와 같은 복잡한 논리를 달성하십시오.

JavaScript에서 배열에 특정 값이 포함되어 있는지 확인하십시오. 가장 일반적인 방법은 부울 값을 반환하는 ()와 구문이 array.includes (valuetofind)입니다. 이전 환경과 호환 해야하는 경우 숫자. indexof (20)! == -1과 같은 indexof ()를 사용하십시오. 객체 또는 복잡한 데이터의 경우 user.some (user => user.id === 1)과 같은 심층 비교에 일부 () 메소드를 사용해야합니다.

비동기 함수의 오류를 처리하려면 시도/캐치를 사용하고 통화 체인에서 처리하고 .catch () 메소드를 사용한 후 처리되지 않은 반응 이벤트를 듣습니다. 1. 시도/캐치를 사용하여 오류를 잡는 오류는 명확한 구조와 함께 권장되는 방법이며 기다릴 수있는 예외를 처리 할 수 있습니다. 2. 통화 체인의 오류 처리는 중앙 집중식 로직 일 수 있으며, 이는 다단계 프로세스에 적합합니다. 3. Async 함수를 호출 한 후 .catch ()를 사용하여 약속 조합 시나리오에 적합합니다. 4. 처리되지 않은 거부를 마지막 방어선으로 기록하기 위해 처리되지 않은 주사 사건을 듣습니다. 위의 방법은 공동으로 비동기 오류가 올바르게 캡처되고 처리되도록 보장합니다.

JavaScript 시간대 문제를 다루는 핵심은 올바른 방법을 선택하는 것입니다. 1. 기본 날짜 객체를 사용하는 경우 UTC 시간에 저장 및 전송하여 표시 할 때 사용자의 로컬 시간대로 변환하는 것이 좋습니다. 2. 복잡한 시간대 조작의 경우 IANA 시간대 데이터베이스를 지원하고 편리한 형식 및 변환 기능을 제공하는 모멘트 타임 존을 사용할 수 있습니다. 3. 디스플레이 시간을 현지화해야하고 타사 라이브러리를 소개하지 않으려면 intl.dateTimeFormat을 사용할 수 있습니다. 4. 현대적인 경량 솔루션 Day.js 및 Timezone 및 UTC 플러그인에는 간결한 API, 우수한 성능이 있으며 시간대 전환을 지원하는 것이 좋습니다.

Virtual Dom은 실제 DOM 업데이트를 최적화하는 프로그래밍 개념입니다. 메모리에서 실제 DOM에 해당하는 트리 구조를 만들면 실제 DOM의 빈번하고 직접 작동을 피합니다. 핵심 원칙은 다음과 같습니다. 1. 데이터가 변경 될 때 새로운 가상 DOM을 생성합니다. 2. 새 가상 Doms와 오래된 가상 Doms의 가장 작은 차이를 찾으십시오. 3. 재 배열 및 재로 그리기의 오버 헤드를 줄이기 위해 실제 DOM의 배치 업데이트. 또한 고유 한 안정 키를 사용하면 목록 비교 효율성을 향상시킬 수있는 반면, 일부 현대 프레임 워크는 가상 DOM을 대체하기 위해 다른 기술을 채택했습니다.

FunctionProgrammingInjavaScriptEmphasizesscycizes, predictableCodeThroughCoreConcepts.1.PureFtionsConsentILTENTINTINTINTINTERTINTERTERNITHERTERNITHETHESTERINGPUTSTINGPUTWERSYEFFECTS
