Погодник на java для начинающих и постарше

в 6:18, , рубрики: api, java, XML, Песочница, яндекс, метки: , , ,

image

Приветствую всех в этот прекрасный день ожидания праздника, это моя первая статья на хабре, в которой я хотел бы рассказа про открытый API погоды Яндекса. Статья является продолжением серии Java для начинающих. Следует отметить, статья рассчитана на тех, кто недавно начал изучения язык или на тех, кто не знаком с данным сервисом, но в любом случае, рад я буду любым читателям (эх тавтология… ). Яндекс дает неплохую возможность для разработчиков, которым требуется в своей программе или на своем сайте разместить погоду, причем информации, которую Яндекс предоставляет более, чем достаточно.

Погоду Вы сможете выбрать за любой день на неделю вперед. Различные состояния (ясно, пасмурно и т. д.), множество языков (для городов, например русский и английский, для состояний все языки стран СНГ и не только: ясно, аяз, açık, ашық и т… д.), я не очень хорош в географии, но, кажется, информация там для всех стран, даже есть миниатюрные картинки состояния погоды, но самое главное, почему я выбрал этот сервис — простая и понятная структура. Сразу оговорюсь, за «рекламу» мне не платили.

Класс с внешним представлением, думаю описывать не надо, я лишь приведу код, чтобы потом не было несостыковок. Рекомендую даже не списывать, а сделать свой, ибо мой убогий:

public WFrame() {
		today = null;
		

		GridBagLayout layout = new GridBagLayout(); // выбрал его, потому что раньше с ним не работал,  вот и экперементирую
		setTitle("Weather");
		setPreferredSize(new Dimension(350, 300));
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setLocationRelativeTo(null);
		setVisible(true);
		contentPane = getContentPane();

		panel = new JPanel();
		panel.setLayout(layout);
		idField = new JtextField();
		idField.setColumns(6);


		// парсим содержимое xml, вытаскиваем от туда необходимые данные
		// заполняем метки
		btn = new JButton("Хочу все знать");
		
		// расположение элементов, в GUI я не силен, критику приму 
		label = new JLabel("Наберите в поле ID вашего города");
		label.setFont(new Font("Verdana", Font.PLAIN, 14));
		
		tLabel = new JLabel("Погода завтра");
		tLabel.setFont(new Font("Verdana", Font.PLAIN, 14));

		iLabel = new JLabel();

		panel.add(label,
				new GBC(0, 0, 2, 1).setFill(GBC.NORTH).setWeight(100, 0));
		panel.add(iLabel,
				new GBC(0, 1, 2, 1).setFill(GBC.NORTH).setWeight(100, 0));
				
		panel.add(btn, new GBC(0, 3, 2, 1).setFill(GBC.NORTH).setWeight(100, 0));
		
		panel.add(idField,
				new GBC(0, 2, 2, 1).setAnchor(GBC.NORTH)
						.setFill(GBC.HORIZONTAL).setIpad(50, 0));
		
		panel.add(tLabel,
				new GBC(0, 4, 2, 1).setAnchor(GBC.NORTH)
						.setFill(GBC.HORIZONTAL).setIpad(50, 0));
		add(panel);
		
		panel.setComponentPopupMenu(menu);
		pack();

	}

В idField мы будем записывать ID нашего города (список городов и государств можете найти здесь weather.yandex.ru/static/cities.xml). Ну а в лэйблы — саму информацию. Критику я воспринимаю нормально, буду рад если сделаете замечания или поправки. Класс GBC – наследник GridBagConstraints, его код я приведу в конце поста.

Еще я бы добавил для удобства всплывающее меню, чтобы айди города каждый раз не набирать:

JPopupMenu menu = new JPopupMenu();
		menu.add(new AbstractAction("Пенза") {

			@Override
			public void actionPerformed(ActionEvent e) {
				setCity("27962");
			}
		});

Естественно добавить нужно не один город, но мне пока хватает Пензы. Для добавления городов в меню лучше создать отдельный класс, но это другая тема, я с этим возиться не стал.

Следующий класс, который я создал — класс Weather для хранения переменных с информацией о погоде. Тут ничего сложного:

public class Weather {
	String city;
	String weatherType;
	String imgType;
	String humidity;// Влажность
	String tom, tomNight;
	int temperature;

	public String toString() {
		return "Weather[city= " + city + ", weatherType=" + weatherType
				+ ", temperature= " + temperature + ",humidity= " + humidity
				+ "]";
	}

}

Далее создаем документ для парсинга


			Document doc = null;
			URL url = new URL("http://export.yandex.ru/weather-ng/forecasts/"
					+ cityID + ".xml");
			//попробуйте пройти по ссылке и посмотреть, что именно мы парсим
			// да и вообще саму структуру							
			URLConnection uc = url.openConnection();
			InputStream is = uc.getInputStream();//создали поток
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder();
			doc = db.parse(is);//непосредственно парсинг
			doc.getDocumentElement().normalize();

Связываем переменную класса NodeList с тегом forecast (см. export.yandex.ru/weather-ng/forecasts/29642.xml — страница с погодой)

nl = doc.getElementsByTagName("forecast").item(0).getChildNodes();

Т.е. получилось у нас что-то типа:

try {
			Document doc = null;
			URL url = new URL("http://export.yandex.ru/weather-ng/forecasts/"
					+ cityID + ".xml");
			//попробуйте пройти по ссылке и посмотреть, что именно мы парсим
			// да и вообще саму структуру							
			URLConnection uc = url.openConnection();
			InputStream is = uc.getInputStream();//создали поток
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder();
			doc = db.parse(is);//непосредственно парсинг
			doc.getDocumentElement().normalize();

			nl = doc.getElementsByTagName("forecast").item(0).getChildNodes();
			//из этого родителя нам и нужно содердимое
		} catch (Exception ex) {
			JOptionPane.showMessageDialog(null,
					"Ошибка при запросе к Яндекс АПИ");
			ex.printStackTrace();
		} 

Что ж связали и разобрали, пора извлекать непосредственно погоду. Как видим у forecast есть наследник fact, а в нем в свою очередь — station и temperature (в названии города иногда указывается аэропорт). Теперь из station и temperature вытащим погоду и название города:

for (int i = 0; i < nl.getLength(); i++) {
			Node child = nl.item(i);

			if (child instanceof Element) {
				if (child.getNodeName().equals("fact")) {
					Node childOfChild = null;
					for (int j = 0; j < child.getChildNodes().getLength(); j++) {
						childOfChild = child.getChildNodes().item(j);

						if ("station".equals(childOfChild.getNodeName())) {
							todayWeather.city = childOfChild.getTextContent();
						}
						if ("temperature".equals(childOfChild.getNodeName()))
							todayWeather.temperature = Integer
									.parseInt(childOfChild.getTextContent());
						if ("weather_type_short".equals(childOfChild
								.getNodeName())) {
							todayWeather.weatherType = childOfChild
									.getTextContent();
						}
						if ("image".equals(childOfChild.getNodeName())) {
							todayWeather.imgType = childOfChild
									.getTextContent();
						}
						if ("humidity".equals(childOfChild.getNodeName())) {
							todayWeather.humidity = childOfChild
									.getTextContent();
						}

					}
				}
				
				if (child.getNodeName().equals("informer")) {
	    			Node childOfChild = null;
					for (int j =0; j<child.getChildNodes().getLength(); j++){
						childOfChild = child.getChildNodes().item(j);
	    				if ((childOfChild.getNodeName().equals("temperature")) && 
						(childOfChild.getAttributes().item(1).getTextContent().equals("night")))
	    				{ todayWeather.tomNight = childOfChild.getTextContent(); }
						
						if (childOfChild.getNodeName().equals("temperature") && 
								(childOfChild.getAttributes().item(1).getTextContent().equals("tomorrow")))
	    				{ todayWeather.tom = childOfChild.getTextContent();}
						
					}
	    		}

			}
		}
		WFrame.today = todayWeather;

Циклом for перебираем всех детей и выбираем лишь нужных нам, как видите я еще выбрал влажность (humidity) и тип картинки. В остальном думаю все предельно понятно.

Осталось лишь написать обработчик события для кнопки:

btn.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				try {
					String cityID = idField.getText();
					if (cityID == null)
						throw new IOException("Не введен ID города");
					WeatherParser.parse(cityID); // процесс присваивания переменным необходимыми значениями
					label.setText(today.city + "(сейчас): " + today.temperature
							+ " С; " + today.weatherType + " Влж:"
							+ today.humidity + " %");

					iLabel = new JLabel(new ImageIcon(ImageIO.read(new URL(
							"http://img.yandex.net/i/wiz" + today.imgType
									+ ".png"))));
					tLabel.setText("Погода завтра ночь/день: "+ today.tomNight+"/"+ today.tom);
					//некорректные конечно названия переменных
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				contentPane.add(iLabel);

			}
		});

В заключении хотелось бы попросить прощения за быдлокодинг в некоторых местах, буду рад если ткнете носом в таковой. Еще одна проблема, которая возникла у меня и которую я так и не решил — отображение картинки, которая появляется лишь если растянуть форму. Так что готов выслушать любую обоснованную критику.

Всем спасибо за внимание, удачного дня!

Код полностью:

1. Класс с фреймом

<code>
public class WFrame extends JFrame {
	static Weather today;
	JPanel panel;
	JTextField idField;
	JButton btn;
	JLabel label;
	JLabel tLabel;
	JLabel iLabel;
	Box b;
	static String id;
	static int t;
	Container contentPane;

	public WFrame() {
		today = null;
		JPopupMenu menu = new JPopupMenu();
		menu.add(new AbstractAction("Пенза") {

			@Override
			public void actionPerformed(ActionEvent e) {
				setCity("27962");
			}
		});
		//несомненно, можно добавить любой город, список которых можно найти здесь
		// http://weather.yandex.ru/static/cities.xml
		

		GridBagLayout layout = new GridBagLayout(); // выбрал его, потому что раньше с ним не работал,  вот и экперементирую
		setTitle("Weather");
		setPreferredSize(new Dimension(350, 300));
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setLocationRelativeTo(null);
		setVisible(true);
		contentPane = getContentPane();

		panel = new JPanel();
		panel.setLayout(layout);
		idField = new JTextField();
		idField.setColumns(6);
		idField.addKeyListener(new KeyListener() {

			@Override
			public void keyTyped(KeyEvent arg0) {
			}

			@Override
			public void keyReleased(KeyEvent e) {
				if (e.getKeyCode() == KeyEvent.VK_ENTER) {
					btn.doClick();
				}
			}

			@Override
			public void keyPressed(KeyEvent arg0) {
			}
		});

		// парсим содержимое xml, вытаскиваем от туда необходимые данные
		// заполняем метки
		btn = new JButton("Хочу все знать");
		btn.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				try {
					String cityID = idField.getText();
					if (cityID == null)
						throw new IOException("Не введен ID города");
					WeatherParser.parse(cityID); // процесс присваивания переменным необходимыми значениями
					label.setText(today.city + "(сейчас): " + today.temperature
							+ " С; " + today.weatherType + " Влж:"
							+ today.humidity + " %");

					iLabel = new JLabel(new ImageIcon(ImageIO.read(new URL(
							"http://img.yandex.net/i/wiz" + today.imgType
									+ ".png"))));
					tLabel.setText("Погода завтра ночь/день: "+ today.tomNight+"/"+ today.tom);
					//некорректные конечно названия переменных
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				contentPane.add(iLabel);

			}
		});

		// расположение элементов, в GUI я не силен, критику приму 
		label = new JLabel("Наберите в поле ID вашего города");
		label.setFont(new Font("Verdana", Font.PLAIN, 14));
		
		tLabel = new JLabel("Погода завтра");
		tLabel.setFont(new Font("Verdana", Font.PLAIN, 14));

		iLabel = new JLabel();

		panel.add(label,
				new GBC(0, 0, 2, 1).setFill(GBC.NORTH).setWeight(100, 0));
		panel.add(iLabel,
				new GBC(0, 1, 2, 1).setFill(GBC.NORTH).setWeight(100, 0));
				
		panel.add(btn, new GBC(0, 3, 2, 1).setFill(GBC.NORTH).setWeight(100, 0));
		
		panel.add(idField,
				new GBC(0, 2, 2, 1).setAnchor(GBC.NORTH)
						.setFill(GBC.HORIZONTAL).setIpad(50, 0));
		
		panel.add(tLabel,
				new GBC(0, 4, 2, 1).setAnchor(GBC.NORTH)
						.setFill(GBC.HORIZONTAL).setIpad(50, 0));
		add(panel);
		
		panel.setComponentPopupMenu(menu);
		pack();

	}

	public static void main(String[] args) {
		// loginWithProxy();
		javax.swing.SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				WFrame frame = new WFrame();
				frame.setDefaultLookAndFeelDecorated(true);
			}
		});
	}

	public void setCity(String id) {
		this.id = id;
		idField.setText(id);
	}

}

2. Класс Weather

public class Weather {
	String city;
	String weatherType;
	String imgType;
	String humidity;// Влажность
	String tom, tomNight;
	int temperature;

	public String toString() {
		return "Weather[city= " + city + ", weatherType=" + weatherType
				+ ", temperature= " + temperature + ",humidity= " + humidity
				+ "]";
	}

}

3. Класс парсинга:

public class WeatherParser {

	static Weather tomorrowWeather;

	WeatherParser(WFrame f) {

		tomorrowWeather = new Weather();
	}

	public static void parse(String cityID) {
		Weather todayWeather = new Weather();
		NodeList nl = null;
		try {
			Document doc = null;
			URL url = new URL("http://export.yandex.ru/weather-ng/forecasts/"
					+ cityID + ".xml");
			//попробуйте пройти по ссылке и посмотреть, что именно мы парсим
			// да и вообще саму структуру							
			URLConnection uc = url.openConnection();
			InputStream is = uc.getInputStream();//создали поток
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder();
			doc = db.parse(is);//непосредственно парсинг
			doc.getDocumentElement().normalize();

			nl = doc.getElementsByTagName("forecast").item(0).getChildNodes();
			//из этого родителя нам и нужно содердимое
		} catch (Exception ex) {
			JOptionPane.showMessageDialog(null,
					"Ошибка при запросе к Яндекс АПИ");
			ex.printStackTrace();
		}

		for (int i = 0; i < nl.getLength(); i++) {
			Node child = nl.item(i);

			if (child instanceof Element) {
				if (child.getNodeName().equals("fact")) {
					Node childOfChild = null;
					for (int j = 0; j < child.getChildNodes().getLength(); j++) {
						childOfChild = child.getChildNodes().item(j);

						if ("station".equals(childOfChild.getNodeName())) {
							todayWeather.city = childOfChild.getTextContent();
						}
						if ("temperature".equals(childOfChild.getNodeName()))
							todayWeather.temperature = Integer
									.parseInt(childOfChild.getTextContent());
						if ("weather_type_short".equals(childOfChild
								.getNodeName())) {
							todayWeather.weatherType = childOfChild
									.getTextContent();
						}
						if ("image".equals(childOfChild.getNodeName())) {
							todayWeather.imgType = childOfChild
									.getTextContent();
						}
						if ("humidity".equals(childOfChild.getNodeName())) {
							todayWeather.humidity = childOfChild
									.getTextContent();
						}

					}
				}
				
				if (child.getNodeName().equals("informer")) {
	    			Node childOfChild = null;
					for (int j =0; j<child.getChildNodes().getLength(); j++){
						childOfChild = child.getChildNodes().item(j);
	    				if ((childOfChild.getNodeName().equals("temperature")) && 
						(childOfChild.getAttributes().item(1).getTextContent().equals("night")))
	    				{ todayWeather.tomNight = childOfChild.getTextContent(); }
						
						if (childOfChild.getNodeName().equals("temperature") && 
								(childOfChild.getAttributes().item(1).getTextContent().equals("tomorrow")))
	    				{ todayWeather.tom = childOfChild.getTextContent();}
						
					}
	    		}


			}
		}
		WFrame.today = todayWeather;

	}

}

4. Ну и класс GBC

import java.awt.GridBagConstraints;

public class GBC extends GridBagConstraints
{

    public GBC(int gridx, int gridy)
    {
       this.gridx = gridx;
       this.gridy = gridy;
    }

    public GBC(int gridx, int gridy, int gridwidth, int gridheight)
    {
       this.gridx = gridx;
       this.gridy = gridy;
       this.gridwidth = gridwidth;
       this.gridheight = gridheight;
    }


    public GBC setAnchor(int anchor)
    {
       this.anchor = anchor;
       return this;
    }

    public GBC setFill(int fill)
    {
       this.fill = fill;
       return this;
    }


    public GBC setWeight(double weightx, double weighty)
    {
       this.weightx = weightx;
       this.weighty = weighty;
       return this;
    }


    public GBC setInsets(int distance)
    {
       this.insets = new java.awt.Insets(
             distance, distance, distance, distance);
       return this;
    }


    public GBC setInsets(int top, int left, int bottom, int right)
    {
       this.insets = new java.awt.Insets(
          top, left, bottom, right);
       return this;
    }


    public GBC setIpad(int ipadx, int ipady)
    {
       this.ipadx = ipadx;
       this.ipady = ipady;
       return this;
    }
}

Автор: vlad0058

Источник

  1. Владислав:

    Оу, моя статейка с хабра, спасибо

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js