PDA

View Full Version : creare una mappa in java


hello
16-05-2007, 01:38
volendo realizzare con java swing qlc di simile a una mappa cliccabile (ad esempio ho un immagine che rappresenta l'italia e voglio cliccare sulle diverse regioni per verificare un evento) come bisogna procedere, potete darmi qlc suggerimento ed è una cosa fattibile utilizzando java?

grazie!!! :)

-fidel-
16-05-2007, 10:22
volendo realizzare con java swing qlc di simile a una mappa cliccabile (ad esempio ho un immagine che rappresenta l'italia e voglio cliccare sulle diverse regioni per verificare un evento) come bisogna procedere, potete darmi qlc suggerimento ed è una cosa fattibile utilizzando java?

grazie!!! :)

Tutto si può fare :)

L'idea più "manuale" (senza usare classi ad hoc) è questa:

1) Carichi l'immagine e la piazzi in una precisa posizione (sai in pratica le coordinate x e y, relative alla finestra, in cui si trova l'angolo superiore sinistro dell'immagine).
2) Conoscendo anche le dimensioni dell'immagine calcolata (e le sai facilmente), ti basta catturare gli eventi "click" del mouse: nella relativa callback, in cui conosci la posizione del mouse in quel momento, puoi sapere DOVE l'utente ha cliccato, e reagire di conseguenza.

Ovviamente puoi affinare molto la cosa, ad esempio crei una sorta di "backbuffer" per l'immagine (una matrice in cui ogni elemento rappresenta una regione dell'immagine - come se suddividessi in tanti quadrati l'immagine di partenza) ed al click del mouse attivi una precisa regione (un elemento del backbuffer).

EDIT: Nel caso vuoi procedere con il "backbuffer", ti consiglio di dare un'occhiata alla classe ImageObserver di java.awt

hello
16-05-2007, 10:59
ma se la zona dove devo cliccare contiene nello stesso rettangolo/quadrato anche una parte di di un'alta regione, cioè non ho forme regolari (es. l'italia con le varie regioni e a ogni regione è associato un evento "clic" del mouse)

hai per favore il link a qualche esempio?

grazie!!! ;)

lovaz
16-05-2007, 11:10
Non e' banale, ma...

Se hai il controllo sulla mappa (se la fai tu) potresti adottare questo accorgimento:
Colori le varie regioni (es. dell'Italia) ognuna di un diverso colore, e quando
rilevi il click del mouse basta che controlli il colore del pixel, e sai su che regione hai cliccato!

PGI-Bis
16-05-2007, 11:31
Quella di lovaz è una soluzione particolarmente acuta. Ti consente, in effetti, di creare immagini cliccabili senza dover passare per uno strumento autonomo di definizione delle regioni. Ed è anche semplice da realizzare, sia teoricamente che praticamente.

Rispetto all'alternativa, cioè l'uso di poligoni vettoriali (a là image map html), consuma un po' più di memoria. Ma vale l'equazione classica cpu = 1 / memoria.

Ecco come si potrebbe fare.

Pigli l'immagine da rendere interattiva. Ne crei una copia e la colori per regione, con GIMP. In un file di testo (o xml o come preferisci) tieni traccia delle associazioni regione-colore. Tipo:

RGB(255, 255, 255) = Lazio
RGB(0, 0, 0) = Emilia Romagna
RGB(100, 100, 100) = Molise

eccetera (ok, non le so tutte :D).

A questo punto hai per le mani due immagini e un file. La cartina, la cartina colorata e il file delle associazioni colore-regione. E sei a posto. Proietti sullo schermo la cartina normale. Carichi in memoria la cartina colorata. Carichi le associazioni colore-regione in una mappa, nel senso della struttura dati (Map<Color, String>) Al click del mouse sulla cartina prendi il colore del pixel sulla cartina in memoria

int argb = cartina.getRGB(mouse.getX(), mouse.getY());
Color pixelColor = new Color(argb);

Col colore ottieni la regione:

String regione = mappaColoriRegioni.get(pixelColor);

E una volta che sei in grado di identificare la regione sei a cavallo. Puoi collegare a colpi di Map il nome della regione a degli oggetti e gli oggetti fanno un po' quello che ti pare.

71104
16-05-2007, 11:36
forse allora può esserti utile il celebre Five-Coloring Algorithm: http://en.wikipedia.org/wiki/Five_color_theorem (guarda in fondo).

-fidel-
16-05-2007, 11:47
Il meccanismo descritto da PGI-Bis è in pratica lo stesso usato nel Picking OpenGL.
Il problema che sorge è in presenza di mappe molto grandi, immagini che occupano molti megabytes o in presenza di molte immagini, in cui avviene/può avvenire un calo di prestazioni.
In quel caso. si può ottimizzare il tutto creando delle aree precise (sempre la matrice di cui parlavo prima) che vengono caricate su richiesta (quando clicchi, se l'elemento della matrice "back" non ha ancora il suo pezzetto di immagine associata, la carichi). Il confronto sul colore lo fai quindi solo sulla sezione (in gergo "texel") corrispondente all'area attiva (area = elemento della matrice).

Ancora, si può sfruttare ad esempio l'algoritmo postato da 71104 per creare dinamicamente la mappa colorata a partire da uno schema vettoriale (per emulare le map html), in questo mdo l'implementazione diventa generica ed applicabile a tutte le immagini che vuoi.

EDIT: il meccanismo che avevo postato all'inizio è semplicissimo da implementare, ma evidentemente non fa al caso tuo :) Comunque neanche il picking "by color" è difficile da implementare, tra l'altro il 90% del lavoro te lo ha fatto PGI-BIS :D

hello
16-05-2007, 11:51
utilizzando il metodo dei colori se ho capito bene verrà visualizzata la cartina originale (con laghi, fiumi, strade, ...) e a questa è sovrapposta un'altra "trasparente" suddivisa per regione = determinato colore, ma come si fa a sovrapporre le due e quella dei colori renderla trasparente :help:

volendo utilizzare invece l'alternativa dei poligoni vettoriali come è, più facile da realizzare oppure no, e come funziona :help:

-fidel-
16-05-2007, 11:56
utilizzando il metodo dei colori se ho capito bene verrà visualizzata la cartina originale (con laghi, fiumi, strade, ...) e a questa è sovrapposta un'altra "trasparente" suddivisa per regione = determinato colore, ma come si fa a sovrapporre le due e quella dei colori renderla trasparente :help:

volendo utilizzare invece l'alternativa dei poligoni vettoriali come è, più facile da realizzare oppure no, e come funziona :help:

La mappa colorata non deve essere visualizzata, ma solo presente in memoria, e fa da riferimento per il picking. Da quella, risali alla regione selezionata ed inneschi l'evento desiderato.
In più poi, puoi anche "illuminare" la regione cliccata sulla cartina visualizzata, ma è un'altro paio di maniche.

Per i poligoni vettoriali, secondo me per il momento lasciali stare (tra l'altro, con l'algoritmo dei 5 colori, usi appunto 5 colori, ma tu non hai 5 regioni... è più facile avere n colori per n regioni distinte, per semplificare l'associazione colore-regione.

PGI-Bis
16-05-2007, 12:06
Per i poligoni dovresti creare a parte uno strumento che ti consenta di creare e salvare dei poligoni vettoriali associati alle regioni.

Dopodichè carichi la tua cartina e la proietti, carichi i poligoni come oggetti GeneralPath e li metti in una lista.

Quando l'utente fa "click", prendi il punto dove l'ha fatto, prendi la lista di GeneralPath che hai memorizzato e, semplicemente, dici:

dato il Point CLICK = mouseEvent.getPoint();
per ogni GeneralPath GP nella lista L
se GP.contains(CLICK)
la regione è quella associata a GP
altrimenti
continua

Il metodo contains di Shape prende un Point e restituisce un boolean che vale true se il Point è contenuto nella Shape e false altrimenti.

Il problema qui è creare i poligoni associati alle regioni. Non basta più Paint. Ci vuole uno strumento vettoriale che possa esportare i poligoni in un formato trasformabile in un GeneralPath. E' un programma di facile realizzazione ma bisogna sempre farlo.

hello
16-05-2007, 12:23
potete per favore farmi un esempio di codice, se vi è possibile! :help:

allego due immagini

http://img185.imageshack.us/img185/4849/immagine1vi6.th.png (http://img185.imageshack.us/my.php?image=immagine1vi6.png)

http://img82.imageshack.us/img82/4816/immagine2yg5.th.png (http://img82.imageshack.us/my.php?image=immagine2yg5.png)

grazie!!!

lovaz
16-05-2007, 13:06
Mostri la prima, carichi la seconda in memoria.

Quando si clicca sullo schermo vai a vedere sulla mappa "nascosta"
il colore del pixel alla posizione cliccata (a meno di offset).

PGI-Bis
16-05-2007, 14:01
Tra un attimo incollo il codice... prima mi bevo un caffettino (ho un bufalo alla brace sullo stomaco)

PGI-Bis
16-05-2007, 14:35
Allora. Questo è il succo di tutto il programma.

package it.hello.imagemap;

import java.awt.event.*;

public interface ClickToItem {

Object getItemForClick(MouseEvent e);
}

Supponiamo che l'immagine colorata (che io non vedo) abbia tre regioni colorate. Associ a queste regioni un identificatore in un file di testo che potrebbe essere:

IN ALTO=255,0,0
IN BASSO=0,0,255
IN MEZZO=0,255,0

A sinistra dell'uguale metti il nome associato alla regione, a destra il colore come tripletta RGB. Si può fare anche senza il nome, usando direttamente il colore come identificatore, ma penso che infilandoci anche una scrittina risulti più chiaro.

Quel formato può essere trasformato in una mappa (Colore, Nome) con una semplice funzione:

private static Map<Object, Object> readColorMap(String relativeURL)
throws IOException
{
Map<Object, Object> map = new HashMap<Object, Object>();
Scanner scanner = new Scanner(Main.class.getResourceAsStream(
relativeURL));
try {
while(scanner.hasNextLine()) {
String[] tokens = scanner.nextLine().split("=");
String name = tokens[0].trim();
String[] rgbValues = tokens[1].split(",");
Color color = new Color(
Integer.parseInt(rgbValues[0].trim()),
Integer.parseInt(rgbValues[1].trim()),
Integer.parseInt(rgbValues[2].trim()));
map.put(color, name);
}
} finally {
if(scanner != null) scanner.close();
}
return map;
}

che poi infilerò nel main.

La versione di ClickToItem che analizza i pixel di un'immagine colorata per determinare quale "nome" sia associato può essere:

package it.hello.imagemap;

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
import java.util.*;

public class ColorClickToItem implements ClickToItem {
private BufferedImage image;
private Rectangle imageBounds;
private Map<Object, Object> colorMap = new HashMap<Object, Object>();

public ColorClickToItem(BufferedImage image, Map<Object, Object> map) {
this.image = image;
imageBounds = new Rectangle(0, 0, image.getWidth(), image.getHeight());
colorMap.putAll(map);
}

public Object getItemForClick(MouseEvent e) {
JComponent component = (JComponent)e.getComponent();
Point position = e.getPoint();
Insets margin = component.getInsets();
position.translate(-margin.top, -margin.left);
Color pixelColor = getPixelColor(position);
return colorMap.get(pixelColor);
}

private Color getPixelColor(Point p) {
return
imageBounds.contains(p) ?
new Color(image.getRGB(p.x, p.y)) :
null;
}
}

Prende il mouse event ed il suo punto. Considerando eventuali margini del componente che ha generato quel click, riporta il punto nel sistema di coordinate dell'immagine. Se il punto così trasformato è contenuto nell'area complessiva dell'immagine allora prende il colore del pixel in quel punto e lo usa per ottenere dalla mappa "colorMap" il relativo "nome".

Prendi il punto, trasforma il punto, trova il pixel, trova l'oggetto associato al pixel.

Il resto è storia. L'interfaccia per provare è una finestra con dentro un'etichetta. L'etichetta proietta un'immagine. C'è un MouseListener connesso all'etichetta. Quando intercetta un click chiede da un ClickToItem quale sia il "nome" corrispondente. E apre una tristissima finestrella con quel nome :D:

package it.hello.imagemap;

import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.util.*;

public class MainFrame {
private JFrame window = new JFrame("Click to item");
private ClickToItem clickManager;

public MainFrame(BufferedImage image, ClickToItem aClickToItem) {
clickManager = aClickToItem;
JLabel imageLabel = new JLabel(new ImageIcon(image));
imageLabel.setBorder(BorderFactory.createLineBorder(Color.BLACK, 2));
imageLabel.addMouseListener(mouseManager);
imageLabel.addMouseMotionListener(mouseManager);
JPanel container = new JPanel(new GridBagLayout());
GridBagConstraints lim = new GridBagConstraints();
lim.gridx = lim.gridy = 0;
container.add(imageLabel, lim);
window.add(container);
window.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
}

public void show() {
window.pack();
window.setSize(
window.getWidth() + window.getWidth() * 5 / 100,
window.getHeight() + window.getHeight() * 5 / 100);
window.setResizable(false);
window.setVisible(true);
}

private void performActionForItem(Object item) {
JOptionPane.showMessageDialog(window, item);
}

private MouseInputAdapter mouseManager = new MouseInputAdapter() {
public void mouseClicked(MouseEvent e) {
if(SwingUtilities.isLeftMouseButton(e)) {
Object item = clickManager.getItemForClick(e);
if(item != null) {
performActionForItem(item);
}
}
}

public void mouseMoved(MouseEvent e) {
if(clickManager.getItemForClick(e) == null) {
e.getComponent().setCursor(Cursor.getDefaultCursor());
} else {
e.getComponent().setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
}
};
}

A questo punto, supponendo che le immagini e il testo si trovino in una cartella "immagini" il cui percorso è relativo a quello delle classi java del programma, supponendo che la cartina sia l'immagine "immagine1vi6.png", che la cartina colorata sia l'immagine "immagine1vi6_colori.png", che il file delle associazioni colore-nome sia "mapitems.txt", il programma parte con:

package it.hello.imagemap;

import java.awt.*;
import java.awt.image.*;
import javax.imageio.*;
import javax.swing.*;
import java.io.*;
import java.util.*;

public class Main {

public static void main(String[] args) {
final BufferedImage image;
final BufferedImage colorMap;
Map<Object, Object> itemMap;

/* Carica le immagini e la mappa */
try {
image = ImageIO.read(Main.class.getResource("/immagini/immagine1vi6.png"));
colorMap = ImageIO.read(Main.class.getResource("/immagini/immagine1vi6_colori.png"));
itemMap = readColorMap("/immagini/mapitems.txt");
} catch(java.io.IOException ex) {
showError(ex);
return;
}

/* Con la mappa crea un ColorClickToItem */
final ColorClickToItem clicker = new ColorClickToItem(
colorMap,
itemMap);

/* Con l'immagine della cartina e il color click to item crea
l'interfaccia e la proietta sullo schermo */
SwingUtilities.invokeLater(new Runnable() { public void run() {
new MainFrame(image, clicker).show();
}});
}

/* se qualcosa va a catafascio... */
private static void showError(Throwable t) {
JOptionPane.showMessageDialog(null,
"<html>Errore del programma<br>"+t.getMessage());
}

/* Procedurone per la creazione di una mappa Color-String a partire da
un file di testo contenente un numero a piacere di linee, dove ogni linea
contiene un [token] avente la forma:
[text]=[RGB]
[text] qualsiasi sequenza di caratteri ASCII diversi dal carattere = e
interruzioni di linea
[RGB]
[R],[G],[B]
[R] = un numero intero da 0 a 255
[G] = un numero intero da 0 a 255
[B] = un numero intero da 0 a 255 */
private static Map<Object, Object> readColorMap(String relativeURL)
throws IOException
{
Map<Object, Object> map = new HashMap<Object, Object>();
Scanner scanner = new Scanner(Main.class.getResourceAsStream(
relativeURL));
try {
while(scanner.hasNextLine()) {
String[] tokens = scanner.nextLine().split("=");
String name = tokens[0].trim();
String[] rgbValues = tokens[1].split(",");
Color color = new Color(
Integer.parseInt(rgbValues[0].trim()),
Integer.parseInt(rgbValues[1].trim()),
Integer.parseInt(rgbValues[2].trim()));
map.put(color, name);
}
} finally {
if(scanner != null) scanner.close();
}
return map;
}
}

Floris
16-05-2007, 14:42
Per dovere di cronaca esiste anche il teorema dei quattro colori.
Cioè ti bastano 4 colori diversi per colorare tutta la mappa.

hello
16-05-2007, 22:55
grazie mille PGI-Bis sei stato gentilissimo adesso devo studiarmi il codice per capire bene come funziona, scusa se non ti ho risposto prima ma soltanto adesso sono riuscito a connettermi perchè ho avuto problemi con l'adsl


per il file mapitems.txt ho copiato l'esempio

IN ALTO=255,0,0
IN BASSO=0,0,255
IN MEZZO=0,255,0

ma quando lancio il programma se clicco nella parte alta esce scritto "IN BASSO" , se clicco nella parte bassa esce scritto "IN ALTO" mentre la parte centrale non è cliccabile!!! quanti colori si possono utilizzare e come calcolare i valori di questi per inserirli nel file .txt ?

ancora grazie!!! ;)

PGI-Bis
16-05-2007, 23:04
Tutti i colori che vuoi. Io ho preso l'immagine che hai incollato, l'ho svuotata e l'ho colorata di rosso in alto, di verde al centro e di blu in basso.

Controlla che i valori dei colori che usi corrispondano a quelli nel file (in paint vedi le triplette rgb facendo doppio click sul colore e scegliendo "definisci colori personalizzati") e non usare immagini compresse con perdita di informazioni (png sì, jpg no).

hello
16-05-2007, 23:27
ho ricolorato l'immagine con i colori che mi hai detto ed adesso funziona perfettamente!!! :D

grazie!!! :)