Last active
August 29, 2015 13:57
-
-
Save briantjacobs/9355399 to your computer and use it in GitHub Desktop.
Styled maps in Matlab
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Copyright (c) 2010-2013, Zohar Bar-Yehuda | |
Copyright (c) 2010, Val Schmidt | |
All rights reserved. | |
Redistribution and use in source and binary forms, with or without | |
modification, are permitted provided that the following conditions are | |
met: | |
* Redistributions of source code must retain the above copyright | |
notice, this list of conditions and the following disclaimer. | |
* Redistributions in binary form must reproduce the above copyright | |
notice, this list of conditions and the following disclaimer in | |
the documentation and/or other materials provided with the distribution | |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE | |
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | |
POSSIBILITY OF SUCH DAMAGE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function varargout = plot_google_map(varargin) | |
% function h = plot_google_map(varargin) | |
% Plots a google map on the current axes using the Google Static Maps API | |
% | |
% USAGE: | |
% h = plot_google_map(Property, Value,...) | |
% Plots the map on the given axes. Used also if no output is specified | |
% | |
% Or: | |
% [lonVec latVec imag] = plot_google_map(Property, Value,...) | |
% Returns the map without plotting it | |
% | |
% PROPERTIES: | |
% Height (640) - Height of the image in pixels (max 640) | |
% Width (640) - Width of the image in pixels (max 640) | |
% Scale (2) - (1/2) Resolution scale factor . using Scale=2 will | |
% double the resulotion of the downloaded image (up | |
% to 1280x1280) and will result in finer rendering, | |
% but processing time will be longer. | |
% MapType - ('roadmap') Type of map to return. Any of [roadmap, | |
% satellite, terrain, hybrid] See the Google Maps API for | |
% more information. | |
% Alpha (1) - (0-1) Transparency level of the map (0 is fully | |
% transparent). While the map is always | |
% moved to the bottom of the plot (i.e. will | |
% not hide previously drawn items), this can | |
% be useful in order to increase readability | |
% if many colors are ploted (using SCATTER | |
% for example). | |
% ShowLabels (1) - (0/1) Controls wheter to display city/street textual labels on the map | |
% Marker - The marker argument is a text string with fields | |
% conforming to the Google Maps API. The | |
% following are valid examples: | |
% '43.0738740,-70.713993' (default midsize orange marker) | |
% '43.0738740,-70.713993,blue' (midsize blue marker) | |
% '43.0738740,-70.713993,yellowa' (midsize yellow | |
% marker with label "A") | |
% '43.0738740,-70.713993,tinyredb' (tiny red marker | |
% with label "B") | |
% Refresh (1) - (0/1) defines whether to automatically refresh the | |
% map upon zoom/pan action on the figure. | |
% AutoAxis (1) - (0/1) defines whether to automatically adjust the axis | |
% of the plot to avoid the map being stretched. | |
% This will adjust the span to be correct | |
% according to the shape of the map axes. | |
% APIKey - (string) set your own API key which you obtained from Google: | |
% http://developers.google.com/maps/documentation/staticmaps/#api_key | |
% This will enable up to 25,000 map requests per day, | |
% compared to a few hundred requests without a key. | |
% To set the key, use: | |
% plot_google_map('APIKey','SomeLongStringObtaindFromGoogle') | |
% You need to do this only once to set the key. | |
% To disable the use of a key, use: | |
% plot_google_map('APIKey','') | |
% | |
% OUTPUT: | |
% h - Handle to the plotted map | |
% | |
% lonVect - Vector of Longidute coordinates (WGS84) of the image | |
% latVect - Vector of Latidute coordinates (WGS84) of the image | |
% imag - Image matrix (height,width,3) of the map | |
% | |
% EXAMPLE - plot a map showing some capitals in Europe: | |
% lat = [48.8708 51.5188 41.9260 40.4312 52.523 37.982]; | |
% lon = [2.4131 -0.1300 12.4951 -3.6788 13.415 23.715]; | |
% plot(lon,lat,'.r','MarkerSize',20) | |
% plot_google_map | |
% | |
% References: | |
% http://www.mathworks.com/matlabcentral/fileexchange/24113 | |
% http://www.maptiler.org/google-maps-coordinates-tile-bounds-projection/ | |
% http://developers.google.com/maps/documentation/staticmaps/ | |
% | |
% Acknowledgement to Val Schmidt for his submission of get_google_map.m | |
% | |
% Author: | |
% Zohar Bar-Yehuda | |
% Version 1.3 - 06/10/2013 | |
% - Improved functionality of AutoAxis, which now handles any shape of map axes. | |
% Now also updates the extent of the map if the figure is resized. | |
% - Added the ShowLabels param which allows hiding the textual labels on the map. | |
% Version 1.2 - 16/06/2012 | |
% - Support use of the "scale=2" parameter by default for finer rendering (set scale=1 if too slow). | |
% - Auto-adjust axis extent so the map isn't stretched. | |
% - Set and use an API key which enables a much higher usage volume per day. | |
% Version 1.1 - 25/08/2011 | |
% store parameters in global variable (used for auto-refresh) | |
global inputParams | |
persistent apiKey | |
if isnumeric(apiKey) | |
% first run, check if API key file exists | |
if exist('api_key.mat','file') | |
load api_key | |
else | |
apiKey = ''; | |
end | |
end | |
axHandle = gca; | |
inputParams.(['ax' num2str(axHandle*1e6,'%.0f')]) = varargin; | |
% Handle input arguments | |
height = 640; | |
width = 640; | |
scale = 2; | |
maptype = 'roadmap'; | |
alphaData = 1; | |
autoRferesh = 1; | |
autoAxis = 1; | |
ShowLabels = 1; | |
hold on | |
markeridx = 1; | |
markerlist = {}; | |
if nargin >= 2 | |
for idx = 1:2:length(varargin) | |
switch lower(varargin{idx}) | |
case 'height' | |
height = varargin{idx+1}; | |
case 'width' | |
width = varargin{idx+1}; | |
case 'maptype' | |
maptype = varargin{idx+1}; | |
case 'alpha' | |
alphaData = varargin{idx+1}; | |
case 'refresh' | |
autoRferesh = varargin{idx+1}; | |
case 'showlabels' | |
ShowLabels = varargin{idx+1}; | |
case 'marker' | |
markerlist{markeridx} = varargin{idx+1}; | |
markeridx = markeridx + 1; | |
case 'autoaxis' | |
autoAxis = varargin{idx+1}; | |
case 'style' | |
styleParams = varargin{idx+1}; | |
case 'apikey' | |
apiKey = varargin{idx+1}; % set new key | |
% save key to file | |
funcFile = which('plot_google_map.m'); | |
pth = fileparts(funcFile); | |
keyFile = fullfile(pth,'api_key.mat'); | |
save(keyFile,'apiKey') | |
otherwise | |
error(['Unrecognized variable: ' varargin{idx}]) | |
end | |
end | |
end | |
if height > 640 | |
height = 640; | |
end | |
if width > 640 | |
width = 640; | |
end | |
curAxis = axis; | |
% Enforce Latitude constraints of EPSG:900913 | |
if curAxis(3) < -85 | |
curAxis(3) = -85; | |
end | |
if curAxis(4) > 85 | |
curAxis(4) = 85; | |
end | |
% Enforce longitude constrains | |
if curAxis(1) < -180 | |
curAxis(1) = -180; | |
end | |
if curAxis(1) > 180 | |
curAxis(1) = 0; | |
end | |
if curAxis(2) > 180 | |
curAxis(2) = 180; | |
end | |
if curAxis(2) < -180 | |
curAxis(2) = 0; | |
end | |
if isequal(curAxis,[0 1 0 1]) % probably an empty figure | |
% display world map | |
curAxis = [-200 200 -85 85]; | |
axis(curAxis) | |
end | |
if autoAxis | |
% adjust current axis limit to avoid strectched maps | |
[xExtent,yExtent] = latLonToMeters(curAxis(3:4), curAxis(1:2) ); | |
xExtent = diff(xExtent); % just the size of the span | |
yExtent = diff(yExtent); | |
% get axes aspect ratio | |
drawnow | |
org_units = get(axHandle,'Units'); | |
set(axHandle,'Units','Pixels') | |
ax_position = get(axHandle,'position'); | |
set(axHandle,'Units',org_units) | |
aspect_ratio = ax_position(4) / ax_position(3); | |
if xExtent*aspect_ratio > yExtent | |
centerX = mean(curAxis(1:2)); | |
centerY = mean(curAxis(3:4)); | |
spanX = (curAxis(2)-curAxis(1))/2; | |
spanY = (curAxis(4)-curAxis(3))/2; | |
% enlarge the Y extent | |
spanY = spanY*xExtent*aspect_ratio/yExtent; % new span | |
if spanY > 85 | |
spanX = spanX * 85 / spanY; | |
spanY = spanY * 85 / spanY; | |
end | |
curAxis(1) = centerX-spanX; | |
curAxis(2) = centerX+spanX; | |
curAxis(3) = centerY-spanY; | |
curAxis(4) = centerY+spanY; | |
elseif yExtent > xExtent*aspect_ratio | |
centerX = mean(curAxis(1:2)); | |
centerY = mean(curAxis(3:4)); | |
spanX = (curAxis(2)-curAxis(1))/2; | |
spanY = (curAxis(4)-curAxis(3))/2; | |
% enlarge the X extent | |
spanX = spanX*yExtent/(xExtent*aspect_ratio); % new span | |
if spanX > 180 | |
spanY = spanY * 180 / spanX; | |
spanX = spanX * 180 / spanX; | |
end | |
curAxis(1) = centerX-spanX; | |
curAxis(2) = centerX+spanX; | |
curAxis(3) = centerY-spanY; | |
curAxis(4) = centerY+spanY; | |
end | |
% Enforce Latitude constraints of EPSG:900913 | |
if curAxis(3) < -85 | |
curAxis(3:4) = curAxis(3:4) + (-85 - curAxis(3)); | |
end | |
if curAxis(4) > 85 | |
curAxis(3:4) = curAxis(3:4) + (85 - curAxis(4)); | |
end | |
axis(curAxis) % update axis as quickly as possible, before downloading new image | |
drawnow | |
end | |
% Delete previous map from plot (if exists) | |
if nargout <= 1 % only if in plotting mode | |
curChildren = get(axHandle,'children'); | |
map_objs = findobj(curChildren,'tag','gmap'); | |
bd_callback = []; | |
for idx = 1:length(map_objs) | |
if ~isempty(get(map_objs(idx),'ButtonDownFcn')) | |
% copy callback properties from current map | |
bd_callback = get(map_objs(idx),'ButtonDownFcn'); | |
end | |
end | |
delete(map_objs) | |
end | |
% Calculate zoom level for current axis limits | |
[xExtent,yExtent] = latLonToMeters(curAxis(3:4), curAxis(1:2) ); | |
minResX = diff(xExtent) / width; | |
minResY = diff(yExtent) / height; | |
minRes = max([minResX minResY]); | |
tileSize = 256; | |
initialResolution = 2 * pi * 6378137 / tileSize; % 156543.03392804062 for tileSize 256 pixels | |
zoomlevel = floor(log2(initialResolution/minRes)); | |
% Enforce valid zoom levels | |
if zoomlevel < 0 | |
zoomlevel = 0; | |
end | |
if zoomlevel > 19 | |
zoomlevel = 19; | |
end | |
% Calculate center coordinate in WGS1984 | |
lat = (curAxis(3)+curAxis(4))/2; | |
lon = (curAxis(1)+curAxis(2))/2; | |
% CONSTRUCT QUERY URL | |
preamble = 'http://maps.googleapis.com/maps/api/staticmap'; | |
location = ['?center=' num2str(lat,10) ',' num2str(lon,10)]; | |
zoomStr = ['&zoom=' num2str(zoomlevel)]; | |
sizeStr = ['&scale=' num2str(scale) '&size=' num2str(width) 'x' num2str(height)]; | |
maptypeStr = ['&maptype=' maptype ]; | |
styleStr = ['&style=' styleParams]; | |
if ~isempty(apiKey) | |
keyStr = ['&key=' apiKey]; | |
else | |
keyStr = ''; | |
end | |
markers = '&markers='; | |
for idx = 1:length(markerlist) | |
if idx < length(markerlist) | |
markers = [markers markerlist{idx} '%7C']; | |
else | |
markers = [markers markerlist{idx}]; | |
end | |
end | |
if ShowLabels == 0 | |
labelsStr = '&style=feature:all|element:labels|visibility:off'; | |
else | |
labelsStr = ''; | |
end | |
if ismember(maptype,{'satellite','hybrid'}) | |
filename = 'tmp.jpg'; | |
format = '&format=jpg'; | |
convertNeeded = 0; | |
else | |
filename = 'tmp.png'; | |
format = '&format=png'; | |
convertNeeded = 1; | |
end | |
sensor = '&sensor=false'; | |
url = [preamble location zoomStr sizeStr maptypeStr format markers labelsStr styleStr sensor keyStr]; | |
disp(url) | |
% Get the image | |
try | |
urlwrite(url,filename); | |
catch % error downloading map | |
warning(sprintf(['Unable to download map form Google Servers.\n' ... | |
'Possible reasons: no network connection, or quota exceeded.\n' ... | |
'Consider using an API key if quota problems persist.'])); | |
varargout{1} = []; | |
varargout{2} = []; | |
varargout{3} = []; | |
return | |
end | |
[M Mcolor] = imread(filename); | |
M = cast(M,'double'); | |
delete(filename); % delete temp file | |
width = size(M,2); | |
height = size(M,1); | |
% Calculate a meshgrid of pixel coordinates in EPSG:900913 | |
centerPixelY = round(height/2); | |
centerPixelX = round(width/2); | |
[centerX,centerY] = latLonToMeters(lat, lon ); % center coordinates in EPSG:900913 | |
curResolution = initialResolution / 2^zoomlevel/scale; % meters/pixel (EPSG:900913) | |
xVec = centerX + ((1:width)-centerPixelX) * curResolution; % x vector | |
yVec = centerY + ((height:-1:1)-centerPixelY) * curResolution; % y vector | |
[xMesh,yMesh] = meshgrid(xVec,yVec); % construct meshgrid | |
% convert meshgrid to WGS1984 | |
[lonMesh,latMesh] = metersToLatLon(xMesh,yMesh); | |
% We now want to convert the image from a colormap image with an uneven | |
% mesh grid, into an RGB truecolor image with a uniform grid. | |
% This would enable displaying it with IMAGE, instead of PCOLOR. | |
% Advantages are: | |
% 1) faster rendering | |
% 2) makes it possible to display together with other colormap annotations (PCOLOR, SCATTER etc.) | |
% Convert image from colormap type to RGB truecolor (if PNG is used) | |
if convertNeeded | |
imag = zeros(height,width,3); | |
for idx = 1:3 | |
imag(:,:,idx) = reshape(Mcolor(M(:)+1+(idx-1)*size(Mcolor,1)),height,width); | |
end | |
else | |
imag = M/255; | |
end | |
% Next, project the data into a uniform WGS1984 grid | |
sizeFactor = 1; % factoring of new image | |
uniHeight = round(height*sizeFactor); | |
uniWidth = round(width*sizeFactor); | |
latVect = linspace(latMesh(1,1),latMesh(end,1),uniHeight); | |
lonVect = linspace(lonMesh(1,1),lonMesh(1,end),uniWidth); | |
[uniLonMesh,uniLatMesh] = meshgrid(lonVect,latVect); | |
uniImag = zeros(uniHeight,uniWidth,3); | |
% old version (projection using INTERP2) | |
% for idx = 1:3 | |
% % 'nearest' method is the fastest. difference from other methods is neglible | |
% uniImag(:,:,idx) = interp2(lonMesh,latMesh,imag(:,:,idx),uniLonMesh,uniLatMesh,'nearest'); | |
% end | |
uniImag = myTurboInterp2(lonMesh,latMesh,imag,uniLonMesh,uniLatMesh); | |
if nargout <= 1 % plot map | |
% display image | |
h = image(lonVect,latVect,uniImag); | |
set(gca,'YDir','Normal') | |
set(h,'tag','gmap') | |
set(h,'AlphaData',alphaData) | |
% add a dummy image to allow pan/zoom out to x2 of the image extent | |
h_tmp = image(lonVect([1 end]),latVect([1 end]),zeros(2),'Visible','off'); | |
set(h_tmp,'tag','gmap') | |
% older version (display without conversion to uniform grid) | |
% h =pcolor(lonMesh,latMesh,(M)); | |
% colormap(Mcolor) | |
% caxis([0 255]) | |
% warning off % to avoid strange rendering warnings | |
% shading flat | |
uistack(h,'bottom') % move map to bottom (so it doesn't hide previously drawn annotations) | |
axis(curAxis) % restore original zoom | |
if nargout == 1 | |
varargout{1} = h; | |
end | |
% if auto-refresh mode - override zoom callback to allow autumatic | |
% refresh of map upon zoom actions. | |
zoomHandle = zoom; | |
panHandle = pan; | |
if autoRferesh | |
set(zoomHandle,'ActionPostCallback',@update_google_map); | |
set(panHandle, 'ActionPostCallback', @update_google_map); | |
else % disable zoom override | |
set(zoomHandle,'ActionPostCallback',[]); | |
set(panHandle, 'ActionPostCallback',[]); | |
end | |
% set callback for figure resize function, to update extents if figure | |
% is streched. | |
figHandle = get(axHandle,'Parent'); | |
set(figHandle, 'ResizeFcn', @update_google_map_fig); | |
% set callback properties | |
set(h,'ButtonDownFcn',bd_callback); | |
else % don't plot, only return map | |
varargout{1} = lonVect; | |
varargout{2} = latVect; | |
varargout{3} = uniImag; | |
end | |
% Coordinate transformation functions | |
function [lon,lat] = metersToLatLon(x,y) | |
% Converts XY point from Spherical Mercator EPSG:900913 to lat/lon in WGS84 Datum | |
originShift = 2 * pi * 6378137 / 2.0; % 20037508.342789244 | |
lon = (x ./ originShift) * 180; | |
lat = (y ./ originShift) * 180; | |
lat = 180 / pi * (2 * atan( exp( lat * pi / 180)) - pi / 2); | |
function [x,y] = latLonToMeters(lat, lon ) | |
% Converts given lat/lon in WGS84 Datum to XY in Spherical Mercator EPSG:900913" | |
originShift = 2 * pi * 6378137 / 2.0; % 20037508.342789244 | |
x = lon * originShift / 180; | |
y = log(tan((90 + lat) * pi / 360 )) / (pi / 180); | |
y = y * originShift / 180; | |
function ZI = myTurboInterp2(X,Y,Z,XI,YI) | |
% An extremely fast nearest neighbour 2D interpolation, assuming both input | |
% and output grids consist only of squares, meaning: | |
% - uniform X for each column | |
% - uniform Y for each row | |
XI = XI(1,:); | |
X = X(1,:); | |
YI = YI(:,1); | |
Y = Y(:,1); | |
xiPos = nan*ones(size(XI)); | |
xLen = length(X); | |
yiPos = nan*ones(size(YI)); | |
yLen = length(Y); | |
% find x conversion | |
xPos = 1; | |
for idx = 1:length(xiPos) | |
if XI(idx) >= X(1) && XI(idx) <= X(end) | |
while xPos < xLen && X(xPos+1)<XI(idx) | |
xPos = xPos + 1; | |
end | |
diffs = abs(X(xPos:xPos+1)-XI(idx)); | |
if diffs(1) < diffs(2) | |
xiPos(idx) = xPos; | |
else | |
xiPos(idx) = xPos + 1; | |
end | |
end | |
end | |
% find y conversion | |
yPos = 1; | |
for idx = 1:length(yiPos) | |
if YI(idx) <= Y(1) && YI(idx) >= Y(end) | |
while yPos < yLen && Y(yPos+1)>YI(idx) | |
yPos = yPos + 1; | |
end | |
diffs = abs(Y(yPos:yPos+1)-YI(idx)); | |
if diffs(1) < diffs(2) | |
yiPos(idx) = yPos; | |
else | |
yiPos(idx) = yPos + 1; | |
end | |
end | |
end | |
ZI = Z(yiPos,xiPos,:); | |
function update_google_map(obj,evd) | |
% callback function for auto-refresh | |
drawnow; | |
global inputParams | |
if isfield(inputParams,['ax' num2str(gca*1e6,'%.0f')]) | |
params = inputParams.(['ax' num2str(gca*1e6,'%.0f')]); | |
plot_google_map(params{:}); | |
end | |
function update_google_map_fig(obj,evd) | |
% callback function for auto-refresh | |
drawnow; | |
global inputParams | |
axes_objs = findobj(get(gcf,'children'),'type','axes'); | |
for idx = 1:length(axes_objs) | |
if ~isempty(findobj(get(axes_objs(idx),'children'),'tag','gmap')); | |
if isfield(inputParams,['ax' num2str(axes_objs(idx)*1e6,'%.0f')]) | |
params = inputParams.(['ax' num2str(axes_objs(idx)*1e6,'%.0f')]); | |
else | |
params = {}; | |
end | |
axes(axes_objs(idx)); | |
plot_google_map(params{:}); | |
break; | |
end | |
end | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
styleParams = '&style=saturation:-100&style=feature:transit|visibility:off&style=feature:road|visibility:simplified&style=feature:road.highway|visibility:off&style=feature:water|lightness:-6&style=element:labels.text|weight:0.1|gamma:4.25&style=feature:water|element:labels|visibility:off&style=feature:administrative.locality|visibility:off'; | |
lat = [48.8708 51.5188 41.9260 40.4312 52.523 37.982]; | |
lon = [2.4131 -0.1300 12.4951 -3.6788 13.415 23.715]; | |
plot(lon,lat,'.r','MarkerSize',20) | |
plot_google_map('maptype','roadmap','style',styleParams) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment