Render Custom Shapes Using Essential JS 2 Maps | Syncfusion Blogs
Detailed Blog page Skeleton loader
Maps custom shape rendering tile image

The primary goal of the Essential JS 2 Maps control is to render SVG shapes using GeoJSON data. Using this control, you can render various maps along with simple geographical features. In addition, you can also render custom shapes to simulate concepts like travel seat selection, sports stadium seating, construction plans, and much more. In this blog, I will walk you through the step-by-step procedure to simulate a bus seat selection concept by rendering custom shapes and applying other EJ 2 Maps control features.

The following GIF image shows the final output of this blog, where the bus seats can be selected by clicking on the shapes and the selected seats are displayed at the right.

Bus seating layout simulation

Rendering custom shapes

Since the EJ 2 Maps control renders shapes from GeoJSON data, you need to build GeoJSON data with coordinates that render a bus seating layout. You also need to add properties like seat number and fill color for each shape. Colors should be specified to each shape to differentiate the booked and available seats. The following code does all of this:

export let busSeat: any = { "type": "FeatureCollection", "features": [
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[0, 0], [0, 20], [20, 20], [20, 0], [0, 0]]], [[[0, 22], [0, 27], [20, 27], [20, 22], [0, 22]]]] }, "properties": { "seatNo": 19, "fill": "grey" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[0, 47], [0, 67], [20, 67], [20, 47], [0, 47]]], [[[0, 69], [0, 74], [20, 74], [20, 69], [0, 69]]]] }, "properties": { "seatNo": 18, "fill": "grey" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[0, 94], [0, 114], [20, 114], [20, 94], [0, 94]]], [[[0, 116], [0, 121], [20, 121], [20, 116], [0, 116]]]] }, "properties": { "seatNo": 13, "fill": "grey" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[0, 141], [0, 161], [20, 161], [20, 141], [0, 141]]], [[[0, 163], [0, 168], [20, 168], [20, 163], [0, 163]]]] }, "properties": { "seatNo": 12, "fill": "grey" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[0, 188], [0, 208], [20, 208], [20, 188], [0, 188]]], [[[0, 210], [0, 215], [20, 215], [20, 210], [0, 210]]]] }, "properties": { "seatNo": 7, "fill": "grey" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[0, 235], [0, 255], [20, 255], [20, 235], [0, 235]]], [[[0, 257], [0, 262], [20, 262], [20, 257], [0, 257]]]] }, "properties": { "seatNo": 6, "fill": "grey" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[0, 282], [0, 302], [20, 302], [20, 282], [0, 282]]], [[[0, 304], [0, 309], [20, 309], [20, 304], [0, 304]]]] }, "properties": { "seatNo": 1, "fill": "grey" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[50, 0], [50, 20], [70, 20], [70, 0], [50, 0]]], [[[50, 22], [50, 27], [70, 27], [70, 22], [50, 22]]]] }, "properties": { "seatNo": 20, "fill": "grey" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[50, 47], [50, 67], [70, 67], [70, 47], [50, 47]]], [[[50, 69], [50, 74], [70, 74], [70, 69], [50, 69]]]] }, "properties": { "seatNo": 17, "fill": "grey" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[50, 94], [50, 114], [70, 114], [70, 94], [50, 94]]], [[[50, 116], [50, 121], [70, 121], [70, 116], [50, 116]]]] }, "properties": { "seatNo": 14, "fill": "Orange" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[50, 141], [50, 161], [70, 161], [70, 141], [50, 141]]], [[[50, 163], [50, 168], [70, 168], [70, 163], [50, 163]]]] }, "properties": { "seatNo": 11, "fill": "grey" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[50, 188], [50, 208], [70, 208], [70, 188], [50, 188]]], [[[50, 210], [50, 215], [70, 215], [70, 210], [50, 210]]]] }, "properties": { "seatNo": 8, "fill": "grey" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[50, 235], [50, 255], [70, 255], [70, 235], [50, 235]]], [[[50, 257], [50, 262], [70, 262], [70, 257], [50, 257]]]] }, "properties": { "seatNo": 5, "fill": "grey" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[50, 282], [50, 302], [70, 302], [70, 282], [50, 282]]], [[[50, 304], [50, 309], [70, 309], [70, 304], [50, 304]]]] }, "properties": { "seatNo": 2, "fill": "Orange" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[80, 0], [80, 20], [100, 20], [100, 0], [80, 0]]], [[[80, 22], [80, 27], [100, 27], [100, 22], [80, 22]]]] }, "properties": { "seatNo": 21, "fill": "grey" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[80, 47], [80, 67], [100, 67], [100, 47], [80, 47]]], [[[80, 69], [80, 74], [100, 74], [100, 69], [80, 69]]]] }, "properties": { "seatNo": 16, "fill": "grey" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[80, 94], [80, 114], [100, 114], [100, 94], [80, 94]]], [[[80, 116], [80, 121], [100, 121], [100, 116], [80, 116]]]] }, "properties": { "seatNo": 15, "fill": "Orange" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[80, 141], [80, 161], [100, 161], [100, 141], [80, 141]]], [[[80, 163], [80, 168], [100, 168], [100, 163], [80, 163]]]] }, "properties": { "seatNo": 10, "fill": "grey" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[80, 188], [80, 208], [100, 208], [100, 188], [80, 188]]], [[[80, 210], [80, 215], [100, 215], [100, 210], [80, 210]]]] }, "properties": { "seatNo": 9, "fill": "grey" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[80, 235], [80, 255], [100, 255], [100, 235], [80, 235]]], [[[80, 257], [80, 262], [100, 262], [100, 257], [80, 257]]]] }, "properties": { "seatNo": 4, "fill": "grey" } },
{ "type": "Feature", "geometry": { "type": "MultiPolygon", "coordinates": [[[[80, 282], [80, 302], [100, 302], [100, 282], [80, 282]]], [[[80, 304], [80, 309], [100, 309], [100, 304], [80, 304]]]] }, "properties": { "seatNo": 3, "fill": "grey" } }
]};

