try {
                                int delay = 1000 + ((Double) (Math.random() * 1000)).intValue() - (int) (System.currentTimeMillis() - last);
                                if (delay < 0) {
                                    delay = 500;
                                }
                                sleep(delay);
                            } catch (Exception e) {
                                // nothing
                            }

package carnero.cgeo.mapcommon;

import gnu.android.app.appmanualclient.*;
import android.app.Activity;
import android.app.ProgressDialog;
import java.util.ArrayList;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import carnero.cgeo.R;
import carnero.cgeo.cgBase;
import carnero.cgeo.cgCache;
import carnero.cgeo.cgCoord;
import carnero.cgeo.cgDirection;
import carnero.cgeo.cgGeo;
import carnero.cgeo.cgSettings;
import carnero.cgeo.cgUpdateDir;
import carnero.cgeo.cgUpdateLoc;
import carnero.cgeo.cgUser;
import carnero.cgeo.cgWarning;
import carnero.cgeo.cgWaypoint;
import carnero.cgeo.cgeoapplication;
import carnero.cgeo.mapinterfaces.ActivityImpl;
import carnero.cgeo.mapinterfaces.CacheOverlayItemImpl;
import carnero.cgeo.mapinterfaces.GeoPointImpl;
import carnero.cgeo.mapinterfaces.MapControllerImpl;
import carnero.cgeo.mapinterfaces.MapFactory;
import carnero.cgeo.mapinterfaces.MapViewImpl;
import carnero.cgeo.mapinterfaces.UserOverlayItemImpl;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import carnero.cgeo.cgSearch;
import java.util.HashMap;
import java.util.Locale;

public class cgeomap extends MapBase {

    private Resources res = null;

    private Activity activity = null;

    private MapViewImpl mapView = null;

    private MapControllerImpl mapController = null;

    private cgSettings settings = null;

    private cgBase base = null;

    private cgWarning warning = null;

    private cgeoapplication app = null;

    private SharedPreferences.Editor prefsEdit = null;

    private cgGeo geo = null;

    private cgDirection dir = null;

    private cgUpdateLoc geoUpdate = new UpdateLoc();

    private cgUpdateDir dirUpdate = new UpdateDir();

    // from intent
    private boolean fromDetailIntent = false;

    private Long searchIdIntent = null;

    private String geocodeIntent = null;

    private Double latitudeIntent = null;

    private Double longitudeIntent = null;

    private String waypointTypeIntent = null;

    // status data
    private Long searchId = null;

    private String token = null;

    private boolean noMapTokenShowed = false;

    // map status data
    private boolean followMyLocation = false;

    private Integer centerLatitude = null;

    private Integer centerLongitude = null;

    private Integer spanLatitude = null;

    private Integer spanLongitude = null;

    private Integer centerLatitudeUsers = null;

    private Integer centerLongitudeUsers = null;

    private Integer spanLatitudeUsers = null;

    private Integer spanLongitudeUsers = null;

    // thread
    private LoadTimer loadTimer = null;

    private UsersTimer usersTimer = null;

    private LoadThread loadThread = null;

    private DownloadThread downloadThread = null;

    private DisplayThread displayThread = null;

    private UsersThread usersThread = null;

    private DisplayUsersThread displayUsersThread = null;

    private LoadDetails loadDetailsThread = null;

    private volatile long loadThreadRun = 0l;

    private volatile long downloadThreadRun = 0l;

    private volatile long usersThreadRun = 0l;

    private volatile boolean downloaded = false;

    // overlays
    private cgMapOverlay overlayCaches = null;

    private cgUsersOverlay overlayUsers = null;

    private cgOverlayScale overlayScale = null;

    private cgMapMyOverlay overlayMyLoc = null;

    // data for overlays
    private int cachesCnt = 0;

    private HashMap<Integer, Drawable> iconsCache = new HashMap<Integer, Drawable>();

    private ArrayList<cgCache> caches = new ArrayList<cgCache>();

    private ArrayList<cgUser> users = new ArrayList<cgUser>();

    private ArrayList<cgCoord> coordinates = new ArrayList<cgCoord>();

    // storing for offline
    private ProgressDialog waitDialog = null;

    private int detailTotal = 0;

    private int detailProgress = 0;

    private Long detailProgressTime = 0l;

    // views
    private ImageView myLocSwitch = null;

    // other things
    // live map (live, dead) or rest (displaying caches on map)
    private boolean live = true;

    // previous state for loadTimer
    private boolean liveChanged = false;

    // if map is already centered
    private boolean centered = false;

    // -""- for setting my location
    private boolean alreadyCentered = false;

