0

I am using the AzureMapsControl component which wraps Azure Maps. What I do is pretty simple just putting pins up. At times, when scrolling, I get a ton of the following error messages in the browser (client). Not clicking, just scrolling (video example).

atlas.min.js:55 Error: Could not load image because of The source image could not be decoded.. Please make sure to use a supported image type such as PNG or JPEG. Note that SVGs are not supported.
    at atlas.min.js:56:190132

Any idea what's going on and what I can do to avoid this?

Update:

I simplified my code a lot (below) and still get this. I set a breakpoint in the browser and here's the error:

ue.arrayBufferToImageBitmap = function(e, t)

And here's the parameters:

e: ArrayBuffer(0)
byteLength: 0
detached: false
maxByteLength: 0
resizable: false
t: (e,t)=> {…}
length: 2
name: "n"
arguments: (...)
caller: (...)

And here's the key part of the call stack (lots of minimized calls not listed):

e.getImage
loadTile
_loadTile
_addTile
_updateRetainedTiles
update
_updateSources
_render

And here's the full code. I've reduced it to just setting the image to pin-blue so there's no issue with checking for type. And if I remove setting OnSourceAdded="OnDatasourceAdded" the problem goes away. So it is an issue with rendering the pin. But I have no custom pin images.

Is it possible that I need to load pin-blue? They display fine so I think they're being loaded.

SearchMap.razor

<AzureMap Id="map"
          @ref="MyMap"
          CameraOptions="new CameraOptions { Zoom = MapZoom }"
          Controls="new Control[]
              {
                  new ZoomControl(new ZoomControlOptions { Style = ControlStyle.Auto }, ControlPosition.TopLeft),
                  new CompassControl(new CompassControlOptions { Style = ControlStyle.Auto }, ControlPosition.BottomLeft),
                  new StyleControl(new StyleControlOptions { Style = ControlStyle.Auto, MapStyles = MapStyle.All() }, ControlPosition.BottomRight)
              }"
          StyleOptions='new StyleOptions { ShowLogo = false, Language = "en-US" }'
          EventActivationFlags="MapEventActivationFlags
                .None()
                .Enable(MapEventType.Ready, MapEventType.SourceAdded,
                MapEventType.Click,  MapEventType.MoveEnd)"
          OnReady="OnMapReadyAsync"
          OnSourceAdded="OnDatasourceAdded"
          OnMoveEnd="OnMapMoveEnd"/>

SearchMap.razor.cs - it's long and I think only OnDatasourceAdded() matters. But I figure better to list it all so there's no assumptions about what is being set up.

using System.Text.Json;
using LouisHowe.web.PageModels.EventJob;
using LouisHowe.web.PageModels.Org;
using LouisHowe.web.PageModels.Shared;
using Microsoft.AspNetCore.Components;
using AzureMapsControl.Components.Atlas;
using AzureMapsControl.Components.Data;
using AzureMapsControl.Components.Layers;
using AzureMapsControl.Components.Map;
using AzureMapsControl.Components.Popups;
using Darnton.Blazor.DeviceInterop.Geolocation;
using TopologyPoint = NetTopologySuite.Geometries.Point;
using TopologyPolygon = NetTopologySuite.Geometries.Polygon;
using AtlasMapPoint = AzureMapsControl.Components.Atlas.Point;
using Position = AzureMapsControl.Components.Atlas.Position;
using NetTopologySuite.Geometries;

namespace LouisHowe.web.Pages.User
{
    public partial class SearchMap : ExComponentBase
    {
        /// <summary>
        /// The initial zoom value for the map.
        /// </summary>
        public static int DefaultZoomLevel = 12;

        /// <summary>
        /// How many degrees of longitude (X) in 1 mile.
        /// </summary>
        public static double LongitudeOneMile = 0.0181818181818182;

        /// <summary>
        /// How many degrees of latitude (Y) in 1 mile.
        /// </summary>
        public static double LatitudeOneMile = 0.0144927536231884;

        [Inject]
        IGeolocationService GeolocationService { get; set; } = default!;

        /// <summary>
        /// The data for the event grid.
        /// </summary>
        [Parameter]
        public SearchMapPageModel? Data { get; set; }

        /// <summary>
        /// The "working" center of the map. At startup, it will set this to, in order: the browser's current position,
        /// The User.Address, the Washington Monument.<br/>
        /// Use X for longitude and Y for latitude. 
        /// </summary>
        [Parameter]
        public TopologyPoint? MapCenter { get; set; }

        /// <summary>
        /// The initial zoom level for the map.
        /// </summary>
        [Parameter]
        public double? MapZoom { get; set; } = DefaultZoomLevel;

        /// <summary>
        /// Called when the map center or extent has changed. This will not be called on a move of less than 10 miles
        /// or a zoom in (or zoom back out to the original zoom). In short, this is called when the map display has
        /// changed enough that the search should be re-run.<br/>
        /// This will also be called when the map is first displayed.<br/>
        /// Passes (center, extent, zoom, runSearch). The extent passed in this is larger by about 10 miles over the
        /// actual map display. This is why it can pass runSearch== false for drags of under 5 miles.<br/>
        /// In Point and Polygon use X for longitude and Y for latitude.
        /// </summary>
        [Parameter]
        public EventCallback<(TopologyPoint, TopologyPolygon, double, bool)> MapMoved { get; set; }

        /// <summary>
        /// Passed to Signup in case user is not logged in.
        /// </summary>
        [Parameter]
        public string? ReturnUrl { get; set; }

        /// <summary>
        /// false if OnMapReadyAsync() has not been called yet. true once it has been called and completed.
        /// </summary>
        private bool _firstMapReadyComplete;

        /// <summary>
        /// The name of the datasource.
        /// </summary>
        private string EntitiesDataSourceId => "EventsAndOrgs";

        /// <summary>
        /// The datasource for the events.
        /// </summary>
        private DataSource? EntitiesDataSource { get; set; }

        /// <summary>
        /// The "type" property for an event.
        /// </summary>
        private static string _typeEvent = "Event";

        /// <summary>
        /// The "type" property for an organization.
        /// </summary>
        private static string _typeOrganization = "Organization";

        /// <summary>
        /// The popup for the pin the mouse is over.
        /// </summary>
        private Popup? PinTooltip { get; set; }

        /// <summary>
        /// The map component.
        /// </summary>
        private AzureMap MyMap { get; set; } = default!;

        /// <summary>
        /// true to show the event popup, false to hide it.
        /// </summary>
        public bool EventPopupVisible { get; set; }

        /// <summary>
        /// The header text for the event popup.
        /// </summary>
        public string? EventPopupHeader { get; set; }

        /// <summary>
        /// The data for the event card.
        /// </summary>
        public EventPageViewModel? EventCardData { get; set; }

        /// <summary>
        /// true to show the organization popup, false to hide it.
        /// </summary>
        public bool OrgPopupVisible { get; set; }

        /// <summary>
        /// The header text for the organization popup.
        /// </summary>
        public string? OrgPopupHeader { get; set; }

        /// <summary>
        /// The data for the organization card.
        /// </summary>
        public OrganizationPageModel? OrgCardData { get; set; }

        /// <summary>
        /// The GetHashCode() of Data the last time we updated it in SetParametersAsync(). We use this to know
        /// when Data has changed.
        /// </summary>
        private int _dataHashCode;

        /// <summary>
        /// The center of the map last time we called MapMoved. We use this to not call MapMoved if the map center
        /// is moved less than 10 miles.
        /// </summary>
        private Position? _lastCenter;

        /// <summary>
        /// The bounds of the map last time we called MapMoved. We use this to not call MapMoved if the map is
        /// zoomed in, or zoomed back out to a level &lt;= the level of the last call to MapMoved.
        /// </summary>
        private BoundingBox? _lastBounds;

        /// <inheritdoc />
        protected override async Task OnInitializedAsync()
        {
            await base.OnInitializedAsync();

            if (string.IsNullOrEmpty(ReturnUrl))
                ReturnUrl = "/User/Search";
        }

        public async Task OnMapReadyAsync(MapEventArgs mapArgs)
        {

            try
            {
                if (!_firstMapReadyComplete)
                {
                    // move it to the search center
                    if (MapCenter is not null)
                    {
                        await mapArgs.Map.SetCameraOptionsAsync(options =>
                            options.Center = new Position(MapCenter.X, MapCenter.Y));

                        // tell the parent (above call does not call the move event
                        var cameraOptions = await mapArgs.Map.GetCameraOptionsAsync();
                        await NewMapLocationAndExtent(cameraOptions.Center, cameraOptions.Bounds, cameraOptions.Zoom, true);
                    }
                    else
                    {
                        var currentPositionResult = await GeolocationService.GetCurrentPosition();
                        if (currentPositionResult.IsSuccess)
                            MapCenter = new TopologyPoint(currentPositionResult.Position.Coords.Longitude,
                                    currentPositionResult.Position.Coords.Latitude)
                            { SRID = 4326 };
                        else if (User.Address.Location is not null)
                            MapCenter = new TopologyPoint(User.Address.Location.X,
                                    User.Address.Location.Y)
                            { SRID = 4326 };
                        else
                        {
                            Trap.Break();
                            MapCenter = new TopologyPoint(-77.035278, 38.889484) { SRID = 4326 };
                        }
                        await mapArgs.Map.SetCameraOptionsAsync(options =>
                            options.Center = new Position(MapCenter.X, MapCenter.Y));
                        // we don't tell the parent because it didn't set it for us.
                    }
                }

                // create the datasources
                EntitiesDataSource = new DataSource(EntitiesDataSourceId)
                {
                    EventActivationFlags = DataSourceEventActivationFlags.None(),
                    Options = new()
                    {
                        Cluster = true,
                        ClusterRadius = 50,
                        ClusterMaxZoom = 24
                    }
                };
                await mapArgs.Map.AddSourceAsync(EntitiesDataSource);

                // add the pins
                if (Data is not null)
                {
                    await AddEventPins(Data.EventData);
                    await AddOrganizationPins(Data.OrgData);
                }
                else Trap.Break(); // handled when set later?

                // set up the popup. We have just one that is opened/updated/closed
                PinTooltip = new Popup(new PopupOptions
                {
                    // Position is set in OnPinMove()
                    Position = MapCenter is null ? new Position(0, 0) : new Position(MapCenter!.X, MapCenter.Y),
                    PixelOffset = new Pixel(0, -20),
                    CloseButton = true,
                    OpenOnAdd = false,
                });

                await mapArgs.Map.AddPopupAsync(PinTooltip);
            }
            catch (Exception ex)
            {
                LoggerEx.LogError(ex, "OnMapReadyAsync");
                ex.Data.TryAdd("Category", typeof(SearchMap));
                throw;
            }
            finally
            {
                _firstMapReadyComplete = true;
            }
        }

        public async Task OnDatasourceAdded(MapSourceEventArgs mapArgs)
        {
            try
            {
                var singleItemLayer = new SymbolLayer
                {
                    Options = new()
                    {
                        Source = mapArgs.Source.Id,
                        IconOptions = new IconOptions
                        {
                            Image = new ExpressionOrString("pin-blue")
                        },
                        Filter = new(new[]
                        {
                            new ExpressionOrString("!"),
                            new Expression(new []
                            {
                                new ExpressionOrString("has"),
                                new ExpressionOrString("point_count"),
                            })
                        })
                    },
                    EventActivationFlags = LayerEventActivationFlags.None()
                        .Enable(LayerEventType.Click)
                        .Enable(LayerEventType.MouseOver)
                        .Enable(LayerEventType.MouseOut)
                };

                await mapArgs.Map.AddLayerAsync(singleItemLayer);
            }
            catch (Exception ex)
            {
                LoggerEx.LogError(ex, "OnDatasourceAdded");
                Trap.Break();
                ex.Data.TryAdd("Category", typeof(SearchMap));
                throw;
            }
        }

