# Plugin Map





Map visualization component for ObjectQL data sources - displays database records as map markers based on location data.

## Installation [#installation]

```bash
npm install @object-ui/plugin-map
```

## Overview [#overview]

The `@object-ui/plugin-map` plugin provides map visualization for ObjectQL data sources. It's designed to work with object-based data providers and automatically maps record fields to map markers/pins.

**Note**: This is a basic implementation suitable for simple use cases. For production applications with advanced mapping needs, consider integrating a dedicated mapping library like Mapbox, Leaflet, or Google Maps.

## Features [#features]

* **ObjectQL Integration**: Works seamlessly with object/value data providers
* **Automatic Field Mapping**: Maps database fields to map markers
* **Location Support**: Handle latitude/longitude or combined location fields
* **Marker Customization**: Configurable marker titles and descriptions
* **Marker Clustering**: Group nearby markers (when many points)
* **Popup/Tooltip**: Show details on marker click
* **Interactive**: Click handling for markers

<PluginLoader plugins="['map']">
  ## Interactive Examples [#interactive-examples]

  ### Store Locations [#store-locations]

  <SchemaExample id="plugin-map/store-locator-map" />

  ### Delivery Tracking [#delivery-tracking]

  <SchemaExample id="plugin-map/real-time-delivery-tracking" />

  ### Event Venues [#event-venues]

  <SchemaExample id="plugin-map/event-venue-finder" />
</PluginLoader>

## Usage [#usage]

### Basic Usage with ObjectQL [#basic-usage-with-objectql]

```tsx
import '@object-ui/plugin-map'
import type { ObjectMapSchema } from '@object-ui/types'

const schema: ObjectMapSchema = {
  type: 'object-map',
  objectName: 'locations',  // Your ObjectQL object
  map: {
    latitudeField: 'lat',
    longitudeField: 'lng',
    titleField: 'name',
    descriptionField: 'address'
  }
}
```

### With Static Data [#with-static-data]

```tsx
const schema = {
  type: 'object-map',
  staticData: [
    {
      id: 1,
      name: 'Office HQ',
      lat: 37.7749,
      lng: -122.4194,
      address: '123 Main St, San Francisco, CA'
    },
    {
      id: 2,
      name: 'Warehouse',
      lat: 37.8044,
      lng: -122.2711,
      address: '456 Oak Ave, Oakland, CA'
    }
  ],
  map: {
    latitudeField: 'lat',
    longitudeField: 'lng',
    titleField: 'name',
    descriptionField: 'address'
  }
}
```

## Schema API [#schema-api]

```plaintext
{
  type: 'object-map',
  objectName?: string,              // ObjectQL object name (read third)
  staticData?: Array<any>,          // Static data array (read second)
  data?: ViewData,                  // Advanced data configuration (read first)
                                    // At least one of data / staticData / objectName is required
  filter?: Array<any>,              // Query filter, sent as $filter
  sort?: string | SortConfig[],     // Sort, sent as $orderby
  map?: ObjectMapConfig,            // Map-specific configuration
  enableClustering?: boolean,       // Cluster nearby markers (auto past 100)
  navigation?: NavigationConfig,    // Record navigation (drawer/dialog/page)
  mapStyle?: string,                // MapLibre style URL/spec
  onMarkerClick?: (record: any) => void,
  className?: string
}
```

### ObjectMapConfig [#objectmapconfig]

```plaintext
{
  latitudeField?: string,      // Field containing latitude
  longitudeField?: string,     // Field containing longitude
  locationField?: string,      // Field with combined location (alternative)
  titleField?: string,         // Field to use as marker title
  descriptionField?: string,   // Field for marker description
  zoom?: number,               // Zoom level (1-20) — opts out of the auto-fit
  center?: [number, number],   // Center coordinates [lat, lng] — opts out of the auto-fit
  style?: string               // MapLibre style URL/spec (overrides the demo default)
}
```

## Configuration [#configuration]

### Field Mapping - Separate Coordinates [#field-mapping---separate-coordinates]

When your data has separate latitude and longitude fields:

```tsx
import type { ObjectMapSchema } from '@object-ui/types';

const storeMap: ObjectMapSchema = {
  type: 'object-map',
  objectName: 'stores',
  map: {
    latitudeField: 'latitude',
    longitudeField: 'longitude',
    titleField: 'storeName',
    descriptionField: 'storeAddress'
  }
};
```

### Field Mapping - Combined Location [#field-mapping---combined-location]

When your data has a combined location field:

```tsx
import type { ObjectMapSchema } from '@object-ui/types';

const placeMap: ObjectMapSchema = {
  type: 'object-map',
  objectName: 'places',
  map: {
    locationField: 'coordinates',  // e.g., "37.7749,-122.4194" or {lat: 37.7749, lng: -122.4194}
    titleField: 'placeName',
    descriptionField: 'description'
  }
};
```

### Initial Camera [#initial-camera]

By default the map has no fixed camera: on load it **fits the records it queried**.
The marker set's bounding box is measured along the shortest arc that contains
every marker — so a set straddling the antimeridian is framed across the line
rather than around the far side of the planet — and the map fits that box with
padding, up to a city-scale zoom ceiling (a single record does not become a
rooftop view). A view with data therefore never opens on an empty viewport.

Two cases sit outside the fit:

* **No records** (empty result, or every record missing coordinates): nothing to
  fit, so the map opens on the whole world.
* **A declared camera** (below): the declaration wins and the fit is skipped.

### Zoom and Center [#zoom-and-center]

Declare either one to take the camera over and opt this view out of the auto-fit:

```tsx
import type { ObjectMapSchema } from '@object-ui/types';

const cameraMap: ObjectMapSchema = {
  type: 'object-map',
  objectName: 'locations',
  map: {
    latitudeField: 'lat',
    longitudeField: 'lng',
    titleField: 'name',
    zoom: 12,                    // Zoom level (1-20)
    center: [37.7749, -122.4194] // [latitude, longitude]
  }
};
```

Declaring only one half keeps the other derived: `zoom` on its own is applied at
the centre of the records, `center` on its own at a continental zoom.

## Data Providers [#data-providers]

### Object Provider (Database) [#object-provider-database]

```tsx
import type { ObjectMapSchema } from '@object-ui/types';

const retailStores: ObjectMapSchema = {
  type: 'object-map',
  objectName: 'retail_stores',
  map: {
    latitudeField: 'store_lat',
    longitudeField: 'store_lng',
    titleField: 'store_name',
    descriptionField: 'store_address'
  }
};
```

### Value Provider (Static) [#value-provider-static]

```tsx
const staticLocations = {
  type: 'object-map',
  staticData: [
    { id: 1, name: 'Location 1', lat: 37.7749, lng: -122.4194 },
    { id: 2, name: 'Location 2', lat: 37.8044, lng: -122.2711 }
  ],
  map: {
    latitudeField: 'lat',
    longitudeField: 'lng',
    titleField: 'name'
  }
};
```

### API Provider — not implemented [#api-provider--not-implemented]

`data.provider: 'api'` has no fetch implementation in `ObjectMap`. A schema that
reaches this branch logs `API provider not yet implemented for ObjectMap`, sets
the record set to empty and renders a map with no markers; `endpoint` and
`method` have no read point anywhere in the package. Without a `DataSource` it
fails one step earlier, with `DataSource required for object/api providers`.

Read from the database with the **Object Provider** above, or pass records you
already hold with the **Value Provider**.

## Event Handling [#event-handling]

### Marker Click [#marker-click]

```tsx
import type { ObjectMapSchema } from '@object-ui/types';

const clickableMap: ObjectMapSchema = {
  type: 'object-map',
  objectName: 'locations',
  map: {
    latitudeField: 'lat',
    longitudeField: 'lng',
    titleField: 'name'
  },
  onMarkerClick: (location: Record<string, unknown>) => {
    console.log('Marker clicked:', location);
    // Show location details
    // Navigate to location page
    // Open directions
  }
};
```

## Examples [#examples]

### Store Locator [#store-locator]

```tsx
import type { ObjectMapSchema } from '@object-ui/types';

const storeLocator: ObjectMapSchema = {
  type: 'object-map',
  objectName: 'retail_locations',
  map: {
    latitudeField: 'latitude',
    longitudeField: 'longitude',
    titleField: 'storeName',
    descriptionField: 'fullAddress',
    zoom: 10,
    center: [37.7749, -122.4194]  // San Francisco
  },
  onMarkerClick: (store: Record<string, unknown>) => {
    // Show store details
    // Display hours, phone, etc.
  }
}
```

### Delivery Tracking [#delivery-tracking-1]

```tsx
import type { ObjectMapSchema } from '@object-ui/types';

const deliveryMap: ObjectMapSchema = {
  type: 'object-map',
  objectName: 'active_deliveries',
  map: {
    latitudeField: 'current_lat',
    longitudeField: 'current_lng',
    titleField: 'driver_name',
    descriptionField: 'delivery_address'
  },
  onMarkerClick: (delivery: Record<string, unknown>) => {
    // Show delivery details
    // Contact driver
  }
}
```

### Real Estate Listings [#real-estate-listings]

```tsx
import type { ObjectMapSchema } from '@object-ui/types';

const propertyMap: ObjectMapSchema = {
  type: 'object-map',
  objectName: 'properties',
  map: {
    latitudeField: 'property_lat',
    longitudeField: 'property_lng',
    titleField: 'property_address',
    descriptionField: 'property_details',
    zoom: 12
  },
  onMarkerClick: (property: Record<string, unknown>) => {
    // Show property details
    // Display photos, price, etc.
  }
}
```

### Event Venues [#event-venues-1]

```tsx
const venueMap = {
  type: 'object-map',
  staticData: [
    {
      id: 1,
      venueName: 'Conference Center',
      lat: 37.7833,
      lng: -122.4167,
      details: 'Capacity: 500 people'
    },
    {
      id: 2,
      venueName: 'Exhibition Hall',
      lat: 37.7891,
      lng: -122.3894,
      details: 'Capacity: 1000 people'
    },
    {
      id: 3,
      venueName: 'Outdoor Amphitheater',
      lat: 37.7694,
      lng: -122.4862,
      details: 'Capacity: 2000 people'
    }
  ],
  map: {
    latitudeField: 'lat',
    longitudeField: 'lng',
    titleField: 'venueName',
    descriptionField: 'details',
    zoom: 11
  },
  onMarkerClick: (venue: Record<string, unknown>) => {
    // Show venue details
    // Book venue
  }
}
```

### Field Service Map [#field-service-map]

```tsx
import type { ObjectMapSchema } from '@object-ui/types';

const serviceMap: ObjectMapSchema = {
  type: 'object-map',
  objectName: 'service_calls',
  map: {
    latitudeField: 'customer_latitude',
    longitudeField: 'customer_longitude',
    titleField: 'customer_name',
    descriptionField: 'service_type',
    zoom: 10
  },
  onMarkerClick: (serviceCall: Record<string, unknown>) => {
    // Show service call details
    // Assign technician
    // Get directions
  }
}
```

## Typical Use Cases [#typical-use-cases]

1. **Store Locator**: Display retail store locations
2. **Fleet Tracking**: Show vehicle or delivery locations
3. **Real Estate**: Display property listings on a map
4. **Event Venues**: Show event or venue locations
5. **Field Service**: Track service calls or technician locations
6. **Customer Locations**: Visualize customer distribution
7. **Asset Tracking**: Show location of equipment or assets

## Location Data Formats [#location-data-formats]

### Separate Fields [#separate-fields]

{/* doc-snippet: fragment — a SHAPE excerpt of one of the READER's own data records, not an expression: a bare object literal at statement position parses as a block with labels (measured: TS1005 x3). No ObjectUI type describes it — these are the caller's own row fields, named by `map.latitudeField` / `map.longitudeField` above */}

```tsx
{
  id: 1,
  name: 'Location',
  latitude: 37.7749,
  longitude: -122.4194
}
```

### Combined String [#combined-string]

{/* doc-snippet: fragment — a SHAPE excerpt of one of the READER's own data records, not an expression: a bare object literal at statement position parses as a block with labels (measured: TS1005 x2). No ObjectUI type describes it — `coordinates` is the caller's own field, named by `map.locationField` above */}

```tsx
{
  id: 1,
  name: 'Location',
  coordinates: '37.7749,-122.4194'
}
```

### Object Format [#object-format]

{/* doc-snippet: fragment — a SHAPE excerpt of one of the READER's own data records, not an expression: a bare object literal at statement position parses as a block with labels (measured: TS1005 x3). No ObjectUI type describes it — `location` is the caller's own field, named by `map.locationField` above */}

```tsx
{
  id: 1,
  name: 'Location',
  location: {
    lat: 37.7749,
    lng: -122.4194
  }
}
```

## Map Controls [#map-controls]

The map typically includes:

* **Zoom controls**: Zoom in/out buttons
* **Pan**: Drag to move around the map
* **Marker click**: Click markers to show details
* **Auto-fit**: Automatically adjust view to show all markers

## Integration with Full-featured Map Libraries [#integration-with-full-featured-map-libraries]

For production applications, you may want to integrate with:

* **Mapbox**: Advanced styling, 3D maps, routing
* **Google Maps**: Street view, directions, extensive POI data
* **Leaflet**: Open-source, lightweight, customizable
* **OpenStreetMap**: Free, community-driven map data

This plugin provides a basic implementation. For advanced features like:

* Directions/routing
* Street view
* 3D buildings
* Traffic data
* Custom map styles
* Advanced geocoding

Consider using one of the full-featured mapping libraries above.

## TypeScript Support [#typescript-support]

```plaintext
import type { ObjectMapSchema, ObjectMapConfig } from '@object-ui/types'

const mapConfig: ObjectMapConfig = {
  latitudeField: 'lat',
  longitudeField: 'lng',
  titleField: 'name',
  descriptionField: 'description',
  zoom: 12,
  center: [37.7749, -122.4194]
}

const mapSchema: ObjectMapSchema = {
  type: 'object-map',
  objectName: 'locations',
  map: mapConfig
}
```

## Related Documentation [#related-documentation]

* [Plugin System Overview](/docs/guide/plugins)
* [Package README](https://github.com/objectstack-ai/objectui/tree/main/packages/plugin-map)
