fix src folder structure

This commit is contained in:
Roman Tsypuk
2017-12-15 23:22:32 +02:00
parent f2d1ac7e48
commit b1e07f762c
981 changed files with 7763 additions and 7775 deletions

View 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(&quot;foo&quot;, 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(&quot;foo&quot;, old, getFoo());
* }
* public String getFoo() {
* return foo;
* }
* }
*
* public class Tester {
* public static void main(String... args) {
* try {
* ABean a = new ABean();
* a.setFoo(&quot;fred&quot;);
* a.addVetoableChangeListener(new VetoableChangeListener() {
* public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
* if (&quot;red&quot;.equals(evt.getNewValue()) {
* throw new PropertyVetoException(&quot;Cannot be red!&quot;, evt);
* }
* }
* }
* a.setFoo(&quot;red&quot;);
* } 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;
}
}

View 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);
}
}
}
}

View 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>

View 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);
}
});
}
}

View 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;
}
}

View 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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View 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 );
}
}
}

View File

@@ -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();
}
});
}
}

View File

@@ -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());
}
}

View File

@@ -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));
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View 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());
}
}

View 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 + "]";
}
}

View File

@@ -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
}
}
}
}

View 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;
}
}

View 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 );
}
}

View 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);
}

View 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&amp;x=1&amp;y=2&amp;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;
}
}

View File

@@ -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);
}

View 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();
}

View File

@@ -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());
}
}

View File

@@ -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);
}

View File

@@ -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
*/
}

View File

@@ -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;
}
}

View File

@@ -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
}
}

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View 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;
}
}

View File

@@ -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);
}
}

View 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;
}
}

View File

@@ -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));
}
});
}
}

View 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);
}
}

View 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);
}
}

View 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&lt;Component&gt; p = new Painter&lt;Component&gt;() {
* 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);
}

View 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 &lt;= 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 &lt;= 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 &lt;= 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 &gt; 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 &lt; 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 &lt; 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);
}
}

View 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;
}
}

View 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();
}