    // handlers
    final private Handler displayHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            final int what = msg.what;
            if (what == 0) {
                // set title
                final StringBuilder title = new StringBuilder();
                if (live == true) {
                    title.append(res.getString(R.string.map_live));
                } else {
                    title.append(res.getString(R.string.map_map));
                }
                if (caches != null && cachesCnt > 0) {
                    title.append(" ");
                    title.append("[");
                    title.append(caches.size());
                    title.append("]");
                }
                base.setTitle(activity, title.toString());
            } else if (what == 1 && mapView != null) {
                mapView.invalidate();
            }
        }
    };

    final private Handler showProgressHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            final int what = msg.what;
            if (what == 0) {
                base.showProgress(activity, false);
            } else if (what == 1) {
                base.showProgress(activity, true);
            }
        }
    };

    final private Handler loadDetailsHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 0) {
                if (waitDialog != null) {
                    // seconds left
                    Float diffTime = new Float((System.currentTimeMillis() - detailProgressTime) / 1000);
                    // left time per cache
                    Float oneCache = diffTime / detailProgress;
                    // seconds remaining
                    Float etaTime = (detailTotal - detailProgress) * oneCache;
                    waitDialog.setProgress(detailProgress);
                    if (etaTime < 40) {
                        waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + res.getString(R.string.caches_eta_ltm));
                    } else if (etaTime < 90) {
                        waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + String.format(Locale.getDefault(), "%.0f", (etaTime / 60)) + " " + res.getString(R.string.caches_eta_min));
                    } else {
                        waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + String.format(Locale.getDefault(), "%.0f", (etaTime / 60)) + " " + res.getString(R.string.caches_eta_mins));
                    }
                }
            } else {
                if (waitDialog != null) {
                    waitDialog.dismiss();
                    waitDialog.setOnCancelListener(null);
                }
                if (geo == null) {
                    geo = app.startGeo(activity, geoUpdate, base, settings, warning, 0, 0);
                }
                if (settings.useCompass == 1 && dir == null) {
                    dir = app.startDir(activity, dirUpdate, warning);
                }
            }
        }
    };

    final private Handler noMapTokenHandler = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            if (!noMapTokenShowed) {
                warning.showToast(res.getString(R.string.map_token_err));
                noMapTokenShowed = true;
            }
        }
    };

    public cgeomap(ActivityImpl activity) {
        super(activity);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        res = this.getResources();
        activity = this.getActivity();
        app = (cgeoapplication) activity.getApplication();
        app.setAction(null);
        settings = new cgSettings(activity, activity.getSharedPreferences(cgSettings.preferences, 0));
        base = new cgBase(app, settings, activity.getSharedPreferences(cgSettings.preferences, 0));
        warning = new cgWarning(activity);
        prefsEdit = activity.getSharedPreferences(cgSettings.preferences, 0).edit();
        MapFactory mapFactory = settings.getMapFactory();
        // reset status
        noMapTokenShowed = false;
        // set layout
        activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        // set layout
        if (settings.skin == 1) {
            activity.setTheme(R.style.light);
        } else {
            activity.setTheme(R.style.dark);
        }
        activity.setContentView(settings.getMapFactory().getMapLayoutId());
        base.setTitle(activity, res.getString(R.string.map_map));
        if (geo == null) {
            geo = app.startGeo(activity, geoUpdate, base, settings, warning, 0, 0);
        }
        if (settings.useCompass == 1 && dir == null) {
            dir = app.startDir(activity, dirUpdate, warning);
        }
        mapView = (MapViewImpl) activity.findViewById(mapFactory.getMapViewId());
        mapView.setMapSource(settings);
        if (!mapView.needsScaleOverlay()) {
            mapView.setBuiltinScale(true);
        }
        // initialize map
        if (settings.maptype == cgSettings.mapSatellite) {
            mapView.setSatellite(true);
        } else {
            mapView.setSatellite(false);
        }
        mapView.setBuiltInZoomControls(true);
        mapView.displayZoomControls(true);
        mapView.preLoad();
        // initialize overlays
        mapView.clearOverlays();
        if (overlayMyLoc == null) {
            overlayMyLoc = new cgMapMyOverlay(settings);
            mapView.addOverlay(mapFactory.getOverlayBaseWrapper(overlayMyLoc));
        }
        if (settings.publicLoc > 0 && overlayUsers == null) {
            overlayUsers = mapView.createAddUsersOverlay(activity, getResources().getDrawable(R.drawable.user_location));
        }
        if (overlayCaches == null) {
            overlayCaches = mapView.createAddMapOverlay(settings, mapView.getContext(), getResources().getDrawable(R.drawable.marker), fromDetailIntent);
        }
        if (overlayScale == null && mapView.needsScaleOverlay()) {
            overlayScale = new cgOverlayScale(activity, settings);
            mapView.addOverlay(mapFactory.getOverlayBaseWrapper(overlayScale));
        }
        mapView.invalidate();
        mapController = mapView.getMapController();
        mapController.setZoom(settings.mapzoom);
        // start location and directory services
        if (geo != null) {
            geoUpdate.updateLoc(geo);
        }
        if (dir != null) {
            dirUpdate.updateDir(dir);
        }
        // get parameters
        Bundle extras = activity.getIntent().getExtras();
        if (extras != null) {
            fromDetailIntent = extras.getBoolean("detail");
            searchIdIntent = extras.getLong("searchid");
            geocodeIntent = extras.getString("geocode");
            latitudeIntent = extras.getDouble("latitude");
            longitudeIntent = extras.getDouble("longitude");
            waypointTypeIntent = extras.getString("wpttype");
            if (searchIdIntent == 0l) {
                searchIdIntent = null;
            }
            if (latitudeIntent == 0.0) {
                latitudeIntent = null;
            }
            if (longitudeIntent == 0.0) {
                longitudeIntent = null;
            }
        }
        // live or death
        if (searchIdIntent == null && geocodeIntent == null && (latitudeIntent == null || longitudeIntent == null)) {
            live = true;
        } else {
            live = false;
        }
        // google analytics
        if (live) {
            base.sendAnal(activity, "/map/live");
            followMyLocation = true;
        } else {
            base.sendAnal(activity, "/map/normal");
            followMyLocation = false;
            if (geocodeIntent != null || searchIdIntent != null || (latitudeIntent != null && longitudeIntent != null)) {
                centerMap(geocodeIntent, searchIdIntent, latitudeIntent, longitudeIntent);
            }
        }
        setMyLoc(null);
        startTimer();
    }

    @Override
    public void onResume() {
        super.onResume();
        settings.load();
        app.setAction(null);
        if (geo == null) {
            geo = app.startGeo(activity, geoUpdate, base, settings, warning, 0, 0);
        }
        if (settings.useCompass == 1 && dir == null) {
            dir = app.startDir(activity, dirUpdate, warning);
        }
        if (geo != null) {
            geoUpdate.updateLoc(geo);
        }
        if (dir != null) {
            dirUpdate.updateDir(dir);
        }
        startTimer();
    }

    @Override
    public void onStop() {
        if (loadTimer != null) {
            loadTimer.stopIt();
            loadTimer = null;
        }
        if (usersTimer != null) {
            usersTimer.stopIt();
            usersTimer = null;
        }
        if (dir != null) {
            dir = app.removeDir();
        }
        if (geo != null) {
            geo = app.removeGeo();
        }
        savePrefs();
        if (mapView != null) {
            mapView.destroyDrawingCache();
        }
        super.onStop();
    }

    @Override
    public void onPause() {
        if (loadTimer != null) {
            loadTimer.stopIt();
            loadTimer = null;
        }
        if (usersTimer != null) {
            usersTimer.stopIt();
            usersTimer = null;
        }
        if (dir != null) {
            dir = app.removeDir();
        }
        if (geo != null) {
            geo = app.removeGeo();
        }
        savePrefs();
        if (mapView != null) {
            mapView.destroyDrawingCache();
        }
        super.onPause();
    }

    @Override
    public void onDestroy() {
        if (loadTimer != null) {
            loadTimer.stopIt();
            loadTimer = null;
        }
        if (usersTimer != null) {
            usersTimer.stopIt();
            usersTimer = null;
        }
        if (dir != null) {
            dir = app.removeDir();
        }
        if (geo != null) {
            geo = app.removeGeo();
        }
        savePrefs();
        if (mapView != null) {
            mapView.destroyDrawingCache();
        }
        super.onDestroy();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        menu.add(0, 1, 0, res.getString(R.string.caches_on_map)).setIcon(android.R.drawable.ic_menu_mapmode);
        menu.add(0, 3, 0, res.getString(R.string.map_live_disable)).setIcon(android.R.drawable.ic_menu_close_clear_cancel);
        menu.add(0, 4, 0, res.getString(R.string.caches_store_offline)).setIcon(android.R.drawable.ic_menu_set_as).setEnabled(false);
        menu.add(0, 2, 0, res.getString(R.string.map_trail_hide)).setIcon(android.R.drawable.ic_menu_recent_history);
        menu.add(0, 5, 0, res.getString(R.string.map_circles_hide)).setIcon(android.R.drawable.ic_menu_view);
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);
        MenuItem item;
        try {
            // view
            item = menu.findItem(1);
            if (mapView != null && mapView.isSatellite() == false) {
                item.setTitle(res.getString(R.string.map_view_satellite));
            } else {
                item.setTitle(res.getString(R.string.map_view_map));
            }
            // show trail
            item = menu.findItem(2);
            if (settings.maptrail == 1) {
                item.setTitle(res.getString(R.string.map_trail_hide));
            } else {
                item.setTitle(res.getString(R.string.map_trail_show));
            }
            // live map
            item = menu.findItem(3);
            if (live == false) {
                item.setEnabled(false);
                item.setTitle(res.getString(R.string.map_live_enable));
            } else {
                if (settings.maplive == 1) {
                    item.setTitle(res.getString(R.string.map_live_disable));
                } else {
                    item.setTitle(res.getString(R.string.map_live_enable));
                }
            }
            // store loaded
            item = menu.findItem(4);
            if (live && !isLoading() && app.getNotOfflineCount(searchId) > 0 && caches != null && caches.size() > 0) {
                item.setEnabled(true);
            } else {
                item.setEnabled(false);
            }
            // show circles
            item = menu.findItem(5);
            if (overlayCaches != null && overlayCaches.getCircles()) {
                item.setTitle(res.getString(R.string.map_circles_hide));
            } else {
                item.setTitle(res.getString(R.string.map_circles_show));
            }
        } catch (Exception e) {
            Log.e(cgSettings.tag, "cgeomap.onPrepareOptionsMenu: " + e.toString());
        }
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        final int id = item.getItemId();
        if (id == 1) {
            if (mapView != null && mapView.isSatellite() == false) {
                mapView.setSatellite(true);
                prefsEdit.putInt("maptype", cgSettings.mapSatellite);
                prefsEdit.commit();
            } else {
                mapView.setSatellite(false);
                prefsEdit.putInt("maptype", cgSettings.mapClassic);
                prefsEdit.commit();
            }
            return true;
        } else if (id == 2) {
            if (settings.maptrail == 1) {
                prefsEdit.putInt("maptrail", 0);
                prefsEdit.commit();
                settings.maptrail = 0;
            } else {
                prefsEdit.putInt("maptrail", 1);
                prefsEdit.commit();
                settings.maptrail = 1;
            }
        } else if (id == 3) {
            if (settings.maplive == 1) {
                settings.liveMapDisable();
            } else {
                settings.liveMapEnable();
            }
            liveChanged = true;
            searchId = null;
            searchIdIntent = null;
        } else if (id == 4) {
            if (live && !isLoading() && caches != null && !caches.isEmpty()) {
                final ArrayList<String> geocodes = new ArrayList<String>();
                ArrayList<cgCache> cachesProtected = (ArrayList<cgCache>) caches.clone();
                try {
                    if (cachesProtected != null && cachesProtected.size() > 0) {
                        final GeoPointImpl mapCenter = mapView.getMapViewCenter();
                        final int mapCenterLat = mapCenter.getLatitudeE6();
                        final int mapCenterLon = mapCenter.getLongitudeE6();
                        final int mapSpanLat = mapView.getLatitudeSpan();
                        final int mapSpanLon = mapView.getLongitudeSpan();
                        for (cgCache oneCache : cachesProtected) {
                            if (oneCache != null && oneCache.latitude != null && oneCache.longitude != null) {
                                if (base.isCacheInViewPort(mapCenterLat, mapCenterLon, mapSpanLat, mapSpanLon, oneCache.latitude, oneCache.longitude) && app.isOffline(oneCache.geocode, null) == false) {
                                    geocodes.add(oneCache.geocode);
                                }
                            }
                        }
                    }
                } catch (Exception e) {
                    Log.e(cgSettings.tag, "cgeomap.onOptionsItemSelected.#4: " + e.toString());
                }
                detailTotal = geocodes.size();
                if (detailTotal == 0) {
                    warning.showToast(res.getString(R.string.warn_save_nothing));
                    return true;
                }
                waitDialog = new ProgressDialog(activity);
                waitDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
                waitDialog.setCancelable(true);
                waitDialog.setMax(detailTotal);
                waitDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {

                    public void onCancel(DialogInterface arg0) {
                        try {
                            if (loadDetailsThread != null) {
                                loadDetailsThread.stopIt();
                            }
                            if (geo == null) {
                                geo = app.startGeo(activity, geoUpdate, base, settings, warning, 0, 0);
                            }
                            if (settings.useCompass == 1 && dir == null) {
                                dir = app.startDir(activity, dirUpdate, warning);
                            }
                        } catch (Exception e) {
                            Log.e(cgSettings.tag, "cgeocaches.onPrepareOptionsMenu.onCancel: " + e.toString());
                        }
                    }
                });
                Float etaTime = new Float((detailTotal * (float) 7) / 60);
                if (etaTime < 0.4) {
                    waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + res.getString(R.string.caches_eta_ltm));
                } else if (etaTime < 1.5) {
                    waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + String.format(Locale.getDefault(), "%.0f", etaTime) + " " + res.getString(R.string.caches_eta_min));
                } else {
                    waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + String.format(Locale.getDefault(), "%.0f", etaTime) + " " + res.getString(R.string.caches_eta_mins));
                }
                waitDialog.show();
                detailProgressTime = System.currentTimeMillis();
                loadDetailsThread = new LoadDetails(loadDetailsHandler, geocodes);
                loadDetailsThread.start();
                return true;
            }
        } else if (id == 5) {
            if (overlayCaches == null) {
                return false;
            }
            overlayCaches.switchCircles();
        }
        return false;
    }

    private void savePrefs() {
        if (mapView == null) {
            return;
        }
        if (mapView.isSatellite()) {
            prefsEdit.putInt("maptype", cgSettings.mapSatellite);
            settings.maptype = cgSettings.mapSatellite;
        } else {
            prefsEdit.putInt("maptype", cgSettings.mapClassic);
            settings.maptype = cgSettings.mapClassic;
        }
        if (prefsEdit == null) {
            prefsEdit = activity.getSharedPreferences(cgSettings.preferences, 0).edit();
        }
        prefsEdit.putInt("mapzoom", mapView.getMapZoomLevel());
        prefsEdit.commit();
    }

    // set center of map to my location
    private void myLocationInMiddle() {
        if (geo == null) {
            return;
        }
        if (!followMyLocation) {
            return;
        }
        centerMap(geo.latitudeNow, geo.longitudeNow);
    }

    private class UpdateLoc extends cgUpdateLoc {

        @Override
        public void updateLoc(cgGeo geo) {
            if (geo == null) {
                return;
            }
            try {
                if (overlayMyLoc == null && mapView != null) {
                    overlayMyLoc = new cgMapMyOverlay(settings);
                    mapView.addOverlay(settings.getMapFactory().getOverlayBaseWrapper(overlayMyLoc));
                }
                if (overlayMyLoc != null && geo.location != null) {
                    overlayMyLoc.setCoordinates(geo.location);
                }
                if (geo.latitudeNow != null && geo.longitudeNow != null) {
                    if (followMyLocation == true) {
                        myLocationInMiddle();
                    }
                }
                if (settings.useCompass == 0 || (geo.speedNow != null && geo.speedNow > 5)) {
                    // use GPS when speed is higher than 18 km/h
                    if (geo.bearingNow != null) {
                        overlayMyLoc.setHeading(geo.bearingNow);
                    } else {
                        overlayMyLoc.setHeading(new Double(0));
                    }
                }
            } catch (Exception e) {
                Log.w(cgSettings.tag, "Failed to update location.");
            }
        }
    }

    private class UpdateDir extends cgUpdateDir {

        @Override
        public void updateDir(cgDirection dir) {
            if (dir == null || dir.directionNow == null) {
                return;
            }
            if (overlayMyLoc != null && mapView != null && (geo == null || geo.speedNow == null || geo.speedNow <= 5)) {
                // use compass when speed is lower than 18 km/h
                overlayMyLoc.setHeading(dir.directionNow);
                mapView.invalidate();
            }
        }
    }

    public void startTimer() {
        if (latitudeIntent != null && longitudeIntent != null) {
            // display just one point
            (new DisplayPointThread()).start();
        } else {
            // start timer
            if (loadTimer != null) {
                loadTimer.stopIt();
                loadTimer = null;
            }
            loadTimer = new LoadTimer();
            loadTimer.start();
        }
        if (settings.publicLoc > 0) {
            if (usersTimer != null) {
                usersTimer.stopIt();
                usersTimer = null;
            }
            usersTimer = new UsersTimer();
            usersTimer.start();
        }
    }

    // loading timer
    private class LoadTimer extends Thread {

        private volatile boolean stop = false;

        public void stopIt() {
            stop = true;
            if (loadThread != null) {
                loadThread.stopIt();
                loadThread = null;
            }
            if (downloadThread != null) {
                downloadThread.stopIt();
                downloadThread = null;
            }
            if (displayThread != null) {
                displayThread.stopIt();
                displayThread = null;
            }
        }

        @Override
        public void run() {
            GeoPointImpl mapCenterNow;
            int centerLatitudeNow;
            int centerLongitudeNow;
            int spanLatitudeNow;
            int spanLongitudeNow;
            boolean moved = false;
            boolean force = false;
            long currentTime = 0;
            while (!stop) {
                try {
                    sleep(250);
                    if (mapView != null) {
                        // get current viewport
                        mapCenterNow = mapView.getMapViewCenter();
                        centerLatitudeNow = mapCenterNow.getLatitudeE6();
                        centerLongitudeNow = mapCenterNow.getLongitudeE6();
                        spanLatitudeNow = mapView.getLatitudeSpan();
                        spanLongitudeNow = mapView.getLongitudeSpan();
                        // check if map moved or zoomed
                        moved = false;
                        force = false;
                        if (liveChanged) {
                            moved = true;
                            force = true;
                        } else if (live && settings.maplive == 1 && downloaded == false) {
                            moved = true;
                        } else if (centerLatitude == null || centerLongitude == null) {
                            moved = true;
                        } else if (spanLatitude == null || spanLongitude == null) {
                            moved = true;
                        } else if ((// changed zoom
                        (Math.abs(spanLatitudeNow - spanLatitude) > 50) || (Math.abs(spanLongitudeNow - spanLongitude) > 50) || (Math.abs(centerLatitudeNow - centerLatitude) > (spanLatitudeNow / 4)) || // map moved
                        (Math.abs(centerLongitudeNow - centerLongitude) > (spanLongitudeNow / 4))) && (cachesCnt <= 0 || caches == null || caches.isEmpty() || !base.isInViewPort(centerLatitude, centerLongitude, centerLatitudeNow, centerLongitudeNow, spanLatitude, spanLongitude, spanLatitudeNow, spanLongitudeNow))) {
                            moved = true;
                        }
                        if (moved && caches != null && centerLatitude != null && centerLongitude != null && ((Math.abs(centerLatitudeNow - centerLatitude) > (spanLatitudeNow * 1.2)) || (Math.abs(centerLongitudeNow - centerLongitude) > (spanLongitudeNow * 1.2)))) {
                            force = true;
                        }
                        // LeeB
                        // save new values
                        if (moved) {
                            liveChanged = false;
                            currentTime = System.currentTimeMillis();
                            if (1000 < (currentTime - loadThreadRun)) {
                                // from web
                                if (20000 < (currentTime - loadThreadRun)) {
                                    // probably stucked thread
                                    force = true;
                                }
                                if (force && loadThread != null && loadThread.isWorking()) {
                                    loadThread.stopIt();
                                    try {
                                        sleep(100);
                                    } catch (Exception e) {
                                        // nothing
                                    }
                                }
                                if (loadThread != null && loadThread.isWorking()) {
                                    continue;
                                }
                                centerLatitude = centerLatitudeNow;
                                centerLongitude = centerLongitudeNow;
                                spanLatitude = spanLatitudeNow;
                                spanLongitude = spanLongitudeNow;
                                // show progress
                                showProgressHandler.sendEmptyMessage(1);
                                loadThread = new LoadThread(centerLatitude, centerLongitude, spanLatitude, spanLongitude);
                                loadThread.setName("loadThread");
                                // loadThread will kick off downloadThread once it's done
                                loadThread.start();
                            }
                        }
                    }
                    if (!isLoading()) {
                        // hide progress
                        showProgressHandler.sendEmptyMessage(0);
                    }
                    yield();
                } catch (Exception e) {
                    Log.w(cgSettings.tag, "cgeomap.LoadTimer.run: " + e.toString());
                }
            }
            ;
        }
    }

    // loading timer
    private class UsersTimer extends Thread {

        private volatile boolean stop = false;

        public void stopIt() {
            stop = true;
            if (usersThread != null) {
                usersThread.stopIt();
                usersThread = null;
            }
            if (displayUsersThread != null) {
                displayUsersThread.stopIt();
                displayUsersThread = null;
            }
        }

        @Override
        public void run() {
            GeoPointImpl mapCenterNow;
            int centerLatitudeNow;
            int centerLongitudeNow;
            int spanLatitudeNow;
            int spanLongitudeNow;
            boolean moved = false;
            long currentTime = 0;
            while (!stop) {
                try {
                    sleep(250);
                    if (mapView != null) {
                        // get current viewport
                        mapCenterNow = mapView.getMapViewCenter();
                        centerLatitudeNow = mapCenterNow.getLatitudeE6();
                        centerLongitudeNow = mapCenterNow.getLongitudeE6();
                        spanLatitudeNow = mapView.getLatitudeSpan();
                        spanLongitudeNow = mapView.getLongitudeSpan();
                        // check if map moved or zoomed
                        moved = false;
                        currentTime = System.currentTimeMillis();
                        if (60000 < (currentTime - usersThreadRun)) {
                            moved = true;
                        } else if (centerLatitudeUsers == null || centerLongitudeUsers == null) {
                            moved = true;
                        } else if (spanLatitudeUsers == null || spanLongitudeUsers == null) {
                            moved = true;
                        } else if ((// changed zoom
                        (Math.abs(spanLatitudeNow - spanLatitudeUsers) > 50) || (Math.abs(spanLongitudeNow - spanLongitudeUsers) > 50) || (Math.abs(centerLatitudeNow - centerLatitudeUsers) > (spanLatitudeNow / 4)) || // map moved
                        (Math.abs(centerLongitudeNow - centerLongitudeUsers) > (spanLongitudeNow / 4))) && !base.isInViewPort(centerLatitudeUsers, centerLongitudeUsers, centerLatitudeNow, centerLongitudeNow, spanLatitudeUsers, spanLongitudeUsers, spanLatitudeNow, spanLongitudeNow)) {
                            moved = true;
                        }
                        // save new values
                        if (moved && (1000 < (currentTime - usersThreadRun))) {
                            if (usersThread != null && usersThread.isWorking()) {
                                continue;
                            }
                            centerLatitudeUsers = centerLatitudeNow;
                            centerLongitudeUsers = centerLongitudeNow;
                            spanLatitudeUsers = spanLatitudeNow;
                            spanLongitudeUsers = spanLongitudeNow;
                            usersThread = new UsersThread(centerLatitude, centerLongitude, spanLatitude, spanLongitude);
                            usersThread.start();
                        }
                    }
                    yield();
                } catch (Exception e) {
                    Log.w(cgSettings.tag, "cgeomap.LoadUsersTimer.run: " + e.toString());
                }
            }
            ;
        }
    }

    // load caches from database
    private class LoadThread extends DoThread {

        public LoadThread(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) {
            super(centerLatIn, centerLonIn, spanLatIn, spanLonIn);
        }

        @Override
        public void run() {
            try {
                stop = false;
                working = true;
                loadThreadRun = System.currentTimeMillis();
                if (stop) {
                    displayHandler.sendEmptyMessage(0);
                    working = false;
                    return;
                }
                // LeeB - I think this can be done better:
                // 1. fetch and draw(in another thread) caches from the db (fast? db read will be the slow bit)
                // 2. fetch and draw(in another thread) and then insert into the db caches from geocaching.com - dont draw/insert if exist in memory?
                // stage 1 - pull and render from the DB only
                if (settings.maplive == 0) {
                    searchId = app.getStoredInViewport(centerLat, centerLon, spanLat, spanLon, settings.cacheType);
                } else {
                    searchId = app.getCachedInViewport(centerLat, centerLon, spanLat, spanLon, settings.cacheType);
                }
                if (searchId != null) {
                    downloaded = true;
                }
                if (stop) {
                    displayHandler.sendEmptyMessage(0);
                    working = false;
                    return;
                }
                caches = app.getCaches(searchId);
                if (stop) {
                    displayHandler.sendEmptyMessage(0);
                    working = false;
                    return;
                }
                // render
                if (displayThread != null && displayThread.isWorking()) {
                    displayThread.stopIt();
                }
                displayThread = new DisplayThread(centerLat, centerLon, spanLat, spanLon);
                displayThread.start();
                if (stop) {
                    displayThread.stopIt();
                    displayHandler.sendEmptyMessage(0);
                    working = false;
                    return;
                }
                // *** this needs to be in it's own thread
                // stage 2 - pull and render from geocaching.com
                // this should just fetch and insert into the db _and_ be cancel-able if the viewport changes
                if (settings.maplive >= 1) {
                    if (downloadThread != null && downloadThread.isWorking()) {
                        downloadThread.stopIt();
                    }
                    downloadThread = new DownloadThread(centerLat, centerLon, spanLat, spanLon);
                    downloadThread.setName("downloadThread");
                    downloadThread.start();
                }
            } finally {
                working = false;
            }
        }
    }

    // load caches from internet
    private class DownloadThread extends DoThread {

        public DownloadThread(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) {
            super(centerLatIn, centerLonIn, spanLatIn, spanLonIn);
        }

        @Override
        public void run() {
            // first time we enter we have crappy long/lat....
            try {
                stop = false;
                working = true;
                downloadThreadRun = System.currentTimeMillis();
                if (stop) {
                    displayHandler.sendEmptyMessage(0);
                    working = false;
                    return;
                }
                double latMin = (centerLat / 1e6) - ((spanLat / 1e6) / 2) - ((spanLat / 1e6) / 4);
                double latMax = (centerLat / 1e6) + ((spanLat / 1e6) / 2) + ((spanLat / 1e6) / 4);
                double lonMin = (centerLon / 1e6) - ((spanLon / 1e6) / 2) - ((spanLon / 1e6) / 4);
                double lonMax = (centerLon / 1e6) + ((spanLon / 1e6) / 2) + ((spanLon / 1e6) / 4);
                double llCache;
                if (latMin > latMax) {
                    llCache = latMax;
                    latMax = latMin;
                    latMin = llCache;
                }
                if (lonMin > lonMax) {
                    llCache = lonMax;
                    lonMax = lonMin;
                    lonMin = llCache;
                }
                // *** this needs to be in it's own thread
                // stage 2 - pull and render from geocaching.com
                // this should just fetch and insert into the db _and_ be cancel-able if the viewport changes
                if (token == null) {
                    token = base.getMapUserToken(noMapTokenHandler);
                }
                if (stop) {
                    displayHandler.sendEmptyMessage(0);
                    working = false;
                    return;
                }
                HashMap<String, String> params = new HashMap<String, String>();
                params.put("usertoken", token);
                params.put("latitude-min", String.format((Locale) null, "%.6f", latMin));
                params.put("latitude-max", String.format((Locale) null, "%.6f", latMax));
                params.put("longitude-min", String.format((Locale) null, "%.6f", lonMin));
                params.put("longitude-max", String.format((Locale) null, "%.6f", lonMax));
                searchId = base.searchByViewport(params, 0);
                if (searchId != null) {
                    downloaded = true;
                }
                if (stop) {
                    displayHandler.sendEmptyMessage(0);
                    working = false;
                    return;
                }
                caches = app.getCaches(searchId, centerLat, centerLon, spanLat, spanLon);
                if (stop) {
                    displayHandler.sendEmptyMessage(0);
                    working = false;
                    return;
                }
                // render
                if (displayThread != null && displayThread.isWorking()) {
                    displayThread.stopIt();
                }
                displayThread = new DisplayThread(centerLat, centerLon, spanLat, spanLon);
                displayThread.start();
            } finally {
                working = false;
            }
        }
    }

    // display (down)loaded caches
    private class DisplayThread extends DoThread {

        public DisplayThread(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) {
            super(centerLatIn, centerLonIn, spanLatIn, spanLonIn);
        }

        @Override
        public void run() {
            try {
                stop = false;
                working = true;
                if (mapView == null || caches == null) {
                    displayHandler.sendEmptyMessage(0);
                    working = false;
                    return;
                }
                // display caches
                final ArrayList<cgCache> cachesProtected = (ArrayList<cgCache>) caches.clone();
                final ArrayList<CacheOverlayItemImpl> items = new ArrayList<CacheOverlayItemImpl>();
                if (cachesProtected != null && !cachesProtected.isEmpty()) {
                    int counter = 0;
                    int icon = 0;
                    Drawable pin = null;
                    CacheOverlayItemImpl item = null;
                    for (cgCache cacheOne : cachesProtected) {
                        if (stop) {
                            displayHandler.sendEmptyMessage(0);
                            working = false;
                            return;
                        }
                        if (cacheOne.latitude == null && cacheOne.longitude == null) {
                            continue;
                        }
                        final cgCoord coord = new cgCoord(cacheOne);
                        coordinates.add(coord);
                        item = settings.getMapFactory().getCacheOverlayItem(coord, cacheOne.type);
                        icon = base.getIcon(true, cacheOne.type, cacheOne.own, cacheOne.found, cacheOne.disabled || cacheOne.archived);
                        pin = null;
                        if (iconsCache.containsKey(icon)) {
                            pin = iconsCache.get(icon);
                        } else {
                            pin = getResources().getDrawable(icon);
                            pin.setBounds(0, 0, pin.getIntrinsicWidth(), pin.getIntrinsicHeight());
                            iconsCache.put(icon, pin);
                        }
                        item.setMarker(pin);
                        items.add(item);
                    }
                    overlayCaches.updateItems(items);
                    displayHandler.sendEmptyMessage(1);
                    cachesCnt = cachesProtected.size();
                    if (stop) {
                        displayHandler.sendEmptyMessage(0);
                        working = false;
                        return;
                    }
                    // display cache waypoints
                    if (cachesCnt == 1 && (geocodeIntent != null || searchIdIntent != null) && !live) {
                        if (cachesCnt == 1 && live == false) {
                            cgCache oneCache = cachesProtected.get(0);
                            if (oneCache != null && oneCache.waypoints != null && !oneCache.waypoints.isEmpty()) {
                                for (cgWaypoint oneWaypoint : oneCache.waypoints) {
                                    if (oneWaypoint.latitude == null && oneWaypoint.longitude == null) {
                                        continue;
                                    }
                                    cgCoord coord = new cgCoord(oneWaypoint);
                                    coordinates.add(coord);
                                    item = settings.getMapFactory().getCacheOverlayItem(coord, null);
                                    icon = base.getIcon(false, oneWaypoint.type, false, false, false);
                                    if (iconsCache.containsKey(icon)) {
                                        pin = iconsCache.get(icon);
                                    } else {
                                        pin = getResources().getDrawable(icon);
                                        pin.setBounds(0, 0, pin.getIntrinsicWidth(), pin.getIntrinsicHeight());
                                        iconsCache.put(icon, pin);
                                    }
                                    item.setMarker(pin);
                                    items.add(item);
                                }
                                overlayCaches.updateItems(items);
                                displayHandler.sendEmptyMessage(1);
                            }
                        }
                    }
                } else {
                    overlayCaches.updateItems(items);
                    displayHandler.sendEmptyMessage(1);
                }
                cachesProtected.clear();
                displayHandler.sendEmptyMessage(0);
            } finally {
                working = false;
            }
        }
    }

    // load users from Go 4 Cache
    private class UsersThread extends DoThread {

        public UsersThread(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) {
            super(centerLatIn, centerLonIn, spanLatIn, spanLonIn);
        }

        @Override
        public void run() {
            try {
                stop = false;
                working = true;
                usersThreadRun = System.currentTimeMillis();
                if (stop) {
                    return;
                }
                double latMin = (centerLat / 1e6) - ((spanLat / 1e6) / 2) - ((spanLat / 1e6) / 4);
                double latMax = (centerLat / 1e6) + ((spanLat / 1e6) / 2) + ((spanLat / 1e6) / 4);
                double lonMin = (centerLon / 1e6) - ((spanLon / 1e6) / 2) - ((spanLon / 1e6) / 4);
                double lonMax = (centerLon / 1e6) + ((spanLon / 1e6) / 2) + ((spanLon / 1e6) / 4);
                double llCache;
                if (latMin > latMax) {
                    llCache = latMax;
                    latMax = latMin;
                    latMin = llCache;
                }
                if (lonMin > lonMax) {
                    llCache = lonMax;
                    lonMax = lonMin;
                    lonMin = llCache;
                }
                users = base.getGeocachersInViewport(settings.getUsername(), latMin, latMax, lonMin, lonMax);
                if (stop) {
                    return;
                }
                if (displayUsersThread != null && displayUsersThread.isWorking()) {
                    displayUsersThread.stopIt();
                }
                displayUsersThread = new DisplayUsersThread(users, centerLat, centerLon, spanLat, spanLon);
                displayUsersThread.start();
            } finally {
                working = false;
            }
        }
    }

    // display users of Go 4 Cache
    private class DisplayUsersThread extends DoThread {

        private ArrayList<cgUser> users = null;

        public DisplayUsersThread(ArrayList<cgUser> usersIn, long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) {
            super(centerLatIn, centerLonIn, spanLatIn, spanLonIn);
            users = usersIn;
        }

        @Override
        public void run() {
            try {
                stop = false;
                working = true;
                if (mapView == null || users == null || users.isEmpty()) {
                    return;
                }
                // display users
                ArrayList<UserOverlayItemImpl> items = new ArrayList<UserOverlayItemImpl>();
                int counter = 0;
                UserOverlayItemImpl item = null;
                for (cgUser userOne : users) {
                    if (stop) {
                        return;
                    }
                    if (userOne.latitude == null && userOne.longitude == null) {
                        continue;
                    }
                    item = settings.getMapFactory().getUserOverlayItemBase(activity, userOne);
                    items.add(item);
                    counter++;
                    if ((counter % 10) == 0) {
                        overlayUsers.updateItems(items);
                        displayHandler.sendEmptyMessage(1);
                    }
                }
                overlayUsers.updateItems(items);
            } finally {
                working = false;
            }
        }
    }

    // display one point
    private class DisplayPointThread extends Thread {

        @Override
        public void run() {
            if (mapView == null || caches == null) {
                return;
            }
            if (latitudeIntent != null && longitudeIntent != null) {
                cgCoord coord = new cgCoord();
                coord.type = "waypoint";
                coord.latitude = latitudeIntent;
                coord.longitude = longitudeIntent;
                coord.name = "some place";
                coordinates.add(coord);
                CacheOverlayItemImpl item = settings.getMapFactory().getCacheOverlayItem(coord, null);
                final int icon = base.getIcon(false, waypointTypeIntent, false, false, false);
                Drawable pin = null;
                if (iconsCache.containsKey(icon)) {
                    pin = iconsCache.get(icon);
                } else {
                    pin = getResources().getDrawable(icon);
                    pin.setBounds(0, 0, pin.getIntrinsicWidth(), pin.getIntrinsicHeight());
                    iconsCache.put(icon, pin);
                }
                item.setMarker(pin);
                overlayCaches.updateItems(item);
                displayHandler.sendEmptyMessage(1);
                cachesCnt = 1;
            } else {
                cachesCnt = 0;
            }
            displayHandler.sendEmptyMessage(0);
        }
    }

    private class DoThread extends Thread {

        protected boolean working = true;

        protected boolean stop = false;

        protected long centerLat = 0l;

        protected long centerLon = 0l;

        protected long spanLat = 0l;

        protected long spanLon = 0l;

        public DoThread(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) {
            centerLat = centerLatIn;
            centerLon = centerLonIn;
            spanLat = spanLatIn;
            spanLon = spanLonIn;
        }

        public synchronized boolean isWorking() {
            return working;
        }

        public synchronized void stopIt() {
            stop = true;
        }
    }

    // get if map is loading something
    private synchronized boolean isLoading() {
        boolean loading = false;
        if (loadThread != null && loadThread.isWorking()) {
            loading = true;
        } else if (downloadThread != null && downloadThread.isWorking()) {
            loading = true;
        } else if (displayThread != null && displayThread.isWorking()) {
            loading = true;
        }
        return loading;
    }

    // store caches
    private class LoadDetails extends Thread {

        private Handler handler = null;

        private ArrayList<String> geocodes = null;

        private volatile boolean stop = false;

        private long last = 0l;

        public LoadDetails(Handler handlerIn, ArrayList<String> geocodesIn) {
            handler = handlerIn;
            geocodes = geocodesIn;
        }

        public void stopIt() {
            stop = true;
        }

        @Override
        public void run() {
            if (geocodes == null || geocodes.isEmpty()) {
                return;
            }
            if (dir != null) {
                dir = app.removeDir();
            }
            if (geo != null) {
                geo = app.removeGeo();
            }
            for (String geocode : geocodes) {
                try {
                    if (stop == true) {
                        break;
                    }
                    if (!app.isOffline(geocode, null)) {
                        if ((System.currentTimeMillis() - last) < 1500) {

                        }
                        if (stop == true) {
                            Log.i(cgSettings.tag, "Stopped storing process.");
                            break;
                        }
                        base.storeCache(app, activity, null, geocode, 1, handler);
                    }
                } catch (Exception e) {
                    Log.e(cgSettings.tag, "cgeocaches.LoadDetails.run: " + e.toString());
                } finally {
                    // one more cache over
                    detailProgress++;
                    handler.sendEmptyMessage(0);
                }
                yield();
                last = System.currentTimeMillis();
            }
            // we're done
            handler.sendEmptyMessage(1);
        }
    }

    // center map to desired location
    private void centerMap(Double latitude, Double longitude) {
        if (latitude == null || longitude == null) {
            return;
        }
        if (mapView == null) {
            return;
        }
        if (!alreadyCentered) {
            alreadyCentered = true;
            mapController.setCenter(makeGeoPoint(latitude, longitude));
        } else {
            mapController.animateTo(makeGeoPoint(latitude, longitude));
        }
    }

    // move map to view results of searchIdIntent
    private void centerMap(String geocodeCenter, Long searchIdCenter, Double latitudeCenter, Double longitudeCenter) {
        if (!centered && (geocodeCenter != null || searchIdIntent != null)) {
            try {
                ArrayList<Object> viewport;
                if (geocodeCenter != null) {
                    viewport = app.getBounds(geocodeCenter);
                } else {
                    viewport = app.getBounds(searchIdCenter);
                }
                Integer cnt = (Integer) viewport.get(0);
                Integer minLat = null;
                Integer maxLat = null;
                Integer minLon = null;
                Integer maxLon = null;
                if (viewport.get(1) != null) {
                    minLat = new Double((Double) viewport.get(1) * 1e6).intValue();
                }
                if (viewport.get(2) != null) {
                    maxLat = new Double((Double) viewport.get(2) * 1e6).intValue();
                }
                if (viewport.get(3) != null) {
                    maxLon = new Double((Double) viewport.get(3) * 1e6).intValue();
                }
                if (viewport.get(4) != null) {
                    minLon = new Double((Double) viewport.get(4) * 1e6).intValue();
                }
                if (cnt == null || cnt <= 0 || minLat == null || maxLat == null || minLon == null || maxLon == null) {
                    return;
                }
                int centerLat = 0;
                int centerLon = 0;
                if ((Math.abs(maxLat) - Math.abs(minLat)) != 0) {
                    centerLat = minLat + ((maxLat - minLat) / 2);
                } else {
                    centerLat = maxLat;
                }
                if ((Math.abs(maxLon) - Math.abs(minLon)) != 0) {
                    centerLon = minLon + ((maxLon - minLon) / 2);
                } else {
                    centerLon = maxLon;
                }
                if (cnt != null && cnt > 0) {
                    mapController.setCenter(settings.getMapFactory().getGeoPointBase(centerLat, centerLon));
                    if (Math.abs(maxLat - minLat) != 0 && Math.abs(maxLon - minLon) != 0) {
                        mapController.zoomToSpan(Math.abs(maxLat - minLat), Math.abs(maxLon - minLon));
                    }
                }
            } catch (Exception e) {
                // nothing at all
            }
            centered = true;
            alreadyCentered = true;
        } else if (!centered && latitudeCenter != null && longitudeCenter != null) {
            try {
                mapController.setCenter(makeGeoPoint(latitudeCenter, longitudeCenter));
            } catch (Exception e) {
                // nothing at all
            }
            centered = true;
            alreadyCentered = true;
        }
    }

    // switch My Location button image
    private void setMyLoc(Boolean status) {
        if (myLocSwitch == null) {
            myLocSwitch = (ImageView) activity.findViewById(R.id.my_position);
        }
        if (status == null) {
            if (followMyLocation == true) {
                myLocSwitch.setImageResource(R.drawable.my_location_on);
            } else {
                myLocSwitch.setImageResource(R.drawable.my_location_off);
            }
        } else {
            if (status == true) {
                myLocSwitch.setImageResource(R.drawable.my_location_on);
            } else {
                myLocSwitch.setImageResource(R.drawable.my_location_off);
            }
        }
        myLocSwitch.setOnClickListener(new MyLocationListener());
    }

    // set my location listener
    private class MyLocationListener implements View.OnClickListener {

        public void onClick(View view) {
            if (myLocSwitch == null) {
                myLocSwitch = (ImageView) activity.findViewById(R.id.my_position);
            }
            if (followMyLocation == true) {
                followMyLocation = false;
                myLocSwitch.setImageResource(R.drawable.my_location_off);
            } else {
                followMyLocation = true;
                myLocationInMiddle();
                myLocSwitch.setImageResource(R.drawable.my_location_on);
            }
        }
    }

    // make geopoint
    private GeoPointImpl makeGeoPoint(Double latitude, Double longitude) {
        return settings.getMapFactory().getGeoPointBase((int) (latitude * 1e6), (int) (longitude * 1e6));
    }

    // close activity and open homescreen
    public void goHome(View view) {
        base.goHome(activity);
    }

    // open manual entry
    public void goManual(View view) {
        try {
            AppManualReaderClient.openManual("c-geo", "c:geo-live-map", activity, "http://example.com");
        } catch (Exception e) {
            // nothing
        }
    }
}


public Element createGhostRow() {
            Element ghostRow = DOM.createTR();
            ghostRow.getStyle().setPropertyPx("margin", 0);
            ghostRow.getStyle().setPropertyPx("padding", 0);
            ghostRow.getStyle().setPropertyPx("height", 0);
            ghostRow.getStyle().setProperty("overflow", "hidden");
            return ghostRow;
        }

package com.google.gwt.gen2.table.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.TableCellElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.gen2.table.override.client.HTMLTable;
import com.google.gwt.gen2.table.override.client.OverrideDOM;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;

/**
 * Implementation class that handles common code shared between the
 * {@link FixedWidthGrid} and {@link FixedWidthFlexTable}.
 */
class FixedWidthTableImpl {

    /**
     * The implementation singleton.
     */
    private static Impl impl;

    /**
     * Get the implementation singleton.
     */
    public static Impl get() {
        if (impl == null) {
            impl = GWT.create(Impl.class);
        }
        return impl;
    }

    /**
     * Information used to calculate the ideal column widths of a table.
     */
    public static class IdealColumnWidthInfo {

        private HTMLTable table;

        private TableRowElement tr;

        private int columnCount;

        private int offset;

        public IdealColumnWidthInfo(HTMLTable table, TableRowElement tr, int columnCount, int offset) {
            this.table = table;
            this.tr = tr;
            this.columnCount = columnCount;
            this.offset = offset;
        }
    }

    /**
     * An implementation used accommodate differences in column width
     * implementations between browsers.
     */
    public static class Impl {

        /**
         * Create a cell to use in the ghost row.
         *
         * @param td the optional td to modify
         * @return the cell
         */
        public Element createGhostCell(Element td) {
            if (td == null) {
                td = OverrideDOM.createTD();
            }
            td.getStyle().setPropertyPx("height", 0);
            td.getStyle().setProperty("overflow", "hidden");
            td.getStyle().setPropertyPx("paddingTop", 0);
            td.getStyle().setPropertyPx("paddingBottom", 0);
            td.getStyle().setPropertyPx("borderTop", 0);
            td.getStyle().setPropertyPx("borderBottom", 0);
            td.getStyle().setPropertyPx("margin", 0);
            return td;
        }

        /**
         * Create the ghost row.
         *
         * @return the ghost row element
         */

/**
         * Returns a cell in the ghost row.
         *
         * @param column the cell's column
         * @return the ghost cell
         */
        public Element getGhostCell(Element ghostRow, int column) {
            return DOM.getChild(ghostRow, column);
        }

        /**
         * Recalculate the ideal column widths of each column in the data table.
         * This method assumes that the tableLayout has already been changed.
         *
         * @param info the {@link IdealColumnWidthInfo}
         * @return the ideal column widths
         */
        public int[] recalculateIdealColumnWidths(IdealColumnWidthInfo info) {
            // Workaround for IE re-entrant bug on window resize
            if (info == null) {
                return new int[0];
            }
            // We need at least one cell to do any calculations
            int columnCount = info.columnCount;
            HTMLTable table = info.table;
            if (!table.isAttached() || table.getRowCount() == 0 || columnCount < 1) {
                return new int[0];
            }
            // Determine the width of each column
            int[] idealWidths = new int[columnCount];
            com.google.gwt.dom.client.Element td = info.tr.getFirstChildElement();
            for (int i = 0; i < info.offset; i++) {
                td = td.getNextSiblingElement();
            }
            for (int i = 0; i < columnCount; i++) {
                idealWidths[i] = td.getClientWidth();
                td = td.getNextSiblingElement();
            }
            return idealWidths;
        }

