cd /news/developer-tools/routing-around-google-maps-in-korea-… Β· home β€Ί topics β€Ί developer-tools β€Ί article
[ARTICLE Β· art-32134] src=dev.to β†— pub= topic=developer-tools verified=true sentiment=Β· neutral

Routing around Google Maps in Korea: Naver & Kakao deep links, weird coordinates, and iOS clipboard

A developer built K-Map Router, a tool that converts Google Maps links into Naver Map or KakaoMap deep links for use in South Korea, where Google Maps lacks walking and transit directions due to map-data export restrictions. The project, deployed on a Cloudflare Worker with zero runtime dependencies, required reverse-engineering undocumented coordinate formats, including protobuf-encoded geocodes from the Google Maps mobile app and the EPSG:5181 coordinate system used by KakaoMap. The developer documented the deep link formats and coordinate decoding logic to help others navigate the fragmented Korean mapping ecosystem.

read4 min views1 publishedJun 18, 2026

If you've traveled to Korea, you've hit this wall: Google Maps can't give you walking or transit directions here.

Map-data export is restricted, so locals use Naver Map or KakaoMap instead. The usual workaround for visitors is

painful β€” copy a place's Korean name from Google, paste it into Naver, repeat for every stop.

So I built K-Map Router: paste a Google Maps link β†’ it opens that place (and the route) in

This post isn't a "look how hard I worked" story β€” honestly, I shipped it fast with heavy AI pair-programming (Claude Code).

It's a field guide to the stuff that's genuinely hard to find documented: how Korean map deep links and coordinates

actually work. If you ever build something in this space, I hope this saves you a few days.

A single Cloudflare Worker serves both the React SPA (static assets) and the POST /api/resolve

endpoint β€” same origin,

free tier, zero runtime dependencies. Coordinate resolution is server-side (the browser β†’ Google is blocked by CORS) and

is nothing but fetch

  • regex + a little decoding. No DB, stateless.

You can't just regex one pattern. A Google Maps URL (after following redirects) hides the coordinates in one of several

shapes, and you have to try them in priority order:

  // 1) place pin β€” most authoritative
  //    ...!3d{lat}!4d{lng}
  // 2) directions waypoint β€” ⚠️ REVERSED: !1d{lng}!2d{lat}
  //    multiple pairs => last = destination, first = origin
  // 3) viewport center β€” /@{lat},{lng},17z
  // 4) ?query= / &destination= / &daddr=
  // 5) ?ll= / &sll=

The one that bit me hardest: ** /dir/ directions URLs store !1d{longitude}!2d{latitude} β€” longitude first.** Read it as

(lat, lng)

and you'll happily return a point that's in the ocean. And when there are multiple pairs, the Links shared from the Google Maps mobile app (the ones with ?g_st=...

) are special. They resolve to something like:

  .../maps?saddr=Seoul+Station&daddr=Gyeongbokgung&geocode=FWoPPQId...;FWFrPQIdEYSRBy...

There are no plaintext coordinates anywhere β€” they're base64url-encoded in the geocode=

param, as a tiny protobuf. Each

;

-separated entry encodes one endpoint:

  // 0x15 = field 2, fixed32 (little-endian) = lat * 1e6
  // 0x1D = field 3, fixed32 (little-endian) = lng * 1e6
  function decodeGeocodeEntry(entry) {
    const bin = atob(entry.replace(/-/g, "+").replace(/_/g, "/"));
    let lat = null, lng = null;
    for (let i = 0; i + 4 < bin.length && (lat === null || lng === null); i++) {
      const tag = bin.charCodeAt(i);
      if (tag !== 0x15 && tag !== 0x1d) continue;
      let v = 0;
      for (let j = 3; j >= 0; j--) v = v * 256 + bin.charCodeAt(i + 1 + j); // LE
      if (v > 0x7fffffff) v -= 0x100000000;
      if (tag === 0x15) lat = v / 1e6; else lng = v / 1e6;
      i += 4;
    }
    return lat !== null && lng !== null ? { lat, lng } : null;
  }

Verified against 경볡ꢁ (Gyeongbokgung): FWFrPQIdEYSRBy...

β†’ 37.579617, 126.977041

. βœ…

Naver (primary β€” best transit + English):

  nmap://route/public?dlat={lat}&dlng={lng}&dname={enc}&appname={APPNAME}

appname

is dname

is optional β€” omit it and Naver shows the real address. Don't send a literal Destination

placeholder.route/walk

, route/car

, route/public

.Kakao (secondary):

  kakaomap://route?ep={lat},{lng}&by=publictransit

by=foot | car | publictransit

.Android: custom schemes are flaky from Chrome. Use an intent://

URL with a built-in store fallback:

  intent://route/public?...#Intent;scheme=nmap;package=com.nhn.android.nmap;S.browser_fallback_url=...;end

For desktop fallback, Kakao's legacy link/to

API can't take a start point. But its redirect target can β€” if you feed it

Kakao's internal WCongnamul coordinates:

  https://map.kakao.com/?map_type=TYPE_MAP&target=traffic&rt={sx},{sy},{ex},{ey}&rt1={from}&rt2={to}

WCongnamul turned out to be EPSG:5181 (a GRS80 Transverse Mercator) scaled Γ—2.5. I implemented the projection by hand and

it matched Kakao's own conversion to the integer for every test point. (Also: never put a comma in the rt1/rt2

label β€” it

breaks the parser and silently drops the destination.)

The "Paste from clipboard" button worked everywhere except iOS. Two reasons, both subtle:

text/uri-list

onlytext/plain

. So navigator.clipboard.readText()

returns an empty string.await

. So a readText()

β†’ fall back to read()

chain NotAllowedError

.The fix is to make exactly one clipboard call inside the gesture, then read the type off the already-resolved

ClipboardItem

(those getType

calls reuse the granted permission):

  const items = await navigator.clipboard.read();   // one call, in the gesture
  for (const item of items) {
    for (const type of ["text/uri-list", "text/plain", "text/html"]) {
      if (!item.types.includes(type)) continue;
      const text = (await (await item.getType(type)).text()).trim();
      // uri-list: first non-comment line is the URL
      if (text) return text;
    }
  }

The iOS "Paste" permission bubble itself is unavoidable β€” it's OS-enforced for any programmatic clipboard read.

Google encodes the travel mode in the link (travelmode=driving

, dirflg=d

, or !3e0

). Reading it means a shared driving

route opens directly in driving directions in Naver/Kakao β€” "plan in Google Maps, navigate in Korea," unchanged.

// Detect dark theme var iframe = document.getElementById('tweet-2067124302403772609-944'); if (document.body.className.includes('dark-theme')) { iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=2067124302403772609&theme=dark" }

If you're building anything that bridges Google Maps and Korean map apps, steal the deep-link and coordinate logic β€” that's

exactly why it's public. Questions welcome. 🐣

── more in #developer-tools 4 stories Β· sorted by recency
── more on @google maps 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/routing-around-googl…] indexed:0 read:4min 2026-06-18 Β· β€”