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