        /**
         * Setup to recalculate column widths.
         *
         * @param table the table
         * @param columnCount the number of columns in the table
         * @param offset the offset of the first logical column
         * @return info used to calculate ideal column widths
         */
        public IdealColumnWidthInfo recalculateIdealColumnWidthsSetup(HTMLTable table, int columnCount, int offset) {
            // Switch to normal layout
            table.getElement().getStyle().setProperty("tableLayout", "");
            // Add a full row to get ideal widths
            TableRowElement tr = Document.get().createTRElement();
            TableCellElement td = Document.get().createTDElement();
            td.setInnerHTML("

");
            for (int i = 0; i < columnCount + offset; i++) {
                tr.appendChild(td.cloneNode(true));
            }
            getTableBody(table).appendChild(tr);
            return new IdealColumnWidthInfo(table, tr, columnCount, offset);
        }

        /**
         * Tear down after recalculating column widths.
         *
         * @param info the {@link IdealColumnWidthInfo}
         */
        public void recalculateIdealColumnWidthsTeardown(IdealColumnWidthInfo info) {
            // Workaround for IE re-entrant bug on window resize
            if (info == null) {
                return;
            }
            info.table.getElement().getStyle().setProperty("tableLayout", "fixed");
            getTableBody(info.table).removeChild(info.tr);
        }

        /**
         * Set the width of a column using the ghost row.
         *
         * @param table the table containing the column
         * @param ghostRow the ghost row element
         * @param column the index of the column
         * @param width the width in pixels
         * @throws IndexOutOfBoundsException
         */
        public void setColumnWidth(HTMLTable table, Element ghostRow, int column, int width) {
            getGhostCell(ghostRow, column).getStyle().setPropertyPx("width", width);
        }

        /**
         * Get the body element of an {@link HTMLTable}.
         *
         * @param table the table
         * @return the body elements
         */
        private native Element getTableBody(HTMLTable table);
    }

    /**
     * IE version of the implementation. IE sets the overall column width instead
     * of the innerWidth, so we need to add the padding and spacing.
     */
    public static class ImplTrident extends Impl {

        @Override
        public void setColumnWidth(HTMLTable table, Element ghostRow, int column, int width) {
            width += 2 * table.getCellPadding() + table.getCellSpacing();
            super.setColumnWidth(table, ghostRow, column, width);
        }
    }

    /**
     * IE6 version of the implementation hides the ghost row using the display
     * attribute.
     */
    public static class ImplIE6 extends ImplTrident {

        @Override
        public Element createGhostRow() {
            Element ghostRow = super.createGhostRow();
            ghostRow.getStyle().setProperty("display", "none");
            return ghostRow;
        }
    }

    /**
     * IE8 version of the implementation.
     */
    public static class ImplIE8 extends ImplTrident {
    }

    /**
     * Safari version of the implementation. Safari sets the overall column width
     * instead of the innerWidth, so we need to add the padding and spacing.
     */
    public static class ImplSafari extends Impl {

        @Override
        public void setColumnWidth(HTMLTable table, Element ghostRow, int column, int width) {
            width += 2 * table.getCellPadding() + table.getCellSpacing();
            super.setColumnWidth(table, ghostRow, column, width);
        }
    }

    /**
     * Opera version of the implementation refreshes the table display so the new
     * column size takes effect. Without the display refresh, the column width
     * doesn't update in the browser.
     */
    public static class ImplOpera extends Impl {

        @Override
        public void setColumnWidth(HTMLTable table, Element ghostRow, int column, int width) {
            super.setColumnWidth(table, ghostRow, column, width);
            Element tableElem = table.getElement();
            Element parentElem = DOM.getParent(tableElem);
            int scrollLeft = 0;
            if (parentElem != null) {
                scrollLeft = parentElem.getScrollLeft();
            }
            tableElem.getStyle().setProperty("display", "none");
            tableElem.getStyle().setProperty("display", "");
            if (parentElem != null) {
                parentElem.setScrollLeft(scrollLeft);
            }
        }
    }
}


for (int i = 0; i < columnCount; i++) {
                idealWidths[i] = td.getClientWidth();
                td = td.getNextSiblingElement();
            }

package com.google.gwt.gen2.table.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.TableCellElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.gen2.table.override.client.HTMLTable;
import com.google.gwt.gen2.table.override.client.OverrideDOM;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;

/**
 * Implementation class that handles common code shared between the
 * {@link FixedWidthGrid} and {@link FixedWidthFlexTable}.
 */
class FixedWidthTableImpl {

    /**
     * The implementation singleton.
     */
    private static Impl impl;

    /**
     * Get the implementation singleton.
     */
    public static Impl get() {
        if (impl == null) {
            impl = GWT.create(Impl.class);
        }
        return impl;
    }

    /**
     * Information used to calculate the ideal column widths of a table.
     */
    public static class IdealColumnWidthInfo {

        private HTMLTable table;

        private TableRowElement tr;

        private int columnCount;

        private int offset;

        public IdealColumnWidthInfo(HTMLTable table, TableRowElement tr, int columnCount, int offset) {
            this.table = table;
            this.tr = tr;
            this.columnCount = columnCount;
            this.offset = offset;
        }
    }

    /**
     * An implementation used accommodate differences in column width
     * implementations between browsers.
     */
    public static class Impl {

        /**
         * Create a cell to use in the ghost row.
         *
         * @param td the optional td to modify
         * @return the cell
         */
        public Element createGhostCell(Element td) {
            if (td == null) {
                td = OverrideDOM.createTD();
            }
            td.getStyle().setPropertyPx("height", 0);
            td.getStyle().setProperty("overflow", "hidden");
            td.getStyle().setPropertyPx("paddingTop", 0);
            td.getStyle().setPropertyPx("paddingBottom", 0);
            td.getStyle().setPropertyPx("borderTop", 0);
            td.getStyle().setPropertyPx("borderBottom", 0);
            td.getStyle().setPropertyPx("margin", 0);
            return td;
        }

        /**
         * Create the ghost row.
         *
         * @return the ghost row element
         */
        public Element createGhostRow() {
            Element ghostRow = DOM.createTR();
            ghostRow.getStyle().setPropertyPx("margin", 0);
            ghostRow.getStyle().setPropertyPx("padding", 0);
            ghostRow.getStyle().setPropertyPx("height", 0);
            ghostRow.getStyle().setProperty("overflow", "hidden");
            return ghostRow;
        }

        /**
         * Returns a cell in the ghost row.
         *
         * @param column the cell's column
         * @return the ghost cell
         */
        public Element getGhostCell(Element ghostRow, int column) {
            return DOM.getChild(ghostRow, column);
        }

        /**
         * Recalculate the ideal column widths of each column in the data table.
         * This method assumes that the tableLayout has already been changed.
         *
         * @param info the {@link IdealColumnWidthInfo}
         * @return the ideal column widths
         */
        public int[] recalculateIdealColumnWidths(IdealColumnWidthInfo info) {
            // Workaround for IE re-entrant bug on window resize
            if (info == null) {
                return new int[0];
            }
            // We need at least one cell to do any calculations
            int columnCount = info.columnCount;
            HTMLTable table = info.table;
            if (!table.isAttached() || table.getRowCount() == 0 || columnCount < 1) {
                return new int[0];
            }
            // Determine the width of each column
            int[] idealWidths = new int[columnCount];
            com.google.gwt.dom.client.Element td = info.tr.getFirstChildElement();
            for (int i = 0; i < info.offset; i++) {
                td = td.getNextSiblingElement();
            }

return idealWidths;
        }

        /**
         * Setup to recalculate column widths.
         *
         * @param table the table
         * @param columnCount the number of columns in the table
         * @param offset the offset of the first logical column
         * @return info used to calculate ideal column widths
         */
        public IdealColumnWidthInfo recalculateIdealColumnWidthsSetup(HTMLTable table, int columnCount, int offset) {
            // Switch to normal layout
            table.getElement().getStyle().setProperty("tableLayout", "");
            // Add a full row to get ideal widths
            TableRowElement tr = Document.get().createTRElement();
            TableCellElement td = Document.get().createTDElement();
            td.setInnerHTML("

");
            for (int i = 0; i < columnCount + offset; i++) {
                tr.appendChild(td.cloneNode(true));
            }
            getTableBody(table).appendChild(tr);
            return new IdealColumnWidthInfo(table, tr, columnCount, offset);
        }

        /**
         * Tear down after recalculating column widths.
         *
         * @param info the {@link IdealColumnWidthInfo}
         */
        public void recalculateIdealColumnWidthsTeardown(IdealColumnWidthInfo info) {
            // Workaround for IE re-entrant bug on window resize
            if (info == null) {
                return;
            }
            info.table.getElement().getStyle().setProperty("tableLayout", "fixed");
            getTableBody(info.table).removeChild(info.tr);
        }

        /**
         * Set the width of a column using the ghost row.
         *
         * @param table the table containing the column
         * @param ghostRow the ghost row element
         * @param column the index of the column
         * @param width the width in pixels
         * @throws IndexOutOfBoundsException
         */
        public void setColumnWidth(HTMLTable table, Element ghostRow, int column, int width) {
            getGhostCell(ghostRow, column).getStyle().setPropertyPx("width", width);
        }

        /**
         * Get the body element of an {@link HTMLTable}.
         *
         * @param table the table
         * @return the body elements
         */
        private native Element getTableBody(HTMLTable table);
    }

    /**
     * IE version of the implementation. IE sets the overall column width instead
     * of the innerWidth, so we need to add the padding and spacing.
     */
    public static class ImplTrident extends Impl {

        @Override
        public void setColumnWidth(HTMLTable table, Element ghostRow, int column, int width) {
            width += 2 * table.getCellPadding() + table.getCellSpacing();
            super.setColumnWidth(table, ghostRow, column, width);
        }
    }

    /**
     * IE6 version of the implementation hides the ghost row using the display
     * attribute.
     */
    public static class ImplIE6 extends ImplTrident {

        @Override
        public Element createGhostRow() {
            Element ghostRow = super.createGhostRow();
            ghostRow.getStyle().setProperty("display", "none");
            return ghostRow;
        }
    }

    /**
     * IE8 version of the implementation.
     */
    public static class ImplIE8 extends ImplTrident {
    }

    /**
     * Safari version of the implementation. Safari sets the overall column width
     * instead of the innerWidth, so we need to add the padding and spacing.
     */
    public static class ImplSafari extends Impl {

        @Override
        public void setColumnWidth(HTMLTable table, Element ghostRow, int column, int width) {
            width += 2 * table.getCellPadding() + table.getCellSpacing();
            super.setColumnWidth(table, ghostRow, column, width);
        }
    }

    /**
     * Opera version of the implementation refreshes the table display so the new
     * column size takes effect. Without the display refresh, the column width
     * doesn't update in the browser.
     */
    public static class ImplOpera extends Impl {

        @Override
        public void setColumnWidth(HTMLTable table, Element ghostRow, int column, int width) {
            super.setColumnWidth(table, ghostRow, column, width);
            Element tableElem = table.getElement();
            Element parentElem = DOM.getParent(tableElem);
            int scrollLeft = 0;
            if (parentElem != null) {
                scrollLeft = parentElem.getScrollLeft();
            }
            tableElem.getStyle().setProperty("display", "none");
            tableElem.getStyle().setProperty("display", "");
            if (parentElem != null) {
                parentElem.setScrollLeft(scrollLeft);
            }
        }
    }
}


public float getNegativeKernelDimMultiple() {
        return negativeKernelDimMultiple;
    }

package net.sf.jaer.eventprocessing.filter;

import com.jogamp.opengl.GL;
import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;
import com.jogamp.opengl.GL2;
import com.jogamp.opengl.GLAutoDrawable;
import net.sf.jaer.Description;
import net.sf.jaer.DevelopmentStatus;
import net.sf.jaer.chip.AEChip;
import net.sf.jaer.event.EventPacket;
import net.sf.jaer.event.OutputEventIterator;
import net.sf.jaer.event.PolarityEvent;
import net.sf.jaer.eventprocessing.EventFilter2D;
import net.sf.jaer.graphics.FrameAnnotater;

/**
 * Computes circular convolutions by splatting out events and checking receiving
 * pixels to see if they exceed a threshold. A behavioral model of
 * Raphael/Bernabe convolution chip, but limited in that it presently only
 * allows positive binary kernel weights, thus the output events can be
 * triggered by lots of input activity.
 *
 * @author tobi
 */
@Description("Computes circular convolutions by splatting out events and checking receiving pixels to see if they exceed a threshold")
@DevelopmentStatus(DevelopmentStatus.Status.Experimental)
public final class CircularConvolutionFilter extends EventFilter2D implements Observer, FrameAnnotater {

    static final int NUM_INPUT_CELL_TYPES = 1;

    protected boolean useBalancedKernel = getBoolean("useBalancedKernel", true);

    protected float negativeKernelDimMultiple = getFloat("negativeKernelDimMultiple", 2);

    private float negativeWeight = 0;

    private int negativeKernelRadius = 0;

    /**
     * the number of cell output types
     */
    // we make it big so rendering is in color
    public final int NUM_OUTPUT_TYPES = 1;

    /**
     * Creates a new instance of CircularConvolutionFilter
     */
    public CircularConvolutionFilter(AEChip chip) {
        super(chip);
        chip.addObserver(this);
        resetFilter();
        setFilterEnabled(false);
        setPropertyTooltip("useBalancedKernel", "balances kernel to zero sum with positive and negative weights");
    }

    @Override
    synchronized public EventPacket filterPacket(EventPacket in) {
        checkOutputPacketEventType(in);
        int sx = chip.getSizeX() - 1;
        int sy = chip.getSizeY() - 1;
        int n = in.getSize();
        OutputEventIterator oi = out.outputIterator();
        for (Object o : in) {
            if (o == null) {
                continue;
            }
            PolarityEvent e = (PolarityEvent) o;
            if (e.isSpecial() || e.isFilteredOut()) {
                PolarityEvent oe = (PolarityEvent) oi.nextOutput();
                oe.copyFrom(e);
                continue;
            }
            x = e.x;
            y = e.y;
            ts = e.timestamp;
            for (Splatt s : splatts) {
                int xoff = x + s.x;
                if ((xoff < 0) || (xoff > sx)) {
                    // precheck array access
                    continue;
                }
                int yoff = y + s.y;
                if ((yoff < 0) || (yoff > sy)) {
                    continue;
                }
                float dtMs = (ts - convolutionLastEventTime[xoff][yoff]) * 1e-3f;
                if (dtMs < 0) {
                    convolutionLastEventTime[xoff][yoff] = ts;
                    // ignore negative dt
                    continue;
                }
                float vmold = convolutionVm[xoff][yoff];
                if (s.weight < 0) {
                    float vm = vmold + s.weight;
                    convolutionVm[xoff][yoff] = vm;
                } else {
                    vmold = (float) (vmold * (Math.exp(-dtMs / tauMs)));
                    float vm = vmold + s.weight;
                    convolutionVm[xoff][yoff] = vm;
                    convolutionLastEventTime[xoff][yoff] = ts;
                    if (vm > threshold) {
                        PolarityEvent oe = (PolarityEvent) oi.nextOutput();
                        oe.copyFrom(e);
                        oe.x = (short) xoff;
                        oe.y = (short) yoff;
                        convolutionVm[xoff][yoff] = 0;
                    }
                }
            }
        }
        return out;
    }

