MATLAB: How to access data in structure by finding one of its elements

cellfindsearchstructstructure

I am making project using data from map. I have 2 structures. "ways" and "nodes". Nodes contain ID, Latitude and Longitude. Ways contain information about "which nodes (IDs) represent this road (they are points marking street)" and data I want to retrieve. Structures are following:
nodes =
version: 0.6000
generator: 'Overpass API'
osm3s: [1x1 struct]
elements: {37952x1 cell}
In elements there are cells containing 1×1 struct like that:
nodes.elements{1,1} =
type: 'node'
id: 30788202
lat: 54.4719
lon: 18.4729
———————
Ways are similar:
ways =
version: 0.6000
generator: 'Overpass API'
osm3s: [1x1 struct]
elements: [247x1 struct]
Inside:
ways.elements =
247x1 struct array with fields:
type
id
nodes
tags
For example:
ways.elements(1) =
type: 'way'
id: 4941052
nodes: [6x1 double]
tags: [1x1 struct]
—————————
Now. I have GPS positions from my ride (a lot of them) – lat and lon. I create small box around each of my positions and then search all the nodes to check if any of them is inside my box. If I find that I am near the node, I search all the ways to find what road is it (I check ways.elements(i).nodes if they contain the node near me). When found I take necessary data from tags.
————————-
Code looks like that:
diff = 0.1e-4; %to create small box around my position
%initialization
speedlimit = zeros(length(GPSinterpol),1);
previousNode = 0; %just to make sure they are different

nodeID = -1; %just to make sure they are different
for k=1:length(GPSinterpol) %for all positions I have been at
position = GPSinterpol(k,:);
positionplus = position + diff; %create upper right corner of the box
positionminus = position - diff; %create lower left corner of the box
for i = 1:length(nodes.elements) %search all nodes
%check if any node is inside my box
if all([nodes.elements{i,1}.lat nodes.elements{i,1}.lon] > positionminus) && all([nodes.elements{i,1}.lat nodes.elements{i,1}.lon] < positionplus)
nodeID = nodes.elements{i,1}.id;
if nodeID ~= previousNode
%find way corresponding our node
for j = 1:length(ways.elements) %search all ways
if any(ways.elements(j,1).nodes == nodeID) %way found
speedlimit(k) = str2double(ways.elements(j,1).tags.maxspeed); %get speedlimit from tags
break
end
end
end
break
end
end
if nodeID == previousNode
speedlimit(k) = speedlimit(k-1);
end
previousNode = nodeID;
end
My question is how to do this better ;). Using loops is extremely slow for that amount of data and I want to use it on even bigger. I hope there are better ways to search structures

Best Answer

First, don't use diff as a variable name since it will stop you using the very useful diff function.
Secondly, it's always dangerous to use length on 2D data, since it returns the length of the largest dimension which you assume is the number of rows. Maybe one day, you'll have only one GPS position, and length will return the number of columns causing failure later on the code. size(GPSinterpol, 1) is guaranteed to return the number of rows no matter what, so why not use it.
Thirdly, from your code, it looks like a node can only belong to one way, or if a node belongs to more than one way, you always select the last way that include that node. So, instead of doing the lookup of which way a node belongs to in a loop, you could do it just once for all the nodes at the start. Personally, I would add the way id to the node structure, but you could also use a containers.map or a simple vector;
waynodes = {way.elements.nodes} %concatenate all the way nodes into a cell array
wayids = repelem([way.elements.id], cellfun(@numel, waynodes)); %replicate the way ids to match the nodes
waynodes = cell2mat(waynodes); %convert cell array into column vector
%store as plain matrix that can be looked up:
nodewaymap = [waynodes, wayids'];
%looking up the way id for a node is
nodewaymap(nodewaymap(:, 1) == nodeID), 2)
%store as containers.Map
nodewaymap = containers.Map(waynodes, wayids); %may fail if node belongs to more than one way
%looking up the way id for a node is simply
nodewaymap(nodeID)
%or add as new field of node structure
nodesstruct = [nodes.elements{:}]; %the cell array complicates everything
nodesid = [nodesstruct.id];
[~, order] = ismember(nodesid, waynodes); %I'm assuming all nodes belong to a way
wayids = num2cell(wayids(order));
[nodesstruct.wayid] = deal(wayids{:}); %add new field in the right order
%looking up is then straightforward:
nodesstruct.wayid
As you can see in the comment above, the storage of the nodes as scalar structure in a cell array, complicates things. You'll be better off converting that cell array into a an array structure:
nodesstruct = [nodes.elements{:}];
Finally, you can indeed get rid of the node searching loop:
positiondiff = 0.1e-4
positionplus = GPSinterpol + positiondiff;
positionminus = GPSinterpol + positionminus;
nodeslatlon = [cat(3, nodesstruct.lan), cat(3, nodesstruct.lon)];
%in R2016b or later:
ismatch = squeeze(all(nodeslatlon > positionminus & nodeslatlon < positionplus));
%versions < R2016b:
ismatch = squeeze(all(bsxfun(@gt, nodeslatlon, positionminus) & bsxfun(@lt, nodeslatlon, positionplus)));
%rows of ismatch are the GPSlocation, columns are the nodes. A 1 indicates that location and node match, so
[~, nodeindex] = max(ismatch, 2); %returns the index of the node that correspond to locations
And depending on the method you used to store the wayid:
%as matrix:
[~, rowindex] = ismember(nodeindex, nodewaymap(:, 1));
gspwayid = nodewaymap(rowindex, 2);
%as map
[~, mapindex] = ismember(nodeindex, nodewaymap.keys);
gpswayid = [nodewaymap.values{mapindex}];
%as field
gpswayid = [nodesstruct(nodeindex).wayid];
And converting to speed:
[~, wayindex] = ismember(gpswayid, [way.elements.id]);
tags = [way.elements(wayindex).tags];
speedlimit = str2double({tags.maxspeed});
And the above just made me realise that storing the structure index of the ways instead of the id, would avoid that last code to ismember.