mirror of
https://github.com/DSheirer/sdrtrunk.git
synced 2026-11-12 08:37:25 -05:00
fix src folder structure
This commit is contained in:
504
src/main/java/org/jdesktop/beans/AbstractBean.java
Normal file
504
src/main/java/org/jdesktop/beans/AbstractBean.java
Normal file
@@ -0,0 +1,504 @@
|
||||
/*
|
||||
* $Id: AbstractBean.java 4088 2011-11-17 19:53:49Z kschaefe $
|
||||
*
|
||||
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
|
||||
* Santa Clara, California 95054, U.S.A. All rights reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package org.jdesktop.beans;
|
||||
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.beans.PropertyChangeSupport;
|
||||
import java.beans.PropertyVetoException;
|
||||
import java.beans.VetoableChangeListener;
|
||||
import java.beans.VetoableChangeSupport;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* A convenience class from which to extend all non-visual AbstractBeans. It
|
||||
* manages the PropertyChange notification system, making it relatively trivial
|
||||
* to add support for property change events in getters/setters.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* A non-visual java bean is a Java class that conforms to the AbstractBean
|
||||
* patterns to allow visual manipulation of the bean's properties and event
|
||||
* handlers at design-time.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Here is a simple example bean that contains one property, foo, and the proper
|
||||
* pattern for implementing property change notification:
|
||||
*
|
||||
* <pre><code>
|
||||
* public class ABean extends AbstractBean {
|
||||
* private String foo;
|
||||
*
|
||||
* public void setFoo(String newFoo) {
|
||||
* String old = getFoo();
|
||||
* this.foo = newFoo;
|
||||
* firePropertyChange("foo", old, getFoo());
|
||||
* }
|
||||
*
|
||||
* public String getFoo() {
|
||||
* return foo;
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* You will notice that "getFoo()" is used in the setFoo method rather than
|
||||
* accessing "foo" directly for the gets. This is done intentionally so that if
|
||||
* a subclass overrides getFoo() to return, for instance, a constant value the
|
||||
* property change notification system will continue to work properly.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The firePropertyChange method takes into account the old value and the new
|
||||
* value. Only if the two differ will it fire a property change event. So you
|
||||
* can be assured from the above code fragment that a property change event will
|
||||
* only occur if old is indeed different from getFoo()
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <code>AbstractBean</code> also supports vetoable
|
||||
* {@link PropertyChangeEvent} events. These events are similar to
|
||||
* <code>PropertyChange</code> events, except a special exception can be used
|
||||
* to veto changing the property. For example, perhaps the property is changing
|
||||
* from "fred" to "red", but a listener deems that "red" is unexceptable. In
|
||||
* this case, the listener can fire a veto exception and the property must
|
||||
* remain "fred". For example:
|
||||
*
|
||||
* <pre><code>
|
||||
* public class ABean extends AbstractBean {
|
||||
* private String foo;
|
||||
*
|
||||
* public void setFoo(String newFoo) throws PropertyVetoException {
|
||||
* String old = getFoo();
|
||||
* this.foo = newFoo;
|
||||
* fireVetoableChange("foo", old, getFoo());
|
||||
* }
|
||||
* public String getFoo() {
|
||||
* return foo;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* public class Tester {
|
||||
* public static void main(String... args) {
|
||||
* try {
|
||||
* ABean a = new ABean();
|
||||
* a.setFoo("fred");
|
||||
* a.addVetoableChangeListener(new VetoableChangeListener() {
|
||||
* public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
|
||||
* if ("red".equals(evt.getNewValue()) {
|
||||
* throw new PropertyVetoException("Cannot be red!", evt);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* a.setFoo("red");
|
||||
* } catch (Exception e) {
|
||||
* e.printStackTrace(); // this will be executed
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </code></pre>
|
||||
*
|
||||
* </p>
|
||||
* <p>
|
||||
* {@code AbstractBean} is not {@link java.io.Serializable}. Special care must
|
||||
* be taken when creating {@code Serializable} subclasses, as the
|
||||
* {@code Serializable} listeners will not be saved. Subclasses will need to
|
||||
* manually save the serializable listeners. The {@link AbstractSerializableBean}
|
||||
* is {@code Serializable} and already handles the listeners correctly. If
|
||||
* possible, it is recommended that {@code Serializable} beans should extend
|
||||
* {@code AbstractSerializableBean}. If it is not possible, the
|
||||
* {@code AbstractSerializableBean} bean implementation provides details on
|
||||
* how to correctly serialize an {@code AbstractBean} subclass.
|
||||
* </p>
|
||||
*
|
||||
* @see AbstractSerializableBean
|
||||
* @status REVIEWED
|
||||
* @author rbair
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public abstract class AbstractBean {
|
||||
/**
|
||||
* Helper class that manages all the property change notification machinery.
|
||||
* PropertyChangeSupport cannot be extended directly because it requires
|
||||
* a bean in the constructor, and the "this" argument is not valid until
|
||||
* after super construction. Hence, delegation instead of extension
|
||||
*/
|
||||
private transient PropertyChangeSupport pcs;
|
||||
|
||||
/**
|
||||
* Helper class that manages all the veto property change notification machinery.
|
||||
*/
|
||||
private transient VetoableChangeSupport vcs;
|
||||
|
||||
/** Creates a new instance of AbstractBean */
|
||||
protected AbstractBean() {
|
||||
pcs = new PropertyChangeSupport(this);
|
||||
vcs = new VetoableChangeSupport(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of AbstractBean, using the supplied PropertyChangeSupport and
|
||||
* VetoableChangeSupport delegates. Neither of these may be null.
|
||||
* @param pcs the property change support
|
||||
* @param vcs the vetoable change support
|
||||
*/
|
||||
protected AbstractBean(PropertyChangeSupport pcs, VetoableChangeSupport vcs) {
|
||||
if (pcs == null) {
|
||||
throw new NullPointerException("PropertyChangeSupport must not be null");
|
||||
}
|
||||
if (vcs == null) {
|
||||
throw new NullPointerException("VetoableChangeSupport must not be null");
|
||||
}
|
||||
|
||||
this.pcs = pcs;
|
||||
this.vcs = vcs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a PropertyChangeListener to the listener list.
|
||||
* The listener is registered for all properties.
|
||||
* The same listener object may be added more than once, and will be called
|
||||
* as many times as it is added.
|
||||
* If <code>listener</code> is null, no exception is thrown and no action
|
||||
* is taken.
|
||||
*
|
||||
* @param listener The PropertyChangeListener to be added
|
||||
*/
|
||||
public final void addPropertyChangeListener(PropertyChangeListener listener) {
|
||||
pcs.addPropertyChangeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a PropertyChangeListener from the listener list.
|
||||
* This removes a PropertyChangeListener that was registered
|
||||
* for all properties.
|
||||
* If <code>listener</code> was added more than once to the same event
|
||||
* source, it will be notified one less time after being removed.
|
||||
* If <code>listener</code> is null, or was never added, no exception is
|
||||
* thrown and no action is taken.
|
||||
*
|
||||
* @param listener The PropertyChangeListener to be removed
|
||||
*/
|
||||
public final void removePropertyChangeListener(PropertyChangeListener listener) {
|
||||
pcs.removePropertyChangeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all the listeners that were added to the
|
||||
* PropertyChangeSupport object with addPropertyChangeListener().
|
||||
* <p>
|
||||
* If some listeners have been added with a named property, then
|
||||
* the returned array will be a mixture of PropertyChangeListeners
|
||||
* and <code>PropertyChangeListenerProxy</code>s. If the calling
|
||||
* method is interested in distinguishing the listeners then it must
|
||||
* test each element to see if it's a
|
||||
* <code>PropertyChangeListenerProxy</code>, perform the cast, and examine
|
||||
* the parameter.
|
||||
*
|
||||
* <pre>
|
||||
* PropertyChangeListener[] listeners = bean.getPropertyChangeListeners();
|
||||
* for (int i = 0; i < listeners.length; i++) {
|
||||
* if (listeners[i] instanceof PropertyChangeListenerProxy) {
|
||||
* PropertyChangeListenerProxy proxy =
|
||||
* (PropertyChangeListenerProxy)listeners[i];
|
||||
* if (proxy.getPropertyName().equals("foo")) {
|
||||
* // proxy is a PropertyChangeListener which was associated
|
||||
* // with the property named "foo"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*</pre>
|
||||
*
|
||||
* @see java.beans.PropertyChangeListenerProxy
|
||||
* @return all of the <code>PropertyChangeListeners</code> added or an
|
||||
* empty array if no listeners have been added
|
||||
*/
|
||||
public final PropertyChangeListener[] getPropertyChangeListeners() {
|
||||
return pcs.getPropertyChangeListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a PropertyChangeListener for a specific property. The listener
|
||||
* will be invoked only when a call on firePropertyChange names that
|
||||
* specific property.
|
||||
* The same listener object may be added more than once. For each
|
||||
* property, the listener will be invoked the number of times it was added
|
||||
* for that property.
|
||||
* If <code>propertyName</code> or <code>listener</code> is null, no
|
||||
* exception is thrown and no action is taken.
|
||||
*
|
||||
* @param propertyName The name of the property to listen on.
|
||||
* @param listener The PropertyChangeListener to be added
|
||||
*/
|
||||
public final void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
|
||||
pcs.addPropertyChangeListener(propertyName, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a PropertyChangeListener for a specific property.
|
||||
* If <code>listener</code> was added more than once to the same event
|
||||
* source for the specified property, it will be notified one less time
|
||||
* after being removed.
|
||||
* If <code>propertyName</code> is null, no exception is thrown and no
|
||||
* action is taken.
|
||||
* If <code>listener</code> is null, or was never added for the specified
|
||||
* property, no exception is thrown and no action is taken.
|
||||
*
|
||||
* @param propertyName The name of the property that was listened on.
|
||||
* @param listener The PropertyChangeListener to be removed
|
||||
*/
|
||||
public final void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
|
||||
pcs.removePropertyChangeListener(propertyName, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all the listeners which have been associated
|
||||
* with the named property.
|
||||
*
|
||||
* @param propertyName The name of the property being listened to
|
||||
* @return all of the <code>PropertyChangeListeners</code> associated with
|
||||
* the named property. If no such listeners have been added,
|
||||
* or if <code>propertyName</code> is null, an empty array is
|
||||
* returned.
|
||||
*/
|
||||
public final PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
|
||||
return pcs.getPropertyChangeListeners(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report a bound property update to any registered listeners.
|
||||
* No event is fired if old and new are equal and non-null.
|
||||
*
|
||||
* <p>
|
||||
* This is merely a convenience wrapper around the more general
|
||||
* firePropertyChange method that takes {@code
|
||||
* PropertyChangeEvent} value.
|
||||
*
|
||||
* @param propertyName The programmatic name of the property
|
||||
* that was changed.
|
||||
* @param oldValue The old value of the property.
|
||||
* @param newValue The new value of the property.
|
||||
*/
|
||||
protected final void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
|
||||
pcs.firePropertyChange(propertyName, oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire an existing PropertyChangeEvent to any registered listeners.
|
||||
* No event is fired if the given event's old and new values are
|
||||
* equal and non-null.
|
||||
* @param evt The PropertyChangeEvent object.
|
||||
*/
|
||||
protected final void firePropertyChange(PropertyChangeEvent evt) {
|
||||
pcs.firePropertyChange(evt);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Report a bound indexed property update to any registered
|
||||
* listeners.
|
||||
* <p>
|
||||
* No event is fired if old and new values are equal
|
||||
* and non-null.
|
||||
*
|
||||
* <p>
|
||||
* This is merely a convenience wrapper around the more general
|
||||
* firePropertyChange method that takes {@code PropertyChangeEvent} value.
|
||||
*
|
||||
* @param propertyName The programmatic name of the property that
|
||||
* was changed.
|
||||
* @param index index of the property element that was changed.
|
||||
* @param oldValue The old value of the property.
|
||||
* @param newValue The new value of the property.
|
||||
*/
|
||||
protected final void fireIndexedPropertyChange(String propertyName, int index,
|
||||
Object oldValue, Object newValue) {
|
||||
pcs.fireIndexedPropertyChange(propertyName, index, oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are any listeners for a specific property, including
|
||||
* those registered on all properties. If <code>propertyName</code>
|
||||
* is null, only check for listeners registered on all properties.
|
||||
*
|
||||
* @param propertyName the property name.
|
||||
* @return true if there are one or more listeners for the given property
|
||||
*/
|
||||
protected final boolean hasPropertyChangeListeners(String propertyName) {
|
||||
return pcs.hasListeners(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are any listeners for a specific property, including
|
||||
* those registered on all properties. If <code>propertyName</code>
|
||||
* is null, only check for listeners registered on all properties.
|
||||
*
|
||||
* @param propertyName the property name.
|
||||
* @return true if there are one or more listeners for the given property
|
||||
*/
|
||||
protected final boolean hasVetoableChangeListeners(String propertyName) {
|
||||
return vcs.hasListeners(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a VetoableListener to the listener list.
|
||||
* The listener is registered for all properties.
|
||||
* The same listener object may be added more than once, and will be called
|
||||
* as many times as it is added.
|
||||
* If <code>listener</code> is null, no exception is thrown and no action
|
||||
* is taken.
|
||||
*
|
||||
* @param listener The VetoableChangeListener to be added
|
||||
*/
|
||||
|
||||
public final void addVetoableChangeListener(VetoableChangeListener listener) {
|
||||
vcs.addVetoableChangeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a VetoableChangeListener from the listener list.
|
||||
* This removes a VetoableChangeListener that was registered
|
||||
* for all properties.
|
||||
* If <code>listener</code> was added more than once to the same event
|
||||
* source, it will be notified one less time after being removed.
|
||||
* If <code>listener</code> is null, or was never added, no exception is
|
||||
* thrown and no action is taken.
|
||||
*
|
||||
* @param listener The VetoableChangeListener to be removed
|
||||
*/
|
||||
public final void removeVetoableChangeListener(VetoableChangeListener listener) {
|
||||
vcs.removeVetoableChangeListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of VetoableChangeListeners. If named vetoable change listeners
|
||||
* were added, then VetoableChangeListenerProxy wrappers will returned
|
||||
* <p>
|
||||
* @return List of VetoableChangeListeners and VetoableChangeListenerProxys
|
||||
* if named property change listeners were added.
|
||||
*/
|
||||
public final VetoableChangeListener[] getVetoableChangeListeners(){
|
||||
return vcs.getVetoableChangeListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a VetoableChangeListener for a specific property. The listener
|
||||
* will be invoked only when a call on fireVetoableChange names that
|
||||
* specific property.
|
||||
* The same listener object may be added more than once. For each
|
||||
* property, the listener will be invoked the number of times it was added
|
||||
* for that property.
|
||||
* If <code>propertyName</code> or <code>listener</code> is null, no
|
||||
* exception is thrown and no action is taken.
|
||||
*
|
||||
* @param propertyName The name of the property to listen on.
|
||||
* @param listener The VetoableChangeListener to be added
|
||||
*/
|
||||
|
||||
public final void addVetoableChangeListener(String propertyName,
|
||||
VetoableChangeListener listener) {
|
||||
vcs.addVetoableChangeListener(propertyName, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a VetoableChangeListener for a specific property.
|
||||
* If <code>listener</code> was added more than once to the same event
|
||||
* source for the specified property, it will be notified one less time
|
||||
* after being removed.
|
||||
* If <code>propertyName</code> is null, no exception is thrown and no
|
||||
* action is taken.
|
||||
* If <code>listener</code> is null, or was never added for the specified
|
||||
* property, no exception is thrown and no action is taken.
|
||||
*
|
||||
* @param propertyName The name of the property that was listened on.
|
||||
* @param listener The VetoableChangeListener to be removed
|
||||
*/
|
||||
|
||||
public final void removeVetoableChangeListener(String propertyName,
|
||||
VetoableChangeListener listener) {
|
||||
vcs.removeVetoableChangeListener(propertyName, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of all the listeners which have been associated
|
||||
* with the named property.
|
||||
*
|
||||
* @param propertyName The name of the property being listened to
|
||||
* @return all the <code>VetoableChangeListeners</code> associated with
|
||||
* the named property. If no such listeners have been added,
|
||||
* or if <code>propertyName</code> is null, an empty array is
|
||||
* returned.
|
||||
*/
|
||||
public final VetoableChangeListener[] getVetoableChangeListeners(String propertyName) {
|
||||
return vcs.getVetoableChangeListeners(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report a vetoable property update to any registered listeners. If
|
||||
* anyone vetos the change, then fire a new event reverting everyone to
|
||||
* the old value and then rethrow the PropertyVetoException.
|
||||
* <p>
|
||||
* No event is fired if old and new are equal and non-null.
|
||||
*
|
||||
* @param propertyName The programmatic name of the property
|
||||
* that is about to change..
|
||||
* @param oldValue The old value of the property.
|
||||
* @param newValue The new value of the property.
|
||||
* @exception PropertyVetoException if the recipient wishes the property
|
||||
* change to be rolled back.
|
||||
*/
|
||||
protected final void fireVetoableChange(String propertyName,
|
||||
Object oldValue, Object newValue)
|
||||
throws PropertyVetoException {
|
||||
vcs.fireVetoableChange(propertyName, oldValue, newValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fire a vetoable property update to any registered listeners. If
|
||||
* anyone vetos the change, then fire a new event reverting everyone to
|
||||
* the old value and then rethrow the PropertyVetoException.
|
||||
* <p>
|
||||
* No event is fired if old and new are equal and non-null.
|
||||
*
|
||||
* @param evt The PropertyChangeEvent to be fired.
|
||||
* @exception PropertyVetoException if the recipient wishes the property
|
||||
* change to be rolled back.
|
||||
*/
|
||||
protected final void fireVetoableChange(PropertyChangeEvent evt)
|
||||
throws PropertyVetoException {
|
||||
vcs.fireVetoableChange(evt);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public Object clone() throws CloneNotSupportedException {
|
||||
AbstractBean result = (AbstractBean) super.clone();
|
||||
result.pcs = new PropertyChangeSupport(result);
|
||||
result.vcs = new VetoableChangeSupport(result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
104
src/main/java/org/jdesktop/beans/AbstractSerializableBean.java
Normal file
104
src/main/java/org/jdesktop/beans/AbstractSerializableBean.java
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* $Id: AbstractSerializableBean.java 4088 2011-11-17 19:53:49Z kschaefe $
|
||||
*
|
||||
* Copyright 2008 Sun Microsystems, Inc., 4150 Network Circle,
|
||||
* Santa Clara, California 95054, U.S.A. All rights reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jdesktop.beans;
|
||||
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.beans.PropertyChangeSupport;
|
||||
import java.beans.VetoableChangeListener;
|
||||
import java.beans.VetoableChangeSupport;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* This subclass enhances {@code AbstractBean} by implementing the
|
||||
* {@code Serializable} interface. {@code AbstractSerializableBean} correctly
|
||||
* serializes all {@code Serializable} listeners that it contains. Implementors
|
||||
* that need to extends {@code AbstractBean} or one of its subclasses and
|
||||
* require serialization should use this class if possible. If it is not
|
||||
* possible to extend this class, the implementation can guide implementors on
|
||||
* how to properly serialize the listeners.
|
||||
*
|
||||
* @author Karl George Schaefer
|
||||
*
|
||||
* @see Serializable
|
||||
* @see ObjectInputStream
|
||||
* @see ObjectOutputStream
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public abstract class AbstractSerializableBean extends AbstractBean implements
|
||||
Serializable {
|
||||
/**
|
||||
* Creates a new instance of {@code AbstractSerializableBean}.
|
||||
*/
|
||||
protected AbstractSerializableBean() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@code AbstractSerializableBean}, using the
|
||||
* supplied support delegates. Neither of these may be {@code null}.
|
||||
*
|
||||
* @param pcs
|
||||
* the property change support class to use
|
||||
* @param vcs
|
||||
* the vetoable change support class to use
|
||||
* @throws NullPointerException
|
||||
* if any parameter is {@code null}
|
||||
*/
|
||||
protected AbstractSerializableBean(PropertyChangeSupport pcs,
|
||||
VetoableChangeSupport vcs) {
|
||||
super(pcs, vcs);
|
||||
}
|
||||
|
||||
private void writeObject(ObjectOutputStream s) throws IOException {
|
||||
s.defaultWriteObject();
|
||||
|
||||
for (PropertyChangeListener l : getPropertyChangeListeners()) {
|
||||
if (l instanceof Serializable) {
|
||||
s.writeObject(l);
|
||||
}
|
||||
}
|
||||
|
||||
for (VetoableChangeListener l : getVetoableChangeListeners()) {
|
||||
if (l instanceof Serializable) {
|
||||
s.writeObject(l);
|
||||
}
|
||||
}
|
||||
|
||||
s.writeObject(null);
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream s) throws ClassNotFoundException,
|
||||
IOException {
|
||||
s.defaultReadObject();
|
||||
|
||||
Object listenerOrNull;
|
||||
while (null != (listenerOrNull = s.readObject())) {
|
||||
if (listenerOrNull instanceof PropertyChangeListener) {
|
||||
addPropertyChangeListener((PropertyChangeListener) listenerOrNull);
|
||||
} else if (listenerOrNull instanceof VetoableChangeListener) {
|
||||
addVetoableChangeListener((VetoableChangeListener) listenerOrNull);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
145
src/main/java/org/jdesktop/swingx/JXMapKit.form
Normal file
145
src/main/java/org/jdesktop/swingx/JXMapKit.form
Normal file
@@ -0,0 +1,145 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.0" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="2"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,0,-11,0,0,1,125"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="org.jdesktop.swingx.JXMapViewer" name="mainMap">
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="1" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="1.0" weightY="1.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="org.jdesktop.swingx.JXMapViewer" name="miniMap">
|
||||
<Properties>
|
||||
<Property name="border" type="javax.swing.border.Border" editor="org.netbeans.modules.form.editors2.BorderEditor">
|
||||
<Border info="org.netbeans.modules.form.compat2.border.LineBorderInfo">
|
||||
<LineBorder/>
|
||||
</Border>
|
||||
</Property>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[100, 100]"/>
|
||||
</Property>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[100, 100]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="1" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="14" weightX="1.0" weightY="1.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||
</Container>
|
||||
<Container class="javax.swing.JPanel" name="jPanel1">
|
||||
<Properties>
|
||||
<Property name="opaque" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="4" insetsLeft="4" insetsBottom="4" insetsRight="4" anchor="16" weightX="1.0" weightY="1.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JButton" name="zoomInButton">
|
||||
<Properties>
|
||||
<Property name="action" type="javax.swing.Action" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<Connection code="getZoomOutAction()" type="code"/>
|
||||
</Property>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/jdesktop/swingx/mapviewer/resources/plus.png"/>
|
||||
</Property>
|
||||
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
|
||||
<Insets value="[2, 2, 2, 2]"/>
|
||||
</Property>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[20, 20]"/>
|
||||
</Property>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[20, 20]"/>
|
||||
</Property>
|
||||
<Property name="opaque" type="boolean" value="false"/>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[20, 20]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="zoomInButtonActionPerformed"/>
|
||||
</Events>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="1" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="12" weightX="1.0" weightY="1.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="zoomOutButton">
|
||||
<Properties>
|
||||
<Property name="action" type="javax.swing.Action" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<Connection code="getZoomInAction()" type="code"/>
|
||||
</Property>
|
||||
<Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor">
|
||||
<Image iconType="3" name="/org/jdesktop/swingx/mapviewer/resources/minus.png"/>
|
||||
</Property>
|
||||
<Property name="margin" type="java.awt.Insets" editor="org.netbeans.beaninfo.editors.InsetsEditor">
|
||||
<Insets value="[2, 2, 2, 2]"/>
|
||||
</Property>
|
||||
<Property name="maximumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[20, 20]"/>
|
||||
</Property>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[20, 20]"/>
|
||||
</Property>
|
||||
<Property name="opaque" type="boolean" value="false"/>
|
||||
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[20, 20]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="18" weightX="0.0" weightY="1.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
<Component class="javax.swing.JSlider" name="zoomSlider">
|
||||
<Properties>
|
||||
<Property name="majorTickSpacing" type="int" value="1"/>
|
||||
<Property name="maximum" type="int" value="15"/>
|
||||
<Property name="minimum" type="int" value="10"/>
|
||||
<Property name="minorTickSpacing" type="int" value="1"/>
|
||||
<Property name="orientation" type="int" value="1"/>
|
||||
<Property name="paintTicks" type="boolean" value="true"/>
|
||||
<Property name="snapToTicks" type="boolean" value="true"/>
|
||||
<Property name="minimumSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
|
||||
<Dimension value="[35, 100]"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="zoomSliderStateChanged"/>
|
||||
</Events>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
|
||||
<GridBagConstraints gridX="0" gridY="0" gridWidth="2" gridHeight="1" fill="3" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="11" weightX="0.0" weightY="0.0"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
724
src/main/java/org/jdesktop/swingx/JXMapKit.java
Normal file
724
src/main/java/org/jdesktop/swingx/JXMapKit.java
Normal file
@@ -0,0 +1,724 @@
|
||||
/*
|
||||
* JXMapKit.java
|
||||
*
|
||||
* Created on November 19, 2006, 3:52 AM
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Action;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSlider;
|
||||
import javax.swing.SwingConstants;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import org.jdesktop.swingx.mapviewer.DefaultTileFactory;
|
||||
import org.jdesktop.swingx.mapviewer.DefaultWaypoint;
|
||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||
import org.jdesktop.swingx.mapviewer.TileFactory;
|
||||
import org.jdesktop.swingx.mapviewer.TileFactoryInfo;
|
||||
import org.jdesktop.swingx.mapviewer.Waypoint;
|
||||
import org.jdesktop.swingx.mapviewer.WaypointPainter;
|
||||
import org.jdesktop.swingx.mapviewer.bmng.CylindricalProjectionTileFactory;
|
||||
import org.jdesktop.swingx.painter.AbstractPainter;
|
||||
import org.jdesktop.swingx.painter.CompoundPainter;
|
||||
import org.jdesktop.swingx.painter.Painter;
|
||||
|
||||
/**
|
||||
* <p>The JXMapKit is a pair of JXMapViewers preconfigured to be easy to use
|
||||
* with common features built in. This includes zoom buttons, a zoom slider,
|
||||
* and a mini-map in the lower right corner showing an overview of the map.
|
||||
* Each feature can be turned off using an appropriate
|
||||
* <CODE>is<I>X</I>visible</CODE> property. For example, to turn
|
||||
* off the minimap call
|
||||
*
|
||||
* <PRE><CODE>jxMapKit.setMiniMapVisible(false);</CODE></PRE>
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The JXMapViewer is preconfigured to connect to maps.swinglabs.org which
|
||||
* serves up global satellite imagery from NASA's
|
||||
* <a href="http://earthobservatory.nasa.gov/Newsroom/BlueMarble/">Blue
|
||||
* Marble NG</a> image collection.
|
||||
* </p>
|
||||
* @author joshy
|
||||
*/
|
||||
public class JXMapKit extends JPanel
|
||||
{
|
||||
private static final long serialVersionUID = -8366577998349912380L;
|
||||
private boolean miniMapVisible = true;
|
||||
private boolean zoomSliderVisible = true;
|
||||
private boolean zoomButtonsVisible = true;
|
||||
private final boolean sliderReversed = false;
|
||||
|
||||
@SuppressWarnings("javadoc")
|
||||
public enum DefaultProviders
|
||||
{
|
||||
SwingLabsBlueMarble, OpenStreetMaps, Custom
|
||||
}
|
||||
|
||||
private DefaultProviders defaultProvider = DefaultProviders.SwingLabsBlueMarble;
|
||||
|
||||
private boolean addressLocationShown = true;
|
||||
|
||||
private boolean dataProviderCreditShown = true;
|
||||
|
||||
/**
|
||||
* Creates a new JXMapKit
|
||||
*/
|
||||
public JXMapKit()
|
||||
{
|
||||
initComponents();
|
||||
setDataProviderCreditShown(false);
|
||||
|
||||
zoomSlider.setOpaque(false);
|
||||
try
|
||||
{
|
||||
Icon minusIcon = new ImageIcon(getClass().getResource("/org/jdesktop/swingx/mapviewer/resources/minus.png"));
|
||||
this.zoomOutButton.setIcon(minusIcon);
|
||||
this.zoomOutButton.setText("");
|
||||
Icon plusIcon = new ImageIcon(getClass().getResource("/org/jdesktop/swingx/mapviewer/resources/plus.png"));
|
||||
this.zoomInButton.setIcon(plusIcon);
|
||||
this.zoomInButton.setText("");
|
||||
}
|
||||
catch (Throwable thr)
|
||||
{
|
||||
System.out.println("error: " + thr.getMessage());
|
||||
thr.printStackTrace();
|
||||
}
|
||||
|
||||
setTileFactory(new CylindricalProjectionTileFactory());
|
||||
|
||||
mainMap.setCenterPosition(new GeoPosition(0, 0));
|
||||
miniMap.setCenterPosition(new GeoPosition(0, 0));
|
||||
mainMap.setRestrictOutsidePanning(true);
|
||||
miniMap.setRestrictOutsidePanning(true);
|
||||
|
||||
rebuildMainMapOverlay();
|
||||
|
||||
/*
|
||||
* // adapter to move the minimap after the main map has moved MouseInputAdapter ma = new MouseInputAdapter() {
|
||||
* public void mouseReleased(MouseEvent e) { miniMap.setCenterPosition(mapCenterPosition); } };
|
||||
* mainMap.addMouseMotionListener(ma); mainMap.addMouseListener(ma);
|
||||
*/
|
||||
|
||||
mainMap.addPropertyChangeListener("center", new PropertyChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt)
|
||||
{
|
||||
Point2D mapCenter = (Point2D) evt.getNewValue();
|
||||
TileFactory tf = mainMap.getTileFactory();
|
||||
GeoPosition mapPos = tf.pixelToGeo(mapCenter, mainMap.getZoom());
|
||||
miniMap.setCenterPosition(mapPos);
|
||||
}
|
||||
});
|
||||
|
||||
mainMap.addPropertyChangeListener("centerPosition", new PropertyChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt)
|
||||
{
|
||||
mapCenterPosition = (GeoPosition) evt.getNewValue();
|
||||
miniMap.setCenterPosition(mapCenterPosition);
|
||||
Point2D pt = miniMap.getTileFactory().geoToPixel(mapCenterPosition, miniMap.getZoom());
|
||||
miniMap.setCenter(pt);
|
||||
miniMap.repaint();
|
||||
}
|
||||
});
|
||||
|
||||
mainMap.addPropertyChangeListener("zoom", new PropertyChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt)
|
||||
{
|
||||
zoomSlider.setValue(mainMap.getZoom());
|
||||
miniMap.setZoom(mainMap.getZoom() + 4);
|
||||
}
|
||||
});
|
||||
|
||||
// an overlay for the mini-map which shows a rectangle representing the main map
|
||||
miniMap.setOverlayPainter(new Painter<JXMapViewer>()
|
||||
{
|
||||
@Override
|
||||
public void paint(Graphics2D g, JXMapViewer map, int width, int height)
|
||||
{
|
||||
// get the viewport rect of the main map
|
||||
Rectangle mainMapBounds = mainMap.getViewportBounds();
|
||||
|
||||
// convert to Point2Ds
|
||||
Point2D upperLeft2D = mainMapBounds.getLocation();
|
||||
Point2D lowerRight2D = new Point2D.Double(upperLeft2D.getX() + mainMapBounds.getWidth(), upperLeft2D
|
||||
.getY() + mainMapBounds.getHeight());
|
||||
|
||||
// convert to GeoPostions
|
||||
GeoPosition upperLeft = mainMap.getTileFactory().pixelToGeo(upperLeft2D, mainMap.getZoom());
|
||||
GeoPosition lowerRight = mainMap.getTileFactory().pixelToGeo(lowerRight2D, mainMap.getZoom());
|
||||
|
||||
// convert to Point2Ds on the mini-map
|
||||
upperLeft2D = map.getTileFactory().geoToPixel(upperLeft, map.getZoom());
|
||||
lowerRight2D = map.getTileFactory().geoToPixel(lowerRight, map.getZoom());
|
||||
|
||||
g = (Graphics2D) g.create();
|
||||
Rectangle rect = map.getViewportBounds();
|
||||
// p("rect = " + rect);
|
||||
g.translate(-rect.x, -rect.y);
|
||||
// Point2D centerpos = map.getTileFactory().geoToPixel(mapCenterPosition, map.getZoom());
|
||||
// p("center pos = " + centerpos);
|
||||
g.setPaint(Color.RED);
|
||||
// g.drawRect((int)centerpos.getX()-30,(int)centerpos.getY()-30,60,60);
|
||||
g.drawRect((int) upperLeft2D.getX(), (int) upperLeft2D.getY(),
|
||||
(int) (lowerRight2D.getX() - upperLeft2D.getX()),
|
||||
(int) (lowerRight2D.getY() - upperLeft2D.getY()));
|
||||
g.setPaint(new Color(255, 0, 0, 50));
|
||||
g.fillRect((int) upperLeft2D.getX(), (int) upperLeft2D.getY(),
|
||||
(int) (lowerRight2D.getX() - upperLeft2D.getX()),
|
||||
(int) (lowerRight2D.getY() - upperLeft2D.getY()));
|
||||
// g.drawOval((int)lowerRight2D.getX(),(int)lowerRight2D.getY(),1,1);
|
||||
g.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
if (getDefaultProvider() == DefaultProviders.OpenStreetMaps)
|
||||
{
|
||||
setZoom(10);
|
||||
}
|
||||
else
|
||||
{
|
||||
setZoom(3);// joshy: hack, i shouldn't need this here
|
||||
}
|
||||
this.setCenterPosition(new GeoPosition(0, 0));
|
||||
}
|
||||
|
||||
// private Point2D mapCenter = new Point2D.Double(0,0);
|
||||
private GeoPosition mapCenterPosition = new GeoPosition(0, 0);
|
||||
private boolean zoomChanging = false;
|
||||
|
||||
/**
|
||||
* Set the current zoomlevel for the main map. The minimap will be updated accordingly
|
||||
* @param zoom the new zoom level
|
||||
*/
|
||||
public void setZoom(int zoom)
|
||||
{
|
||||
zoomChanging = true;
|
||||
mainMap.setZoom(zoom);
|
||||
miniMap.setZoom(mainMap.getZoom() + 4);
|
||||
if (sliderReversed)
|
||||
{
|
||||
zoomSlider.setValue(zoomSlider.getMaximum() - zoom);
|
||||
}
|
||||
else
|
||||
{
|
||||
zoomSlider.setValue(zoom);
|
||||
}
|
||||
zoomChanging = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an action which can be attached to buttons or menu items to make the map zoom out
|
||||
* @return a preconfigured Zoom Out action
|
||||
*/
|
||||
public Action getZoomOutAction()
|
||||
{
|
||||
Action act = new AbstractAction()
|
||||
{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 5525706163434375107L;
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
setZoom(mainMap.getZoom() - 1);
|
||||
}
|
||||
};
|
||||
act.putValue(Action.NAME, "-");
|
||||
return act;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an action which can be attached to buttons or menu items to make the map zoom in
|
||||
* @return a preconfigured Zoom In action
|
||||
*/
|
||||
public Action getZoomInAction()
|
||||
{
|
||||
Action act = new AbstractAction()
|
||||
{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 5779971489365451352L;
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
setZoom(mainMap.getZoom() + 1);
|
||||
}
|
||||
};
|
||||
act.putValue(Action.NAME, "+");
|
||||
return act;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form. WARNING: Do NOT modify this code. The
|
||||
* content of this method is always regenerated by the Form Editor.
|
||||
*/
|
||||
// <editor-fold defaultstate="collapsed" desc=" Generated Code ">//GEN-BEGIN:initComponents
|
||||
private void initComponents()
|
||||
{
|
||||
java.awt.GridBagConstraints gridBagConstraints;
|
||||
|
||||
mainMap = new org.jdesktop.swingx.JXMapViewer();
|
||||
miniMap = new org.jdesktop.swingx.JXMapViewer();
|
||||
jPanel1 = new javax.swing.JPanel();
|
||||
zoomInButton = new javax.swing.JButton();
|
||||
zoomOutButton = new javax.swing.JButton();
|
||||
zoomSlider = new javax.swing.JSlider();
|
||||
|
||||
setLayout(new java.awt.GridBagLayout());
|
||||
|
||||
mainMap.setLayout(new java.awt.GridBagLayout());
|
||||
|
||||
miniMap.setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));
|
||||
miniMap.setMinimumSize(new java.awt.Dimension(100, 100));
|
||||
miniMap.setPreferredSize(new java.awt.Dimension(100, 100));
|
||||
miniMap.setLayout(new java.awt.GridBagLayout());
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 1;
|
||||
gridBagConstraints.gridy = 0;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHEAST;
|
||||
gridBagConstraints.weightx = 1.0;
|
||||
gridBagConstraints.weighty = 1.0;
|
||||
mainMap.add(miniMap, gridBagConstraints);
|
||||
|
||||
jPanel1.setOpaque(false);
|
||||
jPanel1.setLayout(new java.awt.GridBagLayout());
|
||||
|
||||
zoomInButton.setAction(getZoomOutAction());
|
||||
zoomInButton.setIcon(new javax.swing.ImageIcon(getClass().getResource(
|
||||
"/org/jdesktop/swingx/mapviewer/resources/plus.png")));
|
||||
zoomInButton.setMargin(new java.awt.Insets(2, 2, 2, 2));
|
||||
zoomInButton.setMaximumSize(new java.awt.Dimension(20, 20));
|
||||
zoomInButton.setMinimumSize(new java.awt.Dimension(20, 20));
|
||||
zoomInButton.setOpaque(false);
|
||||
zoomInButton.setPreferredSize(new java.awt.Dimension(20, 20));
|
||||
zoomInButton.addActionListener(new java.awt.event.ActionListener()
|
||||
{
|
||||
@Override
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt)
|
||||
{
|
||||
zoomInButtonActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 1;
|
||||
gridBagConstraints.gridy = 1;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHEAST;
|
||||
gridBagConstraints.weightx = 1.0;
|
||||
gridBagConstraints.weighty = 1.0;
|
||||
jPanel1.add(zoomInButton, gridBagConstraints);
|
||||
|
||||
zoomOutButton.setAction(getZoomInAction());
|
||||
zoomOutButton.setIcon(new javax.swing.ImageIcon(getClass().getResource(
|
||||
"/org/jdesktop/swingx/mapviewer/resources/minus.png")));
|
||||
zoomOutButton.setMargin(new java.awt.Insets(2, 2, 2, 2));
|
||||
zoomOutButton.setMaximumSize(new java.awt.Dimension(20, 20));
|
||||
zoomOutButton.setMinimumSize(new java.awt.Dimension(20, 20));
|
||||
zoomOutButton.setOpaque(false);
|
||||
zoomOutButton.setPreferredSize(new java.awt.Dimension(20, 20));
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 1;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
|
||||
gridBagConstraints.weighty = 1.0;
|
||||
jPanel1.add(zoomOutButton, gridBagConstraints);
|
||||
|
||||
zoomSlider.setMajorTickSpacing(1);
|
||||
zoomSlider.setMaximum(15);
|
||||
zoomSlider.setMinimum(10);
|
||||
zoomSlider.setMinorTickSpacing(1);
|
||||
zoomSlider.setOrientation(SwingConstants.VERTICAL);
|
||||
zoomSlider.setPaintTicks(true);
|
||||
zoomSlider.setSnapToTicks(true);
|
||||
zoomSlider.setMinimumSize(new java.awt.Dimension(35, 100));
|
||||
zoomSlider.setPreferredSize(new java.awt.Dimension(35, 190));
|
||||
zoomSlider.addChangeListener(new javax.swing.event.ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged(javax.swing.event.ChangeEvent evt)
|
||||
{
|
||||
zoomSliderStateChanged(evt);
|
||||
}
|
||||
});
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 0;
|
||||
gridBagConstraints.gridwidth = 2;
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.VERTICAL;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTH;
|
||||
jPanel1.add(zoomSlider, gridBagConstraints);
|
||||
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.gridx = 0;
|
||||
gridBagConstraints.gridy = 0;
|
||||
gridBagConstraints.anchor = java.awt.GridBagConstraints.SOUTHWEST;
|
||||
gridBagConstraints.weightx = 1.0;
|
||||
gridBagConstraints.weighty = 1.0;
|
||||
gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
|
||||
mainMap.add(jPanel1, gridBagConstraints);
|
||||
|
||||
gridBagConstraints = new java.awt.GridBagConstraints();
|
||||
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
|
||||
gridBagConstraints.weightx = 1.0;
|
||||
gridBagConstraints.weighty = 1.0;
|
||||
add(mainMap, gridBagConstraints);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void zoomInButtonActionPerformed(java.awt.event.ActionEvent evt)
|
||||
{// GEN-FIRST:event_zoomInButtonActionPerformed
|
||||
// TODO add your handling code here:
|
||||
}// GEN-LAST:event_zoomInButtonActionPerformed
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void zoomSliderStateChanged(javax.swing.event.ChangeEvent evt)
|
||||
{// GEN-FIRST:event_zoomSliderStateChanged
|
||||
if (!zoomChanging)
|
||||
{
|
||||
setZoom(zoomSlider.getValue());
|
||||
}
|
||||
// TODO add your handling code here:
|
||||
}// GEN-LAST:event_zoomSliderStateChanged
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JPanel jPanel1;
|
||||
private org.jdesktop.swingx.JXMapViewer mainMap;
|
||||
private org.jdesktop.swingx.JXMapViewer miniMap;
|
||||
private javax.swing.JButton zoomInButton;
|
||||
private javax.swing.JButton zoomOutButton;
|
||||
private javax.swing.JSlider zoomSlider;
|
||||
|
||||
// End of variables declaration//GEN-END:variables
|
||||
|
||||
/**
|
||||
* Indicates if the mini-map is currently visible
|
||||
* @return the current value of the mini-map property
|
||||
*/
|
||||
public boolean isMiniMapVisible()
|
||||
{
|
||||
return miniMapVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the mini-map should be visible
|
||||
* @param miniMapVisible a new value for the miniMap property
|
||||
*/
|
||||
public void setMiniMapVisible(boolean miniMapVisible)
|
||||
{
|
||||
boolean old = this.isMiniMapVisible();
|
||||
this.miniMapVisible = miniMapVisible;
|
||||
miniMap.setVisible(miniMapVisible);
|
||||
firePropertyChange("miniMapVisible", old, this.isMiniMapVisible());
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the zoom slider is currently visible
|
||||
* @return the current value of the zoomSliderVisible property
|
||||
*/
|
||||
public boolean isZoomSliderVisible()
|
||||
{
|
||||
return zoomSliderVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the zoom slider should be visible
|
||||
* @param zoomSliderVisible the new value of the zoomSliderVisible property
|
||||
*/
|
||||
public void setZoomSliderVisible(boolean zoomSliderVisible)
|
||||
{
|
||||
boolean old = this.isZoomSliderVisible();
|
||||
this.zoomSliderVisible = zoomSliderVisible;
|
||||
zoomSlider.setVisible(zoomSliderVisible);
|
||||
firePropertyChange("zoomSliderVisible", old, this.isZoomSliderVisible());
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the zoom buttons are visible. This is a bound property and can be listed for using a
|
||||
* PropertyChangeListener
|
||||
* @return current value of the zoomButtonsVisible property
|
||||
*/
|
||||
public boolean isZoomButtonsVisible()
|
||||
{
|
||||
return zoomButtonsVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the zoom buttons should be visible. This ia bound property. Changes can be listened for using a
|
||||
* PropertyChaneListener
|
||||
* @param zoomButtonsVisible new value of the zoomButtonsVisible property
|
||||
*/
|
||||
public void setZoomButtonsVisible(boolean zoomButtonsVisible)
|
||||
{
|
||||
boolean old = this.isZoomButtonsVisible();
|
||||
this.zoomButtonsVisible = zoomButtonsVisible;
|
||||
zoomInButton.setVisible(zoomButtonsVisible);
|
||||
zoomOutButton.setVisible(zoomButtonsVisible);
|
||||
firePropertyChange("zoomButtonsVisible", old, this.isZoomButtonsVisible());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the tile factory for both embedded JXMapViewer components. Calling this method will also reset the center
|
||||
* and zoom levels of both maps, as well as the bounds of the zoom slider.
|
||||
* @param fact the new TileFactory
|
||||
*/
|
||||
public void setTileFactory(TileFactory fact)
|
||||
{
|
||||
mainMap.setTileFactory(fact);
|
||||
mainMap.setZoom(fact.getInfo().getDefaultZoomLevel());
|
||||
mainMap.setCenterPosition(new GeoPosition(0, 0));
|
||||
miniMap.setTileFactory(fact);
|
||||
miniMap.setZoom(fact.getInfo().getDefaultZoomLevel() + 3);
|
||||
miniMap.setCenterPosition(new GeoPosition(0, 0));
|
||||
zoomSlider.setMinimum(fact.getInfo().getMinimumZoomLevel());
|
||||
zoomSlider.setMaximum(fact.getInfo().getMaximumZoomLevel());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pos the new center position
|
||||
*/
|
||||
public void setCenterPosition(GeoPosition pos)
|
||||
{
|
||||
mainMap.setCenterPosition(pos);
|
||||
miniMap.setCenterPosition(pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the center geo position
|
||||
*/
|
||||
public GeoPosition getCenterPosition()
|
||||
{
|
||||
return mainMap.getCenterPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the adress location
|
||||
*/
|
||||
public GeoPosition getAddressLocation()
|
||||
{
|
||||
return mainMap.getAddressLocation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pos the address location
|
||||
*/
|
||||
public void setAddressLocation(GeoPosition pos)
|
||||
{
|
||||
mainMap.setAddressLocation(pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the main embedded JXMapViewer component
|
||||
* @return the main map
|
||||
*/
|
||||
public JXMapViewer getMainMap()
|
||||
{
|
||||
return this.mainMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the mini embedded JXMapViewer component
|
||||
* @return the minimap JXMapViewer component
|
||||
*/
|
||||
public JXMapViewer getMiniMap()
|
||||
{
|
||||
return this.miniMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a reference to the zoom in button
|
||||
* @return a jbutton
|
||||
*/
|
||||
public JButton getZoomInButton()
|
||||
{
|
||||
return this.zoomInButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a reference to the zoom out button
|
||||
* @return a jbutton
|
||||
*/
|
||||
public JButton getZoomOutButton()
|
||||
{
|
||||
return this.zoomOutButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a reference to the zoom slider
|
||||
* @return a jslider
|
||||
*/
|
||||
public JSlider getZoomSlider()
|
||||
{
|
||||
return this.zoomSlider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param b the visibility flag
|
||||
*/
|
||||
public void setAddressLocationShown(boolean b)
|
||||
{
|
||||
boolean old = isAddressLocationShown();
|
||||
this.addressLocationShown = b;
|
||||
addressLocationPainter.setVisible(b);
|
||||
firePropertyChange("addressLocationShown", old, b);
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the address location is shown
|
||||
*/
|
||||
public boolean isAddressLocationShown()
|
||||
{
|
||||
return addressLocationShown;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param b the visibility flag
|
||||
*/
|
||||
public void setDataProviderCreditShown(boolean b)
|
||||
{
|
||||
boolean old = isDataProviderCreditShown();
|
||||
this.dataProviderCreditShown = b;
|
||||
dataProviderCreditPainter.setVisible(b);
|
||||
repaint();
|
||||
firePropertyChange("dataProviderCreditShown", old, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the data provider credit is shown
|
||||
*/
|
||||
public boolean isDataProviderCreditShown()
|
||||
{
|
||||
return dataProviderCreditShown;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void rebuildMainMapOverlay()
|
||||
{
|
||||
CompoundPainter<JXMapViewer> cp = new CompoundPainter<JXMapViewer>();
|
||||
cp.setCacheable(false);
|
||||
/*
|
||||
* List<Painter> ptrs = new ArrayList<Painter>(); if(isDataProviderCreditShown()) {
|
||||
* ptrs.add(dataProviderCreditPainter); } if(isAddressLocationShown()) { ptrs.add(addressLocationPainter); }
|
||||
*/
|
||||
cp.setPainters(dataProviderCreditPainter, addressLocationPainter);
|
||||
mainMap.setOverlayPainter(cp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param prov the default provider
|
||||
*/
|
||||
public void setDefaultProvider(DefaultProviders prov)
|
||||
{
|
||||
DefaultProviders old = this.defaultProvider;
|
||||
this.defaultProvider = prov;
|
||||
if (prov == DefaultProviders.SwingLabsBlueMarble)
|
||||
{
|
||||
setTileFactory(new CylindricalProjectionTileFactory());
|
||||
setZoom(3);
|
||||
}
|
||||
if (prov == DefaultProviders.OpenStreetMaps)
|
||||
{
|
||||
TileFactoryInfo info = new OSMTileFactoryInfo();
|
||||
TileFactory tf = new DefaultTileFactory(info);
|
||||
setTileFactory(tf);
|
||||
setZoom(11);
|
||||
setAddressLocation(new GeoPosition(51.5, 0));
|
||||
}
|
||||
firePropertyChange("defaultProvider", old, prov);
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the default provider
|
||||
*/
|
||||
public DefaultProviders getDefaultProvider()
|
||||
{
|
||||
return this.defaultProvider;
|
||||
}
|
||||
|
||||
private AbstractPainter<JXMapViewer> dataProviderCreditPainter = new AbstractPainter<JXMapViewer>(false)
|
||||
{
|
||||
@Override
|
||||
protected void doPaint(Graphics2D g, JXMapViewer map, int width, int height)
|
||||
{
|
||||
g.setPaint(Color.WHITE);
|
||||
g.drawString("data ", 50, map.getHeight() - 10);
|
||||
}
|
||||
};
|
||||
|
||||
private WaypointPainter<Waypoint> addressLocationPainter = new WaypointPainter<Waypoint>()
|
||||
{
|
||||
@Override
|
||||
public Set<Waypoint> getWaypoints()
|
||||
{
|
||||
Set<Waypoint> set = new HashSet<Waypoint>();
|
||||
if (getAddressLocation() != null)
|
||||
{
|
||||
set.add(new DefaultWaypoint(getAddressLocation()));
|
||||
}
|
||||
else
|
||||
{
|
||||
set.add(new DefaultWaypoint(0, 0));
|
||||
}
|
||||
return set;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param args the program args
|
||||
*/
|
||||
public static void main(String... args)
|
||||
{
|
||||
SwingUtilities.invokeLater(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
JXMapKit kit = new JXMapKit();
|
||||
kit.setDefaultProvider(DefaultProviders.OpenStreetMaps);
|
||||
|
||||
TileFactoryInfo info = new OSMTileFactoryInfo();
|
||||
TileFactory tf = new DefaultTileFactory(info);
|
||||
kit.setTileFactory(tf);
|
||||
kit.setZoom(14);
|
||||
kit.setAddressLocation(new GeoPosition(51.5, 0));
|
||||
kit.getMainMap().setDrawTileBorders(true);
|
||||
kit.getMainMap().setRestrictOutsidePanning(true);
|
||||
kit.getMainMap().setHorizontalWrapped(false);
|
||||
|
||||
((DefaultTileFactory) kit.getMainMap().getTileFactory()).setThreadPoolSize(8);
|
||||
JFrame frame = new JFrame("JXMapKit test");
|
||||
frame.add(kit);
|
||||
frame.pack();
|
||||
frame.setSize(500, 300);
|
||||
frame.setVisible(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
787
src/main/java/org/jdesktop/swingx/JXMapViewer.java
Normal file
787
src/main/java/org/jdesktop/swingx/JXMapViewer.java
Normal file
@@ -0,0 +1,787 @@
|
||||
/*
|
||||
* MapViewer.java
|
||||
*
|
||||
* Created on March 14, 2006, 2:14 PM
|
||||
*
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Image;
|
||||
import java.awt.Insets;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.beans.DesignMode;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JPanel;
|
||||
|
||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||
import org.jdesktop.swingx.mapviewer.Tile;
|
||||
import org.jdesktop.swingx.mapviewer.TileFactory;
|
||||
import org.jdesktop.swingx.mapviewer.TileFactoryInfo;
|
||||
import org.jdesktop.swingx.mapviewer.TileListener;
|
||||
import org.jdesktop.swingx.mapviewer.empty.EmptyTileFactory;
|
||||
import org.jdesktop.swingx.painter.AbstractPainter;
|
||||
import org.jdesktop.swingx.painter.Painter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A tile oriented map component that can easily be used with tile sources
|
||||
* on the web like Google and Yahoo maps, satellite data such as NASA imagery,
|
||||
* and also with file based sources like pre-processed NASA images.
|
||||
* A known map provider can be used with the SLMapServerInfo,
|
||||
* which will connect to a 2km resolution version of NASA's Blue Marble Next Generation
|
||||
* imagery. @see SLMapServerInfo for more information.
|
||||
*
|
||||
* Note, the JXMapViewer has three center point properties. The <B>addressLocation</B> property
|
||||
* represents an abstract center of the map. This would usually be something like the first item
|
||||
* in a search result. It is a {@link GeoPosition}. The <b>centerPosition</b> property represents
|
||||
* the current center point of the map. If the user pans the map then the centerPosition point will
|
||||
* change but the <B>addressLocation</B> will not. Calling <B>recenterToAddressLocation()</B> will move the map
|
||||
* back to that center address. The <B>center</B> property represents the same point as the centerPosition
|
||||
* property, but as a Point2D in pixel space instead of a GeoPosition in lat/long space. Note that
|
||||
* the center property is a Point2D in the entire world bitmap, not in the portion of the map currently
|
||||
* visible. You can use the <B>getViewportBounds()</B> method to find the portion of the map currently visible
|
||||
* and adjust your calculations accordingly. Changing the <B>center</B> property will change the <B>centerPosition</B>
|
||||
* property and vice versa. All three properties are bound.
|
||||
* @author Joshua.Marinacci@sun.com
|
||||
* @see org.jdesktop.swingx.mapviewer.bmng.SLMapServerInfo
|
||||
*/
|
||||
public class JXMapViewer extends JPanel implements DesignMode
|
||||
{
|
||||
private static final long serialVersionUID = -3530746298586937321L;
|
||||
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( JXMapViewer.class );
|
||||
|
||||
private final boolean isNegativeYAllowed = true; // maybe rename to isNorthBounded and isSouthBounded?
|
||||
|
||||
/**
|
||||
* The zoom level. Generally a value between 1 and 15 (TODO Is this true for all the mapping worlds? What does this
|
||||
* mean if some mapping system doesn't support the zoom level?
|
||||
*/
|
||||
private int zoomLevel = 1;
|
||||
|
||||
/**
|
||||
* The position, in <I>map coordinates</I> of the center point. This is defined as the distance from the top and
|
||||
* left edges of the map in pixels. Dragging the map component will change the center position. Zooming in/out will
|
||||
* cause the center to be recalculated so as to remain in the center of the new "map".
|
||||
*/
|
||||
private Point2D center = new Point2D.Double(0, 0);
|
||||
|
||||
/**
|
||||
* Indicates whether or not to draw the borders between tiles. Defaults to false. TODO Generally not very nice
|
||||
* looking, very much a product of testing Consider whether this should really be a property or not.
|
||||
*/
|
||||
private boolean drawTileBorders = false;
|
||||
|
||||
/**
|
||||
* Factory used by this component to grab the tiles necessary for painting the map.
|
||||
*/
|
||||
private TileFactory factory;
|
||||
|
||||
/**
|
||||
* The position in latitude/longitude of the "address" being mapped. This is a special coordinate that, when moved,
|
||||
* will cause the map to be moved as well. It is separate from "center" in that "center" tracks the current center
|
||||
* (in pixels) of the viewport whereas this will not change when panning or zooming. Whenever the addressLocation is
|
||||
* changed, however, the map will be repositioned.
|
||||
*/
|
||||
private GeoPosition addressLocation;
|
||||
|
||||
/**
|
||||
* The overlay to delegate to for painting the "foreground" of the map component. This would include painting
|
||||
* waypoints, day/night, etc. Also receives mouse events.
|
||||
*/
|
||||
private Painter<? super JXMapViewer> overlay;
|
||||
|
||||
private boolean designTime;
|
||||
|
||||
private Image loadingImage;
|
||||
|
||||
private boolean restrictOutsidePanning = true;
|
||||
private boolean horizontalWrapped = true;
|
||||
|
||||
/**
|
||||
* Create a new JXMapViewer. By default it will use the EmptyTileFactory
|
||||
*/
|
||||
public JXMapViewer()
|
||||
{
|
||||
factory = new EmptyTileFactory();
|
||||
// setTileFactory(new GoogleTileFactory());
|
||||
|
||||
// make a dummy loading image
|
||||
try
|
||||
{
|
||||
ImageIcon imageIcon = new ImageIcon( "images/loading.png" );
|
||||
this.setLoadingImage( imageIcon.getImage() );
|
||||
}
|
||||
catch (Throwable ex)
|
||||
{
|
||||
|
||||
mLog.error( "JXMapViewer could not load 'loading.png'" );
|
||||
|
||||
BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g2 = img.createGraphics();
|
||||
g2.setColor(Color.black);
|
||||
g2.fillRect(0, 0, 16, 16);
|
||||
g2.dispose();
|
||||
this.setLoadingImage(img);
|
||||
}
|
||||
|
||||
// setAddressLocation(new GeoPosition(37.392137,-121.950431)); // Sun campus
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g)
|
||||
{
|
||||
super.paintComponent(g);
|
||||
|
||||
doPaintComponent(g);
|
||||
}
|
||||
|
||||
// the method that does the actual painting
|
||||
private void doPaintComponent(Graphics g)
|
||||
{/*
|
||||
* if (isOpaque() || isDesignTime()) { g.setColor(getBackground()); g.fillRect(0,0,getWidth(),getHeight()); }
|
||||
*/
|
||||
|
||||
if (isDesignTime())
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
else
|
||||
{
|
||||
int z = getZoom();
|
||||
Rectangle viewportBounds = getViewportBounds();
|
||||
drawMapTiles(g, z, viewportBounds);
|
||||
drawOverlays(z, g, viewportBounds);
|
||||
}
|
||||
|
||||
super.paintBorder(g);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate that the component is being used at design time, such as in a visual editor like NetBeans' Matisse
|
||||
* @param b indicates if the component is being used at design time
|
||||
*/
|
||||
@Override
|
||||
public void setDesignTime(boolean b)
|
||||
{
|
||||
this.designTime = b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the component is being used at design time, such as in a visual editor like NetBeans' Matisse
|
||||
* @return boolean indicating if the component is being used at design time
|
||||
*/
|
||||
@Override
|
||||
public boolean isDesignTime()
|
||||
{
|
||||
return designTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the map tiles. This method is for implementation use only.
|
||||
* @param g Graphics
|
||||
* @param zoom zoom level to draw at
|
||||
* @param viewportBounds the bounds to draw within
|
||||
*/
|
||||
protected void drawMapTiles(final Graphics g, final int zoom, Rectangle viewportBounds)
|
||||
{
|
||||
int size = getTileFactory().getTileSize(zoom);
|
||||
Dimension mapSize = getTileFactory().getMapSize(zoom);
|
||||
|
||||
// calculate the "visible" viewport area in tiles
|
||||
int numWide = viewportBounds.width / size + 2;
|
||||
int numHigh = viewportBounds.height / size + 2;
|
||||
|
||||
// TilePoint topLeftTile = getTileFactory().getTileCoordinate(
|
||||
// new Point2D.Double(viewportBounds.x, viewportBounds.y));
|
||||
TileFactoryInfo info = getTileFactory().getInfo();
|
||||
int tpx = (int) Math.floor(viewportBounds.getX() / info.getTileSize(0));
|
||||
int tpy = (int) Math.floor(viewportBounds.getY() / info.getTileSize(0));
|
||||
// TilePoint topLeftTile = new TilePoint(tpx, tpy);
|
||||
|
||||
// p("top tile = " + topLeftTile);
|
||||
// fetch the tiles from the factory and store them in the tiles cache
|
||||
// attach the tileLoadListener
|
||||
for (int x = 0; x <= numWide; x++)
|
||||
{
|
||||
for (int y = 0; y <= numHigh; y++)
|
||||
{
|
||||
int itpx = x + tpx;// topLeftTile.getX();
|
||||
int itpy = y + tpy;// topLeftTile.getY();
|
||||
// TilePoint point = new TilePoint(x + topLeftTile.getX(), y + topLeftTile.getY());
|
||||
// only proceed if the specified tile point lies within the area being painted
|
||||
if (g.getClipBounds().intersects(
|
||||
new Rectangle(itpx * size - viewportBounds.x, itpy * size - viewportBounds.y, size, size)))
|
||||
{
|
||||
Tile tile = getTileFactory().getTile(itpx, itpy, zoom);
|
||||
int ox = ((itpx * getTileFactory().getTileSize(zoom)) - viewportBounds.x);
|
||||
int oy = ((itpy * getTileFactory().getTileSize(zoom)) - viewportBounds.y);
|
||||
|
||||
// if the tile is off the map to the north/south, then just don't paint anything
|
||||
if (isTileOnMap(itpx, itpy, mapSize))
|
||||
{
|
||||
if (isOpaque())
|
||||
{
|
||||
g.setColor(getBackground());
|
||||
g.fillRect(ox, oy, size, size);
|
||||
}
|
||||
}
|
||||
else if (tile.isLoaded())
|
||||
{
|
||||
g.drawImage(tile.getImage(), ox, oy, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use tile at higher zoom level with 200% magnification
|
||||
Tile superTile = getTileFactory().getTile(itpx / 2, itpy / 2, zoom + 1);
|
||||
|
||||
if (superTile.isLoaded())
|
||||
{
|
||||
int offX = (itpx % 2) * size / 2;
|
||||
int offY = (itpy % 2) * size / 2;
|
||||
g.drawImage(superTile.getImage(), ox, oy, ox + size, oy + size, offX, offY, offX + size / 2, offY + size / 2, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
int imageX = (getTileFactory().getTileSize(zoom) - getLoadingImage().getWidth(null)) / 2;
|
||||
int imageY = (getTileFactory().getTileSize(zoom) - getLoadingImage().getHeight(null)) / 2;
|
||||
g.setColor(Color.GRAY);
|
||||
g.fillRect(ox, oy, size, size);
|
||||
g.drawImage(getLoadingImage(), ox + imageX, oy + imageY, null);
|
||||
}
|
||||
}
|
||||
if (isDrawTileBorders())
|
||||
{
|
||||
|
||||
g.setColor(Color.black);
|
||||
g.drawRect(ox, oy, size, size);
|
||||
g.drawRect(ox + size / 2 - 5, oy + size / 2 - 5, 10, 10);
|
||||
g.setColor(Color.white);
|
||||
g.drawRect(ox + 1, oy + 1, size, size);
|
||||
|
||||
String text = itpx + ", " + itpy + ", " + getZoom();
|
||||
g.setColor(Color.BLACK);
|
||||
g.drawString(text, ox + 10, oy + 30);
|
||||
g.drawString(text, ox + 10 + 2, oy + 30 + 2);
|
||||
g.setColor(Color.WHITE);
|
||||
g.drawString(text, ox + 10 + 1, oy + 30 + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void drawOverlays(final int zoom, final Graphics g, final Rectangle viewportBounds)
|
||||
{
|
||||
if (overlay != null)
|
||||
{
|
||||
overlay.paint((Graphics2D) g, this, getWidth(), getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private boolean isTileOnMap(int x, int y, Dimension mapSize)
|
||||
{
|
||||
return !isNegativeYAllowed && y < 0 || y >= mapSize.getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the map overlay. This is a Painter<JXMapViewer> which will paint on top of the map. It can be used to draw waypoints,
|
||||
* lines, or static overlays like text messages.
|
||||
* @param overlay the map overlay to use
|
||||
*/
|
||||
public void setOverlayPainter(Painter<? super JXMapViewer> overlay)
|
||||
{
|
||||
Painter<? super JXMapViewer> old = getOverlayPainter();
|
||||
this.overlay = overlay;
|
||||
|
||||
PropertyChangeListener listener = new PropertyChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt)
|
||||
{
|
||||
if (evt.getNewValue().equals(Boolean.TRUE))
|
||||
{
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (old instanceof AbstractPainter)
|
||||
{
|
||||
AbstractPainter<?> ap = (AbstractPainter<?>) overlay;
|
||||
ap.removePropertyChangeListener("dirty", listener);
|
||||
}
|
||||
|
||||
if (overlay instanceof AbstractPainter)
|
||||
{
|
||||
AbstractPainter<?> ap = (AbstractPainter<?>) overlay;
|
||||
ap.addPropertyChangeListener("dirty", listener);
|
||||
}
|
||||
|
||||
firePropertyChange("mapOverlay", old, getOverlayPainter());
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current map overlay
|
||||
* @return the current map overlay
|
||||
*/
|
||||
public Painter<? super JXMapViewer> getOverlayPainter()
|
||||
{
|
||||
return overlay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bounds of the viewport in pixels. This can be used to transform points into the world bitmap
|
||||
* coordinate space.
|
||||
* @return the bounds in <em>pixels</em> of the "view" of this map
|
||||
*/
|
||||
public Rectangle getViewportBounds()
|
||||
{
|
||||
return calculateViewportBounds(getCenter());
|
||||
}
|
||||
|
||||
private Rectangle calculateViewportBounds(Point2D centr)
|
||||
{
|
||||
Insets insets = getInsets();
|
||||
// calculate the "visible" viewport area in pixels
|
||||
int viewportWidth = getWidth() - insets.left - insets.right;
|
||||
int viewportHeight = getHeight() - insets.top - insets.bottom;
|
||||
double viewportX = (centr.getX() - viewportWidth / 2);
|
||||
double viewportY = (centr.getY() - viewportHeight / 2);
|
||||
return new Rectangle((int) viewportX, (int) viewportY, viewportWidth, viewportHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current zoom level
|
||||
* @param zoom the new zoom level
|
||||
*/
|
||||
public void setZoom(int zoom)
|
||||
{
|
||||
if (zoom == this.zoomLevel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TileFactoryInfo info = getTileFactory().getInfo();
|
||||
// don't repaint if we are out of the valid zoom levels
|
||||
if (info != null && (zoom < info.getMinimumZoomLevel() || zoom > info.getMaximumZoomLevel()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// if(zoom >= 0 && zoom <= 15 && zoom != this.zoom) {
|
||||
int oldzoom = this.zoomLevel;
|
||||
Point2D oldCenter = getCenter();
|
||||
Dimension oldMapSize = getTileFactory().getMapSize(oldzoom);
|
||||
this.zoomLevel = zoom;
|
||||
this.firePropertyChange("zoom", oldzoom, zoom);
|
||||
|
||||
Dimension mapSize = getTileFactory().getMapSize(zoom);
|
||||
|
||||
setCenter(new Point2D.Double(oldCenter.getX() * (mapSize.getWidth() / oldMapSize.getWidth()), oldCenter.getY()
|
||||
* (mapSize.getHeight() / oldMapSize.getHeight())));
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current zoom level
|
||||
* @return the current zoom level
|
||||
*/
|
||||
public int getZoom()
|
||||
{
|
||||
return this.zoomLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current address location of the map. This property does not change when the user pans the map. This
|
||||
* property is bound.
|
||||
* @return the current map location (address)
|
||||
*/
|
||||
public GeoPosition getAddressLocation()
|
||||
{
|
||||
return addressLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current address location of the map
|
||||
* @param addressLocation the new address location
|
||||
*/
|
||||
public void setAddressLocation(GeoPosition addressLocation)
|
||||
{
|
||||
GeoPosition old = getAddressLocation();
|
||||
this.addressLocation = addressLocation;
|
||||
setCenter(getTileFactory().geoToPixel(addressLocation, getZoom()));
|
||||
|
||||
firePropertyChange("addressLocation", old, getAddressLocation());
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-centers the map to have the current address location be at the center of the map, accounting for the map's
|
||||
* width and height.
|
||||
*/
|
||||
public void recenterToAddressLocation()
|
||||
{
|
||||
setCenter(getTileFactory().geoToPixel(getAddressLocation(), getZoom()));
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the tile borders should be drawn. Mainly used for debugging.
|
||||
* @return the value of this property
|
||||
*/
|
||||
public boolean isDrawTileBorders()
|
||||
{
|
||||
return drawTileBorders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if the tile borders should be drawn. Mainly used for debugging.
|
||||
* @param drawTileBorders new value of this drawTileBorders
|
||||
*/
|
||||
public void setDrawTileBorders(boolean drawTileBorders)
|
||||
{
|
||||
boolean old = isDrawTileBorders();
|
||||
this.drawTileBorders = drawTileBorders;
|
||||
firePropertyChange("drawTileBorders", old, isDrawTileBorders());
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* A property indicating the center position of the map
|
||||
* @param geoPosition the new property value
|
||||
*/
|
||||
public void setCenterPosition(GeoPosition geoPosition)
|
||||
{
|
||||
GeoPosition oldVal = getCenterPosition();
|
||||
setCenter(getTileFactory().geoToPixel(geoPosition, zoomLevel));
|
||||
repaint();
|
||||
GeoPosition newVal = getCenterPosition();
|
||||
firePropertyChange("centerPosition", oldVal, newVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* A property indicating the center position of the map
|
||||
* @return the current center position
|
||||
*/
|
||||
public GeoPosition getCenterPosition()
|
||||
{
|
||||
return getTileFactory().pixelToGeo(getCenter(), zoomLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current factory
|
||||
* @return the current property value
|
||||
*/
|
||||
public TileFactory getTileFactory()
|
||||
{
|
||||
return factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current tile factory (must not be <code>null</code>)
|
||||
* @param factory the new property value
|
||||
*/
|
||||
public void setTileFactory(TileFactory factory)
|
||||
{
|
||||
if (factory == null)
|
||||
throw new NullPointerException("factory must not be null");
|
||||
|
||||
this.factory.removeTileListener(tileLoadListener);
|
||||
this.factory.dispose();
|
||||
|
||||
this.factory = factory;
|
||||
this.setZoom(factory.getInfo().getDefaultZoomLevel());
|
||||
|
||||
factory.addTileListener(tileLoadListener);
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* A property for an image which will be display when an image is still loading.
|
||||
* @return the current property value
|
||||
*/
|
||||
public Image getLoadingImage()
|
||||
{
|
||||
return loadingImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* A property for an image which will be display when an image is still loading.
|
||||
* @param loadingImage the new property value
|
||||
*/
|
||||
public void setLoadingImage(Image loadingImage)
|
||||
{
|
||||
this.loadingImage = loadingImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current pixel center of the map. This point is in the global bitmap coordinate system, not as lat/longs.
|
||||
* @return the current center of the map as a pixel value
|
||||
*/
|
||||
public Point2D getCenter()
|
||||
{
|
||||
return center;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the new center of the map in pixel coordinates.
|
||||
* @param center the new center of the map in pixel coordinates
|
||||
*/
|
||||
public void setCenter(Point2D center)
|
||||
{
|
||||
Point2D old = this.getCenter();
|
||||
|
||||
double centerX = center.getX();
|
||||
double centerY = center.getY();
|
||||
|
||||
Dimension mapSize = getTileFactory().getMapSize(getZoom());
|
||||
int mapHeight = (int) mapSize.getHeight() * getTileFactory().getTileSize(getZoom());
|
||||
int mapWidth = (int) mapSize.getWidth() * getTileFactory().getTileSize(getZoom());
|
||||
|
||||
if (isRestrictOutsidePanning())
|
||||
{
|
||||
Insets insets = getInsets();
|
||||
int viewportHeight = getHeight() - insets.top - insets.bottom;
|
||||
int viewportWidth = getWidth() - insets.left - insets.right;
|
||||
|
||||
// don't let the user pan over the top edge
|
||||
Rectangle newVP = calculateViewportBounds(center);
|
||||
if (newVP.getY() < 0)
|
||||
{
|
||||
centerY = viewportHeight / 2;
|
||||
}
|
||||
|
||||
// don't let the user pan over the left edge
|
||||
if (!isHorizontalWrapped() && newVP.getX() < 0)
|
||||
{
|
||||
centerX = viewportWidth / 2;
|
||||
}
|
||||
|
||||
// don't let the user pan over the bottom edge
|
||||
if (newVP.getY() + newVP.getHeight() > mapHeight)
|
||||
{
|
||||
centerY = mapHeight - viewportHeight / 2;
|
||||
}
|
||||
|
||||
// don't let the user pan over the right edge
|
||||
if (!isHorizontalWrapped() && (newVP.getX() + newVP.getWidth() > mapWidth))
|
||||
{
|
||||
centerX = mapWidth - viewportWidth / 2;
|
||||
}
|
||||
|
||||
// if map is to small then just center it vert
|
||||
if (mapHeight < newVP.getHeight())
|
||||
{
|
||||
centerY = mapHeight / 2;// viewportHeight/2;// - mapHeight/2;
|
||||
}
|
||||
|
||||
// if map is too small then just center it horiz
|
||||
if (!isHorizontalWrapped() && mapWidth < newVP.getWidth())
|
||||
{
|
||||
centerX = mapWidth / 2;
|
||||
}
|
||||
}
|
||||
|
||||
// If center is outside (0, 0,mapWidth, mapHeight)
|
||||
// compute modulo to get it back in.
|
||||
{
|
||||
centerX = centerX % mapWidth;
|
||||
centerY = centerY % mapHeight;
|
||||
|
||||
if (centerX < 0)
|
||||
centerX += mapWidth;
|
||||
|
||||
if (centerY < 0)
|
||||
centerY += mapHeight;
|
||||
}
|
||||
|
||||
GeoPosition oldGP = this.getCenterPosition();
|
||||
this.center = new Point2D.Double(centerX, centerY);
|
||||
firePropertyChange("center", old, this.center);
|
||||
firePropertyChange("centerPosition", oldGP, this.getCenterPosition());
|
||||
repaint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a zoom level so that all points in the specified set will be visible on screen. This is useful if you
|
||||
* have a bunch of points in an area like a city and you want to zoom out so that the entire city and it's points
|
||||
* are visible without panning.
|
||||
* @param positions A set of GeoPositions to calculate the new zoom from
|
||||
*/
|
||||
public void calculateZoomFrom(Set<GeoPosition> positions)
|
||||
{
|
||||
// u.p("calculating a zoom based on: ");
|
||||
// u.p(positions);
|
||||
if (positions.size() < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int zoom = getZoom();
|
||||
Rectangle2D rect = generateBoundingRect(positions, zoom);
|
||||
// Rectangle2D viewport = map.getViewportBounds();
|
||||
int count = 0;
|
||||
while (!getViewportBounds().contains(rect))
|
||||
{
|
||||
// u.p("not contained");
|
||||
Point2D centr = new Point2D.Double(rect.getX() + rect.getWidth() / 2, rect.getY() + rect.getHeight() / 2);
|
||||
GeoPosition px = getTileFactory().pixelToGeo(centr, zoom);
|
||||
// u.p("new geo = " + px);
|
||||
setCenterPosition(px);
|
||||
count++;
|
||||
if (count > 30)
|
||||
break;
|
||||
|
||||
if (getViewportBounds().contains(rect))
|
||||
{
|
||||
// u.p("did it finally");
|
||||
break;
|
||||
}
|
||||
zoom = zoom + 1;
|
||||
if (zoom > 15)
|
||||
{
|
||||
break;
|
||||
}
|
||||
setZoom(zoom);
|
||||
rect = generateBoundingRect(positions, zoom);
|
||||
}
|
||||
}
|
||||
|
||||
private Rectangle2D generateBoundingRect(final Set<GeoPosition> positions, int zoom)
|
||||
{
|
||||
Point2D point1 = getTileFactory().geoToPixel(positions.iterator().next(), zoom);
|
||||
Rectangle2D rect = new Rectangle2D.Double(point1.getX(), point1.getY(), 0, 0);
|
||||
|
||||
for (GeoPosition pos : positions)
|
||||
{
|
||||
Point2D point = getTileFactory().geoToPixel(pos, zoom);
|
||||
rect.add(point);
|
||||
}
|
||||
return rect;
|
||||
}
|
||||
|
||||
// a property change listener which forces repaints when tiles finish loading
|
||||
private TileListener tileLoadListener = new TileListener()
|
||||
{
|
||||
@Override
|
||||
public void tileLoaded(Tile tile)
|
||||
{
|
||||
if (tile.getZoom() == getZoom())
|
||||
{
|
||||
repaint();
|
||||
/* this optimization doesn't save much and it doesn't work if you
|
||||
* wrap around the world
|
||||
Rectangle viewportBounds = getViewportBounds();
|
||||
TilePoint tilePoint = t.getLocation();
|
||||
Point point = new Point(tilePoint.getX() * getTileFactory().getTileSize(), tilePoint.getY() * getTileFactory().getTileSize());
|
||||
Rectangle tileRect = new Rectangle(point, new Dimension(getTileFactory().getTileSize(), getTileFactory().getTileSize()));
|
||||
if (viewportBounds.intersects(tileRect)) {
|
||||
//convert tileRect from world space to viewport space
|
||||
repaint(new Rectangle(
|
||||
tileRect.x - viewportBounds.x,
|
||||
tileRect.y - viewportBounds.y,
|
||||
tileRect.width,
|
||||
tileRect.height
|
||||
));
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @return true if panning is restricted or not
|
||||
*/
|
||||
public boolean isRestrictOutsidePanning()
|
||||
{
|
||||
return restrictOutsidePanning;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param restrictOutsidePanning set if panning is restricted or not
|
||||
*/
|
||||
public void setRestrictOutsidePanning(boolean restrictOutsidePanning)
|
||||
{
|
||||
this.restrictOutsidePanning = restrictOutsidePanning;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if horizontally wrapped or not
|
||||
*/
|
||||
public boolean isHorizontalWrapped()
|
||||
{
|
||||
return horizontalWrapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param horizontalWrapped true if horizontal wrap is enabled
|
||||
*/
|
||||
public void setHorizontalWrapped(boolean horizontalWrapped)
|
||||
{
|
||||
this.horizontalWrapped = horizontalWrapped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the specified GeoPosition to a point in the JXMapViewer's local coordinate space. This method is
|
||||
* especially useful when drawing lat/long positions on the map.
|
||||
* @param pos a GeoPosition on the map
|
||||
* @return the point in the local coordinate space of the map
|
||||
*/
|
||||
public Point2D convertGeoPositionToPoint(GeoPosition pos)
|
||||
{
|
||||
// convert from geo to world bitmap
|
||||
Point2D pt = getTileFactory().geoToPixel(pos, getZoom());
|
||||
// convert from world bitmap to local
|
||||
Rectangle bounds = getViewportBounds();
|
||||
return new Point2D.Double(pt.getX() - bounds.getX(), pt.getY() - bounds.getY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the specified Point2D in the JXMapViewer's local coordinate space to a GeoPosition on the map. This
|
||||
* method is especially useful for determining the GeoPosition under the mouse cursor.
|
||||
* @param pt a point in the local coordinate space of the map
|
||||
* @return the point converted to a GeoPosition
|
||||
*/
|
||||
public GeoPosition convertPointToGeoPosition(Point2D pt)
|
||||
{
|
||||
// convert from local to world bitmap
|
||||
Rectangle bounds = getViewportBounds();
|
||||
Point2D pt2 = new Point2D.Double(pt.getX() + bounds.getX(), pt.getY() + bounds.getY());
|
||||
|
||||
// convert from world bitmap to geo
|
||||
GeoPosition pos = getTileFactory().pixelToGeo(pt2, getZoom());
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @return isNegativeYAllowed
|
||||
*/
|
||||
public boolean isNegativeYAllowed()
|
||||
{
|
||||
return isNegativeYAllowed;
|
||||
}
|
||||
}
|
||||
59
src/main/java/org/jdesktop/swingx/OSMTileFactoryInfo.java
Normal file
59
src/main/java/org/jdesktop/swingx/OSMTileFactoryInfo.java
Normal file
@@ -0,0 +1,59 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
// Fraunhofer Institute for Computer Graphics Research (IGD)
|
||||
// Department Information Visualization and Visual Analytics
|
||||
//
|
||||
// Copyright (c) Fraunhofer IGD. All rights reserved.
|
||||
//
|
||||
// This source code is property of the Fraunhofer IGD and underlies
|
||||
// copyright restrictions. It may only be used with explicit
|
||||
// permission from the respective owner.
|
||||
|
||||
package org.jdesktop.swingx;
|
||||
|
||||
import org.jdesktop.swingx.mapviewer.TileFactoryInfo;
|
||||
|
||||
/**
|
||||
* Uses OpenStreetMap
|
||||
* @author Martin Dummer
|
||||
*/
|
||||
public class OSMTileFactoryInfo extends TileFactoryInfo
|
||||
{
|
||||
private static final int max = 19;
|
||||
|
||||
/**
|
||||
* Default constructor
|
||||
*/
|
||||
public OSMTileFactoryInfo()
|
||||
{
|
||||
super("OpenStreetMap",
|
||||
1, max - 2, max,
|
||||
256, true, true, // tile size is 256 and x/y orientation is normal
|
||||
"http://tile.openstreetmap.org",
|
||||
"x", "y", "z"); // 5/15/10.png
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTileUrl(int x, int y, int zoom)
|
||||
{
|
||||
zoom = max - zoom;
|
||||
String url = this.baseURL + "/" + zoom + "/" + x + "/" + y + ".png";
|
||||
return url;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package org.jdesktop.swingx;
|
||||
|
||||
import org.jdesktop.swingx.mapviewer.TileFactoryInfo;
|
||||
|
||||
/*******************************************************************************
|
||||
* http://www.viavirtualearth.com/vve/Articles/RollYourOwnTileServer.ashx
|
||||
* @author Fabrizio Giudici
|
||||
* @version $Id: MicrosoftVirtualEarthProvider.java 115 2007-11-08 22:04:36Z
|
||||
* fabriziogiudici $
|
||||
********************************************************************************/
|
||||
public class VirtualEarthTileFactoryInfo extends TileFactoryInfo
|
||||
{
|
||||
/**
|
||||
* Use road map
|
||||
*/
|
||||
public final static MVEMode MAP = new MVEMode("map", "map", "r", ".png");
|
||||
|
||||
/**
|
||||
* Use satellite map
|
||||
*/
|
||||
public final static MVEMode SATELLITE = new MVEMode("satellite", "satellite", "a", ".jpeg");
|
||||
|
||||
/**
|
||||
* Use hybrid map
|
||||
*/
|
||||
public final static MVEMode HYBRID = new MVEMode("hybrid", "hybrid", "h", ".jpeg");
|
||||
|
||||
/**
|
||||
* The map mode
|
||||
*/
|
||||
public static class MVEMode
|
||||
{
|
||||
private String type;
|
||||
private String ext;
|
||||
private String name;
|
||||
private String label;
|
||||
|
||||
private MVEMode(final String name, final String label, final String type, final String ext)
|
||||
{
|
||||
this.type = type;
|
||||
this.ext = ext;
|
||||
this.name = name;
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
|
||||
private final static int TOP_ZOOM_LEVEL = 19;
|
||||
|
||||
private final static int MAX_ZOOM_LEVEL = 17;
|
||||
|
||||
private final static int MIN_ZOOM_LEVEL = 2;
|
||||
|
||||
private final static int TILE_SIZE = 256;
|
||||
|
||||
private MVEMode mode;
|
||||
|
||||
/**
|
||||
* @param mode the mode
|
||||
*/
|
||||
public VirtualEarthTileFactoryInfo(MVEMode mode)
|
||||
{
|
||||
super("Virtual Earth", MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL, TOP_ZOOM_LEVEL, TILE_SIZE, false, false, "", "", "", "");
|
||||
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name of the selected mode
|
||||
*/
|
||||
public String getModeName()
|
||||
{
|
||||
return mode.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the label of the selected mode
|
||||
*/
|
||||
public String getModeLabel()
|
||||
{
|
||||
return mode.label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTileUrl(final int x, final int y, final int zoom)
|
||||
{
|
||||
final String quad = tileToQuadKey(x, y, TOP_ZOOM_LEVEL - 0 - zoom);
|
||||
return "http://" + mode.type + quad.charAt(quad.length() - 1) +
|
||||
".ortho.tiles.virtualearth.net/tiles/"
|
||||
+ mode.type + quad + mode.ext + "?g=1";
|
||||
}
|
||||
|
||||
private String tileToQuadKey(final int tx, final int ty, final int zl)
|
||||
{
|
||||
String quad = "";
|
||||
|
||||
for (int i = zl; i > 0; i--)
|
||||
{
|
||||
int mask = 1 << (i - 1);
|
||||
int cell = 0;
|
||||
|
||||
if ((tx & mask) != 0)
|
||||
{
|
||||
cell++;
|
||||
}
|
||||
|
||||
if ((ty & mask) != 0)
|
||||
{
|
||||
cell += 2;
|
||||
}
|
||||
|
||||
quad += cell;
|
||||
}
|
||||
|
||||
return quad;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package org.jdesktop.swingx.input;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import org.jdesktop.swingx.JXMapViewer;
|
||||
|
||||
/**
|
||||
* Centers the map on the mouse cursor
|
||||
* if left is double-clicked or middle mouse
|
||||
* button is pressed.
|
||||
* @author Martin Steiger
|
||||
* @author joshy
|
||||
*/
|
||||
public class CenterMapListener extends MouseAdapter
|
||||
{
|
||||
private JXMapViewer viewer;
|
||||
|
||||
/**
|
||||
* @param viewer the jxmapviewer
|
||||
*/
|
||||
public CenterMapListener(JXMapViewer viewer)
|
||||
{
|
||||
this.viewer = viewer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent evt)
|
||||
{
|
||||
boolean left = SwingUtilities.isLeftMouseButton(evt);
|
||||
boolean middle = SwingUtilities.isMiddleMouseButton(evt);
|
||||
boolean doubleClick = (evt.getClickCount() == 2);
|
||||
|
||||
if (middle || (left && doubleClick))
|
||||
{
|
||||
recenterMap(evt);
|
||||
}
|
||||
}
|
||||
|
||||
private void recenterMap(MouseEvent evt)
|
||||
{
|
||||
Rectangle bounds = viewer.getViewportBounds();
|
||||
double x = bounds.getX() + evt.getX();
|
||||
double y = bounds.getY() + evt.getY();
|
||||
viewer.setCenter(new Point2D.Double(x, y));
|
||||
viewer.setZoom(viewer.getZoom() - 1);
|
||||
viewer.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
97
src/main/java/org/jdesktop/swingx/input/PanKeyListener.java
Normal file
97
src/main/java/org/jdesktop/swingx/input/PanKeyListener.java
Normal file
@@ -0,0 +1,97 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package org.jdesktop.swingx.input;
|
||||
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
import org.jdesktop.swingx.JXMapViewer;
|
||||
|
||||
/**
|
||||
* used to pan using the arrow keys
|
||||
* @author joshy
|
||||
*/
|
||||
public class PanKeyListener extends KeyAdapter
|
||||
{
|
||||
private static final int OFFSET = 10;
|
||||
|
||||
private JXMapViewer viewer;
|
||||
|
||||
/**
|
||||
* @param viewer the jxmapviewer
|
||||
*/
|
||||
public PanKeyListener(JXMapViewer viewer)
|
||||
{
|
||||
this.viewer = viewer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e)
|
||||
{
|
||||
int delta_x = 0;
|
||||
int delta_y = 0;
|
||||
int requestedZoom = 0;
|
||||
|
||||
switch ( e.getKeyCode() )
|
||||
{
|
||||
|
||||
case KeyEvent.VK_LEFT:
|
||||
case KeyEvent.VK_NUMPAD4:
|
||||
delta_x = -OFFSET;
|
||||
break;
|
||||
case KeyEvent.VK_RIGHT:
|
||||
case KeyEvent.VK_NUMPAD6:
|
||||
delta_x = OFFSET;
|
||||
break;
|
||||
case KeyEvent.VK_UP:
|
||||
case KeyEvent.VK_NUMPAD8:
|
||||
delta_y = -OFFSET;
|
||||
break;
|
||||
case KeyEvent.VK_DOWN:
|
||||
case KeyEvent.VK_NUMPAD2:
|
||||
delta_y = OFFSET;
|
||||
break;
|
||||
case KeyEvent.VK_MINUS:
|
||||
case KeyEvent.VK_SUBTRACT:
|
||||
requestedZoom = 1;
|
||||
break;
|
||||
case KeyEvent.VK_ADD:
|
||||
case KeyEvent.VK_EQUALS:
|
||||
requestedZoom = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (delta_x != 0 || delta_y != 0)
|
||||
{
|
||||
Rectangle bounds = viewer.getViewportBounds();
|
||||
double x = bounds.getCenterX() + delta_x;
|
||||
double y = bounds.getCenterY() + delta_y;
|
||||
viewer.setCenter(new Point2D.Double(x, y));
|
||||
viewer.repaint();
|
||||
}
|
||||
|
||||
if( requestedZoom != 0 )
|
||||
{
|
||||
int zoomLevel = viewer.getZoom() + requestedZoom;
|
||||
|
||||
viewer.setZoom( zoomLevel );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package org.jdesktop.swingx.input;
|
||||
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Point;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.event.MouseInputAdapter;
|
||||
|
||||
import org.jdesktop.swingx.JXMapViewer;
|
||||
|
||||
/**
|
||||
* Used to pan using press and drag mouse gestures
|
||||
* @author joshy
|
||||
*/
|
||||
public class PanMouseInputListener extends MouseInputAdapter
|
||||
{
|
||||
private Point prev;
|
||||
private JXMapViewer viewer;
|
||||
|
||||
/**
|
||||
* @param viewer the jxmapviewer
|
||||
*/
|
||||
public PanMouseInputListener(JXMapViewer viewer)
|
||||
{
|
||||
this.viewer = viewer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent evt)
|
||||
{
|
||||
prev = evt.getPoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent evt)
|
||||
{
|
||||
if (!SwingUtilities.isLeftMouseButton(evt))
|
||||
return;
|
||||
|
||||
Point current = evt.getPoint();
|
||||
double x = viewer.getCenter().getX() - (current.x - prev.x);
|
||||
double y = viewer.getCenter().getY() - (current.y - prev.y);
|
||||
|
||||
if (!viewer.isNegativeYAllowed())
|
||||
{
|
||||
if (y < 0)
|
||||
{
|
||||
y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int maxHeight = (int) (viewer.getTileFactory().getMapSize(viewer.getZoom()).getHeight() * viewer
|
||||
.getTileFactory().getTileSize(viewer.getZoom()));
|
||||
if (y > maxHeight)
|
||||
{
|
||||
y = maxHeight;
|
||||
}
|
||||
|
||||
prev = current;
|
||||
viewer.setCenter(new Point2D.Double(x, y));
|
||||
viewer.repaint();
|
||||
viewer.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent evt)
|
||||
{
|
||||
if (!SwingUtilities.isLeftMouseButton(evt))
|
||||
return;
|
||||
|
||||
prev = null;
|
||||
viewer.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e)
|
||||
{
|
||||
SwingUtilities.invokeLater(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
viewer.requestFocusInWindow();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package org.jdesktop.swingx.input;
|
||||
|
||||
import java.awt.event.MouseWheelEvent;
|
||||
import java.awt.event.MouseWheelListener;
|
||||
|
||||
import org.jdesktop.swingx.JXMapViewer;
|
||||
|
||||
/**
|
||||
* zooms using the mouse wheel on the view center
|
||||
* @author joshy
|
||||
*/
|
||||
public class ZoomMouseWheelListenerCenter implements MouseWheelListener
|
||||
{
|
||||
private JXMapViewer viewer;
|
||||
|
||||
/**
|
||||
* @param viewer the jxmapviewer
|
||||
*/
|
||||
public ZoomMouseWheelListenerCenter(JXMapViewer viewer)
|
||||
{
|
||||
this.viewer = viewer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseWheelMoved(MouseWheelEvent e)
|
||||
{
|
||||
viewer.setZoom(viewer.getZoom() + e.getWheelRotation());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package org.jdesktop.swingx.input;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.event.MouseWheelEvent;
|
||||
import java.awt.event.MouseWheelListener;
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
import org.jdesktop.swingx.JXMapViewer;
|
||||
|
||||
/**
|
||||
* zooms to the current mouse cursor
|
||||
* using the mouse wheel
|
||||
* @author Martin Steiger
|
||||
*/
|
||||
public class ZoomMouseWheelListenerCursor implements MouseWheelListener
|
||||
{
|
||||
private JXMapViewer viewer;
|
||||
|
||||
/**
|
||||
* @param viewer the jxmapviewer
|
||||
*/
|
||||
public ZoomMouseWheelListenerCursor(JXMapViewer viewer)
|
||||
{
|
||||
this.viewer = viewer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseWheelMoved(MouseWheelEvent evt)
|
||||
{
|
||||
Point current = evt.getPoint();
|
||||
Rectangle bound = viewer.getViewportBounds();
|
||||
|
||||
double dx = current.x - bound.width / 2;
|
||||
double dy = current.y - bound.height / 2;
|
||||
|
||||
Dimension oldMapSize = viewer.getTileFactory().getMapSize(viewer.getZoom());
|
||||
|
||||
viewer.setZoom(viewer.getZoom() + evt.getWheelRotation());
|
||||
|
||||
Dimension mapSize = viewer.getTileFactory().getMapSize(viewer.getZoom());
|
||||
|
||||
Point2D center = viewer.getCenter();
|
||||
|
||||
double dzw = (mapSize.getWidth() / oldMapSize.getWidth());
|
||||
double dzh = (mapSize.getHeight() / oldMapSize.getHeight());
|
||||
|
||||
double x = center.getX() + dx * (dzw - 1);
|
||||
double y = center.getY() + dy * (dzh - 1);
|
||||
|
||||
viewer.setCenter(new Point2D.Double(x, y));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,405 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package org.jdesktop.swingx.mapviewer;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.SoftReference;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import org.jdesktop.swingx.mapviewer.util.GeoUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The <code>AbstractTileFactory</code> provides
|
||||
* a basic implementation for the TileFactory.
|
||||
*/
|
||||
public abstract class AbstractTileFactory extends TileFactory
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( AbstractTileFactory.class );
|
||||
|
||||
/**
|
||||
* Creates a new instance of DefaultTileFactory using the spcified TileFactoryInfo
|
||||
* @param info a TileFactoryInfo to configure this TileFactory
|
||||
*/
|
||||
public AbstractTileFactory(TileFactoryInfo info)
|
||||
{
|
||||
super(info);
|
||||
}
|
||||
|
||||
// private static final boolean doEagerLoading = true;
|
||||
|
||||
private int threadPoolSize = 4;
|
||||
private ExecutorService service;
|
||||
|
||||
// TODO the tile map should be static ALWAYS, regardless of the number
|
||||
// of GoogleTileFactories because each tile is, really, a singleton.
|
||||
private Map<String, Tile> tileMap = new HashMap<String, Tile>();
|
||||
|
||||
private TileCache cache = new TileCache();
|
||||
|
||||
/**
|
||||
* Returns the tile that is located at the given tilePoint
|
||||
* for this zoom. For example, if getMapSize() returns 10x20
|
||||
* for this zoom, and the tilePoint is (3,5), then the
|
||||
* appropriate tile will be located and returned.
|
||||
*/
|
||||
@Override
|
||||
public Tile getTile(int x, int y, int zoom)
|
||||
{
|
||||
return getTile(x, y, zoom, true);
|
||||
}
|
||||
|
||||
private Tile getTile(int tpx, int tpy, int zoom, boolean eagerLoad)
|
||||
{
|
||||
// wrap the tiles horizontally --> mod the X with the max width
|
||||
// and use that
|
||||
int tileX = tpx;// tilePoint.getX();
|
||||
int numTilesWide = (int) getMapSize(zoom).getWidth();
|
||||
if (tileX < 0)
|
||||
{
|
||||
tileX = numTilesWide - (Math.abs(tileX) % numTilesWide);
|
||||
}
|
||||
|
||||
tileX = tileX % numTilesWide;
|
||||
int tileY = tpy;
|
||||
// TilePoint tilePoint = new TilePoint(tileX, tpy);
|
||||
String url = getInfo().getTileUrl(tileX, tileY, zoom);// tilePoint);
|
||||
// System.out.println("loading: " + url);
|
||||
|
||||
Tile.Priority pri = Tile.Priority.High;
|
||||
if (!eagerLoad)
|
||||
{
|
||||
pri = Tile.Priority.Low;
|
||||
}
|
||||
Tile tile = null;
|
||||
// System.out.println("testing for validity: " + tilePoint + " zoom = " + zoom);
|
||||
if (!tileMap.containsKey(url))
|
||||
{
|
||||
if (!GeoUtil.isValidTile(tileX, tileY, zoom, getInfo()))
|
||||
{
|
||||
tile = new Tile(tileX, tileY, zoom);
|
||||
}
|
||||
else
|
||||
{
|
||||
tile = new Tile(tileX, tileY, zoom, url, pri, this);
|
||||
startLoading(tile);
|
||||
}
|
||||
tileMap.put(url, tile);
|
||||
}
|
||||
else
|
||||
{
|
||||
tile = tileMap.get(url);
|
||||
// if its in the map but is low and isn't loaded yet
|
||||
// but we are in high mode
|
||||
if (tile.getPriority() == Tile.Priority.Low && eagerLoad && !tile.isLoaded())
|
||||
{
|
||||
// System.out.println("in high mode and want a low");
|
||||
// tile.promote();
|
||||
promote(tile);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* if (eagerLoad && doEagerLoading) { for (int i = 0; i<1; i++) { for (int j = 0; j<1; j++) { // preload the 4
|
||||
* tiles under the current one if(zoom > 0) { eagerlyLoad(tilePoint.getX()*2, tilePoint.getY()*2, zoom-1);
|
||||
* eagerlyLoad(tilePoint.getX()*2+1, tilePoint.getY()*2, zoom-1); eagerlyLoad(tilePoint.getX()*2,
|
||||
* tilePoint.getY()*2+1, zoom-1); eagerlyLoad(tilePoint.getX()*2+1, tilePoint.getY()*2+1, zoom-1); } } } }
|
||||
*/
|
||||
|
||||
return tile;
|
||||
}
|
||||
|
||||
/*
|
||||
* private void eagerlyLoad(int x, int y, int zoom) { TilePoint t1 = new TilePoint(x,y); if(!isLoaded(t1,zoom)) {
|
||||
* getTile(t1,zoom,false); } }
|
||||
*/
|
||||
|
||||
// private boolean isLoaded(int x, int y, int zoom) {
|
||||
// String url = getInfo().getTileUrl(zoom,x,y);
|
||||
// return tileMap.containsKey(url);
|
||||
// }
|
||||
|
||||
/**
|
||||
* @return the tile cache
|
||||
*/
|
||||
public TileCache getTileCache()
|
||||
{
|
||||
return cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cache the tile cache
|
||||
*/
|
||||
public void setTileCache(TileCache cache)
|
||||
{
|
||||
this.cache = cache;
|
||||
}
|
||||
|
||||
/** ==== threaded tile loading stuff === */
|
||||
/**
|
||||
* Thread pool for loading the tiles
|
||||
*/
|
||||
private static BlockingQueue<Tile> tileQueue = new PriorityBlockingQueue<Tile>(5, new Comparator<Tile>()
|
||||
{
|
||||
@Override
|
||||
public int compare(Tile o1, Tile o2)
|
||||
{
|
||||
if (o1.getPriority() == Tile.Priority.Low && o2.getPriority() == Tile.Priority.High)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if (o1.getPriority() == Tile.Priority.High && o2.getPriority() == Tile.Priority.Low)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Subclasses may override this method to provide their own executor services. This method will be called each time
|
||||
* a tile needs to be loaded. Implementations should cache the ExecutorService when possible.
|
||||
* @return ExecutorService to load tiles with
|
||||
*/
|
||||
protected synchronized ExecutorService getService()
|
||||
{
|
||||
if (service == null)
|
||||
{
|
||||
// System.out.println("creating an executor service with a threadpool of size " + threadPoolSize);
|
||||
service = Executors.newFixedThreadPool(threadPoolSize, new ThreadFactory()
|
||||
{
|
||||
private int count = 0;
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r)
|
||||
{
|
||||
Thread t = new Thread(r, "map tile fetcher-pool-" + count++);
|
||||
t.setPriority(Thread.MIN_PRIORITY);
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
}
|
||||
});
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
if (service != null)
|
||||
{
|
||||
service.shutdown();
|
||||
service = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the number of threads to use for loading the tiles. This controls the number of threads used by the
|
||||
* ExecutorService returned from getService(). Note, this method should be called before loading the first tile.
|
||||
* Calls after the first tile are loaded will have no effect by default.
|
||||
* @param size the thread pool size
|
||||
*/
|
||||
public void setThreadPoolSize(int size)
|
||||
{
|
||||
if (size <= 0)
|
||||
{
|
||||
throw new IllegalArgumentException("size invalid: " + size
|
||||
+ ". The size of the threadpool must be greater than 0.");
|
||||
}
|
||||
threadPoolSize = size;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected synchronized void startLoading(Tile tile)
|
||||
{
|
||||
if (tile.isLoading())
|
||||
{
|
||||
System.out.println("already loading. bailing");
|
||||
return;
|
||||
}
|
||||
tile.setLoading(true);
|
||||
try
|
||||
{
|
||||
tileQueue.put(tile);
|
||||
getService().submit(createTileRunner(tile));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses can override this if they need custom TileRunners for some reason
|
||||
* @param tile the tile (unused!)
|
||||
* @return the tile runner
|
||||
*/
|
||||
protected Runnable createTileRunner(Tile tile)
|
||||
{
|
||||
return new TileRunner();
|
||||
}
|
||||
|
||||
/**
|
||||
* Increase the priority of this tile so it will be loaded sooner.
|
||||
* @param tile the tile
|
||||
*/
|
||||
public synchronized void promote(Tile tile)
|
||||
{
|
||||
if (tileQueue.contains(tile))
|
||||
{
|
||||
try
|
||||
{
|
||||
tileQueue.remove(tile);
|
||||
tile.setPriority(Tile.Priority.High);
|
||||
tileQueue.put(tile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An inner class which actually loads the tiles. Used by the thread queue. Subclasses can override this if
|
||||
* necessary.
|
||||
*/
|
||||
private class TileRunner implements Runnable
|
||||
{
|
||||
/**
|
||||
* Gets the full URI of a tile.
|
||||
* @param tile the tile
|
||||
* @throws URISyntaxException if the URI is invalid
|
||||
* @return a URI for the tile
|
||||
*/
|
||||
protected URI getURI(Tile tile) throws URISyntaxException
|
||||
{
|
||||
if (tile.getURL() == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return new URI(tile.getURL());
|
||||
}
|
||||
|
||||
/**
|
||||
* implementation of the Runnable interface.
|
||||
*/
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
/*
|
||||
* 3 strikes and you're out. Attempt to load the url. If it fails, decrement the number of tries left and
|
||||
* try again. Log failures. If I run out of try s just get out. This way, if there is some kind of serious
|
||||
* failure, I can get out and let other tiles try to load.
|
||||
*/
|
||||
final Tile tile = tileQueue.remove();
|
||||
|
||||
int trys = 3;
|
||||
while (!tile.isLoaded() && trys > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
BufferedImage img = null;
|
||||
URI uri = getURI(tile);
|
||||
img = cache.get(uri);
|
||||
if (img == null)
|
||||
{
|
||||
byte[] bimg = cacheInputStream(uri.toURL());
|
||||
// img = PaintUtils.loadCompatibleImage(new ByteArrayInputStream(bimg));
|
||||
img = ImageIO.read(new ByteArrayInputStream(bimg));
|
||||
cache.put(uri, bimg, img);
|
||||
img = cache.get(uri);
|
||||
}
|
||||
if (img == null)
|
||||
{
|
||||
System.out.println("error loading: " + uri);
|
||||
trys--;
|
||||
}
|
||||
else
|
||||
{
|
||||
final BufferedImage i = img;
|
||||
SwingUtilities.invokeAndWait(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
tile.image = new SoftReference<BufferedImage>(i);
|
||||
tile.setLoaded(true);
|
||||
fireTileLoadedEvent(tile);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (OutOfMemoryError memErr)
|
||||
{
|
||||
cache.needMoreMemory();
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
if (trys == 0)
|
||||
{
|
||||
mLog.error("Failed to load a tile at url: " +
|
||||
tile.getURL() + ", stopping", e );
|
||||
}
|
||||
else
|
||||
{
|
||||
mLog.error("Failed to load a tile at url: " +
|
||||
tile.getURL() + ", retrying", e );
|
||||
trys--;
|
||||
}
|
||||
}
|
||||
}
|
||||
tile.setLoading(false);
|
||||
}
|
||||
|
||||
private byte[] cacheInputStream(URL url) throws IOException
|
||||
{
|
||||
InputStream ins = url.openStream();
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
byte[] buf = new byte[256];
|
||||
while (true)
|
||||
{
|
||||
int n = ins.read(buf);
|
||||
if (n == -1)
|
||||
break;
|
||||
bout.write(buf, 0, n);
|
||||
}
|
||||
return bout.toByteArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* DefaultTileFactory.java
|
||||
*
|
||||
* Created on June 27, 2006, 2:20 PM
|
||||
*
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.mapviewer;
|
||||
|
||||
/**
|
||||
* A tile factory which configures itself using a TileFactoryInfo object and uses a Google Maps like mercator
|
||||
* projection.
|
||||
* @author joshy
|
||||
*/
|
||||
public class DefaultTileFactory extends AbstractTileFactory
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of DefaultTileFactory using the spcified TileFactoryInfo
|
||||
* @param info a TileFactoryInfo to configure this TileFactory
|
||||
*/
|
||||
public DefaultTileFactory(TileFactoryInfo info)
|
||||
{
|
||||
super(info);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Waypoint.java
|
||||
*
|
||||
* Created on March 30, 2006, 5:22 PM
|
||||
*
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.mapviewer;
|
||||
|
||||
import org.jdesktop.beans.AbstractBean;
|
||||
|
||||
/**
|
||||
* @author joshy
|
||||
*/
|
||||
public class DefaultWaypoint extends AbstractBean implements Waypoint
|
||||
{
|
||||
private GeoPosition position;
|
||||
|
||||
/**
|
||||
* Creates a new instance of Waypoint
|
||||
*/
|
||||
public DefaultWaypoint()
|
||||
{
|
||||
this(new GeoPosition(0, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param latitude the latitude
|
||||
* @param longitude the longitude
|
||||
*/
|
||||
public DefaultWaypoint(double latitude, double longitude)
|
||||
{
|
||||
this(new GeoPosition(latitude, longitude));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param coord the geo coordinate
|
||||
*/
|
||||
public DefaultWaypoint(GeoPosition coord)
|
||||
{
|
||||
this.position = coord;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoPosition getPosition()
|
||||
{
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a new GeoPosition for this Waypoint
|
||||
* @param coordinate a new position
|
||||
*/
|
||||
public void setPosition(GeoPosition coordinate)
|
||||
{
|
||||
GeoPosition old = getPosition();
|
||||
this.position = coordinate;
|
||||
firePropertyChange("position", old, getPosition());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* WaypointRenderer.java
|
||||
*
|
||||
* Created on March 30, 2006, 5:24 PM
|
||||
*
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.mapviewer;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.jdesktop.swingx.JXMapViewer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This is a standard waypoint renderer.
|
||||
* @author joshy
|
||||
*/
|
||||
public class DefaultWaypointRenderer implements WaypointRenderer<Waypoint>
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( DefaultWaypointRenderer.class );
|
||||
|
||||
private BufferedImage img = null;
|
||||
|
||||
/**
|
||||
* Uses a default waypoint image
|
||||
*/
|
||||
public DefaultWaypointRenderer()
|
||||
{
|
||||
try
|
||||
{
|
||||
img = ImageIO.read(getClass().getResource("resources/standard_waypoint.png"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
mLog.error("couldn't read standard_waypoint.png", ex );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintWaypoint(Graphics2D g, JXMapViewer map, Waypoint w)
|
||||
{
|
||||
if (img == null)
|
||||
return;
|
||||
|
||||
Point2D point = map.getTileFactory().geoToPixel(w.getPosition(), map.getZoom());
|
||||
|
||||
int x = (int)point.getX() -img.getWidth() / 2;
|
||||
int y = (int)point.getY() -img.getHeight();
|
||||
|
||||
g.drawImage(img, x, y, null);
|
||||
}
|
||||
}
|
||||
158
src/main/java/org/jdesktop/swingx/mapviewer/GeoBounds.java
Normal file
158
src/main/java/org/jdesktop/swingx/mapviewer/GeoBounds.java
Normal file
@@ -0,0 +1,158 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package org.jdesktop.swingx.mapviewer;
|
||||
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The <code>GeoBounds</code> class provides access the the North East and South West corners of the bounds and provides
|
||||
* an intersects method.
|
||||
* @author Dan Andrews
|
||||
*/
|
||||
public class GeoBounds
|
||||
{
|
||||
|
||||
/** Internal representation of the bounds */
|
||||
private Rectangle2D[] rects;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param minLat The minimum latitude.
|
||||
* @param minLng The minimum longitude.
|
||||
* @param maxLat The maximum latitude.
|
||||
* @param maxLng The maximum longitude.
|
||||
*/
|
||||
public GeoBounds(double minLat, double minLng, double maxLat, double maxLng)
|
||||
{
|
||||
setRect(minLat, minLng, maxLat, maxLng);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param geoPositions A non null list of 2 or more different <code>GeoBounds</code> objects.
|
||||
*/
|
||||
public GeoBounds(Set<GeoPosition> geoPositions)
|
||||
{
|
||||
if (geoPositions == null || geoPositions.size() < 2)
|
||||
{
|
||||
throw new IllegalArgumentException("The attribute 'geoPositions' cannot be null and must "
|
||||
+ "have 2 or more elements.");
|
||||
}
|
||||
double minLat = Integer.MAX_VALUE;
|
||||
double minLng = Integer.MAX_VALUE;
|
||||
double maxLat = Integer.MIN_VALUE;
|
||||
double maxLng = Integer.MIN_VALUE;
|
||||
for (GeoPosition position : geoPositions)
|
||||
{
|
||||
minLat = Math.min(minLat, position.getLatitude());
|
||||
minLng = Math.min(minLng, position.getLongitude());
|
||||
maxLat = Math.max(maxLat, position.getLatitude());
|
||||
maxLng = Math.max(maxLng, position.getLongitude());
|
||||
}
|
||||
setRect(minLat, minLng, maxLat, maxLng);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the internal rectangle representation.
|
||||
* @param minLat The minimum latitude.
|
||||
* @param minLng The minimum longitude.
|
||||
* @param maxLat The maximum latitude.
|
||||
* @param maxLng The maximum longitude.
|
||||
*/
|
||||
private void setRect(double minLat, double minLng, double maxLat, double maxLng)
|
||||
{
|
||||
if (!(minLat < maxLat))
|
||||
{
|
||||
throw new IllegalArgumentException("GeoBounds is not valid - minLat must be less that maxLat.");
|
||||
}
|
||||
if (!(minLng < maxLng))
|
||||
{
|
||||
if (minLng > 0 && minLng < 180 && maxLng < 0)
|
||||
{
|
||||
rects = new Rectangle2D[] {
|
||||
// split into two rects e.g. 176.8793 to 180 and -180 to
|
||||
// -175.0104
|
||||
new Rectangle2D.Double(minLng, minLat, 180 - minLng, maxLat - minLat),
|
||||
new Rectangle2D.Double(-180, minLat, maxLng + 180, maxLat - minLat) };
|
||||
}
|
||||
else
|
||||
{
|
||||
rects = new Rectangle2D[] { new Rectangle2D.Double(minLng, minLat, maxLng - minLng, maxLat - minLat) };
|
||||
|
||||
throw new IllegalArgumentException("GeoBounds is not valid - minLng must be less that maxLng or "
|
||||
+ "minLng must be greater than 0 and maxLng must be less than 0.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rects = new Rectangle2D[] { new Rectangle2D.Double(minLng, minLat, maxLng - minLng, maxLat - minLat) };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if this bounds intersects the other bounds.
|
||||
* @param other The other bounds to test for intersection with.
|
||||
* @return Returns true if bounds intersect.
|
||||
*/
|
||||
public boolean intersects(GeoBounds other)
|
||||
{
|
||||
boolean rv = false;
|
||||
for (Rectangle2D r1 : rects)
|
||||
{
|
||||
for (Rectangle2D r2 : other.rects)
|
||||
{
|
||||
rv = r1.intersects(r2);
|
||||
if (rv)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rv)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the north west position.
|
||||
* @return Returns the north west position.
|
||||
*/
|
||||
public GeoPosition getNorthWest()
|
||||
{
|
||||
return new GeoPosition(rects[0].getX(), rects[0].getMaxY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the south east position.
|
||||
* @return Returns the south east position.
|
||||
*/
|
||||
public GeoPosition getSouthEast()
|
||||
{
|
||||
Rectangle2D r = rects[0];
|
||||
if (rects.length > 1)
|
||||
{
|
||||
r = rects[1];
|
||||
}
|
||||
return new GeoPosition(r.getMaxX(), r.getY());
|
||||
}
|
||||
|
||||
}
|
||||
113
src/main/java/org/jdesktop/swingx/mapviewer/GeoPosition.java
Normal file
113
src/main/java/org/jdesktop/swingx/mapviewer/GeoPosition.java
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* GeoPosition.java
|
||||
*
|
||||
* Created on March 31, 2006, 9:15 PM
|
||||
*
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.mapviewer;
|
||||
|
||||
/**
|
||||
* An immutable coordinate in the real (geographic) world,
|
||||
* composed of a latitude and a longitude.
|
||||
* @author rbair
|
||||
*/
|
||||
public class GeoPosition {
|
||||
private double latitude;
|
||||
private double longitude;
|
||||
|
||||
/**
|
||||
* Creates a new instance of GeoPosition from the specified
|
||||
* latitude and longitude. These are double values in decimal degrees, not
|
||||
* degrees, minutes, and seconds. Use the other constructor for those.
|
||||
* @param latitude a latitude value in decmial degrees
|
||||
* @param longitude a longitude value in decimal degrees
|
||||
*/
|
||||
public GeoPosition(double latitude, double longitude) {
|
||||
this.latitude = latitude;
|
||||
this.longitude = longitude;
|
||||
}
|
||||
// must be an array of length two containing lat then long in that order.
|
||||
/**
|
||||
* Creates a new instance of GeoPosition from the specified
|
||||
* latitude and longitude as an array of two doubles, with the
|
||||
* latitude first. These are double values in decimal degrees, not
|
||||
* degrees, minutes, and seconds. Use the other constructor for those.
|
||||
* @param coords latitude and longitude as a double array of length two
|
||||
*/
|
||||
public GeoPosition(double [] coords) {
|
||||
this.latitude = coords[0];
|
||||
this.longitude = coords[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of GeoPosition from the specified
|
||||
* latitude and longitude.
|
||||
* Each are specified as degrees, minutes, and seconds; not
|
||||
* as decimal degrees. Use the other constructor for those.
|
||||
* @param latDegrees the degrees part of the current latitude
|
||||
* @param latMinutes the minutes part of the current latitude
|
||||
* @param latSeconds the seconds part of the current latitude
|
||||
* @param lonDegrees the degrees part of the current longitude
|
||||
* @param lonMinutes the minutes part of the current longitude
|
||||
* @param lonSeconds the seconds part of the current longitude
|
||||
*/
|
||||
public GeoPosition(int latDegrees, int latMinutes, int latSeconds,
|
||||
int lonDegrees, int lonMinutes, int lonSeconds) {
|
||||
this(latDegrees + (latMinutes + latSeconds/60.0)/60.0,
|
||||
lonDegrees + (lonMinutes + lonSeconds/60.0)/60.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latitude as decimal degrees
|
||||
* @return the latitude as decimal degrees
|
||||
*/
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the longitude as decimal degrees
|
||||
* @return the longitude as decimal degrees
|
||||
*/
|
||||
public double getLongitude() {
|
||||
return longitude;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
long temp;
|
||||
temp = Double.doubleToLongBits(latitude);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
temp = Double.doubleToLongBits(longitude);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj)
|
||||
{
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (!(obj instanceof GeoPosition))
|
||||
return false;
|
||||
GeoPosition other = (GeoPosition) obj;
|
||||
if (Double.doubleToLongBits(latitude) != Double.doubleToLongBits(other.latitude))
|
||||
return false;
|
||||
if (Double.doubleToLongBits(longitude) != Double.doubleToLongBits(other.longitude))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + latitude + ", " + longitude + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package org.jdesktop.swingx.mapviewer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.CacheRequest;
|
||||
import java.net.CacheResponse;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.ResponseCache;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* @author joshy
|
||||
*/
|
||||
public class LocalResponseCache extends ResponseCache
|
||||
{
|
||||
private final static Logger mLog =
|
||||
LoggerFactory.getLogger( LocalResponseCache.class );
|
||||
|
||||
private final File cacheDir;
|
||||
|
||||
private boolean checkForUpdates;
|
||||
|
||||
private String baseURL;
|
||||
|
||||
/**
|
||||
* Private constructor to prevent instantiation.
|
||||
* @param baseURL the URI that should be cached or <code>null</code> (for all URLs)
|
||||
* @param cacheDir the cache directory
|
||||
* @param checkForUpdates true if the URL is queried for newer versions of a file first
|
||||
*/
|
||||
private LocalResponseCache(String baseURL, File cacheDir, boolean checkForUpdates)
|
||||
{
|
||||
this.baseURL = baseURL;
|
||||
this.cacheDir = cacheDir;
|
||||
this.checkForUpdates = checkForUpdates;
|
||||
|
||||
if (!cacheDir.exists())
|
||||
{
|
||||
cacheDir.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this cache as default response cache
|
||||
* @param baseURL the URL, the caching should be restricted to or <code>null</code> for none
|
||||
* @param cacheDir the cache directory
|
||||
* @param checkForUpdates true if the URL is queried for newer versions of a file first
|
||||
*/
|
||||
public static void installResponseCache(String baseURL, File cacheDir, boolean checkForUpdates)
|
||||
{
|
||||
ResponseCache.setDefault(new LocalResponseCache(baseURL, cacheDir, checkForUpdates));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the local File corresponding to the given remote URI.
|
||||
* @param remoteUri the remote URI
|
||||
* @return the corresponding local file
|
||||
*/
|
||||
public File getLocalFile(URI remoteUri)
|
||||
{
|
||||
if (baseURL != null)
|
||||
{
|
||||
String remote = remoteUri.toString();
|
||||
|
||||
if (!remote.startsWith(baseURL))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
String host = remoteUri.getHost();
|
||||
String query = remoteUri.getQuery();
|
||||
String path = remoteUri.getPath();
|
||||
String fragment = remoteUri.getFragment();
|
||||
|
||||
if (host != null)
|
||||
{
|
||||
sb.append(host);
|
||||
}
|
||||
if (path != null)
|
||||
{
|
||||
sb.append(path);
|
||||
}
|
||||
if (query != null)
|
||||
{
|
||||
sb.append('?');
|
||||
sb.append(query);
|
||||
}
|
||||
if (fragment != null)
|
||||
{
|
||||
sb.append('#');
|
||||
sb.append(fragment);
|
||||
}
|
||||
|
||||
String name;
|
||||
|
||||
final int maxLen = 250;
|
||||
|
||||
if (sb.length() < maxLen)
|
||||
{
|
||||
name = sb.toString();
|
||||
}
|
||||
else
|
||||
{
|
||||
name = sb.substring(0, maxLen);
|
||||
}
|
||||
|
||||
name = name.replace('?', '$');
|
||||
name = name.replace('*', '$');
|
||||
name = name.replace(':', '$');
|
||||
name = name.replace('<', '$');
|
||||
name = name.replace('>', '$');
|
||||
name = name.replace('"', '$');
|
||||
|
||||
File f = new File(cacheDir, name);
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param remoteUri the remote URI
|
||||
* @param localFile the corresponding local file
|
||||
* @return true if the resource at the given remote URI is newer than the resource cached locally.
|
||||
*/
|
||||
private static boolean isUpdateAvailable(URI remoteUri, File localFile)
|
||||
{
|
||||
URLConnection conn;
|
||||
try
|
||||
{
|
||||
conn = remoteUri.toURL().openConnection();
|
||||
}
|
||||
catch (MalformedURLException ex)
|
||||
{
|
||||
mLog.error("An exception occurred - ", ex );
|
||||
return false;
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
mLog.error("An exception occurred - ", ex );
|
||||
return false;
|
||||
}
|
||||
if (!(conn instanceof HttpURLConnection))
|
||||
{
|
||||
// don't bother with non-http connections
|
||||
return false;
|
||||
}
|
||||
|
||||
long localLastMod = localFile.lastModified();
|
||||
long remoteLastMod = 0L;
|
||||
HttpURLConnection httpconn = (HttpURLConnection) conn;
|
||||
// disable caching so we don't get in feedback loop with ResponseCache
|
||||
httpconn.setUseCaches(false);
|
||||
try
|
||||
{
|
||||
httpconn.connect();
|
||||
remoteLastMod = httpconn.getLastModified();
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
// log.error("An exception occurred", ex);();
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
httpconn.disconnect();
|
||||
}
|
||||
|
||||
return (remoteLastMod > localLastMod);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheResponse get(URI uri, String rqstMethod, Map<String, List<String>> rqstHeaders) throws IOException
|
||||
{
|
||||
File localFile = getLocalFile(uri);
|
||||
|
||||
if (localFile == null)
|
||||
{
|
||||
// we don't want to cache this URL
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!localFile.exists())
|
||||
{
|
||||
// the file isn't already in our cache, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
if (checkForUpdates)
|
||||
{
|
||||
if (isUpdateAvailable(uri, localFile))
|
||||
{
|
||||
// there is an update available, so don't return cached version
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return new LocalCacheResponse(localFile, rqstHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheRequest put(URI uri, URLConnection conn) throws IOException
|
||||
{
|
||||
// only cache http(s) GET requests
|
||||
if (!(conn instanceof HttpURLConnection) || !(((HttpURLConnection) conn).getRequestMethod().equals("GET")))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
File localFile = getLocalFile(uri);
|
||||
|
||||
if (localFile == null)
|
||||
{
|
||||
// we don't want to cache this URL
|
||||
return null;
|
||||
}
|
||||
|
||||
new File(localFile.getParent()).mkdirs();
|
||||
return new LocalCacheRequest(localFile);
|
||||
}
|
||||
|
||||
private class LocalCacheResponse extends CacheResponse
|
||||
{
|
||||
private FileInputStream fis;
|
||||
private final Map<String, List<String>> headers;
|
||||
|
||||
private LocalCacheResponse(File localFile, Map<String, List<String>> rqstHeaders)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.fis = new FileInputStream(localFile);
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
// should not happen, since we already checked for existence
|
||||
mLog.error("An exception occurred - ", ex );
|
||||
}
|
||||
this.headers = rqstHeaders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getHeaders() throws IOException
|
||||
{
|
||||
return headers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getBody() throws IOException
|
||||
{
|
||||
return fis;
|
||||
}
|
||||
}
|
||||
|
||||
private class LocalCacheRequest extends CacheRequest
|
||||
{
|
||||
private final File localFile;
|
||||
private FileOutputStream fos;
|
||||
|
||||
private LocalCacheRequest(File localFile)
|
||||
{
|
||||
this.localFile = localFile;
|
||||
try
|
||||
{
|
||||
this.fos = new FileOutputStream(localFile);
|
||||
}
|
||||
catch (FileNotFoundException ex)
|
||||
{
|
||||
// should not happen if cache dir is valid
|
||||
mLog.error("An exception occurred", ex );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream getBody() throws IOException
|
||||
{
|
||||
return fos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void abort()
|
||||
{
|
||||
// abandon the cache attempt by closing the stream and deleting
|
||||
// the local file
|
||||
try
|
||||
{
|
||||
fos.close();
|
||||
localFile.delete();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
214
src/main/java/org/jdesktop/swingx/mapviewer/Tile.java
Normal file
214
src/main/java/org/jdesktop/swingx/mapviewer/Tile.java
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Tile.java
|
||||
*
|
||||
* Created on March 14, 2006, 4:53 PM
|
||||
*
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.mapviewer;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.lang.ref.SoftReference;
|
||||
|
||||
import org.jdesktop.beans.AbstractBean;
|
||||
|
||||
/**
|
||||
* The Tile class represents a particular square image piece of the world bitmap at a particular zoom level.
|
||||
* @author joshy
|
||||
*/
|
||||
|
||||
public class Tile extends AbstractBean
|
||||
{
|
||||
/**
|
||||
* The loading priority
|
||||
*/
|
||||
@SuppressWarnings("javadoc")
|
||||
public enum Priority
|
||||
{
|
||||
High, Low
|
||||
}
|
||||
|
||||
private Priority priority = Priority.High;
|
||||
|
||||
private TileFactory dtf;
|
||||
|
||||
private boolean isLoading = false;
|
||||
|
||||
// Most Recently Accessed Tiles. These are strong references, to prevent reloading
|
||||
// of most recently used tiles.
|
||||
// private static final Map<URI, BufferedImage> recentlyAccessed = new HashMap<URI, BufferedImage>();
|
||||
// private static final TileCache cache = new TileCache();
|
||||
|
||||
/**
|
||||
* The url of the image to load for this tile
|
||||
*/
|
||||
private String url;
|
||||
|
||||
/**
|
||||
* Indicates that loading has succeeded. A PropertyChangeEvent will be fired when the loading is completed
|
||||
*/
|
||||
|
||||
private boolean loaded = false;
|
||||
/**
|
||||
* The zoom level this tile is for
|
||||
*/
|
||||
private int zoom, x, y;
|
||||
|
||||
/**
|
||||
* The image loaded for this Tile
|
||||
*/
|
||||
SoftReference<BufferedImage> image = new SoftReference<BufferedImage>(null);
|
||||
|
||||
/**
|
||||
* Create a new Tile at the specified tile point and zoom level
|
||||
* @param x the x value
|
||||
* @param y the y value
|
||||
* @param zoom the zoom level
|
||||
*/
|
||||
public Tile(int x, int y, int zoom)
|
||||
{
|
||||
loaded = false;
|
||||
this.zoom = zoom;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Tile that loads its data from the given URL. The URL must resolve to an image
|
||||
* @param x the x value
|
||||
* @param y the y value
|
||||
* @param zoom the zoom level
|
||||
* @param url the URL
|
||||
* @param priority the priority
|
||||
* @param dtf the tile factory
|
||||
*/
|
||||
Tile(int x, int y, int zoom, String url, Priority priority, TileFactory dtf)
|
||||
{
|
||||
this.url = url;
|
||||
loaded = false;
|
||||
this.zoom = zoom;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.priority = priority;
|
||||
this.dtf = dtf;
|
||||
// startLoading();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if this tile's underlying image has been successfully loaded yet.
|
||||
* @return true if the Tile has been loaded
|
||||
*/
|
||||
public synchronized boolean isLoaded()
|
||||
{
|
||||
return loaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the loaded state, and fires the appropriate property change notification
|
||||
* @param loaded the loaded flag
|
||||
*/
|
||||
synchronized void setLoaded(boolean loaded)
|
||||
{
|
||||
boolean old = isLoaded();
|
||||
this.loaded = loaded;
|
||||
firePropertyChange("loaded", old, isLoaded());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the Image associated with this Tile. This is a read only property This may return null at any time,
|
||||
* however if this returns null, a load operation will automatically be started for it.
|
||||
*/
|
||||
public BufferedImage getImage()
|
||||
{
|
||||
BufferedImage img = image.get();
|
||||
if (img == null)
|
||||
{
|
||||
setLoaded(false);
|
||||
|
||||
// tile factory can be null if the tile has invalid coords or zoom
|
||||
if (dtf != null)
|
||||
{
|
||||
dtf.startLoading(this);
|
||||
}
|
||||
}
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the location in the world at this zoom level that this tile should be placed
|
||||
*/
|
||||
/*
|
||||
* public TilePoint getLocation() { return location; }
|
||||
*/
|
||||
|
||||
/**
|
||||
* @return the zoom level that this tile belongs in
|
||||
*/
|
||||
public int getZoom()
|
||||
{
|
||||
return zoom;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the isLoading
|
||||
*/
|
||||
public boolean isLoading()
|
||||
{
|
||||
return isLoading;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isLoading the isLoading to set
|
||||
*/
|
||||
public void setLoading(boolean isLoading)
|
||||
{
|
||||
this.isLoading = isLoading;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the loading priority of this tile.
|
||||
* @return the priority
|
||||
*/
|
||||
public Priority getPriority()
|
||||
{
|
||||
return priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the loading priority of this tile.
|
||||
* @param priority the priority to set
|
||||
*/
|
||||
public void setPriority(Priority priority)
|
||||
{
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL of this tile.
|
||||
* @return the url
|
||||
*/
|
||||
public String getURL()
|
||||
{
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the x value
|
||||
*/
|
||||
public int getX()
|
||||
{
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the y value
|
||||
*/
|
||||
public int getY()
|
||||
{
|
||||
return y;
|
||||
}
|
||||
|
||||
}
|
||||
141
src/main/java/org/jdesktop/swingx/mapviewer/TileCache.java
Normal file
141
src/main/java/org/jdesktop/swingx/mapviewer/TileCache.java
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* TileCache.java
|
||||
*
|
||||
* Created on January 2, 2007, 7:17 PM
|
||||
*
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.mapviewer;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
/**
|
||||
* An implementation only class for now. For internal use only.
|
||||
* @author joshua.marinacci@sun.com
|
||||
*/
|
||||
public class TileCache
|
||||
{
|
||||
private Map<URI, BufferedImage> imgmap = new HashMap<URI, BufferedImage>();
|
||||
private LinkedList<URI> imgmapAccessQueue = new LinkedList<URI>();
|
||||
private int imagesize = 0;
|
||||
private Map<URI, byte[]> bytemap = new HashMap<URI, byte[]>();
|
||||
private LinkedList<URI> bytemapAccessQueue = new LinkedList<URI>();
|
||||
private int bytesize = 0;
|
||||
|
||||
/**
|
||||
* Default constructor - Empty
|
||||
*/
|
||||
public TileCache()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Put a tile image into the cache. This puts both a buffered image and array of bytes that make up the compressed
|
||||
* image.
|
||||
* @param uri URI of image that is being stored in the cache
|
||||
* @param bimg bytes of the compressed image, ie: the image file that was loaded over the network
|
||||
* @param img image to store in the cache
|
||||
*/
|
||||
public void put(URI uri, byte[] bimg, BufferedImage img)
|
||||
{
|
||||
synchronized (bytemap)
|
||||
{
|
||||
while (bytesize > 1000 * 1000 * 50)
|
||||
{
|
||||
URI olduri = bytemapAccessQueue.removeFirst();
|
||||
byte[] oldbimg = bytemap.remove(olduri);
|
||||
bytesize -= oldbimg.length;
|
||||
log("removed 1 img from byte cache");
|
||||
}
|
||||
|
||||
bytemap.put(uri, bimg);
|
||||
bytesize += bimg.length;
|
||||
bytemapAccessQueue.addLast(uri);
|
||||
}
|
||||
addToImageCache(uri, img);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a buffered image for the requested URI from the cache. This method must return null if the image is not
|
||||
* in the cache. If the image is unavailable but it's compressed version *is* available, then the compressed version
|
||||
* will be expanded and returned.
|
||||
* @param uri URI of the image previously put in the cache
|
||||
* @return the image matching the requested URI, or null if not available
|
||||
* @throws IOException if retrieval fails
|
||||
*/
|
||||
public BufferedImage get(URI uri) throws IOException
|
||||
{
|
||||
synchronized (imgmap)
|
||||
{
|
||||
if (imgmap.containsKey(uri))
|
||||
{
|
||||
imgmapAccessQueue.remove(uri);
|
||||
imgmapAccessQueue.addLast(uri);
|
||||
return imgmap.get(uri);
|
||||
}
|
||||
}
|
||||
synchronized (bytemap)
|
||||
{
|
||||
if (bytemap.containsKey(uri))
|
||||
{
|
||||
log("retrieving from bytes");
|
||||
bytemapAccessQueue.remove(uri);
|
||||
bytemapAccessQueue.addLast(uri);
|
||||
BufferedImage img = ImageIO.read(new ByteArrayInputStream(bytemap.get(uri)));
|
||||
addToImageCache(uri, img);
|
||||
return img;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request that the cache free up some memory. How this happens or how much memory is freed is up to the TileCache
|
||||
* implementation. Subclasses can implement their own strategy. The default strategy is to clear out all buffered
|
||||
* images but retain the compressed versions.
|
||||
*/
|
||||
public void needMoreMemory()
|
||||
{
|
||||
imgmap.clear();
|
||||
log("HACK! need more memory: freeing up memory");
|
||||
}
|
||||
|
||||
private void addToImageCache(final URI uri, final BufferedImage img)
|
||||
{
|
||||
synchronized (imgmap)
|
||||
{
|
||||
try {
|
||||
while (imagesize > 1000 * 1000 * 50)
|
||||
{
|
||||
URI olduri = imgmapAccessQueue.removeFirst();
|
||||
BufferedImage oldimg = imgmap.remove(olduri);
|
||||
imagesize -= oldimg.getWidth() * oldimg.getHeight() * 4;
|
||||
log("removed 1 img from image cache");
|
||||
}
|
||||
imgmap.put(uri, img);
|
||||
imagesize += img.getWidth() * img.getHeight() * 4;
|
||||
imgmapAccessQueue.addLast(uri);
|
||||
} catch (Exception ex) {
|
||||
log("Failed to load tile at URL. Tile is null");
|
||||
}
|
||||
}
|
||||
// log("added to cache: " + " uncompressed = " + imgmap.keySet().size() + " / " + imagesize / 1000 + "k"
|
||||
// + " compressed = " + bytemap.keySet().size() + " / " + bytesize / 1000 + "k");
|
||||
}
|
||||
|
||||
private void log(String string)
|
||||
{
|
||||
// Log.debug( string );
|
||||
}
|
||||
|
||||
}
|
||||
144
src/main/java/org/jdesktop/swingx/mapviewer/TileFactory.java
Normal file
144
src/main/java/org/jdesktop/swingx/mapviewer/TileFactory.java
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* TileFactory.java
|
||||
*
|
||||
* Created on March 17, 2006, 8:01 PM
|
||||
*
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.mapviewer;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jdesktop.swingx.mapviewer.util.GeoUtil;
|
||||
|
||||
/**
|
||||
* A class that can produce tiles and convert coordinates to pixels
|
||||
* @author joshy
|
||||
*/
|
||||
public abstract class TileFactory
|
||||
{
|
||||
private TileFactoryInfo info;
|
||||
private List<TileListener> tileListeners = new ArrayList<TileListener>();
|
||||
|
||||
/**
|
||||
* Creates a new instance of TileFactory
|
||||
* @param info a TileFactoryInfo to configure this TileFactory
|
||||
*/
|
||||
protected TileFactory(TileFactoryInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of an edge of a tile in pixels at the current zoom level. Tiles must be square.
|
||||
* @param zoom the current zoom level
|
||||
* @return the size of an edge of a tile in pixels
|
||||
*/
|
||||
public int getTileSize(int zoom)
|
||||
{
|
||||
return getInfo().getTileSize(zoom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Dimension containing the width and height of the map, in tiles at the current zoom level. So a
|
||||
* Dimension that returns 10x20 would be 10 tiles wide and 20 tiles tall. These values can be multipled by
|
||||
* getTileSize() to determine the pixel width/height for the map at the given zoom level
|
||||
* @return the size of the world bitmap in tiles
|
||||
* @param zoom the current zoom level
|
||||
*/
|
||||
public Dimension getMapSize(int zoom)
|
||||
{
|
||||
return GeoUtil.getMapSize(zoom, getInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Tile at a given TilePoint and zoom level
|
||||
* @return the tile that is located at the given tilePoint for this zoom level. For example, if getMapSize() returns
|
||||
* 10x20 for this zoom, and the tilePoint is (3,5), then the appropriate tile will be located and returned. This
|
||||
* method must not return null. However, it can return dummy tiles that contain no data if it wants. This is
|
||||
* appropriate, for example, for tiles which are outside of the bounds of the map and if the factory doesn't
|
||||
* implement wrapping.
|
||||
* @param x the x value
|
||||
* @param y the y value
|
||||
* @param zoom the current zoom level
|
||||
*/
|
||||
public abstract Tile getTile(int x, int y, int zoom);
|
||||
|
||||
/**
|
||||
* Convert a pixel in the world bitmap at the specified zoom level into a GeoPosition
|
||||
* @param pixelCoordinate a Point2D representing a pixel in the world bitmap
|
||||
* @param zoom the zoom level of the world bitmap
|
||||
* @return the converted GeoPosition
|
||||
*/
|
||||
public GeoPosition pixelToGeo(Point2D pixelCoordinate, int zoom)
|
||||
{
|
||||
return GeoUtil.getPosition(pixelCoordinate, zoom, getInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a GeoPosition to a pixel position in the world bitmap a the specified zoom level.
|
||||
* @param c a GeoPosition
|
||||
* @param zoomLevel the zoom level to extract the pixel coordinate for
|
||||
* @return the pixel point
|
||||
*/
|
||||
public Point2D geoToPixel(GeoPosition c, int zoomLevel)
|
||||
{
|
||||
return GeoUtil.getBitmapCoordinate(c, zoomLevel, getInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the TileFactoryInfo describing this TileFactory
|
||||
* @return a TileFactoryInfo
|
||||
*/
|
||||
public TileFactoryInfo getInfo()
|
||||
{
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a tile listener
|
||||
* @param listener the listener
|
||||
*/
|
||||
public void addTileListener(TileListener listener)
|
||||
{
|
||||
tileListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a tile listener
|
||||
* @param listener the listener
|
||||
*/
|
||||
public void removeTileListener(TileListener listener)
|
||||
{
|
||||
tileListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes this TileFactory
|
||||
*/
|
||||
public abstract void dispose();
|
||||
|
||||
/**
|
||||
* Notifies all tile listeners
|
||||
* @param tile the tile
|
||||
*/
|
||||
protected void fireTileLoadedEvent(Tile tile)
|
||||
{
|
||||
for (TileListener listener : tileListeners)
|
||||
{
|
||||
listener.tileLoaded(tile);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to load the tile using, for example, an <code>ExecutorService</code>.
|
||||
* @param tile The tile to load.
|
||||
*/
|
||||
protected abstract void startLoading(Tile tile);
|
||||
|
||||
}
|
||||
325
src/main/java/org/jdesktop/swingx/mapviewer/TileFactoryInfo.java
Normal file
325
src/main/java/org/jdesktop/swingx/mapviewer/TileFactoryInfo.java
Normal file
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
* TileFactoryInfo.java
|
||||
*
|
||||
* Created on June 26, 2006, 10:47 AM
|
||||
*
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.mapviewer;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
/**
|
||||
* A TileFactoryInfo encapsulates all information specific to a map server. This includes everything from the url to
|
||||
* load the map tiles from to the size and depth of the tiles. Theoretically any map server can be used by installing a
|
||||
* customized TileFactoryInfo. Currently
|
||||
* @author joshy
|
||||
*/
|
||||
public class TileFactoryInfo
|
||||
{
|
||||
private int minimumZoomLevel;
|
||||
private int maximumZoomLevel;
|
||||
private int totalMapZoom;
|
||||
// the size of each tile (assumes they are square)
|
||||
private int tileSize = 256;
|
||||
|
||||
/*
|
||||
* The number of tiles wide at each zoom level
|
||||
*/
|
||||
private int[] mapWidthInTilesAtZoom;
|
||||
/**
|
||||
* An array of coordinates in <em>pixels</em> that indicates the center in the world map for the given zoom level.
|
||||
*/
|
||||
private Point2D[] mapCenterInPixelsAtZoom;// = new Point2D.Double[18];
|
||||
|
||||
/**
|
||||
* An array of doubles that contain the number of pixels per degree of longitude at a give zoom level.
|
||||
*/
|
||||
private double[] longitudeDegreeWidthInPixels;
|
||||
|
||||
/**
|
||||
* An array of doubles that contain the number of radians per degree of longitude at a given zoom level (where
|
||||
* longitudeRadianWidthInPixels[0] is the most zoomed out)
|
||||
*/
|
||||
private double[] longitudeRadianWidthInPixels;
|
||||
|
||||
/**
|
||||
* The base url for loading tiles from.
|
||||
*/
|
||||
protected String baseURL;
|
||||
private String xparam;
|
||||
private String yparam;
|
||||
private String zparam;
|
||||
private boolean xr2l = true;
|
||||
private boolean yt2b = true;
|
||||
|
||||
private int defaultZoomLevel;
|
||||
|
||||
/** A name for this info. */
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Creates a new instance of TileFactoryInfo. Note that TileFactoryInfo should be considered invariate, meaning that
|
||||
* subclasses should ensure all of the properties stay the same after the class is constructed. Returning different
|
||||
* values of getTileSize() for example is considered an error and may result in unexpected behavior.
|
||||
* @param minimumZoomLevel The minimum zoom level
|
||||
* @param maximumZoomLevel the maximum zoom level
|
||||
* @param totalMapZoom the top zoom level, essentially the height of the pyramid
|
||||
* @param tileSize the size of the tiles in pixels (must be square)
|
||||
* @param xr2l if the x goes r to l (is this backwards?)
|
||||
* @param yt2b if the y goes top to bottom
|
||||
* @param baseURL the base url for grabbing tiles
|
||||
* @param xparam the x parameter for the tile url
|
||||
* @param yparam the y parameter for the tile url
|
||||
* @param zparam the z parameter for the tile url
|
||||
*/
|
||||
/*
|
||||
* @param xr2l true if tile x is measured from the far left of the map to the far right, or else false if based on
|
||||
* the center line.
|
||||
* @param yt2b true if tile y is measured from the top (north pole) to the bottom (south pole) or else false if
|
||||
* based on the equator.
|
||||
*/
|
||||
public TileFactoryInfo(int minimumZoomLevel, int maximumZoomLevel, int totalMapZoom, int tileSize, boolean xr2l,
|
||||
boolean yt2b, String baseURL, String xparam, String yparam, String zparam)
|
||||
{
|
||||
this("name not provided", minimumZoomLevel, maximumZoomLevel, totalMapZoom, tileSize, xr2l, yt2b, baseURL,
|
||||
xparam, yparam, zparam);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of TileFactoryInfo. Note that TileFactoryInfo should be considered invariate, meaning that
|
||||
* subclasses should ensure all of the properties stay the same after the class is constructed. Returning different
|
||||
* values of getTileSize() for example is considered an error and may result in unexpected behavior.
|
||||
* @param name A name to identify this information.
|
||||
* @param minimumZoomLevel The minimum zoom level
|
||||
* @param maximumZoomLevel the maximum zoom level
|
||||
* @param totalMapZoom the top zoom level, essentially the height of the pyramid
|
||||
* @param tileSize the size of the tiles in pixels (must be square)
|
||||
* @param xr2l if the x goes r to l (is this backwards?)
|
||||
* @param yt2b if the y goes top to bottom
|
||||
* @param baseURL the base url for grabbing tiles
|
||||
* @param xparam the x parameter for the tile url
|
||||
* @param yparam the y parameter for the tile url
|
||||
* @param zparam the z parameter for the tile url
|
||||
*/
|
||||
/*
|
||||
* @param xr2l true if tile x is measured from the far left of the map to the far right, or else false if based on
|
||||
* the center line.
|
||||
* @param yt2b true if tile y is measured from the top (north pole) to the bottom (south pole) or else false if
|
||||
* based on the equator.
|
||||
*/
|
||||
public TileFactoryInfo(String name, int minimumZoomLevel, int maximumZoomLevel, int totalMapZoom, int tileSize,
|
||||
boolean xr2l, boolean yt2b, String baseURL, String xparam, String yparam, String zparam)
|
||||
{
|
||||
this.name = name;
|
||||
this.minimumZoomLevel = minimumZoomLevel;
|
||||
this.maximumZoomLevel = maximumZoomLevel;
|
||||
this.totalMapZoom = totalMapZoom;
|
||||
this.baseURL = baseURL;
|
||||
this.xparam = xparam;
|
||||
this.yparam = yparam;
|
||||
this.zparam = zparam;
|
||||
this.setXr2l(xr2l);
|
||||
this.setYt2b(yt2b);
|
||||
|
||||
this.tileSize = tileSize;
|
||||
|
||||
// init the num tiles wide
|
||||
int tilesize = this.getTileSize(0);
|
||||
|
||||
longitudeDegreeWidthInPixels = new double[totalMapZoom + 1];
|
||||
longitudeRadianWidthInPixels = new double[totalMapZoom + 1];
|
||||
mapCenterInPixelsAtZoom = new Point2D.Double[totalMapZoom + 1];
|
||||
mapWidthInTilesAtZoom = new int[totalMapZoom + 1];
|
||||
|
||||
// for each zoom level
|
||||
for (int z = totalMapZoom; z >= 0; --z)
|
||||
{
|
||||
// how wide is each degree of longitude in pixels
|
||||
longitudeDegreeWidthInPixels[z] = tilesize / 360.0;
|
||||
// how wide is each radian of longitude in pixels
|
||||
longitudeRadianWidthInPixels[z] = tilesize / (2.0 * Math.PI);
|
||||
int t2 = tilesize / 2;
|
||||
mapCenterInPixelsAtZoom[z] = new Point2D.Double(t2, t2);
|
||||
mapWidthInTilesAtZoom[z] = tilesize / this.getTileSize(0);
|
||||
tilesize *= 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the minimum zoom level
|
||||
*/
|
||||
public int getMinimumZoomLevel()
|
||||
{
|
||||
return minimumZoomLevel;
|
||||
}
|
||||
|
||||
// public void setMinimumZoomLevel(int minimumZoomLevel) {
|
||||
// this.minimumZoomLevel = minimumZoomLevel;
|
||||
// }
|
||||
|
||||
/**
|
||||
* @return the maximum zoom level
|
||||
*/
|
||||
public int getMaximumZoomLevel()
|
||||
{
|
||||
return maximumZoomLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the total map zoom level
|
||||
*/
|
||||
public int getTotalMapZoom()
|
||||
{
|
||||
return totalMapZoom;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param zoom the zoom level
|
||||
* @return the map width in tiles
|
||||
*/
|
||||
public int getMapWidthInTilesAtZoom(int zoom)
|
||||
{
|
||||
return mapWidthInTilesAtZoom[zoom];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param zoom the zoom level
|
||||
* @return the map center in pixels
|
||||
*/
|
||||
public Point2D getMapCenterInPixelsAtZoom(int zoom)
|
||||
{
|
||||
return mapCenterInPixelsAtZoom[zoom];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tile url for the specified tile at the specified zoom level. By default it will generate a tile url
|
||||
* using the base url and parameters specified in the constructor. Thus if <PRE><CODE>baseURl =
|
||||
* http://www.myserver.com/maps?version=0.1 xparam = x yparam = y zparam = z tilepoint = [1,2] zoom level = 3
|
||||
* </CODE> </PRE> then the resulting url would be:
|
||||
* <pre><code>http://www.myserver.com/maps?version=0.1&x=1&y=2&z=3</code></pre> Note that the URL can be
|
||||
* a <CODE>file:</CODE> url.
|
||||
* @param zoom the zoom level
|
||||
* @param x the x value
|
||||
* @param y the y value
|
||||
* @return a valid url to load the tile
|
||||
*/
|
||||
|
||||
public String getTileUrl(int x, int y, int zoom)
|
||||
{
|
||||
// System.out.println("getting tile at zoom: " + zoom);
|
||||
// System.out.println("map width at zoom = " + getMapWidthInTilesAtZoom(zoom));
|
||||
String ypart = "&" + yparam + "=" + y;
|
||||
// System.out.println("ypart = " + ypart);
|
||||
|
||||
if (!yt2b)
|
||||
{
|
||||
int tilemax = getMapWidthInTilesAtZoom(zoom);
|
||||
// int y = tilePoint.getY();
|
||||
ypart = "&" + yparam + "=" + (tilemax / 2 - y - 1);
|
||||
}
|
||||
// System.out.println("new ypart = " + ypart);
|
||||
String url = baseURL + "&" + xparam + "=" + x + ypart +
|
||||
// "&" + yparam + "=" + tilePoint.getY() +
|
||||
"&" + zparam + "=" + zoom;
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tile size.
|
||||
* @param zoom the zoom level
|
||||
* @return the tile size
|
||||
*/
|
||||
public int getTileSize(int zoom)
|
||||
{
|
||||
return tileSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param zoom the zoom level
|
||||
* @return the longitude degree width in pixels
|
||||
*/
|
||||
public double getLongitudeDegreeWidthInPixels(int zoom)
|
||||
{
|
||||
return longitudeDegreeWidthInPixels[zoom];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param zoom the zoom level
|
||||
* @return the longitude radian width in pixels
|
||||
*/
|
||||
public double getLongitudeRadianWidthInPixels(int zoom)
|
||||
{
|
||||
return longitudeRadianWidthInPixels[zoom];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return indicates if the X coordinates of tiles go from right to left or left to right.
|
||||
*/
|
||||
public boolean isXr2l()
|
||||
{
|
||||
return xr2l;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param xr2l indicates if the X coordinates of tiles go from right to left or left to right.
|
||||
*/
|
||||
public void setXr2l(boolean xr2l)
|
||||
{
|
||||
this.xr2l = xr2l;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return indicates if the Y coordinates of tiles go from right to left or left to right.
|
||||
*/
|
||||
public boolean isYt2b()
|
||||
{
|
||||
return yt2b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param yt2b indicates if the Y coordinates of tiles go from right to left or left to right.
|
||||
*/
|
||||
public void setYt2b(boolean yt2b)
|
||||
{
|
||||
this.yt2b = yt2b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the default zoom level
|
||||
*/
|
||||
public int getDefaultZoomLevel()
|
||||
{
|
||||
return defaultZoomLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param defaultZoomLevel the default zoom level
|
||||
*/
|
||||
public void setDefaultZoomLevel(int defaultZoomLevel)
|
||||
{
|
||||
this.defaultZoomLevel = defaultZoomLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of this info.
|
||||
* @return Returns the name of this info class for debugging or GUI widgets.
|
||||
*/
|
||||
public String getName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the base URL
|
||||
*/
|
||||
public String getBaseURL()
|
||||
{
|
||||
return baseURL;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package org.jdesktop.swingx.mapviewer;
|
||||
|
||||
/**
|
||||
* Notified when the status of a tile has changed
|
||||
* @author Martin Steiger
|
||||
*/
|
||||
public interface TileListener
|
||||
{
|
||||
/**
|
||||
* Notification when a tile is loaded
|
||||
* @param tile the tile
|
||||
*/
|
||||
public void tileLoaded(Tile tile);
|
||||
|
||||
}
|
||||
25
src/main/java/org/jdesktop/swingx/mapviewer/Waypoint.java
Normal file
25
src/main/java/org/jdesktop/swingx/mapviewer/Waypoint.java
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Waypoint.java
|
||||
*
|
||||
* Created on March 30, 2006, 5:22 PM
|
||||
*
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.mapviewer;
|
||||
|
||||
|
||||
/**
|
||||
* A Waypoint is a GeoPosition that can be
|
||||
* drawn on a may using a WaypointPainter.
|
||||
* @author joshy
|
||||
*/
|
||||
public interface Waypoint
|
||||
{
|
||||
/**
|
||||
* Get the current GeoPosition of this Waypoint
|
||||
* @return the current position
|
||||
*/
|
||||
public GeoPosition getPosition();
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* WaypointMapOverlay.java
|
||||
*
|
||||
* Created on April 1, 2006, 4:59 PM
|
||||
*
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.mapviewer;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Rectangle;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jdesktop.swingx.JXMapViewer;
|
||||
import org.jdesktop.swingx.painter.AbstractPainter;
|
||||
|
||||
/**
|
||||
* Paints waypoints on the JXMapViewer. This is an
|
||||
* instance of Painter that only can draw on to JXMapViewers.
|
||||
* @param <W> the waypoint type
|
||||
* @author rbair
|
||||
*/
|
||||
public class WaypointPainter<W extends Waypoint> extends AbstractPainter<JXMapViewer>
|
||||
{
|
||||
private WaypointRenderer<? super W> renderer = new DefaultWaypointRenderer();
|
||||
private Set<W> waypoints = new HashSet<W>();
|
||||
|
||||
/**
|
||||
* Creates a new instance of WaypointPainter
|
||||
*/
|
||||
public WaypointPainter()
|
||||
{
|
||||
setAntialiasing(true);
|
||||
setCacheable(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the waypoint renderer to use when painting waypoints
|
||||
* @param r the new WaypointRenderer to use
|
||||
*/
|
||||
public void setRenderer(WaypointRenderer<W> r)
|
||||
{
|
||||
this.renderer = r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current set of waypoints to paint
|
||||
* @return a typed Set of Waypoints
|
||||
*/
|
||||
public Set<W> getWaypoints()
|
||||
{
|
||||
return Collections.unmodifiableSet(waypoints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current set of waypoints to paint
|
||||
* @param waypoints the new Set of Waypoints to use
|
||||
*/
|
||||
public void setWaypoints(Set<? extends W> waypoints)
|
||||
{
|
||||
this.waypoints.clear();
|
||||
this.waypoints.addAll(waypoints);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPaint(Graphics2D g, JXMapViewer map, int width, int height)
|
||||
{
|
||||
if (renderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Rectangle viewportBounds = map.getViewportBounds();
|
||||
|
||||
g.translate(-viewportBounds.getX(), -viewportBounds.getY());
|
||||
|
||||
for (W w : getWaypoints())
|
||||
{
|
||||
renderer.paintWaypoint(g, map, w);
|
||||
}
|
||||
|
||||
g.translate(viewportBounds.getX(), viewportBounds.getY());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* WaypointRenderer.java
|
||||
*
|
||||
* Created on March 30, 2006, 5:56 PM
|
||||
*
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.mapviewer;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
|
||||
import org.jdesktop.swingx.JXMapViewer;
|
||||
|
||||
/**
|
||||
* A interface that draws waypoints. Implementations of WaypointRenderer can
|
||||
* be set on a WayPointPainter to draw waypoints on a JXMapViewer
|
||||
* @param <W> the waypoint type
|
||||
* @author joshua.marinacci@sun.com
|
||||
*/
|
||||
public interface WaypointRenderer<W>
|
||||
{
|
||||
/**
|
||||
* paint the specified waypoint on the specified map and graphics context
|
||||
* @param g the graphics2D object
|
||||
* @param map the map
|
||||
* @param waypoint the waypoint
|
||||
*/
|
||||
public void paintWaypoint(Graphics2D g, JXMapViewer map, W waypoint);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package org.jdesktop.swingx.mapviewer.bmng;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
import org.jdesktop.swingx.mapviewer.DefaultTileFactory;
|
||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||
|
||||
/**
|
||||
* @author joshy
|
||||
*/
|
||||
public class CylindricalProjectionTileFactory extends DefaultTileFactory
|
||||
{
|
||||
/**
|
||||
* Uses {@link SLMapServerInfo}
|
||||
*/
|
||||
public CylindricalProjectionTileFactory()
|
||||
{
|
||||
this(new SLMapServerInfo());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param info the tile factory info
|
||||
*/
|
||||
public CylindricalProjectionTileFactory(SLMapServerInfo info)
|
||||
{
|
||||
super(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMapSize(int zoom)
|
||||
{
|
||||
int midpoint = ((SLMapServerInfo) getInfo()).getMidpoint();
|
||||
if (zoom < midpoint)
|
||||
{
|
||||
int w = (int) Math.pow(2, midpoint - zoom);
|
||||
return new Dimension(w, w / 2);
|
||||
// return super.getMapSize(zoom);
|
||||
}
|
||||
return new Dimension(2, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D geoToPixel(GeoPosition c, int zoom)
|
||||
{
|
||||
// calc the pixels per degree
|
||||
Dimension mapSizeInTiles = getMapSize(zoom);
|
||||
// double size_in_tiles = (double)getInfo().getMapWidthInTilesAtZoom(zoom);
|
||||
// double size_in_tiles = Math.pow(2, getInfo().getTotalMapZoom() - zoom);
|
||||
double size_in_pixels = mapSizeInTiles.getWidth() * getInfo().getTileSize(zoom);
|
||||
double ppd = size_in_pixels / 360;
|
||||
|
||||
// the center of the world
|
||||
double centerX = this.getTileSize(zoom) * mapSizeInTiles.getWidth() / 2;
|
||||
double centerY = this.getTileSize(zoom) * mapSizeInTiles.getHeight() / 2;
|
||||
|
||||
double x = c.getLongitude() * ppd + centerX;
|
||||
double y = -c.getLatitude() * ppd + centerY;
|
||||
|
||||
return new Point2D.Double(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GeoPosition pixelToGeo(Point2D pix, int zoom)
|
||||
{
|
||||
// calc the pixels per degree
|
||||
Dimension mapSizeInTiles = getMapSize(zoom);
|
||||
double size_in_pixels = mapSizeInTiles.getWidth() * getInfo().getTileSize(zoom);
|
||||
double ppd = size_in_pixels / 360;
|
||||
|
||||
// the center of the world
|
||||
double centerX = this.getTileSize(zoom) * mapSizeInTiles.getWidth() / 2;
|
||||
double centerY = this.getTileSize(zoom) * mapSizeInTiles.getHeight() / 2;
|
||||
|
||||
double lon = (pix.getX() - centerX) / ppd;
|
||||
double lat = -(pix.getY() - centerY) / ppd;
|
||||
|
||||
return new GeoPosition(lat, lon);
|
||||
}
|
||||
|
||||
/*
|
||||
* x = lat * ppd + fact x - fact = lat * ppd (x - fact)/ppd = lat y = -lat*ppd + fact -(y-fact)/ppd = lat
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
/*******************************************************************************
|
||||
* SDR Trunk
|
||||
* Copyright (C) 2014 Dennis Sheirer
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
******************************************************************************/
|
||||
package org.jdesktop.swingx.mapviewer.bmng;
|
||||
|
||||
import org.jdesktop.swingx.mapviewer.TileFactoryInfo;
|
||||
|
||||
/**
|
||||
* A TileFactoryInfo subclass which knows how to connect to the SwingLabs map server. This server contains 2k resolution
|
||||
* Blue Marble data from NASA.
|
||||
*/
|
||||
public class SLMapServerInfo extends TileFactoryInfo
|
||||
{
|
||||
private static final int pyramid_top = 8 + 1;
|
||||
private static final int midpoint = 5;
|
||||
private static final int normal_tile_size = 675;
|
||||
|
||||
/**
|
||||
* Uses the default base URL
|
||||
*/
|
||||
public SLMapServerInfo()
|
||||
{
|
||||
this("http://maps.joshy.net/tiles/bmng_tiles_3");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param baseURL the base URL
|
||||
*/
|
||||
public SLMapServerInfo(String baseURL)
|
||||
{
|
||||
// joshy: this was version one of the tiles
|
||||
// super(0, 5, 5, 256, true, false, "http://maps.joshy.net/bmng_tiles_1", "", "", "");
|
||||
// super(0, pyramid_top-1, pyramid_top,
|
||||
// 675,
|
||||
// true, false, "http://maps.joshy.net/bmng_tiles_2", "", "", "");
|
||||
// super(0, pyramid_top-1, pyramid_top,
|
||||
// normal_tile_size,
|
||||
// true, false, "file:/Users/joshy/projects/java.net/ImageTileCutter/tiles", "", "", "");
|
||||
super(0, pyramid_top - 1, pyramid_top, normal_tile_size, true, false, baseURL, "", "", "");
|
||||
setDefaultZoomLevel(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the midpoint
|
||||
*/
|
||||
public int getMidpoint()
|
||||
{
|
||||
return midpoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTileSize(int zoom)
|
||||
{
|
||||
int size = super.getTileSize(zoom);
|
||||
if (zoom < midpoint)
|
||||
{
|
||||
return size;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < zoom + 1 - midpoint; i++)
|
||||
{
|
||||
size = size / 2;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMapWidthInTilesAtZoom(int zoom)
|
||||
{
|
||||
if (zoom < midpoint)
|
||||
{
|
||||
return (int) Math.pow(2, midpoint - zoom);
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTileUrl(int x, int y, int zoom)
|
||||
{
|
||||
int ty = y;
|
||||
int tx = x;
|
||||
|
||||
// int width_in_tiles = (int)Math.pow(2,pyramid_top-zoom);
|
||||
int width_in_tiles = getMapWidthInTilesAtZoom(zoom);
|
||||
// System.out.println("width in tiles = " + width_in_tiles + " x = " + tx + " y = " + ty);
|
||||
if (ty < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (zoom < midpoint)
|
||||
{
|
||||
if (ty >= width_in_tiles / 2)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ty != 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String url = this.baseURL + "/" + zoom + "/" + ty + "/" + tx + ".jpg";
|
||||
// System.out.println("returning: " + url);
|
||||
return url;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* EmptyTileFactory.java
|
||||
*
|
||||
* Created on June 7, 2006, 4:58 PM
|
||||
*
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.mapviewer.empty;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import org.jdesktop.swingx.mapviewer.Tile;
|
||||
import org.jdesktop.swingx.mapviewer.TileFactory;
|
||||
import org.jdesktop.swingx.mapviewer.TileFactoryInfo;
|
||||
|
||||
/**
|
||||
* A null implementation of TileFactory. Draws empty areas.
|
||||
* @author joshy
|
||||
*/
|
||||
public class EmptyTileFactory extends TileFactory
|
||||
{
|
||||
/**
|
||||
* The empty tile image.
|
||||
*/
|
||||
private BufferedImage emptyTile;
|
||||
|
||||
/**
|
||||
* Creates a new instance of EmptyTileFactory
|
||||
*/
|
||||
public EmptyTileFactory()
|
||||
{
|
||||
this(new TileFactoryInfo("EmptyTileFactory 256x256", 1, 15, 17, 256, true, true, "", "x", "y", "z"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of EmptyTileFactory using the specified info.
|
||||
* @param info the tile factory info
|
||||
*/
|
||||
public EmptyTileFactory(TileFactoryInfo info)
|
||||
{
|
||||
super(info);
|
||||
int tileSize = info.getTileSize(info.getMinimumZoomLevel());
|
||||
emptyTile = new BufferedImage(tileSize, tileSize, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g = emptyTile.createGraphics();
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g.setColor(Color.GRAY);
|
||||
g.fillRect(0, 0, tileSize, tileSize);
|
||||
g.setColor(Color.WHITE);
|
||||
g.drawOval(10, 10, tileSize - 20, tileSize - 20);
|
||||
g.fillOval(70, 50, 20, 20);
|
||||
g.fillOval(tileSize - 90, 50, 20, 20);
|
||||
g.fillOval(tileSize / 2 - 10, tileSize / 2 - 10, 20, 20);
|
||||
g.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an instance of an empty tile for the given tile position and zoom on the world map.
|
||||
* @param x The tile's x position on the world map.
|
||||
* @param y The tile's y position on the world map.
|
||||
* @param zoom The current zoom level.
|
||||
*/
|
||||
@Override
|
||||
public Tile getTile(int x, int y, int zoom)
|
||||
{
|
||||
return new Tile(x, y, zoom)
|
||||
{
|
||||
@Override
|
||||
public synchronized boolean isLoaded()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage getImage()
|
||||
{
|
||||
return emptyTile;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose()
|
||||
{
|
||||
// noop
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to load the tile using, for example, an <code>ExecutorService</code>.
|
||||
* @param tile The tile to load.
|
||||
*/
|
||||
@Override
|
||||
protected void startLoading(Tile tile)
|
||||
{
|
||||
// noop
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* ESRITileFactory.java
|
||||
*
|
||||
* Created on November 7, 2006, 10:51 AM
|
||||
*
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.mapviewer.esri;
|
||||
|
||||
import java.awt.geom.Point2D;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
import org.jdesktop.swingx.mapviewer.DefaultTileFactory;
|
||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||
import org.jdesktop.swingx.mapviewer.TileFactoryInfo;
|
||||
import org.jdesktop.swingx.mapviewer.util.GeoUtil;
|
||||
|
||||
/**
|
||||
* @author rbair
|
||||
*/
|
||||
public class ESRITileFactory extends DefaultTileFactory
|
||||
{
|
||||
private static final String projection = "8"; // mercator projection
|
||||
private static final String format = "png"; // get pngs back
|
||||
private String userId;
|
||||
private String datasource; // should be enum
|
||||
|
||||
/** Creates a new instance of ESRITileFactory */
|
||||
public ESRITileFactory()
|
||||
{
|
||||
super(new ESRITileProviderInfo());
|
||||
((ESRITileProviderInfo) super.getInfo()).factory = this;
|
||||
datasource = "ArcWeb:TA.Streets.NA";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id the ESRI user id
|
||||
*/
|
||||
public void setUserID(String id)
|
||||
{
|
||||
this.userId = id;
|
||||
}
|
||||
|
||||
private static final class ESRITileProviderInfo extends TileFactoryInfo
|
||||
{
|
||||
private ESRITileFactory factory;
|
||||
|
||||
private ESRITileProviderInfo()
|
||||
{
|
||||
super(0, 17, 18, 256, false, true, "http://www.arcwebservices.com/services/v2006/restmap?actn=getMap", "","", "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTileUrl(int x, int y, int zoom)
|
||||
{
|
||||
// "&usrid=&ds=&c=-117.1817|34.0556&sf=52500&fmt=&ocs=";
|
||||
// System.out.println("getting tile at zoom: " + zoom);
|
||||
// System.out.println("map width at zoom = " + getMapWidthInTilesAtZoom(zoom));
|
||||
|
||||
// provide the center point of the tile, in lat/long coords
|
||||
int tileY = y;
|
||||
int tileX = x;
|
||||
int pixelX = tileX * factory.getTileSize(zoom) + (factory.getTileSize(zoom) / 2);
|
||||
int pixelY = tileY * factory.getTileSize(zoom) + (factory.getTileSize(zoom) / 2);
|
||||
|
||||
GeoPosition latlong = GeoUtil.getPosition(new Point2D.Double(pixelX, pixelY), zoom, this);
|
||||
|
||||
// Chris is going to hate me for this (relying on 72dpi!), but:
|
||||
// 72 pixels per inch. The earth is 24,859.82 miles in circumference, at the equator.
|
||||
// Thus, the earth is 24,859.82 * 5280 * 12 * 72 pixels in circumference.
|
||||
|
||||
double numFeetPerDegreeLong = 24859.82 * 5280 / 360; // the number of feet per degree longitude at the
|
||||
// equator
|
||||
double numPixelsPerDegreeLong = getLongitudeDegreeWidthInPixels(zoom);
|
||||
double numPixelsPerFoot = 96 * 12;
|
||||
int sf = (int) (numFeetPerDegreeLong / (numPixelsPerDegreeLong / numPixelsPerFoot));
|
||||
|
||||
// round lat and long to 5 decimal places
|
||||
BigDecimal lat = new BigDecimal(latlong.getLatitude());
|
||||
BigDecimal lon = new BigDecimal(latlong.getLongitude());
|
||||
lat = lat.setScale(5, RoundingMode.DOWN);
|
||||
lon = lon.setScale(5, RoundingMode.DOWN);
|
||||
System.out.println("Tile : [" + tileX + ", " + tileY + "]");
|
||||
System.out.println("Pixel : [" + pixelX + ", " + pixelY + "]");
|
||||
System.out.println("Lat/Long : [" + latlong.getLatitude() + ", " + latlong.getLongitude() + "]");
|
||||
System.out.println("Lat2/Long2: [" + lat.doubleValue() + ", " + lon.doubleValue() + "]");
|
||||
|
||||
String url = baseURL + "&usrid=" + factory.userId + "&ds=" + factory.datasource + "&c=" + lon.doubleValue()
|
||||
+ "%7C" + lat.doubleValue() + "&sf=" + sf + // 52500" +
|
||||
"&fmt=" + format + "&ocs=" + projection;
|
||||
System.out.println("the URL: " + url);
|
||||
return url;
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
BIN
src/main/java/org/jdesktop/swingx/mapviewer/resources/minus.png
Normal file
BIN
src/main/java/org/jdesktop/swingx/mapviewer/resources/minus.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 75 B |
BIN
src/main/java/org/jdesktop/swingx/mapviewer/resources/plus.png
Normal file
BIN
src/main/java/org/jdesktop/swingx/mapviewer/resources/plus.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 82 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
167
src/main/java/org/jdesktop/swingx/mapviewer/util/GeoUtil.java
Normal file
167
src/main/java/org/jdesktop/swingx/mapviewer/util/GeoUtil.java
Normal file
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* GeoUtil.java
|
||||
*
|
||||
* Created on June 26, 2006, 10:44 AM
|
||||
*
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.mapviewer.util;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jdesktop.swingx.JXMapViewer;
|
||||
import org.jdesktop.swingx.mapviewer.GeoBounds;
|
||||
import org.jdesktop.swingx.mapviewer.GeoPosition;
|
||||
import org.jdesktop.swingx.mapviewer.TileFactory;
|
||||
import org.jdesktop.swingx.mapviewer.TileFactoryInfo;
|
||||
|
||||
/**
|
||||
* These are math utilities for converting between pixels, tiles, and geographic coordinates. Implements a Google Maps
|
||||
* style mercator projection.
|
||||
* @author joshy
|
||||
*/
|
||||
public final class GeoUtil
|
||||
{
|
||||
/**
|
||||
* @param zoom the zoom level
|
||||
* @param info the tile factory info
|
||||
* @return the size of the map at the given zoom, in tiles (num tiles tall by num tiles wide)
|
||||
*/
|
||||
public static Dimension getMapSize(int zoom, TileFactoryInfo info)
|
||||
{
|
||||
return new Dimension(info.getMapWidthInTilesAtZoom(zoom), info.getMapWidthInTilesAtZoom(zoom));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x the x value
|
||||
* @param y the y value
|
||||
* @param zoomLevel the zoom level
|
||||
* @param info the tile factory info
|
||||
* @return true if this point in <em>tiles</em> is valid at this zoom level. For example, if the zoom level is 0
|
||||
* (zoomed all the way out, where there is only one tile), then x,y must be 0,0
|
||||
*/
|
||||
public static boolean isValidTile(int x, int y, int zoomLevel, TileFactoryInfo info)
|
||||
{
|
||||
// int x = (int)coord.getX();
|
||||
// int y = (int)coord.getY();
|
||||
// if off the map to the top or left
|
||||
if (x < 0 || y < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// if of the map to the right
|
||||
if (info.getMapCenterInPixelsAtZoom(zoomLevel).getX() * 2 <= x * info.getTileSize(zoomLevel))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// if off the map to the bottom
|
||||
if (info.getMapCenterInPixelsAtZoom(zoomLevel).getY() * 2 <= y * info.getTileSize(zoomLevel))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// if out of zoom bounds
|
||||
if (zoomLevel < info.getMinimumZoomLevel() || zoomLevel > info.getMaximumZoomLevel())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a position (latitude/longitude pair) and a zoom level, return the appropriate point in <em>pixels</em>. The
|
||||
* zoom level is necessary because pixel coordinates are in terms of the zoom level
|
||||
* @param c A lat/lon pair
|
||||
* @param zoomLevel the zoom level to extract the pixel coordinate for
|
||||
* @param info the tile factory info
|
||||
* @return the coordinate
|
||||
*/
|
||||
public static Point2D getBitmapCoordinate(GeoPosition c, int zoomLevel, TileFactoryInfo info)
|
||||
{
|
||||
return getBitmapCoordinate(c.getLatitude(), c.getLongitude(), zoomLevel, info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a position (latitude/longitude pair) and a zoom level, return the appropriate point in <em>pixels</em>. The
|
||||
* zoom level is necessary because pixel coordinates are in terms of the zoom level
|
||||
* @param latitude the latitude
|
||||
* @param longitude the longitude
|
||||
* @param zoomLevel the zoom level to extract the pixel coordinate for
|
||||
* @param info the tile factory info
|
||||
* @return the coordinate
|
||||
*/
|
||||
public static Point2D getBitmapCoordinate(double latitude, double longitude, int zoomLevel, TileFactoryInfo info)
|
||||
{
|
||||
double x = info.getMapCenterInPixelsAtZoom(zoomLevel).getX() + longitude
|
||||
* info.getLongitudeDegreeWidthInPixels(zoomLevel);
|
||||
double e = Math.sin(latitude * (Math.PI / 180.0));
|
||||
if (e > 0.9999)
|
||||
{
|
||||
e = 0.9999;
|
||||
}
|
||||
if (e < -0.9999)
|
||||
{
|
||||
e = -0.9999;
|
||||
}
|
||||
double y = info.getMapCenterInPixelsAtZoom(zoomLevel).getY() + 0.5 * Math.log((1 + e) / (1 - e)) * -1
|
||||
* (info.getLongitudeRadianWidthInPixels(zoomLevel));
|
||||
return new Point2D.Double(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an on screen pixel coordinate and a zoom level to a geo position
|
||||
* @param pixelCoordinate the coordinate in pixels
|
||||
* @param zoom the zoom level
|
||||
* @param info the tile factory info
|
||||
* @return a geo position
|
||||
*/
|
||||
public static GeoPosition getPosition(Point2D pixelCoordinate, int zoom, TileFactoryInfo info)
|
||||
{
|
||||
// p(" --bitmap to latlon : " + coord + " " + zoom);
|
||||
double wx = pixelCoordinate.getX();
|
||||
double wy = pixelCoordinate.getY();
|
||||
// this reverses getBitmapCoordinates
|
||||
double flon = (wx - info.getMapCenterInPixelsAtZoom(zoom).getX()) / info.getLongitudeDegreeWidthInPixels(zoom);
|
||||
double e1 = (wy - info.getMapCenterInPixelsAtZoom(zoom).getY())
|
||||
/ (-1 * info.getLongitudeRadianWidthInPixels(zoom));
|
||||
double e2 = (2 * Math.atan(Math.exp(e1)) - Math.PI / 2) / (Math.PI / 180.0);
|
||||
double flat = e2;
|
||||
GeoPosition wc = new GeoPosition(flat, flon);
|
||||
return wc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the map bounds.
|
||||
* @param mapViewer The map viewer.
|
||||
* @return Returns the bounds.
|
||||
*/
|
||||
public static GeoBounds getMapBounds(JXMapViewer mapViewer)
|
||||
{
|
||||
return new GeoBounds(getMapGeoBounds(mapViewer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the bounds as a set of two <code>GeoPosition</code> objects.
|
||||
* @param mapViewer The map viewer.
|
||||
* @return Returns the set of two <code>GeoPosition</code> objects that represent the north west and south east
|
||||
* corners of the map.
|
||||
*/
|
||||
private static Set<GeoPosition> getMapGeoBounds(JXMapViewer mapViewer)
|
||||
{
|
||||
Set<GeoPosition> set = new HashSet<GeoPosition>();
|
||||
TileFactory tileFactory = mapViewer.getTileFactory();
|
||||
int zoom = mapViewer.getZoom();
|
||||
Rectangle2D bounds = mapViewer.getViewportBounds();
|
||||
Point2D pt = new Point2D.Double(bounds.getX(), bounds.getY());
|
||||
set.add(tileFactory.pixelToGeo(pt, zoom));
|
||||
pt = new Point2D.Double(bounds.getX() + bounds.getWidth(), bounds.getY() + bounds.getHeight());
|
||||
set.add(tileFactory.pixelToGeo(pt, zoom));
|
||||
return set;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* MercatorUtils.java
|
||||
*
|
||||
* Created on October 7, 2006, 6:02 PM
|
||||
*
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.mapviewer.util;
|
||||
|
||||
/**
|
||||
* A utility class of methods that help when
|
||||
* dealing with standard Mercator projections.
|
||||
* @author joshua.marinacci@sun.com
|
||||
*/
|
||||
public final class MercatorUtils
|
||||
{
|
||||
|
||||
/**
|
||||
* Creates a new instance of MercatorUtils
|
||||
*/
|
||||
private MercatorUtils()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param longitudeDegrees the longitude in degrees
|
||||
* @param radius the world radius in pixels
|
||||
* @return the x value
|
||||
*/
|
||||
public static int longToX(double longitudeDegrees, double radius)
|
||||
{
|
||||
double longitude = Math.toRadians(longitudeDegrees);
|
||||
return (int) (radius * longitude);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param latitudeDegrees the latitude in degrees
|
||||
* @param radius the world radius in pixels
|
||||
* @return the y value
|
||||
*/
|
||||
public static int latToY(double latitudeDegrees, double radius)
|
||||
{
|
||||
double latitude = Math.toRadians(latitudeDegrees);
|
||||
double y = radius / 2.0 * Math.log((1.0 + Math.sin(latitude)) / (1.0 - Math.sin(latitude)));
|
||||
return (int) y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param x the x value
|
||||
* @param radius the world radius in pixels
|
||||
* @return the longitude in degrees
|
||||
*/
|
||||
public static double xToLong(int x, double radius)
|
||||
{
|
||||
double longRadians = x / radius;
|
||||
double longDegrees = Math.toDegrees(longRadians);
|
||||
/*
|
||||
* The user could have panned around the world a lot of times. Lat long goes from -180 to 180. So every time a
|
||||
* user gets to 181 we want to subtract 360 degrees. Every time a user gets to -181 we want to add 360 degrees.
|
||||
*/
|
||||
int rotations = (int) Math.floor((longDegrees + 180) / 360);
|
||||
double longitude = longDegrees - (rotations * 360);
|
||||
return longitude;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param y the y value
|
||||
* @param radius the world radius in pixels
|
||||
* @return the latitude in degrees
|
||||
*/
|
||||
public static double yToLat(int y, double radius)
|
||||
{
|
||||
double latitude = (Math.PI / 2) - (2 * Math.atan(Math.exp(-1.0 * y / radius)));
|
||||
return Math.toDegrees(latitude);
|
||||
}
|
||||
}
|
||||
113
src/main/java/org/jdesktop/swingx/mapviewer/wms/WMSService.java
Normal file
113
src/main/java/org/jdesktop/swingx/mapviewer/wms/WMSService.java
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* WMSService.java
|
||||
*
|
||||
* Created on October 7, 2006, 6:06 PM
|
||||
*
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.mapviewer.wms;
|
||||
|
||||
import org.jdesktop.swingx.mapviewer.util.MercatorUtils;
|
||||
|
||||
/**
|
||||
* A class that represents a WMS mapping service.
|
||||
* See http://en.wikipedia.org/wiki/Web_Map_Service for more information.
|
||||
* @author joshy
|
||||
*/
|
||||
public class WMSService
|
||||
{
|
||||
private String baseUrl;
|
||||
private String layer;
|
||||
|
||||
/**
|
||||
* Creates a new instance of WMSService
|
||||
* TODO: not working -> remove
|
||||
*/
|
||||
public WMSService()
|
||||
{
|
||||
// by default use a known nasa server
|
||||
setLayer("BMNG");
|
||||
setBaseUrl("http://wms.jpl.nasa.gov/wms.cgi?");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param baseUrl the base URL
|
||||
* @param layer the layer
|
||||
*/
|
||||
public WMSService(String baseUrl, String layer)
|
||||
{
|
||||
this.baseUrl = baseUrl;
|
||||
this.layer = layer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convertes to a WMS URL
|
||||
* @param x the x coordinate
|
||||
* @param y the y coordinate
|
||||
* @param zoom the zomm factor
|
||||
* @param tileSize the tile size
|
||||
* @return a URL request string
|
||||
*/
|
||||
public String toWMSURL(int x, int y, int zoom, int tileSize)
|
||||
{
|
||||
String format = "image/jpeg";
|
||||
String styles = "";
|
||||
String srs = "EPSG:4326";
|
||||
int ts = tileSize;
|
||||
int circumference = widthOfWorldInPixels(zoom, tileSize);
|
||||
double radius = circumference / (2 * Math.PI);
|
||||
double ulx = MercatorUtils.xToLong(x * ts, radius);
|
||||
double uly = MercatorUtils.yToLat(y * ts, radius);
|
||||
double lrx = MercatorUtils.xToLong((x + 1) * ts, radius);
|
||||
double lry = MercatorUtils.yToLat((y + 1) * ts, radius);
|
||||
String bbox = ulx + "," + uly + "," + lrx + "," + lry;
|
||||
String url = getBaseUrl() + "version=1.1.1&request=" + "GetMap&Layers=" + layer + "&format=" + format
|
||||
+ "&BBOX=" + bbox + "&width=" + ts + "&height=" + ts + "&SRS=" + srs + "&Styles=" + styles +
|
||||
// "&transparent=TRUE"+
|
||||
"";
|
||||
return url;
|
||||
}
|
||||
|
||||
private int widthOfWorldInPixels(int zoom, int TILE_SIZE)
|
||||
{
|
||||
// int TILE_SIZE = 256;
|
||||
int tiles = (int) Math.pow(2, zoom);
|
||||
int circumference = TILE_SIZE * tiles;
|
||||
return circumference;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the layer
|
||||
*/
|
||||
public String getLayer()
|
||||
{
|
||||
return layer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param layer the layer
|
||||
*/
|
||||
public void setLayer(String layer)
|
||||
{
|
||||
this.layer = layer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the base URL
|
||||
*/
|
||||
public String getBaseUrl()
|
||||
{
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param baseUrl the base URL
|
||||
*/
|
||||
public void setBaseUrl(String baseUrl)
|
||||
{
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* WMSTileFactory.java
|
||||
*
|
||||
* Created on October 7, 2006, 6:07 PM
|
||||
*
|
||||
* To change this template, choose Tools | Template Manager
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.mapviewer.wms;
|
||||
|
||||
import org.jdesktop.swingx.mapviewer.DefaultTileFactory;
|
||||
import org.jdesktop.swingx.mapviewer.TileFactoryInfo;
|
||||
|
||||
/**
|
||||
* A tile factory that uses a WMS service.
|
||||
* @author joshy
|
||||
*/
|
||||
public class WMSTileFactory extends DefaultTileFactory
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of WMSTileFactory
|
||||
* @param wms the WMSService
|
||||
*/
|
||||
public WMSTileFactory(final WMSService wms)
|
||||
{
|
||||
// tile size and x/y orientation is r2l & t2b
|
||||
super(new TileFactoryInfo(0, 15, 17, 500, true, true, "", "x", "y", "zoom")
|
||||
{
|
||||
@Override
|
||||
public String getTileUrl(int x, int y, int zoom)
|
||||
{
|
||||
int zz = 17 - zoom;
|
||||
int z = 4;
|
||||
z = (int) Math.pow(2, (double) zz - 1);
|
||||
return wms.toWMSURL(x - z, z - 1 - y, zz, getTileSize(zoom));
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
441
src/main/java/org/jdesktop/swingx/painter/AbstractPainter.java
Normal file
441
src/main/java/org/jdesktop/swingx/painter/AbstractPainter.java
Normal file
@@ -0,0 +1,441 @@
|
||||
/*
|
||||
* $Id: AbstractPainter.java 4082 2011-11-15 18:39:43Z kschaefe $
|
||||
*
|
||||
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
|
||||
* Santa Clara, California 95054, U.S.A. All rights reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.painter;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Composite;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.BufferedImageOp;
|
||||
import java.lang.ref.SoftReference;
|
||||
|
||||
import org.jdesktop.beans.AbstractBean;
|
||||
import org.jdesktop.swingx.util.GraphicsUtilities;
|
||||
|
||||
/**
|
||||
* <p>A convenient base class from which concrete {@link Painter} implementations may
|
||||
* extend. It extends {@link org.jdesktop.beans.AbstractBean} as a convenience for
|
||||
* adding property change notification support. In addition, <code>AbstractPainter</code>
|
||||
* provides subclasses with the ability to cacheable painting operations, configure the
|
||||
* drawing surface with common settings (such as antialiasing and interpolation), and
|
||||
* toggle whether a subclass paints or not via the <code>visibility</code> property.</p>
|
||||
*
|
||||
* <p>Subclasses of <code>AbstractPainter</code> generally need only override the
|
||||
* {@link #doPaint(Graphics2D, Object, int, int)} method. If a subclass requires more control
|
||||
* over whether caching is enabled, or for configuring the graphics state, then it
|
||||
* may override the appropriate protected methods to interpose its own behavior.</p>
|
||||
*
|
||||
* <p>For example, here is the doPaint method of a simple <code>Painter</code> that
|
||||
* paints an opaque rectangle:
|
||||
* <pre><code>
|
||||
* public void doPaint(Graphics2D g, T obj, int width, int height) {
|
||||
* g.setPaint(Color.BLUE);
|
||||
* g.fillRect(0, 0, width, height);
|
||||
* }
|
||||
* </code></pre></p>
|
||||
*
|
||||
* @author rbair
|
||||
* @param <T> an optional configuration parameter
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public abstract class AbstractPainter<T> extends AbstractBean implements Painter<T> {
|
||||
/**
|
||||
* An enum representing the possible interpolation values of Bicubic, Bilinear, and
|
||||
* Nearest Neighbor. These map to the underlying RenderingHints,
|
||||
* but are easier to use and serialization safe.
|
||||
*/
|
||||
public enum Interpolation {
|
||||
/**
|
||||
* use bicubic interpolation
|
||||
*/
|
||||
Bicubic(RenderingHints.VALUE_INTERPOLATION_BICUBIC),
|
||||
/**
|
||||
* use bilinear interpolation
|
||||
*/
|
||||
Bilinear(RenderingHints.VALUE_INTERPOLATION_BILINEAR),
|
||||
/**
|
||||
* use nearest neighbor interpolation
|
||||
*/
|
||||
NearestNeighbor(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
|
||||
|
||||
private Object value;
|
||||
|
||||
Interpolation(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
private void configureGraphics(Graphics2D g) {
|
||||
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, value);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------- Instance Variables
|
||||
/**
|
||||
* The cached image, if shouldUseCache() returns true
|
||||
*/
|
||||
private transient SoftReference<BufferedImage> cachedImage;
|
||||
private boolean cacheCleared = true;
|
||||
private boolean cacheable = false;
|
||||
private boolean dirty = false;
|
||||
private BufferedImageOp[] filters = new BufferedImageOp[0];
|
||||
private boolean antialiasing = true;
|
||||
private Interpolation interpolation = Interpolation.NearestNeighbor;
|
||||
private boolean visible = true;
|
||||
private boolean inPaintContext;
|
||||
|
||||
/**
|
||||
* Creates a new instance of AbstractPainter.
|
||||
*/
|
||||
public AbstractPainter() { }
|
||||
|
||||
/**
|
||||
* Creates a new instance of AbstractPainter.
|
||||
* @param cacheable indicates if this painter should be cacheable
|
||||
*/
|
||||
public AbstractPainter(boolean cacheable) {
|
||||
setCacheable(cacheable);
|
||||
}
|
||||
|
||||
/**
|
||||
* A defensive copy of the Effects to apply to the results
|
||||
* of the AbstractPainter's painting operation. The array may
|
||||
* be empty but it will never be null.
|
||||
* @return the array of filters applied to this painter
|
||||
*/
|
||||
public final BufferedImageOp[] getFilters() {
|
||||
BufferedImageOp[] results = new BufferedImageOp[filters.length];
|
||||
System.arraycopy(filters, 0, results, 0, results.length);
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>A convenience method for specifying the filters to use based on
|
||||
* BufferedImageOps. These will each be individually wrapped by an ImageFilter
|
||||
* and then setFilters(Effect... filters) will be called with the resulting
|
||||
* array</p>
|
||||
*
|
||||
*
|
||||
* @param effects the BufferedImageOps to wrap as filters
|
||||
*/
|
||||
public void setFilters(BufferedImageOp ... effects) {
|
||||
if (effects == null) effects = new BufferedImageOp[0];
|
||||
BufferedImageOp[] old = getFilters();
|
||||
this.filters = new BufferedImageOp[effects.length];
|
||||
System.arraycopy(effects, 0, this.filters, 0, this.filters.length);
|
||||
setDirty(true);
|
||||
firePropertyChange("filters", old, getFilters());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if antialiasing is turned on or not. The default value is true.
|
||||
* This is a bound property.
|
||||
* @return the current antialiasing setting
|
||||
*/
|
||||
public boolean isAntialiasing() {
|
||||
return antialiasing;
|
||||
}
|
||||
/**
|
||||
* Sets the antialiasing setting. This is a bound property.
|
||||
* @param value the new antialiasing setting
|
||||
*/
|
||||
public void setAntialiasing(boolean value) {
|
||||
boolean old = isAntialiasing();
|
||||
antialiasing = value;
|
||||
if (old != value) setDirty(true);
|
||||
firePropertyChange("antialiasing", old, isAntialiasing());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current interpolation setting. This property determines if interpolation will
|
||||
* be used when drawing scaled images. @see java.awt.RenderingHints.KEY_INTERPOLATION.
|
||||
* @return the current interpolation setting
|
||||
*/
|
||||
public Interpolation getInterpolation() {
|
||||
return interpolation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new value for the interpolation setting. This setting determines if interpolation
|
||||
* should be used when drawing scaled images. @see java.awt.RenderingHints.KEY_INTERPOLATION.
|
||||
* @param value the new interpolation setting
|
||||
*/
|
||||
public void setInterpolation(Interpolation value) {
|
||||
Object old = getInterpolation();
|
||||
this.interpolation = value == null ? Interpolation.NearestNeighbor : value;
|
||||
if (old != value) setDirty(true);
|
||||
firePropertyChange("interpolation", old, getInterpolation());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the visible property. This controls if the painter should
|
||||
* paint itself. It is true by default. Setting visible to false
|
||||
* is good when you want to temporarily turn off a painter. An example
|
||||
* of this is a painter that you only use when a button is highlighted.
|
||||
*
|
||||
* @return current value of visible property
|
||||
*/
|
||||
public boolean isVisible() {
|
||||
return this.visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets the visible property. This controls if the painter should
|
||||
* paint itself. It is true by default. Setting visible to false
|
||||
* is good when you want to temporarily turn off a painter. An example
|
||||
* of this is a painter that you only use when a button is highlighted.</p>
|
||||
*
|
||||
* @param visible New value of visible property.
|
||||
*/
|
||||
public void setVisible(boolean visible) {
|
||||
boolean old = isVisible();
|
||||
this.visible = visible;
|
||||
if (old != visible) setDirty(true); //not the most efficient, but I must do this otherwise a CompoundPainter
|
||||
//or other aggregate painter won't know that it is now invalid
|
||||
//there might be a tricky solution but that is a performance optimization
|
||||
firePropertyChange("visible", old, isVisible());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Gets whether this <code>AbstractPainter</code> can be cached as an image.
|
||||
* If caching is enabled, then it is the responsibility of the developer to
|
||||
* invalidate the painter (via {@link #clearCache}) if external state has
|
||||
* changed in such a way that the painter is invalidated and needs to be
|
||||
* repainted.</p>
|
||||
*
|
||||
* @return whether this is cacheable
|
||||
*/
|
||||
public boolean isCacheable() {
|
||||
return cacheable;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sets whether this <code>AbstractPainter</code> can be cached as an image.
|
||||
* If true, this is treated as a hint. That is, a cacheable may or may not be used.
|
||||
* The {@link #shouldUseCache} method actually determines whether the cacheable is used.
|
||||
* However, if false, then this is treated as an absolute value. That is, no
|
||||
* cacheable will be used.</p>
|
||||
*
|
||||
* <p>If set to false, then #clearCache is called to free system resources.</p>
|
||||
*
|
||||
* @param cacheable the cache flag
|
||||
*/
|
||||
public void setCacheable(boolean cacheable) {
|
||||
boolean old = isCacheable();
|
||||
this.cacheable = cacheable;
|
||||
firePropertyChange("cacheable", old, isCacheable());
|
||||
if (!isCacheable()) {
|
||||
clearCache();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Call this method to clear the cacheable. This may be called whether there is
|
||||
* a cacheable being used or not. If cleared, on the next call to <code>paint</code>,
|
||||
* the painting routines will be called.</p>
|
||||
*
|
||||
* <p><strong>Subclasses</strong>If overridden in subclasses, you
|
||||
* <strong>must</strong> call super.clearCache, or physical
|
||||
* resources (such as an Image) may leak.</p>
|
||||
*/
|
||||
public void clearCache() {
|
||||
BufferedImage cache = cachedImage == null ? null : cachedImage.get();
|
||||
if (cache != null) {
|
||||
cache.flush();
|
||||
}
|
||||
cacheCleared = true;
|
||||
if (!isCacheable()) {
|
||||
cachedImage = null;
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Only made package private for testing. Don't call this method outside
|
||||
// * of this class! This is NOT a bound property
|
||||
// */
|
||||
// private boolean isCacheCleared() {
|
||||
// return cacheCleared;
|
||||
// }
|
||||
|
||||
/**
|
||||
* <p>Called to allow <code>Painter</code> subclasses a chance to see if any state
|
||||
* in the given object has changed from the last paint operation. If it has, then
|
||||
* the <code>Painter</code> has a chance to mark itself as dirty, thus causing a
|
||||
* repaint, even if cached.</p>
|
||||
*
|
||||
* @param object the object to validate
|
||||
*/
|
||||
protected void validate(T object) { /* do nothing */ }
|
||||
|
||||
/**
|
||||
* Ye olde dirty bit. If true, then the painter is considered dirty and in need of
|
||||
* being repainted. This is a bound property.
|
||||
*
|
||||
* @return true if the painter state has changed and the painter needs to be
|
||||
* repainted.
|
||||
*/
|
||||
protected boolean isDirty() {
|
||||
return dirty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the dirty bit. If true, then the painter is considered dirty, and the cache
|
||||
* will be cleared. This property is bound.
|
||||
*
|
||||
* @param d whether this <code>Painter</code> is dirty.
|
||||
*/
|
||||
protected void setDirty(boolean d) {
|
||||
boolean old = isDirty();
|
||||
this.dirty = d;
|
||||
firePropertyChange("dirty", old, isDirty());
|
||||
if (isDirty()) {
|
||||
clearCache();
|
||||
}
|
||||
}
|
||||
|
||||
boolean isInPaintContext() {
|
||||
return inPaintContext;
|
||||
}
|
||||
|
||||
void setInPaintContext(boolean inPaintContext) {
|
||||
this.inPaintContext = inPaintContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns true if the painter should use caching. This method allows subclasses to
|
||||
* specify the heuristics regarding whether to cache or not. If a <code>Painter</code>
|
||||
* has intelligent rules regarding painting times, and can more accurately indicate
|
||||
* whether it should be cached, it could implement that logic in this method.</p>
|
||||
*
|
||||
* @return whether or not a cache should be used
|
||||
*/
|
||||
protected boolean shouldUseCache() {
|
||||
return isCacheable() || filters.length > 0; //NOTE, I can only do this because getFilters() is final
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>This method is called by the <code>paint</code> method prior to
|
||||
* any drawing operations to configure the drawing surface. The default
|
||||
* implementation sets the rendering hints that have been specified for
|
||||
* this <code>AbstractPainter</code>.</p>
|
||||
*
|
||||
* <p>This method can be overridden by subclasses to modify the drawing
|
||||
* surface before any painting happens.</p>
|
||||
*
|
||||
* @param g the graphics surface to configure. This will never be null.
|
||||
* @see #paint(Graphics2D, Object, int, int)
|
||||
*/
|
||||
protected void configureGraphics(Graphics2D g) {
|
||||
//configure antialiasing
|
||||
if(isAntialiasing()) {
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
} else {
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_OFF);
|
||||
}
|
||||
|
||||
getInterpolation().configureGraphics(g);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses must implement this method and perform custom painting operations
|
||||
* here.
|
||||
* @param width the width
|
||||
* @param height the height
|
||||
* @param g The Graphics2D object in which to paint
|
||||
* @param object an optional configuration parameter
|
||||
*/
|
||||
protected abstract void doPaint(Graphics2D g, T object, int width, int height);
|
||||
|
||||
@Override
|
||||
public final void paint(Graphics2D g, T obj, int width, int height) {
|
||||
if (g == null) {
|
||||
throw new NullPointerException("The Graphics2D must be supplied");
|
||||
}
|
||||
|
||||
if(!isVisible() || width < 1 || height < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
configureGraphics(g);
|
||||
|
||||
//paint to a temporary image if I'm caching, or if there are filters to apply
|
||||
if (shouldUseCache() || filters.length > 0) {
|
||||
validate(obj);
|
||||
BufferedImage cache = cachedImage == null ? null : cachedImage.get();
|
||||
boolean invalidCache = null == cache ||
|
||||
cache.getWidth() != width ||
|
||||
cache.getHeight() != height;
|
||||
|
||||
if (cacheCleared || invalidCache || isDirty()) {
|
||||
//rebuild the cacheable. I do this both if a cacheable is needed, and if any
|
||||
//filters exist. I only *save* the resulting image if caching is turned on
|
||||
if (invalidCache) {
|
||||
cache = GraphicsUtilities.createCompatibleTranslucentImage(width, height);
|
||||
}
|
||||
if (cache != null) {
|
||||
Graphics2D gfx = cache.createGraphics();
|
||||
|
||||
try {
|
||||
gfx.setClip(0, 0, width, height);
|
||||
|
||||
if (!invalidCache) {
|
||||
// If we are doing a repaint, but we didn't have to
|
||||
// recreate the image, we need to clear it back
|
||||
// to a fully transparent background.
|
||||
Composite composite = gfx.getComposite();
|
||||
gfx.setComposite(AlphaComposite.Clear);
|
||||
gfx.fillRect(0, 0, width, height);
|
||||
gfx.setComposite(composite);
|
||||
}
|
||||
|
||||
configureGraphics(gfx);
|
||||
doPaint(gfx, obj, width, height);
|
||||
} finally {
|
||||
gfx.dispose();
|
||||
}
|
||||
|
||||
if (!isInPaintContext()) {
|
||||
for (BufferedImageOp f : getFilters()) {
|
||||
cache = f.filter(cache, null);
|
||||
}
|
||||
}
|
||||
|
||||
//only save the temporary image as the cacheable if I'm caching
|
||||
if (shouldUseCache()) {
|
||||
cachedImage = new SoftReference<BufferedImage>(cache);
|
||||
cacheCleared = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g.drawImage(cache, 0, 0, null);
|
||||
} else {
|
||||
//can't use the cacheable, so just paint
|
||||
doPaint(g, obj, width, height);
|
||||
}
|
||||
|
||||
//painting has occured, so restore the dirty bit to false
|
||||
setDirty(false);
|
||||
}
|
||||
}
|
||||
497
src/main/java/org/jdesktop/swingx/painter/CompoundPainter.java
Normal file
497
src/main/java/org/jdesktop/swingx/painter/CompoundPainter.java
Normal file
@@ -0,0 +1,497 @@
|
||||
/*
|
||||
* $Id: CompoundPainter.java 4147 2012-02-01 17:13:24Z kschaefe $
|
||||
*
|
||||
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
|
||||
* Santa Clara, California 95054, U.S.A. All rights reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.painter;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
import java.beans.PropertyChangeListener;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
//import org.jdesktop.beans.JavaBean;
|
||||
|
||||
/**
|
||||
* <p>A {@link Painter} implementation composed of an array of <code>Painter</code>s.
|
||||
* <code>CompoundPainter</code> provides a means for combining several individual
|
||||
* <code>Painter</code>s, or groups of them, into one logical unit. Each of the
|
||||
* <code>Painter</code>s are executed in order. BufferedImageOp filter effects can
|
||||
* be applied to them together as a whole. The entire set of painting operations
|
||||
* may be cached together.</p>
|
||||
*
|
||||
* <p></p>
|
||||
*
|
||||
* <p>For example, if I want to create a CompoundPainter that started with a blue
|
||||
* background, had pinstripes on it running at a 45 degree angle, and those
|
||||
* pinstripes appeared to "fade in" from left to right, I would write the following:
|
||||
* <pre><code>
|
||||
* Color blue = new Color(0x417DDD);
|
||||
* Color translucent = new Color(blue.getRed(), blue.getGreen(), blue.getBlue(), 0);
|
||||
* panel.setBackground(blue);
|
||||
* panel.setForeground(Color.LIGHT_GRAY);
|
||||
* GradientPaint blueToTranslucent = new GradientPaint(
|
||||
* new Point2D.Double(.4, 0),
|
||||
* blue,
|
||||
* new Point2D.Double(1, 0),
|
||||
* translucent);
|
||||
* MattePainter veil = new MattePainter(blueToTranslucent);
|
||||
* veil.setPaintStretched(true);
|
||||
* Painter pinstripes = new PinstripePainter(45);
|
||||
* Painter backgroundPainter = new RectanglePainter(this.getBackground(), null);
|
||||
* Painter p = new CompoundPainter(backgroundPainter, pinstripes, veil);
|
||||
* panel.setBackgroundPainter(p);
|
||||
* </code></pre></p>
|
||||
*
|
||||
* @author rbair
|
||||
* @param <T> an optional configuration parameter
|
||||
*/
|
||||
//@JavaBean
|
||||
public class CompoundPainter<T> extends AbstractPainter<T>
|
||||
{
|
||||
private static class Handler implements PropertyChangeListener
|
||||
{
|
||||
private final WeakReference<CompoundPainter<?>> ref;
|
||||
|
||||
public Handler(CompoundPainter<?> painter)
|
||||
{
|
||||
ref = new WeakReference<CompoundPainter<?>>(painter);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void propertyChange(PropertyChangeEvent evt)
|
||||
{
|
||||
CompoundPainter<?> painter = ref.get();
|
||||
|
||||
if (painter == null)
|
||||
{
|
||||
AbstractPainter<?> src = (AbstractPainter<?>) evt.getSource();
|
||||
src.removePropertyChangeListener(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
String property = evt.getPropertyName();
|
||||
|
||||
if ("dirty".equals(property) && evt.getNewValue() == Boolean.FALSE)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
painter.setDirty(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Handler handler;
|
||||
|
||||
private List<Painter<T>> painters = new CopyOnWriteArrayList<Painter<T>>();
|
||||
private AffineTransform transform;
|
||||
private boolean clipPreserved = false;
|
||||
|
||||
private boolean checkForDirtyChildPainters = true;
|
||||
|
||||
/** Creates a new instance of CompoundPainter */
|
||||
public CompoundPainter()
|
||||
{
|
||||
this((Painter<T>[]) null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience constructor for creating a CompoundPainter for an array
|
||||
* of painters. A defensive copy of the given array is made, so that future
|
||||
* modification to the array does not result in changes to the CompoundPainter.
|
||||
*
|
||||
* @param painters array of painters, which will be painted in order
|
||||
*/
|
||||
public CompoundPainter(Painter<T>... painters)
|
||||
{
|
||||
handler = new Handler(this);
|
||||
|
||||
setPainters(painters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience constructor for creating a CompoundPainter for a list
|
||||
* of painters. A defensive copy of the given array is made, so that future
|
||||
* modification to the list does not result in changes to the CompoundPainter.
|
||||
*
|
||||
* @param painters array of painters, which will be painted in order
|
||||
*/
|
||||
public CompoundPainter(List<? extends Painter<T>> painters)
|
||||
{
|
||||
handler = new Handler(this);
|
||||
|
||||
setPainters(painters);
|
||||
}
|
||||
/**
|
||||
* Sets the array of Painters to use. These painters will be executed in
|
||||
* order. A null value will be treated as an empty array. To prevent unexpected
|
||||
* behavior all values in provided array are copied to internally held array.
|
||||
* Any changes to the original array will not be reflected.
|
||||
*
|
||||
* @param painters array of painters, which will be painted in order
|
||||
*/
|
||||
public void setPainters(List<? extends Painter<T>> painters)
|
||||
{
|
||||
Collection<Painter<T>> old = new ArrayList<Painter<T>>(getPainters());
|
||||
|
||||
for (Painter<T> p : old)
|
||||
{
|
||||
if (p instanceof AbstractPainter)
|
||||
{
|
||||
((AbstractPainter<?>) p).removePropertyChangeListener(handler);
|
||||
}
|
||||
}
|
||||
|
||||
this.painters.clear();
|
||||
this.painters.addAll(painters);
|
||||
|
||||
for (Painter<T> p : this.painters)
|
||||
{
|
||||
if (p instanceof AbstractPainter)
|
||||
{
|
||||
((AbstractPainter<?>) p).addPropertyChangeListener(handler);
|
||||
}
|
||||
}
|
||||
|
||||
setDirty(true);
|
||||
firePropertyChange("painters", old, getPainters());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the array of Painters to use. These painters will be executed in
|
||||
* order. A null value will be treated as an empty array. To prevent unexpected
|
||||
* behavior all values in provided array are copied to internally held array.
|
||||
* Any changes to the original array will not be reflected.
|
||||
*
|
||||
* @param painters array of painters, which will be painted in order
|
||||
*/
|
||||
public void setPainters(Painter<T>... painters)
|
||||
{
|
||||
List<? extends Painter<T>> l;
|
||||
|
||||
if (painters == null)
|
||||
l = Collections.emptyList();
|
||||
else
|
||||
l = Arrays.asList(painters);
|
||||
|
||||
setPainters(l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a painter to the queue of painters
|
||||
* @param painter the painter that is added
|
||||
*/
|
||||
public void addPainter(Painter<T> painter)
|
||||
{
|
||||
Collection<Painter<T>> old = new ArrayList<Painter<T>>(getPainters());
|
||||
|
||||
this.painters.add(painter);
|
||||
|
||||
if (painter instanceof AbstractPainter)
|
||||
{
|
||||
((AbstractPainter<?>) painter).addPropertyChangeListener(handler);
|
||||
}
|
||||
|
||||
setDirty(true);
|
||||
firePropertyChange("painters", old, getPainters());
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a painter from the queue of painters
|
||||
* @param painter the painter that is added
|
||||
*/
|
||||
public void removePainter(Painter<T> painter)
|
||||
{
|
||||
Collection<Painter<T>> old = new ArrayList<Painter<T>>(getPainters());
|
||||
|
||||
this.painters.remove(painter);
|
||||
|
||||
if (painter instanceof AbstractPainter)
|
||||
{
|
||||
((AbstractPainter<?>) painter).removePropertyChangeListener(handler);
|
||||
}
|
||||
|
||||
setDirty(true);
|
||||
firePropertyChange("painters", old, getPainters());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the array of painters used by this CompoundPainter
|
||||
* @return a defensive copy of the painters used by this CompoundPainter.
|
||||
* This will never be null.
|
||||
*/
|
||||
public final Collection<Painter<T>> getPainters()
|
||||
{
|
||||
return Collections.unmodifiableCollection(painters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the clip produced by any painter is left set once it finishes painting.
|
||||
* Normally the clip will be reset between each painter. Setting clipPreserved to
|
||||
* true can be used to let one painter mask other painters that come after it.
|
||||
* @return if the clip should be preserved
|
||||
* @see #setClipPreserved(boolean)
|
||||
*/
|
||||
public boolean isClipPreserved()
|
||||
{
|
||||
return clipPreserved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets if the clip should be preserved.
|
||||
* Normally the clip will be reset between each painter. Setting clipPreserved to
|
||||
* true can be used to let one painter mask other painters that come after it.
|
||||
*
|
||||
* @param shouldRestoreState new value of the clipPreserved property
|
||||
* @see #isClipPreserved()
|
||||
*/
|
||||
public void setClipPreserved(boolean shouldRestoreState)
|
||||
{
|
||||
boolean oldShouldRestoreState = isClipPreserved();
|
||||
this.clipPreserved = shouldRestoreState;
|
||||
setDirty(true);
|
||||
firePropertyChange("clipPreserved", oldShouldRestoreState, shouldRestoreState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current transform applied to all painters in this CompoundPainter. May be null.
|
||||
* @return the current AffineTransform
|
||||
*/
|
||||
public AffineTransform getTransform()
|
||||
{
|
||||
return transform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a transform to be applied to all painters contained in this CompoundPainter
|
||||
* @param transform a new AffineTransform
|
||||
*/
|
||||
public void setTransform(AffineTransform transform)
|
||||
{
|
||||
AffineTransform old = getTransform();
|
||||
this.transform = transform;
|
||||
setDirty(true);
|
||||
firePropertyChange("transform", old, transform);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Iterates over all child <code>Painter</code>s and gives them a chance
|
||||
* to validate themselves. If any of the child painters are dirty, then
|
||||
* this <code>CompoundPainter</code> marks itself as dirty.</p>
|
||||
*/
|
||||
@Override
|
||||
protected void validate(T object)
|
||||
{
|
||||
boolean dirty = false;
|
||||
for (Painter<T> p : painters)
|
||||
{
|
||||
if (p instanceof AbstractPainter)
|
||||
{
|
||||
AbstractPainter<T> ap = (AbstractPainter<T>) p;
|
||||
ap.validate(object);
|
||||
if (ap.isDirty())
|
||||
{
|
||||
dirty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
clearLocalCacheOnly = true;
|
||||
setDirty(dirty); //super will call clear cache
|
||||
clearLocalCacheOnly = false;
|
||||
}
|
||||
|
||||
//indicates whether the local cache should be cleared only, as opposed to the
|
||||
//cache's of all of the children. This is needed to optimize the caching strategy
|
||||
//when, during validate, the CompoundPainter is marked as dirty
|
||||
private boolean clearLocalCacheOnly = false;
|
||||
|
||||
/**
|
||||
* Used by {@link #isDirty()} to check if the child <code>Painter</code>s
|
||||
* should be checked for their <code>dirty</code> flag as part of
|
||||
* processing.<br>
|
||||
* Default value is: <code>true</code><br>
|
||||
* This should be set to </code>false</code> if the cacheable state
|
||||
* of the child <code>Painter</code>s are different from each other. This
|
||||
* will allow the cacheable == <code>true</code> <code>Painter</code>s to
|
||||
* keep their cached image during regular repaints. In this case,
|
||||
* client code should call {@link #clearCache()} manually when the cacheable
|
||||
* <code>Painter</code>s should be updated.
|
||||
* @return the dirty check flag
|
||||
*
|
||||
*
|
||||
* @see #isDirty()
|
||||
*/
|
||||
public boolean isCheckingDirtyChildPainters()
|
||||
{
|
||||
return checkForDirtyChildPainters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the flag used by {@link #isDirty()} to check if the
|
||||
* child <code>Painter</code>s should be checked for their
|
||||
* <code>dirty</code> flag as part of processing.
|
||||
* @param b the dirty check flag
|
||||
*
|
||||
* @see #isCheckingDirtyChildPainters()
|
||||
* @see #isDirty()
|
||||
*/
|
||||
public void setCheckingDirtyChildPainters(boolean b)
|
||||
{
|
||||
boolean old = isCheckingDirtyChildPainters();
|
||||
this.checkForDirtyChildPainters = b;
|
||||
firePropertyChange("checkingDirtyChildPainters", old, isCheckingDirtyChildPainters());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>This <code>CompoundPainter</code> is dirty if it, or (optionally)
|
||||
* any of its children, are dirty. If the super implementation returns
|
||||
* <code>true</code>, we return <code>true</code>. Otherwise, if
|
||||
* {@link #isCheckingDirtyChildPainters()} is <code>true</code>, we iterate
|
||||
* over all child <code>Painter</code>s and query them to see
|
||||
* if they are dirty. If so, then <code>true</code> is returned.
|
||||
* Otherwise, we return <code>false</code>.</p>
|
||||
*
|
||||
* @see #isCheckingDirtyChildPainters()
|
||||
*/
|
||||
@Override
|
||||
protected boolean isDirty()
|
||||
{
|
||||
boolean dirty = super.isDirty();
|
||||
if (dirty)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (isCheckingDirtyChildPainters())
|
||||
{
|
||||
for (Painter<T> p : painters)
|
||||
{
|
||||
if (p instanceof AbstractPainter)
|
||||
{
|
||||
AbstractPainter<?> ap = (AbstractPainter<?>) p;
|
||||
if (ap.isDirty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDirty(boolean d)
|
||||
{
|
||||
boolean old = super.isDirty();
|
||||
boolean ours = isDirty();
|
||||
|
||||
super.setDirty(d);
|
||||
|
||||
//must perform this check to ensure we do not double notify
|
||||
if (d != old && d == ours)
|
||||
{
|
||||
firePropertyChange("dirty", old, isDirty());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Clears the cache of this <code>Painter</code>, and all child
|
||||
* <code>Painters</code>. This is done to ensure that resources
|
||||
* are collected, even if clearCache is called by some framework
|
||||
* or other code that doesn't realize this is a CompoundPainter.</p>
|
||||
*
|
||||
* <p>Call #clearLocalCache if you only want to clear the cache of this
|
||||
* <code>CompoundPainter</code>
|
||||
*/
|
||||
@Override
|
||||
public void clearCache()
|
||||
{
|
||||
if (!clearLocalCacheOnly)
|
||||
{
|
||||
for (Painter<T> p : painters)
|
||||
{
|
||||
if (p instanceof AbstractPainter)
|
||||
{
|
||||
AbstractPainter<?> ap = (AbstractPainter<?>) p;
|
||||
ap.clearCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
super.clearCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Clears the cache of this painter only, and not of any of the children.</p>
|
||||
*/
|
||||
public void clearLocalCache()
|
||||
{
|
||||
super.clearCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPaint(Graphics2D g, T component, int width, int height)
|
||||
{
|
||||
for (Painter<T> p : getPainters())
|
||||
{
|
||||
Graphics2D temp = (Graphics2D) g.create();
|
||||
|
||||
try
|
||||
{
|
||||
p.paint(temp, component, width, height);
|
||||
if (isClipPreserved())
|
||||
{
|
||||
g.setClip(temp.getClip());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
temp.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureGraphics(Graphics2D g)
|
||||
{
|
||||
//applies the transform
|
||||
AffineTransform tx = getTransform();
|
||||
if (tx != null)
|
||||
{
|
||||
g.setTransform(tx);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldUseCache()
|
||||
{
|
||||
return super.shouldUseCache(); // || (painters != null && painters.length > 1);
|
||||
}
|
||||
}
|
||||
104
src/main/java/org/jdesktop/swingx/painter/Painter.java
Normal file
104
src/main/java/org/jdesktop/swingx/painter/Painter.java
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* $Id: Painter.java 3860 2010-10-26 01:14:53Z kschaefe $
|
||||
*
|
||||
* Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
|
||||
* Santa Clara, California 95054, U.S.A. All rights reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.painter;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
|
||||
/**
|
||||
* <p>A painting delegate. The Painter interface defines exactly one method,
|
||||
* <code>paint</code>. It is used in situations where the developer can change
|
||||
* the painting routine of a component without having to resort to subclassing
|
||||
* the component.</p>
|
||||
*
|
||||
* <p><code>Painter</code>s are simply encapsulations of Java2D code and make
|
||||
* it fairly trivial to reuse existing <code>Painter</code>s or to combine
|
||||
* them together. Implementations of this interface are also trivial to write,
|
||||
* such that if you can't find a <code>Painter</code> that does what you need,
|
||||
* you can write one with minimal effort. Writing a <code>Painter</code> requires
|
||||
* knowledge of Java2D.</p>
|
||||
*
|
||||
* <p>A <code>Painter</code> may be created with a type parameter. This type will be
|
||||
* expected in the <code>paint</code> method. For example, you may wish to write a
|
||||
* <code>Painter</code> that only works with subclasses of {@link java.awt.Component}.
|
||||
* In that case, when the <code>Painter</code> is declared, you may declare that
|
||||
* it requires a <code>Component</code>, allowing the paint method to be type safe. Ex:
|
||||
* <pre><code>
|
||||
* Painter<Component> p = new Painter<Component>() {
|
||||
* public void paint(Graphics2D g, Component c, int width, int height) {
|
||||
* g.setColor(c.getBackground());
|
||||
* //and so forth
|
||||
* }
|
||||
* }
|
||||
* </code></pre></p>
|
||||
*
|
||||
* <p>This class is <strong>not</strong> threadsafe.</p>
|
||||
*
|
||||
* @author rbair
|
||||
* @param <T> an optional configuration parameter
|
||||
* @see AbstractPainter
|
||||
* @see CompoundPainter
|
||||
*/
|
||||
public interface Painter<T> {
|
||||
/**
|
||||
* <p>Renders to the given {@link java.awt.Graphics2D} object. Implementations
|
||||
* of this method <em>may</em> modify state on the <code>Graphics2D</code>, and are not
|
||||
* required to restore that state upon completion. In most cases, it is recommended
|
||||
* that the caller pass in a scratch graphics object. The <code>Graphics2D</code>
|
||||
* must never be null.</p>
|
||||
*
|
||||
* <p>State on the graphics object may be honored by the <code>paint</code> method,
|
||||
* but may not be. For instance, setting the antialiasing rendering hint on the
|
||||
* graphics may or may not be respected by the <code>Painter</code> implementation.</p>
|
||||
*
|
||||
* <p>The supplied object parameter acts as an optional configuration argument.
|
||||
* For example, it could be of type <code>Component</code>. A <code>Painter</code>
|
||||
* that expected it could then read state from that <code>Component</code> and
|
||||
* use the state for painting. For example, an implementation may read the
|
||||
* backgroundColor and use that.</p>
|
||||
*
|
||||
* <p>Generally, to enhance reusability, most standard <code>Painter</code>s ignore
|
||||
* this parameter. They can thus be reused in any context. The <code>object</code>
|
||||
* may be null. Implementations must not throw a NullPointerException if the object
|
||||
* parameter is null.</p>
|
||||
*
|
||||
* <p>Finally, the <code>width</code> and <code>height</code> arguments specify the
|
||||
* width and height that the <code>Painter</code> should paint into. More
|
||||
* specifically, the specified width and height instruct the painter that it should
|
||||
* paint fully within this width and height. Any specified clip on the
|
||||
* <code>g</code> param will further constrain the region.</p>
|
||||
*
|
||||
* <p>For example, suppose I have a <code>Painter</code> implementation that draws
|
||||
* a gradient. The gradient goes from white to black. It "stretches" to fill the
|
||||
* painted region. Thus, if I use this <code>Painter</code> to paint a 500 x 500
|
||||
* region, the far left would be black, the far right would be white, and a smooth
|
||||
* gradient would be painted between. I could then, without modification, reuse the
|
||||
* <code>Painter</code> to paint a region that is 20x20 in size. This region would
|
||||
* also be black on the left, white on the right, and a smooth gradient painted
|
||||
* between.</p>
|
||||
*
|
||||
* @param g The Graphics2D to render to. This must not be null.
|
||||
* @param object an optional configuration parameter. This may be null.
|
||||
* @param width width of the area to paint.
|
||||
* @param height height of the area to paint.
|
||||
*/
|
||||
public void paint(Graphics2D g, T object, int width, int height);
|
||||
}
|
||||
871
src/main/java/org/jdesktop/swingx/util/GraphicsUtilities.java
Normal file
871
src/main/java/org/jdesktop/swingx/util/GraphicsUtilities.java
Normal file
@@ -0,0 +1,871 @@
|
||||
/*
|
||||
* $Id: GraphicsUtilities.java 4082 2011-11-15 18:39:43Z kschaefe $
|
||||
*
|
||||
* Dual-licensed under LGPL (Sun and Romain Guy) and BSD (Romain Guy).
|
||||
*
|
||||
* Copyright 2005 Sun Microsystems, Inc., 4150 Network Circle,
|
||||
* Santa Clara, California 95054, U.S.A. All rights reserved.
|
||||
*
|
||||
* Copyright (c) 2006 Romain Guy <romain.guy@mac.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. The name of the author may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.jdesktop.swingx.util;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Color;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.GraphicsConfiguration;
|
||||
import java.awt.GraphicsEnvironment;
|
||||
import java.awt.Image;
|
||||
import java.awt.Insets;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.Shape;
|
||||
import java.awt.Transparency;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.Raster;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.swing.JComponent;
|
||||
|
||||
/**
|
||||
* <p><code>GraphicsUtilities</code> contains a set of tools to perform
|
||||
* common graphics operations easily. These operations are divided into
|
||||
* several themes, listed below.</p>
|
||||
*
|
||||
* <h2>Compatible Images</h2>
|
||||
*
|
||||
* <p>Compatible images can, and should, be used to increase drawing
|
||||
* performance. This class provides a number of methods to load compatible
|
||||
* images directly from files or to convert existing images to compatibles
|
||||
* images.</p>
|
||||
*
|
||||
* <h2>Creating Thumbnails</h2>
|
||||
*
|
||||
* <p>This class provides a number of methods to easily scale down images.
|
||||
* Some of these methods offer a trade-off between speed and result quality and
|
||||
* should be used all the time. They also offer the advantage of producing
|
||||
* compatible images, thus automatically resulting into better runtime
|
||||
* performance.</p>
|
||||
*
|
||||
* <p>All these methods are both faster than
|
||||
* {@link java.awt.Image#getScaledInstance(int, int, int)} and produce
|
||||
* better-looking results than the various <code>drawImage()</code> methods
|
||||
* in {@link java.awt.Graphics}, which can be used for image scaling.</p>
|
||||
*
|
||||
* <h2>Image Manipulation</h2>
|
||||
*
|
||||
* <p>This class provides two methods to get and set pixels in a buffered image.
|
||||
* These methods try to avoid unmanaging the image in order to keep good
|
||||
* performance.</p>
|
||||
*
|
||||
* @author Romain Guy <romain.guy@mac.com>
|
||||
* @author rbair
|
||||
* @author Karl Schaefer
|
||||
*/
|
||||
@SuppressWarnings("nls")
|
||||
public class GraphicsUtilities {
|
||||
private GraphicsUtilities() {
|
||||
}
|
||||
|
||||
// Returns the graphics configuration for the primary screen
|
||||
private static GraphicsConfiguration getGraphicsConfiguration() {
|
||||
return GraphicsEnvironment.getLocalGraphicsEnvironment().
|
||||
getDefaultScreenDevice().getDefaultConfiguration();
|
||||
}
|
||||
|
||||
private static boolean isHeadless() {
|
||||
return GraphicsEnvironment.isHeadless();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the specified image into a compatible buffered image.
|
||||
*
|
||||
* @param img
|
||||
* the image to convert
|
||||
* @return a compatible buffered image of the input
|
||||
*/
|
||||
public static BufferedImage convertToBufferedImage(Image img) {
|
||||
BufferedImage buff = createCompatibleTranslucentImage(
|
||||
img.getWidth(null), img.getHeight(null));
|
||||
Graphics2D g2 = buff.createGraphics();
|
||||
|
||||
try {
|
||||
g2.drawImage(img, 0, 0, null);
|
||||
} finally {
|
||||
g2.dispose();
|
||||
}
|
||||
|
||||
return buff;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a new <code>BufferedImage</code> using the same color model
|
||||
* as the image passed as a parameter. The returned image is only compatible
|
||||
* with the image passed as a parameter. This does not mean the returned
|
||||
* image is compatible with the hardware.</p>
|
||||
*
|
||||
* @param image the reference image from which the color model of the new
|
||||
* image is obtained
|
||||
* @return a new <code>BufferedImage</code>, compatible with the color model
|
||||
* of <code>image</code>
|
||||
*/
|
||||
public static BufferedImage createColorModelCompatibleImage(BufferedImage image) {
|
||||
ColorModel cm = image.getColorModel();
|
||||
return new BufferedImage(cm,
|
||||
cm.createCompatibleWritableRaster(image.getWidth(),
|
||||
image.getHeight()),
|
||||
cm.isAlphaPremultiplied(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a new compatible image with the same width, height and
|
||||
* transparency as the image specified as a parameter. That is, the
|
||||
* returned BufferedImage will be compatible with the graphics hardware.
|
||||
* If this method is called in a headless environment, then
|
||||
* the returned BufferedImage will be compatible with the source
|
||||
* image.</p>
|
||||
*
|
||||
* @see java.awt.Transparency
|
||||
* @see #createCompatibleImage(int, int)
|
||||
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
|
||||
* @see #createCompatibleTranslucentImage(int, int)
|
||||
* @see #loadCompatibleImage(java.net.URL)
|
||||
* @see #toCompatibleImage(java.awt.image.BufferedImage)
|
||||
* @param image the reference image from which the dimension and the
|
||||
* transparency of the new image are obtained
|
||||
* @return a new compatible <code>BufferedImage</code> with the same
|
||||
* dimension and transparency as <code>image</code>
|
||||
*/
|
||||
public static BufferedImage createCompatibleImage(BufferedImage image) {
|
||||
return createCompatibleImage(image, image.getWidth(), image.getHeight());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a new compatible image of the specified width and height, and
|
||||
* the same transparency setting as the image specified as a parameter.
|
||||
* That is, the returned <code>BufferedImage</code> is compatible with
|
||||
* the graphics hardware. If the method is called in a headless
|
||||
* environment, then the returned BufferedImage will be compatible with
|
||||
* the source image.</p>
|
||||
*
|
||||
* @see java.awt.Transparency
|
||||
* @see #createCompatibleImage(java.awt.image.BufferedImage)
|
||||
* @see #createCompatibleImage(int, int)
|
||||
* @see #createCompatibleTranslucentImage(int, int)
|
||||
* @see #loadCompatibleImage(java.net.URL)
|
||||
* @see #toCompatibleImage(java.awt.image.BufferedImage)
|
||||
* @param width the width of the new image
|
||||
* @param height the height of the new image
|
||||
* @param image the reference image from which the transparency of the new
|
||||
* image is obtained
|
||||
* @return a new compatible <code>BufferedImage</code> with the same
|
||||
* transparency as <code>image</code> and the specified dimension
|
||||
*/
|
||||
public static BufferedImage createCompatibleImage(BufferedImage image,
|
||||
int width, int height) {
|
||||
return isHeadless() ?
|
||||
new BufferedImage(width, height, image.getType()) :
|
||||
getGraphicsConfiguration().createCompatibleImage(width, height,
|
||||
image.getTransparency());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a new opaque compatible image of the specified width and
|
||||
* height. That is, the returned <code>BufferedImage</code> is compatible with
|
||||
* the graphics hardware. If the method is called in a headless
|
||||
* environment, then the returned BufferedImage will be compatible with
|
||||
* the source image.</p>
|
||||
*
|
||||
* @see #createCompatibleImage(java.awt.image.BufferedImage)
|
||||
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
|
||||
* @see #createCompatibleTranslucentImage(int, int)
|
||||
* @see #loadCompatibleImage(java.net.URL)
|
||||
* @see #toCompatibleImage(java.awt.image.BufferedImage)
|
||||
* @param width the width of the new image
|
||||
* @param height the height of the new image
|
||||
* @return a new opaque compatible <code>BufferedImage</code> of the
|
||||
* specified width and height
|
||||
*/
|
||||
public static BufferedImage createCompatibleImage(int width, int height) {
|
||||
return isHeadless() ?
|
||||
new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB) :
|
||||
getGraphicsConfiguration().createCompatibleImage(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a new translucent compatible image of the specified width and
|
||||
* height. That is, the returned <code>BufferedImage</code> is compatible with
|
||||
* the graphics hardware. If the method is called in a headless
|
||||
* environment, then the returned BufferedImage will be compatible with
|
||||
* the source image.</p>
|
||||
*
|
||||
* @see #createCompatibleImage(java.awt.image.BufferedImage)
|
||||
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
|
||||
* @see #createCompatibleImage(int, int)
|
||||
* @see #loadCompatibleImage(java.net.URL)
|
||||
* @see #toCompatibleImage(java.awt.image.BufferedImage)
|
||||
* @param width the width of the new image
|
||||
* @param height the height of the new image
|
||||
* @return a new translucent compatible <code>BufferedImage</code> of the
|
||||
* specified width and height
|
||||
*/
|
||||
public static BufferedImage createCompatibleTranslucentImage(int width,
|
||||
int height) {
|
||||
return isHeadless() ?
|
||||
new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB) :
|
||||
getGraphicsConfiguration().createCompatibleImage(width, height,
|
||||
Transparency.TRANSLUCENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Returns a new compatible image from a stream. The image is loaded from
|
||||
* the specified stream and then turned, if necessary into a compatible
|
||||
* image.
|
||||
* </p>
|
||||
*
|
||||
* @see #createCompatibleImage(java.awt.image.BufferedImage)
|
||||
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
|
||||
* @see #createCompatibleImage(int, int)
|
||||
* @see #createCompatibleTranslucentImage(int, int)
|
||||
* @see #toCompatibleImage(java.awt.image.BufferedImage)
|
||||
* @param in
|
||||
* the stream of the picture to load as a compatible image
|
||||
* @return a new translucent compatible <code>BufferedImage</code> of the
|
||||
* specified width and height
|
||||
* @throws java.io.IOException
|
||||
* if the image cannot be read or loaded
|
||||
*/
|
||||
public static BufferedImage loadCompatibleImage(InputStream in) throws IOException {
|
||||
BufferedImage image = ImageIO.read(in);
|
||||
if(image == null) return null;
|
||||
return toCompatibleImage(image);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a new compatible image from a URL. The image is loaded from the
|
||||
* specified location and then turned, if necessary into a compatible
|
||||
* image.</p>
|
||||
*
|
||||
* @see #createCompatibleImage(java.awt.image.BufferedImage)
|
||||
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
|
||||
* @see #createCompatibleImage(int, int)
|
||||
* @see #createCompatibleTranslucentImage(int, int)
|
||||
* @see #toCompatibleImage(java.awt.image.BufferedImage)
|
||||
* @param resource the URL of the picture to load as a compatible image
|
||||
* @return a new translucent compatible <code>BufferedImage</code> of the
|
||||
* specified width and height
|
||||
* @throws java.io.IOException if the image cannot be read or loaded
|
||||
*/
|
||||
public static BufferedImage loadCompatibleImage(URL resource)
|
||||
throws IOException {
|
||||
BufferedImage image = ImageIO.read(resource);
|
||||
return toCompatibleImage(image);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Return a new compatible image that contains a copy of the specified
|
||||
* image. This method ensures an image is compatible with the hardware,
|
||||
* and therefore optimized for fast blitting operations.</p>
|
||||
*
|
||||
* <p>If the method is called in a headless environment, then the returned
|
||||
* <code>BufferedImage</code> will be the source image.</p>
|
||||
*
|
||||
* @see #createCompatibleImage(java.awt.image.BufferedImage)
|
||||
* @see #createCompatibleImage(java.awt.image.BufferedImage, int, int)
|
||||
* @see #createCompatibleImage(int, int)
|
||||
* @see #createCompatibleTranslucentImage(int, int)
|
||||
* @see #loadCompatibleImage(java.net.URL)
|
||||
* @param image the image to copy into a new compatible image
|
||||
* @return a new compatible copy, with the
|
||||
* same width and height and transparency and content, of <code>image</code>
|
||||
*/
|
||||
public static BufferedImage toCompatibleImage(BufferedImage image) {
|
||||
if (isHeadless()) {
|
||||
return image;
|
||||
}
|
||||
|
||||
if (image.getColorModel().equals(
|
||||
getGraphicsConfiguration().getColorModel())) {
|
||||
return image;
|
||||
}
|
||||
|
||||
BufferedImage compatibleImage =
|
||||
getGraphicsConfiguration().createCompatibleImage(
|
||||
image.getWidth(), image.getHeight(),
|
||||
image.getTransparency());
|
||||
Graphics g = compatibleImage.getGraphics();
|
||||
|
||||
try {
|
||||
g.drawImage(image, 0, 0, null);
|
||||
} finally {
|
||||
g.dispose();
|
||||
}
|
||||
|
||||
return compatibleImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a thumbnail of a source image. <code>newSize</code> defines
|
||||
* the length of the longest dimension of the thumbnail. The other
|
||||
* dimension is then computed according to the dimensions ratio of the
|
||||
* original picture.</p>
|
||||
* <p>This method favors speed over quality. When the new size is less than
|
||||
* half the longest dimension of the source image,
|
||||
* {@link #createThumbnail(BufferedImage, int)} or
|
||||
* {@link #createThumbnail(BufferedImage, int, int)} should be used instead
|
||||
* to ensure the quality of the result without sacrificing too much
|
||||
* performance.</p>
|
||||
*
|
||||
* @see #createThumbnailFast(java.awt.image.BufferedImage, int, int)
|
||||
* @see #createThumbnail(java.awt.image.BufferedImage, int)
|
||||
* @see #createThumbnail(java.awt.image.BufferedImage, int, int)
|
||||
* @param image the source image
|
||||
* @param newSize the length of the largest dimension of the thumbnail
|
||||
* @return a new compatible <code>BufferedImage</code> containing a
|
||||
* thumbnail of <code>image</code>
|
||||
* @throws IllegalArgumentException if <code>newSize</code> is larger than
|
||||
* the largest dimension of <code>image</code> or <= 0
|
||||
*/
|
||||
public static BufferedImage createThumbnailFast(BufferedImage image,
|
||||
int newSize) {
|
||||
float ratio;
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
|
||||
if (width > height) {
|
||||
if (newSize >= width) {
|
||||
throw new IllegalArgumentException("newSize must be lower than" +
|
||||
" the image width");
|
||||
} else if (newSize <= 0) {
|
||||
throw new IllegalArgumentException("newSize must" +
|
||||
" be greater than 0");
|
||||
}
|
||||
|
||||
ratio = (float) width / (float) height;
|
||||
width = newSize;
|
||||
height = (int) (newSize / ratio);
|
||||
} else {
|
||||
if (newSize >= height) {
|
||||
throw new IllegalArgumentException("newSize must be lower than" +
|
||||
" the image height");
|
||||
} else if (newSize <= 0) {
|
||||
throw new IllegalArgumentException("newSize must" +
|
||||
" be greater than 0");
|
||||
}
|
||||
|
||||
ratio = (float) height / (float) width;
|
||||
height = newSize;
|
||||
width = (int) (newSize / ratio);
|
||||
}
|
||||
|
||||
BufferedImage temp = createCompatibleImage(image, width, height);
|
||||
Graphics2D g2 = temp.createGraphics();
|
||||
|
||||
try {
|
||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2.drawImage(image, 0, 0, temp.getWidth(), temp.getHeight(), null);
|
||||
} finally {
|
||||
g2.dispose();
|
||||
}
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a thumbnail of a source image.</p>
|
||||
* <p>This method favors speed over quality. When the new size is less than
|
||||
* half the longest dimension of the source image,
|
||||
* {@link #createThumbnail(BufferedImage, int)} or
|
||||
* {@link #createThumbnail(BufferedImage, int, int)} should be used instead
|
||||
* to ensure the quality of the result without sacrificing too much
|
||||
* performance.</p>
|
||||
*
|
||||
* @see #createThumbnailFast(java.awt.image.BufferedImage, int)
|
||||
* @see #createThumbnail(java.awt.image.BufferedImage, int)
|
||||
* @see #createThumbnail(java.awt.image.BufferedImage, int, int)
|
||||
* @param image the source image
|
||||
* @param newWidth the width of the thumbnail
|
||||
* @param newHeight the height of the thumbnail
|
||||
* @return a new compatible <code>BufferedImage</code> containing a
|
||||
* thumbnail of <code>image</code>
|
||||
* @throws IllegalArgumentException if <code>newWidth</code> is larger than
|
||||
* the width of <code>image</code> or if code>newHeight</code> is larger
|
||||
* than the height of <code>image</code> or if one of the dimensions
|
||||
* is <= 0
|
||||
*/
|
||||
public static BufferedImage createThumbnailFast(BufferedImage image,
|
||||
int newWidth, int newHeight) {
|
||||
if (newWidth >= image.getWidth() ||
|
||||
newHeight >= image.getHeight()) {
|
||||
throw new IllegalArgumentException("newWidth and newHeight cannot" +
|
||||
" be greater than the image" +
|
||||
" dimensions");
|
||||
} else if (newWidth <= 0 || newHeight <= 0) {
|
||||
throw new IllegalArgumentException("newWidth and newHeight must" +
|
||||
" be greater than 0");
|
||||
}
|
||||
|
||||
BufferedImage temp = createCompatibleImage(image, newWidth, newHeight);
|
||||
Graphics2D g2 = temp.createGraphics();
|
||||
|
||||
try {
|
||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2.drawImage(image, 0, 0, temp.getWidth(), temp.getHeight(), null);
|
||||
} finally {
|
||||
g2.dispose();
|
||||
}
|
||||
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a thumbnail of a source image. <code>newSize</code> defines
|
||||
* the length of the longest dimension of the thumbnail. The other
|
||||
* dimension is then computed according to the dimensions ratio of the
|
||||
* original picture.</p>
|
||||
* <p>This method offers a good trade-off between speed and quality.
|
||||
* The result looks better than
|
||||
* {@link #createThumbnailFast(java.awt.image.BufferedImage, int)} when
|
||||
* the new size is less than half the longest dimension of the source
|
||||
* image, yet the rendering speed is almost similar.</p>
|
||||
*
|
||||
* @see #createThumbnailFast(java.awt.image.BufferedImage, int, int)
|
||||
* @see #createThumbnailFast(java.awt.image.BufferedImage, int)
|
||||
* @see #createThumbnail(java.awt.image.BufferedImage, int, int)
|
||||
* @param image the source image
|
||||
* @param newSize the length of the largest dimension of the thumbnail
|
||||
* @return a new compatible <code>BufferedImage</code> containing a
|
||||
* thumbnail of <code>image</code>
|
||||
* @throws IllegalArgumentException if <code>newSize</code> is larger than
|
||||
* the largest dimension of <code>image</code> or <= 0
|
||||
*/
|
||||
public static BufferedImage createThumbnail(BufferedImage image,
|
||||
int newSize) {
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
|
||||
boolean isTranslucent = image.getTransparency() != Transparency.OPAQUE;
|
||||
boolean isWidthGreater = width > height;
|
||||
|
||||
if (isWidthGreater) {
|
||||
if (newSize >= width) {
|
||||
throw new IllegalArgumentException("newSize must be lower than" +
|
||||
" the image width");
|
||||
}
|
||||
} else if (newSize >= height) {
|
||||
throw new IllegalArgumentException("newSize must be lower than" +
|
||||
" the image height");
|
||||
}
|
||||
|
||||
if (newSize <= 0) {
|
||||
throw new IllegalArgumentException("newSize must" +
|
||||
" be greater than 0");
|
||||
}
|
||||
|
||||
float ratioWH = (float) width / (float) height;
|
||||
float ratioHW = (float) height / (float) width;
|
||||
|
||||
BufferedImage thumb = image;
|
||||
BufferedImage temp = null;
|
||||
|
||||
Graphics2D g2 = null;
|
||||
|
||||
try {
|
||||
int previousWidth = width;
|
||||
int previousHeight = height;
|
||||
|
||||
do {
|
||||
if (isWidthGreater) {
|
||||
width /= 2;
|
||||
if (width < newSize) {
|
||||
width = newSize;
|
||||
}
|
||||
height = (int) (width / ratioWH);
|
||||
} else {
|
||||
height /= 2;
|
||||
if (height < newSize) {
|
||||
height = newSize;
|
||||
}
|
||||
width = (int) (height / ratioHW);
|
||||
}
|
||||
|
||||
if (temp == null || isTranslucent) {
|
||||
if (g2 != null) {
|
||||
//do not need to wrap with finally
|
||||
//outer finally block will ensure
|
||||
//that resources are properly reclaimed
|
||||
g2.dispose();
|
||||
}
|
||||
temp = createCompatibleImage(image, width, height);
|
||||
g2 = temp.createGraphics();
|
||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
}
|
||||
if (g2 != null) // always the case
|
||||
g2.drawImage(thumb, 0, 0, width, height,
|
||||
0, 0, previousWidth, previousHeight, null);
|
||||
|
||||
previousWidth = width;
|
||||
previousHeight = height;
|
||||
|
||||
thumb = temp;
|
||||
} while (newSize != (isWidthGreater ? width : height));
|
||||
} finally {
|
||||
if (g2 != null) {
|
||||
g2.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
if (width != thumb.getWidth() || height != thumb.getHeight()) {
|
||||
temp = createCompatibleImage(image, width, height);
|
||||
g2 = temp.createGraphics();
|
||||
|
||||
try {
|
||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2.drawImage(thumb, 0, 0, width, height, 0, 0, width, height, null);
|
||||
} finally {
|
||||
g2.dispose();
|
||||
}
|
||||
|
||||
thumb = temp;
|
||||
}
|
||||
|
||||
return thumb;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns a thumbnail of a source image.</p>
|
||||
* <p>This method offers a good trade-off between speed and quality.
|
||||
* The result looks better than
|
||||
* {@link #createThumbnailFast(java.awt.image.BufferedImage, int)} when
|
||||
* the new size is less than half the longest dimension of the source
|
||||
* image, yet the rendering speed is almost similar.</p>
|
||||
*
|
||||
* @see #createThumbnailFast(java.awt.image.BufferedImage, int)
|
||||
* @see #createThumbnailFast(java.awt.image.BufferedImage, int, int)
|
||||
* @see #createThumbnail(java.awt.image.BufferedImage, int)
|
||||
* @param image the source image
|
||||
* @param newWidth the width of the thumbnail
|
||||
* @param newHeight the height of the thumbnail
|
||||
* @return a new compatible <code>BufferedImage</code> containing a
|
||||
* thumbnail of <code>image</code>
|
||||
* @throws IllegalArgumentException if <code>newWidth</code> is larger than
|
||||
* the width of <code>image</code> or if code>newHeight</code> is larger
|
||||
* than the height of <code>image or if one the dimensions is not > 0</code>
|
||||
*/
|
||||
public static BufferedImage createThumbnail(BufferedImage image,
|
||||
int newWidth, int newHeight) {
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
|
||||
boolean isTranslucent = image.getTransparency() != Transparency.OPAQUE;
|
||||
|
||||
if (newWidth >= width || newHeight >= height) {
|
||||
throw new IllegalArgumentException("newWidth and newHeight cannot" +
|
||||
" be greater than the image" +
|
||||
" dimensions");
|
||||
} else if (newWidth <= 0 || newHeight <= 0) {
|
||||
throw new IllegalArgumentException("newWidth and newHeight must" +
|
||||
" be greater than 0");
|
||||
}
|
||||
|
||||
BufferedImage thumb = image;
|
||||
BufferedImage temp = null;
|
||||
|
||||
Graphics2D g2 = null;
|
||||
|
||||
try {
|
||||
int previousWidth = width;
|
||||
int previousHeight = height;
|
||||
|
||||
do {
|
||||
if (width > newWidth) {
|
||||
width /= 2;
|
||||
if (width < newWidth) {
|
||||
width = newWidth;
|
||||
}
|
||||
}
|
||||
|
||||
if (height > newHeight) {
|
||||
height /= 2;
|
||||
if (height < newHeight) {
|
||||
height = newHeight;
|
||||
}
|
||||
}
|
||||
|
||||
if (temp == null || isTranslucent) {
|
||||
if (g2 != null) {
|
||||
//do not need to wrap with finally
|
||||
//outer finally block will ensure
|
||||
//that resources are properly reclaimed
|
||||
g2.dispose();
|
||||
}
|
||||
temp = createCompatibleImage(image, width, height);
|
||||
g2 = temp.createGraphics();
|
||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
}
|
||||
if (g2 != null) // always the case
|
||||
g2.drawImage(thumb, 0, 0, width, height,
|
||||
0, 0, previousWidth, previousHeight, null);
|
||||
|
||||
previousWidth = width;
|
||||
previousHeight = height;
|
||||
|
||||
thumb = temp;
|
||||
} while (width != newWidth || height != newHeight);
|
||||
} finally {
|
||||
if (g2 != null) {
|
||||
g2.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
if (width != thumb.getWidth() || height != thumb.getHeight()) {
|
||||
temp = createCompatibleImage(image, width, height);
|
||||
g2 = temp.createGraphics();
|
||||
|
||||
try {
|
||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2.drawImage(thumb, 0, 0, width, height, 0, 0, width, height, null);
|
||||
} finally {
|
||||
g2.dispose();
|
||||
}
|
||||
|
||||
thumb = temp;
|
||||
}
|
||||
|
||||
return thumb;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Returns an array of pixels, stored as integers, from a
|
||||
* <code>BufferedImage</code>. The pixels are grabbed from a rectangular
|
||||
* area defined by a location and two dimensions. Calling this method on
|
||||
* an image of type different from <code>BufferedImage.TYPE_INT_ARGB</code>
|
||||
* and <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.</p>
|
||||
*
|
||||
* @param img the source image
|
||||
* @param x the x location at which to start grabbing pixels
|
||||
* @param y the y location at which to start grabbing pixels
|
||||
* @param w the width of the rectangle of pixels to grab
|
||||
* @param h the height of the rectangle of pixels to grab
|
||||
* @param pixels a pre-allocated array of pixels of size w*h; can be null
|
||||
* @return <code>pixels</code> if non-null, a new array of integers
|
||||
* otherwise
|
||||
* @throws IllegalArgumentException is <code>pixels</code> is non-null and
|
||||
* of length < w*h
|
||||
*/
|
||||
public static int[] getPixels(BufferedImage img,
|
||||
int x, int y, int w, int h, int[] pixels) {
|
||||
if (w == 0 || h == 0) {
|
||||
return new int[0];
|
||||
}
|
||||
|
||||
if (pixels == null) {
|
||||
pixels = new int[w * h];
|
||||
} else if (pixels.length < w * h) {
|
||||
throw new IllegalArgumentException("pixels array must have a length" +
|
||||
" >= w*h");
|
||||
}
|
||||
|
||||
int imageType = img.getType();
|
||||
if (imageType == BufferedImage.TYPE_INT_ARGB ||
|
||||
imageType == BufferedImage.TYPE_INT_RGB) {
|
||||
Raster raster = img.getRaster();
|
||||
return (int[]) raster.getDataElements(x, y, w, h, pixels);
|
||||
}
|
||||
|
||||
// Unmanages the image
|
||||
return img.getRGB(x, y, w, h, pixels, 0, w);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Writes a rectangular area of pixels in the destination
|
||||
* <code>BufferedImage</code>. Calling this method on
|
||||
* an image of type different from <code>BufferedImage.TYPE_INT_ARGB</code>
|
||||
* and <code>BufferedImage.TYPE_INT_RGB</code> will unmanage the image.</p>
|
||||
*
|
||||
* @param img the destination image
|
||||
* @param x the x location at which to start storing pixels
|
||||
* @param y the y location at which to start storing pixels
|
||||
* @param w the width of the rectangle of pixels to store
|
||||
* @param h the height of the rectangle of pixels to store
|
||||
* @param pixels an array of pixels, stored as integers
|
||||
* @throws IllegalArgumentException is <code>pixels</code> is non-null and
|
||||
* of length < w*h
|
||||
*/
|
||||
public static void setPixels(BufferedImage img,
|
||||
int x, int y, int w, int h, int[] pixels) {
|
||||
if (pixels == null || w == 0 || h == 0) {
|
||||
return;
|
||||
} else if (pixels.length < w * h) {
|
||||
throw new IllegalArgumentException("pixels array must have a length" +
|
||||
" >= w*h");
|
||||
}
|
||||
|
||||
int imageType = img.getType();
|
||||
if (imageType == BufferedImage.TYPE_INT_ARGB ||
|
||||
imageType == BufferedImage.TYPE_INT_RGB) {
|
||||
WritableRaster raster = img.getRaster();
|
||||
raster.setDataElements(x, y, w, h, pixels);
|
||||
} else {
|
||||
// Unmanages the image
|
||||
img.setRGB(x, y, w, h, pixels, 0, w);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the data from the image.
|
||||
*
|
||||
* @param img
|
||||
* the image to erase
|
||||
*/
|
||||
public static void clear(Image img) {
|
||||
Graphics g = img.getGraphics();
|
||||
|
||||
try {
|
||||
if (g instanceof Graphics2D) {
|
||||
((Graphics2D) g).setComposite(AlphaComposite.Clear);
|
||||
} else {
|
||||
g.setColor(new Color(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
g.fillRect(0, 0, img.getWidth(null), img.getHeight(null));
|
||||
} finally {
|
||||
g.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the clip on a graphics object by merging a supplied clip with the existing one. The new
|
||||
* clip will be an intersection of the old clip and the supplied clip. The old clip shape will
|
||||
* be returned. This is useful for resetting the old clip after an operation is performed.
|
||||
*
|
||||
* @param g
|
||||
* the graphics object to update
|
||||
* @param clip
|
||||
* a new clipping region to add to the graphics clip.
|
||||
* @return the current clipping region of the supplied graphics object. This may return {@code
|
||||
* null} if the current clip is {@code null}.
|
||||
* @throws NullPointerException
|
||||
* if any parameter is {@code null}
|
||||
* @deprecated Use {@link ShapeUtils#mergeClip(Graphics,Shape)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public static Shape mergeClip(Graphics g, Shape clip) {
|
||||
return ShapeUtils.mergeClip(g, clip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws an image on top of a component by doing a 3x3 grid stretch of the image
|
||||
* using the specified insets.
|
||||
* @param g the graphics object
|
||||
* @param comp the component
|
||||
* @param img the image
|
||||
* @param ins the insets
|
||||
*/
|
||||
public static void tileStretchPaint(Graphics g,
|
||||
JComponent comp,
|
||||
BufferedImage img,
|
||||
Insets ins) {
|
||||
|
||||
int left = ins.left;
|
||||
int right = ins.right;
|
||||
int top = ins.top;
|
||||
int bottom = ins.bottom;
|
||||
|
||||
// top
|
||||
g.drawImage(img,
|
||||
0,0,left,top,
|
||||
0,0,left,top,
|
||||
null);
|
||||
g.drawImage(img,
|
||||
left, 0,
|
||||
comp.getWidth() - right, top,
|
||||
left, 0,
|
||||
img.getWidth() - right, top,
|
||||
null);
|
||||
g.drawImage(img,
|
||||
comp.getWidth() - right, 0,
|
||||
comp.getWidth(), top,
|
||||
img.getWidth() - right, 0,
|
||||
img.getWidth(), top,
|
||||
null);
|
||||
|
||||
// middle
|
||||
g.drawImage(img,
|
||||
0, top,
|
||||
left, comp.getHeight()-bottom,
|
||||
0, top,
|
||||
left, img.getHeight()-bottom,
|
||||
null);
|
||||
|
||||
g.drawImage(img,
|
||||
left, top,
|
||||
comp.getWidth()-right, comp.getHeight()-bottom,
|
||||
left, top,
|
||||
img.getWidth()-right, img.getHeight()-bottom,
|
||||
null);
|
||||
|
||||
g.drawImage(img,
|
||||
comp.getWidth()-right, top,
|
||||
comp.getWidth(), comp.getHeight()-bottom,
|
||||
img.getWidth()-right, top,
|
||||
img.getWidth(), img.getHeight()-bottom,
|
||||
null);
|
||||
|
||||
// bottom
|
||||
g.drawImage(img,
|
||||
0,comp.getHeight()-bottom,
|
||||
left, comp.getHeight(),
|
||||
0,img.getHeight()-bottom,
|
||||
left,img.getHeight(),
|
||||
null);
|
||||
g.drawImage(img,
|
||||
left, comp.getHeight()-bottom,
|
||||
comp.getWidth()-right, comp.getHeight(),
|
||||
left, img.getHeight()-bottom,
|
||||
img.getWidth()-right, img.getHeight(),
|
||||
null);
|
||||
g.drawImage(img,
|
||||
comp.getWidth()-right, comp.getHeight()-bottom,
|
||||
comp.getWidth(), comp.getHeight(),
|
||||
img.getWidth()-right, img.getHeight()-bottom,
|
||||
img.getWidth(), img.getHeight(),
|
||||
null);
|
||||
}
|
||||
}
|
||||
159
src/main/java/org/jdesktop/swingx/util/ShapeUtils.java
Normal file
159
src/main/java/org/jdesktop/swingx/util/ShapeUtils.java
Normal file
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* $Id: ShapeUtils.java 4082 2011-11-15 18:39:43Z kschaefe $
|
||||
*
|
||||
* Copyright 2006 Sun Microsystems, Inc., 4150 Network Circle,
|
||||
* Santa Clara, California 95054, U.S.A. All rights reserved.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.jdesktop.swingx.util;
|
||||
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Polygon;
|
||||
import java.awt.Shape;
|
||||
import java.awt.font.GlyphVector;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Area;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
import java.awt.geom.GeneralPath;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author joshy
|
||||
*/
|
||||
public final class ShapeUtils {
|
||||
|
||||
/** Creates a new instance of ShapeUtils */
|
||||
private ShapeUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a polygon with an inside radius of 0.
|
||||
* @param sides number of sides
|
||||
* @param outsideRadius the outside radius
|
||||
* @param normalize normalize
|
||||
* @return the generated shape
|
||||
*/
|
||||
public static Shape generatePolygon(int sides, int outsideRadius, boolean normalize) {
|
||||
return generatePolygon(sides, outsideRadius, 0, normalize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a polygon
|
||||
* @param sides number of sides
|
||||
* @param outsideRadius the outside radius
|
||||
* @param insideRadius the inside radius
|
||||
* @param normalize normalize
|
||||
* @return the generated shape
|
||||
*/
|
||||
public static Shape generatePolygon(int sides, int outsideRadius, int insideRadius,
|
||||
boolean normalize) {
|
||||
Shape shape = generatePolygon(sides, outsideRadius, insideRadius);
|
||||
if (normalize) {
|
||||
Rectangle2D bounds = shape.getBounds2D();
|
||||
GeneralPath path = new GeneralPath(shape);
|
||||
shape = path.createTransformedShape(AffineTransform.getTranslateInstance(
|
||||
-bounds.getX(), -bounds.getY()));
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a polygon
|
||||
* @param sides number of sides
|
||||
* @param outsideRadius the outside radius
|
||||
* @param insideRadius the inside radius
|
||||
* @return the generated shape
|
||||
*/
|
||||
public static Shape generatePolygon(int sides, int outsideRadius, int insideRadius) {
|
||||
if (sides < 3) {
|
||||
return new Ellipse2D.Float(0, 0, 10, 10);
|
||||
}
|
||||
|
||||
AffineTransform trans = new AffineTransform();
|
||||
Polygon poly = new Polygon();
|
||||
for (int i = 0; i < sides; i++) {
|
||||
trans.rotate(Math.PI * 2 / sides / 2);
|
||||
Point2D out = trans.transform(new Point2D.Float(0, outsideRadius), null);
|
||||
poly.addPoint((int) out.getX(), (int) out.getY());
|
||||
trans.rotate(Math.PI * 2 / sides / 2);
|
||||
if (insideRadius > 0) {
|
||||
Point2D in = trans.transform(new Point2D.Float(0, insideRadius), null);
|
||||
poly.addPoint((int) in.getX(), (int) in.getY());
|
||||
}
|
||||
}
|
||||
|
||||
return poly;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param font the font
|
||||
* @param ch a single character
|
||||
* @return the shape
|
||||
*/
|
||||
public static Shape generateShapeFromText(Font font, char ch) {
|
||||
return generateShapeFromText(font, String.valueOf(ch));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param font the font
|
||||
* @param string the text string
|
||||
* @return the shape
|
||||
*/
|
||||
public static Shape generateShapeFromText(Font font, String string) {
|
||||
BufferedImage img = GraphicsUtilities.createCompatibleTranslucentImage(1, 1);
|
||||
Graphics2D g2 = img.createGraphics();
|
||||
|
||||
try {
|
||||
GlyphVector vect = font.createGlyphVector(g2.getFontRenderContext(), string);
|
||||
Shape shape = vect.getOutline(0f, (float) -vect.getVisualBounds().getY());
|
||||
|
||||
return shape;
|
||||
} finally {
|
||||
g2.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the clip on a graphics object by merging a supplied clip with the existing one. The new
|
||||
* clip will be an intersection of the old clip and the supplied clip. The old clip shape will
|
||||
* be returned. This is useful for resetting the old clip after an operation is performed.
|
||||
*
|
||||
* @param g
|
||||
* the graphics object to update
|
||||
* @param clip
|
||||
* a new clipping region to add to the graphics clip.
|
||||
* @return the current clipping region of the supplied graphics object. This may return
|
||||
* {@code null} if the current clip is {@code null}.
|
||||
* @throws NullPointerException
|
||||
* if any parameter is {@code null}
|
||||
*/
|
||||
public static Shape mergeClip(Graphics g, Shape clip) {
|
||||
Shape oldClip = g.getClip();
|
||||
if (oldClip == null) {
|
||||
g.setClip(clip);
|
||||
return null;
|
||||
}
|
||||
Area area = new Area(oldClip);
|
||||
area.intersect(new Area(clip));// new Rectangle(0,0,width,height)));
|
||||
g.setClip(area);
|
||||
return oldClip;
|
||||
}
|
||||
}
|
||||
18
src/main/java/org/jma/encoder/audio/IAudioEncoder.java
Normal file
18
src/main/java/org/jma/encoder/audio/IAudioEncoder.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package org.jma.encoder.audio;
|
||||
|
||||
/**
|
||||
* Retrieved from: http://livertmpjavapublisher.blogspot.com/2011/06/orgtritonuslowlevellameapiiaudioencoder.html
|
||||
* on 5 Sep 2016
|
||||
*/
|
||||
public interface IAudioEncoder
|
||||
{
|
||||
public int encodeBuffer(byte[] input, int offset, int encodedCount, byte[] output);
|
||||
|
||||
public int encodeFinish(byte[] input);
|
||||
|
||||
public int getInputBufferSize();
|
||||
|
||||
public int getOutputBufferSize();
|
||||
|
||||
public void close();
|
||||
}
|
||||
Reference in New Issue
Block a user