Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add snapshot control to download and print map #202

Merged
merged 13 commits into from
Feb 13, 2025

Conversation

paul121
Copy link
Member

@paul121 paul121 commented Jul 12, 2024

Adds an image control (naming?) that allows the user to download an image of the current map view or, using printjs, opens a print dialog with the map.

I implemented this as a OL Control and wrapped it with a behavior (similar to snapping grid). It won't be added to maps by default unless the behavior is explicitly added.

@paul121 paul121 requested review from symbioquine and mstenta July 12, 2024 20:34
@paul121
Copy link
Member Author

paul121 commented Jul 12, 2024

First, click the "Capture image" button. Then the "Download" and "Print" buttons will appear. The download is a link like: <a href={data url} download/> and the print button will open print dialog. If the map is clicked, moved or changed then the "Download" and "Print" buttons are hidden again, and the "Capture image" button must be clicked again.

Screenshot from 2024-07-12 13-35-07

Copy link
Collaborator

@symbioquine symbioquine left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good start! Thanks for working on this @paul121

examples/simple-html-consumer/static/index.html Outdated Show resolved Hide resolved
src/control/Image/Image.js Outdated Show resolved Hide resolved
src/control/Image/Image.js Outdated Show resolved Hide resolved
src/control/Image/Image.js Outdated Show resolved Hide resolved
src/control/Image/Image.js Outdated Show resolved Hide resolved
src/control/Image/Image.js Outdated Show resolved Hide resolved
src/control/Image/Image.js Outdated Show resolved Hide resolved
src/control/Image/Image.js Outdated Show resolved Hide resolved
src/control/Image/Image.js Outdated Show resolved Hide resolved
Copy link
Member

@mstenta mstenta left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be a great option! Thanks for working on it @paul121!

Great feedback @symbioquine. I don't have anything to add at this point.

@paul121
Copy link
Member Author

paul121 commented Jul 18, 2024

Yes - thanks for the great feedback @symbioquine ! I renamed to "Snapshot" and have implemented the code changes you requested.

Still remaining is to simplify the SVG, I'll need to download the program and go through the steps but agree we should do this.

@paul121
Copy link
Member Author

paul121 commented Dec 12, 2024

Hey @symbioquine how do these SVGs look now? I was able to transform/optimize to 24x24 with https://www.svgviewer.dev/ before optimizing again in SVGOMG (maybe not necessary) and it seemed to work well.

@paul121 paul121 requested a review from symbioquine December 12, 2024 19:57
src/control/Snapshot/Snapshot.js Show resolved Hide resolved
src/control/Snapshot/Snapshot.js Outdated Show resolved Hide resolved
@paul121 paul121 force-pushed the 2.x-image-control branch 2 times, most recently from d85954b to 1f88295 Compare February 11, 2025 22:47
Copy link
Member Author

@paul121 paul121 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I was able to get the print SVG down to 1.6KB!

Also ebased on 2.x and force-pushed with another commit to get tests working.

But I found one issue with Mapbox and cross-origin image requests with the map canvas. Especially curious your thoughts @mstenta @symbioquine !

Copy link
Member Author

@paul121 paul121 Feb 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I commented on this file, click the file path above to see the relevant changes)

After testing again locally I discovered a console error when trying to use the Snapshot behavior with Mapbox layers. Notably, there was no issue with Open Street Map layers. The error was basically:

"Tainted canvases may not be exported."

TIL that cross-origin requests have some implications on how images are used in canvas elements... https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image

If the foreign content comes from an image obtained from either as HTMLCanvasElement or ImageBitMap, and the image source doesn't meet the same origin rules, attempts to read the canvas's contents are blocked.

We attempt to read the canvas's contents using the toDataUrl('image/jpeg').

HTML provides a crossorigin attribute for images that, in combination with an appropriate CORS header, allows images defined by the element that are loaded from foreign origins to be used in a as if they had been loaded from the current origin.

So, ultimately, we can solve this by requesting "anonymous" crrss-origin access:

The image is then configured to allow cross-origin downloading by setting its crossOrigin attribute to "anonymous" (that is, allow non-authenticated downloading of the image cross-origin).

