next-maps

repository·main·Indexed 19 days ago

https://github.com/anmolsaini16/next-maps

A lightweight map application built with Next.js (App Router), Mapbox GL JS, and the Mapbox Searchbox API. It features a responsive UI using Tailwind CSS and shadcn/ui, providing components like LocationMarker and LocationPopup, a useMap hook for Mapbox instance access, and utility functions for searching and retrieving location features.

Tokens
3.9K
Snippets
15
Records
15
Agent score
66%

What's inside next-maps

  1. Configure Mapbox environment variables

    main

    The project requires two environment variables to function correctly with Mapbox GL JS and the Mapbox Searchbox API. These must be prefixed with NEXT_PUBLIC_ to be accessible in the browser.

    NEXT_PUBLIC_MAPBOX_TOKEN=your_mapbox_access_token
    NEXT_PUBLIC_MAPBOX_SESSION_TOKEN=your_uuidv4_session_token
  2. Install and run next-maps locally

    main

    To get started with the next-maps project, clone the repository, install the necessary dependencies using npm, and launch the development server.

    git clone https://github.com/AnmolSaini16/next-maps.git
    cd your-repo
    npm install
    npm run dev
  3. Search for location suggestions with `searchLocations`

    main

    Use searchLocations to get a list of LocationSuggestion objects based on a text query. This function uses the Mapbox Search Box API. It requires NEXT_PUBLIC_MAPBOX_TOKEN to be configured in your environment variables.

    By default, it searches within the US and limits results to 5. You can provide a proximity array as [longitude, latitude] to bias results toward a specific area.

    import { searchLocations } from '@/lib/mapbox/api';
    
    const suggestions = await searchLocations({
      query: 'New York',
      country: 'US',
      limit: 10,
      proximity: [-74.006, 40.7128] // [longitude, latitude]
    });
  4. Retrieve detailed location features with `retrieveLocation`

    main

    Use retrieveLocation to fetch full LocationFeature details for a specific location using its mapboxId. This is typically used after a user selects a suggestion from searchLocations to get precise coordinates and metadata.

    This function requires NEXT_PUBLIC_MAPBOX_TOKEN to be configured.

    import { retrieveLocation } from '@/lib/mapbox/api';
    
    // mapboxId is obtained from a LocationSuggestion
    const features = await retrieveLocation('starting_mapbox_id_here');
  5. Configure Mapbox fly-to behavior and search defaults

    main

    The MAP_CONSTANTS object provides default configuration values for map animations and search functionality.

    Fly-to settings:

    • ZOOM: Default zoom level of 14.
    • SPEED: Default animation speed of 4.
    • DURATION: Default animation duration in milliseconds of 1000.

    Search settings:

    • DEBOUNCE_MS: Search input debounce delay of 400ms.
    • DEFAULT_LIMIT: Maximum number of search results returned (5).
    • DEFAULT_COUNTRY: Default country code for searches ("US").
    • DEFAULT_PROXIMITY: Default geographic coordinates for proximity-based searches (defaults to San Francisco: [-122.4194, 37.7749]).
    export const MAP_CONSTANTS = {
      FLY_TO: {
        ZOOM: 14,
        SPEED: 4,
        DURATION: 1000,
      },
      SEARCH: {
        DEBOUNCE_MS: 400,
        DEFAULT_LIMIT: 5,
        DEFAULT_COUNTRY: "US",
        DEFAULT_PROXIMITY: [-122.4194, 37.7749] as [number, number],
      },
    } as const;
  6. LocationFeature property schema for LocationPopup

    main

    When providing a LocationFeature to the LocationPopup component, the following properties in the properties object are used to populate the UI:

    • name: The display name of the location.
    • full_address or address: The physical address.
    • poi_category: An array of strings used for category badges and icon selection.
    • brand: A string representing the brand name (displayed if different from the name).
    • operational_status: A string (e.g., active) used to show an 'Open' or status badge.
    • maki: A string used to look up a specific icon in the iconMap.
    • mapbox_id: Used to display a truncated ID in the footer.
    • coordinates: An object containing latitude and longitude (fallback if geometry.coordinates is missing).

    Coordinates can also be provided via geometry.coordinates as [longitude, latitude].

    {
      "properties": {
        "name": "string",
        "full_address": "string",
        "address": "string",
        "poi_category": ["string"],
        "brand": ["string"],
        "operational_status": "string",
        "maki": "string",
        "mapbox_id": "string",
        "coordinates": {
          "latitude": "number",
          "longitude": "number"
        }
      },
      "geometry": {
        "coordinates": ["number", "number"]
      }
    }
  7. SearchOptions interface

    main

    The configuration object passed to searchLocations.

    PropertyTypeDefaultDescription
    querystringRequiredThe search text.
    countrystring'US'ISO country code to limit results.
    limitnumber5Maximum number of suggestions to return.
    proximity[number, number]undefined[longitude, latitude] to bias results.
    signalAbortSignalundefinedAllows canceling the fetch request.
    export interface SearchOptions {
      query: string;
      country?: string;
      limit?: number;
      proximity?: [number, number]; // [longitude, latitude]
      signal?: AbortSignal;
    }
  8. Map category icons via iconMap

    main

    The iconMap object provides a mapping from category strings (e.g., 'cafe', 'restaurant', 'gym') to Lucide React icons. This can be used to render appropriate visual indicators for different types of locations on the map.

    import { iconMap } from '@/lib/mapbox/utils';
    
    // Example usage:
    const category = 'restaurant';
    const Icon = iconMap[category]; // Returns <Utensils className="h-5 w-5" />
  9. Use the LocationPopup component

    main

    The LocationPopup component is used to display a detailed information popup for a specific location on a Mapbox map. It renders a popup containing the location's name, address, brand, operational status, categories, and coordinates.

    It expects a location prop of type LocationFeature. The component automatically resolves coordinates from geometry.coordinates or properties.coordinates and determines the appropriate icon using the iconMap utility based on the maki property or poi_category array.

    import { LocationPopup } from "@/components/location-popup";
    import { LocationFeature } from "@/lib/mapbox/utils";
    
    // Example usage within a map component
    function MyMap() {
      const selectedLocation: LocationFeature = {
        properties: {
          name: "Coffee Shop",
          full_address: "123 Main St",
          poi_category: ["cafe", "food"],
          operational_status: "active",
          mapbox_id: "mapbox_12345"
        },
        geometry: {
          coordinates: [-122.4194, 37.7749]
        }
      };
    
      return <LocationPopup location={selectedLocation} />;
    }
  10. Use the cn utility for merging Tailwind CSS classes

    main

    The cn utility function is used to conditionally join CSS class names and resolve Tailwind CSS class conflicts. It combines clsx for conditional logic and tailwind-merge to ensure that the last class in the list takes precedence when there are conflicting Tailwind styles (e.g., merging px-2 and px-4 results in px-4).

    import { cn } from '@/lib/utils';
    
    // Example usage with conditional classes and Tailwind conflict resolution
    const className = cn(
      'text-base font-medium', // base classes
      isError && 'text-red-500', // conditional class
      'px-2 py-1 px-4' // conflict: 'px-4' will win due to twMerge
    );
  11. LocationFeature type definition

    main

    The LocationFeature type defines the structure of a GeoJSON Feature representing a specific location. It includes geometry (Point) and a detailed properties object containing metadata such as name, address, context (country, region, etc.), and coordinates.

    export type LocationFeature = {
      type: "Feature";
      geometry: {
        type: "Point";
        coordinates: [number, number];
      };
      properties: {
        name: string;
        name_preferred?: string;
        mapbox_id: string;
        feature_type: string;
        address?: string;
        full_address?: string;
        place_formatted?: string;
        context: {
          country?: { name: string; country_code: string; country_code_alpha_3: string };
          region?: { name: string; region_code: string; region_code_full: string };
          postcode?: { name: string };
          district?: { name: string };
          place?: { name: string };
          locality?: { name: string };
          neighborhood?: { name: string };
          address?: { name: string; address_number?: string; street_name?: string };
          street?: { name: string };
        };
        coordinates: {
          latitude: number;
          longitude: number;
          accuracy?: string;
          routable_points?: { name: string; latitude: number; longitude: number; note?: string }[];
        };
        language?: string;
        maki?: string;
        poi_category?: string[];
        poi_category_ids?: string[];
        brand?: string[];
        brand_id?: string[];
        external_ids?: Record<string, string>;
        metadata?: Record<string, unknown>;
        bbox?: [number, number, number, number];
        operational_status?: string;
      };
    };