지오서비스웹(GEOSERVICE-WEB)

소 개

GEOSERVICE-WEB은 지오서비스(지리, 지도, 위치 등과 관련된 서비스)를 웹에서 제공합니다. 지오서비스웹의 목표는 웹에서 누구나 쉽게 지도 기반의 비지니스를 효과적으로 해결하기 위한 서비스를 제공하는데 있습니다. 웹브라우저에서 아래의 URL을 입력하여 이용할 수 있습니다.

https://www.geoservice.co.kr

지오서비스웹은 공간(Spatial)과 위치(Location)에 대한 다양한 서비스를 웹에서 이용할 수 있습니다. 예를 들어 지오코딩 등과 같은 기능을 이용해 공간 데이터를 생성하고 시각화할 수 있습니다. 또한 자신이 가진 데이터를 아카이브에 업로드하여 언제 어디서든 사용할 수 있으며 다른 사용자와 기본적으로 공유할 수 있습니다. 원한다면 내가 올린 데이터는 공유하지 않도록 설정할 수도 있습니다.

업데이트

  • [2025/09/02] 지오코딩 결과에 19자리의 PNU 코드가 제공됩니다.
  • [2025/09/02] 헤깔려 틀리게 입력한 도로명주소도 지오코딩이 가능해졌습니다.
  • [2025/08/20] 최신 주소 DB(2025년 7월 구축)로 업데이트 되었습니다.
  • [2025/07/15] 지오코딩 결과를 SHP 뿐만 아니라 CSV 형식으로 바로 저장되도록 하였습니다.
  • [2025/03/14] 2025년 1분기 최신 주소 DB로 업데이트 되었습니다.
  • [2025/03/04] 통계 데이터에 대한 파이차트 맵 분석(시각화) 기능이 추가되었습니다.
  • [2024/10/22] SHP 데이터를 SVG 데이터로 생성하는 기능이 추가되었습니다.
  • [2024/10/16] 주소 DB가 2024년 10월 DB로 업데이트되었습니다.
  • [2024/10/14] 티센 폴리곤 생성 기능 추가, 공간 및 속성으로 필터링 하여 공간 데이터를 추출하는 기능 추가
  • [2024/10/01] 공간 데이터에 대한 NULL Shape 지원 및 압축 파일 안의 한글 파일명 오류 등이 수정되었습니다.
  • [2024/08/08] 공간 데이터 편집 기능이 추가되었습니다.
  • [2024/07/20] 지오코딩 등을 위한 입력 데이터 형식으로 CSV 파일 뿐만 아니라 엑셀 파일을 그대로 사용할 수 있도록 하였습니다.
  • [2024/07/08] 주소 DB가 2024년 6월 DB로 업데이트되었습니다.
  • [2024/04/03] 지오코딩과 역지오코딩 수행 속도가 개선되었습니다. (1건 당 평균 0.01초 소요)
  • [2024/02/25] SVG 형식의 행정구역도를 제공합니다. (아카이브에서 사용자명을 gizmo로 입력해 조회 가능)
  • [2024/02/16] 주소 DB가 2024년 1월 DB로 업데이트 되었으며 리버스지오코딩의 정확도가 개선되었습니다.
  • [2024/01/10] 새로운 지오코딩 엔진(gc_v3)으로 업데이트 되었습니다.
  • [2023/12/15] 주소 DB가 2023년 11월 DB로 업데이트 되었습니다.
  • [2023/11/30] 지오코딩 정확도 개선 및 UI 편의성 개선
  • [2023/10/31] HTTPS 적용 및 접속 URL 변경(https://www.geoservice.co.kr)
  • [2023/09/12] 통계분석 기능으로 색상단계구분도 분석이 추가되었습니다.
  • [2023/08/14] 속성 데이터 조인 기능과 지점 간 거리 분석 기능이 추가되었습니다.
  • [2023/07/30] 주요 기능 2가지 추가(헥사곤 데이터 생성, 밀도분석) 및 최신 주소DB(2023년 7월)로 업데이트 되었습니다.
  • [2023/02/21] 지번주소(2023년 2월), 도로명주소(2023년 1월) DB가 업데이트 되었습니다.
  • [2022/10/29] 기능 추가 및 사용자 편의성이 개선되었습니다.
  • [2022/09/25] 기능 추가 및 사용자 편의성이 개선되었습니다.
  • [2022/09/06] 사용자가 제공한 피드백을 통해 확인된 버그 제거 및 기능 개선
  • [2022/08/01] GEOSERVICE-WEB v1.0 서비스 시작

사용자 메뉴얼

지오서비스웹(GEOSERVICE-WEB) 기능 별 메뉴얼

FAQ

지오코딩(역지오코딩 포함) 변환 건수 무제한 사용(설치형 임대 라이선스) 소개

엑스알 지오코더 (XrGeocoder) v4.5 설치형 임대 라이선스 및 사용 설명

PWA (Progressive Web APP) 개발 가이드

vite로 구성된 바닐라 프로젝트가 기준이다. 먼저 index.html 파일에 manifest.json에 대한 연결이 필요하다.

<head>
  <link rel="manifest" href="/manifest.json" />
  <link rel="icon" href="/icon_192x192.png" />
</head>

위의 코드를 보면 선택사항이지만 아이콘도 지정했다. 이 아이콘 역시 manifest 내부에서 언급되는데, index.html은 url을 통해 접속했을때 웹브라우저에 표시될 아이콘이고 manifest에서는 바탕화면 등에 App으로 등록될때 표시되는 아이콘이다. mainfest.json 파일은 다음과 같으며 위치는 public 폴더이다.

{
  "name": "Vite PWA 예제",
  "short_name": "VitePWA",
  "description": "간단한 Vite 기반 PWA 예제",
  "start_url": "/",
  "display": "standalone",
  "theme_color": "#ff0000",
  "background_color": "#ff0000",
  "icons": [
    {
      "src": "/icon_192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "/icon_512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any maskable"
    }
  ]
}

여기까지만 해도 PWA로써 내가 원하는 아이콘과 제목 등으로 설정된 앱으로 설치할 수 있게 된다. 여기에 더해…. main.js 파일에 대한 내용이다. 크게 3가지 내용이다. DOM 구성, 서비스워커 구성, 앱 설치 UI이다. 먼저 DOM 구성은 다음처럼 했다.

import './style.css'

document.querySelector('#app').innerHTML = /*html*/ `
  

Vite PWA 예제

이것은 사용자 정의 Service Worker를 사용한 PWA입니다.

오프라인에서도 동작하며, 설치 가능합니다!

`;

서비스 워크 구성은 다음처럼 했다.

window.addEventListener('load', () => {
  navigator.serviceWorker.register('/sw.js')
    .then(registration => {
      console.log('Service Worker 등록 성공:', registration.scope);
    })
    .catch(error => {
      console.error('Service Worker 등록 실패:', error);
    });
});

앱 설치 UI에 대한 코드는 다음처럼 했다.

let deferredPrompt;
//const installButton = document.getElementById('installButton');
//const installMessage = document.getElementById('installMessage');

window.addEventListener('beforeinstallprompt', (e) => {
  // e.preventDefault();
  deferredPrompt = e;
  installButton.style.display = 'block';
  installMessage.textContent = '앱을 바탕화면에 설치할 수 있습니다!';
});

installButton.addEventListener('click', async () => {
  if (deferredPrompt) {
    deferredPrompt.prompt();
    const { outcome } = await deferredPrompt.userChoice;
    installMessage.textContent = outcome === 'accepted'
      ? '앱 설치가 시작되었습니다!'
      : '앱 설치가 취소되었습니다.';
    deferredPrompt = null;
    installButton.style.display = 'none';
  }
});

window.addEventListener('appinstalled', () => {
  installMessage.textContent = '앱이 성공적으로 설치되었습니다!';
  installButton.style.display = 'none';
});

서비스워커에 대한 sw.js 파일의 코드는 다음과 같으며 파일 위치는 public이다. 이 서비스워커는 캐쉬에 대한 기능이다.

const CACHE_NAME = 'vite-pwa-cache-v1';
const urlsToCache = [
  '/',
  '/index.html',
  '/style.css',
  '/icon_192x192.png',
  '/icon_512x512.png'
];

// 설치 시 캐싱
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(CACHE_NAME)
      .then(cache => {
        console.log('캐시 열기 성공');
        return cache.addAll(urlsToCache);
      })
  );
});

// 요청 처리 (CacheFirst 전략)
self.addEventListener('fetch', event => {
  const dest = event.request.destination;
  // if (event.request.destination === 'document') {
    event.respondWith(
      caches.match(event.request)
        .then(cachedResponse => {
          // 캐시에 있으면 캐시된 응답 반환
          if (cachedResponse) {
            console.log(dest + ' (' + event.request.url + ') from cache');
            return cachedResponse;
          }
          // 캐시에 없으면 네트워크 요청
          return fetch(event.request)
            .then(networkResponse => {
              // 네트워크 응답을 캐시에 저장
              console.log(dest + ' (' + event.request.url + ') from fetch');
              return caches.open(CACHE_NAME).then(cache => {
                cache.put(event.request, networkResponse.clone());
                return networkResponse;
              });
            })
            .catch(() => {
              onsole.log(dest + ' (' + event.request.url + ') failed');
              // 네트워크 요청 실패 시 (오프라인 등)
              return new Response('오프라인 상태입니다. 캐시도 없습니다.', {
                status: 503,
                statusText: 'Service Unavailable'
              });
            });
        })
    );
  // }
});

// 요청 처리 (NetworkFirst 전략)
self.addEventListener('_____fetch', event => {
  console.log(event.request.destination);
  // if (event.request.destination === 'document') {
    event.respondWith(
      fetch(event.request)
        .then(response => {
          // 네트워크 응답을 캐시에 저장
          return caches.open(CACHE_NAME).then(cache => {
            console.log(event.request.url + ' from fetch');
            cache.put(event.request, response.clone());
            return response;
          });
        })
        .catch(() => {
          // 네트워크 실패 시 캐시에서 가져오기
          console.log(event.request.url + ' from cache');
          return caches.match(event.request);
        })
    );
  // }
});

