简体   繁体   中英

Google Maps API markers with multiple icons

I'd like to implement a complex marker using the Google Maps JavaScript API that combines both a static image (PNG file) as well as an image that is editable "on the fly", such as SVG. This is what I'm aiming for:

谷歌地图标记 PNG + SVG

I have already built the "soldier" as a PNG file marker and that all works well. However, I can't seem to find a way to add more than one element to a marker's "icon" property. I have read all of the Google Maps API documentation, reference and examples and looked at this link:

How to place multiple icons for Google map markers in v3?

That link doesn't seem to help exactly, because I want the red bar under the soldier to be dynamically changed as it represents the soldier's health.

Does anyone have a working solution? I don't mind if it doesn't use SVG and uses CSS or even Canvas, as long as it presents a simple rectangle who's length can be dynamically adjusted.

Thank you.

One option would be to use two separate markers, bound together (if you need to move them):

var soldier = new google.maps.Marker({
  position: map.getCenter(),
  map: map,
  draggable: true,
  icon: { url: "http://www.geocodezip.com/mapIcons/greenSoldier.png",
   scaledSize: new google.maps.Size(32,48)
  }
});
var health = new google.maps.Marker({
  position: soldier.getPosition(),
  map: map,
  icon: { path: "M 100 100 L 150 100 L 150 110 L 100 110 z",
  scale: 0.5,
  fillColor: "red",
  fillOpacity: 1.0,
  strokeColor: "black",
  strokeWeight: 2,
  anchor: new google.maps.Point(125,110)
  }
});
health.bindTo("position", soldier);

proof of concept fiddle

结果地图的截图

code snippet:

 function initialize() { var map = new google.maps.Map( document.getElementById("map_canvas"), { center: new google.maps.LatLng(37.4419, -122.1419), zoom: 13, mapTypeId: google.maps.MapTypeId.ROADMAP }); var soldier = new google.maps.Marker({ position: map.getCenter(), map: map, draggable: true, icon: { url: "https://www.geocodezip.net/mapIcons/greenSoldier.png", scaledSize: new google.maps.Size(32, 48) } }); var health = new google.maps.Marker({ position: soldier.getPosition(), map: map, icon: { path: "M 100 100 L 150 100 L 150 110 L 100 110 z", scale: 0.5, fillColor: "green", fillOpacity: 1.0, strokeColor: "black", strokeWeight: 2, anchor: new google.maps.Point(125, 110) } }); health.bindTo("position", soldier); setInterval(changeLength, 5000); var idx = 0; function changeLength() { var icon = health.getIcon(); icon.fillColor = colors[(idx) % colors.length]; icon.path = lengths[(idx++) % lengths.length]; health.setIcon(icon); } } google.maps.event.addDomListener(window, "load", initialize); var colors = ["green", "green", "yellow", "red"]; var lengths = ["M 100 100 L 150 100 L 150 110 L 100 110 z", "M 100 100 L 140 100 L 140 110 L 100 110 z", "M 100 100 L 130 100 L 130 110 L 100 110 z", "M 100 100 L 120 100 L 120 110 L 100 110 z" ]
 html, body, #map_canvas { height: 100%; width: 100%; margin: 0px; padding: 0px }
 <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk"></script> <div id="map_canvas"></div>

As promised, I've included my complete, working code. This function draws a marker as a canvas image. The soldier and his health bar are drawn separately, as the soldier is usually static (unless we want to animate him which we can do through canvas) but the health bar changes when he gets hit. I've tested the code in my game and it's working perfectly.

geocodezip, I didn't end up going with your suggestion for the main reason of not wanting to double the number of markers in the game for performance reasons. A player can have hundreds of units in the game, and there can be hundreds of players at any one time, so doubling the markers to achieve what I wanted could have impacted performance. Your code is great though, and I may use it in situations where doubling markers wouldn't have an impact on performance.

Code below:

function createUnitMarker(latLng, unitData) {
    var img = new Image();
    img.crossOrigin = "Anonymous";
    img.src = 'http://yourservername.com/' + unitData.icon;

    img.onload = function() {
        // google maps markers can only be created inside the onload function, otherwise nothing is drawn

        // source: GitHub, viktorkelemen/Custom canvas google maps marker
        // create the marker and all associated elements
        var canvas, context;
        var color = "blue";
        var healthBar = { // this object controls aspects of the unit health bar
            width: 62,
            height: 10,
            x: 5,
            y: 82
        }

        canvas = document.createElement("canvas");
        context = canvas.getContext("2d");

        canvas.width = unitData.width;
        canvas.height = unitData.height;

        function drawBar(percent, color) {
            // this function draws the unit health bar

            // colours the health bar background grey
            context.fillStyle = "gray";
            context.fillRect(healthBar.x, healthBar.y, healthBar.width, healthBar.height);

            // fills the health bar with the passed colour, or empty it if the unit dies
            if (typeof percent == undefined || percent == null || percent == NaN) {
                percent = 100;
            }

            if (typeof color == undefined || color == null || color == NaN) {
                color = "green";
            }

            // we want to ensure that the health bar fill is always contained within the health bar width
            var width = Math.min(percent * healthBar.width, healthBar.width);
            width = Math.max(width, 0);

            // colour-code the health bar depending on health
            if (percent < 0.4) {
                color = "red";
            }

            else if (percent < 0.7) {
                color = "orange";
            }

            else {
                color = "green";
            }

            context.fillStyle = color;
            context.fillRect(healthBar.x, healthBar.y, width, healthBar.height);

            // places a black stroke around the health bar for greater visibility
            context.lineWidth = 2;
            context.strokeStyle = "black";
            context.strokeRect(healthBar.x, healthBar.y, healthBar.width, healthBar.height);
        }

        function drawIcon(img, width, height) {
            // this function draws the unit

            if (typeof img === "undefined" || img === null || img === "" || img === NaN) {
                img = img;
            }

            if (typeof width === "undefined" || width === null || width === "" || width === NaN) {
                width = unitData.width;
            }

            if (typeof height === "undefined" || height === null || height === "" || height === NaN) {
                height = unitData.height;
            }

            context.clearRect(0, 0, width, height);

            // save the canvas context
            context.save();

            // draw the image
            context.drawImage(img, 0, 0);

            context.restore();
        }

        drawIcon(img, unitData.width, unitData.height);

        // create an icon based on the png we want to use
        var icon = {
            url: canvas.toDataURL(),
            size: new google.maps.Size(unitData.width, unitData.height), // the dimensions in pixels of the marker image
            origin: new google.maps.Point(0, 0), // the origin for this image, where top left is (0, 0)
            labelOrigin: new google.maps.Point(25, 40), // the position of the label relative to the icon
            anchor: new google.maps.Point(unitData.width / 2, unitData.height / 2) // the anchor for this image, or where the "base point" is located
        }

        // this is the key unit object. it should store all information directly related to this unit
        var marker = new google.maps.Marker({
            position: latLng, // the lat/lon where the marker is drawn
            label: {
                color: "yellow",
                fontFamily: "Oswald",
                fontSize: "20px",
                fontWeight: "bold", // use CSS font-weight properties
                text: unitData.unit_ID.toString(),
                visibility: "hidden"
            },
            title: "Infantry", // title on hover
            icon: icon, // the icon to be drawn, using an image file or canvas
            animation: google.maps.Animation.DROP, // a bit of flair :)
            // every element below this point is game-specific and not part of the google maps api
            unitID: unitData.unit_ID, // the unique unit id for this unit as returned by the server
            etc: 0
        });

        // to add the marker to the map, call setMap()          
        marker.setMap(gv.map);

        // draw the health bar last so that it's always on top, and after we know the unit's current/max health
        var healthPercent = marker.current_health / marker.max_health;
        drawBar(healthPercent);

        // these are the key commands required every time we need to re-draw something on the unit
        icon.url = canvas.toDataURL(); // re-draws the canvas, usually because we have changed something above this line
        icon = JSON.parse(JSON.stringify(icon)); // stringifies the icon properties
        marker.setIcon(icon); // applies the changed icon properties to the icon

        marker.addListener('label_changed', function() {
            // this listener is intended to update the unit's health bar on attack. it's a bit of a hack as it relies on changes to the marker's label to fire an event. other options are changing a marker's title, or writing a custom event (which is more involved)
            // draw the health bar last so that it's always on top, and after we know the unit's current/max health
            healthPercent = this.current_health / this.max_health;
            drawBar(healthPercent);

            icon = {
                url: canvas.toDataURL(),
                size: new google.maps.Size(unitData.width, unitData.height), // the dimensions in pixels of the marker image
                origin: new google.maps.Point(0, 0), // the origin for this image, where top left is (0, 0)
                labelOrigin: new google.maps.Point(25, 40), // the position of the label relative to the icon
                anchor: new google.maps.Point(unitData.width / 2, unitData.height / 2) // the anchor for this image, or where the "base point" is located
            }

            // all we want to do is change the icon url, but we can't just pass it to setIcon as it will lose the other icon properties. instead, we clone the icon object and change the url in the clone, passing it to setIcon when selected
            icon = JSON.parse(JSON.stringify(icon));

            this.setIcon(icon);
        });
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM