001 /*
002 Copyright (c) 1996-2012, Damon Hart-Davis
003 All rights reserved.
004
005 Redistribution and use in source and binary forms, with or without
006 modification, are permitted provided that the following conditions are
007 met:
008
009 * Redistributions of source code must retain the above copyright
010 notice, this list of conditions and the following disclaimer.
011
012 * Redistributions in binary form must reproduce the above copyright
013 notice, this list of conditions and the following disclaimer in the
014 documentation and/or other materials provided with the
015 distribution.
016
017 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028 */
029
030 package org.hd.d.pg2k.webSvr.location;
031
032 import java.awt.Dimension;
033 import java.awt.image.BufferedImage;
034 import java.io.IOException;
035 import java.lang.ref.SoftReference;
036
037 import javax.servlet.ServletContext;
038 import javax.servlet.http.HttpServletRequest;
039
040 import org.hd.d.pg2k.svrCore.ImageUtils;
041 import org.hd.d.pg2k.svrCore.MIME.ExhibitMIME;
042 import org.hd.d.pg2k.svrCore.location.Location;
043 import org.hd.d.pg2k.webSvr.util.WebUtils;
044
045 import ORG.hd.d.IsDebug;
046
047 /**
048 * Created by IntelliJ IDEA.
049 * User: Damon Hart-Davis
050 * Date: 01-Sep-2003
051 * Time: 18:40:11
052 */
053
054 /**Utilities to support Location display in Web pages.
055 *
056 */
057 public class LocationUtils
058 {
059 /**Width of (raw and modified) Estd thumbnail in pixels as returned by getEstdLocationThumbnailURL(). */
060 public static final int LOC_TN_WIDTH = 32;
061
062 /**Height of (raw and modified) Estd thumbnail in pixels as returned by getEstdLocationThumbnailURL(). */
063 public static final int LOC_TN_HEIGHT = 22;
064
065 /**Type of thumbnail image that we dynamically generate.
066 * Need not be the same as the raw thumbnail image,
067 * as long as it can (efficiently) encode the modified thumbnail
068 * in a byte-indexed format.
069 * <p>
070 * Good types to use are PNG and GIF (when the LZW patent expires).
071 */
072 static final int DYN_LOC_TN_IMG_TYPE = ExhibitMIME.ET_PNG;
073
074 /**ExhibitMIME entry for the chosen output type. */
075 static final ExhibitMIME.ExhibitTypeParameters ETP =
076 ExhibitMIME.getParamsByType(DYN_LOC_TN_IMG_TYPE);
077
078 /**Colour of cross-hairs to show location of item in RGB format.
079 * Since the icon is usually rendered in green (land) and blue (sea)
080 * this should probably be red or some other high-contrast colour.
081 * This should probably also be fully opaque to minimise encoded image size
082 * (eg 0xffff0000 for opaque red).
083 * <p>
084 * Make this available to (for example) Aloha Earth,
085 * so that we can use a common colour/style throughout.
086 */
087 public static final int DYN_LOC_TN_CROSSHAIR_RGB_COLOUR = 0xffff0000; // Opaque red.
088
089 /**Basic root-relative URL for base thumbnail icon.
090 * This has to be an image type that we can decode, modify, and encode
091 * on the fly.
092 * <p>
093 * Assumed to be low-colour suitable for an index/palette representation
094 * such as GIF or PNG.
095 * <p>
096 * By default rendered n green (land) and blue (sea).
097 * <p>
098 * ON OE is assumed to be centred in the image,
099 * nominally between pixels for an even-pixel dimension.
100 */
101 static final String BASE_ESTD_LOC_TN_URL = "/_static/icon/Earth.png";
102
103 /**Mount point for servlet, including usual suffix for given image type.
104 * May mount this in the "/_static/" area to help clients cache the
105 * modified thumbnails and thus reducing redundant requests for them.
106 * <p>
107 * This must correspond with where the servlet is actually mounted.
108 * (The servlet can be mounted to serve a tree that includes this.)
109 */
110 private static final String DYN_LOC_TN_MOUNT_POINT =
111 "/_static/locEstdTN/Earth" + ETP.dotSuffixForInputFile;
112
113
114 /**Get cross-hair X,Y pixel centre on thumbnail given Estd location.
115 * Since the dimension values are integral,
116 * this rounds to the nearest correct pixel value.
117 * <p>
118 * The result is constrained to valid pixel coordinates
119 * [0, width[ and [0, height[.
120 */
121 private static Dimension getTNCrosshairCentre(final Location.Estd loc)
122 {
123 if(loc == null) { throw new IllegalArgumentException(); }
124
125 final float ePos = loc.getE().value.floatValue() / Location.Estd.MAX_E;
126 final float nPos = loc.getN().value.floatValue() / Location.Estd.MAX_N;
127
128 final int x = Math.max(0, Math.min(LOC_TN_WIDTH - 1,
129 Math.round(((1 + ePos) / 2.0f) * LOC_TN_WIDTH)));
130 // Note that y axis polarity is reverse of N dimension.
131 final int y = Math.max(0, Math.min(LOC_TN_HEIGHT - 1,
132 Math.round(((1 - nPos) / 2.0f) * LOC_TN_HEIGHT)));
133
134 return(new Dimension(x, y));
135 }
136
137 /**Return root-relative (starting with "/") URL for Estd location thumbnail.
138 * May be fixed, or may depend on actual location.
139 *
140 * @param loc the location on Earth to link to the map for; never null
141 * @param embedInHTML if true, result will be embedded in HTML
142 * so we will separate parameters with '&amp;' rather than
143 * an unprotected '&'
144 */
145 public static String getEstdLocationThumbnailRRL(final Location.Estd loc,
146 final boolean embedInHTML)
147 {
148 final Dimension dim = getTNCrosshairCentre(loc);
149 final StringBuilder result =
150 new StringBuilder(DYN_LOC_TN_MOUNT_POINT.length() + 16);
151 result.append(DYN_LOC_TN_MOUNT_POINT);
152 result.append("?x=").append(dim.width);
153 if(embedInHTML) { result.append("&"); } else { result.append('&'); }
154 result.append("y=").append(dim.height);
155 return(result.toString());
156 }
157
158 /**Returns a modified location thumbnail with crosshairs centred on x,y; never null.
159 * This will create an image on first use,
160 * and cache it (via a SoftReference).
161 * <p>
162 * The byte[] returned is not copied and so must not be altered.
163 * This routine is only package-visible.
164 * <p>
165 * This routine is synchronized primarily to make the cache thread-safe,
166 * but has the side-effect of ensuring that only one thumbnail
167 * can be generated at once, thus limiting peak CPU and memory demands.
168 *
169 * @param context context of current WAR; must not be null
170 * @param x x coordinate of crosshair centre within thumbnail
171 * @param y x coordinate of crosshair centre within thumbnail
172 * @return encoded image for modified thumbnail;
173 * this is a shared copy and must not be altered by the caller
174 * which is one reason that the routine is only package-visible
175 * @throws java.lang.IllegalArgumentException if context is null
176 * @throws java.lang.RuntimeException if x or y not valid pixel coords
177 * within the thumbnail, ie where the crosshair could be centred.
178 * @throws java.io.IOException if difficulty generating modified thumbnail
179 * for example because the site is too busy
180 */
181 static synchronized byte[] getLocTNWithCrosshairs(
182 final ServletContext context,
183 final int x, final int y)
184 throws IllegalArgumentException,
185 RuntimeException,
186 IOException
187 {
188 byte result[];
189 if((_gLTWC_cache[x][y] == null) ||
190 ((result = _gLTWC_cache[x][y].get()) == null))
191 {
192 // Refuse to actually generate new thumbnail if overloaded.
193 if(WebUtils.isOverloaded(context))
194 { throw new IOException("too busy to generate thumbnail"); }
195
196 final BufferedImage in =
197 WebUtils.getAndCacheStaticImage(true, // Get a copy.
198 BASE_ESTD_LOC_TN_URL,
199 true, // Force to true colour.
200 context); // Let this be GCed.
201
202 // Draw in the crosshairs...
203 // Do not overwrite at focus so that target pixel can be seen.
204 for(int yc = LOC_TN_HEIGHT; --yc >= 0; )
205 { if(yc != y) { in.setRGB(x, yc, DYN_LOC_TN_CROSSHAIR_RGB_COLOUR); } }
206 for(int xc = LOC_TN_WIDTH; --xc >= 0; )
207 { if(xc != x) { in.setRGB(xc, y, DYN_LOC_TN_CROSSHAIR_RGB_COLOUR); } }
208
209 // Force to indexed image; four colours should be enough/efficient,
210 // and may allow packing of at least 4 pixels to the byte.
211 final BufferedImage out = ImageUtils.makeColourReducedBufferedImage(
212 in, 4, true);
213
214 // Generate and cache encoded image.
215 // Don't attempt to fudge the quality.
216 result = ETP.handler.makeImageBinary(out, Integer.MAX_VALUE);
217 if(result == null) { throw new IOException(); }
218 _gLTWC_cache[x][y] = new SoftReference<byte[]>(result);
219 }
220 return(result);
221 }
222
223 /**Private cache for getLocTNWithCrosshairs() accessed under the class lock.
224 * Arranged as (x,y)-crosshair-centre array of
225 * SoftReference to byte[] encoded images.
226 */
227 @SuppressWarnings("unchecked")
228 private static final SoftReference<byte[]> _gLTWC_cache[][] =
229 new SoftReference[LOC_TN_WIDTH][LOC_TN_HEIGHT];
230
231
232 /**Make HTML img for correct Estd location thumbnail with E/N crosshairs; may be empty but never null.
233 * If there is no location then return the empty string.
234 * <p>
235 * The returned img has the alignment "top" and alt text "Aloha Earth!"
236 * and no whitespace around it.
237 */
238 public static String makeLocTNImgHTML(final HttpServletRequest request, final Location.Estd location)
239 {
240 if(location == null) { return(""); }
241
242 final int capacity = 145;
243 final StringBuilder result = new StringBuilder(capacity);
244 result.append("<img src=\"").
245 append(WebUtils.getOptionalSneakyConcurrencyRRURLPrefix(request)).
246 append(LocationUtils.getEstdLocationThumbnailRRL(location, true)).
247 append('"');
248 result.append(" width=").append(LOC_TN_WIDTH);
249 result.append(" height=").append(LOC_TN_HEIGHT);
250 result.append(" align=top alt=\"Aloha Earth!\" title=\"Aloha Earth!\">");
251 if(IsDebug.isDebug && ((result.length() > capacity) || (result.length() < capacity/2))) { System.err.println("WARNING: makeLocTNImgHTML(): len="+result.length()+" vs capacity="+capacity+"."); }
252 return(result.toString());
253 }
254 }