// 캐시 정리
self.addEventListener('activate', event => {
  const cacheWhitelist = [CACHE_NAME];
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cacheName => {
          if (!cacheWhitelist.includes(cacheName)) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
  console.log('캐시 정리 성공');
});

Push 기능을 위한 Key 생성

npx web-push generate-vapid-keys

아래와 같은 예시로 그 결과가 출력됨.

=======================================

Public Key:
BE1ZFx8L3zod6eVQsp_esYMinG_C5A3CA9w0rKqwwKDyfcMmMHpJpJm0HB4Usp3gnnLi3sQz5exFbGZiNIBBjJk

Private Key:
9KyZMbnDTVhDnabu8xxRt1tSHNvddskvnT3lBwbYEkI

=======================================

Public Key는 클라이언트에, Public Key + Private Key는 서버에 지정된다.

참고로 Push 기능 테스트를 위해 url을 호출해야 하는데 curl을 이용하면 다음과 같다.

curl -X GET http://localhost:3000/send-push

php 버전 업그레이드

워드프레송 양이 날씨가 더운지 PHP 버전이 너무 낮아서 더 이상 블로그 업데이트 지원을 못해주겠다고 한다. 예전 같으면 불같이 대응을 했겠지만, 지금은 ‘응… 뭐 사용하는데 지장없으니깐 패스..’라는 마음으로 넘기다가 불현듯 과거의 스마트했던.. 아니 지금도 스마트하다던.. 내 모습이 떠올라 겁 없이 이미 설치된 PHP를 제거했다.

yum remove php-*

뜨악! 홈페이지 접속이 안된다!! 내가 뭐랬어? 하지말랬잔아… 아니다. 내가 지금까지 시도했던 것중에 실패한게 하나도 없지 않은가… (정말?) 뭐~ 결국 다음 명령으로 해결함.

yum-config-manager --enable remi-php83
yum install php php-fpm -y
yum install php-mysql
systemctl restart php-fpm

아휴… 뭐래…

시간이 흘러 플러그인 업데이트를 하려고 했더니 “디렉토리를 만들 수 없습니다”라는 에러가 표시되는데, 해결책은 etc/php-fpm.d/www.conf 파일에서 user와 group 값을 apache가 아닌 내가 이용하는 웹서버인 nginx로 변경해야 함.

와…. 검색 엔진으로 해결이 안되다가 그록이한테 물어봐서 해결함. 그록아, 감사해~!

Web Component로 만들어진 사용자 정의 Tag에 스타일 지정하기

AI에게 물어봤더니 방법은 3가지랜다. (AI의 답변을 이렇게 기록해 둔 이유는 답변을 얻기 위한 과정이 단순히 몇번의 프롬프트 입력을 통해 얻지 않았기 때문이다.)

첫째, 호스트 요소(:host) 스타일로 흔히 하듯 사용자 정의 Tag에 대해 스타일을 지정하면 된다.

즉, 아래처럼.

/* style.css */
my-counter {
  display: block; /* 핵심! */
  margin: 20px;
  border: 1px solid red;
  width: 300px;}

사실, 처음엔 제대로 작동하지 않았다. 그 이유는 display에 대한 스타일 값을 지정하지 않았기 때문이다. 사용자 정의 Tag의 display 스타일에 대한 기본값은 미지정이다. 이 display를 지정하지 않으면 padding 이든 margin이든 width든 모두 엉망진창으로 계산되어 반영된다. 이 첫번째가 내가 원했던 답안이다. 그런데 AI는 2가지를 더 제시해줬고 내가 원했던 이 것보다 더 나은 방안이라며 제시했다. 정말 더 나은 방안인지 확인은 해보지 않았으나 일단 정리해 둔다.

두번째는 CSS 커스텀 프로퍼티의 사용이다.

즉, CSS 변수를 사용하라는 건데… 이 방식이 가장 권장되는 방식이라고 한다. AI가 알려준 것을 나열하면 다음과 같다.

컴포넌트 제작자가 내부 스타일의 특정 값을 CSS 변수(Custom Property)로 노출시켜주면, 사용자는 외부에서 이 변수 값을 바꿔서 내부 스타일을 변경할 수 있습니다. 이것이 가장 유연하고 권장되는 “스타일링 API” 설계 방식입니다.

컴포넌트 제작자 (MyCounter.js): 내부 스타일에서 CSS 변수를 사용합니다. var(–변수명, 기본값) 형태로 작성합니다.

// src/MyCounter.js
const templateStyleMyCounter = document.createElement('template');
templateStyleMyCounter.innerHTML = /*html*/ `
  
 `;

컴포넌트 사용자 (style.css): 외부 CSS에서 해당 변수의 값을 재정의합니다.

/* style.css */
my-counter {
  /* MyCounter 내부의 버튼 배경색을 초록색으로 변경 */
  --my-counter-button-bg: green;
  
  /* MyCounter 내부의 span 글자색을 보라색으로 변경 */
  --my-counter-span-color: purple;
}

세번째는 ::part 의사 요소를 사용하는 방법이라고 한다.

컴포넌트 내부 요소를 직접적인 대상으로 스타일을 지정할 수 있다. AI가 설명해준 내용 그대로를 언급하면 다음과 같다.

컴포넌트 제작자가 내부의 특정 요소에 part 속성을 부여하여 외부에 노출시키면, 사용자는 ::part() 의사요소를 사용해 해당 부분의 스타일을 직접 지정할 수 있습니다.

컴포넌트 제작자 (MyCounter.js): 스타일을 지정할 수 있도록 하고 싶은 내부 요소에 part 속성을 추가합니다.

// src/MyCounter.js
const templateDOMMyCounter = document.createElement('template');
templateDOMMyCounter.innerHTML = /*html*/ `
  <div>
    <span part="count-display"></span>  
    <button id="incrementButton" part="increment-button">Increment</button> 
  </div>
`;

컴포넌트 사용자 (style.css): ::part() 선택자를 사용하여 해당 파트의 스타일을 지정합니다.

/* style.css */
/* my-counter 내부의 increment-button 파트의 스타일 지정 */
my-counter::part(increment-button) {
  background-color: orange;
  border-radius: 0;
}

/* my-counter 내부의 count-display 파트의 스타일 지정 */
my-counter::part(count-display) {
  font-style: italic;
  color: red;
}

과연 미래의 소프트웨어 개발은 AI로 인해 얼마나 그 수준이 업그레이드 될 것이며 개발 복잡도는 얼마나 올라갈까…… 향상된 수준과 복잡도를 AI 없이 오직 사람만으로 수용할수 있을까?