The code shown below has the following features
- Left//rght click mouse to zoom in/out at the current mouse position
- Click and drag the mouse to pan left/right/up/down
- City colour changes to red when cursor is over it.
It is very common task to display the same data but in different ways. In this case we have city location data that we want to display on a 'map' which we can zoom/pan/select a city.
My solution takes a different approach to the code you posted so some explanation might be useful.
Get a map and pin it against the wall and then look at the centre of the map from about 60cm. You will see the details immediately in front of you but the edges will not be visible. Now move your head from side to side and you will see different parts of the map but at the same scale. To zoom out simply move your head away from the map, you can see more of the map but less detail i.e. at a lower scale. Moving your head towards the map means you see a smaller area but more detail i.e. higher scale.
So we have achieved panning and zooming with our map WITHOUT touching the map. We did it by transforming our view NOT by transforming the map data.
In your original code your City class had x,y attributes to hold the current screen position to display the City, these are calculated in the setCoityXY method depending on current zoom. This is equivalent to transforming the map data.
The code below has a new class called WorldViewData which is used to hold the data and provide suitable methods to transform world to/from display coordinates i.e. transforming the view.
In the draw method lines 48/49 setup the transofrmation required to draw the city locations using world coordinates. In the calss City draw method the only transformation is the translation to the lon/lat (real world) position of the city.
HTH
- ArrayList<City> citys = new ArrayList<City>();
- float minX = MAX_FLOAT;
- float maxX = MIN_FLOAT;
- float minY = MAX_FLOAT;
- float maxY = MIN_FLOAT;
- WorldViewData wvd = new WorldViewData();
- float vpW, vpH; // display location
- City mouseIsOver = null;
- void setup() {
- size(600, 600);
- cursor(CROSS);
- // Define display size from top left corner
- vpW = 400;
- vpH = 250;
- citys.add(new City(53.87472, -119.1225));
- citys.add(new City(55.45, -119.9));
- citys.add(new City(55.209446, -119.42917));
- citys.add(new City(53.543564, -113.490456));
- citys.add(new City(49.261227, -123.11393));
- citys.add(new City(49.88795, -119.49601));
- citys.add(new City(38.581573, -121.4944));
- citys.add(new City(38.617126, -121.328285));
- citys.add(new City(34.278587, -118.50211));
- for (City c : citys) {
- if (c.lat > maxY) maxY = c.lat;
- if (c.lat < minY) minY = c.lat;
- if (c.lon > maxX) maxX = c.lon;
- if (c.lon < minX) minX = c.lon;
- }
- float zoomX = vpW / (maxX - minX);
- float zoomY = vpH / (maxY - minY);
- // Start with display big enough to see all cities.
- wvd.setXY(minX, minY);
- wvd.viewRatio = min(zoomX, zoomY);
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void draw() {
- background(255);
- // Draw the cities
- pushMatrix();
- translate(-wvd.orgX * wvd.viewRatio, -wvd.orgY * wvd.viewRatio);
- scale(wvd.viewRatio);
- for (City c : citys)
- c.draw();
- popMatrix();
- // Cities drawn make the area outside the city
- // display area blue. The blue area have anything
- // you like e.g. controls etc.
- pushStyle();
- noStroke();
- fill(0, 0, 192);
- rect(vpW, 0, width - vpW, height);
- rect(0, vpH, width, height - vpH);
- popStyle();
- }
- // See if we are over a city
- void mouseMoved() {
- float wmX = wvd.pixel2worldX(mouseX);
- float wmY = wvd.pixel2worldY(mouseY);
- mouseIsOver = null;
- for (City c : citys) {
- if (dist(wmX, wmY, c.lon, c.lat) < 3.0 / wvd.viewRatio) {
- mouseIsOver = c;
- }
- }
- }
- // Change zoom level
- void mouseClicked() {
- if (mouseX < vpW && mouseY < vpH) {
- if (mouseButton == LEFT || mouseButton == RIGHT) {
- // Calculate current mouse position position
- float wmX = wvd.pixel2worldX(mouseX);
- float wmY = wvd.pixel2worldY(mouseY);
- float sf = (mouseButton == LEFT) ? 1.1 : 0.9;
- wvd.viewRatio *= sf;
- wvd.viewRatio = constrain(wvd.viewRatio, 0.05, 200.0);
- wvd.orgX = wmX - mouseX / wvd.viewRatio;
- wvd.orgY = wmY - mouseY / wvd.viewRatio;
- }
- }
- }
- // Panning
- void mouseDragged() {
- if (mouseX < vpW && mouseY < vpH) {
- wvd.orgX -= (mouseX - pmouseX) / wvd.viewRatio;
- wvd.orgY -= (mouseY - pmouseY) / wvd.viewRatio;
- }
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- class City {
- float lat;
- float lon;
- City(float lat, float lon) {
- this.lat = lat;
- this.lon = lon;
- }
- void draw() {
- pushMatrix();
- translate(lon, lat);
- imageMode(CENTER);
- stroke(0);
- strokeWeight(0.1);
- if (mouseIsOver == this)
- fill(255, 128, 128);
- else
- fill(128, 255, 128);
- ellipse(0, 0, 1.0, 1.0);
- popMatrix();
- }
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- class WorldViewData {
- // Made public to speed up get access
- public float orgX = 0.0f;
- public float orgY = 0.0f;
- // viewRatio = number of pixels that represents a distance
- // of 1.0 in real world coordinates - bigger the value the
- // greater the magnification
- public float viewRatio = 1.0f;
- public WorldViewData() {
- orgX = 0.0f;
- orgY = 0.0f;
- viewRatio = 1.0f;
- }
- /**
- * Resize the world due to changes in magnification
- * so that the image is centred on the screen
- * @param f new viewRatio
- * @param pw width of view area in pixels
- * @param ph height of view area in pixels
- */
- public void resizeWorld(float zf, int pw, int ph) {
- float newX, newY;
- float w = pw;
- float h = ph;
- // Calculate new origin so as to centre the image
- newX = orgX + w/(2.0f*viewRatio) - w/(2.0f*zf);
- newY = orgY + h/(2.0f*viewRatio) - h/(2.0f*zf);
- orgX = newX;
- orgY = newY;
- viewRatio = zf;
- }
- // Calculate the world X position corresponding to
- // pixel position
- public float pixel2worldX(float px) {
- return orgX + px / viewRatio;
- }
- // Calculate the world Y position corresponding to
- // pixel position
- public float pixel2worldY(float py) {
- return orgY + py / viewRatio;
- }
- // Calculate the display X position corresponding to
- // world position
- public float world2pixelX(float wx) {
- return viewRatio / (wx - orgX);
- }
- // Calculate the display Y position corresponding to
- // world position
- public float world2pixelY(float wy) {
- return viewRatio / (wy - orgY);
- }
- /**
- * Set origin of top left to x, y
- * @param x
- * @param y
- * @return true if the origin has changed else return false
- */
- public boolean setXY(float x, float y) {
- if (orgX != x || orgY != y) {
- orgX = x;
- orgY = y;
- return true;
- }
- else
- return false;
- }
- }