Changing Floors

In order to specify which floor of a building you’re on, you need to be able to switch floors within a building. By default, the ground floor of the building will be shown, but since I’m on floor 4, I need to be able to select that floor and check-in on that floor to fix my position.

We need to register an onClickListener on Google Maps so that tapping on a building will bring up a floor selector and let you switch floors.

First, register an onClickListener in the onMapReady function:

/**
    * Manipulates the map once available.
    * This callback is triggered when the map is ready to be used.
    * This is where we can add markers or lines, add listeners or move the camera. In this case,
    * we just add a marker near Sydney, Australia.
    * If Google Play services is not installed on the device, the user will be prompted to install
    * it inside the SupportMapFragment. This method will only be triggered once the user has
    * installed Google Play services and returned to the app.
    */
@Override
public void onMapReady(GoogleMap googleMap) {
    mMap = googleMap;

    mMap.setLocationSource(locationSource);
    mMap.setMyLocationEnabled(this);
    mMap.setOnPolygonClickListener(onPolygonClickListener);
}

Next, make a AlertDialog called floorSelector and an onClickListener with the following code:

private AlertDialog floorSelector;

/**
    * onPolygonClickListener is called when you tap a building outline on the screen.
    * It will pop up a dialog that allows you to select another floor.  If another
    * floor is selected, it will clear the existing outline and floor plan image, and
    * draw a the new outline and download the floor plan image, if there is one.
    */
GoogleMap.OnPolygonClickListener onPolygonClickListener = new GoogleMap.OnPolygonClickListener() {
    @Override
    public void onPolygonClick(final Polygon polygon) {

        //Each polygon outline on the screen has a tag with the building ID.  We use the
        //building ID to retrieve the list of outlines and images drawn to the screen.
        //We will clear these outlines and draw a new one if the floor changes

        //The selected building
        final UUID buildingID = (UUID) polygon.getTag();

        //The polygons and overlays drawn to the screen for this building
        final BuildingOverlays bo = buildingHashMap.get(buildingID);

        if (bo == null)
            return;

        Log.i("MapsActivity", "Clicked on building id: " + bo.building.toString() + ", currently showing floor " + bo.floor);

        //close existing dialog if it's open
        if (floorSelector != null && floorSelector.isShowing()) {
            floorSelector.dismiss();
            floorSelector.setView(null);
        }

        floorSelector = new AlertDialog.Builder(MapsActivity.this)
                .setTitle("Select a new floor")
                .create();

        final Spinner floorSpinner = new Spinner(MapsActivity.this);
        LinearLayout layout = new LinearLayout(MapsActivity.this);
        layout.setOrientation(LinearLayout.VERTICAL);
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(AbsListView.LayoutParams.WRAP_CONTENT, AbsListView.LayoutParams.MATCH_PARENT);
        layout.addView(floorSpinner, layoutParams);
        floorSelector.setView(layout);

        //use the building to populate the floor spinner with the set of floor labels for that building
        //and set the selected index to the current floor
        floorSpinner.setAdapter(getFloorAdapter(bo.building));
        int position = Math.round(bo.floor);
        final int topFloorNumber = bo.building.getFloors().get(bo.building.getFloors().size() - 1).getFloorNumber();
        floorSpinner.setSelection(topFloorNumber - position);

        //on pressing OK, remove the outlines and image for the current floor, and draw the new outline and
        //download the new image for drawing
        floorSelector.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {
            public void onClick(final DialogInterface dialog, final int id) {
                final int floorIndex = topFloorNumber - floorSpinner.getSelectedItemPosition();

                final NeonBuildingFloor previousFloor = bo.building.getFloor(bo.floor);
                final NeonBuildingFloor newFloor = bo.building.getFloor(floorIndex);

                Log.i("MapsActivity", "user selected floor: " + newFloor.getLabel());

                //if the floor is unchanged, do nothing
                if (previousFloor.getFloorNumber() == newFloor.getFloorNumber())
                    return;

                //remove the current outlines drawn to the map
                for (Polygon p : bo.outlines)
                    p.remove();

                //if there was an image drawn, remove that as well
                if (previousFloor.hasFloorPlan() && floorPlanMap.containsKey(previousFloor.getFloorPlanImageID()))
                    floorPlanMap.get(previousFloor.getFloorPlanImageID()).remove();

                //draw the new outline
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        drawOutline(bo.building, newFloor);
                    }
                });

                //download the new floorplan, if it exists
                if (newFloor.hasFloorPlan())
                    NeonEnvironment.downloadFloorPlan(getApplicationContext(), newFloor, dataHandler, MapsActivity.this);
            }
        });
        floorSelector.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener() {
            public void onClick(final DialogInterface dialog, final int id) {
                dialog.cancel();
            }
        });

        floorSelector.show();
    }

};

/**
    * Constructs an adapter with all the floors in a building
    */
public ArrayAdapter<String> getFloorAdapter(NeonBuilding b) {
    ArrayAdapter<String> adapter = new ArrayAdapter<>(MapsActivity.this, android.R.layout.simple_spinner_item);
    adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

    if (b == null)
        return adapter;

    // add all floors numbers starting from the lowest level
    for (int i = 0; i < b.getFloors().size(); ++i) {
        NeonBuildingFloor floor = b.getFloors().get(b.getFloors().size() - i - 1);
        adapter.add(floor.getLabel());
    }

    return adapter;
}

If you remember back when we drew the outline to the screen in our drawOutline function, we specified that the polygon was clickable and set a tag on that polygon with the building ID:

//add to map
Polygon p = mMap.addPolygon(new PolygonOptions().addAll(ol).clickable(true));

//tag it with the building ID
p.setTag(building.getID());

When the polygon is clicked, we will use the ID in the tag to pull our building overlays object from the hashmap, which specifies the building and floor that is currently being displayed. It will then construct a dialog that is populated with the floors in that building and allow you to select a different one. If the floor selected is different from the current floor, it will remove the outlines and the ground overlay for that floor, and then redraw the outline and get the floorplan for the new floor.

Floorselector