@mapbox/mcp-server 0.12.7 → 0.13.0

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