        /// <inheritdoc />
        public override async Task SetParametersAsync(ParameterView parameters)
        {

            if ((!_firstMapReadyComplete) || EntitiesDataSource is null)
            {
                await base.SetParametersAsync(parameters);
                return;
            }

            parameters.SetParameterProperties(this);

            try
            {
                foreach (var parameter in parameters)
                {
                    if (parameter.Name != nameof(Data) || parameter.Value is not SearchMapPageModel paramData)
                        continue;

                    // if the data hasn't changed, then we're done
                    var valueHashCode = paramData.GetHashCode();
                    if (valueHashCode == _dataHashCode)
                        continue;
                    _dataHashCode = valueHashCode;

                    if (EntitiesDataSource.Shapes is not null && EntitiesDataSource.Shapes.Any())
                        await EntitiesDataSource!.ClearAsync();
                    List<SearchMapEntity> listEvents = paramData.EventData;
                    List<SearchMapEntity> listOrgs = paramData.OrgData;

                    // set the new/additional pins
                    await AddEventPins(listEvents);
                    await AddOrganizationPins(listOrgs);
                    break;
                }

                await base.SetParametersAsync(ParameterView.Empty);
            }
            catch (Exception ex)
            {
                LoggerEx.LogError(ex, "SetParametersAsync");
                ex.Data.TryAdd("Category", typeof(SearchMap));
                throw;
            }
        }

        private async Task AddEventPins(List<SearchMapEntity> listEvents)
        {

            var listPins = new List<Shape>();
            foreach (var entity in listEvents)
            {
                // if no address location, we can't pin it
                var location = entity.Location;
                if (location is null)
                    continue;

                var props = new Dictionary<string, object>
                {
                    { "Type", _typeEvent },
                    { "Id", entity.Id },
                    { "UniqueId", entity.UniqueId },
                    { "Name", entity.Name },
                    { "ProfileUrl", entity.ProfileUrl},
                    { "Description", entity.Description ?? string.Empty }
                };
                if (entity.RecurrenceInfoId is not null)
                {
                    props.Add("RecurrenceInfoId", entity.RecurrenceInfoId!);
                    props.Add("RecurrenceIndex", entity.RecurrenceIndex!);
                }

                var pin = new Shape<AtlasMapPoint>(new AtlasMapPoint(
                        new Position(location.X, location.Y)), props);
                listPins.Add(pin);
            }

            await EntitiesDataSource!.AddAsync(listPins);
        }

        private async Task AddOrganizationPins(List<SearchMapEntity> listOrganizations)
        {

            var listPins = new List<Shape>();
            foreach (var entity in listOrganizations)
            {
                // if no address location, we can't pin it
                var location = entity.Location;
                if (location is null)
                    continue;

                var props = new Dictionary<string, object>
                {
                    { "Type", _typeOrganization },
                    { "Id", entity.Id },
                    { "Name", entity.Name },
                    { "ProfileUrl", entity.ProfileUrl},
                    { "Description", entity.Description ?? string.Empty }
                };

                var pin = new Shape<AtlasMapPoint>(new AtlasMapPoint(
                        new Position(location.X, location.Y)), props);
                listPins.Add(pin);
            }

            await EntitiesDataSource!.AddAsync(listPins);
        }

        private static int? ConvertToInt(object? number)
        {
            if (number is null)
                return null;
            if (number is JsonElement jsonElement)
                return jsonElement.GetInt32();
            return Convert.ToInt32(number);
        }

        private static string? ConvertToString(object? value)
        {
            if (value is null)
                return null;
            if (value is JsonElement jsonElement)
                return jsonElement.GetString() ?? string.Empty;
            return value.ToString() ?? string.Empty;
        }

        private async Task OnMapMoveEnd(MapEventArgs mapArgs)
        {
            try
            {
                if (!_firstMapReadyComplete)
                    return;

                // we decide if we call the event based on the center & extent on the last call vs.
                // the current center & extent as set in the CameraOptions.
                var cameraOptions = await mapArgs.Map.GetCameraOptionsAsync();

                // new extent outside the old one?
                // allow a little shift on the edges
                var extentChanged = _lastBounds is null ||
                    cameraOptions.Bounds.North > _lastBounds.North + LatitudeOneMile ||
                    cameraOptions.Bounds.South < _lastBounds.South - LatitudeOneMile ||
                    cameraOptions.Bounds.West < _lastBounds.West - LongitudeOneMile ||
                    cameraOptions.Bounds.East > _lastBounds.East + LongitudeOneMile;

                bool centerChangedEnough;
                if (_lastCenter is null)
                    centerChangedEnough = true;
                else
                {
                    // If under 5 miles on both axis, don't do anything. We grow the extent by 10 miles
                    // so this should be good.
                    var latitudeDiff = Math.Abs(_lastCenter.Latitude - cameraOptions.Center.Latitude);
                    var longitudeDiff = Math.Abs(_lastCenter.Longitude - cameraOptions.Center.Longitude);
                    centerChangedEnough = latitudeDiff > LatitudeOneMile * 5.0 || longitudeDiff > LongitudeOneMile * 5.0;
                }

                await NewMapLocationAndExtent(cameraOptions.Center, cameraOptions.Bounds, cameraOptions.Zoom,
                    extentChanged || centerChangedEnough);
            }
            catch (Exception ex)
            {
                LoggerEx.LogError(ex, "OnMapMoveEnd");
                ex.Data.TryAdd("Category", typeof(SearchMap));
                throw;
            }
        }

        private async Task NewMapLocationAndExtent(Position centerPosition, BoundingBox box, double? zoom, bool runSearch)
        {

            // We expand the box here, both for _lastBounds and extentRectangle. This increases the search area
            // and means the camera.Bounds will be inside _lastBounds for moves under 10 miles.
            var north = box.North + LatitudeOneMile * 10.0;
            var south = box.South - LatitudeOneMile * 10.0;
            var west = box.West - LongitudeOneMile * 10.0;
            var east = box.East + LongitudeOneMile * 10.0;

            // these are what we sent calling the event. Used solely to determine if the next move
            // needs to run a new search.
            if (runSearch)
            {
                _lastCenter = new Position(centerPosition.Longitude, centerPosition.Latitude);
                _lastBounds = new BoundingBox(west, south, east, north);
            }

            var centerPoint = new TopologyPoint(centerPosition.Longitude, centerPosition.Latitude)
            {
                // // WGS 84 coordinate system (per ChatGPT)
                SRID = 4326
            };

            // Define the four corners of the rectangle
            var upperLeft = new Coordinate(west, north);
            var lowerLeft = new Coordinate(west, south);
            var lowerRight = new Coordinate(east, south);
            var upperRight = new Coordinate(east, north);

            // Create a linear ring (a closed line string) from the corners
            // Must be counterclockwise. If clockwise, that's the outside of the polygon.
            var coordinates = new[] { upperLeft, lowerLeft, lowerRight, upperRight, upperLeft };
            var extentRing = new LinearRing(coordinates)
            {
                SRID = 4326
            };

            // Create the polygon from the linear ring
            var extentRectangle = new TopologyPolygon(extentRing)
            {
                SRID = 4326
            };

            if (MapMoved.HasDelegate)
                await MapMoved.InvokeAsync((centerPoint, extentRectangle, zoom ?? 12, runSearch));
            else
                MapCenter = centerPoint;
        }
    }
}

1 Answer 1

1

I would start by looking at the following code:

Image = new ExpressionOrString(new[]
{
    new ExpressionOrString("match"),
    new Expression(new[]
    {
        new ExpressionOrString("get"),
        new ExpressionOrString("Type"),
    }),

    new ExpressionOrString(_typeEvent),
    new ExpressionOrString("pin-blue"),

    new ExpressionOrString(_typeOrganization),
    new ExpressionOrString("pin-red"),

    new ExpressionOrString("marker-yellow"),
})

In particular I would look at the following:

new Expression(new[]
{
    new ExpressionOrString("get"),
    new ExpressionOrString("Type"),
}),

This code is grabbing the "Type" value from the properties of the feature in the data source. Do you have an image in the maps image sprite for all the possible values you have in your data? Make sure the casings match as well. If you are adding custom images, note that those are added asynchronously, and you would have to wait for them to be added before they would be available to the layers.

Sign up to request clarification or add additional context in comments.

3 Comments

I looked in to that - no luck. I finally removed all the conditionals and just hardcoded pin-blue - still no luck. I updated the question with the browser breakpoint issue - it's trying to load a bitmap that's byte[0]. Any ideas? TIA
Is your data at least rendering? I'm seeing the same error randomly on Azure Maps samples that don't even use a symbol layer or add any custom images. I think there might be a bug introduced in the latest version of the map control or in the map styles.
Yes, it all runs great. One of the QA people had the browser console open for another reason and happened to see this. Otherwise we would not know it was happening. Note that it only happens if we tell it to display pins. Even if hard coded to the blue pin.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.