Thankfully OpenLayers includes a crossOrigin option in their API for what appears like all raster layer sources. I didn't see the option on VectorSource, which makes sense. Adding this solves the problem and Snapshots can be made of both Mapbox and Open Street Map layers!

https://openlayers.org/en/latest/apidoc/module-ol_source_XYZ-XYZ.html

The crossOrigin attribute for loaded images. Note that you must provide a crossOrigin value if you want to access pixel data with the Canvas renderer. See https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image for more detail.

I'm not quite sure why this wasn't happening previously. I suppose it is possible Mapbox changed the default CORS response headers to something less friendly? Even though it still works once we request anonymous?

Anyways... this makes me wonder: Should we always set this value for raster layer sources? In theory I think a map server could reject anonymous CORS requests. In which case this change would break those layers.

So... perhaps we could add crossOrigin as an option to the farmOS-map addLayer() API? Then we could set this crossOrigin value as needed? I think the downside is that unless users explicitly set this flag, it's possible their map layers will be incompatible with the Snapshot behavior - which currently does not have any error handling for this issue (how should we tell the user one of the current map layers is unsupported for snapshotting?)

I assume we could avoid this issue the vast majority of the time by setting crossOrigin to anonymous by default, but allow it to be overridden via the API. Ultimately I think this might be our best option?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm reading the crossorigin MDN page correctly, it seems like there's no way to make the proposed solution a non-breaking change.

One example would be uploading some XYZ layer tiles to farmOS directly and relying on the user already being signed-in to have access to those uploads. It sounds like setting the crossorigin: anonymous would cause farmOS-map to make the tile layer requests as CORS requests even under the same domain.

I'd argue for not including the crossorigin change in this PR and instead adding some error handling to let users know the nature of the problem for failures. (This could also help for other failure scenarios so users don't need to look in the JS console to see why the button didn't do anything.)

A follow-up PR could attempt to fix that problem for Mapbox and perhaps make it possible for other layer types to optionally specify crossorigin: anonymous.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh interesting. So this only happens with MapBox when you want to create a snapshot?

Do you have a MapBox API key locally (does that make any difference, I wonder)?

I'm not quite sure why this wasn't happening previously.

Do you mean in your local testing of the snapshot button this wasn't happening previously?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point @symbioquine, I hadn't thought of tile layers hosted on the same domain. I agree we should make the crossOrigin change separately and add some error handling. Any ideas on how to implement some error handling? Have we done anything similar for other map behaviors? 🤔

@mstenta correct, this only happens with Mapbox. What do you mean by a Mapbox API key locally? I believe I am using a Mapbox API key that is not restricted to any particular domain (it is one from my personal acct). Not the one included in farmOS-map source code here which indeed did not work, as expected!

// Test MapBox API key (only works with farmos.github.io).
var key = 'pk.eyJ1IjoiZmFybWllciIsImEiOiJjazB6ajZjODkwMWIzM2ptcDBvNjl4eGViIn0.oYDl6csdVuVzlB0hf2ju2Q';

I tested this from both the simple-html-consumer example hosted on 127.0.0.1 as well as a local farmOS dev server w/ HTTPS and a local SSL cert via ddev. Just in case this was a weird localhost issue. But experience the same on both.

Do you mean in your local testing of the snapshot button this wasn't happening previously?

Correct. I don't remember what exactly I tested, but I assume I tried both Mapbox and Open Street Map layers

@paul121
Copy link
Member Author

paul121 commented Feb 13, 2025

Chatting today: we're going to move the crossOrigin change into a separate PR, making it possible to specify anonymous for tile layers, and start enabling this for mapbox

@paul121 paul121 changed the title Add image control to download and print map Add snapshot control to download and print map Feb 13, 2025
@paul121
Copy link
Member Author

paul121 commented Feb 13, 2025

Okay, I removed the commit that made crossOrigin changes, going to open a new PR that adds this capability to the addLayer() API.

@paul121 paul121 merged commit adb2bc1 into farmOS:2.x Feb 13, 2025
2 checks passed
@paul121 paul121 deleted the 2.x-image-control branch February 13, 2025 19:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

4 participants