Options
All
  • Public
  • Public/Protected
  • All
Menu

@mappedin/react-native-sdk

Mappedin React Native SDK

Installation

NPM:

npm install @mappedin/react-native-sdk react-native-webview

YARN:

yarn add @mappedin/react-native-sdk react-native-webview

Usage:

We provide both a declarative API using props and an imperative API with methods. For example, to listen to polygon clicks, we provide a prop onPolygonClicked; in order to focus the map onto this polygon, we provide an imperative API method focusOn.

Component Signature:

const MiMapView = (props: TMapViewProps) => React.ReactElement;

Props:

Documented in Detail here

Prop Description Signature Required
options Options to initialize MapView with TMiMapViewOptions Yes
ref Exposes Imperative API MapViewStore React.MutableRefObject<MapViewStore> No
onPolygonClicked Gets called when clicking interactive polygon ({ polygon: MappedinPolygon }) => void No
onDataLoaded Fired when Mappedin data is loaded ({ venueData: Mappedin }) => void No
onFirstMapLoaded Fires when map can first be interacted with () => void No
(deprecated) onBlueDotUpdated Fires when BlueDot updates ({ update: TBlueDotUpdate }) => void No
onBlueDotStateChanged Fires when BlueDot state changes ({ stateChange: TBlueDotStateChange }) => void No
onBlueDotPositionUpdated Fires when BlueDot position changes ({ update: TBlueDotPositionUpdate }) => void No
onStateChanged Fires when SDK State changes ({ state: STATE }) => void No

Imperative API

Documented in Detail here: MapViewStore

Render map

Example:

import { Mappedin } from '@mappedin/react-native-sdk';

const options = {
  clientId: '****',
  clientSecret: '***',
  venue: 'venue-slug',
  perspective: 'Website',
};

// Render map
const App = () => {
  return <MiMapView style={{ flex: 1 }} options={options} />;
};

Example:

import { MiMapView } from '@mappedin/react-native-sdk';
import type { Mappedin } from '@mappedin/react-native-sdk';

const App = () => {
  const [venueData, setVenueData] = React.useState<Mappedin>({});

  return (
    <MiMapView
      options={options}
      onDataLoaded={({ venueData }) => {
        setVenueData(venueData);
      }}
    />
  );
};

Fetching venue data outside of the MiMapView component

It is possible to fetch venue data (locations, maps, etc), outside of the MiMapView component. This can be helpful for cases where a map isn't always needed or there is a need to fetch the data first, and render the map later.


import { getVenue, MiMapView } from '@mappedin/react-native-sdk';

const options = {
  clientId: '****',
  clientSecret: '***',
  venue: 'venue-slug',
  perspective: 'Website',
};

const App = () => {
  const [venueData, setVenueData] = React.useState<Mappedin>({});

  useEffect(() => {
    async function init() {
      const venueData = await getVenue(options);
      console.log(venueData.locations);
      setVenueData(venueData);
    }
  }, []);

  return (
    {venueData != null &&
      <MiMapView
        style={{ flex: 1 }}
        options={options}
        // pass venueData in
        venueData={venueData}
      />
    }
  );
};

Listen to polygon clicks

import { MiMapView } from '@mappedin/react-native-sdk';
import type { MappedinLocation, Mappedin } from '@mappedin/react-native-sdk';

const App = () => {
  const [venueData, setVenueData] = React.useState<Mappedin>({});
  const [
    selectedLocation,
    setSelectedLocation,
  ] = React.useState<MappedinLocation>({});

  return (
    <MiMapView
      style={{ flex: 1 }}
      options={options}
      onDataLoaded={({ venueData: Mappedin }) => {
        setVenueData(venueData);
      }}
      onPolygonClicked={({ polygon }) => {
        setSelectedLocation(polygon.locations[0]);
      }}
    />
  );
};

Rotate the map

import { MiMapView } from '@mappedin/react-native-sdk';
import type { IMapViewStore } from '@mappedin/react-native-sdk';

// Imperative API
const mapView = React.useRef<IMapViewStore>();

const App = () => {
  return (
    <MiMapView
      style={{ flex: 1 }}
      ref={mapView}
      options={options}
      onFirstMapLoaded={() => {
        mapView.current!.CameraControls.setRotation(90);
      }}
    />
  );
};

Focus on the polygon and highlight it

import { MiMapView } from '@mappedin/react-native-sdk';
import type {
  MappedinPolygon,
  IMapViewStore,
  Mappedin,
} from '@mappedin/react-native-sdk';

const App = () => {
  const [venueData, setVenueData] = React.useState<Mappedin>({});
  const [
    selectedLocation,
    setSelectedLocation,
  ] = React.useState<MappedinLocation>({});

  // Imperative API
  const mapView = React.useRef<IMapViewStore>();

  return (
    <MiMapView
      style={{ flex: 1 }}
      ref={mapView}
      options={options}
      onDataLoaded={({ venueData: Mappedin }) => {
        setVenueData(venueData);
      }}
      onPolygonClicked={async ({ polygon: MappedinPolygon }) => {
        setSelectedLocation(polygon.locations[0]);
        mapView.current.clearAllPolygonColors();
        mapView.current.setPolygonColor(polygon, 'red');
        // lets wait for the focusOn to complete
        await mapView.current.focusOn({
          polygons: [polygon],
        });
        console.log('success!');
      }}
    />
  );
};

Enable Blue Dot and listen to update events

import { MiMapView } from '@mappedin/react-native-sdk';
import type {
  TBlueDotUpdate,
  IMapViewStore,
  MappedinMap,
  MappedinNode,
} from '@mappedin/react-native-sdk';

// Imperative API
const mapView = React.useRef<IMapViewStore>();

const App = () => {
  return (
    <MiMapView
      style={{ flex: 1 }}
      ref={mapView}
      options={options}
      onFirstMapLoaded={() => {
        mapView.current.BlueDot.enable();
      }}
      onBlueDotStateChanged={({
        stateChange,
      }: {
        stateChange: TBlueDotStateChange;
      }) => {
        // This event can be used to get real time updates as to what the nearest node is
        const {
          map,
          nearestNode,
        }: {
          map: MappedinMap;
          nearestNode: MappedinNode;
        } = stateChange;
      }}
    />
  );
};

Set SDK into "FOLLOW" mode

Setting the SDK into follow mode will lock the camera to follow the BlueDot at the center of the screen. Interacting with the map will put the SDK back into EXPLORE mode and emit onStateChanged event.

import { MiMapView } from '@mappedin/react-native-sdk';
import type {
  TBlueDotUpdate,
  IMapViewStore,
  MappedinLocation,
  MappedinNode,
} from '@mappedin/react-native-sdk';
import { STATE } from '@mappedin/react-native-sdk';

// Imperative API
const mapView = React.useRef<IMapViewStore>();
const nearestNode = React.useRef<MappedinNode>();
const [
  selectedLocation,
  setSelectedLocation,
] = React.useState<MappedinLocation>({});

const App = () => {
  return (
    <>
      <MiMapView
        style={{ flex: 1 }}
        ref={mapView}
        options={options}
        onStateChanged={({ state }) => {
          console.log('Current State is', state);
        }}
        onDataLoaded={() => {
          mapView.current.setState(STATE.FOLLOW);
          mapView.current.BlueDot.enable();
        }}
        onBlueStateChanged={({ stateChange }) => {
          nearestNode.current = stateChange.nearestNode;
        }}
        onPolygonClicked={({ polygon: MappedinPolygon }) => {
          setSelectedLocation(polygon.locations[0]);
        }}
      />
    </>
  );
};

Get Directions

This API can be used to get a directions object from any MappedinLocation | MappedinNode | MappedinPolygon to any MappedinLocation | MappedinNode | MappedinPolygon. This can be used to show Text directions, as well as draw paths.

Example:

...
<Button
  title="Get Directions"
  onPress={async () => {
    try {
      const directions = await controller.current?.getDirections({
        from: selectedLocation,
        to: destinationLocation,
      });
      console.log(directions);
    } catch (e) {
      console.error(e);
    }
  }}
></Button>
...

Get Directions from One Location to Another and show path and connections on map

import { MiMapView } from '@mappedin/react-native-sdk';
import type {
  TBlueDotUpdate,
  IMapViewStore,
  MappedinLocation,
  MappedinNode,
} from '@mappedin/react-native-sdk';

// Imperative API
const mapView = React.useRef<IMapViewStore>();
const [destination, setDestination] = React.useRef<MappedinLocation>();
const [departure, setDeparture] = React.useState<MappedinLocation>({});

const App = () => {
  return (
    <>
      <MiMapView
        style={{ flex: 1 }}
        ref={mapView}
        options={options}
        onDataLoaded={() => {
          mapView.current.BlueDot.enable();
        }}
        onPolygonClicked={({ polygon: MappedinPolygon }) => {
          // first, let's set departure
          if (departure == null) {
            setDeparture(polygon.locations[0]);
          } else {
            // then, destination
            setDestination(polygon.locations[0]);
          }
        }}
      />
      <Button
        title="Get Directions"
        onPress={async () => {
          try {
            // get Directions
            const directions = await controller.current?.getDirections({
              from: departuer,
              to: destination,
            });
            // draw path on map
            await controller.current?.drawJourney(directions);
            // focus on the path
            controller.current?.focusOn({
              nodes: directions.path,
            });
          } catch (e) {
            console.error(e);
          }
        }}
      ></Button>
      <Button
        title="Reset"
        onPress={() => {
          mapView.current.clearAllPaths();
          setDeparture(undefined);
          setDestination(undefined);
        }}
      ></Button>
    </>
  );
};

Preparing SVGs for use as assets in markers/tooltips/etc

SVG is a popular format for image assets, which means there are a lot of proprietary, broken, or unnecessary tags when getting SVGs online.

Before using SVGs with our SDKs, they need to get “cleaned up” - this also drastically reduces their file size, so win-win.

Preparing

  1. Go to https://jakearchibald.github.io/svgomg/
  2. Drag and drop your SVG
  3. All default settings will work well, but we also recommend enabling “prefer viewBox to width/height”
  4. You can now download the clean SVG or copy and paste its contents.

Once the SVGs are prepared, they need to be wrapped in a div element which will give them an explicit size. This allows markers to be any desired size and the SVG will grow/shrink appropriately. The element can also add a background, shadows, and any other styles.

`
<div style="width: 32px; height: 32px;">
  <svg xmlns="http://www.w3.org/2000/svg">...</svg>
</div>
`

Adding a Coordinate by lat/lon

import { MiMapView } from '@mappedin/react-native-sdk';
import type {
  IMapViewStore,
} from '@mappedin/react-native-sdk';

// Imperative API
const mapView = React.useRef<IMapViewStore>();

const App = () => {
  return (
    <>
      <MiMapView
        style={{ flex: 1 }}
        ref={mapView}
        options={options}
        onFirstMapLoaded={() => {
          // create a coordinate
          const coordinate = mapView.current.venueData.maps[1].createCoordinate(43.52117572969203, -80.53869317220372);

          const coordinate2 = mapView.current.venueData.maps[1].createCoordinate(44.52117572969203, -79.53869317220372);
          // find distance between coordinates:
          console.log(coordinate.absoluteDistanceTo(coordinate2));

          // coordinates can also be used to place markers:
          mapView.current.createMarker(coordinate, ...)
        }}
      />
  )
}

Customizing appearance of labels

Labels are added on map load by default, but this can be overriden by setting labelAllLocationsOnInit to false and taking control of labeling. We provide 2 themes, and full control over further customization.

import { MiMapView, labelThemes } from '@mappedin/react-native-sdk';
import type {
  IMapViewStore,
} from '@mappedin/react-native-sdk';

// Imperative API
const mapView = React.useRef<IMapViewStore>();

const App = () => {
  return (
    <>
      <MiMapView
        style={{ flex: 1 }}
        ref={mapView}
        options={{ ...options, labelAllLocationsOnInit: false }}
        onFirstMapLoaded={() => {
          // use one of the themes
          mapView.current.labelAllLocations({ appearance: labelThemes.lightOnDark })
          // OR, customize a set of properties
          mapView.current.labelAllLocations({ appearance: { text: { size: 12 }} })
          // OR, do both
          mapView.current.labelAllLocations({ appearance:
          {
            ...labelThemes.lightOnDark,
            margin: 20,
            text: {
              ...labelThemes.lightOnDark.text,
              size: 20
            }
            }
          })
        }}
      />
  )
}

Adding/Removing markers

import { MiMapView } from '@mappedin/react-native-sdk';
import type {
  IMapViewStore,
} from '@mappedin/react-native-sdk';

// Imperative API
const mapView = React.useRef<IMapViewStore>();
const markerId = React.useRef(null);

const App = () => {
  return (
    <>
      <MiMapView
        style={{ flex: 1 }}
        ref={mapView}
        options={options}
        onDataLoaded={() => {
          mapView.current.BlueDot.enable();
        }}
        onPolygonClicked={({ polygon }) => {
          if (markerId != null) {
            mapView.current.removeMarker(markerId);
          }
          // Let's add a marker anchored to the top of the first entrance to polygon
          markerId.current = mapView.current.createMarker(polygon.entrances[0], `
            <div style="width: 32px; height: 32px;">
              <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 293.334 293.334"><g fill="#010002"><path d="M146.667 0C94.903 0 52.946 41.957 52.946 93.721c0 22.322 7.849 42.789 20.891 58.878 4.204 5.178 11.237 13.331 14.903 18.906 21.109 32.069 48.19 78.643 56.082 116.864 1.354 6.527 2.986 6.641 4.743.212 5.629-20.609 20.228-65.639 50.377-112.757 3.595-5.619 10.884-13.483 15.409-18.379a94.561 94.561 0 0016.154-24.084c5.651-12.086 8.882-25.466 8.882-39.629C240.387 41.962 198.43 0 146.667 0zm0 144.358c-28.892 0-52.313-23.421-52.313-52.313 0-28.887 23.421-52.307 52.313-52.307s52.313 23.421 52.313 52.307c0 28.893-23.421 52.313-52.313 52.313z"/><circle cx="146.667" cy="90.196" r="21.756"/></g></svg>
            </div>
            `,
          {
            anchor: MARKER_ANCHOR.TOP,
          })
        }}
      />
  )
}

Get nearest node to point on screen

import { MiMapView } from '@mappedin/react-native-sdk';
import type { IMapViewStore } from '@mappedin/react-native-sdk';

// Imperative API
const mapView = React.useRef<IMapViewStore>();

const App = () => {
  return (
    <TouchableWithoutFeedback
      style={{ flex: 1 }}
      onPress={async ({ locationX, locationY }) => {
        // get nearest node to X,Y screen coordinate
        const node = await mapView.current?.getNearestNodeByScreenCoordinates(
          locationX,
          locationY,
        );
      }}
    >
      <MiMapView style={{ flex: 1 }} ref={mapView} options={options} />
    </TouchableWithoutFeedback>
  );
};

Get Directions from BlueDot and show path and connections on map

import { MiMapView } from '@mappedin/react-native-sdk';
import type {
  TBlueDotUpdate,
  IMapViewStore,
  MappedinLocation,
  MappedinNode,
} from '@mappedin/react-native-sdk';

// Imperative API
const mapView = React.useRef<IMapViewStore>();
const nearestNode = React.useRef<MappedinNode>();
const [
  selectedLocation,
  setSelectedLocation,
] = React.useState<MappedinLocation>({});

const App = () => {
  return (
    <>
      <MiMapView
        style={{ flex: 1 }}
        ref={mapView}
        options={options}
        onDataLoaded={() => {
          mapView.current.BlueDot.enable();
        }}
        onBlueDotStateChanged={({ stateChange }) => {
          nearestNode.current = stateChange.nearestNode;
        }}
        onPolygonClicked={({ polygon: MappedinPolygon }) => {
          setSelectedLocation(polygon.locations[0]);
        }}
      />
      <Button
        title="Get Directions"
        onPress={async () => {
          try {
            // get Directions
            const directions = await controller.current?.getDirections({
              from: nearestNode.current,
              to: selectedLocation,
            });
            // draw path and connections on map
            await controller.current?.drawJourney(directions);
            // focus on the path
            controller.current?.focusOn({
              nodes: directions.path,
            });
          } catch (e) {
            console.error(e);
          }
        }}
      ></Button>
    </>
  );
};

Use react-native-location to power BlueDot

import RNLocation from 'react-native-location';

const App = () => {
  ...

  RNLocation.requestPermission({
    ios: 'whenInUse',
    android: {
      detail: 'coarse',
    },
  }).then((granted) => {
    if (granted) {
      // Enable blue dot
      mapView.current.BlueDot.enable();
      RNLocation.subscribeToLocationUpdates((locations) => {
        const {
          accuracy,
          floor,
          latitude,
          longitude,
          timestamp,
        } = locations[0];

        const location = {
          timestamp,
          coords: {
            latitude,
            longitude,
            accuracy,
            floorLevel: floor,
          },
        };
        // pass locations in
        mapView.current.overrideLocation(location);
      });
    }
  });

  ...
};

MiniMap

Component Signature:

const MiMiniMap = (props: TMiniMapProps) => React.ReactElement;

Props:

Documented in Detail here

Prop Description Signature Required
options Options to initialize MapView with TMiMapViewOptions Yes
location Options to initialize MapView with MappedinLocation | string Yes
onLoad Gets called when minimap is rendered () => void No
polygonHighlightColor Color to use when highlighting polygon string No
focusOptions Override focus Options when generating minimap TFocusOptions No

Example:

const App = () => {
  return (
    <MiMiniMap
      style={{ width: 500, height: 200 }}
      options={options: TGetVenueOptions}
      /**
       * Called when miniMap is rendered
       */
      onLoad={() => {}}
      /**
       * What color to use when highlighting polygon
       */
      polygonHighlightColor={string}
      /**
       * Finer control over focus
       */
      focusOptions={options: TFocusOptions}
      location={selectedLocation: MappedinLocation | MappedinLocation["id"]}
    />
  );
};

[experimental] Fetching an offline Venue bundle

It is possible to download the venue bundle with all assets built in, which allows for caching/offline solutions.

Note 1: This must be enabled by Mappedin's Customer Solutions team.

Note 2: This may slow down map rendering for large venues, especially those with many images. We have improvements to this on our roadmap.

import {
  getVenueBundle,
  MiMapView,
  Mappedin,
} from '@mappedin/react-native-sdk';
import { View } from 'react-native';
import React, { useEffect } from 'react';
import fs from 'react-native-fs';

const options = {
  clientId: 'clientId',
  clientSecret: 'clientSecret',
  venue: 'venue-slug',
  perspective: 'Website',
};

const App = () => {
  const [venueData, setVenueData] = React.useState<Mappedin>();

  useEffect(() => {
    async function init() {
      const path = fs.DocumentDirectoryPath + '/bundle.json';
      try {
        // let's check if cache exists
        if (!(await fs.exists(path))) {
          console.log('cache doesnt exist');
          const venue = await getVenueBundle(options);
          setVenueData(venue);
          fs.writeFile(path, venue.toString());
        } else {
          console.log('cache exists, using');
          const venueString = await fs.readFile(path);
          const venue = new Mappedin(options);
          // hydrate the instance with cached data
          venue.hydrate(venueString);
          setVenueData(venue);
        }
      } catch (e) {
        console.error(e);
      }
    }
    init();
  }, []);

  return (
    <View style={{ flex: 1 }}>
      {venueData != null && (
        <MiMapView
          style={{ flex: 1 }}
          options={options}
          // pass venueData in
          venueData={venueData}
        />
      )}
    </View>
  );
};