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 tracks and <trk> segments #56

Closed
velocat opened this issue May 30, 2020 · 11 comments
Closed

Multiple tracks and <trk> segments #56

velocat opened this issue May 30, 2020 · 11 comments

Comments

@velocat
Copy link
Contributor

velocat commented May 30, 2020

Sometimes users can download gpx of this kind:

<gpx>
      <trk> .... </trk>
      <trk> .... </trk>
</gpx>

It contains several tracks, and they are not necessarily an extension of each other.
The ability to create such multitracks is provided by MapSource and BaseCamp - the basic programs for working with tracks.

Multitrack

It would be nice to use the area.defined feature to correctly display these codes:
https://observablehq.com/@d3/area-with-missing-data

let area = this._area = d3.area().curve(interpolation)
    .defined(d => !isNaN(d.z))
    .x(d => (d.xDiagCoord = x(d.dist)))
    .y0(this._height())
    .y1(d => y(d.z));

But I just can’t figure out how to apply the filter to SVG, like this:

svg.append("path")
 	.datum(d.z.filter(area.defined())) // d.z ??
 	.attr("fill", "#eee")
 	.attr("d", area);

I just did not quite understand the principle of building SVG.
Can you tell me a thought?

In general, it seems to me that it would not be bad to add this to the general code :)

@Raruto
Copy link
Owner

Raruto commented Jun 4, 2020

Hi velocat,

I just can’t figure out how to apply the filter to SVG, like this:

Based on your example, it could be such a thing:

var filteringFunc = this._area.defined();
var filteredData = this._data.filter(filteringFunc),

svg.append("path")
  .datum(filteredData)
  .attr("fill", "#eee")
  .attr("d", area);

I just did not quite understand the principle of building SVG.

Try reading this article to better understand how it works:

https://bocoup.com/blog/showing-missing-data-in-line-charts

In general, it seems to me that it would not be bad to add this to the general code :)

An "intermediate" solution already exists:

if (!this.options.skipNullZCoords && data.length > 0) {

But at the moment this library doesn't support multi-track segments, anyway pull requests are welcome.

Happy further investigations,
Raruto

@velocat
Copy link
Contributor Author

velocat commented Jun 4, 2020

But at the moment this library doesn't support multi-track segments, anyway pull requests are welcome.

As you can see from the picture above, I still adapted the multi-track like this:

loadTrace = function(id) {
  var Maintrace = {};

  $.ajax({
    //dataType: "xml", 
    url: "aljax2.php",
    data: {
      mode: "getcontent",
      id: id
    },
    success: function(xml) {
      Maintrace.gpx = new L.GPX(xml, {
        async: true,
        joinTrackSegments: true,
        joinTracks: false,
        marker_options: {
          wptIcons: arrayIcons,
          startIconUrl: null, //'images/map/icon-start.png' , 
          endIconUrl: null, //'images/map/icon-end.png' , 
          iconSize: [13, 13],
          iconAnchor: [6, 13],
          shadowUrl: ''
        },
        polyline_options: {
          color: colors[countColor],
          stroke: '#000',
          opacity: 0.75,
          weight: 4,
          lineCap: 'round',
        }
      }).on('loaded', function(e) {
        if (Tracks.length > 1) {
          controlLayer.addTo(mymap);
          controlLayer.addBaseLayer(e.target, "Все участки");
          MainTrack_id = e.target._leaflet_id;
        }
        var i = 0;
        for (var track in Tracks) {
          loadTrack(track, i++)
        }
        allLayers = e.layers;
        waypoints.addTo(mymap);
      }).on("addline", function(e) {
        var line = e.line;
        line.options.distanceMarkers = {
          lazy: true
        };
        if (!Maintrace.gpx.lines) {
          Maintrace.gpx.lines = []
        }
        Maintrace.gpx.line = line;
        Maintrace.gpx.lines.push(line)
        Tracks.push(e.element);
      }).on('addpoint', function(e) {
        waypoints.addLayer(e.point);
      });
      traces.push(Maintrace);
    }
  });
}


function loadTrack(track, i) {
  var trace = {};

  var XMLTrack = xmlToString(Tracks[track]);

  trace.gpx = new L.GPX(XMLTrack, {
    async: true,
    index: i,
    marker_options: {
      wptIcons: arrayIcons,
      startIconUrl: 'images/map/icon-start.png',
      endIconUrl: 'images/map/icon-end.png',
      iconSize: [13, 13],
      iconAnchor: [6, 13],
      shadowUrl: ''
    },
    polyline_options: {
      color: colors[countColor],
      stroke: '#000',
      opacity: 0.75,
      weight: 4,
      lineCap: 'round',
    }
  });

  trace.gpx.on('loaded', function(e) {
    controlLayer.addBaseLayer(e.target, e.target.get_name());
    if (e.target.options.index == 0) {
      setElevationTrack(0);
    } else {
      mymap.removeLayer(e.target);
    }
  })

  trace.gpx.on("addline", function(e) {
    var line = e.line;
    line.options.distanceMarkers = {
      lazy: true
    };
    trace.gpx.line = line;
  })

  trace.gpx.on("click", function(e) {
    //console.log(i)
  })

  trace.gpx.addTo(mymap);
  traces.push(trace);
}

The function that you indicated works great and that is why there is a flat line of heights from the end of one segment to the beginning of a new one. It is only necessary to paint over it :)) In general, it remains to deal with the paths in svg. If I find a solution, I will definitely share it.

@Raruto
Copy link
Owner

Raruto commented Jun 4, 2020

As you can see from the picture above, I still adapted the multi-track like this:

I can't see it in your code, but I think you solved this problem by calling the addData() function several times.

The function that you indicated works great and that is why there is a flat line of heights from the end of one segment to the beginning of a new one. It is only necessary to paint over it :)) In general, it remains to deal with the paths in svg.

In your tests, you can avoid editing library code and access to the <svg> element as follows:

var controlElevation = L.control.elevation( ... ); // leaflet.js chart object

// ...

var svg = d3.select(controlElevation.getContainer()).select("svg"); // d3.js object
var area = controlElevation._area;
var data = controlElevation._data;

If I find a solution, I will definitely share it.

Ok thanks, if it can help try to check this pull too: MrMufflon/Leaflet.Elevation#84

Have a nice day,
Raruto

@velocat
Copy link
Contributor Author

velocat commented Jun 4, 2020

I can't see it in your code, but I think you solved this problem by calling the addData() function several times.

I did not write code so that there was no long text, but if interested, then here it is:

setElevationTrack = function(index) {
  var trace = traces[index + 1]; // +1 i.e. 0=Main 
  controlElevation.clear();

  if (index == -1) {
    var Lines = trace.gpx.lines;
    Lines.forEach(function(line, i, arr) {
      controlElevation.addData(line);
      controlElevation.hide(); //remove()
      line.setStyle({
        color: colors[countColor],
        weight: 4,
      });
      if (triggerDM == true) {
        line.addDistanceMarkers();
      } else {
        line.removeDistanceMarkers();
      }
    });
    viewMain = true;
  } else {
    controlElevation.addData(trace.gpx.line);
    trace.gpx.setStyle({
      color: colors[countColor],
      weight: 4,
    });
    if (triggerDM == true) {
      trace.gpx.line.addDistanceMarkers();
    } else {
      trace.gpx.line.removeDistanceMarkers();
    }
    viewMain = false;
  }
  set_info(trace);
  mymap.fitBounds(trace.gpx.getBounds());
}

@Raruto
Copy link
Owner

Raruto commented Sep 30, 2020

Hi velocat,

I took a better look at the following example:

Screenshot_2020-09-30 Area with Missing Data(1)

Basically it works because where you have the value: undefined, the date value (eg. 2008-01-02) is still defined:

Screenshot_2020-09-30 Area with Missing Data

Unlike your dataset where you are also missing the x values:

<gpx>
      <trk> .... </trk>
      <trk> .... </trk>
</gpx>

So, i think that to achieve a result like in the following example:

Screenshot_2020-09-30 GPS Visualizer Dynamic elevation profiles in HTML maps

The data property should be refactored into an "array of arrays" (or something like that)

As usual, pull requests are welcome...

Have a nice day,
Raruto

@Raruto Raruto changed the title Area with Missing Data Multiple tracks and <trk> segments Oct 1, 2020
@carlos-mg89
Copy link

I've been doing some research, since multi segment tracks were working for me, and I've just realized that in version 1.5.6 (and below), multi segment tracks are working as expected. However, from version 1.6.0 and above, it doesn't work.

I guess that this change might have something to do with it: 0df53e9#diff-3274f1a37032fb0ae4e2823def0007c634e869ae0dfc304ff6a12c36513c3a52L138-L172

I can see that the library stopped using the leaflet-gpx library. And that library has support for multi segment tracks.

Now they work like a charm. Do you think that it'll be possible to revert this change? I find it quite important, since there are a lot of GPX tracks with several segments in it. It's not about loading several tracks, it's just the same track, with different segments.

@Raruto
Copy link
Owner

Raruto commented Feb 10, 2022

Hi Carlos,

theoretically this library should still be somehow backwards compatible with what produced by MrMufflon:

// https://github.com/MrMufflon/Leaflet.Elevation#how-to-use

var el = L.control.elevation();
el.addTo(map);
var g=new L.GPX("./mytrack.gpx", {async: true});
g.on("addline",function(e){
	el.addData(e.line);
});
g.addTo(map);

obviously taking care to include the necessary dependencies:

...

<!-- leaflet-gpx -->
<script src="https://unpkg.com/[email protected]/gpx.js"></script>

<!-- leaflet-elevation -->
<script src="https://unpkg.com/@raruto/[email protected]/dist/leaflet-elevation.min.js"></script>

...

and possibly integrating it with some changes from that function:

/**
 * Simple GPX data loader.
 */
function GPXLoader(data, control) {
  control = control || this;

  control.options.gpxOptions.polyline_options = L.extend({}, control.options.polyline, control.options.gpxOptions.polyline_options);

  if (control.options.theme) {
    control.options.gpxOptions.polyline_options.className += ' ' + control.options.theme;
  }

  let layer = new L.GPX(data, control.options.gpxOptions);

  // similar to L.GeoJSON.pointToLayer
  layer.on('addpoint', (e) => {
    control.fire("waypoint_added", e);
  });

  // similar to L.GeoJSON.onEachFeature
  layer.on("addline", (e) => {
    control.addData(e.line /*, layer*/ ); // NB uses "_addGPXData"
    control.track_info = L.extend({}, control.track_info, { type: "gpx", name: layer.get_name() });
  });

  // unlike the L.GeoJSON, L.GPX parsing is async
  layer.once('loaded', (e) => {
    L.Control.Elevation._d3LazyLoader.then(() => {
      control._fireEvt("eledata_loaded", { data: data, layer: layer, name: control.track_info.name, track_info: control.track_info });
    });
  });

  return layer;
}

Anyway, have you checked if this library parses them natively? https://github.com/tmcw/togeojson


I find it quite important, since there are a lot of GPX tracks with several segments in it. It's not about loading several tracks, it's just the same track, with different segments.

I understand this would be great, but personally I have no resources to devote to this feature... (anyway, pull requests and or recommendations are always welcome).

Have a nice day,
Raruto

@velocat
Copy link
Contributor Author

velocat commented Feb 11, 2022

I can share my experience:

I upload tracks via fileloader, even if they are on the server. In it, as a handler, they use their own parser "importGeojson", in which I sort through the segments separately, create layers for them and add them to the array of segments.

Next, I just switch between these layers, and each time I switch, I display the height profile for this segment, thereby always up-to-date information on the screen and there are no "gaps", as described above.

If I want to show all the segment layers at the same time, I just disable the height profile.

Segments

@Raruto
Copy link
Owner

Raruto commented Feb 13, 2022

@velocat @carlos-mg89 if it interests you here you can find what I have already tried to do in the past:

multitrack

as you can see the geojson segments are already managed natively (the only problem is to find a way to split the chart profiles):

var opts = {
	map: {
		center: [41.4583, 12.7059],
		zoom: 5,
		fullscreenControl: false,
		resizerControl: true,
		preferCanvas: true,
		rotate: true,
		bearing: 15,
		rotateControl: {
			closeOnZeroBearing: false
		},
	},
	elevationControl: {
		url: 'multi.gpx',
		options: {
			preferCanvas: false,
			theme: "lightblue-theme",
			collapsed: false,
			autohide: false,
			autofitBounds: true,
			position: "bottomleft",
			detached: true,
			summary: "inline",
			imperial: false,
			altitude: true, //"summary",
			distance: true, //"summary",
			slope: true, //"disabled",
			speed: "disabled",
			acceleration: "disabled",
			time: true, //"summary",
			timestamps: true,
			legend: true,
			followMarker: true,
			// zFollow: 13,
			ruler: true,
			// polyline: false,
			// marker: 'position-marker',
			// dragging: true,
			distanceMarkers: true,
			waypoints: true,
			wptIcons: true,
			wptLabels: true,
			almostOver: true,
		},
	},
	layersControl: {
		options: {
			collapsed: false,
		},
	},
};

var map              = new L.Map('map', opts.map);
var controlElevation = L.control.elevation(opts.elevationControl.options);
var controlLayer     = L.control.layers(null, null, opts.layersControl.options);

controlElevation.clear();
controlElevation.redraw();

map.on('eledata_loaded', function(e) {
	if (!controlLayer._map) controlLayer.addTo(map);
	controlElevation._gaps = controlElevation._data.reduce(function(array, item ) {
		if (item.trkEnd) array.push([]);
		if (array.length && (item.trkStart || item.trkEnd)) array[array.length-1].push(item);
		return array;
	}, []);
	e.layer.eachLayer((trkseg) => {
		if(trkseg.feature.geometry.type != "Point") {
			controlLayer.addOverlay(trkseg, trkseg.feature.properties.name);
		}
	});
	// console.log('eledata_loaded');
	// console.log(e);
});

controlElevation.on('eletrack_added', function(e) {
	this._data[0].trkStart = true;
	this._data[this._data.length-1].trkEnd = true;
	this.once('eledata_updated', function(e){
		this._data[this._data.length-1].trkStart = true;
	});
	// console.log('eletrack_added');
	// console.log(this._data[this._data.length-1], e);
});

controlElevation.load(opts.elevationControl.url);
controlElevation.addTo(map);

for those wishing to try, clone this repository into your localhost folder and inside move to the leaflet-elevation folder

  • git clone https://github.com/raruto/leaflet-elevation (inside your localhost folder, eg. /var/www)
  • cd leaflet-elevation
  • npm i
  • npm run dev

After that you can start developing inside the src and test folders (open "http://localhost/leaflet-elevation/test" in your browser to see your changes).

Have a nice day,
Raruto

Raruto added a commit that referenced this issue Feb 25, 2022
@Raruto
Copy link
Owner

Raruto commented Feb 25, 2022

Patch released in version 1.8.1

multi-track

Below a simple example of how to use this in conjunction with L.Control.Layers:

var map = new L.Map('map', opts.map);
var controlElevation = L.control.elevation(opts.elevationControl.options);
var controlLayer = L.control.layers(null, null, opts.layersControl.options);

// Optional
controlElevation.clear();
controlElevation.redraw();

// Add individual "multi-track" segments to layers control
map.on('eledata_loaded', function(e) {
  if (!controlLayer._map) controlLayer.addTo(map);
  e.layer.eachLayer((trkseg) => {
    if (trkseg.feature.geometry.type != "Point") {
      controlLayer.addOverlay(trkseg, trkseg.feature.properties.name);
    }
  });
});

controlElevation.load(opts.elevationControl.url);
controlElevation.addTo(map);

Heartily,
Raruto

@Raruto Raruto closed this as completed Feb 25, 2022
@carlos-mg89
Copy link

@Raruto I tried with the 1.8.x versions but didn't make it work. There were some issues.

However, I've just tried it with the 1.9.6 release, and now it works like a charm.

Thanks a lot!!!

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

3 participants