map.ts 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. /**
  2. * Google Maps API Integration for Manus WebDev Templates
  3. *
  4. * Main function: makeRequest<T>(endpoint, params) - Makes authenticated requests to Google Maps APIs
  5. * All credentials are automatically injected. Array parameters use | as separator.
  6. *
  7. * See API examples below the type definitions for usage patterns.
  8. */
  9. import { ENV } from "./env";
  10. // ============================================================================
  11. // Configuration
  12. // ============================================================================
  13. type MapsConfig = {
  14. baseUrl: string;
  15. apiKey: string;
  16. };
  17. function getMapsConfig(): MapsConfig {
  18. const baseUrl = ENV.forgeApiUrl;
  19. const apiKey = ENV.forgeApiKey;
  20. if (!baseUrl || !apiKey) {
  21. throw new Error(
  22. "Google Maps proxy credentials missing: set BUILT_IN_FORGE_API_URL and BUILT_IN_FORGE_API_KEY"
  23. );
  24. }
  25. return {
  26. baseUrl: baseUrl.replace(/\/+$/, ""),
  27. apiKey,
  28. };
  29. }
  30. // ============================================================================
  31. // Core Request Handler
  32. // ============================================================================
  33. interface RequestOptions {
  34. method?: "GET" | "POST";
  35. body?: Record<string, unknown>;
  36. }
  37. /**
  38. * Make authenticated requests to Google Maps APIs
  39. *
  40. * @param endpoint - The API endpoint (e.g., "/maps/api/geocode/json")
  41. * @param params - Query parameters for the request
  42. * @param options - Additional request options
  43. * @returns The API response
  44. */
  45. export async function makeRequest<T = unknown>(
  46. endpoint: string,
  47. params: Record<string, unknown> = {},
  48. options: RequestOptions = {}
  49. ): Promise<T> {
  50. const { baseUrl, apiKey } = getMapsConfig();
  51. // Construct full URL: baseUrl + /v1/maps/proxy + endpoint
  52. const url = new URL(`${baseUrl}/v1/maps/proxy${endpoint}`);
  53. // Add API key as query parameter (standard Google Maps API authentication)
  54. url.searchParams.append("key", apiKey);
  55. // Add other query parameters
  56. Object.entries(params).forEach(([key, value]) => {
  57. if (value !== undefined && value !== null) {
  58. url.searchParams.append(key, String(value));
  59. }
  60. });
  61. const response = await fetch(url.toString(), {
  62. method: options.method || "GET",
  63. headers: {
  64. "Content-Type": "application/json",
  65. },
  66. body: options.body ? JSON.stringify(options.body) : undefined,
  67. });
  68. if (!response.ok) {
  69. const errorText = await response.text();
  70. throw new Error(
  71. `Google Maps API request failed (${response.status} ${response.statusText}): ${errorText}`
  72. );
  73. }
  74. return (await response.json()) as T;
  75. }
  76. // ============================================================================
  77. // Type Definitions
  78. // ============================================================================
  79. export type TravelMode = "driving" | "walking" | "bicycling" | "transit";
  80. export type MapType = "roadmap" | "satellite" | "terrain" | "hybrid";
  81. export type SpeedUnit = "KPH" | "MPH";
  82. export type LatLng = {
  83. lat: number;
  84. lng: number;
  85. };
  86. export type DirectionsResult = {
  87. routes: Array<{
  88. legs: Array<{
  89. distance: { text: string; value: number };
  90. duration: { text: string; value: number };
  91. start_address: string;
  92. end_address: string;
  93. start_location: LatLng;
  94. end_location: LatLng;
  95. steps: Array<{
  96. distance: { text: string; value: number };
  97. duration: { text: string; value: number };
  98. html_instructions: string;
  99. travel_mode: string;
  100. start_location: LatLng;
  101. end_location: LatLng;
  102. }>;
  103. }>;
  104. overview_polyline: { points: string };
  105. summary: string;
  106. warnings: string[];
  107. waypoint_order: number[];
  108. }>;
  109. status: string;
  110. };
  111. export type DistanceMatrixResult = {
  112. rows: Array<{
  113. elements: Array<{
  114. distance: { text: string; value: number };
  115. duration: { text: string; value: number };
  116. status: string;
  117. }>;
  118. }>;
  119. origin_addresses: string[];
  120. destination_addresses: string[];
  121. status: string;
  122. };
  123. export type GeocodingResult = {
  124. results: Array<{
  125. address_components: Array<{
  126. long_name: string;
  127. short_name: string;
  128. types: string[];
  129. }>;
  130. formatted_address: string;
  131. geometry: {
  132. location: LatLng;
  133. location_type: string;
  134. viewport: {
  135. northeast: LatLng;
  136. southwest: LatLng;
  137. };
  138. };
  139. place_id: string;
  140. types: string[];
  141. }>;
  142. status: string;
  143. };
  144. export type PlacesSearchResult = {
  145. results: Array<{
  146. place_id: string;
  147. name: string;
  148. formatted_address: string;
  149. geometry: {
  150. location: LatLng;
  151. };
  152. rating?: number;
  153. user_ratings_total?: number;
  154. business_status?: string;
  155. types: string[];
  156. }>;
  157. status: string;
  158. };
  159. export type PlaceDetailsResult = {
  160. result: {
  161. place_id: string;
  162. name: string;
  163. formatted_address: string;
  164. formatted_phone_number?: string;
  165. international_phone_number?: string;
  166. website?: string;
  167. rating?: number;
  168. user_ratings_total?: number;
  169. reviews?: Array<{
  170. author_name: string;
  171. rating: number;
  172. text: string;
  173. time: number;
  174. }>;
  175. opening_hours?: {
  176. open_now: boolean;
  177. weekday_text: string[];
  178. };
  179. geometry: {
  180. location: LatLng;
  181. };
  182. };
  183. status: string;
  184. };
  185. export type ElevationResult = {
  186. results: Array<{
  187. elevation: number;
  188. location: LatLng;
  189. resolution: number;
  190. }>;
  191. status: string;
  192. };
  193. export type TimeZoneResult = {
  194. dstOffset: number;
  195. rawOffset: number;
  196. status: string;
  197. timeZoneId: string;
  198. timeZoneName: string;
  199. };
  200. export type RoadsResult = {
  201. snappedPoints: Array<{
  202. location: LatLng;
  203. originalIndex?: number;
  204. placeId: string;
  205. }>;
  206. };
  207. // ============================================================================
  208. // Google Maps API Reference
  209. // ============================================================================
  210. /**
  211. * GEOCODING - Convert between addresses and coordinates
  212. * Endpoint: /maps/api/geocode/json
  213. * Input: { address: string } OR { latlng: string } // latlng: "37.42,-122.08"
  214. * Output: GeocodingResult // results[0].geometry.location, results[0].formatted_address
  215. */
  216. /**
  217. * DIRECTIONS - Get navigation routes between locations
  218. * Endpoint: /maps/api/directions/json
  219. * Input: { origin: string, destination: string, mode?: TravelMode, waypoints?: string, alternatives?: boolean }
  220. * Output: DirectionsResult // routes[0].legs[0].distance, duration, steps
  221. */
  222. /**
  223. * DISTANCE MATRIX - Calculate travel times/distances for multiple origin-destination pairs
  224. * Endpoint: /maps/api/distancematrix/json
  225. * Input: { origins: string, destinations: string, mode?: TravelMode, units?: "metric"|"imperial" } // origins: "NYC|Boston"
  226. * Output: DistanceMatrixResult // rows[0].elements[1] = first origin to second destination
  227. */
  228. /**
  229. * PLACE SEARCH - Find businesses/POIs by text query
  230. * Endpoint: /maps/api/place/textsearch/json
  231. * Input: { query: string, location?: string, radius?: number, type?: string } // location: "40.7,-74.0"
  232. * Output: PlacesSearchResult // results[].name, rating, geometry.location, place_id
  233. */
  234. /**
  235. * NEARBY SEARCH - Find places near a specific location
  236. * Endpoint: /maps/api/place/nearbysearch/json
  237. * Input: { location: string, radius: number, type?: string, keyword?: string } // location: "40.7,-74.0"
  238. * Output: PlacesSearchResult
  239. */
  240. /**
  241. * PLACE DETAILS - Get comprehensive information about a specific place
  242. * Endpoint: /maps/api/place/details/json
  243. * Input: { place_id: string, fields?: string } // fields: "name,rating,opening_hours,website"
  244. * Output: PlaceDetailsResult // result.name, rating, opening_hours, etc.
  245. */
  246. /**
  247. * ELEVATION - Get altitude data for geographic points
  248. * Endpoint: /maps/api/elevation/json
  249. * Input: { locations?: string, path?: string, samples?: number } // locations: "39.73,-104.98|36.45,-116.86"
  250. * Output: ElevationResult // results[].elevation (meters)
  251. */
  252. /**
  253. * TIME ZONE - Get timezone information for a location
  254. * Endpoint: /maps/api/timezone/json
  255. * Input: { location: string, timestamp: number } // timestamp: Math.floor(Date.now()/1000)
  256. * Output: TimeZoneResult // timeZoneId, timeZoneName
  257. */
  258. /**
  259. * ROADS - Snap GPS traces to roads, find nearest roads, get speed limits
  260. * - /v1/snapToRoads: Input: { path: string, interpolate?: boolean } // path: "lat,lng|lat,lng"
  261. * - /v1/nearestRoads: Input: { points: string } // points: "lat,lng|lat,lng"
  262. * - /v1/speedLimits: Input: { path: string, units?: SpeedUnit }
  263. * Output: RoadsResult
  264. */
  265. /**
  266. * PLACE AUTOCOMPLETE - Real-time place suggestions as user types
  267. * Endpoint: /maps/api/place/autocomplete/json
  268. * Input: { input: string, location?: string, radius?: number }
  269. * Output: { predictions: Array<{ description: string, place_id: string }> }
  270. */
  271. /**
  272. * STATIC MAPS - Generate map images as URLs (for emails, reports, <img> tags)
  273. * Endpoint: /maps/api/staticmap
  274. * Input: URL params - center: string, zoom: number, size: string, markers?: string, maptype?: MapType
  275. * Output: Image URL (not JSON) - use directly in <img src={url} />
  276. * Note: Construct URL manually with getMapsConfig() for auth
  277. */