/// <summary> /// This is the method that actually does the work. /// </summary> /// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param> protected override void SolveInstance(IGH_DataAccess DA) { List <Curve> boundary = new List <Curve>(); DA.GetDataList <Curve>(0, boundary); int zoom = -1; DA.GetData <int>(1, ref zoom); string fileloc = ""; DA.GetData <string>(2, ref fileloc); if (!fileloc.EndsWith(@"\")) { fileloc = fileloc + @"\"; } string prefix = ""; DA.GetData <string>(3, ref prefix); if (prefix == "") { prefix = mbSource; } string URL = mbURL; //DA.GetData<string>(4, ref URL); ///get a valid mapbox token to send along with query string mbToken = ""; DA.GetData <string>(4, ref mbToken); if (mbToken == "") { string hmbToken = System.Environment.GetEnvironmentVariable("HERONMAPBOXTOKEN"); if (hmbToken != null) { mbToken = hmbToken; AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, "Using Mapbox token stored in Environment Variable HERONMAPBOXTOKEN."); } else { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "No Mapbox token is specified. Please get a valid token from mapbox.com"); return; } } bool run = false; DA.GetData <bool>("Run", ref run); GH_Structure <GH_String> mapList = new GH_Structure <GH_String>(); GH_Structure <GH_Rectangle> imgFrame = new GH_Structure <GH_Rectangle>(); GH_Structure <GH_String> tCount = new GH_Structure <GH_String>(); for (int i = 0; i < boundary.Count; i++) { GH_Path path = new GH_Path(i); int tileTotalCount = 0; int tileDownloadedCount = 0; //Get image frame for given boundary and make sure it's valid if (!boundary[i].GetBoundingBox(true).IsValid) { AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Boundary is not valid."); return; } BoundingBox boundaryBox = boundary[i].GetBoundingBox(true); ///TODO: look into scaling boundary to get buffer tiles ///file path for final image string imgPath = fileloc + prefix + "_" + i + ".jpg"; //location of final image file mapList.Append(new GH_String(imgPath), path); //create cache folder for images string cacheLoc = fileloc + @"HeronCache\"; List <string> cacheFileLocs = new List <string>(); if (!Directory.Exists(cacheLoc)) { Directory.CreateDirectory(cacheLoc); } //tile bounding box array List <Point3d> boxPtList = new List <Point3d>(); //get the tile coordinates for all tiles within boundary var ranges = Convert.GetTileRange(boundaryBox, zoom); List <List <int> > tileList = new List <List <int> >(); var x_range = ranges.XRange; var y_range = ranges.YRange; if (x_range.Length > 100 || y_range.Length > 100) { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "This tile range is too big (more than 100 tiles in the x or y direction). Check your units."); return; } //cycle through tiles to get bounding box for (int y = (int)y_range.Min; y <= y_range.Max; y++) { for (int x = (int)x_range.Min; x <= x_range.Max; x++) { //add bounding box of tile to list boxPtList.AddRange(Convert.GetTileAsPolygon(zoom, y, x).ToList()); cacheFileLocs.Add(cacheLoc + mbSource.Replace(" ", "") + zoom + x + y + ".jpg"); tileTotalCount = tileTotalCount + 1; } } tCount.Insert(new GH_String(tileTotalCount + " tiles (" + tileDownloadedCount + " downloaded / " + (tileTotalCount - tileDownloadedCount) + " cached)"), path, 0); //bounding box of tile boundaries BoundingBox bbox = new BoundingBox(boxPtList); var rect = BBoxToRect(bbox); imgFrame.Append(new GH_Rectangle(rect), path); AddPreviewItem(imgPath, boundary[i], rect); ///tile range as string for (de)serialization of TileCacheMeta string tileRangeString = zoom.ToString() + x_range[0].ToString() + y_range[0].ToString() + x_range[1].ToString() + y_range[1].ToString(); ///check if the existing final image already covers the boundary. ///if so, no need to download more or reassemble the cached tiles. if ((TileCacheMeta == tileRangeString) && Convert.CheckCacheImagesExist(cacheFileLocs)) { if (File.Exists(imgPath)) { using (Bitmap imageT = new Bitmap(imgPath)) { ///getting commments currently only working for JPG ///TODO: get this to work for any image type or ///find another way to check if the cached image covers the boundary. string imgComment = imageT.GetCommentsFromJPG(); imageT.Dispose(); ///check to see if tilerange in comments matches current tilerange if (imgComment == (mbSource.Replace(" ", "") + tileRangeString)) { AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, "Using existing image."); continue; } } } } ///Query Mapbox URL ///download all tiles within boundary ///merge tiles into one bitmap ///API to query string mbURLauth = mbURL + mbToken; ///Do the work of assembling image ///setup final image container bitmap int fImageW = ((int)x_range.Length + 1) * 512; int fImageH = ((int)y_range.Length + 1) * 512; Bitmap finalImage = new Bitmap(fImageW, fImageH); int imgPosW = 0; int imgPosH = 0; if (run == true) { using (Graphics g = Graphics.FromImage(finalImage)) { g.Clear(Color.Black); for (int y = (int)y_range.Min; y <= (int)y_range.Max; y++) { for (int x = (int)x_range.Min; x <= (int)x_range.Max; x++) { ///create tileCache name string tileCache = mbSource.Replace(" ", "") + zoom + x + y + ".jpg"; string tileCahceLoc = cacheLoc + tileCache; ///check cache folder to see if tile image exists locally if (File.Exists(tileCahceLoc)) { Bitmap tmpImage = new Bitmap(Image.FromFile(tileCahceLoc)); ///add tmp image to final g.DrawImage(tmpImage, imgPosW * 512, imgPosH * 512); tmpImage.Dispose(); } else { tileList.Add(new List <int> { zoom, y, x }); string urlAuth = Convert.GetZoomURL(x, y, zoom, mbURLauth); System.Net.WebClient client = new System.Net.WebClient(); client.DownloadFile(urlAuth, tileCahceLoc); Bitmap tmpImage = new Bitmap(Image.FromFile(tileCahceLoc)); client.Dispose(); ///add tmp image to final g.DrawImage(tmpImage, imgPosW * 512, imgPosH * 512); tmpImage.Dispose(); tileDownloadedCount = tileDownloadedCount + 1; } ///increment x insert position, goes left to right imgPosW++; } ///increment y insert position, goes top to bottom imgPosH++; imgPosW = 0; } ///garbage collection g.Dispose(); ///add tile range meta data to image comments finalImage.AddCommentsToJPG(mbSource.Replace(" ", "") + tileRangeString); ///save the image finalImage.Save(imgPath, System.Drawing.Imaging.ImageFormat.Jpeg); } } //garbage collection finalImage.Dispose(); //add to tile count total tCount.Insert(new GH_String(tileTotalCount + " tiles (" + tileDownloadedCount + " downloaded / " + (tileTotalCount - tileDownloadedCount) + " cached)"), path, 0); //write out new tile range metadata for serialization TileCacheMeta = tileRangeString; //AddPreviewItem(imgPath, boundary[i], rect); } DA.SetDataTree(0, mapList); DA.SetDataTree(1, imgFrame); DA.SetDataTree(2, tCount); DA.SetDataList(3, "copyright Mapbox"); }
/// <summary> /// This is the method that actually does the work. /// </summary> /// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param> protected override void SolveInstance(IGH_DataAccess DA) { List <Curve> boundary = new List <Curve>(); DA.GetDataList(0, boundary); int zoom = -1; DA.GetData(1, ref zoom); string filePath = string.Empty; DA.GetData(2, ref filePath); if (!filePath.EndsWith(@"\")) { filePath = filePath + @"\"; } string prefix = string.Empty; DA.GetData(3, ref prefix); if (prefix == string.Empty) { prefix = slippySource; } string URL = slippyURL; string userAgent = string.Empty; DA.GetData(4, ref userAgent); bool run = false; DA.GetData <bool>("Run", ref run); GH_Structure <GH_String> mapList = new GH_Structure <GH_String>(); GH_Structure <GH_Rectangle> imgFrame = new GH_Structure <GH_Rectangle>(); GH_Structure <GH_String> tCount = new GH_Structure <GH_String>(); for (int i = 0; i < boundary.Count; i++) { GH_Path path = new GH_Path(i); int tileTotalCount = 0; int tileDownloadedCount = 0; ///Get image frame for given boundary and make sure it's valid if (!boundary[i].GetBoundingBox(true).IsValid) { AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Boundary is not valid."); return; } BoundingBox boundaryBox = boundary[i].GetBoundingBox(true); ///TODO: look into scaling boundary to get buffer tiles ///file path for final image string imgPath = filePath + prefix + "_" + i + ".jpg"; if (!tilesOut) { //location of final image file mapList.Append(new GH_String(imgPath), path); } ///create cache folder for images string cacheLoc = filePath + @"HeronCache\"; List <string> cacheFilePaths = new List <string>(); if (!Directory.Exists(cacheLoc)) { Directory.CreateDirectory(cacheLoc); } ///tile bounding box array List <Point3d> boxPtList = new List <Point3d>(); ///get the tile coordinates for all tiles within boundary var ranges = Convert.GetTileRange(boundaryBox, zoom); List <List <int> > tileList = new List <List <int> >(); var x_range = ranges.XRange; var y_range = ranges.YRange; if (x_range.Length > 100 || y_range.Length > 100) { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "This tile range is too big (more than 100 tiles in the x or y direction). Check your units."); return; } List <Rectangle3d> tileRectangles = new List <Rectangle3d>(); ///cycle through tiles to get bounding box for (int y = (int)y_range.Min; y <= y_range.Max; y++) { for (int x = (int)x_range.Min; x <= x_range.Max; x++) { ///add bounding box of tile to list List <Point3d> boxPts = Convert.GetTileAsPolygon(zoom, y, x).ToList(); boxPtList.AddRange(Convert.GetTileAsPolygon(zoom, y, x).ToList()); string cacheFilePath = cacheLoc + slippySource.Replace(" ", "") + zoom + "-" + x + "-" + y + ".jpg"; cacheFilePaths.Add(cacheFilePath); tileTotalCount = tileTotalCount + 1; if (tilesOut) { mapList.Append(new GH_String(cacheFilePath), path); Rectangle3d tileRectangle = BBoxToRect(new BoundingBox(boxPts)); tileRectangles.Add(tileRectangle); imgFrame.Append(new GH_Rectangle(tileRectangle), path); } } } tCount.Insert(new GH_String(tileTotalCount + " tiles (" + tileDownloadedCount + " downloaded / " + (tileTotalCount - tileDownloadedCount) + " cached)"), path, 0); ///bounding box of tile boundaries BoundingBox bbox = new BoundingBox(boxPtList); var rect = BBoxToRect(bbox); if (!tilesOut) { imgFrame.Append(new GH_Rectangle(rect), path); } //AddPreviewItem(imgPath, boundary[i], rect); ///tile range as string for (de)serialization of TileCacheMeta string tileRangeString = zoom.ToString() + x_range[0].ToString() + y_range[0].ToString() + x_range[1].ToString() + y_range[1].ToString(); ///check if the existing final image already covers the boundary. ///if so, no need to download more or reassemble the cached tiles. if ((TileCacheMeta == tileRangeString) && Convert.CheckCacheImagesExist(cacheFilePaths)) { if (File.Exists(imgPath) && !tilesOut) { using (Bitmap imageT = new Bitmap(imgPath)) { ///getting commments currently only working for JPG ///TODO: get this to work for any image type or ///find another way to check if the cached image covers the boundary. string imgComment = imageT.GetCommentsFromJPG(); imageT.Dispose(); ///check to see if tilerange in comments matches current tilerange if (imgComment == (slippySource.Replace(" ", "") + tileRangeString)) { AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, "Using existing image."); AddPreviewItem(imgPath, boundary[i], rect); continue; } } } if (tilesOut) { for (int t = 0; t < cacheFilePaths.Count; t++) { if (File.Exists(cacheFilePaths[t])) { AddPreviewItem(cacheFilePaths[t], tileRectangles[t].ToNurbsCurve(), tileRectangles[t]); } } continue; } } ///Query Slippy URL ///download all tiles within boundary ///merge tiles into one bitmap ///API to query ///Do the work of assembling image ///setup final image container bitmap int fImageW = ((int)x_range.Length + 1) * 256; int fImageH = ((int)y_range.Length + 1) * 256; Bitmap finalImage = new Bitmap(fImageW, fImageH); int imgPosW = 0; int imgPosH = 0; using (Graphics g = Graphics.FromImage(finalImage)) { g.Clear(Color.Black); for (int y = (int)y_range.Min; y <= (int)y_range.Max; y++) { for (int x = (int)x_range.Min; x <= (int)x_range.Max; x++) { //create tileCache name string tileCache = slippySource.Replace(" ", "") + zoom + "-" + x + "-" + y + ".jpg"; string tileCacheLoc = cacheLoc + tileCache; //check cache folder to see if tile image exists locally if (File.Exists(tileCacheLoc)) { Bitmap tmpImage = new Bitmap(Image.FromFile(tileCacheLoc)); ///add tmp image to final g.DrawImage(tmpImage, imgPosW * 256, imgPosH * 256); tmpImage.Dispose(); } else { tileList.Add(new List <int> { zoom, y, x }); string urlAuth = Convert.GetZoomURL(x, y, zoom, slippyURL); Bitmap tmpImage = new Bitmap(256, 256); System.Net.WebClient client = new System.Net.WebClient(); ///insert header if required client.Headers.Add("user-agent", userAgent); if (run == true) { try { client.DownloadFile(urlAuth, tileCacheLoc); tmpImage = new Bitmap(Image.FromFile(tileCacheLoc)); } catch (WebException e) { using (Graphics tmp = Graphics.FromImage(tmpImage)) { tmp.Clear(Color.White); } AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, e.Message); } } client.Dispose(); //add tmp image to final g.DrawImage(tmpImage, imgPosW * 256, imgPosH * 256); tmpImage.Dispose(); tileDownloadedCount = tileDownloadedCount + 1; } //increment x insert position, goes left to right imgPosW++; } //increment y insert position, goes top to bottom imgPosH++; imgPosW = 0; } //garbage collection g.Dispose(); //add tile range meta data to image comments finalImage.AddCommentsToJPG(slippySource.Replace(" ", "") + tileRangeString); //save the image finalImage.Save(imgPath, System.Drawing.Imaging.ImageFormat.Jpeg); } //garbage collection finalImage.Dispose(); if (!tilesOut) { AddPreviewItem(imgPath, boundary[i], rect); } else { for (int t = 0; t < cacheFilePaths.Count; t++) { if (File.Exists(cacheFilePaths[t])) { AddPreviewItem(cacheFilePaths[t], tileRectangles[t].ToNurbsCurve(), tileRectangles[t]); } } } //add to tile count total tCount.Insert(new GH_String(tileTotalCount + " tiles (" + tileDownloadedCount + " downloaded / " + (tileTotalCount - tileDownloadedCount) + " cached)"), path, 0); //write out new tile range metadata for serialization TileCacheMeta = tileRangeString; } DA.SetDataTree(0, mapList); DA.SetDataTree(1, imgFrame); DA.SetDataTree(2, tCount); ///Add copyright info here DA.SetDataList(3, ""); }
/// <summary> /// This is the method that actually does the work. /// </summary> /// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param> protected override void SolveInstance(IGH_DataAccess DA) { List <Curve> boundary = new List <Curve>(); DA.GetDataList <Curve>(0, boundary); int zoom = -1; DA.GetData <int>(1, ref zoom); string folderPath = string.Empty; DA.GetData <string>(2, ref folderPath); if (!folderPath.EndsWith(@"\")) { folderPath = folderPath + @"\"; } string prefix = string.Empty; DA.GetData <string>(3, ref prefix); if (prefix == "") { prefix = mbSource; } string URL = mbURL; ///get a valid mapbox token to send along with query string mbToken = string.Empty; DA.GetData <string>(4, ref mbToken); if (mbToken == "") { string hmbToken = System.Environment.GetEnvironmentVariable("HERONMAPBOXTOKEN"); if (hmbToken != null) { mbToken = hmbToken; AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, "Using Mapbox token stored in Environment Variable HERONMAPBOXTOKEN."); } else { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "No Mapbox token is specified. Please get a valid token from mapbox.com"); return; } } bool run = false; DA.GetData <bool>("Run", ref run); GH_Structure <GH_String> mapList = new GH_Structure <GH_String>(); GH_Structure <GH_Rectangle> imgFrame = new GH_Structure <GH_Rectangle>(); GH_Structure <GH_String> tCount = new GH_Structure <GH_String>(); /* * ///Save for later development of warping * ///Setup for warping * RESTful.GdalConfiguration.ConfigureGdal(); * RESTful.GdalConfiguration.ConfigureOgr(); * * OSGeo.OSR.SpatialReference userSRS = new OSGeo.OSR.SpatialReference(""); * userSRS.SetFromUserInput("WGS84"); * * OSGeo.OSR.SpatialReference mapboxSRS = new OSGeo.OSR.SpatialReference(""); * mapboxSRS.SetFromUserInput("EPSG:3857"); * * ///Set transform from input spatial reference to Rhino spatial reference * OSGeo.OSR.SpatialReference rhinoSRS = new OSGeo.OSR.SpatialReference(""); * rhinoSRS.SetWellKnownGeogCS("WGS84"); * * ///This transform moves and scales the points required in going from userSRS to XYZ and vice versa * //Transform userSRSToModelTransform = Heron.Convert.GetUserSRSToModelTransform(userSRS); * Transform modelToUserSRSTransform = Heron.Convert.GetModelToUserSRSTransform(userSRS); * Transform mapboxSRSToModelTransform = Heron.Convert.GetUserSRSToModelTransform(mapboxSRS); * Transform modelToMapboxSRSTransform = Heron.Convert.GetModelToUserSRSTransform(mapboxSRS); */ for (int i = 0; i < boundary.Count; i++) { GH_Path path = new GH_Path(i); int tileTotalCount = 0; int tileDownloadedCount = 0; //Get image frame for given boundary and make sure it's valid if (!boundary[i].GetBoundingBox(true).IsValid) { AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Boundary is not valid."); return; } BoundingBox boundaryBox = boundary[i].GetBoundingBox(true); ///TODO: look into scaling boundary to get buffer tiles ///file path for final image string imgPath = folderPath + prefix + "_" + i + ".jpg"; if (!tilesOut) { //location of final image file mapList.Append(new GH_String(imgPath), path); } //create cache folder for images string cacheLoc = folderPath + @"HeronCache\"; List <string> cacheFilePaths = new List <string>(); if (!Directory.Exists(cacheLoc)) { Directory.CreateDirectory(cacheLoc); } //tile bounding box array List <Point3d> boxPtList = new List <Point3d>(); //get the tile coordinates for all tiles within boundary var ranges = Convert.GetTileRange(boundaryBox, zoom); List <List <int> > tileList = new List <List <int> >(); var x_range = ranges.XRange; var y_range = ranges.YRange; if (x_range.Length > 100 || y_range.Length > 100) { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "This tile range is too big (more than 100 tiles in the x or y direction). Check your units."); return; } List <Rectangle3d> tileRectangles = new List <Rectangle3d>(); //cycle through tiles to get bounding box for (int y = (int)y_range.Min; y <= y_range.Max; y++) { for (int x = (int)x_range.Min; x <= x_range.Max; x++) { //add bounding box of tile to list List <Point3d> boxPts = Convert.GetTileAsPolygon(zoom, y, x).ToList(); boxPtList.AddRange(boxPts); string cacheFilePath = cacheLoc + mbSource.Replace(" ", "") + zoom + "-" + x + "-" + y + ".jpg"; cacheFilePaths.Add(cacheFilePath); tileTotalCount = tileTotalCount + 1; if (tilesOut) { mapList.Append(new GH_String(cacheFilePath), path); Rectangle3d tileRectangle = BBoxToRect(new BoundingBox(boxPts)); tileRectangles.Add(tileRectangle); imgFrame.Append(new GH_Rectangle(tileRectangle), path); } } } tCount.Insert(new GH_String(tileTotalCount + " tiles (" + tileDownloadedCount + " downloaded / " + (tileTotalCount - tileDownloadedCount) + " cached)"), path, 0); //bounding box of tile boundaries BoundingBox bbox = new BoundingBox(boxPtList); var rect = BBoxToRect(bbox); if (!tilesOut) { imgFrame.Append(new GH_Rectangle(rect), path); } ///tile range as string for (de)serialization of TileCacheMeta string tileRangeString = zoom.ToString() + x_range[0].ToString() + y_range[0].ToString() + x_range[1].ToString() + y_range[1].ToString(); ///check if the existing final image already covers the boundary. ///if so, no need to download more or reassemble the cached tiles. if ((TileCacheMeta == tileRangeString) && Convert.CheckCacheImagesExist(cacheFilePaths)) { if (File.Exists(imgPath) && !tilesOut) { using (Bitmap imageT = new Bitmap(imgPath)) { ///getting commments currently only working for JPG ///TODO: get this to work for any image type or ///find another way to check if the cached image covers the boundary. string imgComment = string.Empty; ///Save for later development of warping //if (!warped) imgComment = imageT.GetCommentsFromJPG(); imageT.Dispose(); ///check to see if tilerange in comments matches current tilerange if (imgComment == (mbSource.Replace(" ", "") + tileRangeString)) { AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, "Using existing image."); AddPreviewItem(imgPath, boundary[i], rect); continue; } } } if (tilesOut) { for (int t = 0; t < cacheFilePaths.Count; t++) { if (File.Exists(cacheFilePaths[t])) { AddPreviewItem(cacheFilePaths[t], tileRectangles[t].ToNurbsCurve(), tileRectangles[t]); } } continue; } } ///Query Mapbox URL ///download all tiles within boundary ///merge tiles into one bitmap ///API to query string mbURLauth = mbURL + mbToken; ///Do the work of assembling image ///setup final image container bitmap int fImageW = ((int)x_range.Length + 1) * 512; int fImageH = ((int)y_range.Length + 1) * 512; Bitmap finalImage = new Bitmap(fImageW, fImageH); int imgPosW = 0; int imgPosH = 0; /* * ///Save for later development of warping * List<GCP> gcpList = new List<GCP>(); */ using (Graphics g = Graphics.FromImage(finalImage)) { g.Clear(Color.Black); for (int y = (int)y_range.Min; y <= (int)y_range.Max; y++) { for (int x = (int)x_range.Min; x <= (int)x_range.Max; x++) { ///create tileCache name string tileCache = mbSource.Replace(" ", "") + zoom + "-" + x + "-" + y + ".jpg"; string tileCacheLoc = cacheLoc + tileCache; /* * ///Save for later development of warping * ///Get GCPs for warping * Point3d tileCorner = Heron.Convert.GetTileAsPolygon(zoom, y, x)[3]; * var tileCorner3857 = Convert.Point3dToOgrPoint(tileCorner, modelToMapboxSRSTransform); * GCP gcp = new GCP(tileCorner3857.GetX(0), tileCorner3857.GetY(0), tileCorner3857.GetZ(0), imgPosW * 512, imgPosH * 512, tileCorner3857.ToString(), zoom + x + y + ""); * gcpList.Add(gcp); */ ///check cache folder to see if tile image exists locally if (File.Exists(tileCacheLoc)) { Bitmap tmpImage = new Bitmap(Image.FromFile(tileCacheLoc)); ///add tmp image to final g.DrawImage(tmpImage, imgPosW * 512, imgPosH * 512); tmpImage.Dispose(); } else { tileList.Add(new List <int> { zoom, y, x }); string urlAuth = Convert.GetZoomURL(x, y, zoom, mbURLauth); Bitmap tmpImage = new Bitmap(512, 512); System.Net.WebClient client = new System.Net.WebClient(); if (run == true) { try { client.DownloadFile(urlAuth, tileCacheLoc); tmpImage = new Bitmap(Image.FromFile(tileCacheLoc)); } catch (WebException e) { using (Graphics tmp = Graphics.FromImage(tmpImage)) { tmp.Clear(Color.White); } AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, e.Message); } } client.Dispose(); ///add tmp image to final g.DrawImage(tmpImage, imgPosW * 512, imgPosH * 512); tmpImage.Dispose(); tileDownloadedCount = tileDownloadedCount + 1; } ///increment x insert position, goes left to right imgPosW++; } ///increment y insert position, goes top to bottom imgPosH++; imgPosW = 0; } ///garbage collection g.Dispose(); ///add tile range meta data to image comments finalImage.AddCommentsToJPG(mbSource.Replace(" ", "") + tileRangeString); ///Save for later development of warping ///if (!warped) ///save the image finalImage.Save(imgPath, ImageFormat.Jpeg); } /* * ///Save for later development of warping * byte[] imageBuffer; * string memFilename = "/vsimem/inmemfile"; * string memTranslated = "/vsimem/inmemfileTranslated"; * string memWarped = "/vsimem/inmemfileWarped"; * using (MemoryStream stream = new MemoryStream()) * { * finalImage.Save(stream, ImageFormat.Jpeg); * imageBuffer = stream.ToArray(); * } */ //garbage collection finalImage.Dispose(); /* * ///Save for later development of warping * Gdal.FileFromMemBuffer(memFilename, imageBuffer); * Dataset gdalImage = Gdal.Open(memFilename, Access.GA_ReadOnly); * var upperLeft3857 = Convert.Point3dToOgrPoint(rect.Corner(3), modelToMapboxSRSTransform); * var lowerRight3857 = Convert.Point3dToOgrPoint(rect.Corner(1), modelToMapboxSRSTransform); * var upperLeft4326 = Convert.Point3dToOgrPoint(rect.Corner(3), modelToUserSRSTransform); * var lowerRight4326 = Convert.Point3dToOgrPoint(rect.Corner(1), modelToUserSRSTransform); * List<string> translateOptions = new List<string> { "-a_srs", "EPSG:3857", * "-r", "bilinear", * "-a_ullr", upperLeft3857.GetX(0).ToString(), upperLeft3857.GetY(0).ToString(), lowerRight3857.GetX(0).ToString(), lowerRight3857.GetY(0).ToString() }; * Dataset gdalTranslated = Gdal.wrapper_GDALTranslate(memTranslated, gdalImage, new GDALTranslateOptions(translateOptions.ToArray()), null, null); * * var wkt = gdalTranslated.GetProjection(); * //gdalTranslated.SetGCPs(gcpList.ToArray(), wkt); * * List<string> warpOptions = new List<string> { "-t_srs", "EPSG:4326", * "-r", "bilinear", * //"-multi", * //"-wo", "NUM_THREADS=6", * "-overwrite", * //"-order", "1", * //"-tps", * "-te_srs", "EPSG:3857", * "-te", upperLeft3857.GetX(0).ToString(), lowerRight3857.GetY(0).ToString(), lowerRight3857.GetX(0).ToString(), upperLeft3857.GetY(0).ToString() }; * * ///https://github.com/OSGeo/gdal/issues/813 * ///https://lists.osgeo.org/pipermail/gdal-dev/2017-February/046046.html * ///Odd way to go about setting source dataset in parameters for Warp is a known issue * * var ptr = new[] { Dataset.getCPtr(gdalTranslated).Handle }; * var gcHandle = GCHandle.Alloc(ptr, GCHandleType.Pinned); * try * { * var dss = new SWIGTYPE_p_p_GDALDatasetShadow(gcHandle.AddrOfPinnedObject(), false, null); * Dataset gdalWarped = Gdal.wrapper_GDALWarpDestName(memWarped, 1, dss, new GDALWarpAppOptions(warpOptions.ToArray()), null, null); * var driver = Gdal.GetDriverByName("JPEG"); * List<string> copyOptions = new List<string> { "QUALITY=95", "COMMENT=" + mbSource.Replace(" ", "") + tileRangeString}; * var copy = driver.CreateCopy(imgPath, gdalWarped, 0, copyOptions.ToArray(), null, null); * copy.Dispose(); * driver.Dispose(); * gdalWarped.Dispose(); * } * finally * { * if (gcHandle.IsAllocated) * gcHandle.Free(); * } * * gdalImage.Dispose(); * gdalTranslated.Dispose(); * Gdal.Unlink(memFilename); * Gdal.Unlink(memTranslated); * Gdal.Unlink(memWarped); */ if (!tilesOut) { AddPreviewItem(imgPath, boundary[i], rect); } else { for (int t = 0; t < cacheFilePaths.Count; t++) { if (File.Exists(cacheFilePaths[t])) { AddPreviewItem(cacheFilePaths[t], tileRectangles[t].ToNurbsCurve(), tileRectangles[t]); } } } //add to tile count total tCount.Insert(new GH_String(tileTotalCount + " tiles (" + tileDownloadedCount + " downloaded / " + (tileTotalCount - tileDownloadedCount) + " cached)"), path, 0); //write out new tile range metadata for serialization TileCacheMeta = tileRangeString; } List <string> mbAtts = new List <string> { "© Mapbox, © OpenStreetMap", "https://www.mapbox.com/about/maps/", "http://www.openstreetmap.org/copyright" }; DA.SetDataTree(0, mapList); DA.SetDataTree(1, imgFrame); DA.SetDataTree(2, tCount); DA.SetDataList(3, mbAtts); }
/// <summary> /// This is the method that actually does the work. /// </summary> /// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param> protected override void SolveInstance(IGH_DataAccess DA) { List <Curve> boundary = new List <Curve>(); DA.GetDataList <Curve>(0, boundary); int zoom = -1; DA.GetData <int>(1, ref zoom); string fileloc = ""; DA.GetData <string>(2, ref fileloc); if (!fileloc.EndsWith(@"\")) { fileloc = fileloc + @"\"; } string prefix = ""; DA.GetData <string>(3, ref prefix); string URL = slippyURL; //DA.GetData<string>(4, ref URL); string userAgent = ""; DA.GetData <string>(4, ref userAgent); bool run = false; DA.GetData <bool>("Run", ref run); GH_Structure <GH_String> mapList = new GH_Structure <GH_String>(); GH_Structure <GH_Curve> imgFrame = new GH_Structure <GH_Curve>(); GH_Structure <GH_Integer> tCount = new GH_Structure <GH_Integer>(); for (int i = 0; i < boundary.Count; i++) { GH_Path path = new GH_Path(i); ///Get image frame for given boundary and make sure it's valid if (!boundary[i].GetBoundingBox(true).IsValid) { AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Boundary is not valid."); return; } BoundingBox boundaryBox = boundary[i].GetBoundingBox(true); ///TODO: look into scaling boundary to get buffer tiles ///file path for final image string imgPath = fileloc + prefix + "_" + i + ".jpg"; ///location of final image file mapList.Append(new GH_String(imgPath), path); ///create cache folder for images string cacheLoc = fileloc + @"HeronCache\"; List <string> cacheFileLocs = new List <string>(); if (!Directory.Exists(cacheLoc)) { Directory.CreateDirectory(cacheLoc); } ///tile bounding box array List <Point3d> boxPtList = new List <Point3d>(); ///get the tile coordinates for all tiles within boundary List <List <int> > ranges = new List <List <int> >(); ranges = Convert.GetTileRange(boundaryBox, zoom); List <List <int> > tileList = new List <List <int> >(); List <int> x_range = ranges[0]; List <int> y_range = ranges[1]; ///cycle through tiles to get bounding box for (int y = y_range[0]; y <= y_range[1]; y++) { for (int x = x_range[0]; x <= x_range[1]; x++) { ///add bounding box of tile to list boxPtList.AddRange(Convert.GetTileAsPolygon(zoom, y, x).ToList()); cacheFileLocs.Add(cacheLoc + slippySource.Replace(" ", "") + zoom + x + y + ".png"); } } ///bounding box of tile boundaries BoundingBox bboxPts = new BoundingBox(boxPtList); ///convert bounding box to polyline List <Point3d> imageCorners = bboxPts.GetCorners().ToList(); imageCorners.Add(imageCorners[0]); imgFrame.Append(new GH_Curve(new Rhino.Geometry.Polyline(imageCorners).ToNurbsCurve()), path); ///tile range as string for (de)serialization of TileCacheMeta string tileRangeString = zoom.ToString() + x_range[0].ToString() + y_range[0].ToString() + x_range[1].ToString() + y_range[1].ToString(); ///check if the existing final image already covers the boundary. ///if so, no need to download more or reassemble the cached tiles. if ((TileCacheMeta == tileRangeString) && Convert.CheckCacheImagesExist(cacheFileLocs)) { if (File.Exists(imgPath)) { using (Bitmap imageT = new Bitmap(imgPath)) { ///getting commments currently only working for JPG ///TODO: get this to work for any image type or ///find another way to check if the cached image covers the boundary. string imgComment = imageT.GetCommentsFromJPG(); imageT.Dispose(); ///check to see if tilerange in comments matches current tilerange if (imgComment == (slippySource.Replace(" ", "") + tileRangeString)) { AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, "Using existing image."); continue; } } } } ///Query Slippy URL ///download all tiles within boundary ///merge tiles into one bitmap ///API to query ///Do the work of assembling image ///setup final image container bitmap int fImageW = (x_range[1] - x_range[0] + 1) * 256; int fImageH = (y_range[1] - y_range[0] + 1) * 256; Bitmap finalImage = new Bitmap(fImageW, fImageH); int imgPosW = 0; int imgPosH = 0; if (run == true) { using (Graphics g = Graphics.FromImage(finalImage)) { g.Clear(Color.Black); for (int y = y_range[0]; y <= y_range[1]; y++) { for (int x = x_range[0]; x <= x_range[1]; x++) { //create tileCache name string tileCache = slippySource.Replace(" ", "") + zoom + x + y + ".png"; string tileCahceLoc = cacheLoc + tileCache; //check cache folder to see if tile image exists locally if (File.Exists(tileCahceLoc)) { Bitmap tmpImage = new Bitmap(Image.FromFile(tileCahceLoc)); ///add tmp image to final g.DrawImage(tmpImage, imgPosW * 256, imgPosH * 256); tmpImage.Dispose(); } else { tileList.Add(new List <int> { zoom, y, x }); string urlAuth = Convert.GetZoomURL(x, y, zoom, slippyURL); System.Net.WebClient client = new System.Net.WebClient(); ///insert header if required client.Headers.Add("user-agent", userAgent); client.DownloadFile(urlAuth, tileCahceLoc); Bitmap tmpImage = new Bitmap(Image.FromFile(tileCahceLoc)); client.Dispose(); //add tmp image to final g.DrawImage(tmpImage, imgPosW * 256, imgPosH * 256); tmpImage.Dispose(); } //increment x insert position, goes left to right imgPosW++; } //increment y insert position, goes top to bottom imgPosH++; imgPosW = 0; } //garbage collection g.Dispose(); //add tile range meta data to image comments finalImage.AddCommentsToJPG(slippySource.Replace(" ", "") + tileRangeString); //save the image finalImage.Save(imgPath, System.Drawing.Imaging.ImageFormat.Jpeg); } } //garbage collection finalImage.Dispose(); //add to tile count total tCount.Append(new GH_Integer(tileList.Count), path); //write out new tile range metadata for serialization TileCacheMeta = tileRangeString; } DA.SetDataTree(0, mapList); DA.SetDataTree(1, imgFrame); DA.SetDataTree(2, tCount); DA.SetDataList(3, "copyright Slippy"); }