001: package net.refractions.udig.project.internal.render.impl;
002:
003: import java.awt.Dimension;
004: import java.awt.Graphics2D;
005: import java.awt.Point;
006: import java.awt.Rectangle;
007: import java.awt.geom.AffineTransform;
008: import java.awt.image.BufferedImage;
009:
010: import net.refractions.udig.project.internal.Messages;
011: import net.refractions.udig.project.internal.ProjectPlugin;
012: import net.refractions.udig.project.internal.Trace;
013: import net.refractions.udig.project.internal.render.RenderContext;
014: import net.refractions.udig.project.internal.render.Renderer;
015: import net.refractions.udig.project.internal.render.RendererDecorator;
016: import net.refractions.udig.project.internal.render.ViewportModel;
017: import net.refractions.udig.project.preferences.PreferenceConstants;
018: import net.refractions.udig.project.render.IRenderContext;
019: import net.refractions.udig.project.render.IRenderer;
020: import net.refractions.udig.project.render.RenderException;
021:
022: import org.eclipse.core.runtime.IProgressMonitor;
023: import org.eclipse.emf.common.notify.Notification;
024: import org.eclipse.emf.common.util.EList;
025: import org.eclipse.emf.common.util.TreeIterator;
026: import org.eclipse.emf.ecore.EClass;
027: import org.eclipse.emf.ecore.EObject;
028: import org.eclipse.emf.ecore.EReference;
029: import org.eclipse.emf.ecore.EStructuralFeature;
030: import org.eclipse.emf.ecore.resource.Resource;
031: import org.eclipse.emf.ecore.util.EcoreUtil;
032: import org.eclipse.jface.preference.IPreferenceStore;
033: import org.geotools.data.FeatureStore;
034:
035: import com.vividsolutions.jts.geom.Coordinate;
036: import com.vividsolutions.jts.geom.Envelope;
037:
038: public class TilingRenderer implements Renderer, RendererDecorator {
039:
040: private static final double SIGNIFICANT = 0.0001;
041:
042: protected Renderer child;
043:
044: protected ViewportModel oldViewport;
045:
046: protected Dimension oldDisplaySize;
047:
048: protected int dScreenW;
049:
050: protected int dScreenH;
051:
052: protected Envelope paintedAreaInWorld;
053:
054: public TilingRenderer(Renderer child) {
055: this .child = child;
056: }
057:
058: public void render(IProgressMonitor monitor) throws RenderException {
059: IPreferenceStore store = ProjectPlugin.getPlugin()
060: .getPreferenceStore();
061: boolean useTiling = store
062: .getBoolean(PreferenceConstants.P_TILING_RENDERER);
063:
064: if (getContext().getGeoResource()
065: .canResolve(FeatureStore.class)
066: || !useTiling) {
067: Envelope renderBounds = getRenderBounds();
068: if (renderBounds == null) {
069: getContext().clearImage();
070: } else {
071: Point min = getContext().worldToPixel(
072: new Coordinate(renderBounds.getMinX(),
073: renderBounds.getMinY()));
074: Point max = getContext().worldToPixel(
075: new Coordinate(renderBounds.getMaxX(),
076: renderBounds.getMaxY()));
077: int width = Math.abs(max.x - min.x);
078: int height = Math.abs(max.y - min.y);
079: Rectangle paintArea = new Rectangle(Math.min(min.x,
080: max.x), Math.min(min.y, max.y), width, height);
081:
082: getContext().clearImage(paintArea);
083: }
084: child.render(monitor);
085: } else {
086: checkState();
087: setNewState();
088: cacheRenderedImage();
089: drawCachedTiles();
090: approximateMissingTiles();
091: drawNeededTiles(monitor);
092: if (monitor.isCanceled()) {
093: oldViewport = null;
094: return;
095: }
096:
097: oldViewport = (ViewportModel) EcoreUtil.copy(getContext()
098: .getViewportModelInternal());
099: oldDisplaySize = getContext().getMapDisplay()
100: .getDisplaySize();
101: }
102: }
103:
104: /**
105: * Draw the non-cached tiles
106: *
107: * @param monitor
108: * @throws RenderException
109: */
110: private void drawNeededTiles(IProgressMonitor monitor)
111: throws RenderException {
112: Envelope currentBounds = getContext().getViewportModel()
113: .getBounds();
114: if (paintedAreaInWorld.isNull()) {
115: child.render(monitor);
116: return;
117: }
118: if (currentBounds.getHeight() - paintedAreaInWorld.getHeight() > currentBounds
119: .getWidth()
120: - paintedAreaInWorld.getWidth()) {
121: renderY(monitor, currentBounds, currentBounds.getMinX(),
122: currentBounds.getMaxX());
123: renderX(monitor, currentBounds, paintedAreaInWorld
124: .getMinY(), paintedAreaInWorld.getMaxY());
125: } else {
126: renderX(monitor, currentBounds, currentBounds.getMinY(),
127: currentBounds.getMaxY());
128: update();
129: renderY(monitor, currentBounds, paintedAreaInWorld
130: .getMinX(), paintedAreaInWorld.getMaxX());
131: }
132: }
133:
134: private void renderX(IProgressMonitor monitor,
135: Envelope currentBounds, double miny, double maxy)
136: throws RenderException {
137: if (paintedAreaInWorld.getWidth() < currentBounds.getWidth()) {
138: double minx, maxx;
139: if (paintedAreaInWorld.getMinX() <= currentBounds.getMinX()) {
140: minx = currentBounds.getMaxX();
141: maxx = paintedAreaInWorld.getMaxX();
142: } else {
143: minx = currentBounds.getMinX();
144: maxx = paintedAreaInWorld.getMinX();
145: }
146:
147: Envelope envelope = new Envelope(minx, maxx, miny, maxy);
148: if (validEnvelope(envelope)) {
149: child.setRenderBounds(envelope);
150: child.render(monitor);
151: }
152: }
153: }
154:
155: private void renderY(IProgressMonitor monitor,
156: Envelope currentBounds, double minx, double maxx)
157: throws RenderException {
158: if (paintedAreaInWorld.getHeight() < currentBounds.getHeight()) {
159: double miny, maxy;
160: if (paintedAreaInWorld.getMinY() <= currentBounds.getMinY()) {
161: miny = currentBounds.getMaxY();
162: maxy = paintedAreaInWorld.getMaxY();
163: } else {
164: miny = currentBounds.getMinY();
165: maxy = paintedAreaInWorld.getMinY();
166: }
167:
168: Envelope envelope = new Envelope(minx, maxx, miny, maxy);
169: if (validEnvelope(envelope)) {
170: child.setRenderBounds(envelope);
171: child.render(monitor);
172: }
173: }
174: }
175:
176: /**
177: * Checks that the given envelope is actually valid. That is, it returns
178: * false if the envelope has duplicate x or y coordinates (and thus is a
179: * rectangle with width 0).
180: *
181: * @param envelope
182: * @return
183: */
184: protected boolean validEnvelope(Envelope envelope) {
185: Point lower = getContext().worldToPixel(
186: new Coordinate(envelope.getMinX(), envelope.getMinY()));
187: Point upper = getContext().worldToPixel(
188: new Coordinate(envelope.getMaxX(), envelope.getMaxY()));
189:
190: if (lower.x == upper.x || lower.y == upper.y) {
191: return false;
192: }
193: return true;
194: }
195:
196: /**
197: * Tell the owner that it can update the image.
198: */
199: private void update() {
200: setState(RENDERING);
201: }
202:
203: /**
204: * Draws an approximation from the missing tiles.
205: */
206: private void approximateMissingTiles() {
207: // TODO Auto-generated method stub
208:
209: }
210:
211: /**
212: * Draws part or the whole of the image from the cached data.
213: */
214: private void drawCachedTiles() {
215: if (getState() == NEVER || getState() == RENDER_REQUEST
216: || getState() == RENDERING) {
217: getContext().clearImage();
218: } else if (!isZoomChanged()) {
219: panImage();
220: } else {
221: getContext().clearImage();
222: }
223: }
224:
225: /**
226: * If the image has been resized or panned then you can copy the area from the old image
227: * quickly.
228: */
229: protected void panImage() {
230: if (oldViewport == null)
231: return;
232:
233: ProjectPlugin.trace(Trace.RENDER, getClass(),
234: "Panning existing image", null); //$NON-NLS-1$
235:
236: Envelope current = getContext().getViewportModel().getBounds();
237: Envelope old = oldViewport.getBounds();
238:
239: if (old.equals(current))
240: return;
241:
242: double worldPaintedMinX = old.getMinX();
243: double worldPaintedMinY = old.getMinY();
244: double worldPaintedMaxX = current.getMaxX();
245: double worldPaintedMaxY = current.getMaxY();
246:
247: if (Math.abs(old.getMinX() - current.getMinX()) > SIGNIFICANT) {
248: if (old.getMinX() > current.getMinX()) {
249: worldPaintedMinX = current.getMaxX();
250: worldPaintedMaxX = old.getMinX();
251: } else {
252: worldPaintedMinX = current.getMinX();
253: worldPaintedMaxX = old.getMaxX();
254: }
255: }
256: if (Math.abs(old.getMinY() - current.getMinY()) > SIGNIFICANT) {
257: if (old.getMinY() > current.getMinY()) {
258: worldPaintedMinY = old.getMinY();
259: worldPaintedMaxY = current.getMaxY();
260: } else {
261: worldPaintedMinY = current.getMinY();
262: worldPaintedMaxY = old.getMaxY();
263: }
264: }
265: paintedAreaInWorld = new Envelope(worldPaintedMinX,
266: worldPaintedMaxX, worldPaintedMinY, worldPaintedMaxY);
267:
268: AffineTransform at = getContext().worldToScreenTransform();
269:
270: double[] points = new double[] { old.getMinX(), old.getMaxY() };
271:
272: at.transform(points, 0, points, 0, 1);
273: double oldminx = points[0];
274: double oldminy = points[1];
275: BufferedImage image = new BufferedImage(oldDisplaySize.width,
276: oldDisplaySize.height, BufferedImage.TYPE_4BYTE_ABGR);
277: image.createGraphics().drawImage(getContext().getImage(), 0, 0,
278: null);
279: getContext().clearImage();
280: Graphics2D graphics = getContext().getImage().createGraphics();
281: AffineTransform transform = AffineTransform
282: .getTranslateInstance(oldminx, oldminy);
283:
284: graphics.drawRenderedImage(image, transform);
285:
286: graphics.dispose();
287:
288: }
289:
290: /**
291: * Splits the existing image into tiles and caches them.
292: */
293: private void cacheRenderedImage() {
294: // TODO Auto-generated method stub
295:
296: }
297:
298: /**
299: * Makes calculations about what the needed areas are, how the new and old area intersect etc..
300: * for use by the other methods
301: */
302: private void setNewState() {
303: paintedAreaInWorld = new Envelope();
304:
305: }
306:
307: /**
308: * This needs to go to the super class. The class throws an exception if the renderer is in
309: * disposed state.
310: */
311: private void checkState() {
312: if (getState() == IRenderer.DISPOSED)
313: throw new IllegalStateException(
314: Messages.TilingRenderer_disposedError);
315: }
316:
317: public void render(Graphics2D graphics, IProgressMonitor monitor)
318: throws RenderException {
319: checkState();
320: child.render(graphics, monitor);
321: }
322:
323: boolean isZoomChanged() {
324: Dimension displaySize = getContext().getRenderManager()
325: .getMapDisplay().getDisplaySize();
326: if (oldViewport == null) {
327: return true;
328: }
329: Envelope old = oldViewport.getBounds();
330: Envelope curr = getContext().getViewportModel().getBounds();
331:
332: if (!displaySize.equals(oldDisplaySize)) {
333: dScreenW = displaySize.width - oldDisplaySize.width;
334: dScreenH = displaySize.height - oldDisplaySize.height;
335: } else {
336: dScreenW = -1;
337: dScreenH = -1;
338: }
339: if (Math.abs(old.getWidth() - curr.getWidth()) > SIGNIFICANT
340: && dScreenH == -1 && dScreenW == -1)
341: return true;
342:
343: if (Math.abs(old.getHeight() - curr.getHeight()) > SIGNIFICANT
344: && dScreenH == -1 && dScreenW == -1)
345: return true;
346:
347: return false;
348: }
349:
350: public RenderContext getContext() {
351: return (RenderContext) child.getContext();
352: }
353:
354: public void dispose() {
355: checkState();
356: purgeImageCache();
357: child.dispose();
358: setState(IRenderer.DISPOSED);
359: }
360:
361: /**
362: * Removes the cache from memory
363: */
364: private void purgeImageCache() {
365: // TODO Auto-generated method stub
366:
367: }
368:
369: public Renderer getRenderer() {
370: return child;
371: }
372:
373: @Override
374: public String toString() {
375: return child.toString();
376: }
377:
378: public EList eAdapters() {
379: return child.eAdapters();
380: }
381:
382: public int getState() {
383: return child.getState();
384: }
385:
386: public void setState(int newState) {
387: child.setState(newState);
388: }
389:
390: public String getName() {
391: return child.getName();
392: }
393:
394: public void setName(String value) {
395: child.setName(value);
396: }
397:
398: public void setContext(IRenderContext context) {
399: child.setContext(context);
400: }
401:
402: public EClass eClass() {
403: return child.eClass();
404: }
405:
406: public Resource eResource() {
407: return child.eResource();
408: }
409:
410: public EObject eContainer() {
411: return child.eContainer();
412: }
413:
414: public EStructuralFeature eContainingFeature() {
415: return child.eContainingFeature();
416: }
417:
418: public EReference eContainmentFeature() {
419: return child.eContainmentFeature();
420: }
421:
422: public EList eContents() {
423: return child.eContents();
424: }
425:
426: public TreeIterator eAllContents() {
427: return child.eAllContents();
428: }
429:
430: public boolean eIsProxy() {
431: return child.eIsProxy();
432: }
433:
434: public EList eCrossReferences() {
435: return child.eCrossReferences();
436: }
437:
438: public Object eGet(EStructuralFeature feature) {
439: return child.eGet(feature);
440: }
441:
442: public Object eGet(EStructuralFeature feature, boolean resolve) {
443: return child.eGet(feature, resolve);
444: }
445:
446: public void eSet(EStructuralFeature feature, Object newValue) {
447: child.eSet(feature, newValue);
448: }
449:
450: public boolean eIsSet(EStructuralFeature feature) {
451: return child.eIsSet(feature);
452: }
453:
454: public void eUnset(EStructuralFeature feature) {
455: child.eUnset(feature);
456: }
457:
458: public boolean eDeliver() {
459: return child.eDeliver();
460: }
461:
462: public void eSetDeliver(boolean deliver) {
463: child.eSetDeliver(deliver);
464: }
465:
466: public void eNotify(Notification notification) {
467: child.eNotify(notification);
468: }
469:
470: public boolean isCacheable() {
471: return child.isCacheable();
472: }
473:
474: public Envelope getRenderBounds() {
475: return child.getRenderBounds();
476: }
477:
478: public void setRenderBounds(Envelope boundsToRender) {
479: child.setRenderBounds(boundsToRender);
480: }
481:
482: public void setRenderBounds(Rectangle screenArea) {
483: child.setRenderBounds(screenArea);
484: }
485:
486: }
|