false, 'error' => 'Missing lat/lng'], JSON_PRETTY_PRINT); exit; } $lat = (float)$js['lat']; $lng = (float)$js['lng']; $debug = !empty($js['debug']); // LIST PlanningOnline MapServer $service = 'https://services.thelist.tas.gov.au/arcgis/rest/services/Public/PlanningOnline/MapServer'; /** * Query a MapServer layer by a point (lng/lat in EPSG:4326). * $returnGeometry: include geometry in the response * $extraParams: override/add raw ArcGIS params (e.g. resultRecordCount) */ function arcgis_query($layerId, $lng, $lat, $outFields = '*', $returnGeometry = false, $extraParams = []) { global $service; $params = array_merge([ 'f' => 'json', 'where' => '1=1', 'returnGeometry' => $returnGeometry ? 'true' : 'false', 'outFields' => $outFields, 'outSR' => '4326', // lat/lng for Leaflet 'geometryType' => 'esriGeometryPoint', 'spatialRel' => 'esriSpatialRelIntersects', 'inSR' => '4326', 'geometry' => json_encode(['x' => (float)$lng, 'y' => (float)$lat]), // Don't artificially limit multi-hit layers; override per-call below 'resultRecordCount'=> 100 ], $extraParams); $url = "{$service}/{$layerId}/query"; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query($params), CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15, ]); $resp = curl_exec($ch); if ($resp === false) { $err = curl_error($ch); curl_close($ch); return ['error' => "cURL error: $err"]; } curl_close($ch); return json_decode($resp, true); } // Layer IDs on PlanningOnline $LAYER_PARCELS = 2; // Cadastral Parcels $LAYER_LGA = 8; // Local Government Areas $LAYER_ZONES = 13; // Tasmanian Planning Scheme Zones $LAYER_CODES = 14; // Tasmanian Planning Scheme Code Overlays // --- Parcels (attributes) --- $parcelsResp = arcgis_query($LAYER_PARCELS, $lng, $lat, '*', false, ['resultRecordCount' => 1]); $parcelFeat = $parcelsResp['features'][0] ?? null; $parcelAttr = $parcelFeat['attributes'] ?? []; if (!$parcelFeat) { echo json_encode(['ok' => false, 'error' => 'No parcel found at that location']); exit; } // PID (field names vary) $pid = $parcelAttr['PID'] ?? $parcelAttr['PROPERTY_ID'] ?? null; // --- Parcel geometry (polygon) for map --- $parcelGeomResp = arcgis_query($LAYER_PARCELS, $lng, $lat, 'PID,OBJECTID', true, ['resultRecordCount' => 1]); $boundaryGeoJSON = null; if (!empty($parcelGeomResp['features'][0]['geometry']['rings'])) { // outSR=4326 → [lng, lat] ready for Leaflet/GeoJSON $rings = $parcelGeomResp['features'][0]['geometry']['rings']; $boundaryGeoJSON = [ 'type' => 'Feature', 'geometry' => [ 'type' => 'Polygon', 'coordinates' => $rings ], 'properties' => [ 'pid' => $pid ] ]; } // --- Compose Title Id --- $volume = $parcelAttr['VOLUME'] ?? null; $folio = isset($parcelAttr['FOLIO']) ? (string)$parcelAttr['FOLIO'] : null; $titleId = null; if ($volume && $folio !== null && $folio !== '') { $titleId = $volume . '/' . $folio; } else { $titleId = $parcelAttr['CT'] ?? $parcelAttr['CT_REFERENCE'] ?? null; } // --- Area --- $sqm = null; if (isset($parcelAttr['MEAS_AREA'])) { $sqm = (float)$parcelAttr['MEAS_AREA']; } elseif (isset($parcelAttr['COMP_AREA'])) { $sqm = (float)$parcelAttr['COMP_AREA']; } elseif (isset($parcelAttr['AREA_SQM'])) { $sqm = (float)$parcelAttr['AREA_SQM']; } $total_area = null; $area_sqm_label = null; $area_ha_label = null; if ($sqm !== null) { $ha = $sqm / 10000.0; $area_sqm_label = number_format($sqm, 0) . ' sqm'; $area_ha_label = rtrim(rtrim(number_format($ha, 4, '.', ''), '0'), '.') . ' ha'; $total_area = [ 'sqm' => (float)$sqm, 'sqm_label' => $area_sqm_label, 'ha' => (float)$ha, 'ha_label' => $area_ha_label ]; } $tenure = $parcelAttr['TENURE_TY'] ?? null; $lpi = $parcelAttr['LPI'] ?? null; $listGuid = $parcelAttr['LIST_GUID'] ?? null; // --- LGA / Council --- $lgaResp = arcgis_query($LAYER_LGA, $lng, $lat, '*', false, ['resultRecordCount' => 1]); $lgaAttr = $lgaResp['features'][0]['attributes'] ?? []; $council = $lgaAttr['LGA_NAME'] ?? $lgaAttr['NAME'] ?? $lgaAttr['COUNCIL'] ?? null; // --- Zones (multiple may intersect the point) --- $zonesResp = arcgis_query($LAYER_ZONES, $lng, $lat, '*', false, ['resultRecordCount' => 100]); $zoneFeatures = $zonesResp['features'] ?? []; $zoneNames = []; $schemeName = null; foreach ($zoneFeatures as $zf) { $a = $zf['attributes'] ?? []; foreach (['ZONE', 'ZONING', 'ZONE_NAME', 'ZONE_LABEL'] as $k) { if (!empty($a[$k])) { $zoneNames[] = $a[$k]; break; } } if (!$schemeName) { $schemeName = $a['LPS'] ?? $a['SCHEME'] ?? 'Tasmanian Planning Scheme'; } } // --- Code overlays (multiple) --- $codesResp = arcgis_query($LAYER_CODES, $lng, $lat, '*', false, ['resultRecordCount' => 200]); $codeFeatures = $codesResp['features'] ?? []; $codeNames = []; foreach ($codeFeatures as $cf) { $a = $cf['attributes'] ?? []; foreach (['CODE', 'OVERLAY', 'OVERLAY_CODE', 'CODE_NAME', 'OVERLAY_DESC'] as $k) { if (!empty($a[$k])) { $codeNames[] = $a[$k]; break; } } } $out = [ 'ok' => true, 'pid' => $pid, 'title_id' => $titleId, 'tenure' => $tenure, 'lpi' => $lpi, 'list_guid' => $listGuid, 'total_area' => $total_area, // structured with sqm/ha + labels 'area_sqm' => $area_sqm_label, // flat labels for your UI 'area_ha' => $area_ha_label, 'council' => $council, 'planning_scheme' => $schemeName, 'planning_zones' => array_values(array_unique(array_filter($zoneNames))), 'planning_codes' => array_values(array_unique(array_filter($codeNames))), 'boundary' => $boundaryGeoJSON ]; if ($debug) { $out['debug'] = [ 'parcel_attrs' => $parcelAttr, 'lga_attrs' => $lgaAttr, 'zones_count' => count($zoneFeatures), 'codes_count' => count($codeFeatures) ]; } echo json_encode($out, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } catch (Throwable $e) { http_response_code(500); echo json_encode(['ok' => false, 'error' => $e->getMessage()]); }