Hexgrid in Power Apps with Location Pin

This makes use of the hexgrid UK shapemap detailed in this article. We can make use of the Power Apps mobile client’s geolocation features to establish where the current user is based. As this comes through as a latitude and longitude value, we can correlate this to our shapemaps lat/lon values and try and place a pin where the user is.

First I establish a collection of SVG shapes in my app. There are several hundred so here is a brief example of how this looks. I take advantage of Named Formulas for this definition as it saves me having to execute any code when my app loads.

colShapemap =
Table(
    { lat:52.26767117, lon:-10.2996237, path:"<path d='M 25.68 713.5 33.91 727.75 25.68 742 9.23 742 1 727.75 9.23 713.5 25.68 713.5 Z'/>" },
    { lat:51.99194656, lon:-10.2996237, path:"<path d='M 9.23 742 25.68 742 33.91 756.25 25.68 770.5 9.23 770.5 1 756.25 9.23 742 Z'/>" },
    { lat:54.28159472, lon:-9.91064186, path:"<path d='M 58.59 514 50.36 528.25 33.91 528.25 25.68 514 33.91 499.75 50.36 499.75 58.59 514 Z'/>" }, ...


The map itself is an image for which I dynamically generate SVG code by concatenating my map collection, using the closing tags as positioners for inserting some formatting values. Where I use “clr…”, that is also a named formula where I store my theme colours.

With(
    {
        latCalc:Min(Filter(colShapemap, lat > varStore.latitude), lat)
    },
    "data:image/svg+xml;utf8, " & 
    EncodeUrl(
        "<svg xmlns='http://www.w3.org/2000/svg' version='1.2' baseProfile='tiny' width='1200' height='2000' viewBox='0 0 800 1000' stroke-linecap='round' stroke-linejoin='round'>" &
        Concat(
            colShapemap,
            Substitute(path, "/>", "") &
            " fill='" & clrMapHTML & "' stroke-width='2' stroke='" & clrBackHTML & "' />"
        )
        & Substitute(
            LookUp(
                colShapemap, 
                lat = latCalc &&
                lon > Max(Filter(colShapemap, lon < varStore.longitude && lat = latCalc), lon)
            ).path, 
            "/>", ""
        ) & " fill='" & clrTitleHTML & "' stroke-width='2' stroke='" & clrBackHTML & "' />"
        & "</svg>"
    )
)


Next I will add an image which is a simple ‘pin’ icon that everyone understands to mean ‘my current location’. We need to position this image over the map image in such a way that it appears to point to the user’s location. How will we accomplish this?

Firstly, we know the position of the map image itself, and it’s height and width. This is our available pixel space to position the pin image. To work out how far across this space we need to move, we can work out the minimum and maximum values for latitude and divide to get a percentage, then apply this to the pixel height. The same can be done for longitude and width.

The end result looks like this. The paths from our SVG are split into individual numbers so we can get the highest of them. I also use the Mod() function to select the correct value from each X/Y pairing. varStore is a variable that holds our current location.

With(
    {
        latCalc:Min(Filter(colShapemap, lat >= varStore.latitude), lat)
    },
    With(
        {
            // the shape co-ordinates for selected region
            Shape:
            Substitute(Substitute(
                LookUp(
                    colShapemap, 
                    lat = latCalc &&
                    lon > Max(Filter(colShapemap, lon < varStore.longitude && lat = latCalc), lon)
                ).path, 
                "<path d='M ", ""), "Z", ""
            ),

            // the X, Y, H and W properties of the image
            StartY:imgStoreCanvasTitleMap.Y,
            StartX:imgStoreCanvasTitleMap.X,
            ImgWidth:imgStoreCanvasTitleMap.Width,
            ImgHeight:imgStoreCanvasTitleMap.Height,

            // the H and W of the viewbox rendered inside the image
            ViewH:1000,
            ViewW:800
        },
        With(
            {
                // extract minimum vertical co-ordinate from shape (minimum should be highest point)
                Coord:
                Value(
                    Min(
                        Filter(
                            ForAll(
                                Sequence(CountRows(Split(Shape, " "))),
                                {
                                    Index:Value,
                                    Coord:Last(FirstN(Split(Shape, " "), Value)).Value
                                }
                            ),
                            Mod(Index, 2) = 0 // gets the Y value from the SVG
                        ),
                        IfError(Value(Coord), Blank())
                    )
                )                                
            },
            With(
                {
                    // position of second co-ord relative to viewbox height, multiplied by image height to get pixel distance vertically
                    DistV:(Coord / ViewH) * ImgHeight
                },

                // add pixel distance to starting Y pos of image
                StartY + DistV - Self.Height + ((DistV - (ImgHeight * 0.5)) * -0.05)
            )
        )
    )
)


Other considerations that are made include the view height and width of the SVG – this is the ‘canvas’ that is drawn within the confines of the image and affects the end visual output. We also need to subtract some distance to account for the height and width of the pin – this needs to be positioned centrally above the actual point on the map.

Here is the final output:


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *