@mapbox/mcp-server 0.12.7 → 0.13.0-dev

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (369) hide show
  1. package/dist/commonjs/config/toolConfig.d.ts +0 -10
  2. package/dist/commonjs/config/toolConfig.d.ts.map +1 -1
  3. package/dist/commonjs/config/toolConfig.js +0 -36
  4. package/dist/commonjs/config/toolConfig.js.map +1 -1
  5. package/dist/commonjs/resources/compute/ComputeResource.d.ts +20 -0
  6. package/dist/commonjs/resources/compute/ComputeResource.d.ts.map +1 -0
  7. package/dist/commonjs/resources/compute/ComputeResource.js +49 -0
  8. package/dist/commonjs/resources/compute/ComputeResource.js.map +1 -0
  9. package/dist/commonjs/resources/inline-payload/InlinePayloadResource.d.ts +17 -0
  10. package/dist/commonjs/resources/inline-payload/InlinePayloadResource.d.ts.map +1 -0
  11. package/dist/commonjs/resources/inline-payload/InlinePayloadResource.js +46 -0
  12. package/dist/commonjs/resources/inline-payload/InlinePayloadResource.js.map +1 -0
  13. package/dist/commonjs/resources/resourceRegistry.d.ts +4 -2
  14. package/dist/commonjs/resources/resourceRegistry.d.ts.map +1 -1
  15. package/dist/commonjs/resources/resourceRegistry.js +16 -6
  16. package/dist/commonjs/resources/resourceRegistry.js.map +1 -1
  17. package/dist/commonjs/resources/ui-apps/MapAppUIResource.d.ts +27 -0
  18. package/dist/commonjs/resources/ui-apps/MapAppUIResource.d.ts.map +1 -0
  19. package/dist/commonjs/resources/ui-apps/{DirectionsAppUIResource.js → MapAppUIResource.js} +15 -17
  20. package/dist/commonjs/resources/ui-apps/MapAppUIResource.js.map +1 -0
  21. package/dist/commonjs/resources/ui-apps/mapAppHtml.d.ts +19 -0
  22. package/dist/commonjs/resources/ui-apps/mapAppHtml.d.ts.map +1 -0
  23. package/dist/commonjs/resources/ui-apps/mapAppHtml.js +1621 -0
  24. package/dist/commonjs/resources/ui-apps/mapAppHtml.js.map +1 -0
  25. package/dist/commonjs/tools/category-search-tool/CategorySearchTool.d.ts.map +1 -1
  26. package/dist/commonjs/tools/category-search-tool/CategorySearchTool.js +46 -41
  27. package/dist/commonjs/tools/category-search-tool/CategorySearchTool.js.map +1 -1
  28. package/dist/commonjs/tools/category-search-tool/CategorySearchTool.output.schema.d.ts +6 -3
  29. package/dist/commonjs/tools/category-search-tool/CategorySearchTool.output.schema.d.ts.map +1 -1
  30. package/dist/commonjs/tools/category-search-tool/CategorySearchTool.output.schema.js +3 -1
  31. package/dist/commonjs/tools/category-search-tool/CategorySearchTool.output.schema.js.map +1 -1
  32. package/dist/commonjs/tools/category-search-tool/buildCategorySearchRequestUrl.d.ts +30 -0
  33. package/dist/commonjs/tools/category-search-tool/buildCategorySearchRequestUrl.d.ts.map +1 -0
  34. package/dist/commonjs/tools/category-search-tool/buildCategorySearchRequestUrl.js +39 -0
  35. package/dist/commonjs/tools/category-search-tool/buildCategorySearchRequestUrl.js.map +1 -0
  36. package/dist/commonjs/tools/difference-tool/DifferenceTool.d.ts.map +1 -1
  37. package/dist/commonjs/tools/difference-tool/DifferenceTool.js +19 -3
  38. package/dist/commonjs/tools/difference-tool/DifferenceTool.js.map +1 -1
  39. package/dist/commonjs/tools/difference-tool/DifferenceTool.output.schema.d.ts +4 -1
  40. package/dist/commonjs/tools/difference-tool/DifferenceTool.output.schema.d.ts.map +1 -1
  41. package/dist/commonjs/tools/difference-tool/DifferenceTool.output.schema.js +7 -3
  42. package/dist/commonjs/tools/difference-tool/DifferenceTool.output.schema.js.map +1 -1
  43. package/dist/commonjs/tools/directions-tool/DirectionsTool.d.ts +0 -9
  44. package/dist/commonjs/tools/directions-tool/DirectionsTool.d.ts.map +1 -1
  45. package/dist/commonjs/tools/directions-tool/DirectionsTool.input.schema.d.ts.map +1 -1
  46. package/dist/commonjs/tools/directions-tool/DirectionsTool.input.schema.js +9 -4
  47. package/dist/commonjs/tools/directions-tool/DirectionsTool.input.schema.js.map +1 -1
  48. package/dist/commonjs/tools/directions-tool/DirectionsTool.js +40 -78
  49. package/dist/commonjs/tools/directions-tool/DirectionsTool.js.map +1 -1
  50. package/dist/commonjs/tools/directions-tool/DirectionsTool.output.schema.d.ts +4 -1
  51. package/dist/commonjs/tools/directions-tool/DirectionsTool.output.schema.d.ts.map +1 -1
  52. package/dist/commonjs/tools/directions-tool/DirectionsTool.output.schema.js +10 -4
  53. package/dist/commonjs/tools/directions-tool/DirectionsTool.output.schema.js.map +1 -1
  54. package/dist/commonjs/tools/directions-tool/buildDirectionsRequestUrl.d.ts +2 -2
  55. package/dist/commonjs/tools/directions-tool/buildDirectionsRequestUrl.d.ts.map +1 -1
  56. package/dist/commonjs/tools/directions-tool/buildDirectionsRequestUrl.js +4 -12
  57. package/dist/commonjs/tools/directions-tool/buildDirectionsRequestUrl.js.map +1 -1
  58. package/dist/commonjs/tools/ground-location-tool/GroundLocationTool.d.ts.map +1 -1
  59. package/dist/commonjs/tools/ground-location-tool/GroundLocationTool.js +52 -18
  60. package/dist/commonjs/tools/ground-location-tool/GroundLocationTool.js.map +1 -1
  61. package/dist/commonjs/tools/ground-location-tool/GroundLocationTool.output.schema.d.ts +4 -1
  62. package/dist/commonjs/tools/ground-location-tool/GroundLocationTool.output.schema.d.ts.map +1 -1
  63. package/dist/commonjs/tools/ground-location-tool/GroundLocationTool.output.schema.js +7 -3
  64. package/dist/commonjs/tools/ground-location-tool/GroundLocationTool.output.schema.js.map +1 -1
  65. package/dist/commonjs/tools/ground-location-tool/buildReverseGeocodeUrl.d.ts +19 -0
  66. package/dist/commonjs/tools/ground-location-tool/buildReverseGeocodeUrl.d.ts.map +1 -0
  67. package/dist/commonjs/tools/ground-location-tool/buildReverseGeocodeUrl.js +26 -0
  68. package/dist/commonjs/tools/ground-location-tool/buildReverseGeocodeUrl.js.map +1 -0
  69. package/dist/commonjs/tools/intersect-tool/IntersectTool.d.ts.map +1 -1
  70. package/dist/commonjs/tools/intersect-tool/IntersectTool.js +19 -3
  71. package/dist/commonjs/tools/intersect-tool/IntersectTool.js.map +1 -1
  72. package/dist/commonjs/tools/intersect-tool/IntersectTool.output.schema.d.ts +4 -1
  73. package/dist/commonjs/tools/intersect-tool/IntersectTool.output.schema.d.ts.map +1 -1
  74. package/dist/commonjs/tools/intersect-tool/IntersectTool.output.schema.js +7 -3
  75. package/dist/commonjs/tools/intersect-tool/IntersectTool.output.schema.js.map +1 -1
  76. package/dist/commonjs/tools/isochrone-tool/IsochroneTool.d.ts.map +1 -1
  77. package/dist/commonjs/tools/isochrone-tool/IsochroneTool.js +51 -47
  78. package/dist/commonjs/tools/isochrone-tool/IsochroneTool.js.map +1 -1
  79. package/dist/commonjs/tools/isochrone-tool/IsochroneTool.output.schema.d.ts +4 -5
  80. package/dist/commonjs/tools/isochrone-tool/IsochroneTool.output.schema.d.ts.map +1 -1
  81. package/dist/commonjs/tools/isochrone-tool/IsochroneTool.output.schema.js +7 -3
  82. package/dist/commonjs/tools/isochrone-tool/IsochroneTool.output.schema.js.map +1 -1
  83. package/dist/commonjs/tools/isochrone-tool/buildIsochroneRequestUrl.d.ts +28 -0
  84. package/dist/commonjs/tools/isochrone-tool/buildIsochroneRequestUrl.d.ts.map +1 -0
  85. package/dist/commonjs/tools/isochrone-tool/buildIsochroneRequestUrl.js +44 -0
  86. package/dist/commonjs/tools/isochrone-tool/buildIsochroneRequestUrl.js.map +1 -0
  87. package/dist/commonjs/tools/map-matching-tool/MapMatchingTool.d.ts.map +1 -1
  88. package/dist/commonjs/tools/map-matching-tool/MapMatchingTool.js +61 -34
  89. package/dist/commonjs/tools/map-matching-tool/MapMatchingTool.js.map +1 -1
  90. package/dist/commonjs/tools/map-matching-tool/MapMatchingTool.output.schema.d.ts +8 -5
  91. package/dist/commonjs/tools/map-matching-tool/MapMatchingTool.output.schema.d.ts.map +1 -1
  92. package/dist/commonjs/tools/map-matching-tool/MapMatchingTool.output.schema.js +11 -5
  93. package/dist/commonjs/tools/map-matching-tool/MapMatchingTool.output.schema.js.map +1 -1
  94. package/dist/commonjs/tools/map-matching-tool/buildMapMatchingRequestUrl.d.ts +25 -0
  95. package/dist/commonjs/tools/map-matching-tool/buildMapMatchingRequestUrl.d.ts.map +1 -0
  96. package/dist/commonjs/tools/map-matching-tool/buildMapMatchingRequestUrl.js +33 -0
  97. package/dist/commonjs/tools/map-matching-tool/buildMapMatchingRequestUrl.js.map +1 -0
  98. package/dist/commonjs/tools/optimization-tool/OptimizationTool.d.ts.map +1 -1
  99. package/dist/commonjs/tools/optimization-tool/OptimizationTool.input.schema.d.ts.map +1 -1
  100. package/dist/commonjs/tools/optimization-tool/OptimizationTool.input.schema.js +19 -4
  101. package/dist/commonjs/tools/optimization-tool/OptimizationTool.input.schema.js.map +1 -1
  102. package/dist/commonjs/tools/optimization-tool/OptimizationTool.js +32 -33
  103. package/dist/commonjs/tools/optimization-tool/OptimizationTool.js.map +1 -1
  104. package/dist/commonjs/tools/optimization-tool/OptimizationTool.output.schema.d.ts +3 -5
  105. package/dist/commonjs/tools/optimization-tool/OptimizationTool.output.schema.d.ts.map +1 -1
  106. package/dist/commonjs/tools/optimization-tool/OptimizationTool.output.schema.js +6 -1
  107. package/dist/commonjs/tools/optimization-tool/OptimizationTool.output.schema.js.map +1 -1
  108. package/dist/commonjs/tools/optimization-tool/buildOptimizationRequestUrl.d.ts +30 -0
  109. package/dist/commonjs/tools/optimization-tool/buildOptimizationRequestUrl.d.ts.map +1 -0
  110. package/dist/commonjs/tools/optimization-tool/buildOptimizationRequestUrl.js +45 -0
  111. package/dist/commonjs/tools/optimization-tool/buildOptimizationRequestUrl.js.map +1 -0
  112. package/dist/commonjs/tools/render-map-tool/RenderMapTool.d.ts +60 -0
  113. package/dist/commonjs/tools/render-map-tool/RenderMapTool.d.ts.map +1 -0
  114. package/dist/commonjs/tools/render-map-tool/RenderMapTool.input.schema.d.ts +41 -0
  115. package/dist/commonjs/tools/render-map-tool/RenderMapTool.input.schema.d.ts.map +1 -0
  116. package/dist/commonjs/tools/render-map-tool/RenderMapTool.input.schema.js +99 -0
  117. package/dist/commonjs/tools/render-map-tool/RenderMapTool.input.schema.js.map +1 -0
  118. package/dist/commonjs/tools/render-map-tool/RenderMapTool.js +246 -0
  119. package/dist/commonjs/tools/render-map-tool/RenderMapTool.js.map +1 -0
  120. package/dist/commonjs/tools/render-map-tool/RenderMapTool.output.schema.d.ts +97 -0
  121. package/dist/commonjs/tools/render-map-tool/RenderMapTool.output.schema.d.ts.map +1 -0
  122. package/dist/commonjs/tools/render-map-tool/RenderMapTool.output.schema.js +35 -0
  123. package/dist/commonjs/tools/render-map-tool/RenderMapTool.output.schema.js.map +1 -0
  124. package/dist/commonjs/tools/reverse-geocode-tool/ReverseGeocodeTool.input.schema.d.ts +1 -1
  125. package/dist/commonjs/tools/reverse-geocode-tool/ReverseGeocodeTool.output.schema.d.ts +3 -3
  126. package/dist/commonjs/tools/search-and-geocode-tool/SearchAndGeocodeTool.d.ts +10 -0
  127. package/dist/commonjs/tools/search-and-geocode-tool/SearchAndGeocodeTool.d.ts.map +1 -1
  128. package/dist/commonjs/tools/search-and-geocode-tool/SearchAndGeocodeTool.js +58 -48
  129. package/dist/commonjs/tools/search-and-geocode-tool/SearchAndGeocodeTool.js.map +1 -1
  130. package/dist/commonjs/tools/search-and-geocode-tool/SearchAndGeocodeTool.output.schema.d.ts +3 -0
  131. package/dist/commonjs/tools/search-and-geocode-tool/SearchAndGeocodeTool.output.schema.d.ts.map +1 -1
  132. package/dist/commonjs/tools/search-and-geocode-tool/SearchAndGeocodeTool.output.schema.js +3 -1
  133. package/dist/commonjs/tools/search-and-geocode-tool/SearchAndGeocodeTool.output.schema.js.map +1 -1
  134. package/dist/commonjs/tools/search-and-geocode-tool/buildSearchAndGeocodeRequestUrl.d.ts +37 -0
  135. package/dist/commonjs/tools/search-and-geocode-tool/buildSearchAndGeocodeRequestUrl.d.ts.map +1 -0
  136. package/dist/commonjs/tools/search-and-geocode-tool/buildSearchAndGeocodeRequestUrl.js +51 -0
  137. package/dist/commonjs/tools/search-and-geocode-tool/buildSearchAndGeocodeRequestUrl.js.map +1 -0
  138. package/dist/commonjs/tools/shared/polygonSchema.d.ts +15 -0
  139. package/dist/commonjs/tools/shared/polygonSchema.d.ts.map +1 -1
  140. package/dist/commonjs/tools/shared/polygonSchema.js +23 -2
  141. package/dist/commonjs/tools/shared/polygonSchema.js.map +1 -1
  142. package/dist/commonjs/tools/toolRegistry.d.ts +3 -2
  143. package/dist/commonjs/tools/toolRegistry.d.ts.map +1 -1
  144. package/dist/commonjs/tools/toolRegistry.js +2 -0
  145. package/dist/commonjs/tools/toolRegistry.js.map +1 -1
  146. package/dist/commonjs/tools/union-tool/UnionTool.d.ts.map +1 -1
  147. package/dist/commonjs/tools/union-tool/UnionTool.js +22 -3
  148. package/dist/commonjs/tools/union-tool/UnionTool.js.map +1 -1
  149. package/dist/commonjs/tools/union-tool/UnionTool.output.schema.d.ts +4 -1
  150. package/dist/commonjs/tools/union-tool/UnionTool.output.schema.d.ts.map +1 -1
  151. package/dist/commonjs/tools/union-tool/UnionTool.output.schema.js +7 -3
  152. package/dist/commonjs/tools/union-tool/UnionTool.output.schema.js.map +1 -1
  153. package/dist/commonjs/tools/union-tool/buildPolygonOpsMapPayload.d.ts +21 -0
  154. package/dist/commonjs/tools/union-tool/buildPolygonOpsMapPayload.d.ts.map +1 -0
  155. package/dist/commonjs/tools/union-tool/buildPolygonOpsMapPayload.js +72 -0
  156. package/dist/commonjs/tools/union-tool/buildPolygonOpsMapPayload.js.map +1 -0
  157. package/dist/commonjs/utils/computeRef.d.ts +38 -0
  158. package/dist/commonjs/utils/computeRef.d.ts.map +1 -0
  159. package/dist/commonjs/utils/computeRef.js +116 -0
  160. package/dist/commonjs/utils/computeRef.js.map +1 -0
  161. package/dist/commonjs/utils/inlinePayloadRef.d.ts +32 -0
  162. package/dist/commonjs/utils/inlinePayloadRef.d.ts.map +1 -0
  163. package/dist/commonjs/utils/inlinePayloadRef.js +56 -0
  164. package/dist/commonjs/utils/inlinePayloadRef.js.map +1 -0
  165. package/dist/commonjs/utils/mapAppPayload.d.ts +206 -0
  166. package/dist/commonjs/utils/mapAppPayload.d.ts.map +1 -0
  167. package/dist/commonjs/utils/mapAppPayload.js +129 -0
  168. package/dist/commonjs/utils/mapAppPayload.js.map +1 -0
  169. package/dist/commonjs/utils/selfFetchRef.d.ts +25 -0
  170. package/dist/commonjs/utils/selfFetchRef.d.ts.map +1 -0
  171. package/dist/commonjs/utils/selfFetchRef.js +75 -0
  172. package/dist/commonjs/utils/selfFetchRef.js.map +1 -0
  173. package/dist/commonjs/utils/storeMapPayload.d.ts +71 -0
  174. package/dist/commonjs/utils/storeMapPayload.d.ts.map +1 -0
  175. package/dist/commonjs/utils/storeMapPayload.js +153 -0
  176. package/dist/commonjs/utils/storeMapPayload.js.map +1 -0
  177. package/dist/commonjs/version.json +3 -3
  178. package/dist/esm/config/toolConfig.d.ts +0 -10
  179. package/dist/esm/config/toolConfig.d.ts.map +1 -1
  180. package/dist/esm/config/toolConfig.js +0 -35
  181. package/dist/esm/config/toolConfig.js.map +1 -1
  182. package/dist/esm/resources/compute/ComputeResource.d.ts +20 -0
  183. package/dist/esm/resources/compute/ComputeResource.d.ts.map +1 -0
  184. package/dist/esm/resources/compute/ComputeResource.js +45 -0
  185. package/dist/esm/resources/compute/ComputeResource.js.map +1 -0
  186. package/dist/esm/resources/inline-payload/InlinePayloadResource.d.ts +17 -0
  187. package/dist/esm/resources/inline-payload/InlinePayloadResource.d.ts.map +1 -0
  188. package/dist/esm/resources/inline-payload/InlinePayloadResource.js +42 -0
  189. package/dist/esm/resources/inline-payload/InlinePayloadResource.js.map +1 -0
  190. package/dist/esm/resources/resourceRegistry.d.ts +4 -2
  191. package/dist/esm/resources/resourceRegistry.d.ts.map +1 -1
  192. package/dist/esm/resources/resourceRegistry.js +16 -6
  193. package/dist/esm/resources/resourceRegistry.js.map +1 -1
  194. package/dist/esm/resources/ui-apps/MapAppUIResource.d.ts +27 -0
  195. package/dist/esm/resources/ui-apps/MapAppUIResource.d.ts.map +1 -0
  196. package/dist/esm/resources/ui-apps/{DirectionsAppUIResource.js → MapAppUIResource.js} +13 -15
  197. package/dist/esm/resources/ui-apps/MapAppUIResource.js.map +1 -0
  198. package/dist/esm/resources/ui-apps/mapAppHtml.d.ts +19 -0
  199. package/dist/esm/resources/ui-apps/mapAppHtml.d.ts.map +1 -0
  200. package/dist/esm/resources/ui-apps/mapAppHtml.js +1617 -0
  201. package/dist/esm/resources/ui-apps/mapAppHtml.js.map +1 -0
  202. package/dist/esm/tools/category-search-tool/CategorySearchTool.d.ts.map +1 -1
  203. package/dist/esm/tools/category-search-tool/CategorySearchTool.js +46 -41
  204. package/dist/esm/tools/category-search-tool/CategorySearchTool.js.map +1 -1
  205. package/dist/esm/tools/category-search-tool/CategorySearchTool.output.schema.d.ts +6 -3
  206. package/dist/esm/tools/category-search-tool/CategorySearchTool.output.schema.d.ts.map +1 -1
  207. package/dist/esm/tools/category-search-tool/CategorySearchTool.output.schema.js +3 -1
  208. package/dist/esm/tools/category-search-tool/CategorySearchTool.output.schema.js.map +1 -1
  209. package/dist/esm/tools/category-search-tool/buildCategorySearchRequestUrl.d.ts +30 -0
  210. package/dist/esm/tools/category-search-tool/buildCategorySearchRequestUrl.d.ts.map +1 -0
  211. package/dist/esm/tools/category-search-tool/buildCategorySearchRequestUrl.js +36 -0
  212. package/dist/esm/tools/category-search-tool/buildCategorySearchRequestUrl.js.map +1 -0
  213. package/dist/esm/tools/difference-tool/DifferenceTool.d.ts.map +1 -1
  214. package/dist/esm/tools/difference-tool/DifferenceTool.js +19 -3
  215. package/dist/esm/tools/difference-tool/DifferenceTool.js.map +1 -1
  216. package/dist/esm/tools/difference-tool/DifferenceTool.output.schema.d.ts +4 -1
  217. package/dist/esm/tools/difference-tool/DifferenceTool.output.schema.d.ts.map +1 -1
  218. package/dist/esm/tools/difference-tool/DifferenceTool.output.schema.js +7 -3
  219. package/dist/esm/tools/difference-tool/DifferenceTool.output.schema.js.map +1 -1
  220. package/dist/esm/tools/directions-tool/DirectionsTool.d.ts +0 -9
  221. package/dist/esm/tools/directions-tool/DirectionsTool.d.ts.map +1 -1
  222. package/dist/esm/tools/directions-tool/DirectionsTool.input.schema.d.ts.map +1 -1
  223. package/dist/esm/tools/directions-tool/DirectionsTool.input.schema.js +9 -4
  224. package/dist/esm/tools/directions-tool/DirectionsTool.input.schema.js.map +1 -1
  225. package/dist/esm/tools/directions-tool/DirectionsTool.js +41 -79
  226. package/dist/esm/tools/directions-tool/DirectionsTool.js.map +1 -1
  227. package/dist/esm/tools/directions-tool/DirectionsTool.output.schema.d.ts +4 -1
  228. package/dist/esm/tools/directions-tool/DirectionsTool.output.schema.d.ts.map +1 -1
  229. package/dist/esm/tools/directions-tool/DirectionsTool.output.schema.js +10 -4
  230. package/dist/esm/tools/directions-tool/DirectionsTool.output.schema.js.map +1 -1
  231. package/dist/esm/tools/directions-tool/buildDirectionsRequestUrl.d.ts +2 -2
  232. package/dist/esm/tools/directions-tool/buildDirectionsRequestUrl.d.ts.map +1 -1
  233. package/dist/esm/tools/directions-tool/buildDirectionsRequestUrl.js +4 -12
  234. package/dist/esm/tools/directions-tool/buildDirectionsRequestUrl.js.map +1 -1
  235. package/dist/esm/tools/ground-location-tool/GroundLocationTool.d.ts.map +1 -1
  236. package/dist/esm/tools/ground-location-tool/GroundLocationTool.js +52 -18
  237. package/dist/esm/tools/ground-location-tool/GroundLocationTool.js.map +1 -1
  238. package/dist/esm/tools/ground-location-tool/GroundLocationTool.output.schema.d.ts +4 -1
  239. package/dist/esm/tools/ground-location-tool/GroundLocationTool.output.schema.d.ts.map +1 -1
  240. package/dist/esm/tools/ground-location-tool/GroundLocationTool.output.schema.js +7 -3
  241. package/dist/esm/tools/ground-location-tool/GroundLocationTool.output.schema.js.map +1 -1
  242. package/dist/esm/tools/ground-location-tool/buildReverseGeocodeUrl.d.ts +19 -0
  243. package/dist/esm/tools/ground-location-tool/buildReverseGeocodeUrl.d.ts.map +1 -0
  244. package/dist/esm/tools/ground-location-tool/buildReverseGeocodeUrl.js +23 -0
  245. package/dist/esm/tools/ground-location-tool/buildReverseGeocodeUrl.js.map +1 -0
  246. package/dist/esm/tools/intersect-tool/IntersectTool.d.ts.map +1 -1
  247. package/dist/esm/tools/intersect-tool/IntersectTool.js +19 -3
  248. package/dist/esm/tools/intersect-tool/IntersectTool.js.map +1 -1
  249. package/dist/esm/tools/intersect-tool/IntersectTool.output.schema.d.ts +4 -1
  250. package/dist/esm/tools/intersect-tool/IntersectTool.output.schema.d.ts.map +1 -1
  251. package/dist/esm/tools/intersect-tool/IntersectTool.output.schema.js +7 -3
  252. package/dist/esm/tools/intersect-tool/IntersectTool.output.schema.js.map +1 -1
  253. package/dist/esm/tools/isochrone-tool/IsochroneTool.d.ts.map +1 -1
  254. package/dist/esm/tools/isochrone-tool/IsochroneTool.js +51 -47
  255. package/dist/esm/tools/isochrone-tool/IsochroneTool.js.map +1 -1
  256. package/dist/esm/tools/isochrone-tool/IsochroneTool.output.schema.d.ts +4 -5
  257. package/dist/esm/tools/isochrone-tool/IsochroneTool.output.schema.d.ts.map +1 -1
  258. package/dist/esm/tools/isochrone-tool/IsochroneTool.output.schema.js +7 -3
  259. package/dist/esm/tools/isochrone-tool/IsochroneTool.output.schema.js.map +1 -1
  260. package/dist/esm/tools/isochrone-tool/buildIsochroneRequestUrl.d.ts +28 -0
  261. package/dist/esm/tools/isochrone-tool/buildIsochroneRequestUrl.d.ts.map +1 -0
  262. package/dist/esm/tools/isochrone-tool/buildIsochroneRequestUrl.js +41 -0
  263. package/dist/esm/tools/isochrone-tool/buildIsochroneRequestUrl.js.map +1 -0
  264. package/dist/esm/tools/map-matching-tool/MapMatchingTool.d.ts.map +1 -1
  265. package/dist/esm/tools/map-matching-tool/MapMatchingTool.js +61 -34
  266. package/dist/esm/tools/map-matching-tool/MapMatchingTool.js.map +1 -1
  267. package/dist/esm/tools/map-matching-tool/MapMatchingTool.output.schema.d.ts +8 -5
  268. package/dist/esm/tools/map-matching-tool/MapMatchingTool.output.schema.d.ts.map +1 -1
  269. package/dist/esm/tools/map-matching-tool/MapMatchingTool.output.schema.js +11 -5
  270. package/dist/esm/tools/map-matching-tool/MapMatchingTool.output.schema.js.map +1 -1
  271. package/dist/esm/tools/map-matching-tool/buildMapMatchingRequestUrl.d.ts +25 -0
  272. package/dist/esm/tools/map-matching-tool/buildMapMatchingRequestUrl.d.ts.map +1 -0
  273. package/dist/esm/tools/map-matching-tool/buildMapMatchingRequestUrl.js +30 -0
  274. package/dist/esm/tools/map-matching-tool/buildMapMatchingRequestUrl.js.map +1 -0
  275. package/dist/esm/tools/optimization-tool/OptimizationTool.d.ts.map +1 -1
  276. package/dist/esm/tools/optimization-tool/OptimizationTool.input.schema.d.ts.map +1 -1
  277. package/dist/esm/tools/optimization-tool/OptimizationTool.input.schema.js +19 -4
  278. package/dist/esm/tools/optimization-tool/OptimizationTool.input.schema.js.map +1 -1
  279. package/dist/esm/tools/optimization-tool/OptimizationTool.js +32 -33
  280. package/dist/esm/tools/optimization-tool/OptimizationTool.js.map +1 -1
  281. package/dist/esm/tools/optimization-tool/OptimizationTool.output.schema.d.ts +3 -5
  282. package/dist/esm/tools/optimization-tool/OptimizationTool.output.schema.d.ts.map +1 -1
  283. package/dist/esm/tools/optimization-tool/OptimizationTool.output.schema.js +6 -1
  284. package/dist/esm/tools/optimization-tool/OptimizationTool.output.schema.js.map +1 -1
  285. package/dist/esm/tools/optimization-tool/buildOptimizationRequestUrl.d.ts +30 -0
  286. package/dist/esm/tools/optimization-tool/buildOptimizationRequestUrl.d.ts.map +1 -0
  287. package/dist/esm/tools/optimization-tool/buildOptimizationRequestUrl.js +42 -0
  288. package/dist/esm/tools/optimization-tool/buildOptimizationRequestUrl.js.map +1 -0
  289. package/dist/esm/tools/render-map-tool/RenderMapTool.d.ts +60 -0
  290. package/dist/esm/tools/render-map-tool/RenderMapTool.d.ts.map +1 -0
  291. package/dist/esm/tools/render-map-tool/RenderMapTool.input.schema.d.ts +41 -0
  292. package/dist/esm/tools/render-map-tool/RenderMapTool.input.schema.d.ts.map +1 -0
  293. package/dist/esm/tools/render-map-tool/RenderMapTool.input.schema.js +96 -0
  294. package/dist/esm/tools/render-map-tool/RenderMapTool.input.schema.js.map +1 -0
  295. package/dist/esm/tools/render-map-tool/RenderMapTool.js +242 -0
  296. package/dist/esm/tools/render-map-tool/RenderMapTool.js.map +1 -0
  297. package/dist/esm/tools/render-map-tool/RenderMapTool.output.schema.d.ts +97 -0
  298. package/dist/esm/tools/render-map-tool/RenderMapTool.output.schema.d.ts.map +1 -0
  299. package/dist/esm/tools/render-map-tool/RenderMapTool.output.schema.js +32 -0
  300. package/dist/esm/tools/render-map-tool/RenderMapTool.output.schema.js.map +1 -0
  301. package/dist/esm/tools/reverse-geocode-tool/ReverseGeocodeTool.input.schema.d.ts +1 -1
  302. package/dist/esm/tools/reverse-geocode-tool/ReverseGeocodeTool.output.schema.d.ts +3 -3
  303. package/dist/esm/tools/search-and-geocode-tool/SearchAndGeocodeTool.d.ts +10 -0
  304. package/dist/esm/tools/search-and-geocode-tool/SearchAndGeocodeTool.d.ts.map +1 -1
  305. package/dist/esm/tools/search-and-geocode-tool/SearchAndGeocodeTool.js +58 -48
  306. package/dist/esm/tools/search-and-geocode-tool/SearchAndGeocodeTool.js.map +1 -1
  307. package/dist/esm/tools/search-and-geocode-tool/SearchAndGeocodeTool.output.schema.d.ts +3 -0
  308. package/dist/esm/tools/search-and-geocode-tool/SearchAndGeocodeTool.output.schema.d.ts.map +1 -1
  309. package/dist/esm/tools/search-and-geocode-tool/SearchAndGeocodeTool.output.schema.js +3 -1
  310. package/dist/esm/tools/search-and-geocode-tool/SearchAndGeocodeTool.output.schema.js.map +1 -1
  311. package/dist/esm/tools/search-and-geocode-tool/buildSearchAndGeocodeRequestUrl.d.ts +37 -0
  312. package/dist/esm/tools/search-and-geocode-tool/buildSearchAndGeocodeRequestUrl.d.ts.map +1 -0
  313. package/dist/esm/tools/search-and-geocode-tool/buildSearchAndGeocodeRequestUrl.js +48 -0
  314. package/dist/esm/tools/search-and-geocode-tool/buildSearchAndGeocodeRequestUrl.js.map +1 -0
  315. package/dist/esm/tools/shared/polygonSchema.d.ts +15 -0
  316. package/dist/esm/tools/shared/polygonSchema.d.ts.map +1 -1
  317. package/dist/esm/tools/shared/polygonSchema.js +23 -2
  318. package/dist/esm/tools/shared/polygonSchema.js.map +1 -1
  319. package/dist/esm/tools/toolRegistry.d.ts +3 -2
  320. package/dist/esm/tools/toolRegistry.d.ts.map +1 -1
  321. package/dist/esm/tools/toolRegistry.js +2 -0
  322. package/dist/esm/tools/toolRegistry.js.map +1 -1
  323. package/dist/esm/tools/union-tool/UnionTool.d.ts.map +1 -1
  324. package/dist/esm/tools/union-tool/UnionTool.js +22 -3
  325. package/dist/esm/tools/union-tool/UnionTool.js.map +1 -1
  326. package/dist/esm/tools/union-tool/UnionTool.output.schema.d.ts +4 -1
  327. package/dist/esm/tools/union-tool/UnionTool.output.schema.d.ts.map +1 -1
  328. package/dist/esm/tools/union-tool/UnionTool.output.schema.js +7 -3
  329. package/dist/esm/tools/union-tool/UnionTool.output.schema.js.map +1 -1
  330. package/dist/esm/tools/union-tool/buildPolygonOpsMapPayload.d.ts +21 -0
  331. package/dist/esm/tools/union-tool/buildPolygonOpsMapPayload.d.ts.map +1 -0
  332. package/dist/esm/tools/union-tool/buildPolygonOpsMapPayload.js +69 -0
  333. package/dist/esm/tools/union-tool/buildPolygonOpsMapPayload.js.map +1 -0
  334. package/dist/esm/utils/computeRef.d.ts +38 -0
  335. package/dist/esm/utils/computeRef.d.ts.map +1 -0
  336. package/dist/esm/utils/computeRef.js +111 -0
  337. package/dist/esm/utils/computeRef.js.map +1 -0
  338. package/dist/esm/utils/inlinePayloadRef.d.ts +32 -0
  339. package/dist/esm/utils/inlinePayloadRef.d.ts.map +1 -0
  340. package/dist/esm/utils/inlinePayloadRef.js +51 -0
  341. package/dist/esm/utils/inlinePayloadRef.js.map +1 -0
  342. package/dist/esm/utils/mapAppPayload.d.ts +206 -0
  343. package/dist/esm/utils/mapAppPayload.d.ts.map +1 -0
  344. package/dist/esm/utils/mapAppPayload.js +124 -0
  345. package/dist/esm/utils/mapAppPayload.js.map +1 -0
  346. package/dist/esm/utils/selfFetchRef.d.ts +25 -0
  347. package/dist/esm/utils/selfFetchRef.d.ts.map +1 -0
  348. package/dist/esm/utils/selfFetchRef.js +70 -0
  349. package/dist/esm/utils/selfFetchRef.js.map +1 -0
  350. package/dist/esm/utils/storeMapPayload.d.ts +71 -0
  351. package/dist/esm/utils/storeMapPayload.d.ts.map +1 -0
  352. package/dist/esm/utils/storeMapPayload.js +145 -0
  353. package/dist/esm/utils/storeMapPayload.js.map +1 -0
  354. package/dist/esm/version.json +3 -3
  355. package/package.json +2 -2
  356. package/dist/commonjs/resources/ui-apps/DirectionsAppUIResource.d.ts +0 -29
  357. package/dist/commonjs/resources/ui-apps/DirectionsAppUIResource.d.ts.map +0 -1
  358. package/dist/commonjs/resources/ui-apps/DirectionsAppUIResource.js.map +0 -1
  359. package/dist/commonjs/resources/ui-apps/directionsAppHtml.d.ts +0 -45
  360. package/dist/commonjs/resources/ui-apps/directionsAppHtml.d.ts.map +0 -1
  361. package/dist/commonjs/resources/ui-apps/directionsAppHtml.js +0 -640
  362. package/dist/commonjs/resources/ui-apps/directionsAppHtml.js.map +0 -1
  363. package/dist/esm/resources/ui-apps/DirectionsAppUIResource.d.ts +0 -29
  364. package/dist/esm/resources/ui-apps/DirectionsAppUIResource.d.ts.map +0 -1
  365. package/dist/esm/resources/ui-apps/DirectionsAppUIResource.js.map +0 -1
  366. package/dist/esm/resources/ui-apps/directionsAppHtml.d.ts +0 -45
  367. package/dist/esm/resources/ui-apps/directionsAppHtml.d.ts.map +0 -1
  368. package/dist/esm/resources/ui-apps/directionsAppHtml.js +0 -636
  369. package/dist/esm/resources/ui-apps/directionsAppHtml.js.map +0 -1
@@ -0,0 +1,1617 @@
1
+ // Copyright (c) Mapbox, Inc.
2
+ // Licensed under the MIT License.
3
+ /**
4
+ * Render the generic Mapbox MCP App HTML — used by both the MCP Apps
5
+ * resource (postMessage delivery) and any tool's inline MCP-UI rawHtml
6
+ * block (initial-data baked in).
7
+ *
8
+ * The iframe is a thin renderer over Mapbox GL JS. Tools produce a
9
+ * `MapAppPayload` (see src/utils/mapAppPayload.ts) and the iframe
10
+ * translates each layer/marker/legend entry into the corresponding
11
+ * GL JS call. No tool-specific code lives in this file.
12
+ */
13
+ // Verified against the current latest stable release (3.26.0, as of this
14
+ // writing) in a real browser: Map/NavigationControl/addSource/addLayer/
15
+ // Marker/Popup/fitBounds/flyTo all work with zero console errors, so there's
16
+ // no reason to stay pinned to an old version.
17
+ export const MAPBOX_GL_VERSION = '3.26.0';
18
+ export function renderMapAppHtml(params) {
19
+ const { publicToken, initialData } = params;
20
+ const apiEndpoint = params.apiEndpoint ?? 'https://api.mapbox.com/';
21
+ const glVersion = params.glVersion ?? MAPBOX_GL_VERSION;
22
+ const initialDataScript = initialData
23
+ ? `<script id="initial-data" type="application/json">${escapeForScript(JSON.stringify(initialData))}</script>`
24
+ : '';
25
+ return `<!DOCTYPE html>
26
+ <html lang="en">
27
+ <head>
28
+ <meta charset="UTF-8">
29
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
30
+ <title>Map</title>
31
+ <link href="https://api.mapbox.com/mapbox-gl-js/v${glVersion}/mapbox-gl.css" rel="stylesheet">
32
+ <script src="https://api.mapbox.com/mapbox-gl-js/v${glVersion}/mapbox-gl.js"></script>
33
+ <style>
34
+ * { margin: 0; padding: 0; box-sizing: border-box; }
35
+ html, body { width: 100%; height: 100%; overflow: hidden;
36
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
37
+ #map { position: absolute; inset: 0; }
38
+ #summary {
39
+ position: absolute; top: 12px; left: 12px; z-index: 10;
40
+ background: rgba(15, 23, 42, 0.88); color: #f1f5f9;
41
+ padding: 8px 12px; border-radius: 6px;
42
+ font-size: 13px; font-weight: 500;
43
+ box-shadow: 0 2px 8px rgba(0,0,0,0.3);
44
+ max-width: calc(100% - 24px); white-space: pre-line;
45
+ }
46
+ #legend {
47
+ position: absolute; bottom: 12px; left: 12px; z-index: 10;
48
+ background: rgba(15, 23, 42, 0.88); color: #f1f5f9;
49
+ padding: 8px 12px; border-radius: 6px;
50
+ font-size: 11px;
51
+ }
52
+ #legend .row { display: flex; align-items: center; margin: 2px 0; }
53
+ #legend .swatch {
54
+ display: inline-block; width: 14px; height: 14px;
55
+ margin-right: 6px; border-radius: 3px;
56
+ }
57
+ #loading {
58
+ position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
59
+ color: #666; font-size: 16px; z-index: 10; pointer-events: none;
60
+ }
61
+ #error {
62
+ position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
63
+ color: #d32f2f; background: #ffebee; border-radius: 8px;
64
+ padding: 20px; max-width: 400px; text-align: center; z-index: 10;
65
+ }
66
+ .marker-badge {
67
+ width: 28px; height: 28px; border-radius: 50%;
68
+ display: flex; align-items: center; justify-content: center;
69
+ color: #fff; font-size: 13px; font-weight: 700;
70
+ border: 2px solid #fff;
71
+ box-shadow: 0 2px 6px rgba(0,0,0,0.4);
72
+ cursor: pointer;
73
+ }
74
+ </style>
75
+ </head>
76
+ <body>
77
+ <div id="map"></div>
78
+ <div id="summary" style="display:none"></div>
79
+ <div id="legend" style="display:none"></div>
80
+ <div id="loading">Loading…</div>
81
+ <div id="error" style="display:none"></div>
82
+ ${initialDataScript}
83
+
84
+ <script>
85
+ (function() {
86
+ var TOKEN = ${JSON.stringify(publicToken)};
87
+ var API_ENDPOINT = ${JSON.stringify(apiEndpoint)};
88
+
89
+ var loadingEl = document.getElementById('loading');
90
+ var errorEl = document.getElementById('error');
91
+ var summaryEl = document.getElementById('summary');
92
+ var legendEl = document.getElementById('legend');
93
+
94
+ var map = null;
95
+ var mapLoaded = false;
96
+ var pendingPayload = null;
97
+ var currentDisplayMode = 'inline';
98
+
99
+ // Tracks what we've added so re-renders can tear down cleanly.
100
+ var trackedLayerIds = [];
101
+ var trackedSourceIds = [];
102
+ var trackedMarkers = [];
103
+
104
+ // --- MCP App postMessage protocol -----------------------------------------
105
+ var messageId = 0;
106
+ var pendingRequests = new Map();
107
+
108
+ function sendRequest(method, params) {
109
+ var id = ++messageId;
110
+ try {
111
+ window.parent.postMessage({
112
+ jsonrpc: '2.0', id: id, method: method, params: params || {}
113
+ }, '*');
114
+ } catch (_) {}
115
+ return new Promise(function(resolve, reject) {
116
+ pendingRequests.set(id, { resolve: resolve, reject: reject });
117
+ });
118
+ }
119
+ function sendNotification(method, params) {
120
+ try {
121
+ window.parent.postMessage({
122
+ jsonrpc: '2.0', method: method, params: params || {}
123
+ }, '*');
124
+ } catch (_) {}
125
+ }
126
+ function requestSizeToFit() {
127
+ if (currentDisplayMode !== 'inline') return;
128
+ sendNotification('ui/notifications/size-changed', { height: 500 });
129
+ }
130
+
131
+ window.addEventListener('message', function(event) {
132
+ var message = event.data;
133
+ if (!message || typeof message !== 'object') return;
134
+ if (message.id !== undefined && pendingRequests.has(message.id)) {
135
+ var handlers = pendingRequests.get(message.id);
136
+ pendingRequests.delete(message.id);
137
+ if (message.error) handlers.reject(new Error(message.error.message));
138
+ else handlers.resolve(message.result);
139
+ return;
140
+ }
141
+ if (message.method === 'ui/notifications/tool-result' && message.params) {
142
+ handleToolResult(message.params);
143
+ }
144
+ if (message.method === 'ui/notifications/host-context-changed' &&
145
+ message.params && message.params.displayMode) {
146
+ currentDisplayMode = message.params.displayMode;
147
+ if (map) setTimeout(function() { map.resize(); }, 100);
148
+ }
149
+ });
150
+
151
+ sendRequest('ui/initialize', {
152
+ protocolVersion: '2026-01-26',
153
+ appCapabilities: {},
154
+ appInfo: { name: 'Mapbox Map', version: '1.0.0' }
155
+ }).then(
156
+ function() { sendNotification('ui/notifications/initialized', {}); },
157
+ function() { sendNotification('ui/notifications/initialized', {}); }
158
+ );
159
+
160
+ // --- Map setup ------------------------------------------------------------
161
+ function initMap() {
162
+ if (!TOKEN) {
163
+ showError('No Mapbox public token available. Set MAPBOX_PUBLIC_TOKEN or grant tokens:read to the OAuth client.');
164
+ return;
165
+ }
166
+ if (typeof mapboxgl === 'undefined') {
167
+ showError('Mapbox GL JS failed to load.');
168
+ return;
169
+ }
170
+ mapboxgl.accessToken = TOKEN;
171
+ map = new mapboxgl.Map({
172
+ container: 'map',
173
+ style: 'mapbox://styles/mapbox/streets-v12',
174
+ center: [0, 20],
175
+ zoom: 1.5
176
+ });
177
+ map.addControl(new mapboxgl.NavigationControl(), 'top-left');
178
+ map.on('load', function() {
179
+ mapLoaded = true;
180
+ if (pendingPayload) {
181
+ render(pendingPayload);
182
+ pendingPayload = null;
183
+ } else {
184
+ consumeInitialData();
185
+ }
186
+ });
187
+ }
188
+ initMap();
189
+
190
+ function consumeInitialData() {
191
+ var el = document.getElementById('initial-data');
192
+ if (!el || !el.textContent) return;
193
+ try {
194
+ var data = JSON.parse(el.textContent);
195
+ if (data && Array.isArray(data.layers)) render(data);
196
+ } catch (_) { /* ignore */ }
197
+ }
198
+
199
+ // --- Tool result extraction ----------------------------------------------
200
+ // Inline payload is checked FIRST, before the ref: some hosts (e.g.
201
+ // ChatGPT's MCP Apps bridge) deliver structuredContent to the iframe
202
+ // intact but have no resources/read equivalent at all, so a ref there
203
+ // would be permanently unusable even though the actual payload data is
204
+ // sitting right there in the same tool result. Hosts that strip
205
+ // structuredContent (e.g. Claude Desktop) never have inline data to find
206
+ // here, so they fall through to the ref path exactly as before.
207
+ function handleToolResult(result) {
208
+ var payload = extractInlinePayload(result);
209
+ if (payload) {
210
+ stageRender(payload);
211
+ return;
212
+ }
213
+ var ref = extractPayloadRef(result);
214
+ if (ref) {
215
+ sendRequest('resources/read', { uri: ref }).then(
216
+ function(rr) {
217
+ // The server marks an expired/unknown ref with mimeType
218
+ // text/plain and a human-readable explanation (see
219
+ // TemporaryDataResource) instead of the usual application/json
220
+ // payload. Surface that explanation directly — e.g. after the
221
+ // conversation is rehydrated the ref may be minutes/hours stale
222
+ // — rather than a generic "malformed" message that gives no clue
223
+ // what happened or what to do about it.
224
+ var expired = readResourceExpiryMessage(rr);
225
+ if (expired) {
226
+ showError(expired + ' Ask again to regenerate the map.');
227
+ return;
228
+ }
229
+ var fetched = readResourceJson(rr);
230
+ if (fetched && looksLikePayload(fetched)) stageRender(fetched);
231
+ else showError('Map payload was empty or malformed.');
232
+ },
233
+ function(err) {
234
+ showError('Could not read map payload: ' +
235
+ (err && err.message ? err.message : err));
236
+ }
237
+ );
238
+ return;
239
+ }
240
+ showError('Tool result did not contain a map payload.');
241
+ }
242
+
243
+ function readResourceExpiryMessage(rr) {
244
+ if (!rr || !Array.isArray(rr.contents) || rr.contents.length === 0) return null;
245
+ var first = rr.contents[0];
246
+ if (first && first.mimeType === 'text/plain' && typeof first.text === 'string') {
247
+ return first.text;
248
+ }
249
+ return null;
250
+ }
251
+
252
+ // Claude Desktop strips structuredContent from tool-result postMessages
253
+ // before forwarding to MCP App iframes — only content and isError
254
+ // survive. So the ref has to ride inside a content[] text item, prefixed
255
+ // with a sentinel the iframe recognizes.
256
+ var REF_SENTINEL = '[[MAPBOX_RENDER_REF]]';
257
+ // Matches either render_map_tool output ref scheme: the legacy
258
+ // server-stored kind, or the self-describing mapbox://inline/ kind (see
259
+ // inlinePayloadRef.ts) it now prefers whenever the payload is small
260
+ // enough to carry inline — a [?] character class matches the literal
261
+ // query-string separator without needing an escaped-question-mark
262
+ // sequence, which this JS-in-a-TS-template-literal file would otherwise
263
+ // have to double-escape.
264
+ var REF_URI_RE = new RegExp(
265
+ 'mapbox://(?:temp/map-payload-[0-9a-fA-F-]+|inline/payload[?]data=[A-Za-z0-9_-]+)'
266
+ );
267
+
268
+ function extractPayloadRef(result) {
269
+ if (!result) return null;
270
+ // structuredContent path: spec-compliant, kept as fallback for hosts
271
+ // that DO forward structuredContent.
272
+ var sc = result.structuredContent;
273
+ if (sc && sc.mapboxRender && typeof sc.mapboxRender.ref === 'string') {
274
+ return sc.mapboxRender.ref;
275
+ }
276
+ // content[] path: scan text items for the sentinel + ref URI.
277
+ if (result.content && result.content.length) {
278
+ for (var i = 0; i < result.content.length; i++) {
279
+ var c = result.content[i];
280
+ if (c && c.type === 'text' && typeof c.text === 'string' &&
281
+ c.text.indexOf(REF_SENTINEL) !== -1) {
282
+ var m = c.text.match(REF_URI_RE);
283
+ if (m) return m[0];
284
+ }
285
+ }
286
+ }
287
+ return null;
288
+ }
289
+
290
+ function extractInlinePayload(result) {
291
+ if (!result) return null;
292
+ var sc = result.structuredContent;
293
+ if (sc && sc.mapboxRender && looksLikePayload(sc.mapboxRender)) {
294
+ return sc.mapboxRender;
295
+ }
296
+ if (result._meta && result._meta.ui && looksLikePayload(result._meta.ui.payload)) {
297
+ return result._meta.ui.payload;
298
+ }
299
+ return null;
300
+ }
301
+
302
+ function looksLikePayload(p) {
303
+ return p && typeof p === 'object' &&
304
+ (Array.isArray(p.layers) || Array.isArray(p.markers));
305
+ }
306
+
307
+ function stageRender(payload) {
308
+ if (payload.summary) {
309
+ summaryEl.textContent = payload.summary;
310
+ summaryEl.style.display = 'block';
311
+ } else {
312
+ summaryEl.style.display = 'none';
313
+ }
314
+ if (!map) { loadingEl.style.display = 'none'; return; }
315
+ if (mapLoaded) render(payload);
316
+ else pendingPayload = payload;
317
+ }
318
+
319
+ function showError(message) {
320
+ loadingEl.style.display = 'none';
321
+ errorEl.textContent = message;
322
+ errorEl.style.display = 'block';
323
+ }
324
+
325
+ // --- Renderer -------------------------------------------------------------
326
+ function teardown() {
327
+ trackedLayerIds.forEach(function(id) {
328
+ if (map.getLayer(id)) map.removeLayer(id);
329
+ });
330
+ trackedSourceIds.forEach(function(id) {
331
+ if (map.getSource(id)) map.removeSource(id);
332
+ });
333
+ trackedMarkers.forEach(function(m) { m.remove(); });
334
+ trackedLayerIds = [];
335
+ trackedSourceIds = [];
336
+ trackedMarkers = [];
337
+ }
338
+
339
+ function bboxAccumulator() {
340
+ var minLng = Infinity, minLat = Infinity;
341
+ var maxLng = -Infinity, maxLat = -Infinity;
342
+ return {
343
+ extend: function(coords) {
344
+ if (typeof coords[0] === 'number') {
345
+ if (coords[0] < minLng) minLng = coords[0];
346
+ if (coords[1] < minLat) minLat = coords[1];
347
+ if (coords[0] > maxLng) maxLng = coords[0];
348
+ if (coords[1] > maxLat) maxLat = coords[1];
349
+ } else {
350
+ for (var i = 0; i < coords.length; i++) this.extend(coords[i]);
351
+ }
352
+ },
353
+ bounds: function() {
354
+ if (!isFinite(minLng)) return null;
355
+ return [[minLng, minLat], [maxLng, maxLat]];
356
+ }
357
+ };
358
+ }
359
+
360
+ var STYLE_DEFAULTS = {
361
+ pin: { color: '#3b82f6' },
362
+ numbered: { color: '#2563eb' },
363
+ start: { color: '#22c55e' },
364
+ end: { color: '#ef4444' }
365
+ };
366
+
367
+ function buildBadgeElement(label, color) {
368
+ var el = document.createElement('div');
369
+ el.className = 'marker-badge';
370
+ el.style.background = color;
371
+ el.textContent = label;
372
+ return el;
373
+ }
374
+
375
+ // Shared by render() (full rebuild) and mergeAdditionalPayload() (adds
376
+ // on top of what's already drawn, for self-fetched layers that resolve
377
+ // after the initial render).
378
+ function addOneLayer(layer, bbox) {
379
+ if (!layer || !layer.id || !layer.data) return;
380
+ map.addSource(layer.id, { type: 'geojson', data: layer.data });
381
+ trackedSourceIds.push(layer.id);
382
+
383
+ var def = {
384
+ id: layer.id,
385
+ type: layer.type,
386
+ source: layer.id
387
+ };
388
+ if (layer.paint) def.paint = layer.paint;
389
+ if (layer.layout) def.layout = layer.layout;
390
+ map.addLayer(def);
391
+ trackedLayerIds.push(layer.id);
392
+
393
+ var feature = layer.data;
394
+ if (feature.type === 'FeatureCollection' && Array.isArray(feature.features)) {
395
+ feature.features.forEach(function(f) {
396
+ if (f && f.geometry) bbox.extend(f.geometry.coordinates);
397
+ });
398
+ } else if (feature.geometry) {
399
+ bbox.extend(feature.geometry.coordinates);
400
+ }
401
+ }
402
+
403
+ function addOneMarker(m, bbox) {
404
+ if (!m || !Array.isArray(m.coordinates) || m.coordinates.length < 2 ||
405
+ typeof m.coordinates[0] !== 'number' ||
406
+ typeof m.coordinates[1] !== 'number') return;
407
+ var style = m.style || 'pin';
408
+ var color = m.color ||
409
+ (STYLE_DEFAULTS[style] && STYLE_DEFAULTS[style].color) || '#3b82f6';
410
+ var marker;
411
+ if (style === 'numbered' || style === 'start' || style === 'end') {
412
+ var label = m.label || (style === 'start' ? 'S' : style === 'end' ? 'E' : '');
413
+ marker = new mapboxgl.Marker({ element: buildBadgeElement(label, color) });
414
+ } else {
415
+ marker = new mapboxgl.Marker({ color: color });
416
+ }
417
+ marker.setLngLat(m.coordinates);
418
+ if (m.popup) marker.setPopup(new mapboxgl.Popup().setText(m.popup));
419
+ marker.addTo(map);
420
+ trackedMarkers.push(marker);
421
+ bbox.extend(m.coordinates);
422
+ }
423
+
424
+ function fitToBounds(bounds) {
425
+ setTimeout(function() {
426
+ map.resize();
427
+ map.fitBounds(bounds, {
428
+ padding: { top: 70, bottom: 50, left: 30, right: 30 },
429
+ duration: 600
430
+ });
431
+ }, 60);
432
+ }
433
+
434
+ // Adds a follow-up payload's layers/markers on top of what's already on
435
+ // the map (does NOT teardown first) and refits to include the new
436
+ // content. Used once a self-fetched layer (e.g. a directions route)
437
+ // resolves after the initial render.
438
+ function mergeAdditionalPayload(payload) {
439
+ if (!map) return;
440
+ var bbox = bboxAccumulator();
441
+ (Array.isArray(payload.layers) ? payload.layers : []).forEach(function(layer) {
442
+ addOneLayer(layer, bbox);
443
+ });
444
+ (Array.isArray(payload.markers) ? payload.markers : []).forEach(function(m) {
445
+ addOneMarker(m, bbox);
446
+ });
447
+ if (payload.summary && summaryEl.style.display === 'none') {
448
+ summaryEl.textContent = payload.summary;
449
+ summaryEl.style.display = 'block';
450
+ }
451
+ var bounds = bbox.bounds();
452
+ if (bounds) fitToBounds(bounds);
453
+ }
454
+
455
+ function render(payload) {
456
+ if (!map) return;
457
+ teardown();
458
+
459
+ var bbox = bboxAccumulator();
460
+ var layers = Array.isArray(payload.layers) ? payload.layers : [];
461
+ var markers = Array.isArray(payload.markers) ? payload.markers : [];
462
+
463
+ layers.forEach(function(layer) { addOneLayer(layer, bbox); });
464
+ markers.forEach(function(m) { addOneMarker(m, bbox); });
465
+
466
+ // Legend
467
+ if (Array.isArray(payload.legend) && payload.legend.length > 0) {
468
+ legendEl.innerHTML = payload.legend.map(function(row) {
469
+ var op = (typeof row.opacity === 'number') ? row.opacity : 1;
470
+ return '<div class="row"><span class="swatch" style="background:' +
471
+ escapeAttr(row.color) + ';opacity:' + op + '"></span>' +
472
+ escapeText(row.label) + '</div>';
473
+ }).join('');
474
+ legendEl.style.display = 'block';
475
+ } else {
476
+ legendEl.style.display = 'none';
477
+ }
478
+
479
+ loadingEl.style.display = 'none';
480
+ requestSizeToFit();
481
+
482
+ // Camera: explicit camera takes precedence; otherwise auto-fit.
483
+ var camera = payload.camera || {};
484
+ if (camera.bounds) {
485
+ fitToBounds(camera.bounds);
486
+ } else if (camera.center) {
487
+ setTimeout(function() {
488
+ map.resize();
489
+ map.flyTo({
490
+ center: camera.center,
491
+ zoom: (typeof camera.zoom === 'number') ? camera.zoom : 12,
492
+ duration: 600
493
+ });
494
+ }, 60);
495
+ } else {
496
+ var bounds = bbox.bounds();
497
+ if (bounds) fitToBounds(bounds);
498
+ }
499
+
500
+ // Deferred large-geometry fetch.
501
+ if (payload.defer && typeof payload.defer.resourceUri === 'string' &&
502
+ typeof payload.defer.layerId === 'string') {
503
+ sendRequest('resources/read', { uri: payload.defer.resourceUri }).then(
504
+ function(rr) {
505
+ var data = readResourceJson(rr);
506
+ if (!data) return;
507
+ var src = map.getSource(payload.defer.layerId);
508
+ if (src && typeof src.setData === 'function') src.setData(data);
509
+ },
510
+ function() { /* ignore — keep what we rendered inline */ }
511
+ );
512
+ }
513
+
514
+ // Self-fetched layers (e.g. a directions route, isochrone contours, a
515
+ // map-matched trace, or search results): the ref only carried the
516
+ // tool's own input params, so fetch and draw them ourselves now that
517
+ // the map is up, rather than depend on server-computed geometry.
518
+ if (Array.isArray(payload.selfFetch)) {
519
+ payload.selfFetch.forEach(function(sf) {
520
+ if (!sf) return;
521
+ if (sf.tool === 'directions') selfFetchDirections(sf.params);
522
+ else if (sf.tool === 'isochrone') selfFetchIsochrone(sf.params);
523
+ else if (sf.tool === 'map_matching') selfFetchMapMatching(sf.params);
524
+ else if (sf.tool === 'search') selfFetchSearch(sf.params);
525
+ else if (sf.tool === 'category_search') selfFetchCategorySearch(sf.params);
526
+ else if (sf.tool === 'optimization') selfFetchOptimization(sf.params);
527
+ else if (sf.tool === 'ground_location') selfFetchGroundLocation(sf.params);
528
+ });
529
+ }
530
+ }
531
+
532
+ function readResourceJson(rr) {
533
+ if (!rr || !Array.isArray(rr.contents) || rr.contents.length === 0) return null;
534
+ var first = rr.contents[0];
535
+ if (!first || typeof first.text !== 'string') return null;
536
+ try { return JSON.parse(first.text); } catch (_) { return null; }
537
+ }
538
+
539
+ // --- Self-fetch: directions ------------------------------------------------
540
+ // Builds a Directions API request directly from directions_tool's own
541
+ // input params and fetches the route ourselves, using the same public
542
+ // token GL JS already uses for tiles — so this layer never depends on
543
+ // server-side storage that a restart or a rehydrated conversation could
544
+ // invalidate (see src/utils/selfFetchRef.ts).
545
+ //
546
+ // Mirrors src/tools/directions-tool/buildDirectionsRequestUrl.ts — kept
547
+ // in sync by the parity test in
548
+ // test/resources/ui-apps/directionsSelfFetchUrlParity.test.ts. Always
549
+ // forces geometries=geojson regardless of what directions_tool's own
550
+ // call requested.
551
+ function formatIsoDateTimeClient(dateTime) {
552
+ var noTzWithSeconds = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}$/;
553
+ if (noTzWithSeconds.test(dateTime)) {
554
+ return dateTime.substring(0, dateTime.lastIndexOf(':'));
555
+ }
556
+ return dateTime;
557
+ }
558
+
559
+ function buildDirectionsApiUrl(params, publicToken, apiEndpoint) {
560
+ var coords = params.coordinates
561
+ .map(function(c) { return c.longitude + ',' + c.latitude; })
562
+ .join(';');
563
+ var encodedCoords = encodeURIComponent(coords);
564
+ var profile = params.routing_profile || 'mapbox/driving-traffic';
565
+
566
+ var qp = new URLSearchParams();
567
+ qp.append('access_token', publicToken);
568
+ qp.append('geometries', 'geojson');
569
+ qp.append('alternatives', params.alternatives ? 'true' : 'false');
570
+ qp.append(
571
+ 'annotations',
572
+ profile === 'mapbox/driving-traffic'
573
+ ? 'distance,congestion,speed'
574
+ : 'distance,speed'
575
+ );
576
+ qp.append('overview', 'full');
577
+
578
+ if (params.depart_at) {
579
+ qp.append('depart_at', formatIsoDateTimeClient(params.depart_at));
580
+ } else if (params.arrive_by) {
581
+ qp.append('arrive_by', formatIsoDateTimeClient(params.arrive_by));
582
+ }
583
+ if (params.max_height !== undefined && params.max_height !== null) {
584
+ qp.append('max_height', String(params.max_height));
585
+ }
586
+ if (params.max_width !== undefined && params.max_width !== null) {
587
+ qp.append('max_width', String(params.max_width));
588
+ }
589
+ if (params.max_weight !== undefined && params.max_weight !== null) {
590
+ qp.append('max_weight', String(params.max_weight));
591
+ }
592
+ qp.append('steps', 'true');
593
+ if (params.exclude) {
594
+ qp.append('exclude', params.exclude);
595
+ }
596
+
597
+ return apiEndpoint + 'directions/v5/' + profile + '/' + encodedCoords + '?' + qp.toString();
598
+ }
599
+ // Exposed so the parity test (Node vm sandbox) can call this in isolation.
600
+ window.__buildDirectionsApiUrl = buildDirectionsApiUrl;
601
+
602
+ // params came from the ref (attacker-controlled if a sibling frame could
603
+ // forge postMessages) and get interpolated into the request URL.
604
+ // coordinates and exclude are both passed through URLSearchParams above,
605
+ // which percent-encodes them safely regardless of content — routing_profile
606
+ // is the one value spliced into the URL *path* unencoded, so its shape is
607
+ // restricted here. The exclude check below is defense-in-depth against a
608
+ // malformed ref, not a compensating control for an encoding gap.
609
+ function isSafeDirectionsParams(params) {
610
+ if (!params || !Array.isArray(params.coordinates)) return false;
611
+ if (params.coordinates.length < 2) return false;
612
+ for (var i = 0; i < params.coordinates.length; i++) {
613
+ var c = params.coordinates[i];
614
+ if (!c || typeof c.longitude !== 'number' || !isFinite(c.longitude)) return false;
615
+ if (typeof c.latitude !== 'number' || !isFinite(c.latitude)) return false;
616
+ }
617
+ if (params.routing_profile !== undefined && params.routing_profile !== null) {
618
+ if (typeof params.routing_profile !== 'string') return false;
619
+ if (!/^mapbox\\/[a-z-]+$/.test(params.routing_profile)) return false;
620
+ }
621
+ if (params.exclude !== undefined && params.exclude !== null) {
622
+ if (typeof params.exclude !== 'string') return false;
623
+ if (!/^[A-Za-z0-9_,.() -]+$/.test(params.exclude)) return false;
624
+ }
625
+ return true;
626
+ }
627
+
628
+ // Standard Google/Mapbox polyline decoder. Precision = 5 (default) or 6.
629
+ function decodePolylineClient(str, precision) {
630
+ precision = precision || 5;
631
+ var factor = Math.pow(10, precision);
632
+ var coords = [];
633
+ var lat = 0;
634
+ var lng = 0;
635
+ var i = 0;
636
+ while (i < str.length) {
637
+ var shift = 0;
638
+ var result = 0;
639
+ var b;
640
+ do {
641
+ b = str.charCodeAt(i++) - 63;
642
+ result |= (b & 0x1f) << shift;
643
+ shift += 5;
644
+ } while (b >= 0x20 && i < str.length);
645
+ lat += (result & 1) ? ~(result >> 1) : (result >> 1);
646
+ shift = 0;
647
+ result = 0;
648
+ do {
649
+ b = str.charCodeAt(i++) - 63;
650
+ result |= (b & 0x1f) << shift;
651
+ shift += 5;
652
+ } while (b >= 0x20 && i < str.length);
653
+ lng += (result & 1) ? ~(result >> 1) : (result >> 1);
654
+ coords.push([lng / factor, lat / factor]);
655
+ }
656
+ return coords;
657
+ }
658
+
659
+ function coordsLookSaneClient(coords) {
660
+ for (var i = 0; i < coords.length; i++) {
661
+ var c = coords[i];
662
+ if (!Array.isArray(c) || c.length !== 2) return false;
663
+ if (c[0] < -180 || c[0] > 180 || c[1] < -90 || c[1] > 90) return false;
664
+ }
665
+ return true;
666
+ }
667
+
668
+ // Mapbox Directions can return geometry in 3 shapes:
669
+ // - GeoJSON : { type: 'LineString', coordinates: [[lng,lat], ...] }
670
+ // - polyline : "encoded_string" (precision 5, default)
671
+ // - polyline6: "encoded_string" (precision 6)
672
+ // We always request geometries=geojson ourselves, but accept all three
673
+ // in case the API ever falls back.
674
+ function pickRouteGeometryClient(route) {
675
+ if (!route || !route.geometry) return null;
676
+ var g = route.geometry;
677
+ if (typeof g === 'object' && Array.isArray(g.coordinates) && g.coordinates.length) {
678
+ return g.coordinates;
679
+ }
680
+ if (typeof g === 'string' && g.length > 0) {
681
+ var coords = decodePolylineClient(g, 5);
682
+ if (coords.length > 0 && coordsLookSaneClient(coords)) return coords;
683
+ coords = decodePolylineClient(g, 6);
684
+ if (coords.length > 0 && coordsLookSaneClient(coords)) return coords;
685
+ }
686
+ return null;
687
+ }
688
+
689
+ function buildDirectionsSummary(route) {
690
+ var parts = [];
691
+ if (typeof route.distance === 'number') {
692
+ parts.push((route.distance / 1609.34).toFixed(1) + ' mi');
693
+ }
694
+ if (typeof route.duration === 'number') {
695
+ parts.push(Math.round(route.duration / 60) + ' min');
696
+ }
697
+ return parts.length ? 'Route: ' + parts.join(', ') : 'Route';
698
+ }
699
+
700
+ function buildDirectionsMiniPayload(coords, route) {
701
+ return {
702
+ summary: buildDirectionsSummary(route),
703
+ layers: [
704
+ {
705
+ id: 'selffetch-directions-route',
706
+ type: 'line',
707
+ data: {
708
+ type: 'Feature',
709
+ geometry: { type: 'LineString', coordinates: coords },
710
+ properties: {}
711
+ },
712
+ paint: { 'line-color': '#3b82f6', 'line-width': 5 },
713
+ layout: { 'line-join': 'round', 'line-cap': 'round' }
714
+ }
715
+ ],
716
+ markers: [
717
+ { coordinates: coords[0], style: 'start', popup: 'Start' },
718
+ { coordinates: coords[coords.length - 1], style: 'end', popup: 'End' }
719
+ ]
720
+ };
721
+ }
722
+
723
+ function selfFetchDirections(params) {
724
+ if (!TOKEN || !isSafeDirectionsParams(params)) {
725
+ showError('Could not fetch route: missing token or invalid parameters.');
726
+ return;
727
+ }
728
+ var url = buildDirectionsApiUrl(params, TOKEN, API_ENDPOINT);
729
+ fetch(url)
730
+ .then(function(res) {
731
+ if (!res.ok) {
732
+ return res
733
+ .json()
734
+ .catch(function() { return null; })
735
+ .then(function(body) {
736
+ var msg =
737
+ body && body.message
738
+ ? body.message
739
+ : 'Directions API error (' + res.status + ')';
740
+ throw new Error(msg);
741
+ });
742
+ }
743
+ return res.json();
744
+ })
745
+ .then(function(data) {
746
+ var route = data && data.routes && data.routes[0];
747
+ var coords = route ? pickRouteGeometryClient(route) : null;
748
+ if (!coords) {
749
+ showError('Directions API returned no route.');
750
+ return;
751
+ }
752
+ mergeAdditionalPayload(buildDirectionsMiniPayload(coords, route));
753
+ })
754
+ .catch(function(err) {
755
+ showError(
756
+ 'Could not fetch route: ' + (err && err.message ? err.message : err)
757
+ );
758
+ });
759
+ }
760
+
761
+ // --- Self-fetch: isochrone --------------------------------------------
762
+ // Mirrors src/tools/isochrone-tool/buildIsochroneRequestUrl.ts — kept in
763
+ // sync by the parity test in
764
+ // test/resources/ui-apps/isochroneSelfFetchUrlParity.test.ts.
765
+ function buildIsochroneApiUrl(params, publicToken, apiEndpoint) {
766
+ var profile = params.profile || 'mapbox/driving-traffic';
767
+ var qp = new URLSearchParams();
768
+ qp.append('access_token', publicToken);
769
+ if (params.contours_minutes && params.contours_minutes.length > 0) {
770
+ qp.append('contours_minutes', params.contours_minutes.join(','));
771
+ }
772
+ if (params.contours_meters && params.contours_meters.length > 0) {
773
+ qp.append('contours_meters', params.contours_meters.join(','));
774
+ }
775
+ if (params.contours_colors && params.contours_colors.length > 0) {
776
+ qp.append('contours_colors', params.contours_colors.join(','));
777
+ }
778
+ if (params.polygons) {
779
+ qp.append('polygons', String(params.polygons));
780
+ }
781
+ if (params.denoise) {
782
+ qp.append('denoise', String(params.denoise));
783
+ }
784
+ if (params.generalize) {
785
+ qp.append('generalize', String(params.generalize));
786
+ }
787
+ if (params.exclude && params.exclude.length > 0) {
788
+ qp.append('exclude', params.exclude.join(','));
789
+ }
790
+ if (params.depart_at) {
791
+ qp.append('depart_at', params.depart_at);
792
+ }
793
+ return apiEndpoint + 'isochrone/v1/' + profile + '/' +
794
+ params.coordinates.longitude + '%2C' + params.coordinates.latitude +
795
+ '?' + qp.toString();
796
+ }
797
+ // Exposed so the parity test (Node vm sandbox) can call this in isolation.
798
+ window.__buildIsochroneApiUrl = buildIsochroneApiUrl;
799
+
800
+ // params came from the ref and get interpolated into the request URL —
801
+ // profile is spliced into the URL path unencoded, so validate it the
802
+ // same way isSafeDirectionsParams validates routing_profile. Every
803
+ // other field goes through URLSearchParams.append, which percent-encodes
804
+ // automatically, so no extra charset validation is needed there.
805
+ function isSafeIsochroneParams(params) {
806
+ if (!params || !params.coordinates) return false;
807
+ var c = params.coordinates;
808
+ if (typeof c.longitude !== 'number' || !isFinite(c.longitude)) return false;
809
+ if (typeof c.latitude !== 'number' || !isFinite(c.latitude)) return false;
810
+ if (params.profile !== undefined && params.profile !== null) {
811
+ if (typeof params.profile !== 'string') return false;
812
+ if (!/^mapbox\\/[a-z-]+$/.test(params.profile)) return false;
813
+ }
814
+ return true;
815
+ }
816
+
817
+ var ISO_HEX6_RE = /^[0-9a-fA-F]{6}$/;
818
+ function sanitizeHexClient(raw, fallback) {
819
+ if (typeof raw !== 'string') return fallback;
820
+ var bare = raw.replace(/^#/, '');
821
+ return ISO_HEX6_RE.test(bare) ? '#' + bare : fallback;
822
+ }
823
+
824
+ // Mirrors buildIsochroneMapPayload (formerly server-side in
825
+ // IsochroneTool.ts, now client-only since the map preview self-fetches).
826
+ function buildIsochroneMiniPayload(data, params) {
827
+ var fc = data;
828
+ if (!fc || fc.type !== 'FeatureCollection' ||
829
+ !Array.isArray(fc.features) || fc.features.length === 0) {
830
+ return null;
831
+ }
832
+
833
+ // Render contours largest-first → smallest-on-top for a clean layered look.
834
+ var ordered = fc.features.slice().reverse();
835
+ var layers = [];
836
+ ordered.forEach(function(feature, i) {
837
+ var props = feature.properties || {};
838
+ var color = sanitizeHexClient(props.color || props.fillColor, '#3b82f6');
839
+ var fillOpacity = (typeof props.fillOpacity === 'number') ? props.fillOpacity : 0.25;
840
+
841
+ if (feature.geometry && feature.geometry.type === 'Polygon' && feature.geometry.coordinates) {
842
+ layers.push({
843
+ id: 'selffetch-isochrone-fill-' + i,
844
+ type: 'fill',
845
+ data: { type: 'Feature', geometry: feature.geometry, properties: {} },
846
+ paint: { 'fill-color': color, 'fill-opacity': fillOpacity }
847
+ });
848
+ }
849
+ layers.push({
850
+ id: 'selffetch-isochrone-line-' + i,
851
+ type: 'line',
852
+ data: { type: 'Feature', geometry: feature.geometry, properties: {} },
853
+ paint: { 'line-color': color, 'line-width': 2, 'line-opacity': 0.9 },
854
+ layout: { 'line-join': 'round', 'line-cap': 'round' }
855
+ });
856
+ });
857
+
858
+ var mode = (params.profile || 'mapbox/driving-traffic').replace('mapbox/', '').replace('-', ' ');
859
+ var summary = 'Isochrone: ' + fc.features.length + ' contour' + (fc.features.length !== 1 ? 's' : '');
860
+ if (params.contours_minutes && params.contours_minutes.length > 0) {
861
+ summary = 'Reachable by ' + mode + ': ' + params.contours_minutes.map(function(m) {
862
+ return m + ' min';
863
+ }).join(', ');
864
+ } else if (params.contours_meters && params.contours_meters.length > 0) {
865
+ summary = 'Reachable by ' + mode + ': ' + params.contours_meters.map(function(m) {
866
+ return m >= 1000 ? (m / 1000).toFixed(1) + ' km' : m + ' m';
867
+ }).join(', ');
868
+ }
869
+
870
+ return {
871
+ summary: summary,
872
+ layers: layers,
873
+ markers: [
874
+ {
875
+ coordinates: [params.coordinates.longitude, params.coordinates.latitude],
876
+ style: 'pin',
877
+ color: '#0f172a',
878
+ popup: 'Origin'
879
+ }
880
+ ]
881
+ };
882
+ }
883
+
884
+ function selfFetchIsochrone(params) {
885
+ if (!TOKEN || !isSafeIsochroneParams(params)) {
886
+ showError('Could not fetch isochrone: missing token or invalid parameters.');
887
+ return;
888
+ }
889
+ var url = buildIsochroneApiUrl(params, TOKEN, API_ENDPOINT);
890
+ fetch(url)
891
+ .then(function(res) {
892
+ if (!res.ok) {
893
+ return res
894
+ .json()
895
+ .catch(function() { return null; })
896
+ .then(function(body) {
897
+ var msg =
898
+ body && body.message
899
+ ? body.message
900
+ : 'Isochrone API error (' + res.status + ')';
901
+ throw new Error(msg);
902
+ });
903
+ }
904
+ return res.json();
905
+ })
906
+ .then(function(data) {
907
+ var mini = buildIsochroneMiniPayload(data, params);
908
+ if (!mini) {
909
+ showError('Isochrone API returned no contours.');
910
+ return;
911
+ }
912
+ mergeAdditionalPayload(mini);
913
+ })
914
+ .catch(function(err) {
915
+ showError(
916
+ 'Could not fetch isochrone: ' + (err && err.message ? err.message : err)
917
+ );
918
+ });
919
+ }
920
+
921
+ // --- Self-fetch: map matching ----------------------------------------
922
+ // Mirrors src/tools/map-matching-tool/buildMapMatchingRequestUrl.ts —
923
+ // kept in sync by the parity test in
924
+ // test/resources/ui-apps/mapMatchingSelfFetchUrlParity.test.ts. Always
925
+ // forces geometries=geojson and overview=full for the self-fetch itself,
926
+ // regardless of what the original tool call requested, so the map
927
+ // preview never depends on it.
928
+ var MAP_MATCHING_PROFILES = ['driving', 'driving-traffic', 'walking', 'cycling'];
929
+
930
+ function buildMapMatchingApiUrl(params, publicToken, apiEndpoint) {
931
+ var coordsString = params.coordinates
932
+ .map(function(c) { return c.longitude + ',' + c.latitude; })
933
+ .join(';');
934
+ var profile = params.profile || 'driving';
935
+
936
+ var qp = new URLSearchParams();
937
+ qp.append('access_token', publicToken);
938
+ qp.append('geometries', 'geojson');
939
+ qp.append('overview', 'full');
940
+ if (params.timestamps) {
941
+ qp.append('timestamps', params.timestamps.join(';'));
942
+ }
943
+ if (params.radiuses) {
944
+ qp.append('radiuses', params.radiuses.join(';'));
945
+ }
946
+ if (params.annotations && params.annotations.length > 0) {
947
+ qp.append('annotations', params.annotations.join(','));
948
+ }
949
+
950
+ return apiEndpoint + 'matching/v5/mapbox/' + profile + '/' + coordsString +
951
+ '?' + qp.toString();
952
+ }
953
+ // Exposed so the parity test (Node vm sandbox) can call this in isolation.
954
+ window.__buildMapMatchingApiUrl = buildMapMatchingApiUrl;
955
+
956
+ // profile is spliced into the URL path unencoded, so it must be one of
957
+ // the known enum values rather than any string. Coordinates get
958
+ // interpolated directly too, so must be finite numbers.
959
+ function isSafeMapMatchingParams(params) {
960
+ if (!params || !Array.isArray(params.coordinates)) return false;
961
+ if (params.coordinates.length < 2) return false;
962
+ for (var i = 0; i < params.coordinates.length; i++) {
963
+ var c = params.coordinates[i];
964
+ if (!c || typeof c.longitude !== 'number' || !isFinite(c.longitude)) return false;
965
+ if (typeof c.latitude !== 'number' || !isFinite(c.latitude)) return false;
966
+ }
967
+ if (params.profile !== undefined && params.profile !== null) {
968
+ if (MAP_MATCHING_PROFILES.indexOf(params.profile) === -1) return false;
969
+ }
970
+ return true;
971
+ }
972
+
973
+ // Mirrors the deleted server-side buildMapMatchingPayload (now
974
+ // client-only since the map preview self-fetches): raw GPS trace as a
975
+ // dashed orange line, matched route as a solid blue line.
976
+ function buildMapMatchingMiniPayload(data, params) {
977
+ var match = data && data.matchings && data.matchings[0];
978
+ if (!match || !match.geometry || !Array.isArray(match.geometry.coordinates) ||
979
+ match.geometry.coordinates.length === 0) {
980
+ return null;
981
+ }
982
+
983
+ var rawCoords = params.coordinates.map(function(c) {
984
+ return [c.longitude, c.latitude];
985
+ });
986
+ var matchedCoords = match.geometry.coordinates;
987
+
988
+ var tracepoints = data.tracepoints || [];
989
+ var matched = tracepoints.filter(function(t) { return t != null; }).length;
990
+ var total = tracepoints.length || params.coordinates.length;
991
+ var confidencePct = Math.round((match.confidence || 0) * 100);
992
+
993
+ return {
994
+ summary: 'Matched ' + matched + '/' + total + ' GPS points (confidence ' + confidencePct + '%)',
995
+ layers: [
996
+ {
997
+ id: 'selffetch-map-matching-raw',
998
+ type: 'line',
999
+ data: { type: 'Feature', geometry: { type: 'LineString', coordinates: rawCoords }, properties: {} },
1000
+ paint: {
1001
+ 'line-color': '#f97316',
1002
+ 'line-width': 2,
1003
+ 'line-dasharray': [2, 2],
1004
+ 'line-opacity': 0.8
1005
+ },
1006
+ layout: { 'line-join': 'round', 'line-cap': 'round' }
1007
+ },
1008
+ {
1009
+ id: 'selffetch-map-matching-matched',
1010
+ type: 'line',
1011
+ data: { type: 'Feature', geometry: { type: 'LineString', coordinates: matchedCoords }, properties: {} },
1012
+ paint: { 'line-color': '#3b82f6', 'line-width': 4 },
1013
+ layout: { 'line-join': 'round', 'line-cap': 'round' }
1014
+ }
1015
+ ],
1016
+ legend: [
1017
+ { label: 'Raw trace', color: '#f97316' },
1018
+ { label: 'Matched route', color: '#3b82f6' }
1019
+ ]
1020
+ };
1021
+ }
1022
+
1023
+ function selfFetchMapMatching(params) {
1024
+ if (!TOKEN || !isSafeMapMatchingParams(params)) {
1025
+ showError('Could not fetch map match: missing token or invalid parameters.');
1026
+ return;
1027
+ }
1028
+ var url = buildMapMatchingApiUrl(params, TOKEN, API_ENDPOINT);
1029
+ fetch(url)
1030
+ .then(function(res) {
1031
+ if (!res.ok) {
1032
+ return res
1033
+ .json()
1034
+ .catch(function() { return null; })
1035
+ .then(function(body) {
1036
+ var msg =
1037
+ body && body.message
1038
+ ? body.message
1039
+ : 'Map Matching API error (' + res.status + ')';
1040
+ throw new Error(msg);
1041
+ });
1042
+ }
1043
+ return res.json();
1044
+ })
1045
+ .then(function(data) {
1046
+ if (data && data.code !== 'Ok') {
1047
+ showError('Map Matching API could not match the trace (code: ' + data.code + ').');
1048
+ return;
1049
+ }
1050
+ var mini = buildMapMatchingMiniPayload(data, params);
1051
+ if (!mini) {
1052
+ showError('Map Matching API returned no matched route.');
1053
+ return;
1054
+ }
1055
+ mergeAdditionalPayload(mini);
1056
+ })
1057
+ .catch(function(err) {
1058
+ showError(
1059
+ 'Could not fetch map match: ' + (err && err.message ? err.message : err)
1060
+ );
1061
+ });
1062
+ }
1063
+
1064
+ // --- Self-fetch: search / geocode --------------------------------------
1065
+ // Mirrors src/tools/search-and-geocode-tool/buildSearchAndGeocodeRequestUrl.ts
1066
+ // — kept in sync by the parity test in
1067
+ // test/resources/ui-apps/searchSelfFetchUrlParity.test.ts. Every param
1068
+ // goes through URLSearchParams.append (nothing is spliced unencoded into
1069
+ // the URL path), so there's no charset validation to mirror here beyond
1070
+ // requiring "q" to be a string.
1071
+ function buildSearchAndGeocodeApiUrl(params, publicToken, apiEndpoint) {
1072
+ var qp = new URLSearchParams();
1073
+ qp.append('q', params.q);
1074
+ qp.append('access_token', publicToken);
1075
+ if (params.language) {
1076
+ qp.append('language', params.language);
1077
+ }
1078
+ qp.append('limit', '10');
1079
+ if (params.proximity) {
1080
+ qp.append('proximity', params.proximity.longitude + ',' + params.proximity.latitude);
1081
+ }
1082
+ if (params.bbox) {
1083
+ qp.append(
1084
+ 'bbox',
1085
+ params.bbox.minLongitude + ',' + params.bbox.minLatitude + ',' +
1086
+ params.bbox.maxLongitude + ',' + params.bbox.maxLatitude
1087
+ );
1088
+ }
1089
+ if (params.country && params.country.length > 0) {
1090
+ qp.append('country', params.country.join(','));
1091
+ }
1092
+ if (params.types && params.types.length > 0) {
1093
+ qp.append('types', params.types.join(','));
1094
+ }
1095
+ if (params.poi_category && params.poi_category.length > 0) {
1096
+ qp.append('poi_category', params.poi_category.join(','));
1097
+ }
1098
+ if (params.auto_complete !== undefined && params.auto_complete !== null) {
1099
+ qp.append('auto_complete', String(params.auto_complete));
1100
+ }
1101
+ if (params.eta_type) {
1102
+ qp.append('eta_type', params.eta_type);
1103
+ }
1104
+ if (params.navigation_profile) {
1105
+ qp.append('navigation_profile', params.navigation_profile);
1106
+ }
1107
+ if (params.origin) {
1108
+ qp.append('origin', params.origin.longitude + ',' + params.origin.latitude);
1109
+ }
1110
+ return apiEndpoint + 'search/searchbox/v1/forward?' + qp.toString();
1111
+ }
1112
+ // Exposed so the parity test (Node vm sandbox) can call this in isolation.
1113
+ window.__buildSearchAndGeocodeApiUrl = buildSearchAndGeocodeApiUrl;
1114
+
1115
+ function isSafeSearchParams(params) {
1116
+ return !!params && typeof params.q === 'string' && params.q.length > 0;
1117
+ }
1118
+
1119
+ // Mirrors src/tools/search-and-geocode-tool/buildSearchMapPayload.ts
1120
+ // (formerly server-side, now client-only since the map preview
1121
+ // self-fetches). Shared by search_and_geocode_tool and
1122
+ // category_search_tool self-fetch — both return the same Search Box
1123
+ // FeatureCollection shape.
1124
+ function buildSearchMiniPayload(data, query, proximity) {
1125
+ var fc = data;
1126
+ if (!fc || !Array.isArray(fc.features)) return null;
1127
+
1128
+ var points = fc.features.filter(function(f) {
1129
+ return f.geometry && f.geometry.type === 'Point' &&
1130
+ Array.isArray(f.geometry.coordinates) &&
1131
+ typeof f.geometry.coordinates[0] === 'number' &&
1132
+ typeof f.geometry.coordinates[1] === 'number';
1133
+ });
1134
+ if (points.length === 0 && !proximity) return null;
1135
+
1136
+ var markers = [];
1137
+ if (proximity) {
1138
+ markers.push({
1139
+ coordinates: [proximity.longitude, proximity.latitude],
1140
+ style: 'pin',
1141
+ color: '#0f172a',
1142
+ popup: 'Search center'
1143
+ });
1144
+ }
1145
+
1146
+ var features = [];
1147
+ points.forEach(function(f, i) {
1148
+ var props = f.properties || {};
1149
+ var popupParts = [(i + 1) + '. ' + (props.name || 'Result')];
1150
+ var addr = props.full_address || props.place_formatted;
1151
+ if (addr) popupParts.push(addr);
1152
+ if (typeof props.distance === 'number') {
1153
+ popupParts.push(Math.round(props.distance) + ' m');
1154
+ }
1155
+ var coords = f.geometry.coordinates;
1156
+ markers.push({
1157
+ coordinates: coords,
1158
+ style: 'numbered',
1159
+ label: String(i + 1),
1160
+ color: '#f97316',
1161
+ popup: popupParts.join(' — ')
1162
+ });
1163
+ features.push({
1164
+ type: 'Feature',
1165
+ geometry: { type: 'Point', coordinates: coords },
1166
+ properties: { idx: i + 1, label: props.name || ('Result ' + (i + 1)) }
1167
+ });
1168
+ });
1169
+
1170
+ var summary = query
1171
+ ? points.length + ' result' + (points.length !== 1 ? 's' : '') + ' for "' + query + '"'
1172
+ : points.length + ' result' + (points.length !== 1 ? 's' : '');
1173
+
1174
+ var layers = features.length > 0 ? [
1175
+ {
1176
+ id: 'selffetch-search-results',
1177
+ type: 'circle',
1178
+ data: { type: 'FeatureCollection', features: features },
1179
+ paint: {
1180
+ 'circle-radius': 6,
1181
+ 'circle-color': '#f97316',
1182
+ 'circle-opacity': 0.15,
1183
+ 'circle-stroke-width': 1,
1184
+ 'circle-stroke-color': '#f97316',
1185
+ 'circle-stroke-opacity': 0.4
1186
+ }
1187
+ }
1188
+ ] : [];
1189
+
1190
+ return { summary: summary, layers: layers, markers: markers };
1191
+ }
1192
+
1193
+ function selfFetchSearch(params) {
1194
+ if (!TOKEN || !isSafeSearchParams(params)) {
1195
+ showError('Could not fetch search results: missing token or invalid parameters.');
1196
+ return;
1197
+ }
1198
+ var url = buildSearchAndGeocodeApiUrl(params, TOKEN, API_ENDPOINT);
1199
+ fetch(url)
1200
+ .then(function(res) {
1201
+ if (!res.ok) {
1202
+ return res
1203
+ .json()
1204
+ .catch(function() { return null; })
1205
+ .then(function(body) {
1206
+ var msg =
1207
+ body && body.message
1208
+ ? body.message
1209
+ : 'Search API error (' + res.status + ')';
1210
+ throw new Error(msg);
1211
+ });
1212
+ }
1213
+ return res.json();
1214
+ })
1215
+ .then(function(data) {
1216
+ var fc = data;
1217
+ // Elicitation resolved to one specific result earlier — filter the
1218
+ // freshly-fetched results down to that one. If ranking shifted
1219
+ // enough that it's no longer present, fall back to showing all
1220
+ // fresh results rather than nothing.
1221
+ if (params.selectedMapboxId && fc && Array.isArray(fc.features)) {
1222
+ var match = fc.features.filter(function(f) {
1223
+ return f.properties && f.properties.mapbox_id === params.selectedMapboxId;
1224
+ });
1225
+ if (match.length > 0) fc = { type: 'FeatureCollection', features: match };
1226
+ }
1227
+ var mini = buildSearchMiniPayload(fc, params.q, params.proximity);
1228
+ if (!mini) {
1229
+ showError('Search API returned no results.');
1230
+ return;
1231
+ }
1232
+ mergeAdditionalPayload(mini);
1233
+ })
1234
+ .catch(function(err) {
1235
+ showError(
1236
+ 'Could not fetch search results: ' + (err && err.message ? err.message : err)
1237
+ );
1238
+ });
1239
+ }
1240
+
1241
+ // --- Self-fetch: category search ---------------------------------------
1242
+ // Mirrors src/tools/category-search-tool/buildCategorySearchRequestUrl.ts
1243
+ // — kept in sync by the parity test in
1244
+ // test/resources/ui-apps/categorySearchSelfFetchUrlParity.test.ts. Shares
1245
+ // buildSearchMiniPayload with the plain search self-fetch above — both
1246
+ // return the same Search Box FeatureCollection shape.
1247
+ function buildCategorySearchApiUrl(params, publicToken, apiEndpoint) {
1248
+ var qp = new URLSearchParams();
1249
+ qp.append('access_token', publicToken);
1250
+ if (params.language) {
1251
+ qp.append('language', params.language);
1252
+ }
1253
+ if (params.limit !== undefined && params.limit !== null) {
1254
+ qp.append('limit', String(params.limit));
1255
+ }
1256
+ if (params.proximity) {
1257
+ qp.append('proximity', params.proximity.longitude + ',' + params.proximity.latitude);
1258
+ }
1259
+ if (params.bbox) {
1260
+ qp.append(
1261
+ 'bbox',
1262
+ params.bbox.minLongitude + ',' + params.bbox.minLatitude + ',' +
1263
+ params.bbox.maxLongitude + ',' + params.bbox.maxLatitude
1264
+ );
1265
+ }
1266
+ if (params.country && params.country.length > 0) {
1267
+ qp.append('country', params.country.join(','));
1268
+ }
1269
+ if (params.poi_category_exclusions && params.poi_category_exclusions.length > 0) {
1270
+ qp.append('poi_category_exclusions', params.poi_category_exclusions.join(','));
1271
+ }
1272
+ return apiEndpoint + 'search/searchbox/v1/category/' +
1273
+ encodeURIComponent(params.category) + '?' + qp.toString();
1274
+ }
1275
+ // Exposed so the parity test (Node vm sandbox) can call this in isolation.
1276
+ window.__buildCategorySearchApiUrl = buildCategorySearchApiUrl;
1277
+
1278
+ function isSafeCategorySearchParams(params) {
1279
+ return !!params && typeof params.category === 'string' && params.category.length > 0;
1280
+ }
1281
+
1282
+ function selfFetchCategorySearch(params) {
1283
+ if (!TOKEN || !isSafeCategorySearchParams(params)) {
1284
+ showError('Could not fetch category search results: missing token or invalid parameters.');
1285
+ return;
1286
+ }
1287
+ var url = buildCategorySearchApiUrl(params, TOKEN, API_ENDPOINT);
1288
+ fetch(url)
1289
+ .then(function(res) {
1290
+ if (!res.ok) {
1291
+ return res
1292
+ .json()
1293
+ .catch(function() { return null; })
1294
+ .then(function(body) {
1295
+ var msg =
1296
+ body && body.message
1297
+ ? body.message
1298
+ : 'Category Search API error (' + res.status + ')';
1299
+ throw new Error(msg);
1300
+ });
1301
+ }
1302
+ return res.json();
1303
+ })
1304
+ .then(function(data) {
1305
+ var mini = buildSearchMiniPayload(data, params.category, params.proximity);
1306
+ if (!mini) {
1307
+ showError('Category Search API returned no results.');
1308
+ return;
1309
+ }
1310
+ mergeAdditionalPayload(mini);
1311
+ })
1312
+ .catch(function(err) {
1313
+ showError(
1314
+ 'Could not fetch category search results: ' + (err && err.message ? err.message : err)
1315
+ );
1316
+ });
1317
+ }
1318
+
1319
+ // --- Self-fetch: trip optimization --------------------------------------
1320
+ // Mirrors src/tools/optimization-tool/buildOptimizationRequestUrl.ts —
1321
+ // kept in sync by the parity test in
1322
+ // test/resources/ui-apps/optimizationSelfFetchUrlParity.test.ts. Always
1323
+ // forces geometries=geojson and overview=full for the self-fetch itself
1324
+ // — overview defaults to 'simplified' (and could even be 'false') on the
1325
+ // original call, either of which would otherwise leave the map preview
1326
+ // with no geometry to draw.
1327
+ var OPTIMIZATION_PROFILES = ['mapbox/driving', 'mapbox/walking', 'mapbox/cycling', 'mapbox/driving-traffic'];
1328
+
1329
+ function buildOptimizationApiUrl(params, publicToken, apiEndpoint) {
1330
+ var coordsString = params.coordinates
1331
+ .map(function(c) { return c.longitude + ',' + c.latitude; })
1332
+ .join(';');
1333
+ var profile = params.profile || 'mapbox/driving';
1334
+
1335
+ var qp = new URLSearchParams();
1336
+ qp.append('access_token', publicToken);
1337
+ if (params.source) {
1338
+ qp.append('source', params.source);
1339
+ }
1340
+ if (params.destination) {
1341
+ qp.append('destination', params.destination);
1342
+ }
1343
+ qp.append('roundtrip', String(params.roundtrip));
1344
+ qp.append('geometries', 'geojson');
1345
+ qp.append('overview', 'full');
1346
+ if (params.steps !== undefined && params.steps !== null) {
1347
+ qp.append('steps', String(params.steps));
1348
+ }
1349
+ if (params.annotations && params.annotations.length > 0) {
1350
+ qp.append('annotations', params.annotations.join(','));
1351
+ }
1352
+ if (params.language) {
1353
+ qp.append('language', params.language);
1354
+ }
1355
+
1356
+ return apiEndpoint + 'optimized-trips/v1/' + profile + '/' + coordsString +
1357
+ '?' + qp.toString();
1358
+ }
1359
+ // Exposed so the parity test (Node vm sandbox) can call this in isolation.
1360
+ window.__buildOptimizationApiUrl = buildOptimizationApiUrl;
1361
+
1362
+ // profile is spliced into the URL path unencoded, so it must be one of
1363
+ // the known enum values rather than any string. Coordinates get
1364
+ // interpolated directly too, so must be finite numbers.
1365
+ function isSafeOptimizationParams(params) {
1366
+ if (!params || !Array.isArray(params.coordinates) || params.coordinates.length < 2) {
1367
+ return false;
1368
+ }
1369
+ for (var i = 0; i < params.coordinates.length; i++) {
1370
+ var c = params.coordinates[i];
1371
+ if (!c || typeof c.longitude !== 'number' || !isFinite(c.longitude)) return false;
1372
+ if (typeof c.latitude !== 'number' || !isFinite(c.latitude)) return false;
1373
+ }
1374
+ if (params.profile !== undefined && params.profile !== null) {
1375
+ if (OPTIMIZATION_PROFILES.indexOf(params.profile) === -1) return false;
1376
+ }
1377
+ return true;
1378
+ }
1379
+
1380
+ // Mirrors the deleted server-side buildOptimizationMapPayload (now
1381
+ // client-only since the map preview self-fetches): a single trip line
1382
+ // plus numbered visit-order markers (start=green, end=red, middle=blue).
1383
+ function buildOptimizationMiniPayload(data, params) {
1384
+ var trip = data && data.trips && data.trips[0];
1385
+ if (!trip || !trip.geometry || !Array.isArray(trip.geometry.coordinates) ||
1386
+ trip.geometry.coordinates.length === 0) {
1387
+ return null;
1388
+ }
1389
+
1390
+ var waypoints = data.waypoints || [];
1391
+ var ordered = waypoints.map(function(wp, inputIndex) {
1392
+ return { wp: wp, inputIndex: inputIndex };
1393
+ }).sort(function(a, b) { return a.wp.waypoint_index - b.wp.waypoint_index; });
1394
+
1395
+ var markers = ordered.map(function(entry, i) {
1396
+ var isStart = i === 0;
1397
+ var isEnd = i === ordered.length - 1;
1398
+ var color = isStart ? '#22c55e' : isEnd ? '#ef4444' : '#2563eb';
1399
+ var popupParts = ['Stop ' + (i + 1) + ' (input #' + entry.inputIndex + ')'];
1400
+ // wp.name is the snapped road name, not a place name — label accordingly.
1401
+ if (entry.wp.name) popupParts.push('on ' + entry.wp.name);
1402
+ return {
1403
+ coordinates: entry.wp.location,
1404
+ style: 'numbered',
1405
+ label: String(i + 1),
1406
+ color: color,
1407
+ popup: popupParts.join(' — ')
1408
+ };
1409
+ });
1410
+
1411
+ var miles = (trip.distance / 1609.34).toFixed(1);
1412
+ var minutes = Math.round(trip.duration / 60);
1413
+ var summary = 'Optimized trip: ' + miles + ' mi, ' + minutes + ' min';
1414
+
1415
+ return {
1416
+ summary: summary,
1417
+ layers: [
1418
+ {
1419
+ id: 'selffetch-optimization-trip',
1420
+ type: 'line',
1421
+ data: { type: 'Feature', geometry: { type: 'LineString', coordinates: trip.geometry.coordinates }, properties: {} },
1422
+ paint: { 'line-color': '#3b82f6', 'line-width': 5, 'line-opacity': 0.85 },
1423
+ layout: { 'line-join': 'round', 'line-cap': 'round' }
1424
+ }
1425
+ ],
1426
+ markers: markers
1427
+ };
1428
+ }
1429
+
1430
+ function selfFetchOptimization(params) {
1431
+ if (!TOKEN || !isSafeOptimizationParams(params)) {
1432
+ showError('Could not fetch optimized trip: missing token or invalid parameters.');
1433
+ return;
1434
+ }
1435
+ var url = buildOptimizationApiUrl(params, TOKEN, API_ENDPOINT);
1436
+ fetch(url)
1437
+ .then(function(res) {
1438
+ if (!res.ok) {
1439
+ return res
1440
+ .json()
1441
+ .catch(function() { return null; })
1442
+ .then(function(body) {
1443
+ var msg =
1444
+ body && body.message
1445
+ ? body.message
1446
+ : 'Optimization API error (' + res.status + ')';
1447
+ throw new Error(msg);
1448
+ });
1449
+ }
1450
+ return res.json();
1451
+ })
1452
+ .then(function(data) {
1453
+ if (data && data.code !== 'Ok') {
1454
+ showError('Optimization API error (code: ' + data.code + ').');
1455
+ return;
1456
+ }
1457
+ var mini = buildOptimizationMiniPayload(data, params);
1458
+ if (!mini) {
1459
+ showError('Optimization API returned no trip.');
1460
+ return;
1461
+ }
1462
+ mergeAdditionalPayload(mini);
1463
+ })
1464
+ .catch(function(err) {
1465
+ showError(
1466
+ 'Could not fetch optimized trip: ' + (err && err.message ? err.message : err)
1467
+ );
1468
+ });
1469
+ }
1470
+
1471
+ // --- Self-fetch: ground location -----------------------------------------
1472
+ // Mirrors src/tools/ground-location-tool/buildReverseGeocodeUrl.ts —
1473
+ // kept in sync by the parity test in
1474
+ // test/resources/ui-apps/groundLocationSelfFetchUrlParity.test.ts. The POI
1475
+ // sub-fetch (if any) reuses buildCategorySearchApiUrl above rather than
1476
+ // duplicating it. The sampling-derived strategy that decided geocodeTypes
1477
+ // and whether a POI fetch is needed was already resolved server-side (the
1478
+ // iframe can't invoke MCP sampling itself) and is carried in the ref.
1479
+ function buildReverseGeocodeApiUrl(input, publicToken, apiEndpoint) {
1480
+ var qp = new URLSearchParams();
1481
+ qp.append('longitude', String(input.longitude));
1482
+ qp.append('latitude', String(input.latitude));
1483
+ qp.append('access_token', publicToken);
1484
+ qp.append('limit', '1');
1485
+ qp.append('types', input.types);
1486
+ if (input.language) {
1487
+ qp.append('language', input.language);
1488
+ }
1489
+ return apiEndpoint + 'search/geocode/v6/reverse?' + qp.toString();
1490
+ }
1491
+ // Exposed so the parity test (Node vm sandbox) can call this in isolation.
1492
+ window.__buildReverseGeocodeApiUrl = buildReverseGeocodeApiUrl;
1493
+
1494
+ function isSafeGroundLocationParams(params) {
1495
+ if (!params) return false;
1496
+ if (typeof params.longitude !== 'number' || !isFinite(params.longitude)) return false;
1497
+ if (typeof params.latitude !== 'number' || !isFinite(params.latitude)) return false;
1498
+ if (typeof params.geocodeTypes !== 'string' || params.geocodeTypes.length === 0) return false;
1499
+ return true;
1500
+ }
1501
+
1502
+ // Mirrors the deleted server-side buildGroundLocationPayload (now
1503
+ // client-only since the map preview self-fetches): a dark origin pin plus
1504
+ // numbered orange POI markers. Isochrone contours aren't drawn here either
1505
+ // — the original never rendered them, only a contour-minutes summary.
1506
+ function buildGroundLocationMiniPayload(place, longitude, latitude, pois) {
1507
+ var markers = [
1508
+ {
1509
+ coordinates: [longitude, latitude],
1510
+ style: 'pin',
1511
+ color: '#0f172a',
1512
+ popup: place
1513
+ }
1514
+ ];
1515
+ (pois || []).forEach(function(poi, i) {
1516
+ var parts = [(i + 1) + '. ' + poi.name];
1517
+ if (poi.address) parts.push(poi.address);
1518
+ if (typeof poi.distance_meters === 'number') {
1519
+ parts.push(Math.round(poi.distance_meters) + ' m');
1520
+ }
1521
+ markers.push({
1522
+ coordinates: [poi.longitude, poi.latitude],
1523
+ style: 'numbered',
1524
+ label: String(i + 1),
1525
+ color: '#f97316',
1526
+ popup: parts.join(' — ')
1527
+ });
1528
+ });
1529
+ return { summary: place, layers: [], markers: markers };
1530
+ }
1531
+
1532
+ function selfFetchGroundLocation(params) {
1533
+ if (!TOKEN || !isSafeGroundLocationParams(params)) {
1534
+ showError('Could not fetch location context: missing token or invalid parameters.');
1535
+ return;
1536
+ }
1537
+ var geocodeUrl = buildReverseGeocodeApiUrl(
1538
+ {
1539
+ longitude: params.longitude,
1540
+ latitude: params.latitude,
1541
+ types: params.geocodeTypes,
1542
+ language: params.language
1543
+ },
1544
+ TOKEN,
1545
+ API_ENDPOINT
1546
+ );
1547
+ var geocodePromise = fetch(geocodeUrl)
1548
+ .then(function(res) { return res.ok ? res.json() : null; })
1549
+ .catch(function() { return null; });
1550
+
1551
+ var poiPromise;
1552
+ if (params.poi && typeof params.poi.query === 'string') {
1553
+ var poiUrl = buildCategorySearchApiUrl(
1554
+ {
1555
+ category: params.poi.query,
1556
+ proximity: { longitude: params.longitude, latitude: params.latitude },
1557
+ limit: params.poi.limit,
1558
+ language: params.language
1559
+ },
1560
+ TOKEN,
1561
+ API_ENDPOINT
1562
+ );
1563
+ poiPromise = fetch(poiUrl)
1564
+ .then(function(res) { return res.ok ? res.json() : null; })
1565
+ .catch(function() { return null; });
1566
+ } else {
1567
+ poiPromise = Promise.resolve(null);
1568
+ }
1569
+
1570
+ Promise.all([geocodePromise, poiPromise]).then(function(results) {
1571
+ var geocodeData = results[0];
1572
+ var poiData = results[1];
1573
+
1574
+ var feature = geocodeData && Array.isArray(geocodeData.features)
1575
+ ? geocodeData.features[0]
1576
+ : null;
1577
+ var place = (feature && feature.properties && feature.properties.name) ||
1578
+ (params.latitude + ', ' + params.longitude);
1579
+
1580
+ var poiFeatures = (poiData && Array.isArray(poiData.features)) ? poiData.features : [];
1581
+ var pois = poiFeatures.map(function(f) {
1582
+ var props = f.properties || {};
1583
+ var coords = (f.geometry && f.geometry.coordinates) || [params.longitude, params.latitude];
1584
+ return {
1585
+ name: props.name || 'Unknown',
1586
+ address: props.full_address || props.place_formatted,
1587
+ longitude: coords[0],
1588
+ latitude: coords[1],
1589
+ distance_meters: props.distance
1590
+ };
1591
+ });
1592
+
1593
+ var mini = buildGroundLocationMiniPayload(place, params.longitude, params.latitude, pois);
1594
+ mergeAdditionalPayload(mini);
1595
+ }).catch(function(err) {
1596
+ showError(
1597
+ 'Could not fetch location context: ' + (err && err.message ? err.message : err)
1598
+ );
1599
+ });
1600
+ }
1601
+
1602
+ function escapeText(s) {
1603
+ return String(s == null ? '' : s)
1604
+ .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
1605
+ }
1606
+ function escapeAttr(s) {
1607
+ return escapeText(s).replace(/"/g, '&quot;');
1608
+ }
1609
+ })();
1610
+ </script>
1611
+ </body>
1612
+ </html>`;
1613
+ }
1614
+ function escapeForScript(s) {
1615
+ return s.replace(/<\/script>/gi, '<\\/script>');
1616
+ }
1617
+ //# sourceMappingURL=mapAppHtml.js.map