org.immopoly.android.widget.ImmoscoutPlacesOverlay.java Source code

Java tutorial

Introduction

Here is the source code for org.immopoly.android.widget.ImmoscoutPlacesOverlay.java

Source

/*
 * This is the Android component of Immopoly
 * http://immopoly.appspot.com
 * Copyright (C) 2011 Tobias Sasse
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/.
 */

package org.immopoly.android.widget;

import java.util.ArrayList;

import org.immopoly.android.R;
import org.immopoly.android.fragments.MapFragment;
import org.immopoly.android.model.Flat;
import org.immopoly.android.model.Flats;

import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v4.app.Fragment;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.ViewGroup.LayoutParams;
import android.widget.RelativeLayout;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.ItemizedOverlay;
import com.google.android.maps.MapView;
import com.google.android.maps.OverlayItem;
import com.google.android.maps.Projection;

public class ImmoscoutPlacesOverlay extends ItemizedOverlay<OverlayItem> {

    private static final float MIN_INTERSECTION_AMOUNT = 0.15f;
    private static int MIN_INTERSECTION_AREA;

    private final ArrayList<ClusterItem> tmpItems = new ArrayList<ClusterItem>();
    private MapView mMapView;
    private Fragment mMapFragment;

    public static Rect markerBounds; // TODO public static
    private Flats mFlats;
    private int prevLatProjection = -1;
    private TeaserView bubble;
    private boolean isPortfolio;
    private GestureDetector gestureDetector;
    private long itemTapTime;

    static Drawable mapMarkerIcon;
    static Drawable mapMarkerIcon_old;
    static Drawable mapMarkerIcon_new;
    static Drawable mapMarkerIcon_owned;
    static Drawable mapMarkerIcon_cluster;

    public ImmoscoutPlacesOverlay(Fragment fragment, MapView mapView, LayoutInflater inflator,
            boolean isPortfolio) {
        super(boundCenter(fragment.getResources().getDrawable(R.drawable.map_marker_icon)));
        mMapView = mapView;
        this.isPortfolio = isPortfolio;
        mMapFragment = fragment;
        Resources resources = fragment.getResources();
        mapMarkerIcon = boundCenter(resources.getDrawable(R.drawable.map_marker_icon));
        mapMarkerIcon_new = boundCenter(resources.getDrawable(R.drawable.map_marker_icon_new));
        mapMarkerIcon_old = boundCenter(resources.getDrawable(R.drawable.map_marker_icon_old));
        mapMarkerIcon_owned = boundCenter(resources.getDrawable(R.drawable.map_marker_property_icon));
        mapMarkerIcon_cluster = boundCenter(resources.getDrawable(R.drawable.map_marker_icon_cluster));

        markerBounds = mapMarkerIcon.getBounds();

        MIN_INTERSECTION_AREA = (int) (markerBounds.width() * markerBounds.height() * MIN_INTERSECTION_AMOUNT);

        ClusterMarker.init(resources.getDisplayMetrics(), mapMarkerIcon.getIntrinsicHeight());
        gestureDetector = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
            public boolean onDoubleTap(MotionEvent e) {
                if (System.currentTimeMillis() - itemTapTime > 500) // hack to prevent zoom for double tap on item
                    mMapView.getController().zoomInFixing((int) e.getX(), (int) e.getY());
                return super.onDoubleTap(e);
            }
        });
    }

    public void setFlats(Flats mFlats) {
        this.mFlats = mFlats;
        clusterize();
    }

    public Rect getMarkerBounds() {
        return markerBounds;
    }

    @Override
    protected OverlayItem createItem(int i) {
        return tmpItems.get(i).getOverlayItem();
    }

    @Override
    public int size() {
        return tmpItems.size();
    }

    @Override
    protected boolean onTap(int index) {
        if (bubble != null)
            bubble.detach();
        if (tmpItems == null || index >= tmpItems.size()) {
            if (mMapFragment instanceof MapFragment) // show wind rose (hack)
                ((MapFragment) mMapFragment).showCompass();
            return false;
        }
        final ClusterItem item = tmpItems.get(index);
        final RelativeLayout.LayoutParams relLayoutParams = new RelativeLayout.LayoutParams(
                LayoutParams.FILL_PARENT, 210);
        relLayoutParams.addRule(RelativeLayout.BELOW, R.id.header);
        bubble = new TeaserView(mMapFragment, mMapView, item.flats, isPortfolio);
        if (mMapFragment instanceof MapFragment) // hide wind rose (hack)
            ((MapFragment) mMapFragment).hideCompass();
        itemTapTime = System.currentTimeMillis(); // hack to prevent zoom for double tap on item
        return true;
    }

    @Override
    public boolean onTap(GeoPoint p, MapView mapView) {
        if (!super.onTap(p, mapView)) {
            hideBubble();
            mMapView.setBuiltInZoomControls(true);
            mMapView.getZoomButtonsController().setVisible(true);
            return true;
        }
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event, MapView mapView) {
        return gestureDetector.onTouchEvent(event);
    }

    // TODO we're doing a lot of strange things here and draw() doesnt feel comfortable with that off-purpose stuff 
    public void draw(Canvas canvas, MapView mapView, boolean shadow) {
        // test for map movement. Remove Balloon evtly. 
        if (bubble != null && bubble.animationDone) {
            Point ntp = bubble.mapIconPos; // screen pos of selected icon as the bubble prefers 
            Point p = new Point(); // current screen pos of selected icon 
            mMapView.getProjection().toPixels(bubble.mapIconGeoPoint, p);
            if (Math.abs(ntp.x - p.x) > 20 || Math.abs(ntp.y - p.y) > 20) {
                hideBubble();
            }
        }
        // test for map zoom. Evtly do clusterizing 
        int latProjection = (mapView.getProjection().fromPixels(200, 200).getLatitudeE6()
                - mapView.getProjection().fromPixels(0, 0).getLatitudeE6());
        if (Math.abs(latProjection - prevLatProjection) > 10) {
            prevLatProjection = latProjection;
            clusterize();
        }
        super.draw(canvas, mapView, shadow);
    }

    /**
     * clusterize flats based on their supposed marker position
     */
    public void clusterize() {
        if (mFlats == null)
            return;
        final Flats flats = mFlats;
        final ArrayList<ClusterItem> items = tmpItems;
        final Projection projection = mMapView.getProjection();
        final Point screenPos = new Point();
        items.clear();

        // create tmp items with flat, geopoint & screenBounds
        for (int i = 0; i < flats.size(); i++) {
            final Flat flat = flats.get(i);
            if (flat.lat == 0 && flat.lng == 0 || !flat.visible)
                continue;
            Rect itemBounds = new Rect(markerBounds);
            GeoPoint point = new GeoPoint((int) (flat.lat * 1E6), (int) (flat.lng * 1E6));
            projection.toPixels(point, screenPos);
            itemBounds.offset(screenPos.x, screenPos.y);
            items.add(new ClusterItem(flat, point, itemBounds));
        }

        // join ClusterItems if their markers would intersect
        ArrayList<ClusterItem> newItems = new ArrayList<ClusterItem>();
        final Rect intersection = new Rect();
        final int size = items.size();
        for (int i = 0; i < size; i++) {
            final ClusterItem item = (ClusterItem) items.get(i);
            if (item.consumed)
                continue;
            for (int j = i + 1; j < size; j++) {
                final ClusterItem otherItem = (ClusterItem) items.get(j);
                if (!otherItem.consumed && intersection.setIntersect(item.screenBounds, otherItem.screenBounds)
                        && intersection.width() * intersection.height() >= MIN_INTERSECTION_AREA)
                    item.add(otherItem);
            }
            newItems.add(item);
        }
        tmpItems.clear();
        tmpItems.addAll(newItems);

        // see http://groups.google.com/group/android-developers/browse_thread/thread/38b11314e34714c3
        setLastFocusedIndex(-1);
        populate();
    }

    public void hideBubble() {
        if (bubble != null) {
            bubble.detach();
            bubble = null;
            if (mMapFragment instanceof MapFragment) // show wind rose (hack)
                ((MapFragment) mMapFragment).showCompass();
        }
    }

    public void updateBubble() {
        if (bubble != null)
            bubble.refreshContent();
    }

    /*
     * Class used while clustering.
     */
    private final class ClusterItem { // TODO join with ImmoPlaceOverlayItem or something
        public boolean consumed;
        long latSum, lonSum;
        Rect screenBounds;
        Flats flats = new Flats();
        GeoPoint point;
        OverlayItem overlayItem;

        public ClusterItem(Flat flat, GeoPoint point, Rect screenBounds) {
            this.screenBounds = screenBounds;
            latSum = point.getLatitudeE6();
            lonSum = point.getLongitudeE6();
            flats.add(flat);
        }

        public void add(ClusterItem item) {
            flats.addAll(item.flats);
            latSum += item.latSum;
            lonSum += item.lonSum;
            item.consumed = true;
        }

        OverlayItem getOverlayItem() {
            if (overlayItem != null)
                return overlayItem;

            int size = flats.size();

            //         point = new GeoPoint( (int) (latSum / size), (int) (lonSum / size) );
            point = new GeoPoint((int) (flats.get(0).lat * 1E6), (int) (flats.get(0).lng * 1E6));

            overlayItem = new ImmoPlaceOverlayItem(point, flats);
            if (size == 1) {
                Flat f = flats.get(0);
                if (f.owned)
                    overlayItem.setMarker(boundCenter(new SingleFlatMarker(mapMarkerIcon_owned)));
                else if (f.age == Flat.AGE_NEW)
                    overlayItem.setMarker(boundCenter(new SingleFlatMarker(mapMarkerIcon_new)));
                else if (f.age == Flat.AGE_OLD)
                    overlayItem.setMarker(boundCenter(new SingleFlatMarker(mapMarkerIcon_old)));
                else
                    overlayItem.setMarker(boundCenter(new SingleFlatMarker(mapMarkerIcon)));
            } else
                overlayItem.setMarker(boundCenter(new ClusterMarker(flats, mapMarkerIcon_cluster)));
            return overlayItem;
        }
    }

}