Initial project bootstrap
这个提交包含在:
319
server/_core/map.ts
普通文件
319
server/_core/map.ts
普通文件
@@ -0,0 +1,319 @@
|
||||
/**
|
||||
* Google Maps API Integration for Manus WebDev Templates
|
||||
*
|
||||
* Main function: makeRequest<T>(endpoint, params) - Makes authenticated requests to Google Maps APIs
|
||||
* All credentials are automatically injected. Array parameters use | as separator.
|
||||
*
|
||||
* See API examples below the type definitions for usage patterns.
|
||||
*/
|
||||
|
||||
import { ENV } from "./env";
|
||||
|
||||
// ============================================================================
|
||||
// Configuration
|
||||
// ============================================================================
|
||||
|
||||
type MapsConfig = {
|
||||
baseUrl: string;
|
||||
apiKey: string;
|
||||
};
|
||||
|
||||
function getMapsConfig(): MapsConfig {
|
||||
const baseUrl = ENV.forgeApiUrl;
|
||||
const apiKey = ENV.forgeApiKey;
|
||||
|
||||
if (!baseUrl || !apiKey) {
|
||||
throw new Error(
|
||||
"Google Maps proxy credentials missing: set BUILT_IN_FORGE_API_URL and BUILT_IN_FORGE_API_KEY"
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
baseUrl: baseUrl.replace(/\/+$/, ""),
|
||||
apiKey,
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Core Request Handler
|
||||
// ============================================================================
|
||||
|
||||
interface RequestOptions {
|
||||
method?: "GET" | "POST";
|
||||
body?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make authenticated requests to Google Maps APIs
|
||||
*
|
||||
* @param endpoint - The API endpoint (e.g., "/maps/api/geocode/json")
|
||||
* @param params - Query parameters for the request
|
||||
* @param options - Additional request options
|
||||
* @returns The API response
|
||||
*/
|
||||
export async function makeRequest<T = unknown>(
|
||||
endpoint: string,
|
||||
params: Record<string, unknown> = {},
|
||||
options: RequestOptions = {}
|
||||
): Promise<T> {
|
||||
const { baseUrl, apiKey } = getMapsConfig();
|
||||
|
||||
// Construct full URL: baseUrl + /v1/maps/proxy + endpoint
|
||||
const url = new URL(`${baseUrl}/v1/maps/proxy${endpoint}`);
|
||||
|
||||
// Add API key as query parameter (standard Google Maps API authentication)
|
||||
url.searchParams.append("key", apiKey);
|
||||
|
||||
// Add other query parameters
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null) {
|
||||
url.searchParams.append(key, String(value));
|
||||
}
|
||||
});
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
method: options.method || "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: options.body ? JSON.stringify(options.body) : undefined,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(
|
||||
`Google Maps API request failed (${response.status} ${response.statusText}): ${errorText}`
|
||||
);
|
||||
}
|
||||
|
||||
return (await response.json()) as T;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Type Definitions
|
||||
// ============================================================================
|
||||
|
||||
export type TravelMode = "driving" | "walking" | "bicycling" | "transit";
|
||||
export type MapType = "roadmap" | "satellite" | "terrain" | "hybrid";
|
||||
export type SpeedUnit = "KPH" | "MPH";
|
||||
|
||||
export type LatLng = {
|
||||
lat: number;
|
||||
lng: number;
|
||||
};
|
||||
|
||||
export type DirectionsResult = {
|
||||
routes: Array<{
|
||||
legs: Array<{
|
||||
distance: { text: string; value: number };
|
||||
duration: { text: string; value: number };
|
||||
start_address: string;
|
||||
end_address: string;
|
||||
start_location: LatLng;
|
||||
end_location: LatLng;
|
||||
steps: Array<{
|
||||
distance: { text: string; value: number };
|
||||
duration: { text: string; value: number };
|
||||
html_instructions: string;
|
||||
travel_mode: string;
|
||||
start_location: LatLng;
|
||||
end_location: LatLng;
|
||||
}>;
|
||||
}>;
|
||||
overview_polyline: { points: string };
|
||||
summary: string;
|
||||
warnings: string[];
|
||||
waypoint_order: number[];
|
||||
}>;
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type DistanceMatrixResult = {
|
||||
rows: Array<{
|
||||
elements: Array<{
|
||||
distance: { text: string; value: number };
|
||||
duration: { text: string; value: number };
|
||||
status: string;
|
||||
}>;
|
||||
}>;
|
||||
origin_addresses: string[];
|
||||
destination_addresses: string[];
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type GeocodingResult = {
|
||||
results: Array<{
|
||||
address_components: Array<{
|
||||
long_name: string;
|
||||
short_name: string;
|
||||
types: string[];
|
||||
}>;
|
||||
formatted_address: string;
|
||||
geometry: {
|
||||
location: LatLng;
|
||||
location_type: string;
|
||||
viewport: {
|
||||
northeast: LatLng;
|
||||
southwest: LatLng;
|
||||
};
|
||||
};
|
||||
place_id: string;
|
||||
types: string[];
|
||||
}>;
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type PlacesSearchResult = {
|
||||
results: Array<{
|
||||
place_id: string;
|
||||
name: string;
|
||||
formatted_address: string;
|
||||
geometry: {
|
||||
location: LatLng;
|
||||
};
|
||||
rating?: number;
|
||||
user_ratings_total?: number;
|
||||
business_status?: string;
|
||||
types: string[];
|
||||
}>;
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type PlaceDetailsResult = {
|
||||
result: {
|
||||
place_id: string;
|
||||
name: string;
|
||||
formatted_address: string;
|
||||
formatted_phone_number?: string;
|
||||
international_phone_number?: string;
|
||||
website?: string;
|
||||
rating?: number;
|
||||
user_ratings_total?: number;
|
||||
reviews?: Array<{
|
||||
author_name: string;
|
||||
rating: number;
|
||||
text: string;
|
||||
time: number;
|
||||
}>;
|
||||
opening_hours?: {
|
||||
open_now: boolean;
|
||||
weekday_text: string[];
|
||||
};
|
||||
geometry: {
|
||||
location: LatLng;
|
||||
};
|
||||
};
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ElevationResult = {
|
||||
results: Array<{
|
||||
elevation: number;
|
||||
location: LatLng;
|
||||
resolution: number;
|
||||
}>;
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type TimeZoneResult = {
|
||||
dstOffset: number;
|
||||
rawOffset: number;
|
||||
status: string;
|
||||
timeZoneId: string;
|
||||
timeZoneName: string;
|
||||
};
|
||||
|
||||
export type RoadsResult = {
|
||||
snappedPoints: Array<{
|
||||
location: LatLng;
|
||||
originalIndex?: number;
|
||||
placeId: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Google Maps API Reference
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* GEOCODING - Convert between addresses and coordinates
|
||||
* Endpoint: /maps/api/geocode/json
|
||||
* Input: { address: string } OR { latlng: string } // latlng: "37.42,-122.08"
|
||||
* Output: GeocodingResult // results[0].geometry.location, results[0].formatted_address
|
||||
*/
|
||||
|
||||
/**
|
||||
* DIRECTIONS - Get navigation routes between locations
|
||||
* Endpoint: /maps/api/directions/json
|
||||
* Input: { origin: string, destination: string, mode?: TravelMode, waypoints?: string, alternatives?: boolean }
|
||||
* Output: DirectionsResult // routes[0].legs[0].distance, duration, steps
|
||||
*/
|
||||
|
||||
/**
|
||||
* DISTANCE MATRIX - Calculate travel times/distances for multiple origin-destination pairs
|
||||
* Endpoint: /maps/api/distancematrix/json
|
||||
* Input: { origins: string, destinations: string, mode?: TravelMode, units?: "metric"|"imperial" } // origins: "NYC|Boston"
|
||||
* Output: DistanceMatrixResult // rows[0].elements[1] = first origin to second destination
|
||||
*/
|
||||
|
||||
/**
|
||||
* PLACE SEARCH - Find businesses/POIs by text query
|
||||
* Endpoint: /maps/api/place/textsearch/json
|
||||
* Input: { query: string, location?: string, radius?: number, type?: string } // location: "40.7,-74.0"
|
||||
* Output: PlacesSearchResult // results[].name, rating, geometry.location, place_id
|
||||
*/
|
||||
|
||||
/**
|
||||
* NEARBY SEARCH - Find places near a specific location
|
||||
* Endpoint: /maps/api/place/nearbysearch/json
|
||||
* Input: { location: string, radius: number, type?: string, keyword?: string } // location: "40.7,-74.0"
|
||||
* Output: PlacesSearchResult
|
||||
*/
|
||||
|
||||
/**
|
||||
* PLACE DETAILS - Get comprehensive information about a specific place
|
||||
* Endpoint: /maps/api/place/details/json
|
||||
* Input: { place_id: string, fields?: string } // fields: "name,rating,opening_hours,website"
|
||||
* Output: PlaceDetailsResult // result.name, rating, opening_hours, etc.
|
||||
*/
|
||||
|
||||
/**
|
||||
* ELEVATION - Get altitude data for geographic points
|
||||
* Endpoint: /maps/api/elevation/json
|
||||
* Input: { locations?: string, path?: string, samples?: number } // locations: "39.73,-104.98|36.45,-116.86"
|
||||
* Output: ElevationResult // results[].elevation (meters)
|
||||
*/
|
||||
|
||||
/**
|
||||
* TIME ZONE - Get timezone information for a location
|
||||
* Endpoint: /maps/api/timezone/json
|
||||
* Input: { location: string, timestamp: number } // timestamp: Math.floor(Date.now()/1000)
|
||||
* Output: TimeZoneResult // timeZoneId, timeZoneName
|
||||
*/
|
||||
|
||||
/**
|
||||
* ROADS - Snap GPS traces to roads, find nearest roads, get speed limits
|
||||
* - /v1/snapToRoads: Input: { path: string, interpolate?: boolean } // path: "lat,lng|lat,lng"
|
||||
* - /v1/nearestRoads: Input: { points: string } // points: "lat,lng|lat,lng"
|
||||
* - /v1/speedLimits: Input: { path: string, units?: SpeedUnit }
|
||||
* Output: RoadsResult
|
||||
*/
|
||||
|
||||
/**
|
||||
* PLACE AUTOCOMPLETE - Real-time place suggestions as user types
|
||||
* Endpoint: /maps/api/place/autocomplete/json
|
||||
* Input: { input: string, location?: string, radius?: number }
|
||||
* Output: { predictions: Array<{ description: string, place_id: string }> }
|
||||
*/
|
||||
|
||||
/**
|
||||
* STATIC MAPS - Generate map images as URLs (for emails, reports, <img> tags)
|
||||
* Endpoint: /maps/api/staticmap
|
||||
* Input: URL params - center: string, zoom: number, size: string, markers?: string, maptype?: MapType
|
||||
* Output: Image URL (not JSON) - use directly in <img src={url} />
|
||||
* Note: Construct URL manually with getMapsConfig() for auth
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
在新工单中引用
屏蔽一个用户