MapBox in cljs: Removing markers from map (after storing them in an atom) - clojurescript

Background:
In mapbox-gl-js, while you can remove layers and features from a map (because the references are stored), you cannot do the same with markers. Instead one has to store the reference to any added marker, otherwise one won't be able to remove them later.
var marker = new mapboxgl.Marker().addTo(map);
marker.remove();
Setup:
I have an atom where I add every marker I create, so I can later clean them.
(defonce markers (r/atom []))
(defn add-marker [map img coordinate]
(let [marker (create-marker img)]
(.setLngLat marker (clj->js coordinate))
(.addTo marker map)
(swap! markers conj marker)))
(defn clear-markers []
(doseq [m (array-seq markers)] (.remove m))
(reset! markers []))
If I call clear-markers however, nothing happens. No error, no warning, the marker just stays in the map.
If I remove the marker right after adding (just to try it out), it works as described in the docs:
(defn test-marker [map img coordinate]
(let [marker (create-marker img)]
(.setLngLat marker (clj->js coordinate))
(.addTo marker map)
(.remove marker)))
Obviously, with this code, the marker will be removed right after adding and thus never be on the map, which is not the desired behaviour, just a test.
I also tried other approaches on how to call .remove on the elements of the vector, the following was my very first try:
(defn clear-markers []
(map #(.remove %) markers))
I'm fairly new to Clojure(Script), so I try to understand where my mistake is.
Is the object in my vector not the same instance maybe, so removing it, will not affect the marker on the map?
Or do I have to do a different approach, when trying to execute sideeffected methods on objects in a vector?
Or did I miss something else entirely?

Just a quick guess, try replacing map with doseq here:
(defn clear-markers []
(doseq [marker #markers]
(.remove marker)))
The map function is lazy and won't run until it has to. Since it appears you are after a side-effect to remove markers, doseq is the right choice. It is intended for side-effects and always runs immediately. It always returns nil.
Also, you need to deref markers to get a vector, then just use that in doseq. Do not use array-seq since the atom stores a plain Clojure/Script vector, not a JS array.
Another tip: always prefer mapv to map. It is eager and removes many timing- & lazy-related issues. Be sure to study the Clojure CheatSheet and the CLJS version.
Also, beware that Reagent makes a big difference between a Clojure list vs a vector. You sometimes need to force an (eager) vector result into a seq with (seq ...) or a list via (apply list ...). You can also use the simple ->list function to emphasize what you are doing:
(s/defn ->list :- [s/Any]
"Coerce any sequential argument into a List."
[arg :- [s/Any]]
(apply list arg))

Related

Hover on <p> element to highlight geographic boundary, D3 Choropleth

I made a choropleth map of Chicago's 77 neighborhoods in D3.
The only challenge is, it's hard to know which neighborhood is which.
So, what I did was make divs with p elements (containing the neighborhood names) in the body of my HTML file and positioned them into a blank spot in my svg/canvas.
See a visual here.
What I'm trying to do is make it so when you hover over the name, the geographic boundary of the neighborhood highlights. Somehow I need to relate the geography to the text, but I have no idea how.
For a more robust solution, you would ideally want to add an id field to your data. This assumes that your data is some format (such as JSON). You may already have a unique identifier that you can use instead, but if not the following should work.
var i = 0;
for (var x in dataSet)
dataSet[x].id = ++i; //Or i++ for zero-based indexing
Now it depends on how you are generating the svg elements, but ideally you are using the enter function of d3. In that case, just create the result of enter as a variable and use it to append both the path (map portion) as well as the text.
var dataSelect = svg.selectAll(".item").data(data.items);
var dataEnter = dataSelect.enter();
dataEnter.append("path").....
dataEnter.append("text").....text(function(d){return d.label;}) //Using text because this is drawn inside the SVG.
With using the data and enter functions, the created objects automatically have the id data bound to them.
This makes it a simple case of text.id == path.id in your mouseover function.
svg.selectAll(".itemText").on("mouseover", function(textItem){
svg.selectAll(".item").each(function (cityItem){
if (cityItem.id == textItem.id)
d3.select(this).style("fill", "green");
else
d3.select(this).style("fill", function(d){return d.color;});
})
});
I've done this in a fiddle which you can see here
Note that this does not use p elements because ideally, if you're using SVG then you probably should use text elements. If you have to use p elements, then you can still use this general technique, but instead using p.text() as the matching factor on mouseover instead of id, assuming that the name is bound to your path data somewhere.

Best and most performant implementation of dynamic shapes in cesium

I am currently working an application that is using a Cesium Viewer. I need to be able to display a collection of shapes that will be updated dynamically. I am having trouble understanding the best way to do this.
I currently am using Entities and using CallbackProperties to allow for the updating of shapes.
You can through this into a sandcastle to get an idea of how I am doing this. There is a polygon object that is being used as the basis for the cesiumCallback, and it is getting edited by another piece of code. (simulated with the setTimeout)
var viewer = new Cesium.Viewer('cesiumContainer', {});
var polygon = {};
polygon.coordinates = [
{longitude: 0, latitude: 0, altitude: 0},
{longitude: 10, latitude: 10, altitude: 0},
{longitude: 10, latitude: 0, altitude: 0}
];
// converts generic style options to cesium one (aka color -> material)
var polOpts = {};
// function for getting location
polOpts.hierarchy = new Cesium.CallbackProperty(function() {
var hierarchy = [];
for (var i = 0; i < polygon.coordinates.length; i++) {
var coordinate = polygon.coordinates[i];
hierarchy.push(Cesium.Cartesian3.fromDegrees(coordinate.longitude, coordinate.latitude, coordinate.altitude));
}
return hierarchy;
}, false);
viewer.entities.add({polygon: polOpts});
setInterval(function(polygon){
polygon.coordinates[0].longitude--;
}.bind(this, polygon), 1000);
The polygon being passed in is a class that generically describes a polygon, so it has an array of coordinates and style options, as well as a render method that calls this method renderPolygon passing in itself.
This method of rendering shapes works for everything I need it to, but it is not very performant. There are two cases for shapes updating, one type of shape will be updated over a long period of time, as a slow rate like once every few seconds. The other is shapes that will will get updated many times, like thousands, in a few seconds, then not change again for a long time, if ever.
I had two ideas for how to fix this.
Idea 1:
Have two methods, a renderDynamicPolygon and a renderStaticPolygon.
The renderDynamicPolygon method would do the above functionality, using the cesiumCallbackProperties. This would be used for shapes that are getting updated many times during the short time they are being updated.
The renderStaticPolygon method would replace the entities properties that are using callbackProperties with constant values, once the updating is done.
This creates a lot of other work to make sure shapes are in the right state, and doesn't help the shapes that are being updated slowly over a long period of time.
Idea 2:
Similarly to how the primitives work, I tried removing the old entity and adding it again with its updated properties each time its need to be updated, but this resulted in flickering, and unlike primitives, i could not find a async property for entities.
I also tried using primitives. It worked great for polylines, I would simply remove the old one and add a new one with the updated properties. I was also using the async = false to ensure there was no flickering. This issue I ran into here was not all shapes can be created using primitives. (Is this true?)
The other thing I tried was using the geometry instance using the geometry and appearance. After going through the tutorial on the cesium website I was able to render a few shapes, and could update the appearance, but found it close to impossible to figure out how to update the shapes correctly, and also have a very hard time getting them to look correct. Shapes need to have the right shape, a fill color and opacity and a stroke color, opacity and weight. I tried to use the polygonOutlineGeometry, but had not luck.
What would be the best way to implement this? Are one of these options headed the right way or is there some other method of doing this I have not uncovered yet?
[Edit] I added an answer of where I have gotten, but still not complete and looking for answers.
I have came up with a pretty good solution to this, but it still has one small issue.
I made too ways of showing entities. I am calling one render and one paint. Render uses the the Cesium.CallbackProperty with the isConstant property true, and paint with the isConstantProperty false.
Then I created a function to change the an entity from render to paint and vice vera. It goes through the entities callback properties an uses the setCallback property to overwrite the property with a the correct function and isConstant value.
Example:
I create a ellipse based on a circle object I have defined.
// isConst is True if it is being "painted" and false if it is being "rendered"
ellipse: lenz.util.extend(this._getStyleOptions(circle), {
semiMinorAxis: new Cesium.CallbackProperty(
this._getRadius.bind(this, circle),
isConst
),
semiMajorAxis: new Cesium.CallbackProperty(
this._getRadius.bind(this, circle),
isConst
),
})
So when the shape is being updated (while the user is drawing a shape) the shape is rendered with the isConstant being false.
Then when the drawing is complete it is converted to the painted version using some code like this:
existingEntity.ellipse.semiMinorAxis.setCallback(
this._getRadius.bind(this, circle),
isConst
);
existingEntity.ellipse.semiMajorAxis.setCallback(
this._getRadius.bind(this, circle, 1),
isConst
);
This works great performance wise. I am able to draw hundreds of shapes without the frame dropping much at all. I have attached a screen shot of the cesium map with 612 entities before and after my changes, the frame rate is in the upper right using the chrome render tool.
Before: Locked up at fps 0.9
Note: I redacted the rest of the ui, witch makes the globe look cut off, sorry
And after the changes: The fps remains at 59.9, almost perfect!
Whenever the entity is 'converted' from using constant to not constant callback properties, it and all other entities of the same type flash off then on again. I cannot find a better way to do this conversion. I feel as thought there must still be some thing I am missing.
You could try using a PositionPropertyArray as the polygon's hierarchy with SampledPositionProperty for any dynamic positions and ConstantPositionProperty for any static positions. I'm not sure if it would perform any better than your solution, but it might be worth testing. Here is an example of how it might work that you can paste into the Cesium Sandcastle:
var viewer = new Cesium.Viewer('cesiumContainer', {});
// required if you want no interpolation of position between times
var noInterpolation = {
type: 'No Interpolation',
getRequiredDataPoints: function (degree) {
return 2;
},
interpolateOrderZero: function (x, xTable, yTable, yStride, result) {
if (!Cesium.defined(result)) {
result = new Array(yStride);
}
for (var i = 0; i < yStride; i++) {
result[i] = yTable[i];
}
return result;
}
};
var start = viewer.clock.currentTime;
// set up the sampled position property
var sampledPositionProperty = new Cesium.SampledPositionProperty();
sampledPositionProperty.forwardExtrapolationType = Cesium.ExtrapolationType.HOLD;
sampledPositionProperty.addSample(start, new Cesium.Cartesian3.fromDegrees(0, 0)); // initial position
sampledPositionProperty.setInterpolationOptions({
interpolationAlgorithm: noInterpolation
});
// set up the sampled position property array
var positions = [
sampledPositionProperty,
new Cesium.ConstantPositionProperty(new Cesium.Cartesian3.fromDegrees(10, 10)),
new Cesium.ConstantPositionProperty(new Cesium.Cartesian3.fromDegrees(10, 0))
];
// add the polygon to Cesium viewer
var polygonEntity = new Cesium.Entity({
polygon: {
hierarchy: new Cesium.PositionPropertyArray(positions)
}
});
viewer.zoomTo(viewer.entities.add(polygonEntity));
// add a sample every second
var counter = 1;
setInterval(function(positionArray) {
var time = new Cesium.JulianDate.addSeconds(start, counter, new Cesium.JulianDate());
var position = new Cesium.Cartesian3.fromDegrees(-counter, 0);
positionArray[0].addSample(time, position);
counter++;
}.bind(this, positions), 1000);
One nice thing about this is you can set the timeline start/end time to a reasonable range and use it to see your polygon at any time within the sample range so you can see the history of your polygons through time (See here for how to change the timeline start/end time). Additionally, you don't need to use timers to set the positions, the time is built in to the SampledPositionProperty (although you can still add samples asynchronously).
However, this also means that the position depends on the current time in the timeline instead of a real-time array value. And you might need to keep track of a time somewhere if you aren't adding all the samples at once.
I've also never done this using ellipses before, but the semiMinorAxis and semiMajorAxis are properties, so you might still be able to use a SampledProperty.
Of course, this doesn't really matter if there are still performance issues. Hopefully it will improve as you don't need to recreate the array from scratch each callback and, depending on how you're getting the data to update the polygons, you might be able to add multiple samples at once. This is just speculation, but it's something to consider.
EDIT
Cesium can handle quite a bit of samples added to a sampled position, for example in the above code if you add a million samples to the position it takes a few seconds to load them all, but renders the polygon at any time without any performance issues. To test this, instead of adding samples using a timer, just add them all directly to the property.
for (var i = 0; i < 1000000; i++) {
var time = new Cesium.JulianDate.addSeconds(start, i, new Cesium.JulianDate());
var position = new Cesium.Cartesian3.fromDegrees(-(i % 2), 0);
positions[0].addSample(time, position);
}
However, if you run into memory problems currently there is no way to remove samples from a position property without accessing private variables. A work around would be to periodically create a new array containing new position properties and use the previous position property array's setValue() method to clear previous values or perhaps to use a TimeIntervalCollectionProperty as in this answer and remove time intervals with the removeInterval method.

Proj4JS projection definition for rotated pole

I am trying to define a rotated pole projection in Proj4JS where the north pole is now is 48N and 176E. I haven't been able to find any other example of rotated-poles in Proj4JS so I have tried to convert one I found for PROJ.4.
proj4.defs('myProjection', '+proj=ob_tran +o_proj=latlon +o_lon_p=-176 +o_lat_p=48 +lon_0=0 +a=1 +to_meter=0.0174532925199');
That line of JS is run without problem, but when I try to use that projection
proj4('EPSG:4326', 'myProjection', [175, -41]);
I get this error
uncaught exception: myProjection
I've tried replacing the projection definition the one for WGS84 and it works fine, so I believe my use of the function is correct, it's the parameters in that string that I am unsure of.
I think what you want is the so-called Azimuthal Equidistant projection. It's the best choice for measuring true distances radiating away from a center point.
If this is what you're looking for, I asked a similar question a while back over on GIS.SE, and for the coordinate you provided (48N, 176E), you could declare the Proj4js projection definition as so..
Proj4js.defs["CUSTOM:10001"] = "+proj=aeqd +lat_0=48.0 +lon_0=176.0 +x_0=0 +y_0=0 +ellps=WGS84 +datum=WGS84 +units=m +no_defs";
I hope it helps.

Selection function comments while expanding in Lisp modes

I'm using a keyboard shortcut bound to:
er/expand-region, which is an interactive Lisp function in `expand-region-core.el'.
to expand the region.
For example when I want to select a function and move it around.
My problem is that if I want to select any function like, say:
;; some comment related to the function
(defn foo [x]
...)
I cannot "expand" to include ";; some comment". As soon as I expand more than the function (without the comment) it expends the full buffer.
While I'd like it to first expand to include the function and the comment and then the full buffer.
It's bothering me so much that I'm temporarily doing this as a workaround:
(defn foo [x]
;; some comment
...)
How can I modify er/expand-region (or another function) so that after expanding to the full function it expands the comments right above the function before expanding to the whole buffer?
From Magnar Sveen, the creator of the package expand-region, taken from his github:
Example:
Let's say you want expand-region to also mark paragraphs and pages in
text-mode. Incidentally Emacs already comes with mark-paragraph and
mark-page. To add it to the try-list, do this:
(defun er/add-text-mode-expansions () (make-variable-buffer-local
'er/try-expand-list) (setq er/try-expand-list (append
er/try-expand-list
'(mark-paragraph
mark-page))))
(add-hook 'text-mode-hook 'er/add-text-mode-expansions)
Add that to
its own file, and add it to the expand-region.el-file, where it says
"Mode-specific expansions"
Warning: Badly written expansions might slow down expand-region
dramatically. Remember to exit quickly before you start traversing the
entire document looking for constructs to mark.
I would say you could add "er/mark-paragraph" to the expand-region list, that should do it.
Following user Dualinity's advice, I added the following to clojure-mode-expansions.el (can be done for other modes than Clojure of course) :
;; added this line at the beginning of the file
(require 'org-mode-expansions)
Then I added the line er/mark-paragraph to the expand list inside the er/add-clojure-mode-expansions method:
(defun er/add-clojure-mode-expansions ()
"Adds clojure-specific expansions for buffers in clojure-mode"
(set (make-local-variable 'er/try-expand-list) (append
er/try-expand-list
'(er/mark-clj-word
er/mark-clj-regexp-literal
er/mark-paragraph ; added this line
er/mark-clj-function-literal))))
I restarted Emacs (not too sure as to what was needed to be sure it was taken into account so I restarted the whole thing).
And that's it: now expanding selects "outer" function comments too.

Connecting two handles in Matlab

I would like to connect my two axes.handles so that when the mouse button is clicked on one, the other one would also do what the first one do. I have an external function that executes out what I want to do when the mouse is clicked. I just need to update the two handles in GUI so that it will do the same thing when one axes is clicked.
In Main GUI
function testminiproj_OpeningFcn(hObject, ~, handles, varargin)
handles.output = hObject;
handles.done=0;
guidata(hObject, handles);
setappdata(0,'figureHandle',gcf);
setappdata(gcf,'axesHandle1',handles.axes6);
setappdata(gcf,'axesHandle2',handles.axes7);
And this is my external function which is callback into main GUI by calling mousemotion;
function varargout = mousemotion(this,varargin)
%// get the figure handle from the application main data
figureHandle = getappdata(0,'figureHandle');
%// get the axes handle from the figure data
axesHandle1 = getappdata(figureHandle,'axesHandle1');
%// get the axes handle from the figure data
axesHandle2 = getappdata(figureHandle,'axesHandle2');
global rdata;
if nargin<1
set(gcf,'WindowButtonDownFcn','mousemotion(''down'')');
set(gcf,'WindowButtonUpFcn','mousemotion(''up'')');
set(gcf,'WindowButtonMotionFcn','');
Appreciate any help. I am bad at trying to put the question across. Hope someone could help. Thanks.
You can make a handles vector. Like this:
axesHandles = [axesHandles1; axesHandles2];
set(axesHandles, 'PropertyName', PropertyValue);
This way both axes' property values will be set to PropertyValue.
You need to work around and manually find out which axes is clicked in.
It's actually not hard.
Just use the Position attribute of the figure and axes.