April 25, 2019
Drupal

Google map with labelled markers

Author photo
Dániel Szabó
Full Stack Developer

One of our clients required an image of a map for print. As will be seen on the map, there will be marked places that represent where the company performed surveys. The goal of this blogpost was to present the evolution of a task like we described above. Although the Google Map developer documentation is very useful we had to read many forums and other blogs where we found the desired solution.

cover

One of our clients required an image of a map for print. As will be seen on the map, there will be marked places that represent where the company performed surveys. During data collection, we were unsure as to which datasets would be included in the finalized work. It was clear that we had to provide a dynamic solution. We used Google Maps API to create an image in document.

The goal of this blogpost was to present the evolution of a task like we described above. Unfortunately we didn’t find detailed and proper documentation to achieve our goal. Although the Google Map developer documentation is very useful we had to read many forums and other blogs where we found the desired solution. But those suggestions weren’t quite detailed and well-documentated than this blogpost wants to be. We’d like to help those whom want to use similar solution.

You can create a Google Map integration quickly and easily by using the Google Map developer documentation as well as countless other tutorials. The extra feature of this task was to use individual icons and labels for marker points. The starter code of the map is very simple: we need a map API js script calling which contains your individual API KEY in the head of HTML.

The next steps are to define a div element named “map” as well as defining the CSS rule for this map by setting the dimensions. To set the parameters of the map, (i.e. centre, zoom, etc), we must create an initialized script.

Since June 2018, we set up a valid billing account via Google Cloud platform Console in order to use Google MAP API.

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Térkép</title>
  6. <style>
  7. #map {
  8. height: 100%;
  9. width: 100%;
  10. }
  11. html, body {
  12. height: 100%;
  13. margin: 0;
  14. padding: 0;
  15. }
  16. </style>
  17. <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&v=3.exp" type="text/javascript"></script>
  18. </head>
  19. <body>
  20. <div id="map"></div>
  21. <script>
  22. function initMap() {
  23. var bp = {lat: 47.538736, lng: 19.04631};
  24. var map = new google.maps.Map(document.getElementById('map'), {
  25. zoom: 11,
  26. center: bp
  27. });
  28. }
  29. initMap();</script>
  30. </body>
  31. </html>

The code above will load a map at 100% the width and height of the user’s browser. Budapest will appear in the centre of the map by using the center property of Google Map object. Budapest is chosen as the map’s central point since it is the place where the client took the survey. Our map appears within the “map” identified div element.

The next step is to add markers to the map where the measurements were done. As data was still being collected, we wanted to create an easy to modify, flexible data-structure that can be simply extendable or changeable. We also wanted to use custom markers as well as show the name of the points. In order to do this we defined an array named locations in Javascript inside the initMap() function. The first element of each item was to name the location, (we will use the first parameter as labels). After this, we defined X and Y coordinates, (i.e. latitude and longitude values). The arranged structure facilitates the transparency of data and the later enlargements.

After the above step, we uploaded a free image into a free image-hosting server. This was done because we wanted to load the image remotely, (and of course our client was able to check our progress from time-to-time). Then we iterate through the locations array and added Markers into the map by using coordinates. For ease of interpretation, we used an item variable inside the iteration for each items.

The position property of the Marker object needs both the latitude and longitude as object. The map parameter connects to the defined Google Map object, (i.e these are the two required parameters of a marker object). You can define a unique image as an icon by the icon attributes. The title option works as a classic HTML-title attributes.

  1. var locations = [
  2. ['Label 1', 47.453740, 19.142052],
  3. ['Label 2', 47.502547, 19.038126],
  4. ['Label 3', 47.650821, 19.020171],
  5. ['Label 4', 47.490881, 19.012405],
  6. ['Label 5', 47.562505, 19.087996],
  7. ['Label 6', 47.481118, 19.250704],
  8. ['Label 7', 47.569537, 19.098241],
  9. ['Label 8', 47.496817, 19.030732],
  10. ['Label 9', 47.480566, 19.276519]
  11. ];
  12. var image = 'https://goo.gl/dqvvFA';
  13. for (var i = 0; i < locations.length; i++) {
  14. var item = locations[i];
  15. var marker = new google.maps.Marker({
  16. position: {lat: item[1], lng: item[2]},
  17. map: map,
  18. icon: image,
  19. title: item[0]
  20. });
  21. }

