OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
MapImageService.cs
Go to the documentation of this file.
1 /*
2  * Copyright (c) Contributors, http://opensimulator.org/
3  * See CONTRIBUTORS.TXT for a full list of copyright holders.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * * Redistributions of source code must retain the above copyright
8  * notice, this list of conditions and the following disclaimer.
9  * * Redistributions in binary form must reproduce the above copyright
10  * notice, this list of conditions and the following disclaimer in the
11  * documentation and/or other materials provided with the distribution.
12  * * Neither the name of the OpenSimulator Project nor the
13  * names of its contributors may be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  *
27  * The design of this map service is based on SimianGrid's PHP-based
28  * map service. See this URL for the original PHP version:
29  * https://github.com/openmetaversefoundation/simiangrid/
30  */
31 
32 using System;
33 using System.Collections.Generic;
34 using System.Drawing;
35 using System.Drawing.Imaging;
36 using System.IO;
37 using System.Net;
38 using System.Reflection;
39 using System.Threading;
40 
41 using Nini.Config;
42 using log4net;
43 using OpenMetaverse;
44 
45 using OpenSim.Framework;
46 using OpenSim.Framework.Console;
47 using OpenSim.Services.Interfaces;
48 
49 
50 namespace OpenSim.Services.MapImageService
51 {
53  {
54  private static readonly ILog m_log =
55  LogManager.GetLogger(
56  MethodBase.GetCurrentMethod().DeclaringType);
57 #pragma warning disable 414
58  private string LogHeader = "[MAP IMAGE SERVICE]";
59 #pragma warning restore 414
60 
61  private const int ZOOM_LEVELS = 8;
62  private const int IMAGE_WIDTH = 256;
63  private const int HALF_WIDTH = 128;
64  private const int JPEG_QUALITY = 80;
65 
66  private static string m_TilesStoragePath = "maptiles";
67 
68  private static object m_Sync = new object();
69  private static bool m_Initialized = false;
70  private static string m_WaterTileFile = string.Empty;
71  private static Color m_Watercolor = Color.FromArgb(29, 71, 95);
72  private static Bitmap m_WaterBitmap = null;
73  private static byte[] m_WaterBytes = null;
74 
75  public MapImageService(IConfigSource config)
76  {
77  if (!m_Initialized)
78  {
79  m_Initialized = true;
80  m_log.Debug("[MAP IMAGE SERVICE]: Starting MapImage service");
81 
82  IConfig serviceConfig = config.Configs["MapImageService"];
83  if (serviceConfig != null)
84  {
85  m_TilesStoragePath = serviceConfig.GetString("TilesStoragePath", m_TilesStoragePath);
86  if (!Directory.Exists(m_TilesStoragePath))
87  Directory.CreateDirectory(m_TilesStoragePath);
88 
89 
90  m_WaterTileFile = Path.Combine(m_TilesStoragePath, "water.jpg");
91  if (!File.Exists(m_WaterTileFile))
92  {
93  Bitmap waterTile = new Bitmap(IMAGE_WIDTH, IMAGE_WIDTH);
94  FillImage(waterTile, m_Watercolor);
95  waterTile.Save(m_WaterTileFile, ImageFormat.Jpeg);
96  m_WaterBitmap = waterTile;
97  }
98 
99  if (File.Exists(m_WaterTileFile))
100  {
101  m_WaterBitmap = new Bitmap(m_WaterTileFile);
102  using (MemoryStream ms = new MemoryStream())
103  {
104  m_WaterBitmap.Save(ms,ImageFormat.Jpeg);
105  ms.Seek(0, SeekOrigin.Begin);
106  m_WaterBytes = ms.ToArray();
107  }
108  }
109  }
110  }
111  }
112 
113  #region IMapImageService
114 
115  public bool AddMapTile(int x, int y, byte[] imageData, UUID scopeID, out string reason)
116  {
117  reason = string.Empty;
118  string fileName = GetFileName(1, x, y, scopeID);
119 
120  lock (m_Sync)
121  {
122  try
123  {
124  using (FileStream f = File.Open(fileName, FileMode.OpenOrCreate, FileAccess.Write))
125  f.Write(imageData, 0, imageData.Length);
126  }
127  catch (Exception e)
128  {
129  m_log.WarnFormat("[MAP IMAGE SERVICE]: Unable to save image file {0}: {1}", fileName, e);
130  reason = e.Message;
131  return false;
132  }
133  }
134 
135  return UpdateMultiResolutionFiles(x, y, scopeID, out reason);
136  }
137 
138  public bool RemoveMapTile(int x, int y, UUID scopeID, out string reason)
139  {
140  reason = String.Empty;
141  string fileName = GetFileName(1, x, y, scopeID);
142 
143  lock (m_Sync)
144  {
145  try
146  {
147  File.Delete(fileName);
148  }
149  catch (Exception e)
150  {
151 
152  m_log.WarnFormat("[MAP IMAGE SERVICE]: Unable to save delete file {0}: {1}", fileName, e);
153  reason = e.Message;
154  return false;
155  }
156  }
157  return UpdateMultiResolutionFiles(x, y, scopeID, out reason);
158  }
159 
160 
161  // When large varregions start up, they can send piles of new map tiles. This causes
162  // this multi-resolution routine to be called a zillion times an causes much CPU
163  // time to be spent creating multi-resolution tiles that will be replaced when
164  // the next maptile arrives.
165  private class mapToMultiRez
166  {
167  public int xx;
168  public int yy;
169  public UUID scopeID;
170  public mapToMultiRez(int pX, int pY, UUID pscopeID)
171  {
172  xx = pX;
173  yy = pY;
174  scopeID = pscopeID;
175  }
176  };
177  private Queue<mapToMultiRez> multiRezToBuild = new Queue<mapToMultiRez>();
178 
179  private bool UpdateMultiResolutionFiles(int x, int y, UUID scopeID, out string reason)
180  {
181  reason = String.Empty;
182 
183  lock (multiRezToBuild)
184  {
185  // m_log.DebugFormat("{0} UpdateMultiResolutionFilesAsync: scheduling update for <{1},{2}>", LogHeader, x, y);
186  multiRezToBuild.Enqueue(new mapToMultiRez(x, y, scopeID));
187  if (multiRezToBuild.Count == 1)
188  Util.FireAndForget(
189  DoUpdateMultiResolutionFilesAsync);
190  }
191 
192  return true;
193  }
194 
195  private void DoUpdateMultiResolutionFilesAsync(object o)
196  {
197  // let acumulate large region tiles
198  Thread.Sleep(60 * 1000); // large regions take time to upload tiles
199 
200  while (multiRezToBuild.Count > 0)
201  {
202  mapToMultiRez toMultiRez = null;
203  lock (multiRezToBuild)
204  {
205  if (multiRezToBuild.Count > 0)
206  toMultiRez = multiRezToBuild.Dequeue();
207  }
208  if (toMultiRez != null)
209  {
210  int x = toMultiRez.xx;
211  int y = toMultiRez.yy;
212  UUID scopeID = toMultiRez.scopeID;
213  // m_log.DebugFormat("{0} DoUpdateMultiResolutionFilesAsync: doing build for <{1},{2}>", LogHeader, x, y);
214 
215  int width = 1;
216 
217  // Stitch seven more aggregate tiles together
218  for (uint zoomLevel = 2; zoomLevel <= ZOOM_LEVELS; zoomLevel++)
219  {
220  // Calculate the width (in full resolution tiles) and bottom-left
221  // corner of the current zoom level
222  width *= 2;
223  int x1 = x - (x % width);
224  int y1 = y - (y % width);
225 
226  lock (m_Sync) // must lock the reading and writing of the maptile files
227  {
228  if (!CreateTile(zoomLevel, x1, y1, scopeID))
229  {
230  m_log.WarnFormat("[MAP IMAGE SERVICE]: Unable to create tile for {0},{1} at zoom level {1}", x, y, zoomLevel);
231  return;
232  }
233  }
234  }
235  }
236  }
237  return;
238  }
239 
240  public byte[] GetMapTile(string fileName, UUID scopeID, out string format)
241  {
242 // m_log.DebugFormat("[MAP IMAGE SERVICE]: Getting map tile {0}", fileName);
243 
244  format = ".jpg";
245  string fullName = Path.Combine(m_TilesStoragePath, scopeID.ToString());
246  fullName = Path.Combine(fullName, fileName);
247  if (File.Exists(fullName))
248  {
249  format = Path.GetExtension(fileName).ToLower();
250  //m_log.DebugFormat("[MAP IMAGE SERVICE]: Found file {0}, extension {1}", fileName, format);
251  return File.ReadAllBytes(fullName);
252  }
253  else if (m_WaterBytes != null)
254  {
255  return (byte[])m_WaterBytes.Clone();
256  }
257  else
258  {
259  m_log.DebugFormat("[MAP IMAGE SERVICE]: unable to get file {0}", fileName);
260  return new byte[0];
261  }
262  }
263 
264  #endregion
265 
266 
267  private string GetFileName(uint zoomLevel, int x, int y, UUID scopeID)
268  {
269  string extension = "jpg";
270  string path = Path.Combine(m_TilesStoragePath, scopeID.ToString());
271  Directory.CreateDirectory(path);
272  return Path.Combine(path, string.Format("map-{0}-{1}-{2}-objects.{3}", zoomLevel, x, y, extension));
273  }
274 
275  private Bitmap GetInputTileImage(string fileName)
276  {
277  try
278  {
279  if (File.Exists(fileName))
280  return new Bitmap(fileName);
281  }
282  catch (Exception e)
283  {
284  m_log.WarnFormat("[MAP IMAGE SERVICE]: Unable to read image data from {0}: {1}", fileName, e);
285  }
286 
287  return null;
288  }
289 
290  private Bitmap GetOutputTileImage(string fileName)
291  {
292  try
293  {
294  if (File.Exists(fileName))
295  return new Bitmap(fileName);
296 
297  else
298  {
299  // Create a new output tile with a transparent background
300  Bitmap bm = new Bitmap(IMAGE_WIDTH, IMAGE_WIDTH, PixelFormat.Format24bppRgb);
301  //bm.MakeTransparent(); // 24bpp does not have transparency, this would make it 32bpp
302  return bm;
303  }
304  }
305  catch (Exception e)
306  {
307  m_log.WarnFormat("[MAP IMAGE SERVICE]: Unable to read image data from {0}: {1}", fileName, e);
308  }
309 
310  return null;
311  }
312 
313  private bool CreateTile(uint zoomLevel, int x, int y, UUID scopeID)
314  {
315 // m_log.DebugFormat("[MAP IMAGE SERVICE]: Create tile for {0} {1}, zoom {2}", x, y, zoomLevel);
316  int prevWidth = (int)Math.Pow(2, (double)zoomLevel - 2);
317  int thisWidth = (int)Math.Pow(2, (double)zoomLevel - 1);
318 
319  // Convert x and y to the bottom left tile for this zoom level
320  int xIn = x - (x % prevWidth);
321  int yIn = y - (y % prevWidth);
322 
323  // Convert x and y to the bottom left tile for the next zoom level
324  int xOut = x - (x % thisWidth);
325  int yOut = y - (y % thisWidth);
326 
327  // Try to open the four input tiles from the previous zoom level
328  Bitmap inputBL = GetInputTileImage(GetFileName(zoomLevel - 1, xIn, yIn, scopeID));
329  Bitmap inputBR = GetInputTileImage(GetFileName(zoomLevel - 1, xIn + prevWidth, yIn, scopeID));
330  Bitmap inputTL = GetInputTileImage(GetFileName(zoomLevel - 1, xIn, yIn + prevWidth, scopeID));
331  Bitmap inputTR = GetInputTileImage(GetFileName(zoomLevel - 1, xIn + prevWidth, yIn + prevWidth, scopeID));
332 
333  // Open the output tile (current zoom level)
334  string outputFile = GetFileName(zoomLevel, xOut, yOut, scopeID);
335 
336  int ntiles = 0;
337  Bitmap output = (Bitmap)m_WaterBitmap.Clone();
338 
339  if (inputBL != null)
340  {
341  ImageCopyResampled(output, inputBL, 0, HALF_WIDTH, 0, 0);
342  inputBL.Dispose();
343  ntiles++;
344  }
345  if (inputBR != null)
346  {
347  ImageCopyResampled(output, inputBR, HALF_WIDTH, HALF_WIDTH, 0, 0);
348  inputBR.Dispose();
349  ntiles++;
350  }
351  if (inputTL != null)
352  {
353  ImageCopyResampled(output, inputTL, 0, 0, 0, 0);
354  inputTL.Dispose();
355  ntiles++;
356  }
357  if (inputTR != null)
358  {
359  ImageCopyResampled(output, inputTR, HALF_WIDTH, 0, 0, 0);
360  inputTR.Dispose();
361  ntiles++;
362  }
363 
364  // Write the modified output
365  if (ntiles == 0)
366  File.Delete(outputFile);
367 
368  else
369  {
370 
371  try
372  {
373  output.Save(outputFile, ImageFormat.Jpeg);
374  }
375  catch (Exception e)
376  {
377  m_log.WarnFormat("[MAP IMAGE SERVICE]: Oops on saving {0} {1}", outputFile, e);
378  }
379  } // Save also as png?
380 
381  output.Dispose();
382  return true;
383  }
384 
385  #region Image utilities
386 
387  private void FillImage(Bitmap bm, Color c)
388  {
389  for (int x = 0; x < bm.Width; x++)
390  for (int y = 0; y < bm.Height; y++)
391  bm.SetPixel(x, y, c);
392  }
393 
394  private void ImageCopyResampled(Bitmap output, Bitmap input, int destX, int destY, int srcX, int srcY)
395  {
396  int resamplingRateX = 2; // (input.Width - srcX) / (output.Width - destX);
397  int resamplingRateY = 2; // (input.Height - srcY) / (output.Height - destY);
398 
399  for (int x = destX; x < destX + HALF_WIDTH; x++)
400  for (int y = destY; y < destY + HALF_WIDTH; y++)
401  {
402  Color p = input.GetPixel(srcX + (x - destX) * resamplingRateX, srcY + (y - destY) * resamplingRateY);
403  output.SetPixel(x, y, p);
404  }
405  }
406 
407  #endregion
408  }
409 }
bool AddMapTile(int x, int y, byte[] imageData, UUID scopeID, out string reason)
Interactive OpenSim region server
Definition: OpenSim.cs:55
bool RemoveMapTile(int x, int y, UUID scopeID, out string reason)
byte[] GetMapTile(string fileName, UUID scopeID, out string format)