I am trying to use leaflet to show a smaller map than usual so I don't want to use the normal tiling system. I don't care about smooth zooming and loading higher resolution tiles when needed. Instead I am trying to add a raster image from an image file. Lets say this file that comes up when I google "hand drawn map"
So I try
download.file('https://external-preview.redd.it/7tYT__KHEh8FBKO6bsqPgC02OgLCHAFVPyjdVZI4bms.jpg?auto=webp&s=ff2fa2e448bb92c4ed6c049133f80370f306acb3',
destfile = 'map.jpg')
map = raster::raster('map.jpg')
# it seems like i need a projection to use a raster image.
# not sure what controls do I have over this, especially in
# absence of a proper map layer and it's likely
# part of the solution
crs(map) = CRS("+init=epsg:4326")
leaflet() %>%
leaflet::addRasterImage(map)
The resulting output is nothing like the input image
How do I take an arbitrary image and place in on a leaflet map?
I failed to find the exact reason why addRasterImage fails here but I found reports that it doesn't behave well on L.CRS.Simple projection, which is what you'll want to use to show a simple rectangle image.
Using htmlwidgets::onRender makes it possible to directly use the javascript function L.imageOverlay to add the image you want
library(leaflet)
# minimal custom image
imageURL = 'https://external-preview.redd.it/7tYT__KHEh8FBKO6bsqPgC02OgLCHAFVPyjdVZI4bms.jpg?auto=webp&s=ff2fa2e448bb92c4ed6c049133f80370f306acb3'
# get image data. we'll use this to set the image size
imageData =
magick::image_read(imageURL) %>% image_info()
leaflet(options = leafletOptions(crs = leafletCRS('L.CRS.Simple'))) %>%
htmlwidgets::onRender(glue::glue("
function(el, x) {
var myMap = this;
var imageUrl = '<imageURL>';
var imageBounds = [[<-imageData$height/2>,<-imageData$width/2>], [<imageData$height/2>,<imageData$width/2>]];
L.imageOverlay(imageUrl, imageBounds).addTo(myMap);
}
",.open = '<', .close = '>'))
For a large image like this if you want to make the image smaller you can either scale down using the imageBounds in javascript side or set minZoom to a negative value and use setView to start out zoomed out.
leaflet(options =
leafletOptions(crs = leafletCRS('L.CRS.Simple'),
minZoom = -1)) %>%
setView(0,0,zoom = -1) %>%
htmlwidgets::onRender(glue::glue("
function(el, x) {
var myMap = this;
var imageUrl = '<imageURL>';
var imageBounds = [[<-imageData$height/2>,<-imageData$width/2>], [<imageData$height/2>,<imageData$width/2>]];
L.imageOverlay(imageUrl, imageBounds).addTo(myMap);
}
",.open = '<', .close = '>'))
Related
Can someone pls tell me what i'm doing wrong. I exported a topojson from mapshaper.org. Then I drew the map using the d3. But the map appears zoomed out(very small). How do I get it to be properly scaled and centered. Pls note that i've used some solutions on stackoverflow and I've tried fitExtent. The issue with fitExtent is that I will not be able to translate the map later when I need to. I want the loaded map to appear scaled and centered. Here's the code to draw the map
let width = 900
let height = 500
let svg = null
let chartContainer = null
// load data
let map = {}
const inputFilepath3 = '../Data/nigeria.topojson'
const data = await d3.json(inputFilepath3)
const object = data.objects.gadm36_NGA_1
map.features = topojson.feature(data, object).features
// build container
if (!svg) {
svg = d3.select('body')
.append('svg')
chartContainer = svg.append('g')
.classed('chart-container', true)
}[![enter image description here][1]][1]
svg.attr('width', width)
svg.attr('height', height)
// build scale
const colorScale = d3.scaleOrdinal(d3.schemeCategory10)
// build geopath generator
const projection = d3.geoAzimuthalEqualArea()
const geoPath = d3.geoPath(null).projection(projection)
// draw map
let ps = chartContainer.selectAll('path.state').data(map.features)
ps.exit().remove()
ps.enter().append('path').classed('state', true)
.attr('d', geoPath)
.attr('fill', function(d,i) {
return colorScale(i)
})
I'm having some troubles with the easybutton leaflet plugin in my Shiny App.
What I'm trying to do is to recenter the view on my points layer when the easybutton is clicked :
...
addCircleMarkers(lng = points$long,
lat = points$lat,
weight = 1, radius = 4,
group = "points",
...
addEasyButton(easyButton(
icon = 'ion-arrow-shrink',
title = 'Reset view',
onClick = JS("function(btn, map) {map.fitBounds(points.getBounds()); }")
))
But it doesn't work : "points is not defined" is printed in the JS console.
How can I get the real leaflet name (JS) of my points layer ?
Thank you.
Despite being a group name, points is not defined within the javascript - you need to use the layerManager to find layers - and passing the layer name from R to js is not quite as straightforward as one might hope.
This is not very clear in the documentation, but you should be able to set a group name for the markers, as you have, and then access it like so:
onClick = JS("function(btn, map) {
var groupLayer = map.layerManager.getLayerGroup('groupName');
}")
To get the bounds you should be able to use:
onClick = JS("function(btn, map) {
var groupLayer = map.layerManager.getLayerGroup('groupName');
map.fitBounds(groupLayer.getBounds());
}")
I've been following a tutorial to create an AR Ruler. Therefore, with the code below, I'm able to place 3D sphere's in my scene (which im looking to keep for the tracking functionality). However, instead of a 3d image, I'm looking to place an image. I attempted changing the dotGeometry and setting it to a UIImage and commenting out the material code but wasn't sure how to deal with with dotNode piece of code. Therefore, how would I be able to set my image as the resulting on-screen addition?
let dotGeometry = SCNSphere(radius: 0.005)
let material = SCNMaterial()
material.diffuse.contents = UIColor.red
dotGeometry.materials = [material]
let dotNode = SCNNode(geometry: dotGeometry)
dotNode.position = SCNVector3(hitResult.worldTransform.columns.3.x, hitResult.worldTransform.columns.3.y, hitResult.worldTransform.columns.3.z)
sceneView.scene.rootNode.addChildNode(dotNode)
You could create a SCNBox and set its length to a very small value to make it appear flat.
let box = SCNBox(width: 0.2, height: 0.2, length: 0.005, chamferRadius: 0)
let material = SCNMaterial()
material.diffuse.contents = UIImage(named: "image.png")
box.materials = [material]
boxNode = SCNNode(geometry: box)
boxNode.opacity = 1.0
boxNode.position = SCNVector3(0,0,-0.5)
scene.rootNode.addChildNode(boxNode)
Or you could follow another approach and add an overlay SKScene (2D) to the SCNScene(3D) as described here : How can I overlay a SKScene over a SCNScene in Swift?
I have an application in Flex 4 with a map, a database of points and a search tool.
When the user types something and does the search it returns name, details and coordinates of the objects in my database.
I have a function that, when i click one of the results of my search, it zooms the selected point of the map.
The question is, i want a function that zooms all the result points at once. For example if i search "tall trees" and it returns 10 points, i want that the map zooms to a position where i can see the 10 points at once.
Below is the code im using to zoom one point at a time, i thought flex would have some kind of function "zoom to group of points", but i cant find anything like this.
private function ResultDG_Click(event:ListEvent):void
{
if (event.rowIndex < 0) return;
var obj:Object = ResultDG.selectedItem;
if (lastIdentifyResultGraphic != null)
{
graphicsLayer.remove(lastIdentifyResultGraphic);
}
if (obj != null)
{
lastIdentifyResultGraphic = obj.graphic as Graphic;
switch (lastIdentifyResultGraphic.geometry.type)
{
case Geometry.MAPPOINT:
lastIdentifyResultGraphic.symbol = objPointSymbol
_map.extent = new Extent((lastIdentifyResultGraphic.geometry as MapPoint).x-0.05,(lastIdentifyResultGraphic.geometry as MapPoint).y-0.05,(lastIdentifyResultGraphic.geometry as MapPoint).x+0.05,(lastIdentifyResultGraphic.geometry as MapPoint).y+0.05,new SpatialReference(29101)).expand(0.001);
break;
case Geometry.POLYLINE:
lastIdentifyResultGraphic.symbol = objPolyLineSymbol;
_map.extent = lastIdentifyResultGraphic.geometry.extent.expand(0.001);
break;
case Geometry.POLYGON:
lastIdentifyResultGraphic.symbol = objPolygonSymbol;
_map.extent = lastIdentifyResultGraphic.geometry.extent.expand(0.001);
break;
}
graphicsLayer.add(lastIdentifyResultGraphic);
}
}
See the GraphicUtil class from com.esri.ags.Utils package. You can use the method "getGraphicsExtent" to generate an extent from an array of Graphics. You then use the extent to set the zoom factor of your map :
var graphics:ArrayCollection = graphicsLayer.graphicProvider as ArrayCollection;
var graphicsArr:Array = graphics.toArray();
// Create an extent from the currently selected graphics
var uExtent:Extent;
uExtent = GraphicUtil.getGraphicsExtent(graphicsArr);
// Zoom to extent created
if (uExtent)
{
map.extent = uExtent;
}
In this case, it would zoom to the full content of your graphics layer. You can always create an array containing only the features you want to zoom to. If you find that the zoom is too close to your data, you can also use map.zoomOut() after setting the extent.
Note: Be careful if you'Ve got TextSymbols in your graphics, it will break the GraphicUtil. In this case you need to filter out the Graphics with TextSymbols
Derp : Did not see the thread was 5 months old... Hope my answer helps other people
Here's the code I am using to blur an image using BitmapData. The function is called on a Slider_changeHandler(event:Event):voidevent and the value of the slider is passed to the function as blurvalue.
The problem is the function works but seems to be cummalative (if that's the correct word!), that is, suppose I slide it to the maximum and after that try to reduce the blur by sliding it back towards the front the blur still keeps increasing. How do I make it to work so when I will slide it up blur increases and when I slide it back blur decreases and when slider is at 0, no blur is applied.
var blur:BlurFilter = new BlurFilter();
blur.blurX = blurvalue;
blur.blurY = blurvalue;
blur.quality = BitmapFilterQuality.MEDIUM;
bitmapdata.applyFilter(bitmapdata,new
Rectangle(0,0,bitmapdata.width,bitmapdata.height),new Point(0,0),
blur);
return bitmapdata;
how about returning a clone of the original bitmapData with the filter applied ?
e.g.
var result:BitmapData = bitmapdata.clone();
var blur:BlurFilter = new BlurFilter();
blur.blurX = blurvalue;
blur.blurY = blurvalue;
blur.quality = BitmapFilterQuality.MEDIUM;
result.applyFilter(result,new
Rectangle(0,0,bitmapdata.width,bitmapdata.height),new Point(0,0),blur);
return result;
Also, if you're using the BlurFilter, you might need a larger rectangle, depending on the amount of blur. For that, you can use the generateFilterRect() method to get correct sized rectangle for the filter.
If I were you, I'd take the BitmapData and put it in a Bitmap object, then add the filters:
var bitmap:Bitmap = new Bitmap(bitmapData);
var blur:BlurFilter = new BlurFilter();
blur.blurX = blurvalue;
blur.blurY = blurvalue;
blur.quality = BitmapFilterQuality.MEDIUM;
bitmap.filters = [blur];
By doing this (interchanging the filters array), you're not making the filters cumulative.