    @Override
    synchronized public void resetFilter() {
        allocateMap();
    }

    @Override
    public void annotate(GLAutoDrawable drawable) {
        // TODO make this a displaylist
        GL2 gl = drawable.getGL().getGL2();
        // where to draw it
        final int xp = -20, yp = 10;
        // how big each one, 1 for actual size
        final int sz = 1;
        // draw negative part as big rectangle
        gl.glColor4f(-negativeWeight, 0, 0, 1);
        gl.glRectf(xp + ((-negativeKernelRadius - .5f) * sz), yp + ((-negativeKernelRadius - .5f) * sz), xp + ((negativeKernelRadius + .5f) * sz), yp + ((negativeKernelRadius + .5f) * sz));
        gl.glColor4f(0, 0, 1, .5f);
        gl.glBegin(GL.GL_LINE_LOOP);
        gl.glVertex2f(xp + ((-negativeKernelRadius - .5f) * sz), yp + ((-negativeKernelRadius - .5f) * sz));
        gl.glVertex2f(xp + ((negativeKernelRadius - .5f) * sz), yp + ((-negativeKernelRadius - .5f) * sz));
        gl.glVertex2f(xp + ((negativeKernelRadius - .5f) * sz), yp + ((negativeKernelRadius - .5f) * sz));
        gl.glVertex2f(xp + ((-negativeKernelRadius - .5f) * sz), yp + ((negativeKernelRadius - .5f) * sz));
        gl.glEnd();
        // draw each positive kernel pixel on top
        for (Splatt s : splatts) {
            if (s.weight > 0) {
                gl.glColor4f(0, s.weight, 0, 1);
                gl.glRectf(xp + ((s.x - .5f) * sz), yp + ((s.y - .5f) * sz), xp + ((s.x + .5f) * sz), yp + ((s.y + .5f) * sz));
            } else {
            }
        }
    }

    /**
     * @return the useBalancedKernel
     */
    public boolean isUseBalancedKernel() {
        return useBalancedKernel;
    }

    /**
     * @param useBalancedKernel the useBalancedKernel to set
     */
    synchronized public void setUseBalancedKernel(boolean useBalancedKernel) {
        this.useBalancedKernel = useBalancedKernel;
        putBoolean("useBalancedKernel", useBalancedKernel);
        computeSplattLookup();
    }

    /**
     * @return the negativeKernelDimMultiple
     */

    /**
     * @param negativeKernelDimMultiple the negativeKernelDimMultiple to set
     */
    public void setNegativeKernelDimMultiple(float negativeKernelDimMultiple) {
        this.negativeKernelDimMultiple = negativeKernelDimMultiple;
        putFloat("negativeKernelDimMultiple", negativeKernelDimMultiple);
        computeSplattLookup();
    }

    final class Splatt {

        int x, y;

        float weight = 1;

        Splatt() {
            x = 0;
            y = 0;
            weight = 1;
        }

        Splatt(int x, int y) {
            this.x = x;
            this.y = y;
        }

        Splatt(int x, int y, float w) {
            this.x = x;
            this.y = y;
            weight = w;
        }

        @Override
        public String toString() {
            return "Splatt: " + x + "," + y + "," + String.format("%.1f", weight);
        }
    }

    private Splatt[] splatts;

    // computes the indices to splatt to from a source event
    // these are octagonal around a point to the neighboring pixels at a certain radius
    // eg, radius=0, impulse kernal=identity kernel
    // radius=1, nearest neighbors in 8 directions
    // TODO include negative weights where there is no kernel circle to end with zero net kernel sum
    synchronized void computeSplattLookup() {
        ArrayList<Splatt> list = new ArrayList<Splatt>();
        // num pixels
        double circum = 2 * Math.PI * radius;
        int xlast = -1, ylast = -1;
        int n = (int) Math.ceil(circum);
        if (radius < 3) {
            switch(radius) {
                case // identity
                0:
                    list.add(new Splatt());
                    break;
                case 1:
                    list.add(new Splatt(1, 0, .25f));
                    list.add(new Splatt(0, 1, .25f));
                    list.add(new Splatt(-1, 0, .25f));
                    list.add(new Splatt(0, -1, .25f));
                    break;
                case 2:
                    list.add(new Splatt(1, 0));
                    list.add(new Splatt(0, 1));
                    list.add(new Splatt(-1, 0));
                    list.add(new Splatt(0, -1));
                    list.add(new Splatt(1, 1));
                    list.add(new Splatt(-1, 1));
                    list.add(new Splatt(-1, -1));
                    list.add(new Splatt(1, -1));
                    float f = 1f / list.size();
                    for (Splatt s : list) {
                        s.weight *= f;
                    }
                    break;
                default:
            }
        } else {
            for (int i = 0; i < n; i++) {
                double theta = (2 * Math.PI * i) / circum;
                double xoff = Math.cos(theta) * radius;
                double yoff = Math.sin(theta) * radius;
                double xround = Math.round(xoff);
                double yround = Math.round(yoff);
                if ((xlast != xround) || (ylast != yround)) {
                    // dont make multiple copies of the same splatt around the circle
                    Splatt s = new Splatt();
                    s.x = (int) xround;
                    s.y = (int) yround;
                    s.weight = 1;
                    xlast = s.x;
                    ylast = s.y;
                    list.add(s);
                }
            }
        }
        // // make negative outside ring, 1/2 weight
        // if((xlast!=xround) || (ylast!=yround)){ // dont make multiple copies of the same splatt around the circle
        // if((xlast!=xround) || (ylast!=yround)){ // dont make multiple copies of the same splatt around the circle
        float sum = 0;
        for (Splatt s : list) {
            sum += s.weight;
        }
        log.info("splatt total positive weight = " + sum + " num weights=" + list.size());
        if (isUseBalancedKernel()) {
            negativeKernelRadius = (int) Math.round(radius * negativeKernelDimMultiple);
            final int numNegWeights = (2 * negativeKernelRadius + 1) * (2 * negativeKernelRadius + 1);
            negativeWeight = -sum / numNegWeights;
            for (int x = -negativeKernelRadius; x <= negativeKernelRadius; x++) {
                for (int y = -negativeKernelRadius; y <= negativeKernelRadius; y++) {
                    list.add(new Splatt(x, y, negativeWeight));
                }
            }
        }
        Object[] oa = list.toArray();
        splatts = new Splatt[oa.length];
        sum = 0;
        for (int i = 0; i < oa.length; i++) {
            splatts[i] = (Splatt) oa[i];
            sum += splatts[i].weight;
        }
        log.info("splatt final total weight = " + sum + " num weights=" + splatts.length);
    }

    int PADDING = 0, P = 0;

    float[][] convolutionVm;

    int[][] convolutionLastEventTime;

    private void allocateMap() {
        if ((chip.getSizeX() == 0) || (chip.getSizeY() == 0)) {
            // log.warning("tried to allocateMap in CircularConvolutionFilter but chip size is 0");
            return;
        }
        convolutionVm = new float[chip.getSizeX()][chip.getSizeY()];
        convolutionLastEventTime = new int[chip.getSizeX()][chip.getSizeY()];
        computeSplattLookup();
    }

    private short x, y;

    private byte type;

    private int ts;

    @Override
    public void initFilter() {
        resetFilter();
    }

    @Override
    public void update(Observable o, Object arg) {
        if (!isFilterEnabled()) {
            return;
        }
        initFilter();
    }

    private int radius = getInt("radius", 3);

    {
        setPropertyTooltip("radius", "radius in pixels of kernal");
    }

    public int getRadius() {
        return radius;
    }

    synchronized public void setRadius(int radius) {
        if (radius < 0) {
            radius = 0;
        } else if (radius > chip.getMaxSize()) {
            radius = chip.getMaxSize();
        }
        if (radius != this.radius) {
            this.radius = radius;
            putInt("radius", radius);
            resetFilter();
        }
    }

    private float tauMs = getFloat("tauMs", 10f);

    {
        setPropertyTooltip("tauMs", "time constant in ms of integrator neuron potential decay");
    }

    public float getTauMs() {
        return tauMs;
    }

    synchronized public void setTauMs(float tauMs) {
        if (tauMs < 0) {
            tauMs = 0;
        } else if (tauMs > 10000) {
            tauMs = 10000f;
        }
        this.tauMs = tauMs;
        getPrefs().putFloat("CircularConvolutionFilter.tauMs", tauMs);
    }

    private float threshold = getFloat("threshold", 1f);

    {
        setPropertyTooltip("threshold", "threahold on ms for firing output event from integrating neuron");
    }

    public float getThreshold() {
        return threshold;
    }

    synchronized public void setThreshold(float threshold) {
        if (threshold < 0) {
            threshold = 0;
        } else if (threshold > 100) {
            threshold = 100;
        }
        this.threshold = threshold;
        putFloat("threshold", threshold);
    }
}