├── Boston_MA_satellite_11.png ├── Cambridge_MA_hybrid_15.png ├── circle_scatter_image.m ├── readme.txt ├── location_name_to_lat_lon.m ├── parse_google_location_data.m ├── fog_of_war.m └── plot_google_map.m /Boston_MA_satellite_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kindofdoon/fog_of_war/HEAD/Boston_MA_satellite_11.png -------------------------------------------------------------------------------- /Cambridge_MA_hybrid_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kindofdoon/fog_of_war/HEAD/Cambridge_MA_hybrid_15.png -------------------------------------------------------------------------------- /circle_scatter_image.m: -------------------------------------------------------------------------------- 1 | function I = circle_scatter_image(loc, circ_diam, ax_lim) 2 | 3 | % Returns an image of filled circles of diameter circ_diam at the 4 | % locations specific by loc. 5 | 6 | % Known limitation: cannot generate images with resolutions exceeding 7 | % screen size due to limitation of getframe command 8 | 9 | % DWD 17-1031 10 | 11 | IM_map = zeros(640); 12 | 13 | fig = 10; 14 | figure(fig) 15 | clf 16 | hold on 17 | plot(loc(:,2),loc(:,1),'o','MarkerEdgeColor','k','MarkerFaceColor','k','MarkerSize',circ_diam) 18 | axis(ax_lim) 19 | 20 | set(gca,'visible', 'off'); 21 | set(gcf,'color','white') 22 | set(gca,'Units','Pixels') 23 | set(gcf,'Units','Pixels') 24 | 25 | rect = [50 50 size(IM_map)]; 26 | 27 | set(gcf,'Position',[rect(1:2) rect(3:4)*1.1]) 28 | set(gca,'Position',rect) 29 | 30 | f = getframe(gcf,rect); 31 | close(fig) 32 | I = 255-(f.cdata); 33 | 34 | end -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | fog_of_war 2 | 3 | Code for generating "fog of war" maps from real-world location data collected by Google via your mobile phone, in the style of classic real-time strategy video games, implemented in MATLAB. 4 | 5 | DWD 17-1031 6 | 7 | Instructions: 8 | 9 | SETUP ================================= 10 | 11 | Go to: https://takeout.google.com/settings/takeout 12 | Log in if needed 13 | Click "Select none" 14 | Check "Location History" 15 | Verify that format is set to "JSON" 16 | Scroll to bottom, click "Next" 17 | Click "Create archive" 18 | Wait, then click "Download" 19 | Enter your password 20 | Open the .zip file that was downloaded 21 | Click "Extract all files" 22 | Unzip to same directory as this function 23 | Cut/paste ...\Takeout\Location History\Location History.json to same directory as this function 24 | Delete "Takeout" folder 25 | Run parse_google_location_data.m 26 | 27 | MAIN BODY ================================= 28 | 29 | Open fog_of_war.m and adjust the user inputs for geographic region and shading settings, then save the function 30 | Run fog_of_war.m 31 | An image file of the shaded map will automatically be created in the working directory -------------------------------------------------------------------------------- /location_name_to_lat_lon.m: -------------------------------------------------------------------------------- 1 | function [coords] = location_name_to_lat_lon(loc_name) 2 | 3 | % Uses the Google Maps Geocoding API to translate an plain-English 4 | % location to latitude-longitude coordinates. 5 | 6 | % Documentation: https://developers.google.com/maps/documentation/geocoding/start 7 | 8 | % DWD 17-1031 9 | 10 | filename = 'temp.json'; 11 | API_key = ''; % add your API_key if desired 12 | URL = ['https://maps.googleapis.com/maps/api/geocode/json?address=' loc_name '&key=' API_key]; 13 | 14 | urlwrite(URL, [filename]); 15 | fileID = fopen(filename,'r'); 16 | data = textscan(fileID,'%s','Delimiter','\n'); 17 | data = data{1,1}; 18 | 19 | lat = -1.111111; % set as defaults 20 | lon = -1.111111; 21 | 22 | for i = 1:size(data,1) % for each line 23 | if strcmp(data{i,1},'"location" : {') == 1 24 | lat_str = data{i+1}; 25 | lat = str2num(lat_str(9:end-1)); 26 | lon_str = data{i+2}; 27 | lon = str2num(lon_str(9:end-1)); 28 | end 29 | end 30 | coords = [lat lon]; 31 | 32 | fclose('all'); 33 | delete(filename) 34 | 35 | end 36 | -------------------------------------------------------------------------------- /parse_google_location_data.m: -------------------------------------------------------------------------------- 1 | function parse_google_location_data 2 | 3 | % Reads a JSON location history file from Google, and 4 | % parses it into a latitude/longitude matrix. 5 | 6 | % DWD 17-1031 7 | 8 | %% Instructions 9 | 10 | % Go to: https://takeout.google.com/settings/takeout 11 | % Log in if needed 12 | % Click "Select none" 13 | % Check "Location History" 14 | % Verify that format is set to "JSON" 15 | % Scroll to bottom, click "Next" 16 | % Click "Create archive" 17 | % Wait, then click "Download" 18 | % Enter your password 19 | % Open the .zip file that was downloaded 20 | % Click "Extract all files" 21 | % Unzip to same directory as this function 22 | % Cut/paste ...\Takeout\Location History\Location History.json to same directory as this function 23 | % Delete "Takeout" folder 24 | 25 | %% User inputs 26 | filename = 'Location History.json'; 27 | 28 | %% Parse the data 29 | fileID = fopen(filename,'r'); 30 | data = textscan(fileID,'%s','Delimiter','\n'); 31 | data = data{1,1}; 32 | 33 | loc = zeros(size(data,1),2); 34 | lat_ind = 1; 35 | lon_ind = 1; 36 | tic 37 | 38 | for i = 1:size(data,1) 39 | line = data{i,1}; 40 | if isempty(findstr(line,'E7'))==0 % found location data 41 | if isempty(findstr(line,'lat'))==0 % if latitude data 42 | loc(lat_ind,1) = str2double(line(16:end-1)); 43 | lat_ind = lat_ind+1; 44 | else % longitude data 45 | loc(lon_ind,2) = str2double(line(16:end-1)); 46 | lon_ind = lon_ind+1; 47 | end 48 | end 49 | end 50 | 51 | % Crop space that was allocated but not used 52 | loc(loc==0) = []; 53 | loc = reshape(loc,[size(loc,2)/2,2]); 54 | loc = loc./10^7; % convert from integer format to double 55 | 56 | runtime = toc; 57 | disp(['Parsed ' num2str(size(data,1)) ' lines in ' num2str(runtime) ' seconds, ' num2str(round(size(data,1)/runtime)) ' lines/second.']) 58 | save_name = [filename(1:end-5) '.mat']; 59 | save(save_name,'loc') 60 | disp(['Matrix of lat/lon points saved to file ' save_name]) 61 | 62 | end -------------------------------------------------------------------------------- /fog_of_war.m: -------------------------------------------------------------------------------- 1 | function fog_of_war 2 | 3 | % Pulls a map image from Google Static Maps, then overlays an opacity 4 | % image to un-shade regions that have been visited. 5 | 6 | % Must run parse_google_location_data.m first to generate the data file 7 | % required for this function. 8 | 9 | % DWD 17-1031 10 | 11 | %% User inputs 12 | location_data_filename = 'Location History.mat'; 13 | 14 | % Define geographic region of interest 15 | center = 'Somerville_MA'; % define as [lat, lon] or 'underscore_separated_plain_english_name' 16 | zoom = 15; % 1: world, 5: continent, 10: city, 15:streets, 20: buildings 17 | 18 | % Display parameters 19 | maptype = 'hybrid'; % satellite, hybrid, roadmap, or terrain 20 | circ_diam = 4; % larger = larger view distance 21 | scale = 1; % 1: 640x640 image, 2: 1280x1280 image; no other values supported currently 22 | 23 | % Opacity map parameters 24 | % standard deviation, opacity;... 25 | layers = [... 26 | 6, 0.25;... 27 | 2.5, 1;... 28 | ]; 29 | 30 | %% Prepare data 31 | load(location_data_filename,'loc') % load location data 32 | if ischar(center) 33 | save_name = center; 34 | [center] = location_name_to_lat_lon(center); % translate 35 | else % user input lat/long instead of place name 36 | save_name = [num2str(center(1)) '_' num2str(center(2))]; 37 | end 38 | save_name = [save_name '_' maptype '_' num2str(zoom) '.png']; 39 | [lon, lat, IM_map] = plot_google_map('maptype', maptype, 'zoom', zoom, 'center', center, 'scale', scale); 40 | 41 | % Crop locations outside domain of interest 42 | a = 1; 43 | loc_ = zeros(size(loc)); 44 | for i = 1:size(loc,1) 45 | if loc(i,1)min(lat) && loc(i,2)min(lon) % if within range 46 | loc_(a,:) = loc(i,:); 47 | a = a+1; 48 | end 49 | end 50 | loc = loc_; 51 | clear loc_ 52 | loc = loc(1:a-1,:); 53 | 54 | %% Generate the fog image 55 | ax_lim = [min(lon) max(lon) min(lat) max(lat)]; 56 | IM_fog = circle_scatter_image(loc, circ_diam, ax_lim); 57 | IM_fog = double(IM_fog); 58 | if scale == 2 59 | IM_fog = imresize(IM_fog,2); 60 | end 61 | 62 | % Build up the opacity mask 63 | IM_opa = zeros([size(IM_map,1),size(IM_map,2)]) + 0.15; % opacity map 64 | for i = 1:size(layers,1) 65 | IM_blu = imgaussfilt(IM_fog,layers(i,1)); % blurred 66 | IM_blu = rgb2gray(IM_blu); 67 | IM_blu = IM_blu./max(max(IM_blu)); % normalize to [0 1] 68 | 69 | IM_opa = IM_opa + IM_blu .* layers(i,2); 70 | end 71 | IM_opa(IM_opa > 1) = 1; 72 | 73 | 74 | %% Prepare and output the final image 75 | IM_res = zeros(size(IM_map)); 76 | IM_blk = zeros(size(IM_opa)); 77 | 78 | for cc = 1:3 % for each color channels 79 | IM_res(:,:,cc) = IM_blk + IM_map(:,:,cc) .* IM_opa; 80 | end 81 | 82 | figure(1) 83 | clf 84 | image(IM_res) 85 | axis tight 86 | axis equal 87 | imwrite(IM_res,save_name) 88 | 89 | end 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /plot_google_map.m: -------------------------------------------------------------------------------- 1 | function varargout = plot_google_map(varargin) 2 | 3 | % Adapted from https://www.mathworks.com/matlabcentral/fileexchange/27627-zoharby-plot-google-map 4 | 5 | % DWD 17-1031 6 | 7 | % function h = plot_google_map(varargin) 8 | % Plots a google map on the current axes using the Google Static Maps API 9 | % 10 | % USAGE: 11 | % h = plot_google_map(Property, Value,...) 12 | % Plots the map on the given axes. Used also if no output is specified 13 | % 14 | % Or: 15 | % [lonVec latVec imag] = plot_google_map(Property, Value,...) 16 | % Returns the map without plotting it 17 | % 18 | % PROPERTIES: 19 | % Axis - Axis handle. If not given, gca is used. (LP) 20 | % Height (640) - Height of the image in pixels (max 640) 21 | % Width (640) - Width of the image in pixels (max 640) 22 | % Scale (2) - (1/2) Resolution scale factor. Using Scale=2 will 23 | % double the resulotion of the downloaded image (up 24 | % to 1280x1280) and will result in finer rendering, 25 | % but processing time will be longer. 26 | % MapType - ('roadmap') Type of map to return. Any of [roadmap, 27 | % satellite, terrain, hybrid]. See the Google Maps API for 28 | % more information. 29 | % Alpha (1) - (0-1) Transparency level of the map (0 is fully 30 | % transparent). While the map is always moved to the 31 | % bottom of the plot (i.e. will not hide previously 32 | % drawn items), this can be useful in order to increase 33 | % readability if many colors are plotted 34 | % (using SCATTER for example). 35 | % ShowLabels (1) - (0/1) Controls whether to display city/street textual labels on the map 36 | % Language - (string) A 2 letter ISO 639-1 language code for displaying labels in a 37 | % local language instead of English (where available). 38 | % For example, for Chinese use: 39 | % plot_google_map('language','zh') 40 | % For the list of codes, see: 41 | % http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes 42 | % Marker - The marker argument is a text string with fields 43 | % conforming to the Google Maps API. The 44 | % following are valid examples: 45 | % '43.0738740,-70.713993' (default midsize orange marker) 46 | % '43.0738740,-70.713993,blue' (midsize blue marker) 47 | % '43.0738740,-70.713993,yellowa' (midsize yellow 48 | % marker with label "A") 49 | % '43.0738740,-70.713993,tinyredb' (tiny red marker 50 | % with label "B") 51 | % Refresh (1) - (0/1) defines whether to automatically refresh the 52 | % map upon zoom/pan action on the figure. 53 | % AutoAxis (1) - (0/1) defines whether to automatically adjust the axis 54 | % of the plot to avoid the map being stretched. 55 | % This will adjust the span to be correct 56 | % according to the shape of the map axes. 57 | % FigureResizeUpdate (1) - (0/1) defines whether to automatically refresh the 58 | % map upon resizing the figure. This will ensure map 59 | % isn't stretched after figure resize. 60 | % APIKey - (string) set your own API key which you obtained from Google: 61 | % http://developers.google.com/maps/documentation/staticmaps/#API_key 62 | % This will enable up to 25,000 map requests per day, 63 | % compared to a few hundred requests without a key. 64 | % To set the key, use: 65 | % plot_google_map('APIKey','SomeLongStringObtaindFromGoogle') 66 | % You need to do this only once to set the key. 67 | % To disable the use of a key, use: 68 | % plot_google_map('APIKey','') 69 | % 70 | % OUTPUT: 71 | % h - Handle to the plotted map 72 | % 73 | % lonVect - Vector of Longidute coordinates (WGS84) of the image 74 | % latVect - Vector of Latidute coordinates (WGS84) of the image 75 | % imag - Image matrix (height,width,3) of the map 76 | % 77 | % EXAMPLE - plot a map showing some capitals in Europe: 78 | % lat = [48.8708 51.5188 41.9260 40.4312 52.523 37.982]; 79 | % lon = [2.4131 -0.1300 12.4951 -3.6788 13.415 23.715]; 80 | % plot(lon,lat,'.r','MarkerSize',20) 81 | % plot_google_map 82 | % 83 | % References: 84 | % http://www.mathworks.com/matlabcentral/fileexchange/24113 85 | % http://www.maptiler.org/google-maps-coordinates-tile-bounds-projection/ 86 | % http://developers.google.com/maps/documentation/staticmaps/ 87 | % 88 | % Acknowledgements: 89 | % Val Schmidt for his submission of get_google_map.m 90 | % 91 | % Author: 92 | % Zohar Bar-Yehuda 93 | % 94 | % Version 1.5 - 20/11/2014 95 | % - Support for MATLAB R2014b 96 | % - several fixes complex layouts: several maps in one figure, 97 | % map inside a panel, specifying axis handle as input (thanks to Luke Plausin) 98 | % Version 1.4 - 25/03/2014 99 | % - Added the language parameter for showing labels in a local language 100 | % - Display the URL on error to allow easier debugging of API errors 101 | % Version 1.3 - 06/10/2013 102 | % - Improved functionality of AutoAxis, which now handles any shape of map axes. 103 | % Now also updates the extent of the map if the figure is resized. 104 | % - Added the showLabels parameter which allows hiding the textual labels on the map. 105 | % Version 1.2 - 16/06/2012 106 | % - Support use of the "scale=2" parameter by default for finer rendering (set scale=1 if too slow). 107 | % - Auto-adjust axis extent so the map isn't stretched. 108 | % - Set and use an API key which enables a much higher usage volume per day. 109 | % Version 1.1 - 25/08/2011 110 | 111 | % persistent apiKey 112 | % if isnumeric(apiKey) 113 | % % first run, check if API key file exists 114 | % if exist('API_key.mat','file') 115 | % load API_key 116 | % else 117 | % apiKey = ''; 118 | % end 119 | % end 120 | % hold on 121 | 122 | API_key = ''; % add your API_key if desired 123 | 124 | % Default parametrs 125 | % axHandle = gca; 126 | center_coords = [41.7473, -73.1887]; 127 | zoomlevel = 12; 128 | height = 640; 129 | width = 640; 130 | scale = 1; 131 | maptype = 'roadmap'; 132 | alphaData = 1; 133 | autoRefresh = 1; 134 | figureResizeUpdate = 1; 135 | autoAxis = 1; 136 | showLabels = 1; 137 | language = ''; 138 | markeridx = 1; 139 | markerlist = {}; 140 | 141 | % Handle input arguments 142 | if nargin >= 2 143 | for idx = 1:2:length(varargin) 144 | switch lower(varargin{idx}) 145 | case 'center' 146 | center_coords = varargin{idx+1}; 147 | case 'zoom' 148 | zoomlevel = varargin{idx+1}; 149 | case 'axis' 150 | axHandle = varargin{idx+1}; 151 | case 'height' 152 | height = varargin{idx+1}; 153 | case 'width' 154 | width = varargin{idx+1}; 155 | case 'maptype' 156 | maptype = varargin{idx+1}; 157 | case 'alpha' 158 | alphaData = varargin{idx+1}; 159 | case 'refresh' 160 | autoRefresh = varargin{idx+1}; 161 | case 'showlabels' 162 | showLabels = varargin{idx+1}; 163 | case 'figureresizeupdate' 164 | figureResizeUpdate = varargin{idx+1}; 165 | case 'language' 166 | language = varargin{idx+1}; 167 | case 'marker' 168 | markerlist{markeridx} = varargin{idx+1}; 169 | markeridx = markeridx + 1; 170 | case 'autoaxis' 171 | autoAxis = varargin{idx+1}; 172 | case 'scale' 173 | scale = varargin{idx+1}; 174 | % case 'apikey' 175 | % apiKey = varargin{idx+1}; % set new key 176 | % % save key to file 177 | % funcFile = which('plot_google_map.m'); 178 | % pth = fileparts(funcFile); 179 | % keyFile = fullfile(pth,'API_key.mat'); 180 | % save(keyFile,'apiKey') 181 | otherwise 182 | error(['Unrecognized variable: ' varargin{idx}]) 183 | end 184 | end 185 | end 186 | if height > 640 187 | height = 640; 188 | end 189 | if width > 640 190 | width = 640; 191 | end 192 | 193 | % % Store paramters in axis handle (for auto refresh callbacks) 194 | % ud = get(axHandle, 'UserData'); 195 | % ud.gmap_params = varargin; 196 | % set(axHandle, 'UserData', ud); 197 | % 198 | % curAxis = axis(axHandle); 199 | % % Enforce Latitude constraints of EPSG:900913 200 | % if curAxis(3) < -85 201 | % curAxis(3) = -85; 202 | % end 203 | % if curAxis(4) > 85 204 | % curAxis(4) = 85; 205 | % end 206 | % % Enforce longitude constrains 207 | % if curAxis(1) < -180 208 | % curAxis(1) = -180; 209 | % end 210 | % if curAxis(1) > 180 211 | % curAxis(1) = 0; 212 | % end 213 | % if curAxis(2) > 180 214 | % curAxis(2) = 180; 215 | % end 216 | % if curAxis(2) < -180 217 | % curAxis(2) = 0; 218 | % end 219 | % 220 | % if isequal(curAxis,[0 1 0 1]) % probably an empty figure 221 | % % display world map 222 | % curAxis = [-200 200 -85 85]; 223 | % axis(curAxis) 224 | % end 225 | 226 | 227 | % if autoAxis 228 | % % adjust current axis limit to avoid strectched maps 229 | % [xExtent,yExtent] = latLonToMeters(curAxis(3:4), curAxis(1:2) ); 230 | % xExtent = diff(xExtent); % just the size of the span 231 | % yExtent = diff(yExtent); 232 | % % get axes aspect ratio 233 | % drawnow 234 | % org_units = get(axHandle,'Units'); 235 | % set(axHandle,'Units','Pixels') 236 | % ax_position = get(axHandle,'position'); 237 | % set(axHandle,'Units',org_units) 238 | % aspect_ratio = ax_position(4) / ax_position(3); 239 | % 240 | % if xExtent*aspect_ratio > yExtent 241 | % centerX = mean(curAxis(1:2)); 242 | % centerY = mean(curAxis(3:4)); 243 | % spanX = (curAxis(2)-curAxis(1))/2; 244 | % spanY = (curAxis(4)-curAxis(3))/2; 245 | % 246 | % % enlarge the Y extent 247 | % spanY = spanY*xExtent*aspect_ratio/yExtent; % new span 248 | % if spanY > 85 249 | % spanX = spanX * 85 / spanY; 250 | % spanY = spanY * 85 / spanY; 251 | % end 252 | % curAxis(1) = centerX-spanX; 253 | % curAxis(2) = centerX+spanX; 254 | % curAxis(3) = centerY-spanY; 255 | % curAxis(4) = centerY+spanY; 256 | % elseif yExtent > xExtent*aspect_ratio 257 | % 258 | % centerX = mean(curAxis(1:2)); 259 | % centerY = mean(curAxis(3:4)); 260 | % spanX = (curAxis(2)-curAxis(1))/2; 261 | % spanY = (curAxis(4)-curAxis(3))/2; 262 | % % enlarge the X extent 263 | % spanX = spanX*yExtent/(xExtent*aspect_ratio); % new span 264 | % if spanX > 180 265 | % spanY = spanY * 180 / spanX; 266 | % spanX = spanX * 180 / spanX; 267 | % end 268 | % 269 | % curAxis(1) = centerX-spanX; 270 | % curAxis(2) = centerX+spanX; 271 | % curAxis(3) = centerY-spanY; 272 | % curAxis(4) = centerY+spanY; 273 | % end 274 | % % Enforce Latitude constraints of EPSG:900913 275 | % if curAxis(3) < -85 276 | % curAxis(3:4) = curAxis(3:4) + (-85 - curAxis(3)); 277 | % end 278 | % if curAxis(4) > 85 279 | % curAxis(3:4) = curAxis(3:4) + (85 - curAxis(4)); 280 | % end 281 | % axis(axHandle, curAxis); % update axis as quickly as possible, before downloading new image 282 | % drawnow 283 | % end 284 | 285 | % % Delete previous map from plot (if exists) 286 | % if nargout <= 1 % only if in plotting mode 287 | % curChildren = get(axHandle,'children'); 288 | % map_objs = findobj(curChildren,'tag','gmap'); 289 | % bd_callback = []; 290 | % for idx = 1:length(map_objs) 291 | % if ~isempty(get(map_objs(idx),'ButtonDownFcn')) 292 | % % copy callback properties from current map 293 | % bd_callback = get(map_objs(idx),'ButtonDownFcn'); 294 | % end 295 | % end 296 | % delete(map_objs) 297 | % 298 | % end 299 | 300 | % Calculate zoom level for current axis limits 301 | % [xExtent,yExtent] = latLonToMeters(curAxis(3:4), curAxis(1:2) ); 302 | % minResX = diff(xExtent) / width; 303 | % minResY = diff(yExtent) / height; 304 | % minRes = max([minResX minResY]); 305 | tileSize = 256; 306 | initialResolution = 2 * pi * 6378137 / tileSize; % 156543.03392804062 for tileSize 256 pixels 307 | % zoomlevel = floor(log2(initialResolution/minRes)); 308 | % 309 | % % Enforce valid zoom levels 310 | % if zoomlevel < 0 311 | % zoomlevel = 0; 312 | % end 313 | % if zoomlevel > 19 314 | % zoomlevel = 19; 315 | % end 316 | 317 | % Calculate center coordinate in WGS1984 318 | lat = center_coords(1);%43.7623;%(curAxis(3)+curAxis(4))/2; 319 | lon = center_coords(2);%-69.3203;%(curAxis(1)+curAxis(2))/2; 320 | 321 | % Construct query URL 322 | preamble = 'http://maps.googleapis.com/maps/api/staticmap'; 323 | location = ['?center=' num2str(lat) ',' num2str(lon)]; 324 | zoomStr = ['&zoom=' num2str(zoomlevel)]; 325 | sizeStr = ['&scale=' num2str(scale) '&size=' num2str(width) 'x' num2str(height)]; 326 | maptypeStr = ['&maptype=' maptype ]; 327 | % if ~isempty(apiKey) 328 | keyStr = ['&key=' API_key]; 329 | % else 330 | % keyStr = ''; 331 | % end 332 | markers = '&markers='; 333 | for idx = 1:length(markerlist) 334 | if idx < length(markerlist) 335 | markers = [markers markerlist{idx} '%7C']; 336 | else 337 | markers = [markers markerlist{idx}]; 338 | end 339 | end 340 | if showLabels == 0 341 | labelsStr = '&style=feature:all|element:labels|visibility:off'; 342 | else 343 | labelsStr = ''; 344 | end 345 | if ~isempty(language) 346 | languageStr = ['&language=' language]; 347 | else 348 | languageStr = ''; 349 | end 350 | 351 | if ismember(maptype,{'satellite','hybrid'}) 352 | filename = 'tmp.jpg'; 353 | format = '&format=jpg'; 354 | convertNeeded = 0; 355 | else 356 | filename = 'tmp.png'; 357 | format = '&format=png'; 358 | convertNeeded = 1; 359 | end 360 | sensor = '&sensor=false'; 361 | url = [preamble location zoomStr sizeStr maptypeStr format markers labelsStr languageStr sensor keyStr]; 362 | 363 | % Get the image 364 | try 365 | urlwrite(url,filename); 366 | catch % error downloading map 367 | warning(sprintf(['Unable to download map form Google Servers.\n' ... 368 | 'Possible reasons: no network connection, quota exceeded, or some other error.\n' ... 369 | 'Consider using an API key if quota problems persist.\n\n' ... 370 | 'To debug, try pasting the following URL in your browser, which may result in a more informative error:\n%s'], url)); 371 | varargout{1} = []; 372 | varargout{2} = []; 373 | varargout{3} = []; 374 | return 375 | end 376 | [M Mcolor] = imread(filename); 377 | M = cast(M,'double'); 378 | delete(filename); % delete temp file 379 | width = size(M,2); 380 | height = size(M,1); 381 | 382 | % Calculate a meshgrid of pixel coordinates in EPSG:900913 383 | centerPixelY = round(height/2); 384 | centerPixelX = round(width/2); 385 | [centerX,centerY] = latLonToMeters(lat, lon ); % center coordinates in EPSG:900913 386 | curResolution = initialResolution / 2^zoomlevel/scale; % meters/pixel (EPSG:900913) 387 | xVec = centerX + ((1:width)-centerPixelX) * curResolution; % x vector 388 | yVec = centerY + ((height:-1:1)-centerPixelY) * curResolution; % y vector 389 | [xMesh,yMesh] = meshgrid(xVec,yVec); % construct meshgrid 390 | 391 | % convert meshgrid to WGS1984 392 | [lonMesh,latMesh] = metersToLatLon(xMesh,yMesh); 393 | 394 | % We now want to convert the image from a colormap image with an uneven 395 | % mesh grid, into an RGB truecolor image with a uniform grid. 396 | % This would enable displaying it with IMAGE, instead of PCOLOR. 397 | % Advantages are: 398 | % 1) faster rendering 399 | % 2) makes it possible to display together with other colormap annotations (PCOLOR, SCATTER etc.) 400 | 401 | % Convert image from colormap type to RGB truecolor (if PNG is used) 402 | if convertNeeded 403 | imag = zeros(height,width,3); 404 | for idx = 1:3 405 | imag(:,:,idx) = reshape(Mcolor(M(:)+1+(idx-1)*size(Mcolor,1)),height,width); 406 | end 407 | else 408 | imag = M/255; 409 | end 410 | 411 | % Next, project the data into a uniform WGS1984 grid 412 | sizeFactor = 1; % factoring of new image 413 | uniHeight = round(height*sizeFactor); 414 | uniWidth = round(width*sizeFactor); 415 | latVect = linspace(latMesh(1,1),latMesh(end,1),uniHeight); 416 | lonVect = linspace(lonMesh(1,1),lonMesh(1,end),uniWidth); 417 | [uniLonMesh,uniLatMesh] = meshgrid(lonVect,latVect); 418 | uniImag = zeros(uniHeight,uniWidth,3); 419 | 420 | % old version (projection using INTERP2) 421 | % for idx = 1:3 422 | % % 'nearest' method is the fastest. difference from other methods is neglible 423 | % uniImag(:,:,idx) = interp2(lonMesh,latMesh,imag(:,:,idx),uniLonMesh,uniLatMesh,'nearest'); 424 | % end 425 | uniImag = myTurboInterp2(lonMesh,latMesh,imag,uniLonMesh,uniLatMesh); 426 | 427 | if nargout <= 1 % plot map 428 | % display image 429 | hold(axHandle, 'on'); 430 | h = image(lonVect,latVect,uniImag, 'Parent', axHandle); 431 | set(axHandle,'YDir','Normal') 432 | set(h,'tag','gmap') 433 | set(h,'AlphaData',alphaData) 434 | 435 | % add a dummy image to allow pan/zoom out to x2 of the image extent 436 | h_tmp = image(lonVect([1 end]),latVect([1 end]),zeros(2),'Visible','off', 'Parent', axHandle); 437 | set(h_tmp,'tag','gmap') 438 | 439 | % older version (display without conversion to uniform grid) 440 | % h =pcolor(lonMesh,latMesh,(M)); 441 | % colormap(Mcolor) 442 | % caxis([0 255]) 443 | % warning off % to avoid strange rendering warnings 444 | % shading flat 445 | 446 | uistack(h,'bottom') % move map to bottom (so it doesn't hide previously drawn annotations) 447 | axis(axHandle, curAxis) % restore original zoom 448 | if nargout == 1 449 | varargout{1} = h; 450 | end 451 | 452 | % if auto-refresh mode - override zoom callback to allow autumatic 453 | % refresh of map upon zoom actions. 454 | figHandle = axHandle; 455 | while ~strcmpi(get(figHandle, 'Type'), 'figure') 456 | % Recursively search for parent figure in case axes are in a panel 457 | figHandle = get(figHandle, 'Parent'); 458 | end 459 | 460 | zoomHandle = zoom(axHandle); 461 | panHandle = pan(figHandle); % This isn't ideal, doesn't work for contained axis 462 | if autoRefresh 463 | set(zoomHandle,'ActionPostCallback',@update_google_map); 464 | set(panHandle, 'ActionPostCallback', @update_google_map); 465 | else % disable zoom override 466 | set(zoomHandle,'ActionPostCallback',[]); 467 | set(panHandle, 'ActionPostCallback',[]); 468 | end 469 | 470 | % set callback for figure resize function, to update extents if figure 471 | % is streched. 472 | if figureResizeUpdate &&isempty(get(figHandle, 'ResizeFcn')) 473 | % set only if not already set by someone else 474 | set(figHandle, 'ResizeFcn', @update_google_map_fig); 475 | end 476 | 477 | % set callback properties 478 | set(h,'ButtonDownFcn',bd_callback); 479 | else % don't plot, only return map 480 | varargout{1} = lonVect; 481 | varargout{2} = latVect; 482 | varargout{3} = uniImag; 483 | end 484 | 485 | 486 | % Coordinate transformation functions 487 | 488 | function [lon,lat] = metersToLatLon(x,y) 489 | % Converts XY point from Spherical Mercator EPSG:900913 to lat/lon in WGS84 Datum 490 | originShift = 2 * pi * 6378137 / 2.0; % 20037508.342789244 491 | lon = (x ./ originShift) * 180; 492 | lat = (y ./ originShift) * 180; 493 | lat = 180 / pi * (2 * atan( exp( lat * pi / 180)) - pi / 2); 494 | 495 | function [x,y] = latLonToMeters(lat, lon ) 496 | % Converts given lat/lon in WGS84 Datum to XY in Spherical Mercator EPSG:900913" 497 | originShift = 2 * pi * 6378137 / 2.0; % 20037508.342789244 498 | x = lon * originShift / 180; 499 | y = log(tan((90 + lat) * pi / 360 )) / (pi / 180); 500 | y = y * originShift / 180; 501 | 502 | 503 | function ZI = myTurboInterp2(X,Y,Z,XI,YI) 504 | % An extremely fast nearest neighbour 2D interpolation, assuming both input 505 | % and output grids consist only of squares, meaning: 506 | % - uniform X for each column 507 | % - uniform Y for each row 508 | XI = XI(1,:); 509 | X = X(1,:); 510 | YI = YI(:,1); 511 | Y = Y(:,1); 512 | 513 | xiPos = nan*ones(size(XI)); 514 | xLen = length(X); 515 | yiPos = nan*ones(size(YI)); 516 | yLen = length(Y); 517 | % find x conversion 518 | xPos = 1; 519 | for idx = 1:length(xiPos) 520 | if XI(idx) >= X(1) && XI(idx) <= X(end) 521 | while xPos < xLen && X(xPos+1)= Y(end) 536 | while yPos < yLen && Y(yPos+1)>YI(idx) 537 | yPos = yPos + 1; 538 | end 539 | diffs = abs(Y(yPos:yPos+1)-YI(idx)); 540 | if diffs(1) < diffs(2) 541 | yiPos(idx) = yPos; 542 | else 543 | yiPos(idx) = yPos + 1; 544 | end 545 | end 546 | end 547 | ZI = Z(yiPos,xiPos,:); 548 | 549 | 550 | function update_google_map(obj,evd) 551 | % callback function for auto-refresh 552 | drawnow; 553 | try 554 | axHandle = evd.Axes; 555 | catch ex 556 | % Event doesn't contain the correct axes. Panic! 557 | axHandle = gca; 558 | end 559 | ud = get(axHandle, 'UserData'); 560 | if isfield(ud, 'gmap_params') 561 | params = ud.gmap_params; 562 | plot_google_map(params{:}); 563 | end 564 | 565 | 566 | function update_google_map_fig(obj,evd) 567 | % callback function for auto-refresh 568 | drawnow; 569 | axes_objs = findobj(get(gcf,'children'),'type','axes'); 570 | for idx = 1:length(axes_objs) 571 | if ~isempty(findobj(get(axes_objs(idx),'children'),'tag','gmap')); 572 | ud = get(axes_objs(idx), 'UserData'); 573 | if isfield(ud, 'gmap_params') 574 | params = ud.gmap_params; 575 | else 576 | params = {}; 577 | end 578 | 579 | % Add axes to inputs if needed 580 | if ~sum(strcmpi(params, 'Axis')) 581 | params = [params, {'Axis', axes_objs(idx)}]; 582 | end 583 | plot_google_map(params{:}); 584 | end 585 | end 586 | 587 | --------------------------------------------------------------------------------