aboutsummaryrefslogtreecommitdiffstats
path: root/main/src/cgeo/geocaching/speech/TextFactory.java
blob: 4d59b72fdf6380a8c337f782932b1a589fa48bd6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package cgeo.geocaching.speech;

import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.R;
import cgeo.geocaching.location.Geopoint;
import cgeo.geocaching.location.IConversion;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.utils.AngleUtils;

import java.util.Locale;

/**
 * Creates the output to be read by TTS.
 *
 * Note: some languages need to read "one hour" as "a hour" (indefinite article). Also, other languages
 * use the <tt>quantity="1"</tt> plurals rule for other values than 1, such as Slovenian, so it is not
 * possible to store the literal value to use for 1 in this rule. For this reason, we need to have one
 * string for the unit quantity ("one meter") and a plurals rule for everything else.
 *
 * See http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html for rules
 * on unit expressions.
 */
public class TextFactory {
    public static String getText(Geopoint position, Geopoint target, float direction) {
        if (position == null || target == null) {
            return null;
        }
        return getDirection(position, target, direction) + ". " + getDistance(position, target);
    }

    private static String getDistance(Geopoint position, Geopoint target) {
        final float kilometers = position.distanceTo(target);

        if (Settings.useImperialUnits()) {
            return getDistance(kilometers / IConversion.MILES_TO_KILOMETER,
                    (int) (kilometers * 1000.0 * IConversion.METERS_TO_FEET),
                    3.0f, 0.2f, 300,
                    R.plurals.tts_miles, R.string.tts_one_mile,
                    R.plurals.tts_feet, R.string.tts_one_foot);
        }
        return getDistance(kilometers, (int) (kilometers * 1000.0),
                5.0f, 1.0f, 50,
                R.plurals.tts_kilometers, R.string.tts_one_kilometer,
                R.plurals.tts_meters, R.string.tts_one_meter);
    }

    private static String getDistance(float farDistance, int nearDistance,
            float farFarAway, float farNearAway, int nearFarAway,
            int farId, int farOneId, int nearId, int nearOneId) {
        if (farDistance >= farFarAway) {
            // example: "5 kilometers" - always without decimal digits
            final int quantity = Math.round(farDistance);
            if (quantity == 1) {
                return getString(farOneId, quantity, String.valueOf(quantity));
            }
            return getQuantityString(farId, quantity, String.valueOf(quantity));
        }
        if (farDistance >= farNearAway) {
            // example: "2.2 kilometers" - decimals if necessary
            final float precision1 = Math.round(farDistance * 10.0f) / 10.0f;
            final float precision0 = Math.round(farDistance);
            if (Math.abs(precision1 - precision0) < 0.0001) {
                // this is an int - e.g. 2 kilometers
                final int quantity = (int) precision0;
                if (quantity == 1) {
                    return getString(farOneId, quantity, String.valueOf(quantity));
                }
                return getQuantityString(farId, quantity, String.valueOf(quantity));
            }
            // this is no int - e.g. 1.7 kilometers
            final String digits = String.format(Locale.getDefault(), "%.1f", farDistance);
            // always use the plural (9 leads to plural)
            return getQuantityString(farId, 9, digits);
        }
        // example: "34 meters"
        int quantity = nearDistance;
        if (quantity > nearFarAway) {
            // example: "120 meters" - rounded to 10 meters
            quantity = (int) Math.round(quantity / 10.0) * 10;
        }
        if (quantity == 1) {
            return getString(nearOneId, quantity, String.valueOf(quantity));
        }
        return getQuantityString(nearId, quantity, String.valueOf(quantity));
    }

    private static String getString(int resourceId, Object... formatArgs) {
        return CgeoApplication.getInstance().getString(resourceId, formatArgs);
    }

    private static String getQuantityString(int resourceId, int quantity, Object... formatArgs) {
        return CgeoApplication.getInstance().getResources().getQuantityString(resourceId, quantity, formatArgs);
    }

    private static String getDirection(Geopoint position, Geopoint target, float direction) {
        final int bearing = (int) position.bearingTo(target);
        final int degrees = (int) AngleUtils.normalize(bearing - direction);

        int hours = (degrees + 15) / 30;
        if (hours == 0) {
            hours = 12;
        }
        if (hours == 1) {
            return getString(R.string.tts_one_oclock, String.valueOf(hours));
        }
        return getString(R.string.tts_oclock, String.valueOf(hours));
    }
}