Now, assign this to the shapeData property in the EJ 2 Maps control. Also, since you need to render custom shapes, you must specify Normal for the geometryType property. The default value is Geographic, because it renders geographic maps by default.

import { busSeat } from './bus-seat';
import { Maps } from '@syncfusion/ej2-maps'; 

let maps: Maps = new Maps({
  height: '400',
  layers: [
    {
      geometryType: 'Normal',
      shapeData: busSeat
    }
  ]
});

maps.appendTo('#maps');

Bind colors to shapes

Colors are applied to the shapes to differentiate booked and available seats. To do this, bind the field with colors from the data source to the colorValuePath property.

let maps: Maps = new Maps({
  height: '400',
  layers: [
    {
      geometryType: 'Normal',
      shapeData: busSeat,
      shapeSettings: {
        colorValuePath: 'fill'
      }
    }
  ]
});

Enabling selection

For selecting shapes in maps, the selection feature should be enabled. All the shapes in the maps can be selected. Thus, you need to add code in the itemSelection event to prohibit the selection of already booked seats.

let maps: Maps = new Maps({
   itemSelection: (args: ISelectionEventArgs) => {
      // Prohibit selection on booked seats
      if ((args.shapeData as SeatInfo).fill === 'Orange') {
         args.fill = 'Orange !important';
         document.getElementById(args.target).setAttribute('class', 'ShapeselectionMapStyle');
         return;
      }
      args.fill = 'green';
      let seat: number = (args.shapeData as SeatInfo).seatNo;
      let connector: string = ' ';

      // To add initial/connector text
      if (seatInfo.innerHTML === '') {
         seatInfo.innerHTML = 'Seats Selected -';
      } else {
         connector = ', ';
      }
      let seatString: string = '' + connector + seat + '';
      let seatString1: string = ' ' + seat + ',';
      let lastString: string = 'Seats Selected - ' + seat + '';
      
      // To add/remove the seat number from the selected list
      if (seatInfo.innerHTML.indexOf(seatString) === -1 && seatInfo.innerHTML.indexOf(seatString1) === -1 && seatInfo.innerHTML.indexOf(lastString) === -1) {
         seatInfo.innerHTML += '' + connector + seat + '';
      } else {
         seatInfo.innerHTML = seatInfo.innerHTML.replace(seatString, '');
         seatInfo.innerHTML = seatInfo.innerHTML.replace(seatString1, '');
         if (seatInfo.innerHTML === lastString) {
            seatInfo.innerHTML = '';
         }
      }
   },
   height: '400',
   width: '200',
   zoomSettings: {
      enable: false
   },
   layers: [
   {
      geometryType: 'Normal',
      shapeData:busSeat,
      shapeSettings: {
         colorValuePath: 'fill'
      },
      selectionSettings: {
         enable: true,
         enableMultiSelect: true
      }
    }
  ]
});

In the previous code sample, I have prohibited the selection of already-booked seats. Also, I have displayed the seat number to the right of the map when clicking on not-booked seats.

Adding customizations

You can add titles, images, and borders to maps in HTML to create the appearance of a bus seating layout, and also add a button to clear the selection in HTML.

// code for clearing the seat selection
document.getElementById('clear-btn').onclick = () => {
  seatInfo.innerHTML = '';
  let selected: HTMLCollection = document.getElementsByClassName('ShapeselectionMapStyle');
  for (let i:number = 0, length:number = selected.length; i < length; i++) {
    selected[0].setAttribute('class', '');
  }
};

Conclusion

In just a little bit of code, you have built a bus seating layout by rendering custom shapes using Essential JS 2 Maps component. An interactive version of this sample is available on Stackblitz.

In a similar way, you can render any desired custom shapes using Essential JS 2 Maps component. We hope that you will enjoy this custom shape rendering feature that makes the Syncfusion Maps component more usable. If you have any questions or require any clarifications, please let us know in the comments section below. You can also contact us through our support forum, Direct-Trac or feedback portal. We are always happy to assist you!

If you like this blog post, we think you’ll also like the following free ebooks:

Be the first to get updates

Jayavigneshwaran G

Meet the Author

Jayavigneshwaran G

Jayavigneshwaran is a Product Manager at Syncfusion. He has been a web developer since 2013 and working on the custom control development. He is passionate about web technologies, who publishes articles to help the web developers. Currently, he is exploring mobile application development using Flutter.