get_district_plan
Query a Korean address or PNU to retrieve district unit plan membership and development activity permit restriction overlays. District plans can override building coverage, floor area ratio, and use.
Instructions
Return 지구단위계획구역 membership and 개발행위허가제한지역 overlays. When district_plan is non-empty, 건폐율·용적률·용도 may be overridden by the plan (see 국토계획법 제52조). V-World only returns the geometric hit — actual plan text must come from 지자체 고시문.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Korean address (e.g. '경기도 평택시 포승읍 내기리 680') or 19-digit PNU |
Implementation Reference
- src/tools/get_district_plan.ts:10-48 (handler)The main handler function that resolves a query to a point, queries V-World overlays for 지구단위계획구역 (LT_C_UPISUQ161) and 개발행위허가제한지역 (LT_C_UPISUQ171), then returns structured JSON with district plan and development permit restriction data.
export const getDistrictPlanTool = async ({ query }: { query: string }) => { const resolved = await resolveToPoint(query); const q = await queryOverlays(LAYERS, resolved.point_wgs84); const result = { query, pnu: resolved.pnu, point_wgs84: resolved.point_wgs84, district_plan: q.hits .filter((h) => h.layer === "LT_C_UPISUQ161") .map((h) => ({ layer: h.layer, name: h.name, designation_year: h.designation_year, designation_number: h.designation_number, sido: h.sido, sigg: h.sigg, extra: h.properties, })), development_permit_restriction: q.hits .filter((h) => h.layer === "LT_C_UPISUQ171") .map((h) => ({ layer: h.layer, name: h.name, designation_year: h.designation_year, designation_number: h.designation_number, })), layer_errors: q.errors.length > 0 ? q.errors : undefined, source: { resolved_at: new Date().toISOString(), layers: LAYERS.map((l) => l.id), }, note: "When district_plan is non-empty, 건폐율/용적률 may be overridden by the 지구단위계획 (see 국토계획법 제52조). This tool returns the V-World geometric hit only — the actual plan text is NOT in V-World and must be fetched from the local 지자체 portal or 고시문.", disclaimer: DISCLAIMER, }; return { content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }] }; }; - src/tools/get_district_plan.ts:5-8 (schema)Layer definitions for the two V-World layers queried by this tool: 지구단위계획구역 and 개발행위허가제한지역.
const LAYERS: LayerDef[] = [ { id: "LT_C_UPISUQ161", label: "지구단위계획구역" }, { id: "LT_C_UPISUQ171", label: "개발행위허가제한지역" }, ]; - src/server.ts:47-52 (registration)Registration of the 'get_district_plan' tool in the MCP server with its description and schema binding.
server.tool( "get_district_plan", "Return 지구단위계획구역 membership and 개발행위허가제한지역 overlays. When district_plan is non-empty, 건폐율·용적률·용도 may be overridden by the plan (see 국토계획법 제52조). V-World only returns the geometric hit — actual plan text must come from 지자체 고시문.", QUERY_SCHEMA, getDistrictPlanTool ); - src/server.ts:11-11 (registration)Import of the getDistrictPlanTool handler from the tools module.
import { getDistrictPlanTool } from "./tools/get_district_plan.js"; - src/lib/overlays.ts:83-128 (helper)The queryOverlays helper function used by the handler to query all layers and return hits/errors.
export async function queryOverlays( layers: LayerDef[], point: { x: number; y: number }, opts: QueryOverlaysOptions | number = {} ): Promise<OverlayQueryResult> { const options: QueryOverlaysOptions = typeof opts === "number" ? { perLayerSize: opts } : opts; const perLayerSize = options.perLayerSize ?? 5; const geomFilter = options.geomFilter ?? (options.radius_m && options.radius_m > 0 ? pointToBox(point, options.radius_m) : undefined); const results = await Promise.all( layers.map(async (layer) => { try { const feats = geomFilter ? await getFeatures({ layer: layer.id, geomFilter, size: perLayerSize }) : await getFeatures({ layer: layer.id, point, size: perLayerSize }); return { layer, feats, error: null as LayerQueryError | null }; } catch (e) { const err: LayerQueryError = e instanceof VWorldError ? { layer: layer.id, layer_label: layer.label, error_code: e.code ?? "UNKNOWN", error_message: e.message, } : { layer: layer.id, layer_label: layer.label, error_code: "UNHANDLED", error_message: e instanceof Error ? e.message : String(e), }; return { layer, feats: [] as VWorldFeature[], error: err }; } }) ); const hits: OverlayHit[] = []; const errors: LayerQueryError[] = []; for (const r of results) { if (r.error) errors.push(r.error); for (const f of r.feats) hits.push(featureToHit(r.layer, f)); } return { hits, errors }; }