Skip to main content

Amap

This guide explains how to integrate Amap (高德地图) services into your superun application. The integration uses an edge function to securely retrieve API configuration, then initializes the map in the frontend.

Documentation

For more details about Amap APIs, refer to the official documentation:

Environment Variables

Add the following environment variables to your supabase secrets, the plugin_secret_prefix MUST be SUPERUN
  • AMAP_WEB_API_KEY - Amap Web API Key
  • AMAP_JS_API_KEY - Amap JavaScript API Key
  • AMAP_SECURITY_CODE - Amap Security Code
Important: Never expose these API keys directly in client-side code. Always use an edge function to retrieve the configuration securely.

Step 1: Create Edge Function

Create a Supabase Edge Function to securely return the Amap API configuration:
// supabase/functions/amap-config/index.ts

serve(async (req) => {
  // Return Amap configuration
  return new Response(
    JSON.stringify({
      webApiKey: Deno.env.get("SUPERUN_AMAP_WEB_API_KEY"),
      jsApiKey: Deno.env.get("SUPERUN_AMAP_JS_API_KEY"),
      securityCode: Deno.env.get("SUPERUN_AMAP_SECURITY_CODE"),
    }),
    {
      headers: { "Content-Type": "application/json" },
    }
  );
});

Step 2: Install Amap Loader

Install the Amap JavaScript API loader:
npm i @amap/[email protected] --save

Step 3: Frontend Integration

import { useEffect, useRef, useState } from "react";
import AMapLoader from "@amap/amap-jsapi-loader";
import { supabase } from "@/integrations/supabase/client";

export default function AmapComponent({
  center = [116.397428, 39.90923],
  zoom = 13,
  mapStyle = "amap://styles/dark",
  onMapReady,
  onMapError,
  className = "h-full w-full",
}: AmapComponentProps) {
  const mapContainerRef = useRef<HTMLDivElement>(null);
  const mapInstanceRef = useRef<any>(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    const initMap = async () => {
      try {
        // Step 1: Fetch API key from edge function
        const { data, error } = await supabase.functions.invoke("amap-config");
        
        if (error) throw error;
        
        if (!data?.jsApiKey) {
          throw new Error("No API key returned");
        }

        // Step 2: Configure security code
        (window as any)._AMapSecurityConfig = {
          securityJsCode: data.securityCode,
        };

        // Step 3: Load AMap using official loader
        setIsLoading(true);
        
        const AMap = await AMapLoader.load({
          key: data.jsApiKey, // Use key from Edge Function
          version: "2.0",
          plugins: [
            "AMap.Geolocation",
            "AMap.Scale",
            "AMap.ToolBar",
            "AMap.Marker",
            "AMap.InfoWindow",
          ],
        });

        // Step 4: Initialize map
        if (!mapContainerRef.current) return;
        
        const map = new AMap.Map(mapContainerRef.current, {
          zoom,
          center,
          mapStyle,
          viewMode: "3D",
        });

        mapInstanceRef.current = map;
        setIsLoading(false);

        // Step 6: Callback with map instance
        if (onMapReady) {
          onMapReady(map);
        }
      } catch (error) {
        console.error("Failed to initialize Amap:", error);
        setIsLoading(false);
        if (onMapError) {
          onMapError(error as Error);
        }
      }
    };

    initMap();

    // Cleanup: destroy map on unmount
    return () => {
      if (mapInstanceRef.current) {
        mapInstanceRef.current.destroy();
        mapInstanceRef.current = null;
      }
    };
  }, []); // Run once on mount

  return (
    <div className="relative h-full w-full">
      <div ref={mapContainerRef} className={className} />
      
      {/* Loading overlay */}
      {isLoading && (
        <div className="absolute inset-0 flex items-center justify-center bg-stone-900/90">
          <div className="text-center">
            <div className="mb-4 h-12 w-12 animate-spin rounded-full border-4 border-stone-700 border-t-amber-500 mx-auto" />
            <p className="text-sm text-stone-400">加载地图中...</p>
          </div>
        </div>
      )}
    </div>
  );
}