The result is good but it is not feasible for static images, (which we really need for the final result). The labels should be shown by default. The HTML title, (which is provided by the title parameter of Maps Marker), can’t help. So we started to examine the InfoWindow() object of Google Map. However, this was an aberration because it opened its box only by clicking on the marker.

maps

The next step brought us closer to the desired goal. We can define a custom label to each marker by the label property of the Marker object. If the Marker has label attributes, we can use the icon to add other parameters for the labels. We can use the icon’s labelOrigin property which defines the offset of a label’s position in the map. The X and Y offsets work with integers and affects the position of the defined label.

So, we changed the iteration to the following:

  1. for (var i = 0; i < locations.length; i++) {
  2. var item = locations[i];
  3. var marker = new google.maps.Marker({
  4. position: {lat: item[1], lng: item[2]},
  5. map: map,
  6. icon: {
  7. url: image,
  8. labelOrigin: { x: 12, y: -10}
  9. },
  10. title: item[0],
  11. label: {
  12. text: item[0],
  13. color: '#222222',
  14. fontSize: '12px'
  15. }
  16. });
  17. }


As seen in the code above, the icon received an object with two values, (url and labelOrigin), instead of a plain URL. The url parameter obtains the URL of the icon. Thanks to the labelOrigin, we can reposition our label according to the top-left corner of a loaded image. The label will be positioned along the x and y coordinate axis. We can either use the x and y values as mentioned above or use a new google.maps.point(12,-10) definition without using object. For labelOrigin. Both ways give the same result.

The additional, currently unused parameter options of icon are as follows: 

  1. Anchor. The Anchor is the icon that can be repositioned from its center. The values are corresponding to the location of the marker, (i.e. on x:120 value the image will move left by 120px)
  2. Origin. The Origin appears in the icon’s position within the image as an offset. (This only makes sense if you are using an image from sprite image).
  3. Size. If using a sprite image, the size parameter is required. This parameter doesn’t resize our image. However, due to its size, it will reposition the icon.
  4. ScaledSize. ScaledSize is the actual size of our icon. After using this parameter, the image shown at the size we have defined, may be distorted.


The text parameter of the label property contains the printed label. The color parameter defines the color of the text. The fontSize determines the size of our label in pixels. Besides these, the label has additional options such as fontWeight, (i.e. the label in bold font), and fontFamily, (custom font families can be used). We didn’t want to use fontweight nor fontfamily for this project.

maps

As you can see in the map above, the inscriptions are not clearly visible, particularly if they are overlapping each other. Due to this lack of visibility, we had to find a more effective solution for displaying labels. After many trial and errors, we found a super gmap extension library which we loaded for our project.

At this point of the process, our client asked whether locations can be categorized by different coloured icons, (since their work too is categorized by certain viewpoints). We re-structured the data in order to handle all requests and parameters, (such as titles, coloured labels, individual icons, position of labels and coordinates).

Each item in locations array contains 6 parameters instead of the original 3 options. The six parameters are as follows:

  1. Title (will be used as label)
  2. X coordinate (latitude)
  3. Y coordinate (longitude)
  4. Color-code
  5. X position of label
  6. Y position of label


The X and Y position labels represent the relative offset to the marker’s point with negative prefix. The -10 value in “X position of label” represents that the label will move to the right by 10px while 50 value in the “Y position of label” will move the label higher by 50px.

  1. var locations = [
  2. ['Label 1', 47.453740, 19.142052, 'green', 38, -3],
  3. ['Label 2', 47.502547, 19.038126, 'red', -10, 20],
  4. ['Label 3', 47.650821, 19.020171, 'brown', 23, -3],
  5. ['Label 4', 47.490881, 19.012405, 'yellow', 23, -3],
  6. ['Label 5', 47.562505, 19.087996, 'red', 23, -3],
  7. ['Label 6', 47.481118, 19.250704, 'yellow', 37, -3],
  8. ['Label 7', 47.569537, 19.098241, 'yellow', -10, 20],
  9. ['Label 8', 47.496817, 19.030732, 'turquoise', 55, 33],
  10. ['Label 9', 47.480566, 19.276519, 'green', 10, -3],
  11. ['Label 10', 47.478538, 19.046445, 'turquoise', 23, -3],
  12. ['Label 11', 47.435689, 19.210308, 'turquoise', 10, -3],
  13. ['Label 12', 47.492465, 19.052041, 'red', -10, 15],
  14. ['Label 13', 47.523764, 19.096748, 'green', 25, -3],
  15. ['Label 14', 47.521279, 19.161779, 'yellow', 25, 50],
  16. ['Label 15', 47.438614, 19.183433, 'yellow', 45, -5],
  17. ['Label 16', 47.501973, 19.034013, 'brown', 40, 45]
  18. ];


We created an array named icons for the different colors which contains the icon path URLs as well as the hexadecimal codes of the colors. We didn’t use hexadecimal code values in the production code because the view of the labels will be defined by the CSS classes. 

Since we used the color-codes’ names as CSS classes, we had to update the stylesheet definitions.

The keys in the icons array are corresponding to the color-code of items in location array. The definition of icons array can be seen below:

  1. var icons = {
  2. 'green': {
  3. url: 'https://goo.gl/qvLZ4R',
  4. color: '#58D400'
  5. },
  6. 'yellow': {
  7. url: 'https://goo.gl/G6HyHS',
  8. color: '#FCCA00'
  9. },
  10. 'red': {
  11. url: 'https://goo.gl/6hkqX1',
  12. color: '#D80027'
  13. },
  14. 'turquoise': {
  15. url: 'https://goo.gl/uLRpYZ',
  16. color: '#00D9D2'
  17. },
  18. 'brown': {
  19. url: 'https://goo.gl/XTosFM',
  20. color: '#BF5300'
  21. }
  22. };


From hereon, we used the newly loaded MarkerWithLabel class instead of the Marker object provided by Google Map API. The position, map and the icon attributes are similar to the Marker’s attributes, however, the other parameters go through the properties of the new object. 

The labelContent contains the text that will be displayed as title/label. The labelAnchor uses a Google Map Point object containing the offset of the label according to the marker’s position. This offset was starkly opposed to what we expected; the positive numbers shifted the labels left and downwards, while the negative numbers shifted the labels right and upwards. For the labelClass options, we defined CSS classes for the labels. By defining CSS classes for the labels, the label-boxes can be easily designed by stylesheets. The labelInBackground property allows labels to be placed in the background among layers.

  1. for (var i = 0; i < locations.length; i++) {
  2. var item = locations[i];
  3. var marker = new MarkerWithLabel({
  4. position: {lat: item[1], lng: item[2]},
  5. map: map,
  6. icon: icons[item[3]].url,
  7. labelContent: item[0],
  8. labelAnchor: new google.maps.Point(item[4], item[5]),
  9. // the CSS class for the label
  10. labelClass: "label " + item[3],
  11. labelInBackground: true
  12. });
  13. }


Our last task was to come up with a design solution that allows for the clear visibility of labels. The stylesheet definitions were extended in the head of the HTML in order to use the defined classes declared for labels.

  1. .label {
  2. color: #000;
  3. background-color: white;
  4. border: 1px solid #000;
  5. font-family: "Lucida Grande", "Arial", sans-serif;
  6. font-size: 12px;
  7. text-align: center;
  8. white-space: nowrap;
  9. padding: 2px;
  10. }
  11. .label.green {
  12. background-color: #58D400;
  13. }
  14. .label.red {
  15. background-color: #D80027;
  16. color: #fff;
  17. }
  18. .label.yellow {
  19. background-color: #FCCA00;
  20. }
  21. .label.turquoise {
  22. background-color: #00D9D2;
  23. }
  24. .label.brown {
  25. background-color: #BF5300;
  26. color: #fff;
  27. }
maps

Please see below the full source code:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Térkép</title>
  6. <style>
  7. #map {
  8. height: 100%;
  9. width: 100%;
  10. }
  11. html, body {
  12. height: 100%;
  13. margin: 0;
  14. padding: 0;
  15. }
  16. .label {
  17. color: #000;
  18. background-color: white;
  19. border: 1px solid #000;
  20. font-family: "Lucida Grande", "Arial", sans-serif;
  21. font-size: 12px;
  22. text-align: center;
  23. white-space: nowrap;
  24. padding: 2px;
  25. }
  26. .label.green {
  27. background-color: #58D400;
  28. }
  29. .label.red {
  30. background-color: #D80027;
  31. color: #fff;
  32. }
  33. .label.yellow {
  34. background-color: #FCCA00;
  35. }
  36. .label.turquoise {
  37. background-color: #00D9D2;
  38. }
  39. .label.brown {
  40. background-color: #BF5300;
  41. color: #fff;
  42. }
  43. </style>
  44. <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyC3rthxrJtfGHmGJijI_fMrT96ecSc-AL8&v=3.exp" type="text/javascript"></script>
  45. <script src="http://cdn.sobekrepository.org/includes/gmaps-markerwithlabel/1.9.1/gmaps-markerwithlabel-1.9.1.js" type="text/javascript"></script>
  46. </head>
  47. <body>
  48. <div id="map"></div>
  49. <script>
  50. function initMap() {
  51. var bp = {lat: 47.538736, lng: 19.04631};
  52. var map = new google.maps.Map(document.getElementById('map'), {
  53. zoom: 11,
  54. center: bp
  55. });
  56. var locations = [
  57. ['Label 1', 47.453740, 19.142052, 'green', 38, -3],
  58. ['Label 2', 47.502547, 19.038126, 'red', -10, 20],
  59. ['Label 3', 47.650821, 19.020171, 'brown', 23, -3],
  60. ['Label 4', 47.490881, 19.012405, 'yellow', 23, -3],
  61. ['Label 5', 47.562505, 19.087996, 'red', 23, -3],
  62. ['Label 6', 47.481118, 19.250704, 'yellow', 37, -3],
  63. ['Label 7', 47.569537, 19.098241, 'yellow', -10, 20],
  64. ['Label 8', 47.496817, 19.030732, 'turquoise', 55, 33],
  65. ['Label 9', 47.480566, 19.276519, 'green', 10, -3],
  66. ['Label 10', 47.478538, 19.046445, 'turquoise', 23, -3],
  67. ['Label 11', 47.435689, 19.210308, 'turquoise', 10, -3],
  68. ['Label 12', 47.492465, 19.052041, 'red', -10, 15],
  69. ['Label 13', 47.523764, 19.096748, 'green', 25, -3],
  70. ['Label 14', 47.521279, 19.161779, 'yellow', 25, 50],
  71. ['Label 15', 47.438614, 19.183433, 'yellow', 45, -5],
  72. ['Label 16', 47.501973, 19.034013, 'brown', 40, 45]
  73. ];
  74. var icons = {
  75. 'green': {
  76. url: 'https://goo.gl/qvLZ4R',
  77. color: '#58D400'
  78. },
  79. 'yellow': {
  80. url: 'https://goo.gl/G6HyHS',
  81. color: '#FCCA00'
  82. },
  83. 'red': {
  84. url: 'https://goo.gl/6hkqX1',
  85. color: '#D80027'
  86. },
  87. 'turquoise': {
  88. url: 'https://goo.gl/uLRpYZ',
  89. color: '#00D9D2'
  90. },
  91. 'brown': {
  92. url: 'https://goo.gl/XTosFM',
  93. color: '#BF5300'
  94. }
  95. };
  96. var image = 'https://goo.gl/dqvvFA';
  97. for (var i = 0; i < locations.length; i++) {
  98. var item = locations[i];
  99. var marker = new MarkerWithLabel({
  100. position: {lat: item[1], lng: item[2]},
  101. map: map,
  102. icon: icons[item[3]].url,
  103. labelContent: item[0],
  104. labelAnchor: new google.maps.Point(item[4], item[5]),
  105. // the CSS class for the label
  106. labelClass: "label " + item[3],
  107. labelInBackground: true
  108. });
  109. }
  110. }
  111. initMap();
  112. </script>
  113. </body>
  114. </html>

Related posts

banner
Author
2021-04-16
Drupal

Since the release of Drupal 8 on November 19, 2015, we have been continuously working on moving clients and their sites from Drupal 7 to the latest version. The migration process has, more often than not, proved to be challenging.

Corporate intranet on decoupled Drupal
Author
2018-04-04
Drupal

We've decided to rewrite our corporate intranet using a decoupled architecture instead of porting it directly to Drupal 8 as is.