Custom tiles on canvas

Open in CodeSandbox

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
    <script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
    <!-- To make the map appear, you must add your apikey -->
    <script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>

    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="../variables.ts"
    ></script>
    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
      import {layerId, LOCATION, rasterDataSource} from '../variables';

      window.map = null;

      main();

      async function main() {
        // Waiting for all api elements to be loaded
        await mappable.ready;
        const {MMap, MMapDefaultSchemeLayer, MMapLayer, MMapTileDataSource} = mappable;
        // Initialize the map
        map = new MMap(
          // Pass the link to the HTMLElement of the container
          document.getElementById('app'),
          // Pass the map initialization parameters
          {location: LOCATION, showScaleInCopyrights: true},
          [
            // Add a map scheme layer
            new MMapDefaultSchemeLayer({}),
            // Add a data source for a custom layer
            new MMapTileDataSource({id: layerId, raster: rasterDataSource}),
            // Add a layer with the received data
            new MMapLayer({source: layerId, type: layerId})
            /*
      A text identifier is used to link the data source and the layer.
      Be careful, the identifier for the data source is set in the id field,
      and the source field is used when transferring to the layer.
      */
          ]
        );
      }
    </script>

    <!-- prettier-ignore -->
    <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
    <link rel="stylesheet" href="./common.css" />
    <link rel="stylesheet" href="../variables.css" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
    <script crossorigin src="https://cdn.jsdelivr.net/npm/react@17/umd/react.production.min.js"></script>
    <script crossorigin src="https://cdn.jsdelivr.net/npm/react-dom@17/umd/react-dom.production.min.js"></script>
    <script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
    <!-- To make the map appear, you must add your apikey -->
    <script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>

    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="../variables.ts"
    ></script>
    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="react, typescript" type="text/babel">
      import {layerId, LOCATION, rasterDataSource} from '../variables';

      window.map = null;

      main();

      async function main() {
        // For each object in the JS API, there is a React counterpart
        // To use the React version of the API, include the module @mappable-world/mappable-reactify
        const [mappableReact] = await Promise.all([
          mappable.import('@mappable-world/mappable-reactify'),
          mappable.ready
        ]);
        const reactify = mappableReact.reactify.bindTo(React, ReactDOM);
        const {MMap, MMapDefaultSchemeLayer, MMapLayer, MMapTileDataSource} = reactify.module(mappable);

        function App() {
          return (
            // Initialize the map and pass initialization parameters
            <MMap location={LOCATION} showScaleInCopyrights={true} ref={(x) => (map = x)}>
              {/* Add a map scheme layer */}
              <MMapDefaultSchemeLayer />
              {/* Add a data source for a custom layer */}
              <MMapTileDataSource id={layerId} raster={rasterDataSource} />
              {/* Add a layer with the received data */}
              <MMapLayer source={layerId} type={layerId} />
              {/*
                A text identifier is used to link the data source and the layer.
                Be careful, the identifier for the data source is set in the id field,
                and the source field is used when transferring to the layer.
                */}
            </MMap>
          );
        }

        ReactDOM.render(
          <React.StrictMode>
            <App />
          </React.StrictMode>,
          document.getElementById('app')
        );
      }
    </script>

    <!-- prettier-ignore -->
    <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
    <link rel="stylesheet" href="./common.css" />
    <link rel="stylesheet" href="../variables.css" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
    <script crossorigin src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
    <script crossorigin src="https://cdn.jsdelivr.net/npm/@babel/standalone@7/babel.min.js"></script>
    <!-- To make the map appear, you must add your apikey -->
    <script src="https://js.api.mappable.world/v3/?apikey=<YOUR_APIKEY>&lang=en_US" type="text/javascript"></script>

    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="../variables.ts"
    ></script>
    <script
      data-plugins="transform-modules-umd"
      data-presets="typescript"
      type="text/babel"
      src="./common.ts"
    ></script>
    <script data-plugins="transform-modules-umd" data-presets="typescript" type="text/babel">
      import {layerId, LOCATION, rasterDataSource} from '../variables';

      window.map = null;

      async function main() {
        // For each object in the JS API, there is a Vue counterpart
        // To use the Vue version of the API, include the module @mappable-world/mappable-vuefy
        const [mappableVue] = await Promise.all([mappable.import('@mappable-world/mappable-vuefy'), mappable.ready]);
        const vuefy = mappableVue.vuefy.bindTo(Vue);
        const {MMap, MMapDefaultSchemeLayer, MMapLayer, MMapTileDataSource} = vuefy.module(mappable);

        const app = Vue.createApp({
          components: {
            MMap,
            MMapDefaultSchemeLayer,
            MMapLayer,
            MMapTileDataSource
          },
          setup() {
            const refMap = (ref) => {
              window.map = ref?.entity;
            };
            return {
              LOCATION,
              refMap,
              layerId,
              rasterDataSource
            };
          },
          template: `
      <!-- Initialize the map and pass initialization parameters -->
      <MMap :location="LOCATION" :showScaleInCopyrights="true" :ref="refMap">
        <!-- Add a map scheme layer -->
        <MMapDefaultSchemeLayer/>

        <!-- Add a data source for a custom layer -->
        <MMapTileDataSource :id="layerId" :raster="rasterDataSource"/>
        <!-- Add a layer with the received data -->
        <MMapLayer :source="layerId" :type="layerId"/>
        <!--
         A text identifier is used to link the data source and the layer.
         Be careful, the identifier for the data source is set in the id field,
         and the source field is used when transferring to the layer.
         -->
      </MMap>`
        });
        app.mount('#app');
      }

      main();
    </script>

    <!-- prettier-ignore -->
    <style> html, body, #app { width: 100%; height: 100%; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; } .toolbar { position: absolute; z-index: 1000; top: 0; left: 0; display: flex; align-items: center; padding: 16px; } .toolbar a { padding: 16px; }  </style>
    <link rel="stylesheet" href="./common.css" />
    <link rel="stylesheet" href="../variables.css" />
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
import type {RasterTileDataSourceDescription, FetchedTile} from '@mappable-world/mappable-types';

const TILE_SIZE = 256; // canvas tile size

export const layerId = 'SHIP'; // id for linking data and layer

// Promise that will be fulfilled when the image is loaded
const loadImage = (): Promise<HTMLImageElement> => {
  return new Promise<HTMLImageElement>((resolve, reject) => {
    const image = new Image();
    image.src = '../ship.svg';
    image.onload = () => resolve(image);
    image.onerror = () => reject();
  });
};

export const rasterDataSource: RasterTileDataSourceDescription = {
  // Name of data provided by this data source. Should be referenced in layer
  type: layerId,

  /* 
    fetchTile is called to get data for displaying a custom tile
    This method can be of several variants: 
    1) x y z placeholders for tile coordinates
    2) method that returns final url
    3) method that fetches tile manually

    In this example, we use option 3
    */
  fetchTile: (x: number, y: number, z: number): Promise<FetchedTile> => {
    // When the image is loaded fetchTile will return a canvas tile promise
    return loadImage().then((image) => {
      const canvas = document.createElement('canvas');

      canvas.width = TILE_SIZE;
      canvas.height = TILE_SIZE;

      const ctx = canvas.getContext('2d');

      if (!ctx) return;

      ctx.strokeStyle = '#007afce6';
      ctx.strokeRect(0, 0, TILE_SIZE, TILE_SIZE);

      ctx.font = '20px Arial';
      ctx.fillText(JSON.stringify({x, y, z}), 10, 30);

      ctx.drawImage(image, TILE_SIZE / 2, TILE_SIZE / 2, TILE_SIZE / 2, TILE_SIZE / 2);

      return {image: canvas};
    });
  }
};
import type {
  FetchedRasterTile,
  RasterTileDataSourceDescription,
  MMapLocationRequest
} from '@mappable-world/mappable-types';

export const LOCATION: MMapLocationRequest = {
  center: [55.169, 25.1141], // starting position [lng, lat]
  zoom: 13 // starting zoom
};

const TILE_SIZE = 230; // canvas tile size

export const layerId = 'canvas-layer-id'; // id for linking data and layer

// Promise that will be fulfilled when the image is loaded
const ImagePromise = new Promise<HTMLImageElement>((resolve, reject) => {
  const image = new Image();
  image.src = '../lightning.svg';
  image.onload = () => resolve(image);
  image.onerror = () => reject();
});

export const rasterDataSource: RasterTileDataSourceDescription = {
  // Name of data provided by this data source. Should be referenced in layer
  type: layerId,

  /*
    fetchTile is called to get data for displaying a custom tile
    This method can be of several variants:
    1) x y z placeholders for tile coordinates
    2) method that returns final url
    3) method that fetches tile manually

    In this example, we use option 3
    */
  fetchTile: (x: number, y: number, z: number): Promise<FetchedRasterTile> => {
    // When the image is loaded fetchTile will return a canvas tile promise
    return ImagePromise.then((image) => {
      const canvas = document.createElement('canvas');

      canvas.width = TILE_SIZE;
      canvas.height = TILE_SIZE;

      const ctx = canvas.getContext('2d');

      if (!ctx) return;

      ctx.strokeStyle = '#7B7D85';
      ctx.strokeRect(0, 0, TILE_SIZE, TILE_SIZE);

      ctx.font = '500 24px Arial';
      ctx.fillText('Use APIs to', 25, 40);
      ctx.fillText('make a dream', 25, 67);
      ctx.fillText('come true', 25, 94);

      ctx.drawImage(image, 140, 130, 56, 70);

      const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
      gradient.addColorStop(0, 'rgba(18, 45, 178, 0.11)');
      gradient.addColorStop(0.6771, 'rgba(18, 45, 178, 0.00)');

      ctx.fillStyle = gradient;
      ctx.fillRect(0, 0, canvas.width, canvas.height);

      return {image: canvas};
    });
  }
};