OLD-list_lookup.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. <?php
  2. // list_lookup.php
  3. // Turn off display of errors in the response, log instead
  4. ini_set('display_errors', '0');
  5. error_reporting(E_ALL);
  6. //require_once __DIR__ . '/rate_limit.php';
  7. // Convert PHP errors to exceptions so we can return JSON
  8. set_error_handler(function($severity, $message, $file, $line) {
  9. throw new ErrorException($message, 0, $severity, $file, $line);
  10. });
  11. // Always return JSON
  12. header('Content-Type: application/json; charset=utf-8');
  13. try {
  14. $raw = file_get_contents('php://input');
  15. $js = json_decode($raw, true);
  16. if (!$js || !isset($js['lat'], $js['lng'])) {
  17. http_response_code(400);
  18. echo json_encode(['ok' => false, 'error' => 'Missing lat/lng'], JSON_PRETTY_PRINT);
  19. exit;
  20. }
  21. $lat = (float)$js['lat'];
  22. $lng = (float)$js['lng'];
  23. $debug = !empty($js['debug']);
  24. // LIST PlanningOnline MapServer
  25. $service = 'https://services.thelist.tas.gov.au/arcgis/rest/services/Public/PlanningOnline/MapServer';
  26. /**
  27. * Query a MapServer layer by a point (lng/lat in EPSG:4326).
  28. * $returnGeometry: include geometry in the response
  29. * $extraParams: override/add raw ArcGIS params (e.g. resultRecordCount)
  30. */
  31. function arcgis_query($layerId, $lng, $lat, $outFields = '*', $returnGeometry = false, $extraParams = []) {
  32. global $service;
  33. $params = array_merge([
  34. 'f' => 'json',
  35. 'where' => '1=1',
  36. 'returnGeometry' => $returnGeometry ? 'true' : 'false',
  37. 'outFields' => $outFields,
  38. 'outSR' => '4326', // lat/lng for Leaflet
  39. 'geometryType' => 'esriGeometryPoint',
  40. 'spatialRel' => 'esriSpatialRelIntersects',
  41. 'inSR' => '4326',
  42. 'geometry' => json_encode(['x' => (float)$lng, 'y' => (float)$lat]),
  43. // Don't artificially limit multi-hit layers; override per-call below
  44. 'resultRecordCount'=> 100
  45. ], $extraParams);
  46. $url = "{$service}/{$layerId}/query";
  47. $ch = curl_init($url);
  48. curl_setopt_array($ch, [
  49. CURLOPT_POST => true,
  50. CURLOPT_POSTFIELDS => http_build_query($params),
  51. CURLOPT_RETURNTRANSFER => true,
  52. CURLOPT_TIMEOUT => 15,
  53. ]);
  54. $resp = curl_exec($ch);
  55. if ($resp === false) {
  56. $err = curl_error($ch);
  57. curl_close($ch);
  58. return ['error' => "cURL error: $err"];
  59. }
  60. curl_close($ch);
  61. return json_decode($resp, true);
  62. }
  63. // Layer IDs on PlanningOnline
  64. $LAYER_PARCELS = 2; // Cadastral Parcels
  65. $LAYER_LGA = 8; // Local Government Areas
  66. $LAYER_ZONES = 13; // Tasmanian Planning Scheme Zones
  67. $LAYER_CODES = 14; // Tasmanian Planning Scheme Code Overlays
  68. // --- Parcels (attributes) ---
  69. $parcelsResp = arcgis_query($LAYER_PARCELS, $lng, $lat, '*', false, ['resultRecordCount' => 1]);
  70. $parcelFeat = $parcelsResp['features'][0] ?? null;
  71. $parcelAttr = $parcelFeat['attributes'] ?? [];
  72. if (!$parcelFeat) {
  73. echo json_encode(['ok' => false, 'error' => 'No parcel found at that location']);
  74. exit;
  75. }
  76. // PID (field names vary)
  77. $pid = $parcelAttr['PID'] ?? $parcelAttr['PROPERTY_ID'] ?? null;
  78. // --- Parcel geometry (polygon) for map ---
  79. $parcelGeomResp = arcgis_query($LAYER_PARCELS, $lng, $lat, 'PID,OBJECTID', true, ['resultRecordCount' => 1]);
  80. $boundaryGeoJSON = null;
  81. if (!empty($parcelGeomResp['features'][0]['geometry']['rings'])) {
  82. // outSR=4326 → [lng, lat] ready for Leaflet/GeoJSON
  83. $rings = $parcelGeomResp['features'][0]['geometry']['rings'];
  84. $boundaryGeoJSON = [
  85. 'type' => 'Feature',
  86. 'geometry' => [
  87. 'type' => 'Polygon',
  88. 'coordinates' => $rings
  89. ],
  90. 'properties' => [
  91. 'pid' => $pid
  92. ]
  93. ];
  94. }
  95. // --- Compose Title Id ---
  96. $volume = $parcelAttr['VOLUME'] ?? null;
  97. $folio = isset($parcelAttr['FOLIO']) ? (string)$parcelAttr['FOLIO'] : null;
  98. $titleId = null;
  99. if ($volume && $folio !== null && $folio !== '') {
  100. $titleId = $volume . '/' . $folio;
  101. } else {
  102. $titleId = $parcelAttr['CT'] ?? $parcelAttr['CT_REFERENCE'] ?? null;
  103. }
  104. // --- Area ---
  105. $sqm = null;
  106. if (isset($parcelAttr['MEAS_AREA'])) {
  107. $sqm = (float)$parcelAttr['MEAS_AREA'];
  108. } elseif (isset($parcelAttr['COMP_AREA'])) {
  109. $sqm = (float)$parcelAttr['COMP_AREA'];
  110. } elseif (isset($parcelAttr['AREA_SQM'])) {
  111. $sqm = (float)$parcelAttr['AREA_SQM'];
  112. }
  113. $total_area = null;
  114. $area_sqm_label = null;
  115. $area_ha_label = null;
  116. if ($sqm !== null) {
  117. $ha = $sqm / 10000.0;
  118. $area_sqm_label = number_format($sqm, 0) . ' sqm';
  119. $area_ha_label = rtrim(rtrim(number_format($ha, 4, '.', ''), '0'), '.') . ' ha';
  120. $total_area = [
  121. 'sqm' => (float)$sqm,
  122. 'sqm_label' => $area_sqm_label,
  123. 'ha' => (float)$ha,
  124. 'ha_label' => $area_ha_label
  125. ];
  126. }
  127. $tenure = $parcelAttr['TENURE_TY'] ?? null;
  128. $lpi = $parcelAttr['LPI'] ?? null;
  129. $listGuid = $parcelAttr['LIST_GUID'] ?? null;
  130. // --- LGA / Council ---
  131. $lgaResp = arcgis_query($LAYER_LGA, $lng, $lat, '*', false, ['resultRecordCount' => 1]);
  132. $lgaAttr = $lgaResp['features'][0]['attributes'] ?? [];
  133. $council = $lgaAttr['LGA_NAME'] ?? $lgaAttr['NAME'] ?? $lgaAttr['COUNCIL'] ?? null;
  134. // --- Zones (multiple may intersect the point) ---
  135. $zonesResp = arcgis_query($LAYER_ZONES, $lng, $lat, '*', false, ['resultRecordCount' => 100]);
  136. $zoneFeatures = $zonesResp['features'] ?? [];
  137. $zoneNames = [];
  138. $schemeName = null;
  139. foreach ($zoneFeatures as $zf) {
  140. $a = $zf['attributes'] ?? [];
  141. foreach (['ZONE', 'ZONING', 'ZONE_NAME', 'ZONE_LABEL'] as $k) {
  142. if (!empty($a[$k])) { $zoneNames[] = $a[$k]; break; }
  143. }
  144. if (!$schemeName) {
  145. $schemeName = $a['LPS'] ?? $a['SCHEME'] ?? 'Tasmanian Planning Scheme';
  146. }
  147. }
  148. // --- Code overlays (multiple) ---
  149. $codesResp = arcgis_query($LAYER_CODES, $lng, $lat, '*', false, ['resultRecordCount' => 200]);
  150. $codeFeatures = $codesResp['features'] ?? [];
  151. $codeNames = [];
  152. foreach ($codeFeatures as $cf) {
  153. $a = $cf['attributes'] ?? [];
  154. foreach (['CODE', 'OVERLAY', 'OVERLAY_CODE', 'CODE_NAME', 'OVERLAY_DESC'] as $k) {
  155. if (!empty($a[$k])) { $codeNames[] = $a[$k]; break; }
  156. }
  157. }
  158. $out = [
  159. 'ok' => true,
  160. 'pid' => $pid,
  161. 'title_id' => $titleId,
  162. 'tenure' => $tenure,
  163. 'lpi' => $lpi,
  164. 'list_guid' => $listGuid,
  165. 'total_area' => $total_area, // structured with sqm/ha + labels
  166. 'area_sqm' => $area_sqm_label, // flat labels for your UI
  167. 'area_ha' => $area_ha_label,
  168. 'council' => $council,
  169. 'planning_scheme' => $schemeName,
  170. 'planning_zones' => array_values(array_unique(array_filter($zoneNames))),
  171. 'planning_codes' => array_values(array_unique(array_filter($codeNames))),
  172. 'boundary' => $boundaryGeoJSON
  173. ];
  174. if ($debug) {
  175. $out['debug'] = [
  176. 'parcel_attrs' => $parcelAttr,
  177. 'lga_attrs' => $lgaAttr,
  178. 'zones_count' => count($zoneFeatures),
  179. 'codes_count' => count($codeFeatures)
  180. ];
  181. }
  182. echo json_encode($out, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
  183. } catch (Throwable $e) {
  184. http_response_code(500);
  185. echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
  186. }