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

Multiple GeoJSON timeline layers, with a single timeline control #23

Closed
DrYSG opened this issue Nov 23, 2015 · 28 comments
Closed

Multiple GeoJSON timeline layers, with a single timeline control #23

DrYSG opened this issue Nov 23, 2015 · 28 comments
Milestone

Comments

@DrYSG
Copy link

DrYSG commented Nov 23, 2015

I really like the simplicity and speed of your plug in. There is a much more complex one for leaflet: https://github.com/socib/Leaflet.TimeDimension but I see no advantages for GeoJSON with it. Except for one feature I could really use (unless I am missing something, and it is already possible).

How can I have multiple GeoJSON (actually your sub-class timeline) all controlled synchronounsly from one timeline control? Yes, I could manage the visibility within the layer via properties, but then I lose out on all the layer control plugins (tree, select) and and the other features that come with leveraging the native Leaflet layer model.

I have not dived into the source, but it seems like this would be a great feature for those who need to compare event streams and look for conjunctions of events.

@skeate
Copy link
Owner

skeate commented Nov 25, 2015

To see if I understand this correctly: add multiple Leaflet.timeline layers, and all of those get handled by one timeline control, which shows all events from all timeline layers?

Sounds very reasonable, and would probably help clean up the code in reworking it. Right now there's a lot of interdependency between the control and the layer; implementing this would sort of force a separation.

Could get hairy on edge cases, e.g. the timeline is currently playing and then the user toggles a layer.

@DrYSG
Copy link
Author

DrYSG commented Nov 25, 2015

You have it exactly. The cleanest from the user API viewpoint. Would be if be to have the first L.Timeline work as normal, and if you chose to add a second, it would only add the GeoJSON layer, and would "connect" to the original Timeline control. If you are refactoring, then I would make each layer subscribe to the Timeline control, and seperate the code modularly. But an alternative is to make the TimeLineControl (TLC) the master and have the layers register with it, and it loops through the layers to "animate" them.

There probably are other ways to tackle this also.

Our use case is a small area of land where we have lots of sensors (internet of things) and we are trying to compare what features they pick up and which they don't and what footprints they have and which they do not. We want to see all the A/B choices.

My worry right now is about the Date() resolution. Sensor data is coming in at 100Hz in some cases and so I need the start and stop to be about 10ms apart on the timeline.

@DrYSG
Copy link
Author

DrYSG commented Nov 25, 2015

RE: edge conditions

If the master is the layer (it does the animating) and just subscribes to time events, you can handle the layer invisible issue cleanly. You could also have the TLC as master and simply update the GeoJSON layer if it was invisible. I would leave the tick marks as they were even if a layer was turned off, it seems too much work to keep separate bags for each set of tic marks.

@skeate
Copy link
Owner

skeate commented Nov 25, 2015

Regarding your last point, I think that shouldn't matter. The timeline automatically adjusts to the endpoints of the data given to it, and calculates a step amount based on that. I don't remember anything forcing the step to be an integer.

It does raise a point about the multiple-layers thing. I guess there could be two modes for merging, offset and absolute. Basically if you have one layer which is 10ms of data at noon on day 1, then another layer which is 10ms of data at noon on day 2, then the timeline will show two blips one day apart for absolute mode, or one 10ms span from 0 to 10ms in offset mode

@DrYSG
Copy link
Author

DrYSG commented Nov 25, 2015

Well, most use cases I can think of where you have 2+ layers is where the data actually overlaps in time. Not blips widely scattered. You want to do A & B comparison of data at the same time. If data is for a few seconds each day (with lots of data at each time). Then it is going to be really hard to get the duration and steps with the right granularity. You would have to go to a different time model, where gaps are fine and the timeline moves by time-varying intervals based on data presence.

@skeate
Copy link
Owner

skeate commented Nov 25, 2015

Offset mode would basically make the layers behave as if the first start time for each was the same for all.

So...

Times of dataset 1:   0   5  10  15
Times of dataset 2: 100 106 112 113

Absolute:

1    1    1    1       2     2     22
|----+----+----+ . . . +----+----+----|
0    5   10   15      100  105  110  115

Offset:

1
2         12        1   2 2   1
|---------+---------+---------+
0         5        10        15

(note: would've been better to show dataset 1 and 2 as starting at non-zero times to show that offset would always start at 0 regardless of the datasets.. but I already typed this up as it is 😓)

Because the timeline just jumps by a certain step each frame, if the data you have is in tiny clusters relative to the overall time span, the animation would show it all happening in one frame. (Though using the next/previous would still work, because those just find the next point in the data, they don't move stepwise)

The same thing could be achieved by just preprocessing the data to align them all to the same start point, but this way might be easier for some people.

@DrYSG
Copy link
Author

DrYSG commented Nov 25, 2015

I would not be fond of the offset mode. I do not want the analysis framework shifting the times of my data to indicate overlap when they really are separate.

Of course, I might be looking at this all through my "soda straw" :-(

But what I have is a lot of sensors (LIDAR, Stereo Vision, Doppler Radar, etc) all reporting of features. What I want to do is FUSION of the sensors. Each sample is time-stamped with an absolute GPS clock value. This is key for the FUSION, since I need to know what sensors see simultaneously. I don't want an shift of timeline. I want to see in Leaflet the raw overlay and figure out what I need for Fusion algorithms.

Also I really like what you did with intervalFromFeature, function, since I don't get the data stream tagged with start/stop. Instead I get a time stamp for each sample. Knowing the sample rate (4Hz, 50 Hz, 1000Hz) I can then use the intervalFromFeature to compute start/stop.

Yes, there might be need to overlap separate runs, but I have pretty good tools for bulk processing the JSON if I want to (SAFE FME). So if I want to align separate runs on separate days I just have to add an offset to the intervalFromFeature function. That strikes me as the best way to get offsets.

BTW, I assume you know about the trade-offs of continuous time vs. discrete time:

https://en.wikipedia.org/wiki/Discrete_time_and_continuous_time
http://math.stackexchange.com/questions/348560/continuous-time-versus-discrete-time-stochastic-models

@skeate
Copy link
Owner

skeate commented Nov 29, 2015

The offset vs absolute mode would be a toggleable option, defaulting to absolute. Kind of in keeping with the "minimal external processing needed" course this has been taking (e.g. with the intervalFromFeature function that vencax wrote).

Also, while I don't think it's quite possible right now, the amount of work to shift it away from Date-based values on the slider is fairly negligible. It would still be the default, since this is Leaflet.timeline, but you could at least switch that functionality out for any kind of scale you want -- unitless floating points, temperatures, populations...

@DrYSG
Copy link
Author

DrYSG commented Nov 30, 2015

Now that is a fascinating generalization, and I applaud it.

@DrYSG
Copy link
Author

DrYSG commented Dec 2, 2015

Perhaps this should be a different issue, but I noticed that the "change" event does not return much useful info. While you are refactoring could you have it return the current time on the timeline, and the list of GeoJSON features that are active?

Why do I ask?

I have a GeoJSON file that has point features for the location of a automobile and its heading. I am using a RotableMarker (https://github.com/bbecquet/Leaflet.RotatedMarker) that shows that marker. I just allow the GeoJSON layer to default for pointToLayer, it would have a blue marker. I want it to set the makers as invisible on load, and then to render it manual on each timeline change event (translate and rotate).

I think you could stick the active features for the timeline into a jagged array, with a bin for each bucket in the duration/step. Then put the active features for that bucket of time in the list. It should be easy to return that pointer with the event object.

What do you think?

@skeate
Copy link
Owner

skeate commented Dec 2, 2015

You can (I believe) already access the time with L.Timeline#time, and the currently displayed layers with L.Timeline#displayedLayers, e.g.:

var layer = L.timeline(...);
layer.on('change', function() {
  var currentTime = layer.time;
  var currentLayers = layer.displayedLayers;
});

It's easy enough to throw that into the change event data (and I will), but if you want to use it now you should be able to.

@DrYSG
Copy link
Author

DrYSG commented Dec 2, 2015

I was hoping for convenience values attached to the event, because what I need is less the actual time, but more the features that are active at CurrentTime. But both would be good:

e.time = Timeline.Date
e.features  = [ {featureA, featureB, ...]; // across all GeoJson Timeline objects controlled by the timeline.

@skeate
Copy link
Owner

skeate commented Dec 3, 2015

Right, that's what's in displayedLayers (though not across all layers since multiple layers on one control isn't a thing yet).

Could perhaps better be called displayedFeatures

@DrYSG
Copy link
Author

DrYSG commented Dec 3, 2015

Neat. Tried it and it works great. BTW, if you want another demo of what you can do with your timeline, I have a simple one that could be in the examples for doing cloud and pollution spills. It is about 10 seconds of animation.

croppercapture 1

@skeate
Copy link
Owner

skeate commented Dec 3, 2015

I was actually planning on adding something in the readme asking for anyone who uses it to let me know what sorts of things they're doing with it, and maybe including links to examples "in the wild" (with permission of course). I've only seen a few, but they've all been really neat.

@skeate skeate added this to the 1.0.0 milestone Dec 7, 2015
@skeate
Copy link
Owner

skeate commented Dec 8, 2015

I've published a beta of 1.0.0, which (among other things) allows multiple L.Timeline layers to be controlled by one L.TimelineSliderControl.

Please try it out, and let me know if anything's awry, either with the code or the docs. Thanks!

@DrYSG
Copy link
Author

DrYSG commented Dec 8, 2015

I will, but one quick question, that is not covered in the documentation. What we call a klutz question: If you want multiple GeoJSON layers controlled by a single Timeline, what do you do? Do I guess right that you just keep adding Leaflet.Timelines()? What if they have different time ranges, durations, steps, time, formoutOutput, enablePlayback? And if not, then how does one add a second GeoJSON layer to the existing timeline?

@skeate
Copy link
Owner

skeate commented Dec 8, 2015

Yeah, each GeoJSON layer would be a separate L.Timeline. I need to add something to add more features to an existing layer, but for now if you want multiple separate features in one layer you could wrap them up in a GeoJSON featureCollection.

The layers themselves no longer have a concept of duration, steps, etc. All of that is managed by the control. The only options for the layers is getInterval (previously intervalFromFeature) and drawOnSetTime.

The layers expose the start (x) and end (y) of their datasets and the control figures out the range it should have based on all of the layers it manages (min of xs, max of ys). You could also specify a start and end in the control's options, and then the playback range is always between those values, no matter what data exists on the layers.

The steps and duration aren't really meant to have anything to do with the data. They're a means of mucking with the playback speed. Individual points are accessible if necessary by dragging the slider and/or using the next/prev buttons

@DrYSG
Copy link
Author

DrYSG commented Dec 8, 2015

Ok, I got it sorry for being dense. I am working on other parts of the problem this morning, and the brains cells for javascript are being used for UDP, Python, WebSockets, and a DotNet C# gateway that is translating from binary UDP sockets to GeoJSON for this layer. I want to get this tested this afternoon.

@DrYSG
Copy link
Author

DrYSG commented Dec 8, 2015

In addition to Polyline and Polygon layers, I have GeoJSON layer that has point data (the location of a car). It seemed to me a waste to instantiate over 5,000 marker objects for a GeoJSON timeline for that layer. So what I tried to do is set a GeoJSON.filter function that checks to see if the GeoJSON has GPS property, and not "show" that GeoJSON layer.

         this.show = function (feature, layer) {
             var type = feature.properties.type;
             switch (type) {
                 case "Oxford.GPS":
                     return false;
                 default:
                     return true;
             }
         }

Then I catch the change event and get the list of objects, (and i was going to the properties to update a single marker (which is actually has a rotate with it)

Now, it is true that I get the change event for each object on the timeline (including these point objects) But the list of changed objects is 0, when I filter. But it is fine if I don't.

         this.newTime = function (e) {
             var currentTime = timeLayer.time;
             var currentLayers = timeLayer.displayedLayers;
             console.log("Timeline Change: " + JSON.stringify(e.type));
             $.each(currentLayers, function (index, layer) {
                 var bag = layer.geoJSON.properties;
                 var type = bag.type;
                 var loc = layer.geoJSON.geometry.coordinates;
                 switch (type) {
                     case "Oxford.GPS":
                         Overlays.doGPS(loc, bag);
                         break;
                     default:
                         console.log("overlays.newTime: unknown GeoJSON type: " + type);
                 }
             });
         }

        this.doGPS = function (loc, props) {
             CarState.lat = loc[1];
             CarState.lon = loc[0];
             CarState.alt = loc[2];
             CarState.heading = props.heading;
             CarState.time = props.start;
             CarState.valid = props.valid;
             switch (props.correction) {
                 case 0:
                     CarState.correction = "None";
                     break;
                 case 1:
                     CarState.correction = "WAAS";
                     break;
                 case 2:
                     CarState.correction = "Differential";
                     break;
                 case 3:
                     CarState.correction = "RTK";
                     break;
                 case 4:
                     CarState.correction = "Other";
                     break;
             }
             Mapper.update();
         }

@skeate
Copy link
Owner

skeate commented Dec 8, 2015

If you're filtering out features of type "Oxford.GPS", they never get added to the L.Timeline layer, and thus are never in displayedLayers.

But I think if I switch getDisplayed() back to the way it used to work, then it would return all the features at the current time, even if not actually displayed on the map. (though, a new name would be in order for that)

@DrYSG
Copy link
Author

DrYSG commented Dec 8, 2015

Right, I suspected that. But the odd thing was the change events were happing based on where the "invisible" items were. So they seemed to be added to the GeoJSON layer. So I was hoping it would be possible to get even the filtered ones back with the getDisplayed() without too much hassle.

getAllCurrent() ? (better than getDisplayedAndNotDisplayed() )

@DrYSG
Copy link
Author

DrYSG commented Dec 9, 2015

I know I am going to have to learn coffeeScript so I can help you directly (and figure out how to get gitHub source control to work with our firewall, I can do local git repositories, and our local server, but I.T. is nasty here, and we have real problems with going through the firewall. So I hope you don't mind and I will post the issues I have below.

@skeate
Copy link
Owner

skeate commented Dec 9, 2015

FWIW it's no longer written in Coffeescript; it's ES2015 (aka ES6) now. Still transpiled, but it's Javascript -- just from the future.

@DrYSG
Copy link
Author

DrYSG commented Dec 9, 2015

  1. I was finding that the POINT features, which default to a marker icon, were disappearing at random points in the timeline. It was driving me crazy. The lat/long was fine. What I nailed it down was the that Leaflet sets z-index based on latitude. So for stretches the car icon would disappear. What I think is the source is that they L.Timeline is adding all the markers to the marker pane, and that is creating 5K z-ordered values. And leaflet and for a lot of markers the Z-index is negative. so it goes below the tile layer.

This could be all fixed for me if I could specify the GeoJSON filter: function, and stilll gett the getDisplayed() function to return all of them. Right now I have a kludge to sett zIndexOffset for the car icon marker to 10,000. So that it stays on top of all the junk in the marker pane. I don't know if this is going to be an issue with the PolyLine and Polygon GeoJSON paths.

  1. For my application, I first create the L.TimeLineControl() and then the user chooses to add the L.Timelines by choosing log files (in GeoJSON Timeline Format) and this created new L.Timelines and adds them to the overlay control, This is NOT working.

Multiple issues;
a. start/stop is not known when the initial timellineControl is created.
b. I hacked this to have some reasonable values, but the subsequent load of the Timeline does not work at all. Here is what I used:
start: "2015-11-19T15:51:56.2660000Z",
end: "2015-11-19T15:52:57.3530000Z",
c. Bug: the formatOutput( date) function is getting a string value not a DateTime. so date.toISOString() does not work. After the initial string value of "2015-11-19T15:51:56.2660000Z",, it is feeding it a numeric value of 2015, or 44, or other values. clearly not a unix time.

  1. What are you going to do if a person uses the LayerControl to turn on/off a layer
  2. Why is the change event associated with a layer, seems more logical with the TimelineSlider, especailly since the getDisplayed is for the slider, not with the layer. Or swap both to the laery.

Minor documentation issues:

  1. Show how to created multiple TimeLines for one Slider (and add/remove Timelines) while leaving the Slider up.
  2. updateDisplayedLayers() reffered to but not described (very minor, I won't use this)
  3. since formatOutput is only for dates, why not call it formatDate, and document that this is for showing the date on the TilelineSlider control.
  4. Clarify that steps and duration is only used for the PLAY function of the TimelineSlider, but that clicking on locations does not use this.
  5. waitToIUpdateMap is really confusing. it seems that true means the same as drawOnSetTime since you say that if it is true, only the date is set, not the map. If formating the date is a problem, then you could call it manualDateUpdate, and manualMapUpdate, and have both false as defaults.

I probably need functions for setStart() and setEnd() since different logs will have different time ranges.

BTW, I did something slightly illegal, and extended the GeoJSON spec, At the end of each GeoJSON file I load I put a bunch of metadata, which is ignored in all the programs I have tried.

....

{"type":"Point","coordinates":[-83.697503617676588,42.302661024805644,278.33480834960938]},"type":"Feature"},

{"properties":{"heading":275.377625,"correction":3,"valid":true,"type":"Oxford.GPS","start":"2015-11-19T15:52:57.3530000Z","end":"2015-11-19T15:52:57.3630000Z"},
"geometry":{"type":"Point","coordinates":[-83.697504531266375,42.302661093646158,278.33636474609375]},"type":"Feature"}
   ],
  "version": "1.0",
  "samples": 6152,
  "start": "2015-11-19T15:51:56.2660000Z",
  "end": "2015-11-19T15:52:57.3530000Z",
  "duration": 61087.00,
  "description": "demo"
}

@DrYSG
Copy link
Author

DrYSG commented Dec 9, 2015

I found this undocumented method in your source: addTimelines() and did the call, but it did not update the TimelineSlider. Hey, thanks for switching to ES6!

@skeate
Copy link
Owner

skeate commented Dec 9, 2015

since formatOutput is only for dates, why not call it formatDate, and document that this is for showing the date on the TilelineSlider control.

The name was actually changed specifically because it doesn't refer only to dates anymore. The default behavior does treat everything like a date, but it's all overridable.

@DrYSG
Copy link
Author

DrYSG commented Dec 9, 2015

I think I got you. You are saying that begin/end pairs in the GeoJSON as well as the axis of the "timeline" can be any continuous monotonically increasing value. E.G. instead of time, one could have luminence, chrominance, or pressure,

Any comment on my other findings? Would some of the "geoJSOn files that I am using help". I want to help contribute to this.

@skeate skeate closed this as completed Feb 27, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants