пятница, 11 мая 2012 г.

Метод Монте-Карло: вычисление площади фигуры, ограниченной неравенствами

Я опишу решение задачи численного поиска площади фигуры, ограниченной системой неравенств.  Задача решена в учебных целях. Программа написана на Java с использованием Swing в среде NetBeans. Исходные данные следующие (приведены для примера). Есть система неравенств, ограничивающих фигуру:
Есть координаты точки, принадлежащей этой фигуре: x=0,75; y=1,25.
Требуется найти площадь фигуры. Графически условия задачи выглядят так:
В проекте я использовал библиотеки:
commons-math3-3.0.jar
commons-lang-2.6.jar
jfreechart-1.0.14.jar
jcommon-1.0.17.jar
orson-0.5.0.jar
и глобальную библиотеку JDK 1.7.
Я создал класс Point, который содержит координаты точки и признак принадлежности фигуре:
package mmc;

public class Point {
        private double x;
        private double y;
        private boolean inside;

        public Point(double x, double y) {
            this.x = x;
            this.y = y;
        }

        public double getX() {
            return x;
        }

        public void setX(double x) {
            this.x = x;
        }

        public double getY() {
            return y;
        }

        public void setY(double y) {
            this.y = y;
        }

        public boolean getInside() {
            return inside;
        }

        public void setInside(boolean inside) {
            this.inside = inside;
        }
}
Затем я создал класс MethodMonteCarlo с классами, содержащими четыре неравенства f0, f1, f2 и f3 (Вы можете заменить и произвольно расширить перечень неравенств):
package mmc;

import java.util.ArrayList;
import java.util.List;
import org.apache.commons.math3.analysis.UnivariateFunction;
import org.apache.commons.math3.stat.StatUtils;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.math3.random.MersenneTwister;

public class MethodMonteCarlo {

    public static double COEFF = 0.05;
    private Point insidePoint, leftBottom, rightTop;
    private MersenneTwister generator;
    private double percent;

    private interface Func extends UnivariateFunction {
        boolean contains(Point p);
    }

    private static class f0 implements Func {
        public boolean contains(Point p) {
            return p.getY() - value(p.getX()) > 0;
        }
        public double value(double x) {
            return Math.pow(x, 2)+0.1;
        }
    }

    private static class f1 implements Func {
        public boolean contains(Point p) {
            return p.getY() - value(p.getX()) < 0;
        }
        public double value(double x) {
            return 8 * Math.pow(x, 2);
        }
    }

    private static class f2 implements Func {
        public boolean contains(Point p) {
            return p.getY() - value(p.getX()) < 0;
        }
        public double value(double x) {
            return -2 * x + 3;
        }
    }

    private static class f3 implements Func {
        public boolean contains(Point p) {
            return p.getY() - value(p.getX()) > 0;
        }
        public double value(double x) {
            return -10 * x + 5;
        }
    }

    private Func[] func;

    public MethodMonteCarlo(Point insidePoint) {
        percent = 100d;
        generator = new MersenneTwister();
        func = new Func[] { new f0(), new f1(), new f2(), new f3() };
        this.insidePoint = insidePoint;
        leftBottom = new Point(insidePoint.getX() - 0.1, insidePoint.getY() - 0.1);
        rightTop = new Point(insidePoint.getX() + 0.1, insidePoint.getY() + 0.1);
    }

    public Point getInsidePoint() {
        return insidePoint;
    }

    public Point getLeftBottom() {
        return leftBottom;
    }

    public Point getRightTop() {
        return rightTop;
    }

    private double min(Double[] d) {
        return d.length > 0 ? StatUtils.min(ArrayUtils.toPrimitive(d)) : Double.MAX_VALUE;
    }

    private double max(Double[] d) {
        return d.length > 0 ? StatUtils.max(ArrayUtils.toPrimitive(d)) : -Double.MAX_VALUE;
    }

    public double getPercent() {
        return percent;
    }

    private Double[] toDoubleArray(Point[] points, boolean inside, boolean xAxis) {
        List d = new ArrayList();
        for (int i = 0; i < points.length; i++)
            if ((inside && points[i].getInside()) || (!inside && !points[i].getInside()))
                d.add(xAxis ? points[i].getX() : points[i].getY());
        return d.toArray(new Double[0]);
    }

    public boolean checkLeft(Point[] points) {
        double minInside = min(toDoubleArray(points, true, true));
        double minOutside = min(toDoubleArray(points, false, true));
        double maxInside = max(toDoubleArray(points, true, true));
        double width = maxInside - minInside;
        return minInside - minOutside > COEFF * width;
    }

    public boolean checkRight(Point[] points) {
        double maxInside = max(toDoubleArray(points, true, true));
        double maxOutside = max(toDoubleArray(points, false, true));
        double minInside = min(toDoubleArray(points, true, true));
        double width = maxInside - minInside;
        return maxOutside - maxInside > COEFF * width;
    }

    public boolean checkBottom(Point[] points) {
        double minInside = min(toDoubleArray(points, true, false));
        double minOutside = min(toDoubleArray(points, false, false));
        double maxInside = max(toDoubleArray(points, true, false));
        double height = maxInside - minInside;
        return minInside - minOutside > COEFF * height;
    }

    public boolean checkTop(Point[] points) {
        double maxInside = max(toDoubleArray(points, true, false));
        double maxOutside = max(toDoubleArray(points, false, false));
        double minInside = min(toDoubleArray(points, true, false));
        double height = maxInside - minInside;
        return  maxOutside - maxInside > COEFF * height;
    }

    private boolean contains(int index, Point p) {
        return func[index].contains(p);
    }

    private boolean contains(Point p) {
        boolean result = true;
        for (int i = 0; i < func.length; i++) {
            if (!contains(i, p)) {
                result = false;
                break;
            }
        }
        p.setInside(result);
        return result;
    }

    public Point[] run() {
        int count = 10000;
        double insidePoints = 0;
        Point[] points = new Point[count];
        for (int i = 0; i < count; i++) {
            double x = leftBottom.getX() + generator.nextDouble() * (rightTop.getX() - leftBottom.getX());
            double y = leftBottom.getY() + generator.nextDouble() * (rightTop.getY() - leftBottom.getY());
            points[i] = new Point(x, y);
            if (contains(points[i]))
                insidePoints++;
        }
        percent = 100d * (insidePoints / count);
        return points;
    }

}
Здесь в методе run() используется генератор псевдослучайных чисел generator.nextDouble().
В этом методе я использовал генерирование 10 тысяч точек. Вы можете выбрать любое количество.
И затем создал класс MainJFrame:
package mmc;

import org.jfree.data.xy.*;

public class MainJFrame extends javax.swing.JFrame {

    private MethodMonteCarlo methodMonteCarlo;

    public MainJFrame() {
        initComponents();
        methodMonteCarlo = new MethodMonteCarlo(new Point(0.75, 1.25));
        setLocationRelativeTo(null);
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    //                           
    ...
    }//                         

    private void quitJButtonActionPerformed(java.awt.event.ActionEvent evt) {                                            
        System.exit(0);
    }                                           

    private void appendString(final String s) {
        new Thread(new Runnable(){
            public void run() {
                javax.swing.SwingUtilities.invokeLater(new Runnable(){
                    public void run() {
                        jTextArea1.append(s);
                    }
                });
            }
        }).start();
    }

    private void runJButtonActionPerformed(java.awt.event.ActionEvent evt) {                                           
        double coeff = 1 + MethodMonteCarlo.COEFF;
        Point[] points;
        Point insidePoint = methodMonteCarlo.getInsidePoint();
        boolean left, right, bottom, top;
        Point leftBottom = methodMonteCarlo.getLeftBottom();
        Point rightTop = methodMonteCarlo.getRightTop();
        do {
            points = methodMonteCarlo.run();
            appendString(String.format("Доля точек в области = %.2f%% \n", methodMonteCarlo.getPercent()));
            left = methodMonteCarlo.checkLeft(points);
            right = methodMonteCarlo.checkRight(points);
            bottom = methodMonteCarlo.checkBottom(points);
            top = methodMonteCarlo.checkTop(points);
            if (!left) {
                leftBottom.setX(insidePoint.getX() - coeff * (insidePoint.getX() - leftBottom.getX()));
                appendString(String.format("Мин. X = %.2f \n", leftBottom.getX()));
            }
            if (!right) {
                rightTop.setX(insidePoint.getX() + coeff * (rightTop.getX() - insidePoint.getX()));
                appendString(String.format("Макс. X = %.2f \n", rightTop.getX()));
            }
            if (!bottom) {
                leftBottom.setY(insidePoint.getY() - coeff * (insidePoint.getY() - leftBottom.getY()));
                appendString(String.format("Мин. Y = %.2f \n", leftBottom.getY()));
            }
            if (!top) {
                rightTop.setY(insidePoint.getY() + coeff * (rightTop.getY() - insidePoint.getY()));
                appendString(String.format("Макс. Y = %.2f \n", rightTop.getY()));
            }
        } while (!left || !right || !bottom || !top);
        drawChart(points);
        double area = methodMonteCarlo.getPercent() * (rightTop.getX() - leftBottom.getX()) * (rightTop.getY() - leftBottom.getY());
        appendString(String.format("Площадь = %.2f \n", area));
    }                                          

    private void drawChart(Point[] points) {
        final XYSeries series0 = new XYSeries("outer");
        final XYSeries series1 = new XYSeries("inner");
        for (int i = 0; i < points.length; i++) {
            if (points[i].getInside()) {
                series1.add(points[i].getX(), points[i].getY());
            } else {
                series0.add(points[i].getX(), points[i].getY());
            }
        }
        final XYSeriesCollection dataset = new XYSeriesCollection();
        dataset.addSeries(series0);
        dataset.addSeries(series1);
        scatterChart.setDataset(dataset);
    }
}
с соответствующей формой:
И главный класс Main:
/*
 * Метод Монте-Карло
 */

package mmc;

import javax.swing.UIManager;

public class Main {

    public static void main(String[] args) {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
            e.printStackTrace();
        }
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new MainJFrame().setVisible(true);
            }
        });
    }

}
В результате, при запуске приложения открывается окно:
При нажатии кнопки Расчёт запускается итерация, на каждом шаге которой каждая граница (верхняя, нижняя, левая, правая), начиная с +/-0,1 (в абсолютных единицах) относительно заданной внутренней точки, расширяется на 5% (в 1,05 раза) до тех пор, пока отношение ширины (высоты) расширяемой области по отношению к ширине (высоте) интервала не достигнет 105% (это определено константой public static double COEFF = 0.05):
Здесь видно, какую площадь занимает фигура: 51,40.
Площадь вычисляется так:
(количество попавших в фигуру точек / количество точек в прямоугольнике) * (площадь описывающего прямоугольника).
Исходный код этого проекта доступен по ссылке mmc.zip. Для скачивания файла после перехода по ссылке выберите пункт меню Файл > Загрузить.
Вы можете использовать этот код свободно, без каких-либо лицензионных ограничений!

среда, 29 февраля 2012 г.

PDF из карты GeoServer с русским текстом через Mapfish print create.json

Раньше я публиковал PDF из карты GeoServer с русским текстом, как сделать русский текст в PDF через print.pdf. А теперь потребовалось сделать тоже самое через create.json. Причём HTTP POST запрос формируется на сервере приложения, и всё описанное уже не работает! Теперь нужно всё делать заново. У нас разрабатывается приложение, которое на стороне клиента создаёт бин printBean. Выглядит это так:
Все данные поступают на сервер. Там формируется POST запрос к геосерверу через create.json. Все строки передаются в UTF-8. Но модуль печати по-умолчанию применяет ANSI с кодировкой Cp1252! В предыдущем посте описано, что мы используем на геосервере TTF с кириллицей. Так что можно обмануть плагин, чтобы не лезть в его исходники и ничего не менять.
Перед передачей json на геосервер я применил для параметров, содержащих русский текст, следующую функцию
    private static String encodeToAnsi(String source) {
        return new String(source.getBytes(Charset.forName("cp1251")), Charset.forName("cp1252"));
    }
Всё просто. При формировании json вызывается эта функция в нужных местах:
JSONObject spec = new JSONObject();
spec.put("header", encodeToAnsi(printBean.getHeader()));
spec.put("footer", encodeToAnsi(printBean.getFooter()));
И вот вам результат:
Текст отображается кириллицей! Несмотря на то, что послан был в кодировке Cp1252:
{"footer": "Íèæíèé",
 "layout": "A4 portrait",
 "pages": [{"bbox": [3815,6450,7278,8756]}],
 "layers": [{
  "layers": ["egko","troad_direction"],
  "opacity": 1,
  "baseURL": "http://localhost:8080/geoserver/wms",
  "styles": ["",""],
  "singleTile": true,
  "format": "image/png",
  "type": "WMS"
 }],
 "srs": "EPSG:60000",
 "dpi": 100,
 "header": "Âåðõíèé",
 "units": "m"
}

пятница, 20 января 2012 г.

Реанимация плагина Adobe SVG Viewer в браузере Internet Explorer 9

Одно из моих давно разработанных на Дельфи приложений работает с SVG с помощью давно снятого с сопровождения плагина Adobe SVG Viewer. Раньше использовалить браузеры IE 7 и IE 8. А после обновления до IE 9 плагин перестал запускаться. Но приложение удалось оживить! Браузер при обновлении внёс изменения в реестр Windows. Для исправления в ветке реестра
[HKEY_CLASSES_ROOT\MIME\Database\Content Type\image/svg+xml]
потребовалось изменить значение параметра:
"CLSID"="{377B5106-3B4E-4A2D-8520-8767590CAC86}"
После этого всё заработало: